Skip to main content

chub_cli/
lib.rs

1pub mod commands;
2pub mod mcp;
3pub mod output;
4pub mod welcome;
5
6use clap::{Parser, Subcommand};
7
8#[derive(Parser)]
9#[command(
10    name = "chub",
11    version,
12    about = "chub — agent-agnostic context, tracking, and cost analytics for AI-assisted development"
13)]
14struct Cli {
15    /// Output in JSON format
16    #[arg(long, global = true)]
17    json: bool,
18
19    #[command(subcommand)]
20    command: Commands,
21}
22
23#[derive(Subcommand)]
24enum Commands {
25    /// Build registry.json from a content directory
26    Build(commands::build::BuildArgs),
27    /// Search docs and skills (no query lists all)
28    Search(commands::search::SearchArgs),
29    /// List all available docs and skills
30    List(commands::search::SearchArgs),
31    /// Fetch docs or skills by ID (auto-detects type)
32    Get(commands::get::GetArgs),
33    /// Refresh the cached registry index
34    Update(commands::update::UpdateArgs),
35    /// Manage the local cache
36    Cache(commands::cache::CacheArgs),
37    /// Attach agent notes to a doc or skill
38    Annotate(commands::annotate::AnnotateArgs),
39    /// Rate a doc or skill (up/down)
40    Feedback(commands::feedback::FeedbackArgs),
41    /// Start MCP stdio server for AI coding agents
42    Mcp,
43
44    // --- Team features ---
45    /// Initialize .chub/ project directory
46    Init(commands::init::InitArgs),
47    /// Manage pinned docs
48    Pin(commands::pin::PinArgs),
49    /// Manage context profiles
50    Profile(commands::profile::ProfileArgs),
51    /// Detect dependencies and match to available docs
52    Detect(commands::detect::DetectArgs),
53    /// Generate/sync agent config files (CLAUDE.md, .cursorrules, etc.)
54    AgentConfig(commands::agent_config::AgentConfigArgs),
55    /// Check freshness of pinned doc versions vs installed deps
56    Check(commands::check::CheckArgs),
57    /// Browse and query project context docs
58    Context(commands::context_cmd::ContextArgs),
59    /// Show usage analytics
60    Stats(commands::stats::StatsArgs),
61    /// Serve a content directory as an HTTP registry
62    Serve(commands::serve::ServeArgs),
63    /// Manage doc bundles (shareable collections)
64    Bundle(commands::bundle::BundleArgs),
65    /// Manage doc snapshots for reproducible builds
66    Snapshot(commands::snapshot::SnapshotArgs),
67    /// View and manage local usage telemetry
68    Telemetry(commands::telemetry_cmd::TelemetryArgs),
69    /// Track AI coding agent usage (sessions, tokens, costs)
70    Track(commands::track::TrackArgs),
71}
72
73pub async fn run() {
74    let cli = Cli::parse();
75
76    // MCP server runs its own flow — no welcome, no registry preload from CLI
77    if matches!(cli.command, Commands::Mcp) {
78        if let Err(e) = mcp::server::run_mcp_server().await {
79            eprintln!("[chub-mcp] Fatal: {}", e);
80            std::process::exit(1);
81        }
82        return;
83    }
84
85    welcome::show_welcome_if_needed(cli.json);
86
87    // Commands that don't need registry
88    match cli.command {
89        Commands::Build(args) => {
90            if let Err(e) = commands::build::run(args, cli.json) {
91                output::error(&e.to_string(), cli.json);
92                std::process::exit(1);
93            }
94            return;
95        }
96        Commands::Update(args) => {
97            if let Err(e) = commands::update::run(args, cli.json).await {
98                output::error(&e.to_string(), cli.json);
99                std::process::exit(1);
100            }
101            return;
102        }
103        Commands::Cache(args) => {
104            commands::cache::run(args, cli.json);
105            return;
106        }
107        Commands::Annotate(args) => {
108            commands::annotate::run(args, cli.json).await;
109            return;
110        }
111        Commands::Init(args) => {
112            commands::init::run(args, cli.json);
113            return;
114        }
115        Commands::Pin(args) => {
116            commands::pin::run(args, cli.json);
117            return;
118        }
119        Commands::Profile(args) => {
120            commands::profile::run(args, cli.json);
121            return;
122        }
123        Commands::AgentConfig(args) => {
124            commands::agent_config::run(args, cli.json);
125            return;
126        }
127        Commands::Check(args) => {
128            commands::check::run(args, cli.json);
129            return;
130        }
131        Commands::Context(args) => {
132            commands::context_cmd::run(args, cli.json);
133            return;
134        }
135        Commands::Stats(args) => {
136            commands::stats::run(args, cli.json);
137            return;
138        }
139        Commands::Serve(args) => {
140            if let Err(e) = commands::serve::run(args, cli.json).await {
141                output::error(&e.to_string(), cli.json);
142                std::process::exit(1);
143            }
144            return;
145        }
146        Commands::Bundle(args) => {
147            commands::bundle::run(args, cli.json);
148            return;
149        }
150        Commands::Snapshot(args) => {
151            commands::snapshot::run(args, cli.json);
152            return;
153        }
154        Commands::Telemetry(args) => {
155            commands::telemetry_cmd::run(args, cli.json);
156            return;
157        }
158        Commands::Track(args) => {
159            commands::track::run(args, cli.json).await;
160            return;
161        }
162        _ => {}
163    }
164
165    // Commands that need registry — ensure it's available
166    if let Err(e) = chub_core::fetch::ensure_registry().await {
167        output::error(
168            &format!(
169                "Registry not available: {}. Run `chub update` to refresh.",
170                e
171            ),
172            cli.json,
173        );
174        std::process::exit(1);
175    }
176
177    let merged = chub_core::registry::load_merged();
178
179    match cli.command {
180        Commands::Search(args) | Commands::List(args) => {
181            commands::search::run(args, cli.json, &merged);
182        }
183        Commands::Get(args) => {
184            if let Err(e) = commands::get::run(args, cli.json, &merged).await {
185                output::error(&e.to_string(), cli.json);
186                std::process::exit(1);
187            }
188        }
189        Commands::Feedback(args) => {
190            commands::feedback::run(args, cli.json, Some(&merged)).await;
191        }
192        Commands::Detect(args) => {
193            commands::detect::run(args, cli.json, &merged);
194        }
195        // Already handled above
196        Commands::Build(_)
197        | Commands::Update(_)
198        | Commands::Cache(_)
199        | Commands::Annotate(_)
200        | Commands::Init(_)
201        | Commands::Pin(_)
202        | Commands::Profile(_)
203        | Commands::AgentConfig(_)
204        | Commands::Check(_)
205        | Commands::Context(_)
206        | Commands::Stats(_)
207        | Commands::Serve(_)
208        | Commands::Bundle(_)
209        | Commands::Snapshot(_)
210        | Commands::Telemetry(_)
211        | Commands::Track(_)
212        | Commands::Mcp => unreachable!(),
213    }
214}