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
326#[derive(Subcommand)]
327pub enum AgentAction {
328    Create { name: String },
329    List,
330    Edit { name: String },
331    Rm { name: String },
332    Run { name: String, task: String },
333    Mention { name: String, message: String },
334}
335
336#[derive(Subcommand)]
337pub enum AuthAction {
338    Add {
339        provider: String,
340    },
341    List,
342    Rm {
343        provider: String,
344    },
345    /// Authenticate a provider via OAuth device flow (github/google/microsoft).
346    Login {
347        provider: String,
348        /// OAuth client id (or set <PROVIDER>_CLIENT_ID env var)
349        #[arg(long)]
350        client_id: Option<String>,
351    },
352}
353
354#[derive(Subcommand)]
355pub enum SkillsAction {
356    List,
357    View {
358        name: String,
359    },
360    Create {
361        name: String,
362    },
363    Install {
364        source: String,
365    },
366    Update {
367        name: String,
368    },
369    Prune,
370    /// Remove a skill by name (e.g. to delete junk auto-learned skills)
371    Rm {
372        name: String,
373    },
374}
375
376#[derive(Subcommand)]
377pub enum PluginsAction {
378    List,
379    Install {
380        source: String,
381        #[arg(long)]
382        allow: bool,
383    },
384    Rm {
385        name: String,
386    },
387}
388
389#[derive(Subcommand)]
390pub enum GithubAction {
391    /// Review a pull request: fetch diff via `gh`, run a read-only review prompt
392    Review {
393        /// PR number
394        pr: u64,
395        /// Print the review plan without invoking the model or posting comments
396        #[arg(long)]
397        dry_run: bool,
398        /// Override the model id
399        #[arg(long)]
400        model: Option<String>,
401        /// Restrict tool allow-list (comma-separated). Empty = inherit config.
402        #[arg(long)]
403        allowed_tools: Option<String>,
404    },
405    /// Show CI status for the current branch (via `gh run list`)
406    Status,
407    /// Fetch CI logs for a workflow run id (via `gh run view --log`)
408    Logs { run_id: String },
409}
410
411#[derive(Subcommand)]
412pub enum SecurityAction {
413    /// Run a full security audit
414    Audit {
415        /// Emit JSON instead of human-readable summary
416        #[arg(long)]
417        json: bool,
418    },
419}
420
421#[derive(Subcommand)]
422pub enum ToolsAction {
423    List {
424        #[arg(long)]
425        surface: Option<String>,
426    },
427    Enable {
428        tool: String,
429    },
430    Disable {
431        tool: String,
432    },
433}
434
435#[derive(Subcommand)]
436pub enum McpAction {
437    Add {
438        server: String,
439
440        /// Command to launch the MCP server
441        #[arg(long)]
442        command: Option<String>,
443
444        /// Command arguments, either repeated or space-delimited
445        #[arg(long, value_delimiter = ' ', allow_hyphen_values = true)]
446        args: Vec<String>,
447
448        /// Transport backend: stdio, sse, or url
449        #[arg(long)]
450        transport: Option<String>,
451    },
452    List,
453    Rm {
454        server: String,
455    },
456}
457
458#[derive(Subcommand)]
459pub enum CheckpointAction {
460    /// List all checkpoints
461    List,
462    /// Show diff between HEAD and a checkpoint
463    Diff {
464        /// Checkpoint ID
465        id: String,
466    },
467    /// Delete checkpoints older than N days (default: 30)
468    Prune {
469        /// Remove checkpoints older than this many days
470        #[arg(long, default_value = "30")]
471        older_than_days: u64,
472    },
473}
474
475#[derive(Subcommand)]
476pub enum GatewayAction {
477    Start,
478    Status,
479    Health,
480    Abort { run: String },
481    Stop,
482}
483
484#[derive(Subcommand)]
485pub enum SessionAction {
486    List,
487    Export {
488        id: String,
489        path: Option<PathBuf>,
490    },
491    Cleanup {
492        #[arg(long, default_value_t = 30)]
493        older_than_days: u64,
494    },
495}
496
497#[derive(Subcommand)]
498pub enum ProfileAction {
499    Create { name: String },
500    List,
501    Use { name: String },
502}
503
504#[derive(Subcommand)]
505pub enum ImportSource {
506    Openclaw { path: Option<PathBuf> },
507}
508
509#[derive(Subcommand)]
510pub enum MemoryAction {
511    List,
512    Forget {
513        id: String,
514    },
515    Add {
516        key: String,
517        value: String,
518    },
519    Replace {
520        id: String,
521        key: String,
522        value: String,
523    },
524    Recall {
525        query: String,
526        #[arg(long, default_value_t = 10)]
527        limit: usize,
528    },
529    Consolidate,
530    Docs,
531    Search {
532        query: String,
533        #[arg(long, default_value_t = 10)]
534        limit: usize,
535    },
536    Scroll {
537        session: String,
538        #[arg(long, default_value_t = 0)]
539        around: usize,
540        #[arg(long, default_value_t = 3)]
541        before: usize,
542        #[arg(long, default_value_t = 3)]
543        after: usize,
544    },
545    Graph {
546        #[command(subcommand)]
547        action: GraphAction,
548    },
549}
550
551#[derive(Subcommand)]
552pub enum GraphAction {
553    UpsertNode {
554        id: String,
555        label: String,
556        #[arg(long, default_value = "entity")]
557        kind: String,
558        #[arg(long, default_value = "{}")]
559        properties: String,
560    },
561    UpsertEdge {
562        from_id: String,
563        relation: String,
564        to_id: String,
565        #[arg(long)]
566        id: Option<String>,
567        #[arg(long, default_value_t = 1.0)]
568        weight: f64,
569        #[arg(long, default_value = "{}")]
570        properties: String,
571    },
572    Get {
573        id: String,
574    },
575    Neighbors {
576        id: String,
577        #[arg(long, default_value = "both")]
578        direction: String,
579        #[arg(long, default_value_t = 20)]
580        limit: usize,
581    },
582    Search {
583        query: String,
584        #[arg(long, default_value_t = 20)]
585        limit: usize,
586    },
587    Export,
588    DeleteNode {
589        id: String,
590    },
591    DeleteEdge {
592        id: String,
593    },
594    SyncNeo4j,
595}
596
597#[derive(Subcommand)]
598pub enum PermissionAction {
599    /// Show current permission mode and rules
600    List,
601    /// Set permission mode (read-only|plan|supervised|trusted|autonomous|emergency-stop)
602    Set { mode: String },
603    /// Add an explicitly allowed tool pattern
604    AllowTool { tool: String },
605    /// Add a tool pattern that always asks for approval
606    AskTool { tool: String },
607    /// Add an explicitly denied tool pattern
608    DenyTool { tool: String },
609    /// Add an allowed path boundary
610    AllowPath { path: PathBuf },
611    /// Add a denied path boundary
612    DenyPath { path: PathBuf },
613}
614
615#[derive(Subcommand)]
616pub enum RouteAction {
617    /// Pin the intelligent auto-router to a specific provider for all tiers.
618    /// Example: sparrow route set opencode-go
619    Set {
620        /// Provider ID to use for all task tiers (e.g. opencode-go, anthropic, nvidia)
621        provider: String,
622    },
623    /// Clear the pinned provider — let the multi-tier policy decide per task.
624    Clear,
625    /// Show the current routing config (preferred provider + per-tier policy).
626    Show,
627}
628
629#[derive(Subcommand)]
630pub enum HookAction {
631    /// Install pre-commit security hook
632    Install,
633    /// Scan staged files (or all files with --all) for secrets
634    Scan {
635        /// Scan entire working tree instead of just staged files
636        #[arg(long)]
637        all: bool,
638    },
639}
640
641#[derive(Subcommand)]
642pub enum VoiceAction {
643    /// Convert text to speech
644    Speak { text: String },
645    /// Transcribe audio file
646    Transcribe { file: String },
647    /// List available voice providers
648    Providers,
649}