Skip to main content

codetether_agent/cli/
mod.rs

1//! CLI command definitions and handlers
2
3pub mod auth;
4pub mod browserctl;
5pub mod clipboard;
6pub mod config;
7pub mod context;
8pub mod go_ralph;
9pub mod oracle;
10pub mod run;
11pub mod run_checkpoint;
12pub mod run_loop;
13pub mod search;
14pub mod search_render;
15
16use clap::{Parser, Subcommand};
17use std::path::PathBuf;
18
19/// CodeTether Agent - A2A-native AI coding agent
20///
21/// By default, runs as an A2A worker connecting to the CodeTether server.
22/// Use the 'tui' subcommand for interactive terminal mode.
23#[derive(Parser, Debug)]
24#[command(name = "codetether")]
25#[command(version, about, long_about = None)]
26pub struct Cli {
27    /// Project directory to operate on
28    #[arg(global = true, last = true)]
29    pub project: Option<PathBuf>,
30
31    /// Print logs to stderr
32    #[arg(long, global = true)]
33    pub print_logs: bool,
34
35    /// Log level
36    #[arg(long, global = true, value_parser = ["DEBUG", "INFO", "WARN", "ERROR"])]
37    pub log_level: Option<String>,
38
39    // Default A2A args (when no subcommand)
40    /// A2A server URL (default mode)
41    #[arg(short, long, env = "CODETETHER_SERVER")]
42    pub server: Option<String>,
43
44    /// Authentication token
45    #[arg(short, long, env = "CODETETHER_TOKEN")]
46    pub token: Option<String>,
47
48    /// Worker name
49    #[arg(short, long, env = "CODETETHER_WORKER_NAME")]
50    pub name: Option<String>,
51
52    #[command(subcommand)]
53    pub command: Option<Command>,
54}
55
56#[derive(Subcommand, Debug)]
57pub enum Command {
58    /// Start interactive terminal UI
59    Tui(TuiArgs),
60
61    /// Start a headless API server
62    Serve(ServeArgs),
63
64    /// Run with a message (non-interactive)
65    Run(RunArgs),
66
67    /// Create or update GitHub pull requests with CodeTether provenance
68    Pr(PrArgs),
69
70    /// Authenticate provider credentials and store in Vault
71    Auth(AuthArgs),
72
73    /// Manage configuration
74    Config(ConfigArgs),
75
76    /// Convert clipboard images into terminal-pasteable CodeTether input
77    Clipboard(clipboard::ClipboardArgs),
78
79    /// Browse or reset the active session context
80    Context(ContextArgs),
81
82    /// A2A worker mode (explicit - also the default)
83    Worker(A2aArgs),
84
85    /// Internal command: Git credential helper for worker-managed repositories.
86    #[command(hide = true)]
87    GitCredentialHelper(GitCredentialHelperArgs),
88
89    /// Spawn an A2A agent runtime with auto card registration and peer discovery
90    Spawn(SpawnArgs),
91
92    /// Execute task with parallel sub-agents (swarm mode)
93    Swarm(SwarmArgs),
94
95    /// Internal command: execute one swarm subtask payload.
96    #[command(hide = true)]
97    SwarmSubagent(SwarmSubagentArgs),
98
99    /// Analyze large content with RLM (Recursive Language Model)
100    Rlm(RlmArgs),
101
102    /// Deterministic oracle utilities (validate/sync)
103    Oracle(OracleArgs),
104
105    /// Autonomous PRD-driven agent loop (Ralph)
106    Ralph(RalphArgs),
107
108    /// Model Context Protocol (MCP) server/client
109    Mcp(McpArgs),
110
111    /// Show telemetry and execution statistics
112    Stats(StatsArgs),
113
114    /// Clean up orphaned worktrees and branches from failed Ralph runs
115    Cleanup(CleanupArgs),
116
117    /// List available models from all configured providers
118    Models(ModelsArgs),
119
120    /// Build a persistent codebase index for faster workspace introspection
121    Index(IndexArgs),
122
123    /// Run benchmark suite against models using Ralph PRDs
124    Benchmark(BenchmarkArgs),
125
126    /// Moltbook — social network for AI agents
127    Moltbook(MoltbookArgs),
128
129    /// Manage OKRs (Objectives and Key Results)
130    Okr(OkrArgs),
131
132    /// OKR-governed autonomous opportunity scanner/executor
133    Forage(ForageArgs),
134
135    /// LLM-routed search across grep/glob/web/memory/RLM backends
136    Search(SearchArgs),
137
138    /// Control a local Chromium browser via DevTools (alias: browser)
139    #[command(name = "browserctl", visible_alias = "browser")]
140    Browserctl(browserctl::BrowserCtlArgs),
141}
142
143#[derive(Parser, Debug)]
144pub struct AuthArgs {
145    #[command(subcommand)]
146    pub command: AuthCommand,
147}
148
149#[derive(Subcommand, Debug)]
150pub enum AuthCommand {
151    /// Authenticate with GitHub Copilot using device flow
152    Copilot(CopilotAuthArgs),
153
154    /// Authenticate OpenAI Codex with a ChatGPT subscription via OAuth (Plus/Pro/Team/Enterprise)
155    Codex(CodexAuthArgs),
156
157    /// Import browser cookies (Netscape format) into Vault for a provider
158    Cookies(CookieAuthArgs),
159
160    /// Register a new CodeTether account with email/password
161    Register(RegisterAuthArgs),
162
163    /// Login to a CodeTether server with email/password
164    Login(LoginAuthArgs),
165}
166
167#[derive(Parser, Debug)]
168pub struct RegisterAuthArgs {
169    /// CodeTether server URL (e.g., https://api.codetether.run)
170    #[arg(short, long, env = "CODETETHER_SERVER")]
171    pub server: String,
172
173    /// Email address
174    #[arg(short, long)]
175    pub email: Option<String>,
176
177    /// First name (optional)
178    #[arg(long)]
179    pub first_name: Option<String>,
180
181    /// Last name (optional)
182    #[arg(long)]
183    pub last_name: Option<String>,
184
185    /// Referral source (optional)
186    #[arg(long)]
187    pub referral_source: Option<String>,
188}
189
190#[derive(Parser, Debug)]
191pub struct LoginAuthArgs {
192    /// CodeTether server URL (e.g., https://api.codetether.io)
193    #[arg(short, long, env = "CODETETHER_SERVER")]
194    pub server: String,
195
196    /// Email address
197    #[arg(short, long)]
198    pub email: Option<String>,
199}
200
201#[derive(Parser, Debug)]
202pub struct CopilotAuthArgs {
203    /// GitHub Enterprise URL or domain (e.g. company.ghe.com)
204    #[arg(long)]
205    pub enterprise_url: Option<String>,
206
207    /// GitHub OAuth app client ID for Copilot device flow
208    #[arg(long, env = "CODETETHER_COPILOT_OAUTH_CLIENT_ID")]
209    pub client_id: Option<String>,
210}
211
212#[derive(Parser, Debug)]
213pub struct CodexAuthArgs {
214    /// Use ChatGPT device-code authentication flow (recommended for remote SSH sessions)
215    #[arg(long, default_value_t = false)]
216    pub device_code: bool,
217}
218
219#[derive(Parser, Debug)]
220pub struct CookieAuthArgs {
221    /// Provider ID to store under (e.g. nextdoor-web, gemini-web)
222    #[arg(long, default_value = "nextdoor-web")]
223    pub provider: String,
224
225    /// Path to Netscape cookies export file
226    #[arg(long)]
227    pub file: PathBuf,
228
229    /// Keep all valid cookies instead of filtering to auth-focused names
230    #[arg(long, default_value_t = false)]
231    pub keep_all: bool,
232}
233
234#[derive(Parser, Debug)]
235pub struct TuiArgs {
236    /// Project directory
237    pub project: Option<PathBuf>,
238
239    /// Allow network access in sandboxed commands
240    #[arg(long)]
241    pub allow_network: bool,
242
243    /// Disable the built-in A2A peer endpoint. By default the TUI binds an
244    /// A2A peer with auto-port, auto-name, and mDNS-based peer discovery —
245    /// other CodeTether processes on the same host or LAN find each other
246    /// without flags. Pass `--no-a2a` for purely interactive mode.
247    #[arg(long = "no-a2a", action = clap::ArgAction::SetFalse, default_value_t = true)]
248    pub a2a: bool,
249
250    /// Override the auto-picked A2A port. By default the OS assigns one
251    /// (port 0). Specify a port for stable curl-able URLs.
252    #[arg(long)]
253    pub a2a_port: Option<u16>,
254
255    /// A2A bind hostname. The default (`0.0.0.0`) provides the intended
256    /// zero-config path: auto-port, auto-name, and mDNS discovery without
257    /// extra flags. Use `127.0.0.1` to make the peer loopback-only.
258    #[arg(long, default_value = "0.0.0.0")]
259    pub a2a_hostname: String,
260
261    /// Public URL published in the agent card.
262    #[arg(long)]
263    pub a2a_public_url: Option<String>,
264
265    /// Override the auto-picked agent name (default: <host>-<repo>-<pid>).
266    #[arg(long)]
267    pub a2a_name: Option<String>,
268
269    /// Optional description for the A2A card.
270    #[arg(long)]
271    pub a2a_description: Option<String>,
272
273    /// Explicit peer seed URLs (in addition to mDNS-discovered peers).
274    /// Useful for cross-host setups where mDNS isn't routable.
275    #[arg(long, value_delimiter = ',', env = "CODETETHER_A2A_PEERS")]
276    pub a2a_peer: Vec<String>,
277
278    /// Discovery interval for explicit --a2a-peer seeds, in seconds.
279    /// Clamped to ≥ 5. (mDNS is event-driven, not polled.)
280    #[arg(long, default_value = "15")]
281    pub a2a_discovery_interval_secs: u64,
282
283    /// Disable auto-intro to newly discovered peers.
284    #[arg(long = "a2a-no-auto-introduce", action = clap::ArgAction::SetFalse, default_value_t = true)]
285    pub a2a_auto_introduce: bool,
286
287    /// Disable mDNS-based peer discovery. Without mDNS, only explicit
288    /// --a2a-peer seeds are discovered. mDNS is on by default for true P2P.
289    #[arg(long = "a2a-no-mdns", action = clap::ArgAction::SetFalse, default_value_t = true)]
290    pub a2a_mdns: bool,
291}
292
293#[derive(Parser, Debug)]
294pub struct ServeArgs {
295    /// Port to listen on
296    #[arg(short, long, default_value = "4096")]
297    pub port: u16,
298
299    /// Hostname to bind to
300    #[arg(long, default_value = "127.0.0.1")]
301    pub hostname: String,
302
303    /// Enable mDNS discovery
304    #[arg(long)]
305    pub mdns: bool,
306}
307
308#[derive(Parser, Debug)]
309pub struct RunArgs {
310    /// Message to send (can be multiple words, quoted or unquoted)
311    pub message: String,
312
313    /// Continue the last session
314    #[arg(short, long)]
315    pub continue_session: bool,
316
317    /// Session ID to continue
318    #[arg(short, long)]
319    pub session: Option<String>,
320
321    /// Model to use (provider/model format)
322    #[arg(short, long)]
323    pub model: Option<String>,
324
325    /// Agent to use
326    #[arg(long)]
327    pub agent: Option<String>,
328
329    /// Output format
330    #[arg(long, default_value = "default", value_parser = ["default", "json"])]
331    pub format: String,
332
333    /// Files to attach
334    #[arg(short, long)]
335    pub file: Vec<PathBuf>,
336
337    /// Import and continue a Codex CLI session by ID
338    #[arg(long)]
339    pub codex_session: Option<String>,
340
341    /// Maximum agentic loop steps (default: 250, minimum: 1)
342    #[arg(long)]
343    pub max_steps: Option<usize>,
344
345    /// Auto-continue checkpoint/resume cycles until this many attempts (minimum: 1)
346    #[arg(long)]
347    pub auto_continue_until: Option<usize>,
348
349    /// Number of parallel speculative branches to race (1-8, default: 1).
350    /// When > 1, enables many-worlds speculative dev via the collapse controller.
351    #[arg(long, default_value = "1")]
352    pub branches: usize,
353
354    /// Optional comma-separated strategy prompts for speculative branches
355    /// (e.g. "planner,testfirst,minimal,refactor"). Defaults to built-in rotation.
356    #[arg(long, value_delimiter = ',')]
357    pub strategies: Vec<String>,
358}
359
360#[derive(Parser, Debug)]
361pub struct PrArgs {
362    #[command(subcommand)]
363    pub command: PrCommand,
364}
365
366#[derive(Subcommand, Debug)]
367pub enum PrCommand {
368    /// Create or update a pull request for the current branch
369    Create(CreatePrArgs),
370}
371
372#[derive(Parser, Debug, Clone)]
373pub struct CreatePrArgs {
374    /// Pull request title
375    #[arg(long)]
376    pub title: String,
377
378    /// Optional pull request body
379    #[arg(long)]
380    pub body: Option<String>,
381
382    /// Optional path to a pull request body file
383    #[arg(long)]
384    pub body_file: Option<PathBuf>,
385
386    /// Base branch
387    #[arg(long, default_value = "main")]
388    pub base: String,
389
390    /// Head branch override (defaults to current branch)
391    #[arg(long)]
392    pub head: Option<String>,
393
394    /// Create the PR as draft
395    #[arg(long, default_value_t = false)]
396    pub draft: bool,
397
398    /// Emit JSON instead of KEY=VALUE lines
399    #[arg(long, default_value_t = false)]
400    pub json: bool,
401
402    /// Project directory override
403    #[arg(long)]
404    pub project: Option<PathBuf>,
405}
406
407/// Arguments for standalone worker HTTP server (testing/debugging)
408#[derive(Parser, Debug, Clone)]
409pub struct WorkerServerArgs {
410    /// Hostname to bind
411    #[arg(long, default_value = "0.0.0.0")]
412    pub hostname: String,
413
414    /// Port to bind
415    #[arg(short, long, default_value = "8080")]
416    pub port: u16,
417}
418
419#[derive(Parser, Debug, Clone)]
420pub struct A2aArgs {
421    /// A2A server URL
422    #[arg(short, long, env = "CODETETHER_SERVER", default_value = crate::a2a::worker::DEFAULT_A2A_SERVER_URL)]
423    pub server: String,
424
425    /// Authentication token
426    #[arg(short, long, env = "CODETETHER_TOKEN")]
427    pub token: Option<String>,
428
429    /// Worker name
430    #[arg(short, long, env = "CODETETHER_WORKER_NAME")]
431    pub name: Option<String>,
432
433    /// Comma-separated list of workspace paths (alias: --codebases)
434    #[arg(short, long, visible_alias = "codebases")]
435    pub workspaces: Option<String>,
436
437    /// Maximum number of tasks this worker will process concurrently
438    #[arg(
439        long,
440        env = "CODETETHER_WORKER_MAX_CONCURRENT_TASKS",
441        default_value_t = 4
442    )]
443    pub max_concurrent_tasks: usize,
444
445    /// Auto-approve policy: all, safe (read-only), none
446    #[arg(long, default_value = "safe", value_parser = ["all", "safe", "none"])]
447    pub auto_approve: String,
448
449    /// Email for task completion reports
450    #[arg(short, long)]
451    pub email: Option<String>,
452
453    /// Push notification endpoint URL
454    #[arg(long)]
455    pub push_url: Option<String>,
456
457    /// Hostname to bind the worker HTTP server
458    #[arg(long, default_value = "0.0.0.0", env = "CODETETHER_WORKER_HOST")]
459    pub hostname: String,
460
461    /// Port for the worker HTTP server (for health/readiness probes)
462    #[arg(long, default_value = "8080", env = "CODETETHER_WORKER_PORT")]
463    pub port: u16,
464
465    /// Public base URL advertised for this worker's HTTP interfaces.
466    #[arg(long, env = "CODETETHER_WORKER_PUBLIC_URL")]
467    pub public_url: Option<String>,
468
469    /// Disable the worker HTTP server (for environments without K8s)
470    #[arg(long, env = "CODETETHER_WORKER_HTTP_DISABLED")]
471    pub no_http_server: bool,
472}
473
474#[derive(Parser, Debug, Clone)]
475pub struct GitCredentialHelperArgs {
476    /// Workspace/codebase ID for which Git credentials should be minted.
477    #[arg(long = "workspace-id", alias = "codebase-id")]
478    pub workspace_id: String,
479
480    /// CodeTether server URL.
481    #[arg(long, env = "CODETETHER_SERVER")]
482    pub server: Option<String>,
483
484    /// CodeTether bearer token.
485    #[arg(long, env = "CODETETHER_TOKEN")]
486    pub token: Option<String>,
487
488    /// Worker ID requesting the credential.
489    #[arg(long, env = "CODETETHER_WORKER_ID")]
490    pub worker_id: Option<String>,
491
492    /// Git helper operation (get/store/erase). Git passes this positionally.
493    pub operation: Option<String>,
494}
495
496#[derive(Parser, Debug, Clone)]
497pub struct SpawnArgs {
498    /// Agent name. Defaults to <host>-<repo>-<short-pid>.
499    #[arg(short, long)]
500    pub name: Option<String>,
501
502    /// Hostname to bind. 127.0.0.1 (default) is loopback-only and SAFE
503    /// (the peer is unreachable from outside the host) but mDNS multicast
504    /// does NOT traverse the Linux `lo` interface, so same-host peers
505    /// cannot find each other via mDNS at this binding. Use 0.0.0.0 to
506    /// be reachable on the LAN AND to enable mDNS auto-discovery between
507    /// same-host agents (multicast loops back through the real interface).
508    #[arg(long, default_value = "127.0.0.1")]
509    pub hostname: String,
510
511    /// Port to bind. 0 (default) lets the OS pick an available port.
512    /// Specify a port if you need a stable URL for curl scripts.
513    #[arg(short, long, default_value = "0")]
514    pub port: u16,
515
516    /// Public URL published in the agent card (defaults to http://<hostname>:<port>).
517    #[arg(long)]
518    pub public_url: Option<String>,
519
520    /// Optional custom agent description for the card
521    #[arg(short, long)]
522    pub description: Option<String>,
523
524    /// Explicit peer seed URLs (in addition to mDNS-discovered peers).
525    /// Useful for cross-host setups where mDNS isn't routable.
526    #[arg(long, value_delimiter = ',', env = "CODETETHER_A2A_PEERS")]
527    pub peer: Vec<String>,
528
529    /// Discovery interval for explicit --peer seeds, in seconds. Clamped
530    /// to ≥ 5. (mDNS discovery is event-driven, not polled.)
531    #[arg(long, default_value = "15")]
532    pub discovery_interval_secs: u64,
533
534    /// Disable sending an automatic intro message to newly discovered peers
535    #[arg(long = "no-auto-introduce", action = clap::ArgAction::SetFalse, default_value_t = true)]
536    pub auto_introduce: bool,
537
538    /// Disable mDNS announce + browse. Without mDNS, only explicit
539    /// --peer seeds are discovered. mDNS is on by default for true P2P.
540    #[arg(long = "no-mdns", action = clap::ArgAction::SetFalse, default_value_t = true)]
541    pub mdns: bool,
542}
543
544#[derive(Parser, Debug)]
545pub struct ConfigArgs {
546    /// Show current configuration
547    #[arg(long)]
548    pub show: bool,
549
550    /// Initialize default configuration
551    #[arg(long)]
552    pub init: bool,
553
554    /// Set a configuration value
555    #[arg(long)]
556    pub set: Option<String>,
557}
558
559#[derive(Parser, Debug)]
560pub struct ContextArgs {
561    #[command(subcommand)]
562    pub command: ContextCommand,
563}
564
565#[derive(Subcommand, Debug)]
566pub enum ContextCommand {
567    /// Emit a [CONTEXT RESET] marker into the latest session
568    Reset(ContextResetArgs),
569    /// Browse the latest session as virtual turn files
570    Browse(ContextBrowseArgs),
571}
572
573#[derive(Parser, Debug)]
574pub struct ContextResetArgs {
575    /// Optional summary for the reset marker
576    #[arg(long)]
577    pub summary: Option<String>,
578}
579
580#[derive(Parser, Debug)]
581pub struct ContextBrowseArgs {
582    #[command(subcommand)]
583    pub command: Option<ContextBrowseCommand>,
584}
585
586#[derive(Subcommand, Debug)]
587pub enum ContextBrowseCommand {
588    /// List virtual paths for each turn in the latest session
589    List,
590    /// Show one turn as markdown-like text
591    ShowTurn(ContextShowTurnArgs),
592}
593
594#[derive(Parser, Debug)]
595pub struct ContextShowTurnArgs {
596    /// Zero-based turn index
597    pub turn: usize,
598}
599
600#[derive(Parser, Debug)]
601pub struct SwarmArgs {
602    /// Task to execute with swarm
603    pub task: String,
604
605    /// Model to use (provider/model format, e.g. zai/glm-5 or openrouter/z-ai/glm-5)
606    #[arg(short, long)]
607    pub model: Option<String>,
608
609    /// Decomposition strategy: auto, domain, data, stage, none
610    #[arg(short = 's', long, default_value = "auto")]
611    pub strategy: String,
612
613    /// Maximum number of concurrent sub-agents
614    #[arg(long, default_value = "100")]
615    pub max_subagents: usize,
616
617    /// Maximum steps per sub-agent
618    #[arg(long, default_value = "100")]
619    pub max_steps: usize,
620
621    /// Timeout per sub-agent (seconds)
622    #[arg(long, default_value = "300")]
623    pub timeout: u64,
624
625    /// Output as JSON
626    #[arg(long)]
627    pub json: bool,
628
629    /// Sub-agent execution mode: local | k8s
630    #[arg(long, default_value = "local", value_parser = ["local", "k8s", "kubernetes", "kubernetes-pod", "pod"])]
631    pub execution_mode: String,
632
633    /// Maximum concurrent Kubernetes sub-agent pods when execution mode is k8s.
634    #[arg(long, default_value = "8")]
635    pub k8s_pod_budget: usize,
636
637    /// Optional image override for Kubernetes sub-agent pods.
638    #[arg(long)]
639    pub k8s_image: Option<String>,
640}
641
642#[derive(Parser, Debug)]
643pub struct SwarmSubagentArgs {
644    /// Base64 payload for the remote subtask (JSON encoded)
645    #[arg(long)]
646    pub payload_base64: Option<String>,
647
648    /// Read payload from an environment variable
649    #[arg(long, default_value = "CODETETHER_SWARM_SUBTASK_PAYLOAD")]
650    pub payload_env: String,
651}
652
653#[derive(Parser, Debug)]
654pub struct RlmArgs {
655    /// Query to answer about the content
656    pub query: String,
657
658    /// Model to use (provider/model format)
659    #[arg(short, long)]
660    pub model: Option<String>,
661
662    /// File paths to analyze
663    #[arg(short, long)]
664    pub file: Vec<PathBuf>,
665
666    /// Direct content to analyze (use - for stdin)
667    #[arg(long)]
668    pub content: Option<String>,
669
670    /// Content type hint: code, logs, conversation, documents, auto
671    #[arg(long, default_value = "auto")]
672    pub content_type: String,
673
674    /// Maximum tokens for output
675    #[arg(long, default_value = "4000")]
676    pub max_tokens: usize,
677
678    /// Output as JSON
679    #[arg(long)]
680    pub json: bool,
681
682    /// Enable verbose output (shows context summary)
683    #[arg(short, long)]
684    pub verbose: bool,
685
686    /// Deprecated no-op: oracle verification is now always enabled by default
687    #[arg(long)]
688    pub oracle_verify: bool,
689
690    /// Disable deterministic oracle verification (emergency opt-out)
691    #[arg(long)]
692    pub no_oracle_verify: bool,
693
694    /// Number of independent runs for semantic consensus verification
695    #[arg(long, default_value = "1")]
696    pub consensus_runs: usize,
697
698    /// Consensus threshold for semantic verification (1.0 = unanimous)
699    #[arg(long, default_value = "1.0")]
700    pub consensus_threshold: f32,
701
702    /// Temperature for RLM generation (defaults: 0.3 single run, 0.75 consensus)
703    #[arg(long)]
704    pub analysis_temperature: Option<f32>,
705
706    /// Directory to write split oracle JSONL datasets
707    #[arg(long)]
708    pub oracle_out_dir: Option<PathBuf>,
709
710    /// Output prefix for oracle split JSONL files
711    #[arg(long, default_value = "rlm_oracle")]
712    pub oracle_prefix: String,
713}
714
715#[derive(Parser, Debug)]
716pub struct OracleArgs {
717    #[command(subcommand)]
718    pub command: OracleCommand,
719}
720
721#[derive(Subcommand, Debug)]
722pub enum OracleCommand {
723    /// Validate a FINAL(JSON) payload deterministically against source content
724    Validate(OracleValidateArgs),
725    /// Sync pending oracle spool files to MinIO/S3
726    Sync(OracleSyncArgs),
727}
728
729#[derive(Parser, Debug)]
730pub struct OracleValidateArgs {
731    /// Query associated with the payload (used as trace prompt)
732    #[arg(long)]
733    pub query: String,
734
735    /// Source file to validate against
736    #[arg(short, long)]
737    pub file: Option<PathBuf>,
738
739    /// Source content to validate against (use - for stdin)
740    #[arg(long)]
741    pub content: Option<String>,
742
743    /// FINAL(JSON) payload string
744    #[arg(long)]
745    pub payload: Option<String>,
746
747    /// Path to a file containing FINAL(JSON) payload
748    #[arg(long)]
749    pub payload_file: Option<PathBuf>,
750
751    /// Output as JSON
752    #[arg(long)]
753    pub json: bool,
754
755    /// Persist validated record to oracle storage pipeline
756    #[arg(long)]
757    pub persist: bool,
758}
759
760#[derive(Parser, Debug)]
761pub struct OracleSyncArgs {
762    /// Output as JSON
763    #[arg(long)]
764    pub json: bool,
765}
766
767#[derive(Parser, Debug)]
768pub struct RalphArgs {
769    /// Action to perform
770    #[arg(value_parser = ["run", "status", "create-prd"])]
771    pub action: String,
772
773    /// Path to prd.json file
774    #[arg(short, long, default_value = "prd.json")]
775    pub prd: PathBuf,
776
777    /// Feature name (for create-prd)
778    #[arg(short, long)]
779    pub feature: Option<String>,
780
781    /// Project name (for create-prd)
782    #[arg(long = "project-name")]
783    pub project_name: Option<String>,
784
785    /// Maximum iterations
786    #[arg(long, default_value = "10")]
787    pub max_iterations: usize,
788
789    /// Model to use
790    #[arg(short, long)]
791    pub model: Option<String>,
792
793    /// Output as JSON
794    #[arg(long)]
795    pub json: bool,
796}
797
798#[derive(Parser, Debug)]
799pub struct McpArgs {
800    /// Action to perform
801    #[arg(value_parser = ["serve", "connect", "list-tools", "call"])]
802    pub action: String,
803
804    /// Command to spawn for connecting to MCP server (flag form)
805    #[arg(short, long)]
806    pub command: Option<String>,
807
808    /// Server name for registry
809    #[arg(long)]
810    pub server_name: Option<String>,
811
812    /// Tool name for call action
813    #[arg(long)]
814    pub tool: Option<String>,
815
816    /// JSON arguments for tool call
817    #[arg(long)]
818    pub arguments: Option<String>,
819
820    /// Output as JSON
821    #[arg(long)]
822    pub json: bool,
823
824    /// Resolve a worker's first-class bus connection via the control plane.
825    #[arg(long)]
826    pub worker_id: Option<String>,
827
828    /// Resolve the owning worker via a registered workspace ID.
829    #[arg(long)]
830    pub workspace_id: Option<String>,
831
832    /// URL of the CodeTether HTTP server's bus SSE endpoint.
833    /// When set, the MCP server connects to the agent bus and exposes
834    /// bus_events, bus_status, and ralph_status tools plus codetether:// resources.
835    /// Example: http://localhost:8001/v1/bus/stream
836    #[arg(long)]
837    pub bus_url: Option<String>,
838
839    /// Command (and args) to spawn for connecting to MCP server (positional form).
840    /// Example: `codetether mcp connect npx -y @modelcontextprotocol/server-filesystem /path`
841    #[arg(allow_hyphen_values = true)]
842    pub command_args: Vec<String>,
843}
844
845#[derive(Parser, Debug)]
846pub struct StatsArgs {
847    /// Show tool execution history
848    #[arg(short, long)]
849    pub tools: bool,
850
851    /// Show file change history
852    #[arg(short, long)]
853    pub files: bool,
854
855    /// Show token usage
856    #[arg(long)]
857    pub tokens: bool,
858
859    /// Filter by tool name
860    #[arg(long)]
861    pub tool: Option<String>,
862
863    /// Filter by file path
864    #[arg(long)]
865    pub file: Option<String>,
866
867    /// Number of recent entries to show
868    #[arg(short, long, default_value = "20")]
869    pub limit: usize,
870
871    /// Output as JSON
872    #[arg(long)]
873    pub json: bool,
874
875    /// Show all/summary (default shows summary)
876    #[arg(long)]
877    pub all: bool,
878}
879
880#[derive(Parser, Debug)]
881pub struct CleanupArgs {
882    /// Dry run - show what would be cleaned up without deleting
883    #[arg(short, long)]
884    pub dry_run: bool,
885
886    /// Clean up worktrees only (not branches)
887    #[arg(long)]
888    pub worktrees_only: bool,
889
890    /// Output as JSON
891    #[arg(long)]
892    pub json: bool,
893}
894
895#[derive(Parser, Debug)]
896pub struct ModelsArgs {
897    /// Filter by provider name
898    #[arg(short, long)]
899    pub provider: Option<String>,
900
901    /// Output as JSON
902    #[arg(long)]
903    pub json: bool,
904}
905
906#[derive(Parser, Debug)]
907pub struct IndexArgs {
908    /// Root directory to index (defaults to current directory)
909    #[arg(short, long)]
910    pub path: Option<PathBuf>,
911
912    /// Output file for index JSON (defaults to CODETETHER_DATA_DIR/indexes/...)
913    #[arg(short, long)]
914    pub output: Option<PathBuf>,
915
916    /// Maximum file size to index in KiB
917    #[arg(long, default_value = "1024")]
918    pub max_file_size_kib: u64,
919
920    /// Local open-source embedding model identifier
921    #[arg(long, default_value = "hash-v1")]
922    pub embedding_model: String,
923
924    /// Embedding provider ID (`local`, `huggingface`, etc.)
925    #[arg(long, default_value = "local")]
926    pub embedding_provider: String,
927
928    /// Embedding vector dimensions
929    #[arg(long, default_value = "384")]
930    pub embedding_dimensions: usize,
931
932    /// Number of documents per embedding batch
933    #[arg(long, default_value = "32")]
934    pub embedding_batch_size: usize,
935
936    /// Maximum retry attempts for a failed remote embedding batch
937    #[arg(long, default_value = "3")]
938    pub embedding_max_retries: u32,
939
940    /// Initial retry backoff in milliseconds for remote embedding failures
941    #[arg(long, default_value = "250")]
942    pub embedding_retry_initial_ms: u64,
943
944    /// Maximum retry backoff in milliseconds for remote embedding failures
945    #[arg(long, default_value = "2000")]
946    pub embedding_retry_max_ms: u64,
947
948    /// Max characters per file used for embedding input
949    #[arg(long, default_value = "8000")]
950    pub embedding_input_chars: usize,
951
952    /// Include hidden files/directories (dotfiles)
953    #[arg(long, default_value_t = false)]
954    pub include_hidden: bool,
955
956    /// Output result as JSON
957    #[arg(long)]
958    pub json: bool,
959}
960
961#[derive(Parser, Debug)]
962pub struct MoltbookArgs {
963    #[command(subcommand)]
964    pub command: MoltbookCommand,
965}
966
967#[derive(Subcommand, Debug)]
968pub enum MoltbookCommand {
969    /// Register a new agent on Moltbook
970    Register(MoltbookRegisterArgs),
971
972    /// Check claim status
973    Status,
974
975    /// View your Moltbook profile
976    Profile,
977
978    /// Update your profile description
979    UpdateProfile(MoltbookUpdateProfileArgs),
980
981    /// Create a post (defaults to m/general)
982    Post(MoltbookPostArgs),
983
984    /// Post a CodeTether introduction to Moltbook
985    Intro,
986
987    /// Run a heartbeat — check feed, show recent posts
988    Heartbeat,
989
990    /// Comment on a Moltbook post
991    Comment(MoltbookCommentArgs),
992
993    /// Search Moltbook posts and comments
994    Search(MoltbookSearchArgs),
995}
996
997#[derive(Parser, Debug)]
998pub struct MoltbookRegisterArgs {
999    /// Agent name to register on Moltbook
1000    pub name: String,
1001
1002    /// Optional extra description (CodeTether branding is always included)
1003    #[arg(short, long)]
1004    pub description: Option<String>,
1005}
1006
1007#[derive(Parser, Debug)]
1008pub struct MoltbookUpdateProfileArgs {
1009    /// Extra description to append
1010    #[arg(short, long)]
1011    pub description: Option<String>,
1012}
1013
1014#[derive(Parser, Debug)]
1015pub struct MoltbookPostArgs {
1016    /// Post title
1017    pub title: String,
1018
1019    /// Post content
1020    #[arg(short, long)]
1021    pub content: String,
1022
1023    /// Submolt to post in
1024    #[arg(short, long, default_value = "general")]
1025    pub submolt: String,
1026}
1027
1028#[derive(Parser, Debug)]
1029pub struct MoltbookCommentArgs {
1030    /// Post ID to comment on
1031    pub post_id: String,
1032
1033    /// Comment content
1034    pub content: String,
1035}
1036
1037#[derive(Parser, Debug)]
1038pub struct MoltbookSearchArgs {
1039    /// Search query
1040    pub query: String,
1041
1042    /// Max results
1043    #[arg(short, long, default_value = "10")]
1044    pub limit: usize,
1045}
1046
1047#[derive(Parser, Debug)]
1048pub struct BenchmarkArgs {
1049    /// Directory containing benchmark PRD files
1050    #[arg(long, default_value = "benchmarks")]
1051    pub prd_dir: String,
1052
1053    /// Models to benchmark (comma-separated, format: provider:model)
1054    #[arg(short, long, value_delimiter = ',')]
1055    pub models: Vec<String>,
1056
1057    /// Only run PRDs matching this tier (1, 2, or 3)
1058    #[arg(long)]
1059    pub tier: Option<u8>,
1060
1061    /// Run model×PRD combos in parallel
1062    #[arg(long)]
1063    pub parallel: bool,
1064
1065    /// Maximum iterations per story
1066    #[arg(long, default_value = "10")]
1067    pub max_iterations: usize,
1068
1069    /// Timeout per story in seconds
1070    #[arg(long, default_value = "300")]
1071    pub story_timeout: u64,
1072
1073    /// Output file path
1074    #[arg(short, long, default_value = "benchmark_results.json")]
1075    pub output: String,
1076
1077    /// Cost ceiling per run in USD (prevents runaway spending)
1078    #[arg(long, default_value = "50.0")]
1079    pub cost_ceiling: f64,
1080
1081    /// Submit results to this API URL
1082    #[arg(long)]
1083    pub submit_url: Option<String>,
1084
1085    /// API key for submitting results (Bearer token)
1086    #[arg(long, env = "BENCHMARK_API_KEY")]
1087    pub submit_key: Option<String>,
1088
1089    /// Output as JSON to stdout
1090    #[arg(long)]
1091    pub json: bool,
1092}
1093
1094#[derive(Parser, Debug)]
1095pub struct OkrArgs {
1096    /// Action to perform
1097    #[arg(value_parser = ["list", "status", "create", "runs", "export", "stats", "report"])]
1098    pub action: String,
1099
1100    /// OKR ID (UUID)
1101    #[arg(short, long)]
1102    pub id: Option<String>,
1103
1104    /// OKR title (for create)
1105    #[arg(short, long)]
1106    pub title: Option<String>,
1107
1108    /// OKR description (for create)
1109    #[arg(short, long)]
1110    pub description: Option<String>,
1111
1112    /// Target value for key result (for create)
1113    #[arg(long)]
1114    pub target: Option<f64>,
1115
1116    /// Unit for key result (for create)
1117    #[arg(long, default_value = "%")]
1118    pub unit: String,
1119
1120    /// Filter by status: draft, active, completed, cancelled, on_hold
1121    #[arg(long)]
1122    pub status: Option<String>,
1123
1124    /// Filter by owner
1125    #[arg(long)]
1126    pub owner: Option<String>,
1127
1128    /// Output as JSON
1129    #[arg(long)]
1130    pub json: bool,
1131
1132    /// Include evidence links in output
1133    #[arg(long)]
1134    pub evidence: bool,
1135}
1136
1137#[derive(Parser, Debug)]
1138#[command(
1139    about = "LLM-routed search across grep/glob/web/memory/RLM backends",
1140    long_about = "Uses the configured LLM router (default zai/glm-5.1) to pick the best search backend for your query, runs it, and returns normalized results.",
1141    after_long_help = "Examples:\n  codetether search \"where is fn main\"\n  codetether search --json \"how to center a div\"\n  codetether search --top-n 3 --router-model zai/glm-5.1 \"latest rust async trait docs\""
1142)]
1143pub struct SearchArgs {
1144    /// Natural-language search query
1145    pub query: String,
1146
1147    /// Maximum number of backends to run
1148    #[arg(long, default_value = "1")]
1149    pub top_n: usize,
1150
1151    /// Override router model (default zai/glm-5.1 or CODETETHER_SEARCH_ROUTER_MODEL)
1152    #[arg(long)]
1153    pub router_model: Option<String>,
1154
1155    /// Emit structured JSON instead of human output
1156    #[arg(long)]
1157    pub json: bool,
1158}
1159
1160#[derive(Parser, Debug)]
1161#[command(
1162    about = "OKR-governed autonomous opportunity scanner/executor",
1163    long_about = "Scan OKRs/KRs for high-priority opportunities and optionally execute the top selections with the build agent.",
1164    after_long_help = "Examples:\n  codetether forage --top 5\n  codetether forage --loop --interval-secs 600\n  codetether forage --loop --execute --top 1 --interval-secs 600 --max-cycles 48 --run-timeout-secs 900 --model minimax/MiniMax-M2.5\n  codetether forage --loop --execute --execution-engine swarm --swarm-max-subagents 8 --swarm-strategy auto --model zai/glm-5"
1165)]
1166pub struct ForageArgs {
1167    /// Show top-N opportunities each cycle
1168    #[arg(long, default_value = "3")]
1169    pub top: usize,
1170
1171    /// Keep running continuously
1172    #[arg(long = "loop")]
1173    pub loop_mode: bool,
1174
1175    /// Seconds between loop cycles
1176    #[arg(long, default_value = "120")]
1177    pub interval_secs: u64,
1178
1179    /// Maximum cycles when looping (0 = unlimited)
1180    #[arg(long, default_value = "0")]
1181    pub max_cycles: usize,
1182
1183    /// Execute selected opportunities via `codetether run`
1184    #[arg(long)]
1185    pub execute: bool,
1186
1187    /// Disable S3/MinIO archival requirement (for local-only execution)
1188    #[arg(long)]
1189    pub no_s3: bool,
1190
1191    /// Moonshot mission statement(s) used as a strategic rubric for prioritization.
1192    ///
1193    /// Repeat the flag to provide multiple missions.
1194    #[arg(long = "moonshot")]
1195    pub moonshots: Vec<String>,
1196
1197    /// Optional file containing moonshot mission statements (JSON array or newline-delimited text).
1198    #[arg(long)]
1199    pub moonshot_file: Option<PathBuf>,
1200
1201    /// Require minimum moonshot alignment for an opportunity to be selected.
1202    #[arg(long)]
1203    pub moonshot_required: bool,
1204
1205    /// Minimum moonshot alignment ratio when `--moonshot-required` is enabled.
1206    #[arg(long, default_value = "0.10")]
1207    pub moonshot_min_alignment: f64,
1208
1209    /// Execution engine for `--execute`: run | swarm | go
1210    #[arg(long, default_value = "run", value_parser = ["run", "swarm", "go"])]
1211    pub execution_engine: String,
1212
1213    /// Timeout per execution task in seconds (only used with --execute)
1214    #[arg(long, default_value = "900")]
1215    pub run_timeout_secs: u64,
1216
1217    /// Stop immediately on execution error/timeout (default: continue loop)
1218    #[arg(long)]
1219    pub fail_fast: bool,
1220
1221    /// Swarm decomposition strategy when --execution-engine=swarm
1222    #[arg(long, default_value = "auto", value_parser = ["auto", "domain", "data", "stage", "none"])]
1223    pub swarm_strategy: String,
1224
1225    /// Maximum concurrent sub-agents when --execution-engine=swarm
1226    #[arg(long, default_value = "8")]
1227    pub swarm_max_subagents: usize,
1228
1229    /// Maximum steps per sub-agent when --execution-engine=swarm
1230    #[arg(long, default_value = "100")]
1231    pub swarm_max_steps: usize,
1232
1233    /// Timeout per sub-agent in seconds when --execution-engine=swarm
1234    #[arg(long, default_value = "300")]
1235    pub swarm_subagent_timeout_secs: u64,
1236
1237    /// Optional model override for execution mode
1238    #[arg(short, long)]
1239    pub model: Option<String>,
1240
1241    /// Output as JSON
1242    #[arg(long)]
1243    pub json: bool,
1244}