#![ cfg_attr( doc, doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "readme.md" ) ) ) ]
#![ cfg_attr( not( feature = "enabled" ), allow( unused ) ) ]
#![ warn( missing_docs ) ]
pub const COMMANDS_YAML : &str = concat!( env!( "CARGO_MANIFEST_DIR" ), "/unilang.commands.yaml" );
pub mod paths;
pub mod token;
pub mod account;
pub mod persist;
#[ cfg( feature = "enabled" ) ]
pub mod adapter;
#[ cfg( feature = "enabled" ) ]
pub mod output;
#[ cfg( feature = "enabled" ) ]
pub mod commands;
#[ cfg( feature = "enabled" ) ]
pub mod usage;
pub use paths::ClaudePaths;
pub use persist::PersistPaths;
#[ cfg( feature = "enabled" ) ]
#[ inline ]
pub fn register_commands( registry : &mut unilang::registry::CommandRegistry )
{
use unilang::data::Kind;
use commands::
{
credentials_status_routine,
account_list_routine,
account_limits_routine,
account_status_routine,
account_save_routine,
account_switch_routine,
account_delete_routine,
token_status_routine,
paths_routine,
usage_routine,
};
let v = || reg_arg_opt( "verbosity", Kind::Integer );
let fmt = || reg_arg_opt( "format", Kind::String );
let dry = || reg_arg_opt( "dry", Kind::Boolean );
let nam = || reg_arg_opt( "name", Kind::String );
let thr = || reg_arg_opt( "threshold", Kind::Integer );
reg_cmd( registry, ".credentials.status", "Show live credential metadata without account store dependency", vec![ v(), fmt() ], Box::new( credentials_status_routine ) );
reg_cmd( registry, ".account.list", "List all saved accounts with subscription type and token state", vec![ v(), fmt() ], Box::new( account_list_routine ) );
reg_cmd( registry, ".account.limits", "Show rate-limit utilization for the selected account (FR-18)", vec![ nam(), v(), fmt() ], Box::new( account_limits_routine ) );
reg_cmd( registry, ".account.status", "Show active account name and token state; optionally query a named account", vec![ nam(), v(), fmt() ], Box::new( account_status_routine ) );
reg_cmd( registry, ".account.save", "Save current credentials as a named account profile", vec![ nam(), dry() ], Box::new( account_save_routine ) );
reg_cmd( registry, ".account.switch", "Switch active account by name with atomic credential rotation", vec![ nam(), dry() ], Box::new( account_switch_routine ) );
reg_cmd( registry, ".account.delete", "Delete a saved account from the account store", vec![ nam(), dry() ], Box::new( account_delete_routine ) );
reg_cmd( registry, ".token.status", "Show active OAuth token expiry classification", vec![ v(), fmt(), thr() ], Box::new( token_status_routine ) );
reg_cmd( registry, ".paths", "Show all resolved ~/.claude/ canonical file paths", vec![ v(), fmt() ], Box::new( paths_routine ) );
reg_cmd( registry, ".usage", "Show 7-day token usage from stats-cache.json", vec![ v(), fmt() ], Box::new( usage_routine ) );
}
#[ cfg( feature = "enabled" ) ]
fn reg_arg_opt( name : &str, kind : unilang::data::Kind ) -> unilang::data::ArgumentDefinition
{
unilang::data::ArgumentDefinition::new( name, kind ).with_optional( None::< String > )
}
#[ cfg( feature = "enabled" ) ]
fn reg_cmd(
registry : &mut unilang::registry::CommandRegistry,
name : &str,
desc : &str,
args : Vec< unilang::data::ArgumentDefinition >,
routine : unilang::registry::CommandRoutine,
)
{
let def = unilang::data::CommandDefinition::former()
.name( name )
.description( desc )
.arguments( args )
.end();
registry
.command_add_runtime( &def, routine )
.expect( "internal error: failed to register command" );
}
#[ cfg( feature = "enabled" ) ]
mod cli
{
use crate::adapter::argv_to_unilang_tokens;
use crate::commands::dot_routine;
use unilang::data::{ CommandDefinition, ErrorCode };
use unilang::interpreter::{ ExecutionContext, Interpreter };
use unilang::parser::{ Parser, UnilangParserOptions };
use unilang::registry::CommandRegistry;
use unilang::semantic::SemanticAnalyzer;
pub( super ) fn exit_code_for( e : &unilang::error::Error ) -> i32
{
if let unilang::error::Error::Execution( ref data ) = e
{
match data.code
{
ErrorCode::InternalError | ErrorCode::CommandNotImplemented => 2,
_ => 1,
}
}
else
{
1
}
}
pub( super ) fn build_registry() -> CommandRegistry
{
let mut registry = CommandRegistry::new();
{
let def = CommandDefinition::former()
.name( "." )
.description( "Show help (alias for .help)" )
.arguments( vec![] )
.hidden_from_list( true )
.end();
registry
.command_add_runtime( &def, Box::new( dot_routine ) )
.expect( "internal error: failed to register ." );
}
crate::register_commands( &mut registry );
registry
}
pub( super ) fn print_usage( binary : &str )
{
println!( "Usage: {binary} [command] [key::value ...]" );
println!();
println!( "Manage Claude Code account credentials and token state." );
println!();
println!( "Commands:" );
println!( " .account.list [v::0-2] [format::text|json] List all saved accounts" );
println!( " .account.status [v::0-2] [format::text|json] Show active account and token state" );
println!( " .account.save name::STRING [dry::bool] Save current credentials as named account" );
println!( " .account.switch name::STRING [dry::bool] Switch active account" );
println!( " .account.delete name::STRING [dry::bool] Delete a saved account" );
println!( " .token.status [v::0-2] [format::text|json] Show OAuth token expiry status" );
println!( " .paths [v::0-2] [format::text|json] Show all ~/.claude/ canonical paths" );
println!( " .usage [v::0-2] [format::text|json] Show 7-day token usage summary" );
println!( " .credentials.status [v::0-2] [format::text|json] Show live credentials (no account store needed)" );
println!();
println!( "Options:" );
println!( " v::0-2 Verbosity level (default: 1)" );
println!( " format::text|json Output format (default: text)" );
println!( " dry::bool Preview without applying" );
println!( " name::STRING Account name" );
println!();
println!( "Examples:" );
println!( " {binary} .account.list" );
println!( " {binary} .account.list v::2" );
println!( " {binary} .account.switch name::work" );
println!( " {binary} .account.switch name::work dry::true" );
println!( " {binary} .token.status format::json" );
println!( " {binary} .paths v::2" );
println!( " {binary} .usage" );
println!( " {binary} .usage v::2" );
println!( " {binary} .credentials.status" );
}
pub( super ) fn run( binary : &str, argv : &[ String ] )
{
let ( tokens, needs_help ) = match argv_to_unilang_tokens( argv )
{
Ok( r ) => r,
Err( e ) =>
{
eprintln!( "Error: {e}" );
eprintln!( "Run '{binary} --help' for usage." );
std::process::exit( 1 );
}
};
if needs_help
{
print_usage( binary );
return;
}
let registry = build_registry();
let parser = Parser::new( UnilangParserOptions::default() );
let instruction = match parser.parse_from_argv( &tokens )
{
Ok( i ) => i,
Err( e ) =>
{
eprintln!( "Error: {e}" );
std::process::exit( 1 );
}
};
let instructions = [ instruction ];
let analyzer = SemanticAnalyzer::new( &instructions, ®istry );
let commands = match analyzer.analyze()
{
Ok( cmds ) => cmds,
Err( e ) =>
{
eprintln!( "Error: {e}" );
std::process::exit( exit_code_for( &e ) );
}
};
let interpreter = Interpreter::new( &commands, ®istry );
let mut context = ExecutionContext::default();
match interpreter.run( &mut context )
{
Ok( outputs ) =>
{
for out in outputs
{
print!( "{}", out.content );
}
}
Err( e ) =>
{
eprintln!( "Error: {e}" );
std::process::exit( exit_code_for( &e ) );
}
}
}
}
#[ cfg( feature = "enabled" ) ]
#[ inline ]
pub fn run_cli()
{
let binary = std::env::args()
.next()
.as_deref()
.and_then( | p | std::path::Path::new( p ).file_name() )
.and_then( | n | n.to_str() )
.unwrap_or( "clp" )
.to_owned();
let argv : Vec< String > = std::env::args().skip( 1 ).collect();
cli::run( &binary, &argv );
}