use agent_trace::commands;
use agent_trace::commands::context::ContextCmd;
use agent_trace::commands::model::ModelCmd;
use agent_trace::commands::resume::ResumeCmd;
use agent_trace::mcp;
use agent_trace::observability::{self, TerminalOutput};
use agent_trace::runtime::require_synthesis_backend;
use agent_trace::types::DocType;
use anyhow::Result;
use clap::{ArgAction, Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(
name = "agent-trace",
version,
about = "Agent Document Manager — git-backed document store with AI integration",
arg_required_else_help = true
)]
pub struct Cli {
#[arg(long, global = true)]
pub agent: Option<String>,
#[arg(short, long, action = ArgAction::Count, global = true)]
pub verbose: u8,
#[arg(long, global = true)]
pub quiet: bool,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
Init {
#[arg(default_value = ".")]
path: PathBuf,
#[arg(long)]
scan: bool,
},
Open {
path: Option<PathBuf>,
#[arg(long)]
agent: Option<String>,
#[arg(long)]
ascii: bool,
},
Status {
path: Option<PathBuf>,
},
Repair,
Add {
doc_type: DocType,
file: PathBuf,
},
Ls {
#[arg(long = "type", value_name = "TYPE")]
type_filter: Option<DocType>,
#[arg(long)]
json: bool,
},
Info {
file: PathBuf,
},
Reclassify {
file: PathBuf,
new_type: DocType,
},
Untrack {
file: PathBuf,
},
Rm {
file: PathBuf,
},
Unlock {
file: PathBuf,
#[arg(long = "for")]
for_actor: String,
#[arg(long, default_value = "10")]
duration: u32,
},
Violations {
#[arg(long)]
limit: Option<usize>,
},
Context {
#[command(subcommand)]
subcommand: ContextCmd,
},
Log {
file: Option<PathBuf>,
#[arg(long)]
limit: Option<usize>,
#[arg(long)]
actor: Option<String>,
#[arg(long = "type", value_name = "TYPE")]
type_filter: Option<DocType>,
},
Diff {
file: PathBuf,
v1: Option<u32>,
v2: Option<u32>,
},
Show {
file: PathBuf,
version: u32,
},
Restore {
file: PathBuf,
version: u32,
},
Replace {
find: String,
replace: String,
#[arg(long = "type", value_name = "TYPE")]
type_filter: Option<DocType>,
#[arg(long)]
dry_run: bool,
},
Model {
#[command(subcommand)]
subcommand: ModelCmd,
},
Resume {
#[command(subcommand)]
subcommand: ResumeCmd,
},
Connect {
name: String,
},
Disconnect,
Write {
file: PathBuf,
#[arg(long)]
content: Option<String>,
},
Mcp {
#[arg(long, default_value = ".")]
path: PathBuf,
#[arg(long)]
actor: Option<String>,
},
}
fn store_root_for_command(cmd: &Commands) -> PathBuf {
match cmd {
Commands::Init { path, .. } => path.clone(),
Commands::Open { path, .. } | Commands::Status { path, .. } => {
path.clone().unwrap_or_else(|| PathBuf::from("."))
}
Commands::Mcp { path, .. } => path.clone(),
_ => PathBuf::from("."),
}
}
fn requires_synthesis_gate(cmd: &Commands) -> bool {
!matches!(
cmd,
Commands::Model { .. }
| Commands::Connect { .. }
| Commands::Disconnect
| Commands::Mcp { .. }
)
}
fn main() -> Result<()> {
let cli = Cli::parse();
observability::init_tracing(cli.verbose)?;
let output = TerminalOutput::new(cli.quiet);
if requires_synthesis_gate(&cli.command) {
let root = store_root_for_command(&cli.command);
require_synthesis_backend(Some(root.as_path()))?;
}
match cli.command {
Commands::Init { path, scan } => commands::init::run(&path, scan, &output),
Commands::Open { path, agent, ascii } => {
let root = path.unwrap_or_else(|| PathBuf::from("."));
commands::open::run(&root, agent.or(cli.agent), ascii, &output)
}
Commands::Status { path } => {
let root = path.unwrap_or_else(|| PathBuf::from("."));
commands::status::run(&root, &output)
}
Commands::Repair => commands::repair::run(&PathBuf::from("."), &output),
Commands::Add { doc_type, file } => {
commands::add::run(&PathBuf::from("."), doc_type, &file, &output)
}
Commands::Ls { type_filter, json } => {
commands::ls::run(&PathBuf::from("."), type_filter.as_ref(), json, &output)
}
Commands::Info { file } => commands::info::run(&PathBuf::from("."), &file, &output),
Commands::Reclassify { file, new_type } => {
commands::reclassify::run(&PathBuf::from("."), &file, new_type, &output)
}
Commands::Untrack { file } => commands::untrack::run(&PathBuf::from("."), &file, &output),
Commands::Rm { file } => commands::rm::run(&PathBuf::from("."), &file, &output),
Commands::Unlock {
file,
for_actor,
duration,
} => commands::unlock::run(&PathBuf::from("."), &file, &for_actor, duration, &output),
Commands::Violations { limit } => {
commands::violations::run(&PathBuf::from("."), limit, &output)
}
Commands::Context { subcommand } => {
commands::context::run(&PathBuf::from("."), subcommand, &output)
}
Commands::Log {
file,
limit,
actor,
type_filter,
} => commands::log::run(
&PathBuf::from("."),
file.as_deref(),
limit,
actor.as_deref(),
type_filter.as_ref(),
&output,
),
Commands::Diff { file, v1, v2 } => {
commands::diff::run(&PathBuf::from("."), &file, v1, v2, &output)
}
Commands::Show { file, version } => {
commands::show::run(&PathBuf::from("."), &file, version, &output)
}
Commands::Restore { file, version } => {
commands::restore::run(&PathBuf::from("."), &file, version, &output)
}
Commands::Replace {
find,
replace,
type_filter,
dry_run,
} => commands::replace::run(
&PathBuf::from("."),
&find,
&replace,
type_filter.as_ref(),
dry_run,
&output,
),
Commands::Model { subcommand } => {
commands::model::run(subcommand, Some(&PathBuf::from(".")), &output)
}
Commands::Resume { subcommand } => {
commands::resume::run(&PathBuf::from("."), subcommand, &output)
}
Commands::Connect { name } => {
commands::connect::run_connect(&PathBuf::from("."), &name, &output)
}
Commands::Disconnect => commands::connect::run_disconnect(&PathBuf::from("."), &output),
Commands::Write { file, content } => {
commands::write_cmd::run(&PathBuf::from("."), &file, content, cli.agent, &output)
}
Commands::Mcp { path, actor } => mcp::server::run(&path, actor),
}
}