mod agents_md;
mod cli;
mod commands;
mod context;
mod output;
mod event_helper;
mod router;
use clap::Parser;
use cli::{Cli, Command};
use libgrite_core::GriteError;
fn main() {
let cli = Cli::parse();
let result = run_command(&cli);
if let Err(e) = result {
output::output_error(&cli, &e);
std::process::exit(e.exit_code());
}
}
fn run_command(cli: &Cli) -> Result<(), GriteError> {
if router::should_route_through_daemon(&cli.command) {
if let Some(ipc_cmd) = router::cli_to_ipc_command(&cli.command) {
if let Some(result) = try_route_through_daemon(cli, ipc_cmd)? {
return result;
}
}
}
match &cli.command {
Command::Init { no_agents_md } => commands::init::run(cli, *no_agents_md),
Command::Actor { cmd } => commands::actor::run(cli, cmd.clone()),
Command::Issue { cmd } => commands::issue::run(cli, cmd.clone()),
Command::Db { cmd } => commands::db::run(cli, cmd.clone()),
Command::Export { format, since } => commands::export::run(cli, format.clone(), since.clone()),
Command::Rebuild { from_snapshot } => commands::rebuild::run(cli, *from_snapshot),
Command::Sync { remote, pull, push } => commands::sync::run(cli, remote.clone(), *pull, *push),
Command::Snapshot { cmd } => commands::snapshot::run(cli, cmd.clone()),
Command::Daemon { cmd } => commands::daemon::run(cli, cmd.clone()),
Command::Lock { cmd } => commands::lock::run(cli, cmd.clone()),
Command::Doctor { fix } => commands::doctor::run(cli, *fix),
Command::Context { cmd } => commands::context::run(cli, cmd.clone()),
}
}
fn try_route_through_daemon(
cli: &Cli,
ipc_cmd: libgrite_ipc::IpcCommand,
) -> Result<Option<Result<(), GriteError>>, GriteError> {
let ctx = match context::GriteContext::resolve(cli) {
Ok(ctx) => ctx,
Err(_) => return Ok(None), };
match router::route_command(&ctx, cli, ipc_cmd)? {
router::RouteResult::Local => Ok(None),
router::RouteResult::DaemonResponse(response) => {
Ok(Some(handle_daemon_response(cli, response)))
}
router::RouteResult::Blocked { pid, expires_in_ms } => {
Err(GriteError::DbBusy(format!(
"Data directory locked by daemon (PID {}, expires in {}s). Use --no-daemon to wait or try later.",
pid,
expires_in_ms / 1000
)))
}
}
}
fn handle_daemon_response(cli: &Cli, response: libgrite_ipc::IpcResponse) -> Result<(), GriteError> {
if response.ok {
if let Some(data) = response.data {
if cli.json {
if !cli.quiet {
println!("{}", data);
}
} else {
output_daemon_data(cli, &data)?;
}
}
Ok(())
} else {
let (code, message) = match response.error {
Some(err) => (err.code, err.message),
None => ("unknown".to_string(), "Unknown error".to_string()),
};
match code.as_str() {
"not_found" => Err(GriteError::NotFound(message)),
"invalid_input" | "invalid_args" => Err(GriteError::InvalidArgs(message)),
"conflict" => Err(GriteError::Conflict(message)),
"db_busy" => Err(GriteError::DbBusy(message)),
"ipc_error" => Err(GriteError::Ipc(message)),
_ => Err(GriteError::Internal(message)),
}
}
}
fn output_daemon_data(cli: &Cli, data: &str) -> Result<(), GriteError> {
if cli.quiet {
return Ok(());
}
if let Ok(json) = serde_json::from_str::<serde_json::Value>(data) {
if let Some(issues) = json.get("issues") {
if let Some(arr) = issues.as_array() {
for issue in arr {
let id = issue.get("id").and_then(|v| v.as_str()).unwrap_or("?");
let state = issue.get("state").and_then(|v| v.as_str()).unwrap_or("?");
let title = issue.get("title").and_then(|v| v.as_str()).unwrap_or("?");
println!("{} [{}] {}", &id[..8.min(id.len())], state, title);
}
}
} else if json.get("issue_id").is_some() {
let issue_id = json.get("issue_id").and_then(|v| v.as_str()).unwrap_or("?");
println!("Created issue {}", issue_id);
} else if json.get("event_count").is_some() {
let events = json.get("event_count").and_then(|v| v.as_u64()).unwrap_or(0);
let issues = json.get("issue_count").and_then(|v| v.as_u64()).unwrap_or(0);
println!("Rebuilt: {} events, {} issues", events, issues);
} else if json.get("path").is_some() {
println!("{}", serde_json::to_string_pretty(&json)?);
} else {
println!("{}", serde_json::to_string_pretty(&json)?);
}
} else {
println!("{}", data);
}
Ok(())
}