use clap::{Parser, Subcommand};
use colored::Colorize;
use std::path::PathBuf;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
mod audit;
mod commands;
#[derive(Parser)]
#[command(name = "arbor")]
#[command(author = "Arbor Contributors")]
#[command(version)]
#[command(about = "The Graph-Native Intelligence Layer for Code", long_about = None)]
struct Cli {
#[arg(short, long, global = true)]
verbose: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Setup {
#[arg(default_value = ".")]
path: PathBuf,
#[arg(long)]
follow_symlinks: bool,
#[arg(long)]
no_cache: bool,
},
Init {
#[arg(default_value = ".")]
path: PathBuf,
},
Index {
#[arg(default_value = ".")]
path: PathBuf,
#[arg(long)]
changed_only: bool,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
follow_symlinks: bool,
#[arg(long)]
no_cache: bool,
},
Query {
query: String,
#[arg(default_value = ".")]
path: PathBuf,
#[arg(short, long, default_value = "10")]
limit: usize,
},
Diff {
#[arg(default_value = ".")]
path: PathBuf,
#[arg(short, long, default_value = "5")]
depth: usize,
#[arg(long)]
json: bool,
},
Check {
#[arg(default_value = ".")]
path: PathBuf,
#[arg(short, long, default_value = "5")]
depth: usize,
#[arg(long, default_value = "25")]
max_blast_radius: usize,
#[arg(long)]
no_fail: bool,
#[arg(long)]
json: bool,
},
Serve {
#[arg(short, long, default_value = "7432")]
port: u16,
#[arg(long)]
headless: bool,
#[arg(default_value = ".")]
path: PathBuf,
#[arg(long)]
follow_symlinks: bool,
},
Export {
#[arg(short, long, default_value = "arbor-graph.json")]
output: PathBuf,
#[arg(default_value = ".")]
path: PathBuf,
},
Status {
#[arg(default_value = ".")]
path: PathBuf,
#[arg(long)]
files: bool,
},
Viz {
#[arg(default_value = ".")]
path: PathBuf,
#[arg(long)]
follow_symlinks: bool,
},
Bridge {
#[arg(default_value = ".")]
path: PathBuf,
#[arg(long)]
viz: bool,
#[arg(long)]
follow_symlinks: bool,
},
#[command(visible_alias = "check-health")]
Doctor {
#[arg(default_value = ".")]
path: PathBuf,
},
Refactor {
target: String,
#[arg(default_value = ".")]
path: PathBuf,
#[arg(short, long, default_value = "5")]
depth: usize,
#[arg(long)]
why: bool,
#[arg(long)]
json: bool,
},
Explain {
question: String,
#[arg(default_value = ".")]
path: PathBuf,
#[arg(short, long, default_value = "4000")]
tokens: usize,
#[arg(long)]
why: bool,
#[arg(long)]
json: bool,
},
Open {
symbol: String,
#[arg(default_value = ".")]
path: PathBuf,
},
Gui {
#[arg(default_value = ".")]
path: PathBuf,
},
PrSummary {
symbols: String,
#[arg(default_value = ".")]
path: PathBuf,
},
Watch {
#[arg(default_value = ".")]
path: PathBuf,
},
Audit {
sink: String,
#[arg(short, long, default_value = "8")]
depth: usize,
#[arg(long, default_value = "text")]
format: String,
#[arg(default_value = ".")]
path: PathBuf,
},
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
let filter = if cli.verbose { "debug" } else { "info" };
tracing_subscriber::registry()
.with(
tracing_subscriber::fmt::layer()
.with_writer(std::io::stderr)
.with_target(false),
)
.with(tracing_subscriber::EnvFilter::new(filter))
.init();
let result = match cli.command {
Commands::Setup {
path,
follow_symlinks,
no_cache,
} => commands::init(&path)
.and_then(|_| commands::index(&path, None, follow_symlinks, no_cache, false)),
Commands::Init { path } => commands::init(&path),
Commands::Index {
path,
changed_only,
output,
follow_symlinks,
no_cache,
} => commands::index(
&path,
output.as_deref(),
follow_symlinks,
no_cache,
changed_only,
),
Commands::Query { query, path, limit } => commands::query(&query, limit, &path),
Commands::Diff { path, depth, json } => commands::diff(&path, depth, json),
Commands::Check {
path,
depth,
max_blast_radius,
no_fail,
json,
} => commands::check(&path, depth, max_blast_radius, no_fail, json),
Commands::Serve {
port,
headless,
path,
follow_symlinks,
} => commands::serve(port, headless, &path, follow_symlinks).await,
Commands::Export { output, path } => commands::export(&path, &output),
Commands::Status { path, files } => commands::status(&path, files),
Commands::Viz {
path,
follow_symlinks,
} => commands::viz(&path, follow_symlinks).await,
Commands::Bridge {
path,
viz,
follow_symlinks,
} => commands::bridge(&path, viz, follow_symlinks).await,
Commands::Doctor { path } => commands::check_health(Some(&path)).await,
Commands::Refactor {
target,
path,
depth,
why,
json,
} => commands::refactor(&target, depth, why, json, &path),
Commands::Explain {
question,
path,
tokens,
why,
json,
} => commands::explain(&question, tokens, why, json, &path),
Commands::Open { symbol, path } => commands::open(&symbol, &path),
Commands::Gui { path } => commands::gui(&path),
Commands::PrSummary { symbols, path } => commands::pr_summary(&symbols, &path),
Commands::Watch { path } => commands::watch(&path).await,
Commands::Audit {
sink,
depth,
format,
path,
} => commands::audit(&sink, depth, &format, &path),
};
if let Err(e) = result {
eprintln!("{} {}", "error:".red().bold(), e);
std::process::exit(1);
}
}