pub(crate) mod runtime;
use std::collections::BTreeMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltInCommand {
Help,
Update,
Login,
Logout,
Compact,
Fast,
Ultrafast,
SummarizeStart,
SummarizeStop,
Theme,
Settings,
New,
Side,
Changes,
Rewind,
Export,
Model,
Usage,
Mcp,
Subagents,
SystemPrompt,
Skills,
Sessions,
PruneSessions,
Quit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum FastModeCommand {
Toggle,
On,
Off,
Status,
}
pub(crate) const FAST_MODE_USAGE: &str = "usage: /fast [on|off|status] (bare /fast toggles)";
pub(crate) const ULTRAFAST_MODE_USAGE: &str =
"usage: /ultrafast [on|off|status] (bare /ultrafast toggles)";
pub(crate) fn parse_fast_mode_command(arg: Option<&str>) -> Result<FastModeCommand, &'static str> {
match arg.map(str::trim).filter(|arg| !arg.is_empty()) {
None => Ok(FastModeCommand::Toggle),
Some("on") => Ok(FastModeCommand::On),
Some("off") => Ok(FastModeCommand::Off),
Some("status") => Ok(FastModeCommand::Status),
Some(_) => Err(FAST_MODE_USAGE),
}
}
impl BuiltInCommand {
pub const ALL: [Self; 25] = [
Self::Help,
Self::Update,
Self::Login,
Self::Logout,
Self::Compact,
Self::Fast,
Self::Ultrafast,
Self::SummarizeStart,
Self::SummarizeStop,
Self::Theme,
Self::Settings,
Self::New,
Self::Side,
Self::Changes,
Self::Rewind,
Self::Export,
Self::Model,
Self::Usage,
Self::Mcp,
Self::Subagents,
Self::SystemPrompt,
Self::Skills,
Self::Sessions,
Self::PruneSessions,
Self::Quit,
];
pub fn name(self) -> &'static str {
match self {
Self::Help => "help",
Self::Update => "update",
Self::Login => "login",
Self::Logout => "logout",
Self::Compact => "compact",
Self::Fast => "fast",
Self::Ultrafast => "ultrafast",
Self::SummarizeStart => "summarize-start",
Self::SummarizeStop => "summarize-stop",
Self::Theme => "theme",
Self::Settings => "settings",
Self::New => "new",
Self::Side => "side",
Self::Changes => "changes",
Self::Rewind => "rewind",
Self::Export => "export",
Self::Model => "model",
Self::Usage => "usage",
Self::Mcp => "mcp",
Self::Subagents => "subagents",
Self::SystemPrompt => "system-prompt",
Self::Skills => "skills",
Self::Sessions => "sessions",
Self::PruneSessions => "prune-sessions",
Self::Quit => "quit",
}
}
pub fn description(self) -> &'static str {
match self {
Self::Help => "open Mission Control help",
Self::Update => "update with Cargo and reopen this idle session",
Self::Login => "connect OpenAI Codex or configure a custom provider",
Self::Logout => "remove provider auth or custom provider metadata",
Self::Compact => "summarize earlier session turns into a replay boundary",
Self::New => "start a new session",
Self::Side => "open an isolated side conversation",
Self::Fast => "toggle global Fast mode",
Self::Ultrafast => "toggle global Ultrafast mode (higher provider usage)",
Self::SummarizeStart => "start the session summary",
Self::SummarizeStop => "stop the session summary",
Self::Theme => "preview and save the Mission Control theme",
Self::Settings => "edit global or project settings",
Self::Changes => "show rewindable local filesystem changes for this session",
Self::Rewind => "rewind to a prior prompt: conversation, files, or both",
Self::Export => {
"export active session to a unique Unix-only unencrypted ZIP; contains sensitive raw data"
}
Self::Model => "select a provider-qualified model",
Self::Usage => "show provider usage and quota remaining",
Self::Mcp => "manage MCP server availability",
Self::Subagents => "manage subagent profile availability",
Self::SystemPrompt => "show the current computed system prompt",
Self::Skills => "manage assistant skill availability",
Self::Sessions => "switch active Mission Control session",
Self::PruneSessions => "delete old local session JSONL files",
Self::Quit => "exit magi-code",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SlashCommand {
pub name: String,
pub description: String,
pub command: BuiltInCommand,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ParsedSlashCommand<'a> {
Empty,
Prompt(&'a str),
BuiltIn {
command: BuiltInCommand,
arg: Option<&'a str>,
},
Unknown {
token: &'a str,
},
}
pub(crate) fn parse_slash_command<'a>(
input: &'a str,
commands: &CommandRegistry,
) -> ParsedSlashCommand<'a> {
let trimmed = input.trim();
if trimmed.is_empty() {
return ParsedSlashCommand::Empty;
}
if !trimmed.starts_with('/') {
return ParsedSlashCommand::Prompt(trimmed);
}
let (token, arg) = split_slash_command(trimmed);
match commands.get(token).map(|command| command.command) {
Some(command) => ParsedSlashCommand::BuiltIn { command, arg },
None => ParsedSlashCommand::Unknown { token },
}
}
fn split_slash_command(input: &str) -> (&str, Option<&str>) {
input
.split_once(char::is_whitespace)
.map_or((input, None), |(token, arg)| {
(token, Some(arg.trim()).filter(|arg| !arg.is_empty()))
})
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CommandRegistry {
commands: BTreeMap<String, SlashCommand>,
}
impl CommandRegistry {
pub fn mvp() -> Self {
let mut registry = Self::default();
for command in BuiltInCommand::ALL {
registry.insert_builtin(command);
}
registry
}
pub fn get(&self, input: &str) -> Option<&SlashCommand> {
input
.strip_prefix('/')
.and_then(|name| self.commands.get(name))
}
pub fn list(&self) -> Vec<&SlashCommand> {
self.commands.values().collect()
}
fn insert_builtin(&mut self, command: BuiltInCommand) {
let name = command.name().to_string();
self.commands.insert(
name.clone(),
SlashCommand {
name,
description: command.description().to_string(),
command,
},
);
}
}