use anyhow::Result;
use clap::{Parser, Subcommand};
use std::net::SocketAddr;
use trusty_memory::commands::migrate::{handle_migrate, MigrateTarget};
use trusty_memory::commands::service::{handle_service, ServiceAction};
use trusty_memory::commands::setup::handle_setup;
use trusty_memory::{run_http, run_stdio, AppState};
#[derive(Debug, Parser)]
#[command(
name = "trusty-memory",
version,
about = "Memory palace MCP server + migration utility",
long_about = "MCP server (stdio + HTTP/SSE) for trusty-memory, plus a \
`migrate kuzu-memory` subcommand that rewrites Claude \
settings files referencing the legacy kuzu-memory server."
)]
struct Cli {
#[arg(short, long, action = clap::ArgAction::Count, global = true)]
verbose: u8,
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
Serve {
#[arg(long, value_name = "ADDR")]
http: Option<SocketAddr>,
#[arg(long, value_name = "NAME")]
palace: Option<String>,
},
Migrate {
#[arg(value_enum)]
target: MigrateTarget,
#[arg(long)]
dry_run: bool,
#[arg(long)]
config_only: bool,
},
Setup,
Service {
#[command(subcommand)]
action: ServiceAction,
},
#[command(subcommand_required = true)]
Monitor {
#[command(subcommand)]
target: MonitorTarget,
},
}
#[derive(Debug, Subcommand)]
enum MonitorTarget {
Web,
Tui,
Status {
#[arg(long)]
json: bool,
},
Palaces {
id: Option<String>,
#[arg(long)]
json: bool,
},
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
let log_buffer = trusty_common::init_tracing_with_buffer(
cli.verbose,
trusty_common::log_buffer::DEFAULT_LOG_CAPACITY,
);
match cli.command {
Command::Serve { http, palace } => run_serve(http, palace, log_buffer).await,
Command::Migrate {
target,
dry_run,
config_only,
} => handle_migrate(target, dry_run, config_only),
Command::Setup => handle_setup(),
Command::Service { action } => handle_service(&action),
Command::Monitor { target } => run_monitor(target).await,
}
}
async fn run_monitor(target: MonitorTarget) -> Result<()> {
use trusty_memory::commands::monitor;
match target {
MonitorTarget::Web => match trusty_common::read_daemon_addr("trusty-memory")? {
Some(addr) => {
println!("{addr}/ui");
Ok(())
}
None => {
eprintln!("trusty-memory daemon not running (no address found)");
std::process::exit(1);
}
},
MonitorTarget::Tui => trusty_common::monitor::memory_tui::run().await,
MonitorTarget::Status { json } => monitor::handle_status(json).await,
MonitorTarget::Palaces { id, json } => monitor::handle_palaces(id, json).await,
}
}
async fn run_serve(
http: Option<SocketAddr>,
palace: Option<String>,
log_buffer: trusty_common::log_buffer::LogBuffer,
) -> Result<()> {
let data_root = trusty_common::resolve_data_dir("trusty-memory")?;
if let Some(addr) = http {
let state = AppState::new(data_root)
.with_default_palace(palace)
.with_log_buffer(log_buffer);
run_http(state, addr).await
} else {
let state = AppState::new(data_root).with_default_palace(palace);
run_stdio(state).await
}
}