use unilang::data::{ ErrorCode, ErrorData, OutputData };
use unilang::interpreter::ExecutionContext;
use unilang::semantic::VerifiedCommand;
use crate::output::{ OutputFormat, OutputOptions, json_escape };
use claude_runner_core::process::{ ProcessInfo, find_claude_processes, send_sigkill, send_sigterm };
#[ allow( clippy::needless_pass_by_value, clippy::missing_inline_in_public_items ) ]
pub fn processes_routine( cmd : VerifiedCommand, _ctx : ExecutionContext ) -> Result< OutputData, ErrorData >
{
let opts = OutputOptions::from_cmd( &cmd )?;
let procs = find_claude_processes();
let content = match opts.format
{
OutputFormat::Json =>
{
if procs.is_empty()
{
"{\"processes\":[]}\n".to_string()
}
else
{
let entries : Vec< String > = procs.iter().map( | p |
{
let cwd = json_escape( &p.cwd.to_string_lossy() );
format!( " {{\"pid\":{},\"cwd\":\"{cwd}\"}}", p.pid )
} ).collect();
format!( "{{\"processes\":[\n{}\n]}}\n", entries.join( ",\n" ) )
}
}
OutputFormat::Text =>
{
if procs.is_empty()
{
String::new()
}
else
{
let lines : Vec< String > = procs.iter().map( | p |
match opts.verbosity
{
0 => format!( "{} {}", p.pid, p.cwd.display() ),
_ => format!( "PID: {} CWD: {}", p.pid, p.cwd.display() ),
}
).collect();
format!( "{}\n", lines.join( "\n" ) )
}
}
};
Ok( OutputData::new( content, "text" ) )
}
fn send_kill_signals( procs : &[ ProcessInfo ], force : bool ) -> Result< (), ErrorData >
{
if force
{
let mut failures = Vec::new();
for p in procs
{
if let Err( e ) = send_sigkill( p.pid ) { failures.push( format!( "PID {}: {e}", p.pid ) ); }
}
if !failures.is_empty()
{
return Err( ErrorData::new( ErrorCode::InternalError, format!( "SIGKILL failed: {}", failures.join( ", " ) ) ) );
}
}
else
{
let mut failures = Vec::new();
for p in procs
{
if let Err( e ) = send_sigterm( p.pid ) { failures.push( format!( "PID {}: {e}", p.pid ) ); }
}
if !failures.is_empty()
{
return Err( ErrorData::new( ErrorCode::InternalError, format!( "SIGTERM failed: {}", failures.join( ", " ) ) ) );
}
std::thread::sleep( core::time::Duration::from_secs( 2 ) );
let survivors = find_claude_processes();
let mut kfailures = Vec::new();
for p in &survivors
{
if let Err( e ) = send_sigkill( p.pid ) { kfailures.push( format!( "PID {}: {e}", p.pid ) ); }
}
if !kfailures.is_empty()
{
return Err( ErrorData::new( ErrorCode::InternalError, format!( "SIGKILL failed: {}", kfailures.join( ", " ) ) ) );
}
}
Ok( () )
}
#[ allow( clippy::needless_pass_by_value, clippy::missing_inline_in_public_items ) ]
pub fn processes_kill_routine( cmd : VerifiedCommand, _ctx : ExecutionContext ) -> Result< OutputData, ErrorData >
{
let opts = OutputOptions::from_cmd( &cmd )?;
let procs = find_claude_processes();
if procs.is_empty()
{
let content = match opts.format
{
OutputFormat::Json => "{\"killed\":0}\n".to_string(),
OutputFormat::Text =>
{
if opts.verbosity == 0 { "0\n".to_string() } else { "no active processes\n".to_string() }
}
};
return Ok( OutputData::new( content, "text" ) );
}
if super::is_dry( &cmd )
{
let signal = if super::is_force( &cmd ) { "SIGKILL" } else { "SIGTERM" };
let pids : Vec< String > = procs.iter().map( | p | p.pid.to_string() ).collect();
let content = match opts.format
{
OutputFormat::Json =>
{
format!( "{{\"dry_run\":true,\"signal\":\"{signal}\",\"pids\":[{}]}}\n", pids.join( "," ) )
}
OutputFormat::Text =>
{
if opts.verbosity == 0
{
format!( "{}\n", pids.join( "\n" ) )
}
else
{
let lines : Vec< String > = procs.iter()
.map( | p | format!( "[dry-run] would send {signal} to PID {}", p.pid ) )
.collect();
format!( "{}\n", lines.join( "\n" ) )
}
}
};
return Ok( OutputData::new( content, "text" ) );
}
let count = procs.len();
send_kill_signals( &procs, super::is_force( &cmd ) )?;
std::thread::sleep( core::time::Duration::from_millis( 500 ) );
let remaining = find_claude_processes().len();
if remaining > 0
{
return Err( ErrorData::new(
ErrorCode::InternalError,
format!( "killed {count} process(es) but {remaining} could not be terminated" ),
) );
}
let content = match opts.format
{
OutputFormat::Json => format!( "{{\"killed\":{count}}}\n" ),
OutputFormat::Text =>
{
if opts.verbosity == 0
{
format!( "{count}\n" )
}
else
{
format!( "killed {count} process(es)\n" )
}
}
};
Ok( OutputData::new( content, "text" ) )
}