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