1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
use std::{num::NonZeroUsize, path::PathBuf, time::Duration};
use clap::{Parser, Subcommand, ValueEnum};
use rho_providers::{credentials::CredentialStoreBackend, reasoning::ReasoningLevel};
use crate::app::automation_protocol::parse_duration;
fn parse_credential_store_backend(value: &str) -> Result<CredentialStoreBackend, String> {
CredentialStoreBackend::parse(value).map_err(|error| error.to_string())
}
fn parse_auth_profile(value: &str) -> Result<String, String> {
let profiles = rho_providers::auth_profiles();
if profiles.contains(&value) {
return Ok(value.to_string());
}
Err(format!(
"invalid value '{value}' for '--auth'; expected one of: {}",
profiles.join(", ")
))
}
/// Output contract used by a non-interactive `rho run` invocation.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, ValueEnum)]
pub enum OutputFormat {
/// Print only the authoritative final assistant answer.
#[default]
Text,
/// Stream independently versioned JSON Lines events.
Jsonl,
}
/// Output contract for workflow plans and snapshots.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, ValueEnum)]
pub enum WorkflowDocumentFormat {
/// Print a human-readable document.
#[default]
Text,
/// Print one JSON document.
Json,
}
/// Output contract for workflow execution.
#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
pub enum WorkflowRunFormat {
/// Print human-readable state changes.
Text,
/// Stream versioned JSON Lines events.
Jsonl,
}
#[derive(Parser, Debug)]
#[command(name = "rho")]
pub struct Cli {
#[arg(long)]
pub provider: Option<String>,
#[arg(long)]
pub model: Option<String>,
#[arg(long)]
pub config: Option<PathBuf>,
#[arg(long, value_parser = parse_auth_profile)]
pub auth: Option<String>,
/// Do not send rho's system prompt, including AGENTS.md and skill context.
#[arg(long)]
pub no_system_prompt: bool,
/// Do not expose any tools to the model.
#[arg(long)]
pub no_tools: bool,
/// Do not expose the delegated-agent tools (agent/agents) to the model.
#[arg(long, global = true)]
pub no_subagents: bool,
/// Select the agent definition used for this session or automation run.
#[arg(long, global = true, value_name = "ID")]
pub agent: Option<String>,
/// Override reasoning level: off, minimal, low, medium, high, xhigh, or max.
#[arg(long)]
pub reasoning: Option<ReasoningLevel>,
/// Resume an existing session by UUID or UUID prefix. Omit the ID to choose from a picker.
#[arg(short = 'R', long, value_name = "ID", num_args = 0..=1)]
pub resume: Option<Option<String>>,
#[command(subcommand)]
pub command: Option<Command>,
}
#[derive(Subcommand, Debug)]
pub enum Command {
/// Run one non-interactive automation prompt and print the final answer.
Run {
/// Read additional prompt text from stdin.
#[arg(long)]
stdin: bool,
/// Stream progress to stdout and write a structured status/result
/// file (JSON) that is updated during the run and finalized on exit.
#[arg(long, value_name = "PATH")]
output_file: Option<PathBuf>,
/// Select plain final-answer output or a JSON Lines event stream.
#[arg(long, value_enum, default_value_t)]
output: OutputFormat,
/// Override the model-step budget for this run.
#[arg(long, value_name = "N")]
max_steps: Option<NonZeroUsize>,
/// Cancel the run after this wall-clock duration.
#[arg(long, value_name = "DURATION", value_parser = parse_duration)]
timeout: Option<Duration>,
/// Prompt text to send to the agent.
#[arg(value_name = "PROMPT", num_args = 0..)]
prompt: Vec<String>,
},
/// Watch a delegated agent run in a read-only TUI.
Attach {
/// Delegated run ID shown when the agent was started.
#[arg(value_name = "ID")]
id: String,
},
/// Log in to a provider from a browser or device-code flow.
Login {
/// Provider to authenticate, for example openai-codex or github-copilot.
#[arg(value_name = "PROVIDER")]
provider: String,
/// Use device-code login instead of opening a local browser callback.
#[arg(long)]
device_auth: bool,
},
/// Configure or probe provider credential storage.
CredentialStore {
#[command(subcommand)]
command: CredentialStoreCommand,
},
/// Update rho using the detected installation method.
Update,
/// List, rename, or delete saved sessions.
Sessions {
#[command(subcommand)]
command: SessionsCommand,
},
/// Validate, plan, run, and inspect deterministic workflows.
Workflow {
#[command(subcommand)]
command: WorkflowCommand,
},
/// Internal supervised workflow planner worker. Not a public command.
#[command(name = "__workflow_planner_worker", hide = true)]
WorkflowPlannerWorker,
}
/// Argv entry for the internal supervised planner worker process.
pub const WORKFLOW_PLANNER_WORKER_COMMAND: &str = "__workflow_planner_worker";
#[derive(Subcommand, Debug)]
pub enum WorkflowCommand {
/// Validate a Starlark workflow without creating durable state.
Validate {
/// Workflow entry file under the current workspace.
#[arg(value_name = "FILE")]
file: PathBuf,
/// Supply one non-secret workflow input as KEY=JSON. May be repeated.
#[arg(long, value_name = "KEY=JSON")]
input: Vec<String>,
},
/// Validate, freeze, and persist an immutable workflow plan.
Plan {
/// Workflow entry file under the current workspace.
#[arg(value_name = "FILE")]
file: PathBuf,
/// Supply one non-secret workflow input as KEY=JSON. May be repeated.
#[arg(long, value_name = "KEY=JSON")]
input: Vec<String>,
/// Select human-readable or machine-readable plan output.
#[arg(long, value_enum, default_value_t)]
output: WorkflowDocumentFormat,
},
/// Create and run a workflow from an immutable plan.
Run {
/// Full plan UUID or unique UUID prefix.
#[arg(value_name = "PLAN_ID")]
plan_id: String,
/// Confirm the exact plan digest without an interactive prompt.
#[arg(long)]
yes: bool,
/// Select text or JSON Lines instead of the workflow TUI.
#[arg(long, value_enum)]
output: Option<WorkflowRunFormat>,
},
/// Read one durable workflow run snapshot.
Status {
/// Full run UUID or unique UUID prefix.
#[arg(value_name = "RUN_ID")]
run_id: String,
/// Select human-readable or machine-readable snapshot output.
#[arg(long, value_enum, default_value_t)]
output: WorkflowDocumentFormat,
},
/// Request cancellation of a durable workflow run.
Cancel {
/// Full run UUID or unique UUID prefix.
#[arg(value_name = "RUN_ID")]
run_id: String,
},
/// Resume a durable workflow run from its frozen graph.
Resume {
/// Full run UUID or unique UUID prefix.
#[arg(value_name = "RUN_ID")]
run_id: String,
/// Confirm the frozen graph without an interactive prompt.
#[arg(long)]
yes: bool,
/// Confirm that no prior process remains and relaunch uncertain attempts.
#[arg(long)]
recover_uncertain: bool,
/// Select text or JSON Lines instead of the workflow TUI.
#[arg(long, value_enum)]
output: Option<WorkflowRunFormat>,
},
}
#[derive(Subcommand, Debug)]
pub enum SessionsCommand {
/// List saved sessions for the current workspace (or all projects).
List {
/// Include sessions from every workspace, not only the current directory.
#[arg(long)]
all_projects: bool,
},
/// Delete one or more sessions by UUID or UUID prefix.
Rm {
/// Session UUID or unique prefix. May be repeated.
#[arg(value_name = "ID", required = true, num_args = 1..)]
ids: Vec<String>,
/// Delete even when a parent-linked run is still non-terminal.
///
/// Use only for stale Starting/Running artifacts left after a crash.
#[arg(long)]
force: bool,
/// Skip the confirmation prompt for cross-project deletes.
#[arg(short = 'y', long)]
yes: bool,
},
/// Rename a session by UUID or UUID prefix.
Rename {
/// Session UUID or unique prefix.
#[arg(value_name = "ID")]
id_prefix: String,
/// New session title. Multiple words are joined with spaces.
#[arg(value_name = "TITLE", required = true, num_args = 1.., trailing_var_arg = true)]
title: Vec<String>,
},
}
#[derive(Subcommand, Debug)]
pub enum CredentialStoreCommand {
/// Test a credential backend by writing and deleting a temporary secret.
Probe {
/// Backend to test: os or file (`auto` is accepted as an alias for os).
#[arg(
value_name = "BACKEND",
default_value = "os",
value_parser = parse_credential_store_backend
)]
backend: CredentialStoreBackend,
},
/// Show the configured credential backend (unset, os, or file).
Status,
/// Save the credential backend used by future rho processes.
Set {
/// Backend to use: os or file (`auto` is accepted as an alias for os).
#[arg(value_name = "BACKEND", value_parser = parse_credential_store_backend)]
backend: CredentialStoreBackend,
},
}
#[cfg(test)]
#[path = "cli_tests.rs"]
mod tests;