magi-code 0.96.2

Repository-aware CLI coding agent for terminal work
Documentation
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,
            },
        );
    }
}