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
98#[derive(Parser, Debug)]
99pub struct AuthArgs {
100    #[command(subcommand)]
101    pub command: AuthCommand,
102}
103
104#[derive(Subcommand, Debug)]
105pub enum AuthCommand {
106    /// Authenticate with GitHub Copilot using device flow
107    Copilot(CopilotAuthArgs),
108
109    /// Login to a CodeTether server with email/password
110    Login(LoginAuthArgs),
111}
112
113#[derive(Parser, Debug)]
114pub struct LoginAuthArgs {
115    /// CodeTether server URL (e.g., https://api.codetether.io)
116    #[arg(short, long, env = "CODETETHER_SERVER")]
117    pub server: String,
118
119    /// Email address
120    #[arg(short, long)]
121    pub email: Option<String>,
122}
123
124#[derive(Parser, Debug)]
125pub struct CopilotAuthArgs {
126    /// GitHub Enterprise URL or domain (e.g. company.ghe.com)
127    #[arg(long)]
128    pub enterprise_url: Option<String>,
129
130    /// GitHub OAuth app client ID for Copilot device flow
131    #[arg(long, env = "CODETETHER_COPILOT_OAUTH_CLIENT_ID")]
132    pub client_id: Option<String>,
133}
134
135#[derive(Parser, Debug)]
136pub struct TuiArgs {
137    /// Project directory
138    pub project: Option<PathBuf>,
139}
140
141#[derive(Parser, Debug)]
142pub struct ServeArgs {
143    /// Port to listen on
144    #[arg(short, long, default_value = "4096")]
145    pub port: u16,
146
147    /// Hostname to bind to
148    #[arg(long, default_value = "127.0.0.1")]
149    pub hostname: String,
150
151    /// Enable mDNS discovery
152    #[arg(long)]
153    pub mdns: bool,
154}
155
156#[derive(Parser, Debug)]
157pub struct RunArgs {
158    /// Message to send (can be multiple words, quoted or unquoted)
159    pub message: String,
160
161    /// Continue the last session
162    #[arg(short, long)]
163    pub continue_session: bool,
164
165    /// Session ID to continue
166    #[arg(short, long)]
167    pub session: Option<String>,
168
169    /// Model to use (provider/model format)
170    #[arg(short, long)]
171    pub model: Option<String>,
172
173    /// Agent to use
174    #[arg(long)]
175    pub agent: Option<String>,
176
177    /// Output format
178    #[arg(long, default_value = "default", value_parser = ["default", "json"])]
179    pub format: String,
180
181    /// Files to attach
182    #[arg(short, long)]
183    pub file: Vec<PathBuf>,
184}
185
186#[derive(Parser, Debug, Clone)]
187pub struct A2aArgs {
188    /// A2A server URL
189    #[arg(short, long, env = "CODETETHER_SERVER", default_value = crate::a2a::worker::DEFAULT_A2A_SERVER_URL)]
190    pub server: String,
191
192    /// Authentication token
193    #[arg(short, long, env = "CODETETHER_TOKEN")]
194    pub token: Option<String>,
195
196    /// Worker name
197    #[arg(short, long, env = "CODETETHER_WORKER_NAME")]
198    pub name: Option<String>,
199
200    /// Comma-separated list of codebase paths
201    #[arg(short, long)]
202    pub codebases: Option<String>,
203
204    /// Auto-approve policy: all, safe (read-only), none
205    #[arg(long, default_value = "safe", value_parser = ["all", "safe", "none"])]
206    pub auto_approve: String,
207
208    /// Email for task completion reports
209    #[arg(short, long)]
210    pub email: Option<String>,
211
212    /// Push notification endpoint URL
213    #[arg(long)]
214    pub push_url: Option<String>,
215}
216
217#[derive(Parser, Debug, Clone)]
218pub struct SpawnArgs {
219    /// Agent name
220    #[arg(short, long)]
221    pub name: Option<String>,
222
223    /// Hostname to bind the spawned A2A agent
224    #[arg(long, default_value = "127.0.0.1")]
225    pub hostname: String,
226
227    /// Port to bind the spawned A2A agent
228    #[arg(short, long, default_value = "4097")]
229    pub port: u16,
230
231    /// Public URL published in the agent card (defaults to http://<hostname>:<port>)
232    #[arg(long)]
233    pub public_url: Option<String>,
234
235    /// Optional custom agent description for the card
236    #[arg(short, long)]
237    pub description: Option<String>,
238
239    /// Peer seed URLs to discover and talk to (repeat flag or comma-separated)
240    #[arg(long, value_delimiter = ',', env = "CODETETHER_A2A_PEERS")]
241    pub peer: Vec<String>,
242
243    /// Discovery interval in seconds
244    #[arg(long, default_value = "15")]
245    pub discovery_interval_secs: u64,
246
247    /// Disable sending an automatic intro message to newly discovered peers
248    #[arg(long = "no-auto-introduce", action = clap::ArgAction::SetFalse, default_value_t = true)]
249    pub auto_introduce: bool,
250}
251
252#[derive(Parser, Debug)]
253pub struct ConfigArgs {
254    /// Show current configuration
255    #[arg(long)]
256    pub show: bool,
257
258    /// Initialize default configuration
259    #[arg(long)]
260    pub init: bool,
261
262    /// Set a configuration value
263    #[arg(long)]
264    pub set: Option<String>,
265}
266
267#[derive(Parser, Debug)]
268pub struct SwarmArgs {
269    /// Task to execute with swarm
270    pub task: String,
271
272    /// Model to use (provider/model format, e.g. zai/glm-5 or openrouter/z-ai/glm-5)
273    #[arg(short, long)]
274    pub model: Option<String>,
275
276    /// Decomposition strategy: auto, domain, data, stage, none
277    #[arg(short = 's', long, default_value = "auto")]
278    pub strategy: String,
279
280    /// Maximum number of concurrent sub-agents
281    #[arg(long, default_value = "100")]
282    pub max_subagents: usize,
283
284    /// Maximum steps per sub-agent
285    #[arg(long, default_value = "100")]
286    pub max_steps: usize,
287
288    /// Timeout per sub-agent (seconds)
289    #[arg(long, default_value = "300")]
290    pub timeout: u64,
291
292    /// Output as JSON
293    #[arg(long)]
294    pub json: bool,
295}
296
297#[derive(Parser, Debug)]
298pub struct RlmArgs {
299    /// Query to answer about the content
300    pub query: String,
301
302    /// File paths to analyze
303    #[arg(short, long)]
304    pub file: Vec<PathBuf>,
305
306    /// Direct content to analyze (use - for stdin)
307    #[arg(long)]
308    pub content: Option<String>,
309
310    /// Content type hint: code, logs, conversation, documents, auto
311    #[arg(long, default_value = "auto")]
312    pub content_type: String,
313
314    /// Maximum tokens for output
315    #[arg(long, default_value = "4000")]
316    pub max_tokens: usize,
317
318    /// Output as JSON
319    #[arg(long)]
320    pub json: bool,
321
322    /// Enable verbose output (shows context summary)
323    #[arg(short, long)]
324    pub verbose: bool,
325}
326
327#[derive(Parser, Debug)]
328pub struct RalphArgs {
329    /// Action to perform
330    #[arg(value_parser = ["run", "status", "create-prd"])]
331    pub action: String,
332
333    /// Path to prd.json file
334    #[arg(short, long, default_value = "prd.json")]
335    pub prd: PathBuf,
336
337    /// Feature name (for create-prd)
338    #[arg(short, long)]
339    pub feature: Option<String>,
340
341    /// Project name (for create-prd)
342    #[arg(long = "project-name")]
343    pub project_name: Option<String>,
344
345    /// Maximum iterations
346    #[arg(long, default_value = "10")]
347    pub max_iterations: usize,
348
349    /// Model to use
350    #[arg(short, long)]
351    pub model: Option<String>,
352
353    /// Output as JSON
354    #[arg(long)]
355    pub json: bool,
356}
357
358#[derive(Parser, Debug)]
359pub struct McpArgs {
360    /// Action to perform
361    #[arg(value_parser = ["serve", "connect", "list-tools", "call"])]
362    pub action: String,
363
364    /// Command to spawn for connecting to MCP server
365    #[arg(short, long)]
366    pub command: Option<String>,
367
368    /// Server name for registry
369    #[arg(long)]
370    pub server_name: Option<String>,
371
372    /// Tool name for call action
373    #[arg(long)]
374    pub tool: Option<String>,
375
376    /// JSON arguments for tool call
377    #[arg(long)]
378    pub arguments: Option<String>,
379
380    /// Output as JSON
381    #[arg(long)]
382    pub json: bool,
383}
384
385#[derive(Parser, Debug)]
386pub struct StatsArgs {
387    /// Show tool execution history
388    #[arg(short, long)]
389    pub tools: bool,
390
391    /// Show file change history
392    #[arg(short, long)]
393    pub files: bool,
394
395    /// Show token usage
396    #[arg(long)]
397    pub tokens: bool,
398
399    /// Filter by tool name
400    #[arg(long)]
401    pub tool: Option<String>,
402
403    /// Filter by file path
404    #[arg(long)]
405    pub file: Option<String>,
406
407    /// Number of recent entries to show
408    #[arg(short, long, default_value = "20")]
409    pub limit: usize,
410
411    /// Output as JSON
412    #[arg(long)]
413    pub json: bool,
414
415    /// Show all/summary (default shows summary)
416    #[arg(long)]
417    pub all: bool,
418}
419
420#[derive(Parser, Debug)]
421pub struct CleanupArgs {
422    /// Dry run - show what would be cleaned up without deleting
423    #[arg(short, long)]
424    pub dry_run: bool,
425
426    /// Clean up worktrees only (not branches)
427    #[arg(long)]
428    pub worktrees_only: bool,
429
430    /// Output as JSON
431    #[arg(long)]
432    pub json: bool,
433}
434
435#[derive(Parser, Debug)]
436pub struct ModelsArgs {
437    /// Filter by provider name
438    #[arg(short, long)]
439    pub provider: Option<String>,
440
441    /// Output as JSON
442    #[arg(long)]
443    pub json: bool,
444}
445
446#[derive(Parser, Debug)]
447pub struct MoltbookArgs {
448    #[command(subcommand)]
449    pub command: MoltbookCommand,
450}
451
452#[derive(Subcommand, Debug)]
453pub enum MoltbookCommand {
454    /// Register a new agent on Moltbook
455    Register(MoltbookRegisterArgs),
456
457    /// Check claim status
458    Status,
459
460    /// View your Moltbook profile
461    Profile,
462
463    /// Update your profile description
464    UpdateProfile(MoltbookUpdateProfileArgs),
465
466    /// Create a post (defaults to m/general)
467    Post(MoltbookPostArgs),
468
469    /// Post a CodeTether introduction to Moltbook
470    Intro,
471
472    /// Run a heartbeat — check feed, show recent posts
473    Heartbeat,
474
475    /// Comment on a Moltbook post
476    Comment(MoltbookCommentArgs),
477
478    /// Search Moltbook posts and comments
479    Search(MoltbookSearchArgs),
480}
481
482#[derive(Parser, Debug)]
483pub struct MoltbookRegisterArgs {
484    /// Agent name to register on Moltbook
485    pub name: String,
486
487    /// Optional extra description (CodeTether branding is always included)
488    #[arg(short, long)]
489    pub description: Option<String>,
490}
491
492#[derive(Parser, Debug)]
493pub struct MoltbookUpdateProfileArgs {
494    /// Extra description to append
495    #[arg(short, long)]
496    pub description: Option<String>,
497}
498
499#[derive(Parser, Debug)]
500pub struct MoltbookPostArgs {
501    /// Post title
502    pub title: String,
503
504    /// Post content
505    #[arg(short, long)]
506    pub content: String,
507
508    /// Submolt to post in
509    #[arg(short, long, default_value = "general")]
510    pub submolt: String,
511}
512
513#[derive(Parser, Debug)]
514pub struct MoltbookCommentArgs {
515    /// Post ID to comment on
516    pub post_id: String,
517
518    /// Comment content
519    pub content: String,
520}
521
522#[derive(Parser, Debug)]
523pub struct MoltbookSearchArgs {
524    /// Search query
525    pub query: String,
526
527    /// Max results
528    #[arg(short, long, default_value = "10")]
529    pub limit: usize,
530}
531
532#[derive(Parser, Debug)]
533pub struct BenchmarkArgs {
534    /// Directory containing benchmark PRD files
535    #[arg(long, default_value = "benchmarks")]
536    pub prd_dir: String,
537
538    /// Models to benchmark (comma-separated, format: provider:model)
539    #[arg(short, long, value_delimiter = ',')]
540    pub models: Vec<String>,
541
542    /// Only run PRDs matching this tier (1, 2, or 3)
543    #[arg(long)]
544    pub tier: Option<u8>,
545
546    /// Run model×PRD combos in parallel
547    #[arg(long)]
548    pub parallel: bool,
549
550    /// Maximum iterations per story
551    #[arg(long, default_value = "10")]
552    pub max_iterations: usize,
553
554    /// Timeout per story in seconds
555    #[arg(long, default_value = "300")]
556    pub story_timeout: u64,
557
558    /// Output file path
559    #[arg(short, long, default_value = "benchmark_results.json")]
560    pub output: String,
561
562    /// Cost ceiling per run in USD (prevents runaway spending)
563    #[arg(long, default_value = "50.0")]
564    pub cost_ceiling: f64,
565
566    /// Submit results to this API URL (e.g. https://opencode.ai/bench/submission)
567    #[arg(long)]
568    pub submit_url: Option<String>,
569
570    /// API key for submitting results (Bearer token)
571    #[arg(long, env = "BENCHMARK_API_KEY")]
572    pub submit_key: Option<String>,
573
574    /// Output as JSON to stdout
575    #[arg(long)]
576    pub json: bool,
577}