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 the intelligent auto-router to a specific provider for all tiers.
687 /// Example: sparrow route set opencode-go
688 Set {
689 /// Provider ID to use for all task tiers (e.g. opencode-go, anthropic, nvidia)
690 provider: String,
691 },
692 /// Clear the pinned provider — let the multi-tier policy decide per task.
693 Clear,
694 /// Show the current routing config (preferred provider + per-tier policy).
695 Show,
696}
697
698#[derive(Subcommand)]
699pub enum HookAction {
700 /// Install pre-commit security hook
701 Install,
702 /// Scan staged files (or all files with --all) for secrets
703 Scan {
704 /// Scan entire working tree instead of just staged files
705 #[arg(long)]
706 all: bool,
707 },
708}
709
710#[derive(Subcommand)]
711pub enum VoiceAction {
712 /// Convert text to speech
713 Speak { text: String },
714 /// Transcribe audio file
715 Transcribe { file: String },
716 /// List available voice providers
717 Providers,
718}