mod parse;
mod env;
mod execution;
mod cred_parse;
mod builder;
mod fence;
mod credential;
mod help;
mod gate;
mod ps;
mod kill;
use claude_runner_core::{ ClaudeCommand, EffortLevel, IsolatedModel };
use parse::CliArgs;
use cred_parse::{
parse_isolated_args, parse_refresh_args,
apply_isolated_env_vars, apply_refresh_env_vars,
};
pub use fence::strip_fences;
use credential::{ run_isolated_command, run_refresh_command };
use help::print_ask_help;
use gate::wait_for_session_slot;
pub( super ) use ps::dispatch_ps;
pub( super ) use kill::dispatch_kill;
pub( super ) use parse::parse_args;
pub( super ) use env::apply_env_vars;
pub( super ) use builder::build_claude_command;
pub( super ) use help::print_help;
pub( super ) fn handle_dry_run( builder : &ClaudeCommand )
{
let env = builder.describe_env();
let command = builder.describe();
if !env.is_empty() { println!( "{env}" ); }
println!( "{command}" );
}
const KNOWN_SUBCOMMANDS : &[ &str ] = &[ "run", "ask", "isolated", "refresh", "help", "ps", "kill" ];
pub( super ) fn guard_unknown_subcommand( tokens : &[ String ] )
{
if let Some( first ) = tokens.first()
{
let is_identifier = !first.starts_with( '-' )
&& !first.is_empty()
&& first.chars().all( | c | c.is_alphanumeric() || c == '_' || c == '-' );
if is_identifier
{
for &sub in KNOWN_SUBCOMMANDS
{
if first != sub
&& ( sub.starts_with( first.as_str() ) || first.starts_with( sub ) || is_close_typo( first, sub ) )
{
eprintln!(
"Error: unknown subcommand: {first}. Did you mean '{sub}'?\nRun with --help for usage."
);
std::process::exit( 1 );
}
}
}
}
}
fn is_close_typo( first : &str, sub : &str ) -> bool
{
if first.chars().next() != sub.chars().next() { return false; }
let a = first.as_bytes();
let b = sub.as_bytes();
let la = a.len();
let lb = b.len();
if la.abs_diff( lb ) > 1 { return false; }
if la == lb
{
return a.iter().zip( b.iter() ).filter( |( x, y )| x != y ).count() == 1;
}
let ( longer, shorter ) = if la > lb { ( a, b ) } else { ( b, a ) };
let mut i = 0;
let mut j = 0;
let mut skipped = false;
while i < longer.len() && j < shorter.len()
{
if longer[ i ] == shorter[ j ] { i += 1; j += 1; }
else if skipped { return false; }
else { skipped = true; i += 1; }
}
true
}
pub( super ) fn run_built_command( builder : &ClaudeCommand, cli : &CliArgs )
{
let verbosity = cli.verbosity.unwrap_or_default();
let max_sessions = cli.max_sessions.unwrap_or( 30 );
wait_for_session_slot( max_sessions, verbosity );
if cli.trace || verbosity.shows_verbose_detail()
{
let env = builder.describe_env();
let command = builder.describe();
let mut preview = String::new();
if !env.is_empty() { preview.push_str( &env ); preview.push( '\n' ); }
preview.push_str( &command );
eprintln!( "{preview}" );
}
if cli.print_mode || ( cli.message.is_some() && !cli.interactive )
{
execution::run_print_mode( builder, cli );
}
else
{
execution::run_interactive( builder, cli );
}
}
pub( super ) fn dispatch_run( tokens : &[ String ] ) -> !
{
let mut cli = match parse_args( tokens )
{
Ok( c ) => c,
Err( e ) => { eprintln!( "Error: {e}" ); std::process::exit( 1 ); }
};
if let Err( e ) = apply_env_vars( &mut cli )
{
eprintln!( "Error: {e}" );
std::process::exit( 1 );
}
if cli.help
{
print_help();
std::process::exit( 0 );
}
if cli.print_mode && cli.message.is_none()
{
eprintln!( "Error: --print requires a message argument" );
eprintln!( "Run with --help for usage." );
std::process::exit( 1 );
}
let builder = build_claude_command( &cli );
{
let verbosity_for_warning = cli.verbosity.unwrap_or_default();
if cli.keep_claudecode
&& verbosity_for_warning.shows_warnings()
&& std::env::var( "CLAUDECODE" ).is_ok()
{
eprintln!(
"Warning: --keep-claudecode is set and CLAUDECODE is present in environment; \
child claude will run in nested-agent mode"
);
}
}
if cli.dry_run
{
handle_dry_run( &builder );
std::process::exit( 0 );
}
run_built_command( &builder, &cli );
std::process::exit( 0 );
}
pub( super ) fn dispatch_ask( tokens : &[ String ] ) -> !
{
if tokens.iter().skip( 1 ).any( | t | t == "--help" || t == "-h" )
{
print_ask_help();
}
if tokens.get( 1 ).map( String::as_str ) == Some( "help" )
{
print_ask_help();
}
dispatch_run( &tokens[ 1 .. ] );
}
pub( super ) fn dispatch_isolated( tokens : &[ String ] ) -> !
{
let mut cli = match parse_isolated_args( &tokens[ 1 .. ] )
{
Ok( c ) => c,
Err( e ) => { eprintln!( "Error: {e}" ); std::process::exit( 1 ); }
};
apply_isolated_env_vars( &mut cli );
if cli.creds_path.is_empty()
{
eprintln!( "Error: cannot resolve credentials path: HOME is not set; provide --creds or set CLR_CREDS\nRun with --help for usage." );
std::process::exit( 1 );
}
run_isolated_command(
"isolated",
&cli.creds_path,
cli.timeout_secs,
cli.trace,
IsolatedModel::Default,
EffortLevel::Max,
cli.message.as_deref(),
&cli.passthrough_args,
cli.message.is_some(), false, )
}
pub( super ) fn dispatch_refresh( tokens : &[ String ] ) -> !
{
let mut cli = match parse_refresh_args( &tokens[ 1 .. ] )
{
Ok( c ) => c,
Err( e ) => { eprintln!( "Error: {e}" ); std::process::exit( 1 ); }
};
apply_refresh_env_vars( &mut cli );
if cli.creds_path.is_empty()
{
eprintln!( "Error: cannot resolve credentials path: HOME is not set; provide --creds or set CLR_CREDS\nRun with --help for usage." );
std::process::exit( 1 );
}
run_refresh_command( &cli.creds_path, cli.timeout_secs, cli.trace )
}