Skip to main content

sparrow/cli/
mod.rs

1use clap::{Parser, Subcommand};
2use std::path::PathBuf;
3
4#[derive(Parser)]
5#[command(name = "sparrow", about = "one cli · grows with you", version)]
6pub struct Cli {
7    #[command(subcommand)]
8    pub command: Option<Commands>,
9
10    /// Launch terminal TUI (native)
11    #[arg(long)]
12    pub tui: bool,
13
14    /// Launch webview console (HTTP + WebSocket)
15    #[arg(long)]
16    pub web: bool,
17
18    /// JSON output (NDJSON event stream)
19    #[arg(long)]
20    pub json: bool,
21
22    /// Override autonomy level
23    #[arg(long)]
24    pub autonomy: Option<String>,
25
26    /// Force a specific model
27    #[arg(long)]
28    pub model: Option<String>,
29
30    /// Prefer local/offline models
31    #[arg(long, global = true)]
32    pub local: bool,
33
34    /// Session budget cap (USD)
35    #[arg(long, global = true)]
36    pub budget: Option<f64>,
37
38    /// Hard stop on cumulative USD spent in this run (alias for --budget,
39    /// kept separately to match competitor tools' UX).
40    #[arg(long, global = true)]
41    pub max_cost_usd: Option<f64>,
42
43    /// Hard stop on wall-clock seconds elapsed in this run.
44    #[arg(long, global = true)]
45    pub max_wall_secs: Option<u64>,
46
47    /// Hard stop on total tokens consumed in this run.
48    #[arg(long, global = true)]
49    pub max_tokens: Option<u64>,
50
51    /// Bind address for daemon / cockpit servers (default 127.0.0.1).
52    /// Use 0.0.0.0 when running under WSL or in a container.
53    #[arg(long, global = true)]
54    pub bind: Option<String>,
55
56    /// Sandbox backend
57    #[arg(long, global = true)]
58    pub sandbox: Option<String>,
59
60    /// Profile name
61    #[arg(long, global = true)]
62    pub profile: Option<String>,
63
64    /// Disable checkpointing
65    #[arg(long, global = true)]
66    pub no_checkpoint: bool,
67
68    /// Run as a named agent
69    #[arg(long)]
70    pub agent: Option<String>,
71
72    /// Continue the most recent session (any surface) instead of this
73    /// directory's session
74    #[arg(long = "continue", global = true)]
75    pub continue_last: bool,
76
77    /// Start with a fresh context (ignore this directory's saved session)
78    #[arg(long, global = true)]
79    pub fresh: bool,
80
81    /// Skip the pre-run quote confirmation
82    #[arg(long, global = true)]
83    pub yes: bool,
84}
85
86#[derive(Subcommand)]
87pub enum Commands {
88    /// Run a single agentic task
89    Run {
90        /// Task description
91        task: String,
92
93        /// Emit NDJSON event stream (same as the global --json flag, but may
94        /// follow the task: `sparrow run "..." --json`)
95        #[arg(long)]
96        json: bool,
97    },
98
99    /// Create a read-only execution plan for a task
100    Plan {
101        /// Task description
102        task: String,
103
104        /// Emit JSON instead of Markdown
105        #[arg(long)]
106        json: bool,
107    },
108
109    /// Adversarial review of the current local diff (uncommitted, staged,
110    /// and commits ahead of `--base`). Read-only — no edits, no commits, no
111    /// network beyond the model call. Findings are structured around
112    /// security, correctness, regressions, performance, and readability.
113    Review {
114        /// Base ref to diff against (defaults to `origin/main`, then `main`,
115        /// then `HEAD~1`).
116        #[arg(long)]
117        base: Option<String>,
118
119        /// Only review changes touching these path globs (repeatable).
120        #[arg(long)]
121        paths: Vec<String>,
122
123        /// Print the prompt the model will see and exit (no model call).
124        #[arg(long)]
125        dry_run: bool,
126    },
127
128    /// Interactive multi-turn chat
129    Chat,
130
131    /// Launch TUI
132    Tui,
133
134    /// Launch first-run setup, then the WebView cockpit
135    Launch {
136        /// TCP port for the WebView HTTP/WS server
137        #[arg(long, default_value = "9339")]
138        port: u16,
139
140        /// Launch the terminal TUI instead of the WebView cockpit
141        #[arg(long)]
142        tui: bool,
143    },
144
145    /// Launch webview console (HTTP + WebSocket)
146    Console {
147        /// TCP port for the webview HTTP/WS server
148        #[arg(long, default_value = "9339")]
149        port: u16,
150    },
151
152    /// Run headless Sparrow runtime daemon
153    Daemon,
154
155    /// Manage persistent agents
156    Agent {
157        #[command(subcommand)]
158        action: AgentAction,
159    },
160
161    /// Run swarm: planner → coder → verifier
162    Swarm {
163        /// Task or plan file
164        task: String,
165    },
166
167    /// Schedule periodic jobs
168    Schedule {
169        /// Task description
170        task: String,
171
172        /// Cron expression
173        #[arg(long)]
174        cron: String,
175
176        /// Autonomy level for scheduled jobs
177        #[arg(long)]
178        autonomy: Option<String>,
179
180        /// Report to surfaces
181        #[arg(long)]
182        report: Vec<String>,
183    },
184
185    /// Manage model routing
186    Model {
187        /// Set active route
188        #[arg(long)]
189        set: Option<String>,
190
191        /// List available models
192        #[arg(long)]
193        list: bool,
194    },
195
196    /// Configure intelligent auto-routing provider
197    Route {
198        #[command(subcommand)]
199        action: RouteAction,
200    },
201
202    /// Manage provider credentials
203    Auth {
204        #[command(subcommand)]
205        action: AuthAction,
206    },
207
208    /// Manage skill library
209    Skills {
210        #[command(subcommand)]
211        action: SkillsAction,
212    },
213
214    /// Manage local Sparrow plugins
215    Plugins {
216        #[command(subcommand)]
217        action: PluginsAction,
218    },
219
220    /// Inspect and gate toolsets
221    Tools {
222        #[command(subcommand)]
223        action: ToolsAction,
224    },
225
226    /// Security audit of config, permissions, plugins, hooks, secrets
227    Security {
228        #[command(subcommand)]
229        action: SecurityAction,
230    },
231
232    /// GitHub Action / remote PR workflow
233    Github {
234        #[command(subcommand)]
235        action: GithubAction,
236    },
237
238    /// Compact context and write a durable handoff doc
239    Compact {
240        /// Task description (recorded in the handoff)
241        #[arg(long)]
242        task: Option<String>,
243        /// Output path (default: .sparrow/handoff/<timestamp>.md)
244        #[arg(long)]
245        out: Option<PathBuf>,
246        /// Emit JSON instead of Markdown to stdout (the file is always Markdown)
247        #[arg(long)]
248        json: bool,
249    },
250
251    /// Manage MCP connectors
252    Mcp {
253        #[command(subcommand)]
254        action: McpAction,
255    },
256
257    /// List checkpoints
258    Checkpoint {
259        #[command(subcommand)]
260        action: CheckpointAction,
261    },
262
263    /// Rewind to a checkpoint
264    Rewind {
265        /// Checkpoint ID or number
266        id: String,
267    },
268
269    /// Replay a transcript
270    Replay {
271        /// Run ID to replay
272        run_id: String,
273        /// Open an interactive TUI scrubber (←/→ to step through events)
274        #[arg(long)]
275        scrub: bool,
276    },
277
278    /// Start/stop gateway daemon
279    Gateway {
280        #[command(subcommand)]
281        action: GatewayAction,
282    },
283
284    /// Manage saved sessions
285    Sessions {
286        #[command(subcommand)]
287        action: SessionAction,
288    },
289
290    /// Interactive tutorial
291    Learn,
292
293    /// Initialize a project with .sparrow/ config
294    Init,
295
296    /// Show live status (active runs, budget, session)
297    Status,
298
299    /// Manage persistent memory
300    Memory {
301        #[command(subcommand)]
302        action: MemoryAction,
303    },
304
305    /// Inspect and update permission policy
306    Permissions {
307        #[command(subcommand)]
308        action: PermissionAction,
309    },
310
311    /// Profile management
312    Profile {
313        #[command(subcommand)]
314        action: ProfileAction,
315    },
316
317    /// Import config from another tool (claude-code, codex, opencode, openclaw)
318    Import {
319        #[command(subcommand)]
320        source: ImportSource,
321    },
322
323    /// Edit configuration
324    Config {
325        /// Open config.toml in editor
326        #[arg(short)]
327        edit: bool,
328    },
329
330    /// Self-update
331    Update,
332
333    /// Run diagnostics
334    Doctor,
335
336    /// (Re)run conversational setup
337    Setup,
338
339    /// Run a self-contained demo (snake game)
340    Demo,
341
342    /// Share latest session as GitHub Gist
343    Share,
344
345    /// Install or scan security pre-commit hooks
346    Hook {
347        #[command(subcommand)]
348        action: HookAction,
349    },
350
351    /// Voice commands (speak, transcribe, providers)
352    Voice {
353        #[command(subcommand)]
354        action: VoiceAction,
355    },
356
357    /// Test browser/vision (screenshot, navigate)
358    Browser {
359        /// URL to test
360        #[arg(default_value = "https://example.com")]
361        url: String,
362    },
363}
364
365#[derive(Subcommand)]
366pub enum AgentAction {
367    Create { name: String },
368    List,
369    Edit { name: String },
370    Rm { name: String },
371    Run { name: String, task: String },
372    Mention { name: String, message: String },
373}
374
375#[derive(Subcommand)]
376pub enum AuthAction {
377    Add {
378        provider: String,
379    },
380    List,
381    Rm {
382        provider: String,
383    },
384    /// Authenticate a provider via OAuth device flow (github/google/microsoft).
385    Login {
386        provider: String,
387        /// OAuth client id (or set <PROVIDER>_CLIENT_ID env var)
388        #[arg(long)]
389        client_id: Option<String>,
390    },
391}
392
393#[derive(Subcommand)]
394pub enum SkillsAction {
395    List,
396    View {
397        name: String,
398    },
399    Create {
400        name: String,
401    },
402    /// Install a skill from GitHub (gh:user/repo[/path]), a git URL, or a
403    /// local path to a SKILL.md
404    Install {
405        source: String,
406    },
407    Update {
408        name: String,
409    },
410    Prune,
411    /// Remove a skill by name (e.g. to delete junk auto-learned skills)
412    Rm {
413        name: String,
414    },
415}
416
417#[derive(Subcommand)]
418pub enum PluginsAction {
419    List,
420    Install {
421        source: String,
422        #[arg(long)]
423        allow: bool,
424    },
425    Rm {
426        name: String,
427    },
428}
429
430#[derive(Subcommand)]
431pub enum GithubAction {
432    /// Review a pull request: fetch diff via `gh`, run a read-only review prompt
433    Review {
434        /// PR number
435        pr: u64,
436        /// Print the review plan without invoking the model or posting comments
437        #[arg(long)]
438        dry_run: bool,
439        /// Override the model id
440        #[arg(long)]
441        model: Option<String>,
442        /// Restrict tool allow-list (comma-separated). Empty = inherit config.
443        #[arg(long)]
444        allowed_tools: Option<String>,
445    },
446    /// Show CI status for the current branch (via `gh run list`)
447    Status,
448    /// Fetch CI logs for a workflow run id (via `gh run view --log`)
449    Logs { run_id: String },
450}
451
452#[derive(Subcommand)]
453pub enum SecurityAction {
454    /// Run a full security audit
455    Audit {
456        /// Emit JSON instead of human-readable summary
457        #[arg(long)]
458        json: bool,
459    },
460}
461
462#[derive(Subcommand)]
463pub enum ToolsAction {
464    List {
465        #[arg(long)]
466        surface: Option<String>,
467    },
468    Enable {
469        tool: String,
470    },
471    Disable {
472        tool: String,
473    },
474}
475
476#[derive(Subcommand)]
477pub enum McpAction {
478    Add {
479        server: String,
480
481        /// Command to launch the MCP server
482        #[arg(long)]
483        command: Option<String>,
484
485        /// Command arguments, either repeated or space-delimited
486        #[arg(long, value_delimiter = ' ', allow_hyphen_values = true)]
487        args: Vec<String>,
488
489        /// Transport backend: stdio, sse, or url
490        #[arg(long)]
491        transport: Option<String>,
492    },
493    List,
494    Rm {
495        server: String,
496    },
497}
498
499#[derive(Subcommand)]
500pub enum CheckpointAction {
501    /// List all checkpoints
502    List,
503    /// Show diff between HEAD and a checkpoint
504    Diff {
505        /// Checkpoint ID
506        id: String,
507    },
508    /// Delete checkpoints older than N days (default: 30)
509    Prune {
510        /// Remove checkpoints older than this many days
511        #[arg(long, default_value = "30")]
512        older_than_days: u64,
513    },
514}
515
516#[derive(Subcommand)]
517pub enum GatewayAction {
518    Start,
519    Status,
520    Health,
521    Abort { run: String },
522    Stop,
523}
524
525#[derive(Subcommand)]
526pub enum SessionAction {
527    List,
528    Export {
529        id: String,
530        path: Option<PathBuf>,
531    },
532    Cleanup {
533        #[arg(long, default_value_t = 30)]
534        older_than_days: u64,
535    },
536    /// Full-text search across sessions
537    Search {
538        query: String,
539        #[arg(long, default_value_t = 10)]
540        limit: usize,
541    },
542}
543
544#[derive(Subcommand)]
545pub enum ProfileAction {
546    Create { name: String },
547    List,
548    Use { name: String },
549}
550
551#[derive(Subcommand)]
552pub enum ImportSource {
553    /// Import from Claude Code (~/.claude/)
554    ClaudeCode {
555        /// Path to project with .claude/ directory (defaults to cwd)
556        path: Option<PathBuf>,
557    },
558    /// Import from OpenAI Codex CLI (~/.codex/)
559    Codex {
560        /// Path to project with codex config (defaults to cwd)
561        path: Option<PathBuf>,
562    },
563    /// Import from OpenCode (~/.config/opencode/)
564    #[command(name = "opencode")]
565    OpenCode {
566        /// Path to project with opencode.json (defaults to cwd)
567        path: Option<PathBuf>,
568    },
569    /// Import from OpenClaw (~/.openclaw/)
570    Openclaw {
571        /// Path to the OpenClaw config directory (defaults to ~/.openclaw)
572        path: Option<PathBuf>,
573    },
574    /// Auto-detect installed tools and import each one
575    Auto,
576}
577
578#[derive(Subcommand)]
579pub enum MemoryAction {
580    List,
581    Forget {
582        id: String,
583    },
584    Add {
585        key: String,
586        value: String,
587    },
588    Replace {
589        id: String,
590        key: String,
591        value: String,
592    },
593    Recall {
594        query: String,
595        #[arg(long, default_value_t = 10)]
596        limit: usize,
597    },
598    Consolidate,
599    Docs,
600    Search {
601        query: String,
602        #[arg(long, default_value_t = 10)]
603        limit: usize,
604    },
605    Scroll {
606        session: String,
607        #[arg(long, default_value_t = 0)]
608        around: usize,
609        #[arg(long, default_value_t = 3)]
610        before: usize,
611        #[arg(long, default_value_t = 3)]
612        after: usize,
613    },
614    Graph {
615        #[command(subcommand)]
616        action: GraphAction,
617    },
618}
619
620#[derive(Subcommand)]
621pub enum GraphAction {
622    UpsertNode {
623        id: String,
624        label: String,
625        #[arg(long, default_value = "entity")]
626        kind: String,
627        #[arg(long, default_value = "{}")]
628        properties: String,
629    },
630    UpsertEdge {
631        from_id: String,
632        relation: String,
633        to_id: String,
634        #[arg(long)]
635        id: Option<String>,
636        #[arg(long, default_value_t = 1.0)]
637        weight: f64,
638        #[arg(long, default_value = "{}")]
639        properties: String,
640    },
641    Get {
642        id: String,
643    },
644    Neighbors {
645        id: String,
646        #[arg(long, default_value = "both")]
647        direction: String,
648        #[arg(long, default_value_t = 20)]
649        limit: usize,
650    },
651    Search {
652        query: String,
653        #[arg(long, default_value_t = 20)]
654        limit: usize,
655    },
656    Export,
657    DeleteNode {
658        id: String,
659    },
660    DeleteEdge {
661        id: String,
662    },
663    SyncNeo4j,
664}
665
666#[derive(Subcommand)]
667pub enum PermissionAction {
668    /// Show current permission mode and rules
669    List,
670    /// Set permission mode (read-only|plan|supervised|trusted|autonomous|emergency-stop)
671    Set { mode: String },
672    /// Add an explicitly allowed tool pattern
673    AllowTool { tool: String },
674    /// Add a tool pattern that always asks for approval
675    AskTool { tool: String },
676    /// Add an explicitly denied tool pattern
677    DenyTool { tool: String },
678    /// Add an allowed path boundary
679    AllowPath { path: PathBuf },
680    /// Add a denied path boundary
681    DenyPath { path: PathBuf },
682}
683
684#[derive(Subcommand)]
685pub enum RouteAction {
686    /// Pin routing to a specific provider or provider/model.
687    /// Examples: sparrow route set deepseek | sparrow route set deepseek/deepseek-v4-pro
688    Set {
689        /// Provider ID, or provider/model (e.g. \"deepseek/deepseek-v4-pro\")
690        provider: String,
691    },
692    /// Clear the pinned provider/model — let the multi-tier policy decide per task.
693    Clear,
694    /// Show the current routing config (preferred provider + per-tier policy).
695    Show,
696    /// Switch to manual mode — always use the chosen provider/model, never fall back.
697    Manual,
698    /// Switch to auto mode — tier-based policy + free_first fallback (default).
699    Auto,
700}
701
702#[derive(Subcommand)]
703pub enum HookAction {
704    /// Install pre-commit security hook
705    Install,
706    /// Scan staged files (or all files with --all) for secrets
707    Scan {
708        /// Scan entire working tree instead of just staged files
709        #[arg(long)]
710        all: bool,
711    },
712}
713
714#[derive(Subcommand)]
715pub enum VoiceAction {
716    /// Convert text to speech
717    Speak { text: String },
718    /// Transcribe audio file
719    Transcribe { file: String },
720    /// List available voice providers
721    Providers,
722}