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