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)]
58pub struct InitArgs {
59 #[arg]
61 pub name: Option<String>,
62 #[option]
64 pub docker: bool,
65 #[option]
67 pub native: bool,
68 #[option]
70 pub with_hf: bool,
71}
72
73#[derive(crate::FdlArgs, Debug)]
88pub struct AddArgs {
89 #[arg]
91 pub target: Option<String>,
92 #[option]
94 pub playground: bool,
95 #[option]
97 pub install: bool,
98}
99
100#[derive(crate::FdlArgs, Debug)]
102pub struct InstallArgs {
103 #[option]
105 pub check: bool,
106 #[option]
108 pub dev: bool,
109}
110
111#[derive(crate::FdlArgs, Debug)]
113pub struct LibtorchListArgs {
114 #[option]
116 pub json: bool,
117}
118
119#[derive(crate::FdlArgs, Debug)]
121pub struct LibtorchActivateArgs {
122 #[arg]
124 pub variant: Option<String>,
125}
126
127#[derive(crate::FdlArgs, Debug)]
129pub struct LibtorchRemoveArgs {
130 #[arg]
132 pub variant: Option<String>,
133}
134
135#[derive(crate::FdlArgs, Debug)]
137pub struct LibtorchDownloadArgs {
138 #[option]
140 pub cpu: bool,
141 #[option(choices = &["12.6", "12.8"])]
143 pub cuda: Option<String>,
144 #[option]
146 pub path: Option<String>,
147 #[option]
149 pub no_activate: bool,
150 #[option]
152 pub dry_run: bool,
153}
154
155#[derive(crate::FdlArgs, Debug)]
157pub struct LibtorchBuildArgs {
158 #[option]
160 pub archs: Option<String>,
161 #[option(default = "6")]
163 pub jobs: usize,
164 #[option]
166 pub docker: bool,
167 #[option]
169 pub native: bool,
170 #[option]
172 pub dry_run: bool,
173}
174
175#[derive(crate::FdlArgs, Debug)]
177pub struct SkillInstallArgs {
178 #[option]
180 pub tool: Option<String>,
181 #[option]
183 pub skill: Option<String>,
184}
185
186#[derive(crate::FdlArgs, Debug)]
188pub struct SchemaListArgs {
189 #[option]
191 pub json: bool,
192}
193
194#[derive(crate::FdlArgs, Debug)]
196pub struct SchemaClearArgs {
197 #[arg]
199 pub cmd: Option<String>,
200}
201
202#[derive(crate::FdlArgs, Debug)]
204pub struct SchemaRefreshArgs {
205 #[arg]
207 pub cmd: Option<String>,
208}
209
210pub struct BuiltinSpec {
216 pub path: &'static [&'static str],
219 pub description: Option<&'static str>,
222 pub schema_fn: Option<fn() -> Schema>,
227}
228
229pub fn registry() -> &'static [BuiltinSpec] {
233 static REG: &[BuiltinSpec] = &[
234 BuiltinSpec {
235 path: &["setup"],
236 description: Some("Interactive guided setup"),
237 schema_fn: Some(SetupArgs::schema),
238 },
239 BuiltinSpec {
240 path: &["libtorch"],
241 description: Some("Manage libtorch installations"),
242 schema_fn: None,
243 },
244 BuiltinSpec {
245 path: &["libtorch", "download"],
246 description: Some("Download pre-built libtorch"),
247 schema_fn: Some(LibtorchDownloadArgs::schema),
248 },
249 BuiltinSpec {
250 path: &["libtorch", "build"],
251 description: Some("Build libtorch from source"),
252 schema_fn: Some(LibtorchBuildArgs::schema),
253 },
254 BuiltinSpec {
255 path: &["libtorch", "list"],
256 description: Some("Show installed variants"),
257 schema_fn: Some(LibtorchListArgs::schema),
258 },
259 BuiltinSpec {
260 path: &["libtorch", "activate"],
261 description: Some("Set active variant"),
262 schema_fn: Some(LibtorchActivateArgs::schema),
263 },
264 BuiltinSpec {
265 path: &["libtorch", "remove"],
266 description: Some("Remove a variant"),
267 schema_fn: Some(LibtorchRemoveArgs::schema),
268 },
269 BuiltinSpec {
270 path: &["libtorch", "info"],
271 description: Some("Show active variant details"),
272 schema_fn: None,
273 },
274 BuiltinSpec {
275 path: &["init"],
276 description: Some("Scaffold a new floDl project"),
277 schema_fn: Some(InitArgs::schema),
278 },
279 BuiltinSpec {
280 path: &["add"],
281 description: Some("Add a flodl ecosystem crate (currently: flodl-hf)"),
282 schema_fn: Some(AddArgs::schema),
283 },
284 BuiltinSpec {
285 path: &["diagnose"],
286 description: Some("System and GPU diagnostics"),
287 schema_fn: Some(DiagnoseArgs::schema),
288 },
289 BuiltinSpec {
290 path: &["install"],
291 description: Some("Install or update fdl globally"),
292 schema_fn: Some(InstallArgs::schema),
293 },
294 BuiltinSpec {
295 path: &["skill"],
296 description: Some("Manage AI coding assistant skills"),
297 schema_fn: None,
298 },
299 BuiltinSpec {
300 path: &["skill", "install"],
301 description: Some("Install skills for the detected tool"),
302 schema_fn: Some(SkillInstallArgs::schema),
303 },
304 BuiltinSpec {
305 path: &["skill", "list"],
306 description: Some("Show available skills"),
307 schema_fn: None,
308 },
309 BuiltinSpec {
310 path: &["api-ref"],
311 description: Some("Generate flodl API reference"),
312 schema_fn: Some(ApiRefArgs::schema),
313 },
314 BuiltinSpec {
315 path: &["config"],
316 description: Some("Inspect resolved project configuration"),
317 schema_fn: None,
318 },
319 BuiltinSpec {
320 path: &["config", "show"],
321 description: Some("Print the resolved merged config"),
322 schema_fn: None,
323 },
324 BuiltinSpec {
325 path: &["schema"],
326 description: Some("Inspect, clear, or refresh cached --fdl-schema outputs"),
327 schema_fn: None,
328 },
329 BuiltinSpec {
330 path: &["schema", "list"],
331 description: Some("Show every cached schema with status"),
332 schema_fn: Some(SchemaListArgs::schema),
333 },
334 BuiltinSpec {
335 path: &["schema", "clear"],
336 description: Some("Delete cached schema(s)"),
337 schema_fn: Some(SchemaClearArgs::schema),
338 },
339 BuiltinSpec {
340 path: &["schema", "refresh"],
341 description: Some("Re-probe each entry and rewrite the cache"),
342 schema_fn: Some(SchemaRefreshArgs::schema),
343 },
344 BuiltinSpec {
345 path: &["completions"],
346 description: Some("Emit shell completion script (bash|zsh|fish)"),
347 schema_fn: None,
348 },
349 BuiltinSpec {
350 path: &["autocomplete"],
351 description: Some("Install completions into the detected shell"),
352 schema_fn: None,
353 },
354 BuiltinSpec {
357 path: &["version"],
358 description: None,
359 schema_fn: None,
360 },
361 ];
362 REG
363}
364
365pub fn is_builtin_name(name: &str) -> bool {
368 registry()
369 .iter()
370 .any(|s| s.path.len() == 1 && s.path[0] == name)
371}
372
373pub fn visible_top_level() -> Vec<(&'static str, &'static str)> {
377 registry()
378 .iter()
379 .filter(|s| s.path.len() == 1)
380 .filter_map(|s| s.description.map(|d| (s.path[0], d)))
381 .collect()
382}
383
384#[cfg(test)]
385mod tests {
386 use super::*;
387 use std::collections::HashSet;
388
389
390
391 #[test]
392 fn registry_has_no_duplicate_paths() {
393 let mut seen = HashSet::new();
394 for s in registry() {
395 let key = s.path.join(" ");
396 assert!(
397 seen.insert(key.clone()),
398 "duplicate registry path: {key}"
399 );
400 }
401 }
402
403 #[test]
404 fn hidden_entries_have_no_description() {
405 for s in registry() {
406 if s.path == ["version"] {
407 assert!(s.description.is_none(),
408 "`version` is hidden but carries a description");
409 }
410 }
411 }
412
413 #[test]
414 fn every_parent_has_at_least_one_child() {
415 let parents: HashSet<&str> = registry()
416 .iter()
417 .filter(|s| s.path.len() == 1 && s.schema_fn.is_none()
418 && s.description.is_some())
419 .map(|s| s.path[0])
420 .collect();
421
422 for parent in &parents {
425 let has_child = registry().iter().any(|s| s.path.len() == 2 && s.path[0] == *parent);
426 if !has_child {
427 continue;
430 }
431 assert!(has_child, "parent `{parent}` has no child entries");
432 }
433 }
434
435 #[test]
436 fn top_level_dispatched_by_main_is_in_registry() {
437 let dispatched = [
441 "setup", "libtorch", "diagnose", "api-ref", "init", "add",
442 "install", "skill", "schema", "completions", "autocomplete",
443 "config", "version",
444 ];
445 for name in &dispatched {
446 assert!(
447 is_builtin_name(name),
448 "`{name}` dispatched by main.rs but missing from registry"
449 );
450 }
451 }
452
453 #[test]
454 fn visible_top_level_matches_help_ordering() {
455 let top = visible_top_level();
456 let names: Vec<&str> = top.iter().map(|(n, _)| *n).collect();
457 assert_eq!(
459 names,
460 vec![
461 "setup", "libtorch", "init", "add", "diagnose", "install",
462 "skill", "api-ref", "config", "schema", "completions",
463 "autocomplete",
464 ]
465 );
466 }
467
468 #[test]
469 fn libtorch_download_schema_carries_cuda_choices() {
470 let spec = registry()
471 .iter()
472 .find(|s| s.path == ["libtorch", "download"])
473 .expect("libtorch download entry present");
474 let schema = (spec.schema_fn.expect("download has schema"))();
475 let cuda = schema
476 .options
477 .get("cuda")
478 .expect("`--cuda` option declared");
479 let choices = cuda.choices.as_ref().expect("--cuda has choices");
480 let values: Vec<String> = choices
481 .iter()
482 .filter_map(|v| v.as_str().map(str::to_string))
483 .collect();
484 assert_eq!(values, vec!["12.6".to_string(), "12.8".into()]);
485 }
486}