use crate::args::FdlArgsTrait;
use crate::config::Schema;
#[derive(crate::FdlArgs, Debug)]
pub struct SetupArgs {
#[option(short = 'y')]
pub non_interactive: bool,
#[option]
pub force: bool,
}
#[derive(crate::FdlArgs, Debug)]
pub struct DiagnoseArgs {
#[option]
pub json: bool,
}
#[derive(crate::FdlArgs, Debug)]
pub struct ApiRefArgs {
#[option]
pub json: bool,
#[option]
pub path: Option<String>,
}
#[derive(crate::FdlArgs, Debug)]
pub struct InitArgs {
#[arg]
pub name: Option<String>,
#[option]
pub docker: bool,
}
#[derive(crate::FdlArgs, Debug)]
pub struct InstallArgs {
#[option]
pub check: bool,
#[option]
pub dev: bool,
}
#[derive(crate::FdlArgs, Debug)]
pub struct LibtorchListArgs {
#[option]
pub json: bool,
}
#[derive(crate::FdlArgs, Debug)]
pub struct LibtorchActivateArgs {
#[arg]
pub variant: Option<String>,
}
#[derive(crate::FdlArgs, Debug)]
pub struct LibtorchRemoveArgs {
#[arg]
pub variant: Option<String>,
}
#[derive(crate::FdlArgs, Debug)]
pub struct LibtorchDownloadArgs {
#[option]
pub cpu: bool,
#[option(choices = &["12.6", "12.8"])]
pub cuda: Option<String>,
#[option]
pub path: Option<String>,
#[option]
pub no_activate: bool,
#[option]
pub dry_run: bool,
}
#[derive(crate::FdlArgs, Debug)]
pub struct LibtorchBuildArgs {
#[option]
pub archs: Option<String>,
#[option(default = "6")]
pub jobs: usize,
#[option]
pub docker: bool,
#[option]
pub native: bool,
#[option]
pub dry_run: bool,
}
#[derive(crate::FdlArgs, Debug)]
pub struct SkillInstallArgs {
#[option]
pub tool: Option<String>,
#[option]
pub skill: Option<String>,
}
#[derive(crate::FdlArgs, Debug)]
pub struct SchemaListArgs {
#[option]
pub json: bool,
}
#[derive(crate::FdlArgs, Debug)]
pub struct SchemaClearArgs {
#[arg]
pub cmd: Option<String>,
}
#[derive(crate::FdlArgs, Debug)]
pub struct SchemaRefreshArgs {
#[arg]
pub cmd: Option<String>,
}
pub struct BuiltinSpec {
pub path: &'static [&'static str],
pub description: Option<&'static str>,
pub schema_fn: Option<fn() -> Schema>,
}
pub fn registry() -> &'static [BuiltinSpec] {
static REG: &[BuiltinSpec] = &[
BuiltinSpec {
path: &["setup"],
description: Some("Interactive guided setup"),
schema_fn: Some(SetupArgs::schema),
},
BuiltinSpec {
path: &["libtorch"],
description: Some("Manage libtorch installations"),
schema_fn: None,
},
BuiltinSpec {
path: &["libtorch", "download"],
description: Some("Download pre-built libtorch"),
schema_fn: Some(LibtorchDownloadArgs::schema),
},
BuiltinSpec {
path: &["libtorch", "build"],
description: Some("Build libtorch from source"),
schema_fn: Some(LibtorchBuildArgs::schema),
},
BuiltinSpec {
path: &["libtorch", "list"],
description: Some("Show installed variants"),
schema_fn: Some(LibtorchListArgs::schema),
},
BuiltinSpec {
path: &["libtorch", "activate"],
description: Some("Set active variant"),
schema_fn: Some(LibtorchActivateArgs::schema),
},
BuiltinSpec {
path: &["libtorch", "remove"],
description: Some("Remove a variant"),
schema_fn: Some(LibtorchRemoveArgs::schema),
},
BuiltinSpec {
path: &["libtorch", "info"],
description: Some("Show active variant details"),
schema_fn: None,
},
BuiltinSpec {
path: &["init"],
description: Some("Scaffold a new floDl project"),
schema_fn: Some(InitArgs::schema),
},
BuiltinSpec {
path: &["diagnose"],
description: Some("System and GPU diagnostics"),
schema_fn: Some(DiagnoseArgs::schema),
},
BuiltinSpec {
path: &["install"],
description: Some("Install or update fdl globally"),
schema_fn: Some(InstallArgs::schema),
},
BuiltinSpec {
path: &["skill"],
description: Some("Manage AI coding assistant skills"),
schema_fn: None,
},
BuiltinSpec {
path: &["skill", "install"],
description: Some("Install skills for the detected tool"),
schema_fn: Some(SkillInstallArgs::schema),
},
BuiltinSpec {
path: &["skill", "list"],
description: Some("Show available skills"),
schema_fn: None,
},
BuiltinSpec {
path: &["api-ref"],
description: Some("Generate flodl API reference"),
schema_fn: Some(ApiRefArgs::schema),
},
BuiltinSpec {
path: &["config"],
description: Some("Inspect resolved project configuration"),
schema_fn: None,
},
BuiltinSpec {
path: &["config", "show"],
description: Some("Print the resolved merged config"),
schema_fn: None,
},
BuiltinSpec {
path: &["schema"],
description: Some("Inspect, clear, or refresh cached --fdl-schema outputs"),
schema_fn: None,
},
BuiltinSpec {
path: &["schema", "list"],
description: Some("Show every cached schema with status"),
schema_fn: Some(SchemaListArgs::schema),
},
BuiltinSpec {
path: &["schema", "clear"],
description: Some("Delete cached schema(s)"),
schema_fn: Some(SchemaClearArgs::schema),
},
BuiltinSpec {
path: &["schema", "refresh"],
description: Some("Re-probe each entry and rewrite the cache"),
schema_fn: Some(SchemaRefreshArgs::schema),
},
BuiltinSpec {
path: &["completions"],
description: Some("Emit shell completion script (bash|zsh|fish)"),
schema_fn: None,
},
BuiltinSpec {
path: &["autocomplete"],
description: Some("Install completions into the detected shell"),
schema_fn: None,
},
BuiltinSpec {
path: &["version"],
description: None,
schema_fn: None,
},
];
REG
}
pub fn is_builtin_name(name: &str) -> bool {
registry()
.iter()
.any(|s| s.path.len() == 1 && s.path[0] == name)
}
pub fn visible_top_level() -> Vec<(&'static str, &'static str)> {
registry()
.iter()
.filter(|s| s.path.len() == 1)
.filter_map(|s| s.description.map(|d| (s.path[0], d)))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn registry_has_no_duplicate_paths() {
let mut seen = HashSet::new();
for s in registry() {
let key = s.path.join(" ");
assert!(
seen.insert(key.clone()),
"duplicate registry path: {key}"
);
}
}
#[test]
fn hidden_entries_have_no_description() {
for s in registry() {
if s.path == ["version"] {
assert!(s.description.is_none(),
"`version` is hidden but carries a description");
}
}
}
#[test]
fn every_parent_has_at_least_one_child() {
let parents: HashSet<&str> = registry()
.iter()
.filter(|s| s.path.len() == 1 && s.schema_fn.is_none()
&& s.description.is_some())
.map(|s| s.path[0])
.collect();
for parent in &parents {
let has_child = registry().iter().any(|s| s.path.len() == 2 && s.path[0] == *parent);
if !has_child {
continue;
}
assert!(has_child, "parent `{parent}` has no child entries");
}
}
#[test]
fn top_level_dispatched_by_main_is_in_registry() {
let dispatched = [
"setup", "libtorch", "diagnose", "api-ref", "init", "install",
"skill", "schema", "completions", "autocomplete", "config",
"version",
];
for name in &dispatched {
assert!(
is_builtin_name(name),
"`{name}` dispatched by main.rs but missing from registry"
);
}
}
#[test]
fn visible_top_level_matches_help_ordering() {
let top = visible_top_level();
let names: Vec<&str> = top.iter().map(|(n, _)| *n).collect();
assert_eq!(
names,
vec![
"setup", "libtorch", "init", "diagnose", "install", "skill",
"api-ref", "config", "schema", "completions", "autocomplete",
]
);
}
#[test]
fn libtorch_download_schema_carries_cuda_choices() {
let spec = registry()
.iter()
.find(|s| s.path == ["libtorch", "download"])
.expect("libtorch download entry present");
let schema = (spec.schema_fn.expect("download has schema"))();
let cuda = schema
.options
.get("cuda")
.expect("`--cuda` option declared");
let choices = cuda.choices.as_ref().expect("--cuda has choices");
let values: Vec<String> = choices
.iter()
.filter_map(|v| v.as_str().map(str::to_string))
.collect();
assert_eq!(values, vec!["12.6".to_string(), "12.8".into()]);
}
}