Skip to main content

codetether_agent/cli/
mod.rs

1//! CLI command definitions and handlers
2
3pub mod auth;
4pub mod config;
5pub mod go_ralph;
6pub mod run;
7
8use clap::{Parser, Subcommand};
9use std::path::PathBuf;
10
11/// CodeTether Agent - A2A-native AI coding agent
12///
13/// By default, runs as an A2A worker connecting to the CodeTether server.
14/// Use the 'tui' subcommand for interactive terminal mode.
15#[derive(Parser, Debug)]
16#[command(name = "codetether")]
17#[command(version, about, long_about = None)]
18pub struct Cli {
19    /// Project directory to operate on
20    #[arg(global = true)]
21    pub project: Option<PathBuf>,
22
23    /// Print logs to stderr
24    #[arg(long, global = true)]
25    pub print_logs: bool,
26
27    /// Log level
28    #[arg(long, global = true, value_parser = ["DEBUG", "INFO", "WARN", "ERROR"])]
29    pub log_level: Option<String>,
30
31    // Default A2A args (when no subcommand)
32    /// A2A server URL (default mode)
33    #[arg(short, long, env = "CODETETHER_SERVER")]
34    pub server: Option<String>,
35
36    /// Authentication token
37    #[arg(short, long, env = "CODETETHER_TOKEN")]
38    pub token: Option<String>,
39
40    /// Worker name
41    #[arg(short, long, env = "CODETETHER_WORKER_NAME")]
42    pub name: Option<String>,
43
44    #[command(subcommand)]
45    pub command: Option<Command>,
46}
47
48#[derive(Subcommand, Debug)]
49pub enum Command {
50    /// Start interactive terminal UI
51    Tui(TuiArgs),
52
53    /// Start a headless API server
54    Serve(ServeArgs),
55
56    /// Run with a message (non-interactive)
57    Run(RunArgs),
58
59    /// Authenticate provider credentials and store in Vault
60    Auth(AuthArgs),
61
62    /// Manage configuration
63    Config(ConfigArgs),
64
65    /// A2A worker mode (explicit - also the default)
66    Worker(A2aArgs),
67
68    /// Spawn an A2A agent runtime with auto card registration and peer discovery
69    Spawn(SpawnArgs),
70
71    /// Execute task with parallel sub-agents (swarm mode)
72    Swarm(SwarmArgs),
73
74    /// Internal command: execute one swarm subtask payload.
75    #[command(hide = true)]
76    SwarmSubagent(SwarmSubagentArgs),
77
78    /// Analyze large content with RLM (Recursive Language Model)
79    Rlm(RlmArgs),
80
81    /// Autonomous PRD-driven agent loop (Ralph)
82    Ralph(RalphArgs),
83
84    /// Model Context Protocol (MCP) server/client
85    Mcp(McpArgs),
86
87    /// Show telemetry and execution statistics
88    Stats(StatsArgs),
89
90    /// Clean up orphaned worktrees and branches from failed Ralph runs
91    Cleanup(CleanupArgs),
92
93    /// List available models from all configured providers
94    Models(ModelsArgs),
95
96    /// Run benchmark suite against models using Ralph PRDs
97    Benchmark(BenchmarkArgs),
98
99    /// Moltbook — social network for AI agents
100    Moltbook(MoltbookArgs),
101
102    /// Manage OKRs (Objectives and Key Results)
103    Okr(OkrArgs),
104}
105
106#[derive(Parser, Debug)]
107pub struct AuthArgs {
108    #[command(subcommand)]
109    pub command: AuthCommand,
110}
111
112#[derive(Subcommand, Debug)]
113pub enum AuthCommand {
114    /// Authenticate with GitHub Copilot using device flow
115    Copilot(CopilotAuthArgs),
116
117    /// Register a new CodeTether account with email/password
118    Register(RegisterAuthArgs),
119
120    /// Login to a CodeTether server with email/password
121    Login(LoginAuthArgs),
122}
123
124#[derive(Parser, Debug)]
125pub struct RegisterAuthArgs {
126    /// CodeTether server URL (e.g., https://api.codetether.run)
127    #[arg(short, long, env = "CODETETHER_SERVER")]
128    pub server: String,
129
130    /// Email address
131    #[arg(short, long)]
132    pub email: Option<String>,
133
134    /// First name (optional)
135    #[arg(long)]
136    pub first_name: Option<String>,
137
138    /// Last name (optional)
139    #[arg(long)]
140    pub last_name: Option<String>,
141
142    /// Referral source (optional)
143    #[arg(long)]
144    pub referral_source: Option<String>,
145}
146
147#[derive(Parser, Debug)]
148pub struct LoginAuthArgs {
149    /// CodeTether server URL (e.g., https://api.codetether.io)
150    #[arg(short, long, env = "CODETETHER_SERVER")]
151    pub server: String,
152
153    /// Email address
154    #[arg(short, long)]
155    pub email: Option<String>,
156}
157
158#[derive(Parser, Debug)]
159pub struct CopilotAuthArgs {
160    /// GitHub Enterprise URL or domain (e.g. company.ghe.com)
161    #[arg(long)]
162    pub enterprise_url: Option<String>,
163
164    /// GitHub OAuth app client ID for Copilot device flow
165    #[arg(long, env = "CODETETHER_COPILOT_OAUTH_CLIENT_ID")]
166    pub client_id: Option<String>,
167}
168
169#[derive(Parser, Debug)]
170pub struct TuiArgs {
171    /// Project directory
172    pub project: Option<PathBuf>,
173}
174
175#[derive(Parser, Debug)]
176pub struct ServeArgs {
177    /// Port to listen on
178    #[arg(short, long, default_value = "4096")]
179    pub port: u16,
180
181    /// Hostname to bind to
182    #[arg(long, default_value = "127.0.0.1")]
183    pub hostname: String,
184
185    /// Enable mDNS discovery
186    #[arg(long)]
187    pub mdns: bool,
188}
189
190#[derive(Parser, Debug)]
191pub struct RunArgs {
192    /// Message to send (can be multiple words, quoted or unquoted)
193    pub message: String,
194
195    /// Continue the last session
196    #[arg(short, long)]
197    pub continue_session: bool,
198
199    /// Session ID to continue
200    #[arg(short, long)]
201    pub session: Option<String>,
202
203    /// Model to use (provider/model format)
204    #[arg(short, long)]
205    pub model: Option<String>,
206
207    /// Agent to use
208    #[arg(long)]
209    pub agent: Option<String>,
210
211    /// Output format
212    #[arg(long, default_value = "default", value_parser = ["default", "json"])]
213    pub format: String,
214
215    /// Files to attach
216    #[arg(short, long)]
217    pub file: Vec<PathBuf>,
218}
219
220/// Arguments for standalone worker HTTP server (testing/debugging)
221#[derive(Parser, Debug, Clone)]
222pub struct WorkerServerArgs {
223    /// Hostname to bind
224    #[arg(long, default_value = "0.0.0.0")]
225    pub hostname: String,
226
227    /// Port to bind
228    #[arg(short, long, default_value = "8080")]
229    pub port: u16,
230}
231
232#[derive(Parser, Debug, Clone)]
233pub struct A2aArgs {
234    /// A2A server URL
235    #[arg(short, long, env = "CODETETHER_SERVER", default_value = crate::a2a::worker::DEFAULT_A2A_SERVER_URL)]
236    pub server: String,
237
238    /// Authentication token
239    #[arg(short, long, env = "CODETETHER_TOKEN")]
240    pub token: Option<String>,
241
242    /// Worker name
243    #[arg(short, long, env = "CODETETHER_WORKER_NAME")]
244    pub name: Option<String>,
245
246    /// Comma-separated list of workspace paths (alias: --codebases)
247    #[arg(short, long, visible_alias = "codebases")]
248    pub workspaces: Option<String>,
249
250    /// Auto-approve policy: all, safe (read-only), none
251    #[arg(long, default_value = "safe", value_parser = ["all", "safe", "none"])]
252    pub auto_approve: String,
253
254    /// Email for task completion reports
255    #[arg(short, long)]
256    pub email: Option<String>,
257
258    /// Push notification endpoint URL
259    #[arg(long)]
260    pub push_url: Option<String>,
261
262    /// Hostname to bind the worker HTTP server
263    #[arg(long, default_value = "0.0.0.0", env = "CODETETHER_WORKER_HOST")]
264    pub hostname: String,
265
266    /// Port for the worker HTTP server (for health/readiness probes)
267    #[arg(long, default_value = "8080", env = "CODETETHER_WORKER_PORT")]
268    pub port: u16,
269
270    /// Disable the worker HTTP server (for environments without K8s)
271    #[arg(long, env = "CODETETHER_WORKER_HTTP_DISABLED")]
272    pub no_http_server: bool,
273}
274
275#[derive(Parser, Debug, Clone)]
276pub struct SpawnArgs {
277    /// Agent name
278    #[arg(short, long)]
279    pub name: Option<String>,
280
281    /// Hostname to bind the spawned A2A agent
282    #[arg(long, default_value = "127.0.0.1")]
283    pub hostname: String,
284
285    /// Port to bind the spawned A2A agent
286    #[arg(short, long, default_value = "4097")]
287    pub port: u16,
288
289    /// Public URL published in the agent card (defaults to http://<hostname>:<port>)
290    #[arg(long)]
291    pub public_url: Option<String>,
292
293    /// Optional custom agent description for the card
294    #[arg(short, long)]
295    pub description: Option<String>,
296
297    /// Peer seed URLs to discover and talk to (repeat flag or comma-separated)
298    #[arg(long, value_delimiter = ',', env = "CODETETHER_A2A_PEERS")]
299    pub peer: Vec<String>,
300
301    /// Discovery interval in seconds
302    #[arg(long, default_value = "15")]
303    pub discovery_interval_secs: u64,
304
305    /// Disable sending an automatic intro message to newly discovered peers
306    #[arg(long = "no-auto-introduce", action = clap::ArgAction::SetFalse, default_value_t = true)]
307    pub auto_introduce: bool,
308}
309
310#[derive(Parser, Debug)]
311pub struct ConfigArgs {
312    /// Show current configuration
313    #[arg(long)]
314    pub show: bool,
315
316    /// Initialize default configuration
317    #[arg(long)]
318    pub init: bool,
319
320    /// Set a configuration value
321    #[arg(long)]
322    pub set: Option<String>,
323}
324
325#[derive(Parser, Debug)]
326pub struct SwarmArgs {
327    /// Task to execute with swarm
328    pub task: String,
329
330    /// Model to use (provider/model format, e.g. zai/glm-5 or openrouter/z-ai/glm-5)
331    #[arg(short, long)]
332    pub model: Option<String>,
333
334    /// Decomposition strategy: auto, domain, data, stage, none
335    #[arg(short = 's', long, default_value = "auto")]
336    pub strategy: String,
337
338    /// Maximum number of concurrent sub-agents
339    #[arg(long, default_value = "100")]
340    pub max_subagents: usize,
341
342    /// Maximum steps per sub-agent
343    #[arg(long, default_value = "100")]
344    pub max_steps: usize,
345
346    /// Timeout per sub-agent (seconds)
347    #[arg(long, default_value = "300")]
348    pub timeout: u64,
349
350    /// Output as JSON
351    #[arg(long)]
352    pub json: bool,
353
354    /// Sub-agent execution mode: local | k8s
355    #[arg(long, default_value = "local", value_parser = ["local", "k8s", "kubernetes", "kubernetes-pod", "pod"])]
356    pub execution_mode: String,
357
358    /// Maximum concurrent Kubernetes sub-agent pods when execution mode is k8s.
359    #[arg(long, default_value = "8")]
360    pub k8s_pod_budget: usize,
361
362    /// Optional image override for Kubernetes sub-agent pods.
363    #[arg(long)]
364    pub k8s_image: Option<String>,
365}
366
367#[derive(Parser, Debug)]
368pub struct SwarmSubagentArgs {
369    /// Base64 payload for the remote subtask (JSON encoded)
370    #[arg(long)]
371    pub payload_base64: Option<String>,
372
373    /// Read payload from an environment variable
374    #[arg(long, default_value = "CODETETHER_SWARM_SUBTASK_PAYLOAD")]
375    pub payload_env: String,
376}
377
378#[derive(Parser, Debug)]
379pub struct RlmArgs {
380    /// Query to answer about the content
381    pub query: String,
382
383    /// File paths to analyze
384    #[arg(short, long)]
385    pub file: Vec<PathBuf>,
386
387    /// Direct content to analyze (use - for stdin)
388    #[arg(long)]
389    pub content: Option<String>,
390
391    /// Content type hint: code, logs, conversation, documents, auto
392    #[arg(long, default_value = "auto")]
393    pub content_type: String,
394
395    /// Maximum tokens for output
396    #[arg(long, default_value = "4000")]
397    pub max_tokens: usize,
398
399    /// Output as JSON
400    #[arg(long)]
401    pub json: bool,
402
403    /// Enable verbose output (shows context summary)
404    #[arg(short, long)]
405    pub verbose: bool,
406}
407
408#[derive(Parser, Debug)]
409pub struct RalphArgs {
410    /// Action to perform
411    #[arg(value_parser = ["run", "status", "create-prd"])]
412    pub action: String,
413
414    /// Path to prd.json file
415    #[arg(short, long, default_value = "prd.json")]
416    pub prd: PathBuf,
417
418    /// Feature name (for create-prd)
419    #[arg(short, long)]
420    pub feature: Option<String>,
421
422    /// Project name (for create-prd)
423    #[arg(long = "project-name")]
424    pub project_name: Option<String>,
425
426    /// Maximum iterations
427    #[arg(long, default_value = "10")]
428    pub max_iterations: usize,
429
430    /// Model to use
431    #[arg(short, long)]
432    pub model: Option<String>,
433
434    /// Output as JSON
435    #[arg(long)]
436    pub json: bool,
437}
438
439#[derive(Parser, Debug)]
440pub struct McpArgs {
441    /// Action to perform
442    #[arg(value_parser = ["serve", "connect", "list-tools", "call"])]
443    pub action: String,
444
445    /// Command to spawn for connecting to MCP server
446    #[arg(short, long)]
447    pub command: Option<String>,
448
449    /// Server name for registry
450    #[arg(long)]
451    pub server_name: Option<String>,
452
453    /// Tool name for call action
454    #[arg(long)]
455    pub tool: Option<String>,
456
457    /// JSON arguments for tool call
458    #[arg(long)]
459    pub arguments: Option<String>,
460
461    /// Output as JSON
462    #[arg(long)]
463    pub json: bool,
464
465    /// URL of the CodeTether HTTP server's bus SSE endpoint.
466    /// When set, the MCP server connects to the agent bus and exposes
467    /// bus_events, bus_status, and ralph_status tools plus codetether:// resources.
468    /// Example: http://localhost:8001/v1/bus/stream
469    #[arg(long)]
470    pub bus_url: Option<String>,
471}
472
473#[derive(Parser, Debug)]
474pub struct StatsArgs {
475    /// Show tool execution history
476    #[arg(short, long)]
477    pub tools: bool,
478
479    /// Show file change history
480    #[arg(short, long)]
481    pub files: bool,
482
483    /// Show token usage
484    #[arg(long)]
485    pub tokens: bool,
486
487    /// Filter by tool name
488    #[arg(long)]
489    pub tool: Option<String>,
490
491    /// Filter by file path
492    #[arg(long)]
493    pub file: Option<String>,
494
495    /// Number of recent entries to show
496    #[arg(short, long, default_value = "20")]
497    pub limit: usize,
498
499    /// Output as JSON
500    #[arg(long)]
501    pub json: bool,
502
503    /// Show all/summary (default shows summary)
504    #[arg(long)]
505    pub all: bool,
506}
507
508#[derive(Parser, Debug)]
509pub struct CleanupArgs {
510    /// Dry run - show what would be cleaned up without deleting
511    #[arg(short, long)]
512    pub dry_run: bool,
513
514    /// Clean up worktrees only (not branches)
515    #[arg(long)]
516    pub worktrees_only: bool,
517
518    /// Output as JSON
519    #[arg(long)]
520    pub json: bool,
521}
522
523#[derive(Parser, Debug)]
524pub struct ModelsArgs {
525    /// Filter by provider name
526    #[arg(short, long)]
527    pub provider: Option<String>,
528
529    /// Output as JSON
530    #[arg(long)]
531    pub json: bool,
532}
533
534#[derive(Parser, Debug)]
535pub struct MoltbookArgs {
536    #[command(subcommand)]
537    pub command: MoltbookCommand,
538}
539
540#[derive(Subcommand, Debug)]
541pub enum MoltbookCommand {
542    /// Register a new agent on Moltbook
543    Register(MoltbookRegisterArgs),
544
545    /// Check claim status
546    Status,
547
548    /// View your Moltbook profile
549    Profile,
550
551    /// Update your profile description
552    UpdateProfile(MoltbookUpdateProfileArgs),
553
554    /// Create a post (defaults to m/general)
555    Post(MoltbookPostArgs),
556
557    /// Post a CodeTether introduction to Moltbook
558    Intro,
559
560    /// Run a heartbeat — check feed, show recent posts
561    Heartbeat,
562
563    /// Comment on a Moltbook post
564    Comment(MoltbookCommentArgs),
565
566    /// Search Moltbook posts and comments
567    Search(MoltbookSearchArgs),
568}
569
570#[derive(Parser, Debug)]
571pub struct MoltbookRegisterArgs {
572    /// Agent name to register on Moltbook
573    pub name: String,
574
575    /// Optional extra description (CodeTether branding is always included)
576    #[arg(short, long)]
577    pub description: Option<String>,
578}
579
580#[derive(Parser, Debug)]
581pub struct MoltbookUpdateProfileArgs {
582    /// Extra description to append
583    #[arg(short, long)]
584    pub description: Option<String>,
585}
586
587#[derive(Parser, Debug)]
588pub struct MoltbookPostArgs {
589    /// Post title
590    pub title: String,
591
592    /// Post content
593    #[arg(short, long)]
594    pub content: String,
595
596    /// Submolt to post in
597    #[arg(short, long, default_value = "general")]
598    pub submolt: String,
599}
600
601#[derive(Parser, Debug)]
602pub struct MoltbookCommentArgs {
603    /// Post ID to comment on
604    pub post_id: String,
605
606    /// Comment content
607    pub content: String,
608}
609
610#[derive(Parser, Debug)]
611pub struct MoltbookSearchArgs {
612    /// Search query
613    pub query: String,
614
615    /// Max results
616    #[arg(short, long, default_value = "10")]
617    pub limit: usize,
618}
619
620#[derive(Parser, Debug)]
621pub struct BenchmarkArgs {
622    /// Directory containing benchmark PRD files
623    #[arg(long, default_value = "benchmarks")]
624    pub prd_dir: String,
625
626    /// Models to benchmark (comma-separated, format: provider:model)
627    #[arg(short, long, value_delimiter = ',')]
628    pub models: Vec<String>,
629
630    /// Only run PRDs matching this tier (1, 2, or 3)
631    #[arg(long)]
632    pub tier: Option<u8>,
633
634    /// Run model×PRD combos in parallel
635    #[arg(long)]
636    pub parallel: bool,
637
638    /// Maximum iterations per story
639    #[arg(long, default_value = "10")]
640    pub max_iterations: usize,
641
642    /// Timeout per story in seconds
643    #[arg(long, default_value = "300")]
644    pub story_timeout: u64,
645
646    /// Output file path
647    #[arg(short, long, default_value = "benchmark_results.json")]
648    pub output: String,
649
650    /// Cost ceiling per run in USD (prevents runaway spending)
651    #[arg(long, default_value = "50.0")]
652    pub cost_ceiling: f64,
653
654    /// Submit results to this API URL
655    #[arg(long)]
656    pub submit_url: Option<String>,
657
658    /// API key for submitting results (Bearer token)
659    #[arg(long, env = "BENCHMARK_API_KEY")]
660    pub submit_key: Option<String>,
661
662    /// Output as JSON to stdout
663    #[arg(long)]
664    pub json: bool,
665}
666
667#[derive(Parser, Debug)]
668pub struct OkrArgs {
669    /// Action to perform
670    #[arg(value_parser = ["list", "status", "create", "runs", "export", "stats", "report"])]
671    pub action: String,
672
673    /// OKR ID (UUID)
674    #[arg(short, long)]
675    pub id: Option<String>,
676
677    /// OKR title (for create)
678    #[arg(short, long)]
679    pub title: Option<String>,
680
681    /// OKR description (for create)
682    #[arg(short, long)]
683    pub description: Option<String>,
684
685    /// Target value for key result (for create)
686    #[arg(long)]
687    pub target: Option<f64>,
688
689    /// Unit for key result (for create)
690    #[arg(long, default_value = "%")]
691    pub unit: String,
692
693    /// Filter by status: draft, active, completed, cancelled, on_hold
694    #[arg(long)]
695    pub status: Option<String>,
696
697    /// Filter by owner
698    #[arg(long)]
699    pub owner: Option<String>,
700
701    /// Output as JSON
702    #[arg(long)]
703    pub json: bool,
704
705    /// Include evidence links in output
706    #[arg(long)]
707    pub evidence: bool,
708}