Skip to main content

liminal_cli/agent/
cli.rs

1use super::config::Seat;
2use std::path::Path;
3use std::process::ExitCode;
4
5/// Commands for the ACP seat and its child-facing ticket.
6#[derive(Clone, Copy, Debug, clap::Subcommand)]
7pub enum AgentCommand {
8    /// Host one ACP harness on the Lys-launched seat's pass.
9    Run,
10    /// Serve the same ticket tools without opening a second inbox connection.
11    #[command(hide = true)]
12    Tools,
13}
14
15/// Run the host or the MCP ticket handed to its harness.
16/// Diagnostics name metadata failures and never format a frame or a launch argument.
17#[must_use]
18pub fn run(path: &Path, command: AgentCommand) -> ExitCode {
19    let result = Seat::load(path).and_then(|seat| match command {
20        AgentCommand::Tools => tools(&seat),
21        AgentCommand::Run => {
22            let runtime = tokio::runtime::Builder::new_multi_thread()
23                .enable_all()
24                .build()?;
25            let outcome = runtime.block_on(super::host::serve(&seat))?;
26            for error in outcome.errors {
27                eprintln!("liminal agent: {error}");
28            }
29            Ok(outcome.status)
30        }
31    });
32    match result {
33        Ok(status) => u8::try_from(status).map_or_else(
34            |_| {
35                eprintln!("liminal agent: harness exit status is outside the process exit range");
36                ExitCode::from(1)
37            },
38            ExitCode::from,
39        ),
40        Err(error) => {
41            eprintln!("liminal agent: {error}");
42            ExitCode::from(1)
43        }
44    }
45}
46
47fn tools(seat: &Seat) -> anyhow::Result<i32> {
48    let live = crate::Live::new(&seat.ticket).map_err(anyhow::Error::msg)?;
49    let mut server = crate::Server::new(live, seat.ticket.limits.frame_bytes);
50    let stdin = std::io::stdin();
51    server.serve(&mut stdin.lock(), &mut std::io::stdout())?;
52    Ok(0)
53}