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