leviath_cli/commands/run/
mod.rs1pub mod manifest;
10pub mod session;
11
12use std::collections::HashMap;
13
14use clap::Args;
15
16pub use session::{
18 ProviderCreds, build_provider_registry, build_provider_registry_from_config,
19 provider_creds_from_config,
20};
21
22#[derive(Args, Debug, Clone, Default)]
24pub struct RunArgs {
25 #[arg(value_name = "PATH")]
27 pub path: Option<String>,
28
29 #[arg(short, long)]
31 pub task: Option<String>,
32
33 #[arg(short, long)]
35 pub model: Option<String>,
36
37 #[arg(long)]
40 pub yolo: bool,
41
42 #[arg(long)]
44 pub allow: Vec<String>,
45
46 #[arg(long)]
48 pub max_depth: Option<usize>,
49
50 #[arg(long)]
54 pub no_seed_commands: bool,
55
56 #[arg(skip)]
60 pub regions: HashMap<String, String>,
61}
62
63const KNOWN_RUN_FLAGS: &[&str] = &[
66 "task",
67 "model",
68 "yolo",
69 "allow",
70 "max-depth",
71 "no-seed-commands",
72 "verbose",
73 "help",
74 "version",
75];
76
77pub fn extract_region_flags(argv: Vec<String>) -> (Vec<String>, HashMap<String, String>) {
87 let sub_pos = argv
90 .iter()
91 .enumerate()
92 .skip(1)
93 .find(|(_, t)| !t.starts_with('-'))
94 .map(|(i, _)| i);
95 let Some(sub_pos) = sub_pos else {
96 return (argv, HashMap::new());
97 };
98 if argv[sub_pos] != "run" {
99 return (argv, HashMap::new());
100 }
101
102 let mut out: Vec<String> = argv[..=sub_pos].to_vec();
103 let mut regions = HashMap::new();
104 let mut i = sub_pos + 1;
105 while i < argv.len() {
106 let token = &argv[i];
107 if let Some(name) = token.strip_prefix("--") {
108 let (name, inline) = match name.split_once('=') {
110 Some((n, v)) => (n, Some(v.to_string())),
111 None => (name, None),
112 };
113 if !name.is_empty() && !KNOWN_RUN_FLAGS.contains(&name) {
114 let value = match inline {
115 Some(v) => v,
116 None => {
117 i += 1;
119 argv.get(i).cloned().unwrap_or_default()
120 }
121 };
122 regions.insert(name.to_string(), value);
123 i += 1;
124 continue;
125 }
126 }
127 out.push(token.clone());
128 i += 1;
129 }
130 (out, regions)
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 fn argv(parts: &[&str]) -> Vec<String> {
138 parts.iter().map(|s| s.to_string()).collect()
139 }
140
141 #[test]
142 fn extracts_dynamic_region_flags_and_preserves_known_ones() {
143 let (out, regions) = extract_region_flags(argv(&[
144 "lev",
145 "run",
146 "agents/reviewer",
147 "--task",
148 "review it",
149 "--files",
150 "@src/main.rs",
151 "--review-criteria",
152 "@policy.md",
153 "--yolo",
154 ]));
155 assert_eq!(
157 out,
158 argv(&[
159 "lev",
160 "run",
161 "agents/reviewer",
162 "--task",
163 "review it",
164 "--yolo",
165 ])
166 );
167 assert_eq!(
168 regions.get("files").map(String::as_str),
169 Some("@src/main.rs")
170 );
171 assert_eq!(
172 regions.get("review-criteria").map(String::as_str),
173 Some("@policy.md")
174 );
175 }
176
177 #[test]
178 fn extracts_equals_joined_region_flag() {
179 let (out, regions) = extract_region_flags(argv(&["lev", "run", "a", "--criteria=be safe"]));
180 assert_eq!(out, argv(&["lev", "run", "a"]));
181 assert_eq!(regions.get("criteria").map(String::as_str), Some("be safe"));
182 }
183
184 #[test]
185 fn no_region_flags_leaves_argv_unchanged() {
186 let input = argv(&["lev", "run", "a", "--task", "t"]);
187 let (out, regions) = extract_region_flags(input.clone());
188 assert_eq!(out, input);
189 assert!(regions.is_empty());
190 }
191
192 #[test]
193 fn non_run_subcommand_is_untouched() {
194 let input = argv(&["lev", "ps", "--weird", "x"]);
196 let (out, regions) = extract_region_flags(input.clone());
197 assert_eq!(out, input);
198 assert!(regions.is_empty());
199 }
200
201 #[test]
202 fn no_subcommand_token_is_untouched() {
203 let input = argv(&["lev", "--verbose"]);
205 let (out, regions) = extract_region_flags(input.clone());
206 assert_eq!(out, input);
207 assert!(regions.is_empty());
208 }
209
210 #[test]
211 fn trailing_region_flag_without_value_maps_to_empty() {
212 let (out, regions) = extract_region_flags(argv(&["lev", "run", "a", "--spec"]));
214 assert_eq!(out, argv(&["lev", "run", "a"]));
215 assert_eq!(regions.get("spec").map(String::as_str), Some(""));
216 }
217
218 #[test]
219 fn global_verbose_before_run_still_activates() {
220 let (_out, regions) = extract_region_flags(argv(&["lev", "-v", "run", "a", "--spec", "x"]));
221 assert_eq!(regions.get("spec").map(String::as_str), Some("x"));
222 }
223}