/ types / command.ts
command.ts
  1  import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/index.mjs'
  2  import type { UUID } from 'crypto'
  3  import type { CanUseToolFn } from '../hooks/useCanUseTool.js'
  4  import type { CompactionResult } from '../services/compact/compact.js'
  5  import type { ScopedMcpServerConfig } from '../services/mcp/types.js'
  6  import type { ToolUseContext } from '../Tool.js'
  7  import type { EffortValue } from '../utils/effort.js'
  8  import type { IDEExtensionInstallationStatus, IdeType } from '../utils/ide.js'
  9  import type { SettingSource } from '../utils/settings/constants.js'
 10  import type { HooksSettings } from '../utils/settings/types.js'
 11  import type { ThemeName } from '../utils/theme.js'
 12  import type { LogOption } from './logs.js'
 13  import type { Message } from './message.js'
 14  import type { PluginManifest } from './plugin.js'
 15  
 16  export type LocalCommandResult =
 17    | { type: 'text'; value: string }
 18    | {
 19        type: 'compact'
 20        compactionResult: CompactionResult
 21        displayText?: string
 22      }
 23    | { type: 'skip' } // Skip messages
 24  
 25  export type PromptCommand = {
 26    type: 'prompt'
 27    progressMessage: string
 28    contentLength: number // Length of command content in characters (used for token estimation)
 29    argNames?: string[]
 30    allowedTools?: string[]
 31    model?: string
 32    source: SettingSource | 'builtin' | 'mcp' | 'plugin' | 'bundled'
 33    pluginInfo?: {
 34      pluginManifest: PluginManifest
 35      repository: string
 36    }
 37    disableNonInteractive?: boolean
 38    // Hooks to register when this skill is invoked
 39    hooks?: HooksSettings
 40    // Base directory for skill resources (used to set CLAUDE_PLUGIN_ROOT environment variable for skill hooks)
 41    skillRoot?: string
 42    // Execution context: 'inline' (default) or 'fork' (run as sub-agent)
 43    // 'inline' = skill content expands into the current conversation
 44    // 'fork' = skill runs in a sub-agent with separate context and token budget
 45    context?: 'inline' | 'fork'
 46    // Agent type to use when forked (e.g., 'Bash', 'general-purpose')
 47    // Only applicable when context is 'fork'
 48    agent?: string
 49    effort?: EffortValue
 50    // Glob patterns for file paths this skill applies to
 51    // When set, the skill is only visible after the model touches matching files
 52    paths?: string[]
 53    getPromptForCommand(
 54      args: string,
 55      context: ToolUseContext,
 56    ): Promise<ContentBlockParam[]>
 57  }
 58  
 59  /**
 60   * The call signature for a local command implementation.
 61   */
 62  export type LocalCommandCall = (
 63    args: string,
 64    context: LocalJSXCommandContext,
 65  ) => Promise<LocalCommandResult>
 66  
 67  /**
 68   * Module shape returned by load() for lazy-loaded local commands.
 69   */
 70  export type LocalCommandModule = {
 71    call: LocalCommandCall
 72  }
 73  
 74  type LocalCommand = {
 75    type: 'local'
 76    supportsNonInteractive: boolean
 77    load: () => Promise<LocalCommandModule>
 78  }
 79  
 80  export type LocalJSXCommandContext = ToolUseContext & {
 81    canUseTool?: CanUseToolFn
 82    setMessages: (updater: (prev: Message[]) => Message[]) => void
 83    options: {
 84      dynamicMcpConfig?: Record<string, ScopedMcpServerConfig>
 85      ideInstallationStatus: IDEExtensionInstallationStatus | null
 86      theme: ThemeName
 87    }
 88    onChangeAPIKey: () => void
 89    onChangeDynamicMcpConfig?: (
 90      config: Record<string, ScopedMcpServerConfig>,
 91    ) => void
 92    onInstallIDEExtension?: (ide: IdeType) => void
 93    resume?: (
 94      sessionId: UUID,
 95      log: LogOption,
 96      entrypoint: ResumeEntrypoint,
 97    ) => Promise<void>
 98  }
 99  
