AI Assistant

Claude Code's slash command system provides an extensible registry of user-invokable commands. Commands are typed, lazily loaded, and sourced from built-in definitions, skills directories, plugins, workflows, and MCP servers.

Command Type Definition

Every command shares a CommandBase shape and is discriminated by its type field:

type CommandBase = {
  availability?: CommandAvailability[]
  description: string
  name: string
  aliases?: string[]
  argumentHint?: string
  isEnabled?: () => boolean
  immediate?: boolean
}

type Command = CommandBase & (
  | PromptCommand        // type: 'prompt'
  | LocalCommand         // type: 'local'
  | LocalJSXCommand      // type: 'local-jsx'
)

Command Types

promptPromptCommand
Generates content that is sent to the model as a user message. Includes a getPromptForCommand() function that returns ContentBlockParam[]. Used by skills and MCP-sourced commands.
localLocalCommand
Executes locally and returns a LocalCommandResult (text, compaction result, or skip). Does not render JSX.
local-jsxLocalJSXCommand
Executes locally and returns a React node for terminal rendering. The most common type for interactive commands like /help, /model, /config.

PromptCommand Fields

progressMessagestring
Text shown in the spinner while the command runs.
contentLengthnumber
Character length of command content, used for token estimation.
sourcestring
Origin of the command: 'builtin' | 'mcp' | 'plugin' | 'bundled' or a SettingSource.
context'inline' | 'fork'
Execution context. 'inline' expands into the current conversation; 'fork' runs in a sub-agent with separate context.
hooksHooksSettings
Hooks to register when the skill is invoked.
pathsstring[]
Glob patterns for files this skill applies to. When set, the skill is only visible after the model touches matching files.
effortEffortValue
Effort level override for this command.

Availability

Commands can declare which auth/provider environments they are available in:

type CommandAvailability =
  | 'claude-ai'   // Claude.ai OAuth subscriber
  | 'console'     // Console API key user (direct api.anthropic.com)

Commands without availability are universal. The meetsAvailabilityRequirement() function checks this at runtime, and it is intentionally not memoized because auth state can change mid-session (e.g., after /login).

Lazy Loading

Commands use lazy loading to defer heavy imports until invocation. Both local and local-jsx commands define a load() function:

// help command definition
const help = {
  type: 'local-jsx',
  name: 'help',
  description: 'Show help and available commands',
  load: () => import('./help.js'),
} satisfies Command

// model command with dynamic description
export default {
  type: 'local-jsx',
  name: 'model',
  get description() {
    return `Set the AI model (currently ${renderModelName(getMainLoopModel())})`
  },
  argumentHint: '[model]',
  load: () => import('./model.js'),
} satisfies Command

The load() pattern uses dynamic import() so the bundler can code-split command implementations. This keeps startup time low: the command registry loads instantly, but the implementation code for any given command only loads when that command is first invoked.

Command Registry

The getCommands() function in src/commands.ts is the main entry point for retrieving available commands. It aggregates commands from multiple sources.

Source Priority

Commands are assembled from these sources (in order):

  1. Bundled skills: shipped with Claude Code, registered synchronously
  2. Built-in plugin skills: from enabled built-in plugins
  3. Skill directory commands: from .claude/skills/ directories
  4. Workflow commands: from workflow scripts (feature-gated)
  5. Plugin commands: from installed plugins
  6. Plugin skills: skill commands from plugins
  7. Built-in commands: the COMMANDS() array of core slash commands

Loading and Filtering

export async function getCommands(cwd: string): Promise<Command[]> {
  const allCommands = await loadAllCommands(cwd)  // memoized by cwd
  const dynamicSkills = getDynamicSkills()         // discovered during file ops

  const baseCommands = allCommands.filter(
    cmd => meetsAvailabilityRequirement(cmd) && isCommandEnabled(cmd),
  )

  // Dedupe dynamic skills against base commands
  const uniqueDynamicSkills = dynamicSkills.filter(
    s => !baseCommandNames.has(s.name) && meetsAvailabilityRequirement(s)
  )

  return [...baseCommands, ...uniqueDynamicSkills]
}

The heavy loading (loadAllCommands) is memoized by cwd, but availability and isEnabled checks run fresh every call so auth/feature-flag changes take effect immediately.

Internal-Only Commands

A separate INTERNAL_ONLY_COMMANDS array contains commands restricted to Anthropic employees (USER_TYPE === 'ant'), including debugging tools, internal workflows, and development utilities.

Command Lifecycle

When the user types /, the command registry is queried. Commands are filtered by availability, enablement, and (for dynamic skills) file-path matching. The result is displayed as a completion list.

The command name and arguments are parsed from the user input. Aliases are resolved to canonical names. The argumentHint field provides placeholder text for argument completion.

For local and local-jsx commands, load() is called to import the implementation module. This is the point where heavy dependencies are pulled in.

  • prompt: getPromptForCommand(args, context) generates content blocks, which are sent to the model as a user message
  • local: call(args, context) returns a LocalCommandResult
  • local-jsx: call(onDone, context, args) returns a React node and calls onDone when complete

The onDone callback (for local-jsx) or return value (for local/prompt) determines what happens next:

  • display: 'skip': no visible output
  • display: 'system': shown as a system message
  • display: 'user': shown as a user message
  • shouldQuery: true: triggers a model query after the command completes
  • metaMessages: additional model-visible but UI-hidden messages

Feature-Gated Commands

Several commands are conditionally imported based on build-time feature flags:

const voiceCommand = feature('VOICE_MODE')
  ? require('./commands/voice/index.js').default
  : null

const bridge = feature('BRIDGE_MODE')
  ? require('./commands/bridge/index.js').default
  : null

const workflowsCmd = feature('WORKFLOW_SCRIPTS')
  ? require('./commands/workflows/index.js').default
  : null

This pattern uses require() inside feature gates so the bundler can eliminate unreachable command code entirely from production builds that do not enable those features.