use std::path::PathBuf;
use clap::{Parser, Subcommand};
fn default_input_dir() -> PathBuf {
let home = std::env::var("HOME")
.or_else(|_| std::env::var("USERPROFILE"))
.unwrap_or_else(|_| ".".to_string());
PathBuf::from(home).join(".claude").join("projects")
}
fn default_output_db() -> PathBuf {
let data_home = std::env::var("XDG_DATA_HOME")
.ok()
.filter(|s| !s.is_empty())
.map(PathBuf::from)
.unwrap_or_else(|| {
let home = std::env::var("HOME")
.or_else(|_| std::env::var("USERPROFILE"))
.unwrap_or_else(|_| ".".to_string());
PathBuf::from(home).join(".local").join("share")
});
data_home.join("cct").join("transcripts.duckdb")
}
#[derive(Parser, Debug)]
#[command(
name = "cct",
version,
about = "Claude Code transcript tools — ingest JSONL transcripts into DuckDB and serve the viewer UI"
)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}
#[derive(Subcommand, Debug)]
pub enum Command {
Ingest(IngestArgs),
Serve(ServeArgs),
Info(InfoArgs),
Update(UpdateArgs),
Report(ReportArgs),
Extract(ExtractArgs),
}
#[derive(Parser, Debug, Clone)]
pub struct IngestArgs {
#[arg(short = 'i', long = "input-dir", default_value_os_t = default_input_dir())]
pub input_dir: PathBuf,
#[arg(short = 'j', long = "jobs", default_value_t = 0)]
pub jobs: usize,
#[arg(short = 'o', long = "output", default_value_os_t = default_output_db())]
pub output: PathBuf,
#[arg(long = "pricing")]
pub pricing: Option<PathBuf>,
#[arg(long = "no-progress")]
pub no_progress: bool,
}
#[derive(Parser, Debug)]
pub struct ServeArgs {
#[arg(long = "db", default_value_os_t = default_output_db())]
pub db: PathBuf,
#[arg(long = "port", default_value_t = 8766)]
pub port: u16,
#[arg(long = "decomp-days", default_value_t = 30)]
pub decomp_days: i32,
}
#[derive(Parser, Debug)]
pub struct InfoArgs {
#[arg(long = "db", default_value_os_t = default_output_db())]
pub db: PathBuf,
}
#[derive(Parser, Debug)]
pub struct UpdateArgs {
#[arg(long = "version")]
pub version: Option<String>,
#[arg(short = 'y', long = "yes")]
pub yes: bool,
}
#[derive(Parser, Debug)]
pub struct ReportArgs {
#[command(subcommand)]
pub kind: ReportKind,
}
#[derive(Subcommand, Debug)]
pub enum ReportKind {
Usage(UsageArgs),
}
#[derive(Parser, Debug)]
pub struct UsageArgs {
#[arg(long = "db", default_value_os_t = default_output_db())]
pub db: PathBuf,
#[arg(long = "project")]
pub project: Option<PathBuf>,
#[arg(long = "all", conflicts_with_all = ["project", "no_subdirs"])]
pub all: bool,
#[arg(long = "no-subdirs")]
pub no_subdirs: bool,
#[arg(long = "from")]
pub from: Option<String>,
#[arg(long = "to")]
pub to: Option<String>,
#[arg(long = "json")]
pub json: bool,
}
#[derive(Parser, Debug)]
pub struct ExtractArgs {
#[command(subcommand)]
pub kind: ExtractKind,
}
#[derive(Subcommand, Debug)]
pub enum ExtractKind {
Sessions(ExtractSessionsArgs),
}
#[derive(Parser, Debug)]
pub struct ExtractSessionsArgs {
#[arg(long = "db", default_value_os_t = default_output_db())]
pub db: PathBuf,
#[arg(long = "project")]
pub project: Option<PathBuf>,
#[arg(long = "all", conflicts_with_all = ["project", "no_subdirs"])]
pub all: bool,
#[arg(long = "no-subdirs")]
pub no_subdirs: bool,
#[arg(long = "session")]
pub session: Option<String>,
#[arg(long = "from")]
pub from: Option<String>,
#[arg(long = "to")]
pub to: Option<String>,
}