100  export type ResumeEntrypoint =
101    | 'cli_flag'
102    | 'slash_command_picker'
103    | 'slash_command_session_id'
104    | 'slash_command_title'
105    | 'fork'
106  
107  export type CommandResultDisplay = 'skip' | 'system' | 'user'
108  
109  /**
110   * Callback when a command completes.
111   * @param result - Optional user-visible message to display
112   * @param options - Optional configuration for command completion
113   * @param options.display - How to display the result: 'skip' | 'system' | 'user' (default)
114   * @param options.shouldQuery - If true, send messages to the model after command completes
115   * @param options.metaMessages - Additional messages to insert as isMeta (model-visible but hidden)
116   */
117  export type LocalJSXCommandOnDone = (
118    result?: string,
119    options?: {
120      display?: CommandResultDisplay
121      shouldQuery?: boolean
122      metaMessages?: string[]
123      nextInput?: string
124      submitNextInput?: boolean
125    },
126  ) => void
127  
128  /**
129   * The call signature for a local JSX command implementation.
130   */
131  export type LocalJSXCommandCall = (
132    onDone: LocalJSXCommandOnDone,
133    context: ToolUseContext & LocalJSXCommandContext,
134    args: string,
135  ) => Promise<React.ReactNode>
136  
137  /**
138   * Module shape returned by load() for lazy-loaded commands.
139   */
140  export type LocalJSXCommandModule = {
141    call: LocalJSXCommandCall
142  }
143  
144  type LocalJSXCommand = {
145    type: 'local-jsx'
146    /**
147     * Lazy-load the command implementation.
148     * Returns a module with a call() function.
149     * This defers loading heavy dependencies until the command is invoked.
150     */
151    load: () => Promise<LocalJSXCommandModule>
152  }
153  
154  /**
155   * Declares which auth/provider environments a command is available in.
156   *
157   * This is separate from `isEnabled()`:
158   *   - `availability` = who can use this (auth/provider requirement, static)
159   *   - `isEnabled()`  = is this turned on right now (GrowthBook, platform, env vars)
160   *
161   * Commands without `availability` are available everywhere.
162   * Commands with `availability` are only shown if the user matches at least one
163   * of the listed auth types. See meetsAvailabilityRequirement() in commands.ts.
164   *
165   * Example: `availability: ['claude-ai', 'console']` shows the command to
166   * claude.ai subscribers and direct Console API key users (api.anthropic.com),
167   * but hides it from Bedrock/Vertex/Foundry users and custom base URL users.
168   */
169  export type CommandAvailability =
170    // claude.ai OAuth subscriber (Pro/Max/Team/Enterprise via claude.ai)
171    | 'claude-ai'
172    // Console API key user (direct api.anthropic.com, not via claude.ai OAuth)
173    | 'console'
174  
175  export type CommandBase = {
176    availability?: CommandAvailability[]
177    description: string
178    hasUserSpecifiedDescription?: boolean
179    /** Defaults to true. Only set when the command has conditional enablement (feature flags, env checks, etc). */
180    isEnabled?: () => boolean
181    /** Defaults to false. Only set when the command should be hidden from typeahead/help. */
182    isHidden?: boolean
183    name: string
184    aliases?: string[]
185    isMcp?: boolean
186    argumentHint?: string // Hint text for command arguments (displayed in gray after command)
187    whenToUse?: string // From the "Skill" spec. Detailed usage scenarios for when to use this command
188    version?: string // Version of the command/skill
189    disableModelInvocation?: boolean // Whether to disable this command from being invoked by models
190    userInvocable?: boolean // Whether users can invoke this skill by typing /skill-name
191    loadedFrom?:
192      | 'commands_DEPRECATED'
193      | 'skills'
194      | 'plugin'
195      | 'managed'
196      | 'bundled'
197      | 'mcp' // Where the command was loaded from
198    kind?: 'workflow' // Distinguishes workflow-backed commands (badged in autocomplete)
199    immediate?: boolean // If true, command executes immediately without waiting for a stop point (bypasses queue)
200    isSensitive?: boolean // If true, args are redacted from the conversation history
201    /** Defaults to `name`. Only override when the displayed name differs (e.g. plugin prefix stripping). */
202    userFacingName?: () => string
203  }
204  
205  export type Command = CommandBase &
206    (PromptCommand | LocalCommand | LocalJSXCommand)
207  
208  /** Resolves the user-visible name, falling back to `cmd.name` when not overridden. */
209  export function getCommandName(cmd: CommandBase): string {
210    return cmd.userFacingName?.() ?? cmd.name
211  }
212  
213  /** Resolves whether the command is enabled, defaulting to true. */
214  export function isCommandEnabled(cmd: CommandBase): boolean {
215    return cmd.isEnabled?.() ?? true
216  }