use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use std::path::PathBuf;
mod commands;
#[derive(Parser)]
#[command(
name = "drift",
version,
about = "Drift AI — capture, compact, and bind AI coding sessions to your git history."
)]
struct Cli {
#[arg(long, global = true)]
repo: Option<PathBuf>,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Init,
Capture {
#[arg(long)]
session: Option<String>,
#[arg(long)]
agent: Option<String>,
#[arg(long = "all-since")]
all_since: Option<String>,
},
Watch,
Cost {
#[arg(long)]
since: Option<String>,
#[arg(long)]
until: Option<String>,
#[arg(long)]
model: Option<String>,
#[arg(long = "by")]
by: Option<String>,
},
Handoff {
#[arg(long)]
branch: Option<String>,
#[arg(long)]
since: Option<String>,
#[arg(long)]
session: Option<String>,
#[arg(long, default_value = "claude-code")]
to: Option<String>,
#[arg(long)]
output: Option<PathBuf>,
#[arg(long)]
print: bool,
},
List {
#[arg(long)]
agent: Option<String>,
},
Show { session_id: String },
Blame {
file: PathBuf,
#[arg(long)]
line: Option<u32>,
#[arg(long)]
range: Option<String>,
},
Trace { session_id: String },
Diff { event_id: String },
Rejected {
#[arg(long)]
since: Option<String>,
},
Log {
#[arg(last = true)]
git_args: Vec<String>,
},
Config {
#[command(subcommand)]
action: ConfigAction,
},
Bind { commit: String, session_id: String },
AutoBind,
InstallHook,
Sync {
#[command(subcommand)]
dir: SyncDir,
},
Mcp,
}
#[derive(Subcommand)]
enum ConfigAction {
Get { key: String },
Set { key: String, value: String },
List,
}
#[derive(Subcommand)]
enum SyncDir {
Push { remote: String },
Pull { remote: String },
}
fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn,drift=info")),
)
.init();
let cli = Cli::parse();
let repo = cli
.repo
.clone()
.or_else(|| std::env::current_dir().ok())
.context("resolve repo root")?;
match cli.command {
Command::Init => commands::init::run(&repo),
Command::Capture {
session,
agent,
all_since,
} => commands::capture::run(
&repo,
session.as_deref(),
agent.as_deref(),
all_since.as_deref(),
),
Command::Watch => commands::watch::run(&repo),
Command::Cost {
since,
until,
model,
by,
} => commands::cost::run(
&repo,
since.as_deref(),
until.as_deref(),
model.as_deref(),
by.as_deref(),
),
Command::Handoff {
branch,
since,
session,
to,
output,
print,
} => commands::handoff::run(
&repo,
branch.as_deref(),
since.as_deref(),
session.as_deref(),
to.as_deref(),
output.as_deref(),
print,
),
Command::List { agent } => commands::list::run(&repo, agent.as_deref()),
Command::Show { session_id } => commands::show::run(&repo, &session_id),
Command::Blame { file, line, range } => {
commands::blame::run(&repo, &file, line, range.as_deref())
}
Command::Trace { session_id } => commands::trace::run(&repo, &session_id),
Command::Diff { event_id } => commands::diff::run(&repo, &event_id),
Command::Rejected { since } => commands::rejected::run(&repo, since.as_deref()),
Command::Log { git_args } => commands::log::run(&repo, &git_args),
Command::Config { action } => match action {
ConfigAction::Get { key } => commands::config::get(&repo, &key),
ConfigAction::Set { key, value } => commands::config::set(&repo, &key, &value),
ConfigAction::List => commands::config::list(&repo),
},
Command::Bind { commit, session_id } => commands::bind::run(&repo, &commit, &session_id),
Command::AutoBind => commands::auto_bind::run(&repo),
Command::InstallHook => commands::install_hook::run(&repo),
Command::Sync { dir } => match dir {
SyncDir::Push { remote } => commands::sync::push(&repo, &remote),
SyncDir::Pull { remote } => commands::sync::pull(&repo, &remote),
},
Command::Mcp => drift_mcp::run_stdio(&repo),
}
}