commands — Slash Command System

Slash Command System

80+ slash commands orchestrating the complete command lifecycle from registration through parsing to execution. Three command types: PromptCommand (model-invoked skills), LocalJSXCommand (React UI), LocalCommand (pure function). Extensible via skills, plugins, and MCP.

87
Commands
207
Files
3
Types (Prompt / JSX / Local)
TS
TypeScript

🔵 Slash Commands 🟢 Skills & Plugins 🟡 Lazy Loading 🔴 Auth-Gated

What are commands? Commands are the primary interface between the user and Claude Code's capabilities. When a user types /commit or /config, the commands module resolves the input to a registered handler, validates permissions, assembles context, and dispatches execution—either locally or through the model.

💡 Analogy: Think of commands as the speed-dial buttons on an office phone. Each button is labeled, pre-configured for a specific destination, and handles all the dialing complexity behind a single press. Some buttons connect you internally (LocalCommand), some patch you through to an operator (PromptCommand), and some light up a fancy display panel (LocalJSXCommand).

Architecture

flowchart LR
  User[" User Input
/command args"] subgraph Registry["Command Registry"] findCmd["findCommand()"] Validate["validateAuth()"] end subgraph Types["Command Types"] Prompt["PromptCommand
Model-invoked skill"] JSX["LocalJSXCommand
React UI render"] Local["LocalCommand
Pure function"] end subgraph Categories["Command Categories"] Git["/commit /diff /review"] Session["/session /resume /branch"] Config["/config /theme /model"] MCP["/mcp /skills /plugin"] Dev["/doctor /status /stats"] end subgraph Execution["Execution Layer"] BuildPrompt["buildPrompt()"] ModelCall["Model Invocation"] RenderJSX["Ink Render"] RunLocal["Direct Execution"] end Result[" Result Display"] User --> findCmd findCmd --> Validate Validate --> Prompt Validate --> JSX Validate --> Local Prompt --> BuildPrompt BuildPrompt --> ModelCall JSX --> RenderJSX Local --> RunLocal ModelCall --> Result RenderJSX --> Result RunLocal --> Result Categories -.-> Types

Data Flow — /commit Command

sequenceDiagram
  participant U as User
  participant P as Parser
  participant R as Registry
  participant A as Auth Gate
  participant C as CommitCommand
  participant B as PromptBuilder
  participant M as Model (Claude)
  participant T as Tool Executor
  participant G as Git CLI
  participant D as Display

  U->>P: /commit --all "feat: add search"
  P->>R: findCommand("commit")
  R->>A: checkPermissions(user, "commit")
  A-->>R:  Authorized
  R->>C: execute(args, context)
  C->>B: buildPrompt(diffContext, message)
  B-->>C: SystemPrompt + UserPrompt
  C->>M: streamChat(prompt)
  M->>T: tool_call: bash("git add -A")
  T->>G: exec("git add -A && git commit -m ...")
  G-->>T: commit abc1234
  T-->>M: tool_result: success
  M-->>C: "Committed abc1234"
  C->>D: renderSuccess(commitHash)
  D-->>U:  Committed abc1234

🚶 Step-by-Step: /config Through the Full Lifecycle

Follow a single /config set theme dark invocation as it flows through the entire commands pipeline:

  1. Input Parsing — The CLI input loop detects the / prefix and extracts the raw token "config" plus arguments ["set", "theme", "dark"].
  2. Command Lookup — findCommand("config") searches the command registry. The registry is lazily populated on first access; if the config module hasn't been loaded yet, its dynamic import() fires now (~2 ms).
  3. Type Resolution — The registry identifies /config as a LocalJSXCommand, meaning it renders a React (Ink) component rather than invoking the model.
  4. Auth Gate — validateAuth() checks whether the current session has the required permissions. /config is available to all users—no auth gate blocks it.
  5. Argument Validation — The command's argument schema (sub-command set, key theme, value dark) is validated against its declared options. Invalid keys produce a helpful error message.
  6. State Read — The config command reads the current configuration from ~/.claude/settings.json and the project-level .claude/settings.json, merging them with precedence rules.
  7. Mutation — The set sub-command writes theme: "dark" into the appropriate config file. A file lock prevents concurrent writes from parallel sessions.
  8. JSX Render — The ConfigView Ink component receives the updated config object and renders a confirmation panel: a diff of old → new values, styled with the new theme colors.
  9. Side Effects — A configChanged event is emitted on the global event bus, triggering subscribers (the theme engine, prompt builder, and telemetry collector) to reload their cached values.
  10. Completion — Control returns to the input loop. The user sees ✅ theme → dark and the UI instantly re-renders with the dark palette. Total wall time: ~15 ms.

Key Components

CommandTypeCategoryDescription
/commitPromptCommandGitStages changes, generates commit message via model, runs git commit
/diffPromptCommandGitSummarizes working-tree or branch diffs with AI-generated explanations
/reviewPromptCommandGitCode-review of staged changes; produces inline comments and summary
/sessionLocalJSXCommandSessionLists, creates, and switches conversation sessions
/resumeLocalCommandSessionRestores a previously saved session from disk
/branchPromptCommandSessionCreates a Git branch and a new session simultaneously
/configLocalJSXCommandConfigGets/sets configuration values; renders current config as table
/themeLocalCommandConfigQuick toggle between light, dark, and system themes
/modelLocalJSXCommandConfigSwitches the active Claude model (Haiku, Sonnet, Opus)
/mcpLocalJSXCommandMCPManages MCP server connections and tool registrations
/skillsLocalJSXCommandMCPInstalls, lists, and removes agent skill definitions
/pluginLocalCommandMCPLoads and configures third-party plugin modules
/doctorLocalJSXCommandDevRuns diagnostic checks on environment, auth, and connectivity
/statusLocalCommandDevPrints current session info: model, tokens used, active tools
/statsLocalJSXCommandDevDisplays usage statistics, cost breakdown, and latency percentiles

Configuration & Feature Flags

SettingScopeDefaultDescription
commands.lazyLoadGlobaltrueDefer command module imports until first invocation
commands.authGatePer-commandfalseRequire authenticated session to execute
commands.maxHistorySession100Number of recent commands stored for /history
commands.timeoutPer-command30000Maximum execution time in milliseconds
commands.skillsEnabledGlobaltrueAllow skill-provided commands to register
commands.pluginDirProject.claude/plugins/Directory scanned for plugin command modules
commands.mcpAutoDiscoverGlobaltrueAuto-register commands from connected MCP servers
commands.featureFlags.reviewGlobaltrueEnable the experimental /review command

Sub-Modules

mcp/

MCP integration commands — server management, tool registration, and protocol bridge.

  • /mcp — connect/disconnect MCP servers
  • /skills — install & manage agent skills
  • /plugin — third-party plugin loader

config/

Configuration and preference commands — theme, model selection, and settings management.

  • /config — get/set configuration values
  • /theme — toggle UI theme
  • /model — switch active Claude model

session/

Session lifecycle commands — create, resume, branch, and manage conversation state.

  • /session — list & switch sessions
  • /resume — restore saved session
  • /branch — new branch + session

Directory Map

Source tree for commands/
commands/
├── index.ts                     ← Registry entry-point, re-exports findCommand()
├── types.ts                     ← PromptCommand / LocalJSXCommand / LocalCommand interfaces
├── registry.ts                  ← Lazy command map, register() / unregister()
├── parser.ts                    ← Slash-prefix parser, argument tokeniser
├── validator.ts                 ← Argument schema validation & error messages
├── auth-gate.ts                 ← Per-command permission checks
│
├── commit/                      ← /commit — AI-assisted git commit
│   ├── index.ts                 ← PromptCommand definition
│   ├── prompt.ts                ← System & user prompt templates
│   └── diff-context.ts          ← Staged-diff extraction helpers
├── diff/                        ← /diff — AI diff summaries
│   └── index.ts
├── review/                      ← /review — Code review command
│   ├── index.ts
│   └── review-prompt.ts
│
├── session/                     ← /session, /resume, /branch
│   ├── index.ts                 ← LocalJSXCommand for session list
│   ├── resume.ts                ← LocalCommand for session restore
│   └── branch.ts                ← PromptCommand for branch+session
├── config/                      ← /config, /theme, /model
│   ├── index.ts                 ← LocalJSXCommand for config UI
│   ├── theme.ts                 ← LocalCommand for theme toggle
│   └── model.ts                 ← LocalJSXCommand for model picker
│
├── mcp/                         ← /mcp, /skills, /plugin
│   ├── index.ts                 ← MCP server management
│   ├── skills.ts                ← Skill install/remove
│   └── plugin.ts                ← Plugin loader
├── doctor/                      ← /doctor — environment diagnostics
│   └── index.ts
├── status/                      ← /status — session info
│   └── index.ts
├── stats/                       ← /stats — usage statistics
│   └── index.ts
│
├── help/                        ← /help — command listing & docs
│   └── index.ts
├── clear/                       ← /clear — clear terminal
│   └── index.ts
├── history/                     ← /history — recent command log
│   └── index.ts
└── ... (70+ more command directories)

Code Example — Registering a Command

import { registerCommand, PromptCommand } from "../registry";

const commitCommand: PromptCommand = {
  name: "commit",
  description: "AI-assisted git commit with generated message",
  aliases: ["ci"],
  category: "git",
  authRequired: false,

  async buildPrompt(args, context) {
    const diff = await context.git.getStagedDiff();
    return {
      system: "You are a commit message generator...",
      user: `Write a commit message for:\n\n${diff}`,
    };
  },

  async onComplete(result, context) {
    await context.git.commit(result.message);
    context.display.success(`Committed ${result.hash}`);
  },
};

registerCommand(commitCommand);