Skip to main content

codetether_agent/cli/
mod.rs

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