1use crate::args::FdlArgsTrait;
11use crate::config::Schema;
12
13#[derive(crate::FdlArgs, Debug)]
23pub struct SetupArgs {
24 #[option(short = 'y')]
26 pub non_interactive: bool,
27 #[option]
29 pub force: bool,
30}
31
32#[derive(crate::FdlArgs, Debug)]
34pub struct DiagnoseArgs {
35 #[option]
37 pub json: bool,
38}
39
40#[derive(crate::FdlArgs, Debug)]
42pub struct ApiRefArgs {
43 #[option]
45 pub json: bool,
46 #[option]
48 pub path: Option<String>,
49}
50
51#[derive(crate::FdlArgs, Debug)]
53pub struct InitArgs {
54 #[arg]
56 pub name: Option<String>,
57 #[option]
59 pub docker: bool,
60}
61
62#[derive(crate::FdlArgs, Debug)]
64pub struct InstallArgs {
65 #[option]
67 pub check: bool,
68 #[option]
70 pub dev: bool,
71}
72
73#[derive(crate::FdlArgs, Debug)]
75pub struct LibtorchListArgs {
76 #[option]
78 pub json: bool,
79}
80
81#[derive(crate::FdlArgs, Debug)]
83pub struct LibtorchActivateArgs {
84 #[arg]
86 pub variant: Option<String>,
87}
88
89#[derive(crate::FdlArgs, Debug)]
91pub struct LibtorchRemoveArgs {
92 #[arg]
94 pub variant: Option<String>,
95}
96
97#[derive(crate::FdlArgs, Debug)]
99pub struct LibtorchDownloadArgs {
100 #[option]
102 pub cpu: bool,
103 #[option(choices = &["12.6", "12.8"])]
105 pub cuda: Option<String>,
106 #[option]
108 pub path: Option<String>,
109 #[option]
111 pub no_activate: bool,
112 #[option]
114 pub dry_run: bool,
115}
116
117#[derive(crate::FdlArgs, Debug)]
119pub struct LibtorchBuildArgs {
120 #[option]
122 pub archs: Option<String>,
123 #[option(default = "6")]
125 pub jobs: usize,
126 #[option]
128 pub docker: bool,
129 #[option]
131 pub native: bool,
132 #[option]
134 pub dry_run: bool,
135}
136
137#[derive(crate::FdlArgs, Debug)]
139pub struct SkillInstallArgs {
140 #[option]
142 pub tool: Option<String>,
143 #[option]
145 pub skill: Option<String>,
146}
147
148#[derive(crate::FdlArgs, Debug)]
150pub struct SchemaListArgs {
151 #[option]
153 pub json: bool,
154}
155
156#[derive(crate::FdlArgs, Debug)]
158pub struct SchemaClearArgs {
159 #[arg]
161 pub cmd: Option<String>,
162}
163
164#[derive(crate::FdlArgs, Debug)]
166pub struct SchemaRefreshArgs {
167 #[arg]
169 pub cmd: Option<String>,
170}
171
172pub struct BuiltinSpec {
178 pub path: &'static [&'static str],
181 pub description: Option<&'static str>,
184 pub schema_fn: Option<fn() -> Schema>,
189}
190
191pub fn registry() -> &'static [BuiltinSpec] {
195 static REG: &[BuiltinSpec] = &[
196 BuiltinSpec {
197 path: &["setup"],
198 description: Some("Interactive guided setup"),
199 schema_fn: Some(SetupArgs::schema),
200 },
201 BuiltinSpec {
202 path: &["libtorch"],
203 description: Some("Manage libtorch installations"),
204 schema_fn: None,
205 },
206 BuiltinSpec {
207 path: &["libtorch", "download"],
208 description: Some("Download pre-built libtorch"),
209 schema_fn: Some(LibtorchDownloadArgs::schema),
210 },
211 BuiltinSpec {
212 path: &["libtorch", "build"],
213 description: Some("Build libtorch from source"),
214 schema_fn: Some(LibtorchBuildArgs::schema),
215 },
216 BuiltinSpec {
217 path: &["libtorch", "list"],
218 description: Some("Show installed variants"),
219 schema_fn: Some(LibtorchListArgs::schema),
220 },
221 BuiltinSpec {
222 path: &["libtorch", "activate"],
223 description: Some("Set active variant"),
224 schema_fn: Some(LibtorchActivateArgs::schema),
225 },
226 BuiltinSpec {
227 path: &["libtorch", "remove"],
228 description: Some("Remove a variant"),
229 schema_fn: Some(LibtorchRemoveArgs::schema),
230 },
231 BuiltinSpec {
232 path: &["libtorch", "info"],
233 description: Some("Show active variant details"),
234 schema_fn: None,
235 },
236 BuiltinSpec {
237 path: &["init"],
238 description: Some("Scaffold a new floDl project"),
239 schema_fn: Some(InitArgs::schema),
240 },
241 BuiltinSpec {
242 path: &["diagnose"],
243 description: Some("System and GPU diagnostics"),
244 schema_fn: Some(DiagnoseArgs::schema),
245 },
246 BuiltinSpec {
247 path: &["install"],
248 description: Some("Install or update fdl globally"),
249 schema_fn: Some(InstallArgs::schema),
250 },
251 BuiltinSpec {
252 path: &["skill"],
253 description: Some("Manage AI coding assistant skills"),
254 schema_fn: None,
255 },
256 BuiltinSpec {
257 path: &["skill", "install"],
258 description: Some("Install skills for the detected tool"),
259 schema_fn: Some(SkillInstallArgs::schema),
260 },
261 BuiltinSpec {
262 path: &["skill", "list"],
263 description: Some("Show available skills"),
264 schema_fn: None,
265 },
266 BuiltinSpec {
267 path: &["api-ref"],
268 description: Some("Generate flodl API reference"),
269 schema_fn: Some(ApiRefArgs::schema),
270 },
271 BuiltinSpec {
272 path: &["config"],
273 description: Some("Inspect resolved project configuration"),
274 schema_fn: None,
275 },
276 BuiltinSpec {
277 path: &["config", "show"],
278 description: Some("Print the resolved merged config"),
279 schema_fn: None,
280 },
281 BuiltinSpec {
282 path: &["schema"],
283 description: Some("Inspect, clear, or refresh cached --fdl-schema outputs"),
284 schema_fn: None,
285 },
286 BuiltinSpec {
287 path: &["schema", "list"],
288 description: Some("Show every cached schema with status"),
289 schema_fn: Some(SchemaListArgs::schema),
290 },
291 BuiltinSpec {
292 path: &["schema", "clear"],
293 description: Some("Delete cached schema(s)"),
294 schema_fn: Some(SchemaClearArgs::schema),
295 },
296 BuiltinSpec {
297 path: &["schema", "refresh"],
298 description: Some("Re-probe each entry and rewrite the cache"),
299 schema_fn: Some(SchemaRefreshArgs::schema),
300 },
301 BuiltinSpec {
302 path: &["completions"],
303 description: Some("Emit shell completion script (bash|zsh|fish)"),
304 schema_fn: None,
305 },
306 BuiltinSpec {
307 path: &["autocomplete"],
308 description: Some("Install completions into the detected shell"),
309 schema_fn: None,
310 },
311 BuiltinSpec {
314 path: &["version"],
315 description: None,
316 schema_fn: None,
317 },
318 ];
319 REG
320}
321
322pub fn is_builtin_name(name: &str) -> bool {
325 registry()
326 .iter()
327 .any(|s| s.path.len() == 1 && s.path[0] == name)
328}
329
330pub fn visible_top_level() -> Vec<(&'static str, &'static str)> {
334 registry()
335 .iter()
336 .filter(|s| s.path.len() == 1)
337 .filter_map(|s| s.description.map(|d| (s.path[0], d)))
338 .collect()
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344 use std::collections::HashSet;
345
346
347
348 #[test]
349 fn registry_has_no_duplicate_paths() {
350 let mut seen = HashSet::new();
351 for s in registry() {
352 let key = s.path.join(" ");
353 assert!(
354 seen.insert(key.clone()),
355 "duplicate registry path: {key}"
356 );
357 }
358 }
359
360 #[test]
361 fn hidden_entries_have_no_description() {
362 for s in registry() {
363 if s.path == ["version"] {
364 assert!(s.description.is_none(),
365 "`version` is hidden but carries a description");
366 }
367 }
368 }
369
370 #[test]
371 fn every_parent_has_at_least_one_child() {
372 let parents: HashSet<&str> = registry()
373 .iter()
374 .filter(|s| s.path.len() == 1 && s.schema_fn.is_none()
375 && s.description.is_some())
376 .map(|s| s.path[0])
377 .collect();
378
379 for parent in &parents {
382 let has_child = registry().iter().any(|s| s.path.len() == 2 && s.path[0] == *parent);
383 if !has_child {
384 continue;
387 }
388 assert!(has_child, "parent `{parent}` has no child entries");
389 }
390 }
391
392 #[test]
393 fn top_level_dispatched_by_main_is_in_registry() {
394 let dispatched = [
398 "setup", "libtorch", "diagnose", "api-ref", "init", "install",
399 "skill", "schema", "completions", "autocomplete", "config",
400 "version",
401 ];
402 for name in &dispatched {
403 assert!(
404 is_builtin_name(name),
405 "`{name}` dispatched by main.rs but missing from registry"
406 );
407 }
408 }
409
410 #[test]
411 fn visible_top_level_matches_help_ordering() {
412 let top = visible_top_level();
413 let names: Vec<&str> = top.iter().map(|(n, _)| *n).collect();
414 assert_eq!(
416 names,
417 vec![
418 "setup", "libtorch", "init", "diagnose", "install", "skill",
419 "api-ref", "config", "schema", "completions", "autocomplete",
420 ]
421 );
422 }
423
424 #[test]
425 fn libtorch_download_schema_carries_cuda_choices() {
426 let spec = registry()
427 .iter()
428 .find(|s| s.path == ["libtorch", "download"])
429 .expect("libtorch download entry present");
430 let schema = (spec.schema_fn.expect("download has schema"))();
431 let cuda = schema
432 .options
433 .get("cuda")
434 .expect("`--cuda` option declared");
435 let choices = cuda.choices.as_ref().expect("--cuda has choices");
436 let values: Vec<String> = choices
437 .iter()
438 .filter_map(|v| v.as_str().map(str::to_string))
439 .collect();
440 assert_eq!(values, vec!["12.6".to_string(), "12.8".into()]);
441 }
442}