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