Skip to main content

uira_cli/
commands.rs

1//! CLI commands
2
3use clap::{Parser, Subcommand, ValueEnum};
4use std::path::PathBuf;
5
6#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
7pub enum CliMode {
8    Interactive,
9    Rpc,
10}
11
12/// Uira - Native AI Coding Agent
13#[derive(Parser, Debug)]
14#[command(name = "uira-agent")]
15#[command(author, version, about, long_about = None)]
16pub struct Cli {
17    /// Runtime mode (interactive, rpc)
18    #[arg(long, value_enum, default_value_t = CliMode::Interactive)]
19    pub mode: CliMode,
20
21    /// Prompt to execute (interactive mode if omitted)
22    #[arg(trailing_var_arg = true)]
23    pub prompt: Vec<String>,
24
25    /// Model to use (e.g., claude-sonnet-4-20250514, gpt-4o)
26    #[arg(short, long)]
27    pub model: Option<String>,
28
29    /// Provider to use (anthropic, openai, ollama, opencode)
30    #[arg(short, long)]
31    pub provider: Option<String>,
32
33    /// Sandbox policy (read-only, workspace-write, full-access, custom)
34    #[arg(long, default_value = "workspace-write")]
35    pub sandbox: String,
36
37    /// Path to JSON file containing custom sandbox rules (used when --sandbox custom)
38    #[arg(long)]
39    pub sandbox_rules: Option<PathBuf>,
40
41    /// Run in full-auto mode (no approval prompts)
42    #[arg(long)]
43    pub full_auto: bool,
44
45    /// Verbose output - show streaming events (tool calls, thinking, etc.)
46    #[arg(short, long)]
47    pub verbose: bool,
48
49    /// Enable ralph mode for persistent task completion
50    #[arg(long)]
51    pub ralph: bool,
52
53    /// Agent to use (e.g., architect, executor, explore, designer)
54    /// Each agent has specialized prompts and a default model tier
55    #[arg(short, long)]
56    pub agent: Option<String>,
57
58    /// Output format (text, json, jsonl)
59    #[arg(long, default_value = "text")]
60    pub output: String,
61
62    #[command(subcommand)]
63    pub command: Option<Commands>,
64}
65
66#[derive(Subcommand, Debug)]
67pub enum Commands {
68    /// Execute prompt non-interactively
69    Exec {
70        /// The prompt to execute
71        prompt: String,
72
73        /// Output as JSON
74        #[arg(long)]
75        json: bool,
76    },
77
78    /// Resume a previous session
79    Resume {
80        /// Session ID to resume
81        session_id: Option<String>,
82
83        /// Fork from a parent session instead of resuming directly
84        #[arg(long)]
85        fork: bool,
86
87        /// Number of messages to keep when forking (default: all)
88        #[arg(long)]
89        fork_at: Option<usize>,
90    },
91
92    /// List and manage sessions
93    Sessions {
94        #[command(subcommand)]
95        command: SessionsCommands,
96    },
97
98    /// Authentication commands
99    Auth {
100        #[command(subcommand)]
101        command: AuthCommands,
102    },
103
104    /// Configuration management
105    Config {
106        #[command(subcommand)]
107        command: ConfigCommands,
108    },
109
110    /// Goal verification commands
111    Goals {
112        #[command(subcommand)]
113        command: GoalsCommands,
114    },
115
116    /// Background task management
117    Tasks {
118        #[command(subcommand)]
119        command: TasksCommands,
120    },
121
122    /// Generate shell completion scripts
123    Completion {
124        /// Target shell to generate completions for
125        #[arg(value_enum)]
126        shell: clap_complete::Shell,
127    },
128
129    /// Gateway server management
130    Gateway {
131        #[command(subcommand)]
132        command: GatewayCommands,
133    },
134
135    /// Manage skills
136    Skills {
137        #[command(subcommand)]
138        command: SkillsCommands,
139    },
140}
141
142#[derive(Subcommand, Debug)]
143pub enum AuthCommands {
144    /// Login to a provider
145    Login {
146        /// Provider to login to (omit to see available providers)
147        provider: Option<String>,
148    },
149    /// Logout from a provider
150    Logout {
151        /// Provider to logout from
152        provider: String,
153    },
154    /// Show current authentication status
155    Status,
156}
157
158#[derive(Subcommand, Debug)]
159pub enum ConfigCommands {
160    /// Show current configuration
161    Show,
162    /// Set a configuration value
163    Set {
164        /// Configuration key
165        key: String,
166        /// Configuration value
167        value: String,
168    },
169    /// Get a configuration value
170    Get {
171        /// Configuration key
172        key: String,
173    },
174    /// Reset configuration to defaults
175    Reset,
176}
177
178#[derive(Subcommand, Debug)]
179pub enum GoalsCommands {
180    /// Run goal verification
181    Check,
182    /// List configured goals
183    List,
184    /// Show goal verification status
185    Status,
186}
187
188#[derive(Subcommand, Debug)]
189pub enum TasksCommands {
190    /// List all background tasks
191    List,
192    /// Get task status
193    Status {
194        /// Task ID to check
195        task_id: String,
196    },
197    /// Cancel a task
198    Cancel {
199        /// Task ID to cancel
200        task_id: String,
201    },
202}
203
204#[derive(Subcommand, Debug)]
205pub enum SessionsCommands {
206    /// List all sessions (with fork relationships)
207    List {
208        /// Show only recent N sessions
209        #[arg(short, long, default_value = "20")]
210        limit: usize,
211
212        /// Show fork tree structure
213        #[arg(long)]
214        tree: bool,
215    },
216    /// Show session details
217    Info {
218        /// Session ID to inspect
219        session_id: String,
220    },
221    /// Delete a session
222    Delete {
223        /// Session ID to delete
224        session_id: String,
225    },
226}
227
228#[derive(Subcommand, Debug)]
229pub enum GatewayCommands {
230    /// Start the WebSocket gateway server
231    Start {
232        /// Host to bind to (default: from config or 127.0.0.1)
233        #[arg(long)]
234        host: Option<String>,
235        /// Port to bind to (default: from config or 18790)
236        #[arg(long)]
237        port: Option<u16>,
238        /// Authentication token for WebSocket connections
239        #[arg(long)]
240        auth_token: Option<String>,
241    },
242}
243
244#[derive(Subcommand, Debug)]
245pub enum SkillsCommands {
246    /// List all discovered skills
247    List,
248    /// Show details of a specific skill
249    Show {
250        /// Name of the skill to show
251        name: String,
252    },
253    /// Install a skill from a local path
254    Install {
255        /// Path to the skill directory (must contain SKILL.md)
256        path: String,
257    },
258}
259
260impl Cli {
261    pub fn get_prompt(&self) -> Option<String> {
262        if self.prompt.is_empty() {
263            None
264        } else {
265            Some(self.prompt.join(" "))
266        }
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273    use clap::Parser;
274
275    #[test]
276    fn defaults_to_interactive_mode() {
277        let cli = Cli::parse_from(["uira-agent"]);
278        assert_eq!(cli.mode, CliMode::Interactive);
279    }
280
281    #[test]
282    fn parses_rpc_mode_flag() {
283        let cli = Cli::parse_from(["uira-agent", "--mode", "rpc"]);
284        assert_eq!(cli.mode, CliMode::Rpc);
285    }
286
287    #[test]
288    fn parses_custom_sandbox_rules_flag() {
289        let cli = Cli::parse_from([
290            "uira-agent",
291            "--sandbox",
292            "custom",
293            "--sandbox-rules",
294            "./sandbox-rules.json",
295        ]);
296        assert_eq!(cli.sandbox, "custom");
297        assert_eq!(
298            cli.sandbox_rules,
299            Some(PathBuf::from("./sandbox-rules.json"))
300        );
301    }
302}