350 lines
9.4 KiB
Rust
350 lines
9.4 KiB
Rust
//! Slash command registry and dispatch system
|
|
//!
|
|
//! This module provides a modular command system inspired by Codex-rs.
|
|
//! Commands are organized by category and dispatched through a central registry.
|
|
|
|
mod config;
|
|
mod core;
|
|
mod debug;
|
|
mod init;
|
|
mod note;
|
|
mod queue;
|
|
mod review;
|
|
mod session;
|
|
mod skills;
|
|
|
|
use crate::tui::app::{App, AppAction};
|
|
|
|
/// Result of executing a command
|
|
#[derive(Debug, Clone)]
|
|
pub struct CommandResult {
|
|
/// Optional message to display to the user
|
|
pub message: Option<String>,
|
|
/// Optional action for the app to take
|
|
pub action: Option<AppAction>,
|
|
}
|
|
|
|
impl CommandResult {
|
|
/// Create an empty result (command succeeded with no output)
|
|
pub fn ok() -> Self {
|
|
Self {
|
|
message: None,
|
|
action: None,
|
|
}
|
|
}
|
|
|
|
/// Create a result with just a message
|
|
pub fn message(msg: impl Into<String>) -> Self {
|
|
Self {
|
|
message: Some(msg.into()),
|
|
action: None,
|
|
}
|
|
}
|
|
|
|
/// Create a result with an action
|
|
pub fn action(action: AppAction) -> Self {
|
|
Self {
|
|
message: None,
|
|
action: Some(action),
|
|
}
|
|
}
|
|
|
|
/// Create a result with both message and action
|
|
#[allow(dead_code)]
|
|
pub fn with_message_and_action(msg: impl Into<String>, action: AppAction) -> Self {
|
|
Self {
|
|
message: Some(msg.into()),
|
|
action: Some(action),
|
|
}
|
|
}
|
|
|
|
/// Create an error message result
|
|
pub fn error(msg: impl Into<String>) -> Self {
|
|
Self {
|
|
message: Some(format!("Error: {}", msg.into())),
|
|
action: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Command metadata for help and autocomplete
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct CommandInfo {
|
|
pub name: &'static str,
|
|
pub aliases: &'static [&'static str],
|
|
pub description: &'static str,
|
|
pub usage: &'static str,
|
|
}
|
|
|
|
/// All registered commands
|
|
pub const COMMANDS: &[CommandInfo] = &[
|
|
// Core commands
|
|
CommandInfo {
|
|
name: "help",
|
|
aliases: &["?"],
|
|
description: "Show help information",
|
|
usage: "/help [command]",
|
|
},
|
|
CommandInfo {
|
|
name: "clear",
|
|
aliases: &[],
|
|
description: "Clear conversation history",
|
|
usage: "/clear",
|
|
},
|
|
CommandInfo {
|
|
name: "exit",
|
|
aliases: &["quit", "q"],
|
|
description: "Exit the application",
|
|
usage: "/exit",
|
|
},
|
|
CommandInfo {
|
|
name: "model",
|
|
aliases: &[],
|
|
description: "Switch or view current model",
|
|
usage: "/model [name]",
|
|
},
|
|
CommandInfo {
|
|
name: "queue",
|
|
aliases: &["queued"],
|
|
description: "View or edit queued messages",
|
|
usage: "/queue [list|edit <n>|drop <n>|clear]",
|
|
},
|
|
CommandInfo {
|
|
name: "subagents",
|
|
aliases: &["agents"],
|
|
description: "List sub-agent status",
|
|
usage: "/subagents",
|
|
},
|
|
CommandInfo {
|
|
name: "deepseek",
|
|
aliases: &["dashboard", "api"],
|
|
description: "Show DeepSeek dashboard and docs links",
|
|
usage: "/deepseek",
|
|
},
|
|
CommandInfo {
|
|
name: "home",
|
|
aliases: &["stats", "overview"],
|
|
description: "Show home dashboard with stats and quick actions",
|
|
usage: "/home",
|
|
},
|
|
CommandInfo {
|
|
name: "note",
|
|
aliases: &[],
|
|
description: "Append note to persistent notes file (.deepseek/notes.md)",
|
|
usage: "/note <text>",
|
|
},
|
|
// Session commands
|
|
CommandInfo {
|
|
name: "save",
|
|
aliases: &[],
|
|
description: "Save session to file",
|
|
usage: "/save [path]",
|
|
},
|
|
CommandInfo {
|
|
name: "sessions",
|
|
aliases: &["resume"],
|
|
description: "Open session picker",
|
|
usage: "/sessions",
|
|
},
|
|
CommandInfo {
|
|
name: "load",
|
|
aliases: &[],
|
|
description: "Load session from file",
|
|
usage: "/load [path]",
|
|
},
|
|
CommandInfo {
|
|
name: "compact",
|
|
aliases: &[],
|
|
description: "Toggle auto-compaction",
|
|
usage: "/compact",
|
|
},
|
|
CommandInfo {
|
|
name: "export",
|
|
aliases: &[],
|
|
description: "Export conversation to markdown",
|
|
usage: "/export [path]",
|
|
},
|
|
// Config commands
|
|
CommandInfo {
|
|
name: "config",
|
|
aliases: &[],
|
|
description: "Display current configuration",
|
|
usage: "/config",
|
|
},
|
|
CommandInfo {
|
|
name: "set",
|
|
aliases: &[],
|
|
description: "Modify a setting",
|
|
usage: "/set <key> <value>",
|
|
},
|
|
CommandInfo {
|
|
name: "yolo",
|
|
aliases: &[],
|
|
description: "Enable YOLO mode (shell + trust + auto-approve)",
|
|
usage: "/yolo",
|
|
},
|
|
CommandInfo {
|
|
name: "trust",
|
|
aliases: &[],
|
|
description: "Enable trust mode (access files outside workspace)",
|
|
usage: "/trust",
|
|
},
|
|
CommandInfo {
|
|
name: "logout",
|
|
aliases: &[],
|
|
description: "Clear API key and return to setup",
|
|
usage: "/logout",
|
|
},
|
|
// Debug commands
|
|
CommandInfo {
|
|
name: "tokens",
|
|
aliases: &[],
|
|
description: "Show token usage for session",
|
|
usage: "/tokens",
|
|
},
|
|
CommandInfo {
|
|
name: "system",
|
|
aliases: &[],
|
|
description: "Show current system prompt",
|
|
usage: "/system",
|
|
},
|
|
CommandInfo {
|
|
name: "context",
|
|
aliases: &[],
|
|
description: "Show context window usage",
|
|
usage: "/context",
|
|
},
|
|
CommandInfo {
|
|
name: "undo",
|
|
aliases: &[],
|
|
description: "Remove last message pair",
|
|
usage: "/undo",
|
|
},
|
|
CommandInfo {
|
|
name: "retry",
|
|
aliases: &[],
|
|
description: "Retry the last request",
|
|
usage: "/retry",
|
|
},
|
|
CommandInfo {
|
|
name: "init",
|
|
aliases: &[],
|
|
description: "Generate AGENTS.md for project",
|
|
usage: "/init",
|
|
},
|
|
CommandInfo {
|
|
name: "settings",
|
|
aliases: &[],
|
|
description: "Show persistent settings",
|
|
usage: "/settings",
|
|
},
|
|
// Skills commands
|
|
CommandInfo {
|
|
name: "skills",
|
|
aliases: &[],
|
|
description: "List available skills",
|
|
usage: "/skills",
|
|
},
|
|
CommandInfo {
|
|
name: "skill",
|
|
aliases: &[],
|
|
description: "Activate a skill for next message",
|
|
usage: "/skill <name>",
|
|
},
|
|
CommandInfo {
|
|
name: "review",
|
|
aliases: &[],
|
|
description: "Run a structured code review on a file, diff, or PR",
|
|
usage: "/review <target>",
|
|
},
|
|
// Debug/cost command
|
|
CommandInfo {
|
|
name: "cost",
|
|
aliases: &[],
|
|
description: "Show session cost breakdown",
|
|
usage: "/cost",
|
|
},
|
|
];
|
|
|
|
/// Execute a slash command
|
|
pub fn execute(cmd: &str, app: &mut App) -> CommandResult {
|
|
let parts: Vec<&str> = cmd.trim().splitn(2, ' ').collect();
|
|
let command = parts[0].to_lowercase();
|
|
let command = command.strip_prefix('/').unwrap_or(&command);
|
|
let arg = parts.get(1).map(|s| s.trim());
|
|
|
|
// Match command or alias
|
|
match command {
|
|
// Core commands
|
|
"help" | "?" => core::help(app, arg),
|
|
"clear" => core::clear(app),
|
|
"exit" | "quit" | "q" => core::exit(),
|
|
"model" => core::model(app, arg),
|
|
"queue" | "queued" => queue::queue(app, arg),
|
|
"subagents" | "agents" => core::subagents(app),
|
|
"deepseek" | "dashboard" | "api" => core::deepseek_links(),
|
|
"home" | "stats" | "overview" => core::home_dashboard(app),
|
|
"note" => note::note(app, arg),
|
|
|
|
// Session commands
|
|
"save" => session::save(app, arg),
|
|
"sessions" | "resume" => session::sessions(app),
|
|
"load" => session::load(app, arg),
|
|
"compact" => session::compact(app),
|
|
"export" => session::export(app, arg),
|
|
|
|
// Config commands
|
|
"config" => config::show_config(app),
|
|
"settings" => config::show_settings(app),
|
|
"set" => config::set_config(app, arg),
|
|
"yolo" => config::yolo(app),
|
|
"trust" => config::trust(app),
|
|
"logout" => config::logout(app),
|
|
|
|
// Debug commands
|
|
"tokens" => debug::tokens(app),
|
|
"cost" => debug::cost(app),
|
|
"system" => debug::system_prompt(app),
|
|
"context" => debug::context(app),
|
|
"undo" => debug::undo(app),
|
|
"retry" => debug::retry(app),
|
|
|
|
// Project commands
|
|
"init" => init::init(app),
|
|
|
|
// Skills commands
|
|
"skills" => skills::list_skills(app),
|
|
"skill" => skills::run_skill(app, arg),
|
|
"review" => review::review(app, arg),
|
|
|
|
_ => CommandResult::error(format!(
|
|
"Unknown command: /{command}. Type /help for available commands."
|
|
)),
|
|
}
|
|
}
|
|
|
|
/// Get command info by name or alias
|
|
pub fn get_command_info(name: &str) -> Option<&'static CommandInfo> {
|
|
let name = name.strip_prefix('/').unwrap_or(name);
|
|
COMMANDS
|
|
.iter()
|
|
.find(|cmd| cmd.name == name || cmd.aliases.contains(&name))
|
|
}
|
|
|
|
/// Get all commands matching a prefix (for autocomplete)
|
|
#[allow(dead_code)]
|
|
pub fn commands_matching(prefix: &str) -> Vec<&'static CommandInfo> {
|
|
let prefix = prefix.strip_prefix('/').unwrap_or(prefix).to_lowercase();
|
|
COMMANDS
|
|
.iter()
|
|
.filter(|cmd| {
|
|
cmd.name.starts_with(&prefix) || cmd.aliases.iter().any(|a| a.starts_with(&prefix))
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
// No unit tests currently required for command routing.
|
|
}
|