#[derive(Debug, Clone, Copy)]
pub struct SlashCommand {
pub name: &'static str,
pub aliases: &'static [&'static str],
pub description: &'static str,
pub arg_hint: Option<&'static str>,
}
pub const COMMAND_REGISTRY: &[SlashCommand] = &[
SlashCommand {
name: "model",
aliases: &[],
description: "Switch model (auto-pulls if needed) or show current",
arg_hint: Some("[name]"),
},
SlashCommand {
name: "reasoning",
aliases: &[],
description: "Set reasoning depth (none, minimal, low, medium, high, max, xhigh)",
arg_hint: Some("[level]"),
},
SlashCommand {
name: "clear",
aliases: &[],
description: "Clear chat history",
arg_hint: None,
},
SlashCommand {
name: "save",
aliases: &[],
description: "Save current conversation",
arg_hint: Some("[name]"),
},
SlashCommand {
name: "load",
aliases: &[],
description: "Load a conversation",
arg_hint: Some("[name]"),
},
SlashCommand {
name: "list",
aliases: &[],
description: "List saved conversations",
arg_hint: None,
},
SlashCommand {
name: "usage",
aliases: &[],
description: "Show provider token usage and session totals",
arg_hint: None,
},
SlashCommand {
name: "context",
aliases: &[],
description: "Show current context-window estimate and prompt budget",
arg_hint: None,
},
SlashCommand {
name: "compact",
aliases: &["compress", "summarize"],
description: "Compact conversation context with optional focus instructions",
arg_hint: Some("[instructions]"),
},
SlashCommand {
name: "cloud-setup",
aliases: &[],
description: "Configure Ollama Cloud API key",
arg_hint: None,
},
SlashCommand {
name: "help",
aliases: &["h"],
description: "Show command help",
arg_hint: None,
},
SlashCommand {
name: "quit",
aliases: &["q"],
description: "Quit the application",
arg_hint: None,
},
];
pub fn filter_by_prefix(typed: &str) -> Vec<&'static SlashCommand> {
let needle = typed.to_lowercase();
if needle.is_empty() {
return COMMAND_REGISTRY.iter().collect();
}
COMMAND_REGISTRY
.iter()
.filter(|cmd| {
cmd.name.starts_with(&needle) || cmd.aliases.iter().any(|a| a.starts_with(&needle))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn filter_by_prefix_empty_returns_all() {
let result = filter_by_prefix("");
assert_eq!(result.len(), COMMAND_REGISTRY.len());
assert_eq!(result[0].name, COMMAND_REGISTRY[0].name);
}
#[test]
fn filter_by_prefix_exact_match() {
let result = filter_by_prefix("model");
assert_eq!(result.len(), 1);
assert_eq!(result[0].name, "model");
}
#[test]
fn filter_by_prefix_partial_prefix_matches_one() {
let result = filter_by_prefix("mod");
assert_eq!(result.len(), 1);
assert_eq!(result[0].name, "model");
}
#[test]
fn filter_by_prefix_no_match_returns_empty() {
let result = filter_by_prefix("zzzzz");
assert!(result.is_empty());
}
#[test]
fn filter_by_prefix_matches_aliases() {
let result = filter_by_prefix("q");
assert!(
result.iter().any(|c| c.name == "quit"),
"expected quit in: {:?}",
result.iter().map(|c| c.name).collect::<Vec<_>>()
);
}
#[test]
fn filter_by_prefix_is_case_insensitive() {
let upper = filter_by_prefix("MODEL");
assert_eq!(upper.len(), 1);
assert_eq!(upper[0].name, "model");
}
#[test]
fn registry_has_no_duplicate_names() {
let mut names: Vec<&str> = COMMAND_REGISTRY.iter().map(|c| c.name).collect();
names.sort_unstable();
let len_before = names.len();
names.dedup();
assert_eq!(
names.len(),
len_before,
"duplicate command name detected in COMMAND_REGISTRY"
);
}
}