liminal-cli 0.3.0

The liminal command: serve the network, maintain its stores, or serve its discovery MCP over stdio
Documentation
use super::config::Seat;
use std::path::Path;
use std::process::ExitCode;

/// Commands for the ACP seat and its child-facing ticket.
#[derive(Clone, Copy, Debug, clap::Subcommand)]
pub enum AgentCommand {
    /// Host one ACP harness on the Lys-launched seat's pass.
    Run,
    /// Serve the same ticket tools without opening a second inbox connection.
    #[command(hide = true)]
    Tools,
}

/// Run the host or the MCP ticket handed to its harness.
/// Diagnostics name metadata failures and never format a frame or a launch argument.
#[must_use]
pub fn run(path: &Path, command: AgentCommand) -> ExitCode {
    let result = Seat::load(path).and_then(|seat| match command {
        AgentCommand::Tools => tools(&seat),
        AgentCommand::Run => {
            let runtime = tokio::runtime::Builder::new_multi_thread()
                .enable_all()
                .build()?;
            let outcome = runtime.block_on(super::host::serve(&seat))?;
            for error in outcome.errors {
                eprintln!("liminal agent: {error}");
            }
            Ok(outcome.status)
        }
    });
    match result {
        Ok(status) => u8::try_from(status).map_or_else(
            |_| {
                eprintln!("liminal agent: harness exit status is outside the process exit range");
                ExitCode::from(1)
            },
            ExitCode::from,
        ),
        Err(error) => {
            eprintln!("liminal agent: {error}");
            ExitCode::from(1)
        }
    }
}

fn tools(seat: &Seat) -> anyhow::Result<i32> {
    let live = crate::Live::new(&seat.ticket).map_err(anyhow::Error::msg)?;
    let mut server = crate::Server::new(live, seat.ticket.limits.frame_bytes);
    let stdin = std::io::stdin();
    server.serve(&mut stdin.lock(), &mut std::io::stdout())?;
    Ok(0)
}