1use std::{num::NonZeroUsize, path::PathBuf, time::Duration};
2
3use clap::{Parser, Subcommand, ValueEnum};
4
5use rho_providers::{credentials::CredentialStoreBackend, reasoning::ReasoningLevel};
6
7use crate::app::automation_protocol::parse_duration;
8
9fn parse_credential_store_backend(value: &str) -> Result<CredentialStoreBackend, String> {
10 CredentialStoreBackend::parse(value).map_err(|error| error.to_string())
11}
12
13fn parse_auth_profile(value: &str) -> Result<String, String> {
14 let profiles = rho_providers::auth_profiles();
15 if profiles.contains(&value) {
16 return Ok(value.to_string());
17 }
18 Err(format!(
19 "invalid value '{value}' for '--auth'; expected one of: {}",
20 profiles.join(", ")
21 ))
22}
23
24#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, ValueEnum)]
26pub enum OutputFormat {
27 #[default]
29 Text,
30 Jsonl,
32}
33
34#[derive(Parser, Debug)]
35#[command(name = "rho")]
36pub struct Cli {
37 #[arg(long)]
38 pub provider: Option<String>,
39 #[arg(long)]
40 pub model: Option<String>,
41 #[arg(long)]
42 pub config: Option<PathBuf>,
43 #[arg(long, value_parser = parse_auth_profile)]
44 pub auth: Option<String>,
45 #[arg(long)]
47 pub no_system_prompt: bool,
48 #[arg(long)]
50 pub no_tools: bool,
51 #[arg(long, global = true)]
53 pub no_subagents: bool,
54 #[arg(long, global = true, value_name = "ID")]
56 pub agent: Option<String>,
57 #[arg(long)]
59 pub reasoning: Option<ReasoningLevel>,
60 #[arg(short = 'R', long, value_name = "ID", num_args = 0..=1)]
62 pub resume: Option<Option<String>>,
63 #[command(subcommand)]
64 pub command: Option<Command>,
65}
66
67#[derive(Subcommand, Debug)]
68pub enum Command {
69 Run {
71 #[arg(long)]
73 stdin: bool,
74 #[arg(long, value_name = "PATH")]
77 output_file: Option<PathBuf>,
78 #[arg(long, value_enum, default_value_t)]
80 output: OutputFormat,
81 #[arg(long, value_name = "N")]
83 max_steps: Option<NonZeroUsize>,
84 #[arg(long, value_name = "DURATION", value_parser = parse_duration)]
86 timeout: Option<Duration>,
87 #[arg(value_name = "PROMPT", num_args = 0..)]
89 prompt: Vec<String>,
90 },
91 Attach {
93 #[arg(value_name = "ID")]
95 id: String,
96 },
97 Login {
99 #[arg(value_name = "PROVIDER")]
101 provider: String,
102 #[arg(long)]
104 device_auth: bool,
105 },
106 CredentialStore {
108 #[command(subcommand)]
109 command: CredentialStoreCommand,
110 },
111 Update,
113 Sessions {
115 #[command(subcommand)]
116 command: SessionsCommand,
117 },
118}
119
120#[derive(Subcommand, Debug)]
121pub enum SessionsCommand {
122 List {
124 #[arg(long)]
126 all_projects: bool,
127 },
128 Rm {
130 #[arg(value_name = "ID", required = true, num_args = 1..)]
132 ids: Vec<String>,
133 #[arg(long)]
137 force: bool,
138 #[arg(short = 'y', long)]
140 yes: bool,
141 },
142 Rename {
144 #[arg(value_name = "ID")]
146 id_prefix: String,
147 #[arg(value_name = "TITLE", required = true, num_args = 1.., trailing_var_arg = true)]
149 title: Vec<String>,
150 },
151}
152
153#[derive(Subcommand, Debug)]
154pub enum CredentialStoreCommand {
155 Probe {
157 #[arg(
159 value_name = "BACKEND",
160 default_value = "os",
161 value_parser = parse_credential_store_backend
162 )]
163 backend: CredentialStoreBackend,
164 },
165 Status,
167 Set {
169 #[arg(value_name = "BACKEND", value_parser = parse_credential_store_backend)]
171 backend: CredentialStoreBackend,
172 },
173}
174
175#[cfg(test)]
176#[path = "cli_tests.rs"]
177mod tests;