1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
//! csift - "ripgrep for Claude Code session transcripts".
//!
//! Fast regex `list` / `search` / `agents` / `whoami` / `files` / `recover` over
//! `~/.claude/projects/**/*.jsonl`. This file is the thin binary entrypoint: parse
//! args, dispatch to a subcommand handler, map errors to a process exit code. All real
//! work lives in the modules.
mod agents;
mod bash_danger;
mod bash_mutations;
mod cli;
mod elicitation;
mod files;
mod image;
mod model;
mod parse;
mod path;
mod plan;
mod recover;
mod search;
mod session;
mod show;
mod stats;
mod subagent;
mod text;
mod time_window;
mod timez;
mod turns;
mod whoami;
use std::process::ExitCode;
use anyhow::Result;
use crate::cli::{parse_argv, Cli, Command};
/// Global allocator: mimalloc. The scan paths (search/turns/files/recover) allocate
/// per-record Strings from many rayon workers at once; macOS's default libmalloc
/// serializes under that load (nanov2/tiny-malloc lock contention shows up directly
/// in profiles). mimalloc's per-thread heaps remove the contention - a measured
/// multi-subcommand win with zero behaviour change (SPEC §7 performance contract).
#[global_allocator]
static GLOBAL_ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
fn main() -> ExitCode {
// `parse_argv` wraps clap with an argv-normalization pass so a `--format`/`--kind`
// flag works in ANY position relative to a leading-`-` encoded project target
// (see cli::normalize_argv - fixes the allow_hyphen_values greedy-absorb bug).
let cli = parse_argv();
// Install the `--claude-home` override (if any) BEFORE dispatch, so every subcommand's
// path resolution honors it. `$CLAUDE_CONFIG_DIR` is read directly by `path::claude_home`
// and needs no wiring here.
if let Some(dir) = cli.claude_home.clone() {
path::set_claude_home_override(dir);
}
match run(cli) {
Ok(()) => ExitCode::SUCCESS,
Err(err) => {
// No silent failure: surface the full error chain on stderr.
eprintln!("csift: error: {err:#}");
ExitCode::FAILURE
}
}
}
fn run(cli: Cli) -> Result<()> {
match cli.command {
Command::List(args) => session::run_list(&args),
Command::Search(args) => search::run_search(&args),
Command::Show(args) => show::run_show(&args),
Command::Stats(args) => stats::run_stats(&args),
Command::Whoami(args) => whoami::run_whoami(&args),
Command::Agents(args) => agents::run_agents(&args),
Command::Files(args) => files::run_files(&args),
Command::Recover(args) => recover::run_recover(&args),
Command::Plan(args) => plan::run_plan(&args),
Command::Verbatim(args) => turns::run_verbatim(&args),
Command::Image(args) => image::run_image(&args),
// The hidden rename tombstone (cli.rs): always the pointed error, never a run.
Command::Turns(_) => anyhow::bail!(
"`csift turns` was RENAMED to `csift verbatim` in v0.5 — same command, same \
flags (reconstruct the VERBATIM turns a compaction summary clipped): re-run \
as `csift verbatim …`. To simply READ a session's recent turns (no compaction \
involved), that is `csift show <target> --turn -3..`."
),
}
}