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}
143
144#[derive(Subcommand, Debug)]
145pub enum CredentialStoreCommand {
146 Probe {
148 #[arg(
150 value_name = "BACKEND",
151 default_value = "os",
152 value_parser = parse_credential_store_backend
153 )]
154 backend: CredentialStoreBackend,
155 },
156 Status,
158 Set {
160 #[arg(value_name = "BACKEND", value_parser = parse_credential_store_backend)]
162 backend: CredentialStoreBackend,
163 },
164}
165
166#[cfg(test)]
167#[path = "cli_tests.rs"]
168mod tests;