Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
claude-code commands.ts
25 kB · 754 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reorderedimport addDir from './commands/add-dir/index.js'import autofixPr from './commands/autofix-pr/index.js'import backfillSessions from './commands/backfill-sessions/index.js'import btw from './commands/btw/index.js'import goodClaude from './commands/good-claude/index.js'import issue from './commands/issue/index.js'import feedback from './commands/feedback/index.js'import clear from './commands/clear/index.js'import color from './commands/color/index.js'import commit from './commands/commit.js'import copy from './commands/copy/index.js'import desktop from './commands/desktop/index.js'import commitPushPr from './commands/commit-push-pr.js'import compact from './commands/compact/index.js'import config from './commands/config/index.js'import { context, contextNonInteractive } from './commands/context/index.js'import cost from './commands/cost/index.js'import diff from './commands/diff/index.js'import ctx_viz from './commands/ctx_viz/index.js'import doctor from './commands/doctor/index.js'import memory from './commands/memory/index.js'import help from './commands/help/index.js'import ide from './commands/ide/index.js'import init from './commands/init.js'import initVerifiers from './commands/init-verifiers.js'import keybindings from './commands/keybindings/index.js'import login from './commands/login/index.js'import logout from './commands/logout/index.js'import installGitHubApp from './commands/install-github-app/index.js'import installSlackApp from './commands/install-slack-app/index.js'import breakCache from './commands/break-cache/index.js'import mcp from './commands/mcp/index.js'import mobile from './commands/mobile/index.js'import onboarding from './commands/onboarding/index.js'import pr_comments from './commands/pr_comments/index.js'import releaseNotes from './commands/release-notes/index.js'import rename from './commands/rename/index.js'import resume from './commands/resume/index.js'import review, { ultrareview } from './commands/review.js'import session from './commands/session/index.js'import share from './commands/share/index.js'import skills from './commands/skills/index.js'import status from './commands/status/index.js'import tasks from './commands/tasks/index.js'import teleport from './commands/teleport/index.js'/* eslint-disable @typescript-eslint/no-require-imports */const agentsPlatform = process.env.USER_TYPE === 'ant' ? require('./commands/agents-platform/index.js').default : null/* eslint-enable @typescript-eslint/no-require-imports */import securityReview from './commands/security-review.js'import bughunter from './commands/bughunter/index.js'import terminalSetup from './commands/terminalSetup/index.js'import usage from './commands/usage/index.js'import theme from './commands/theme/index.js'import vim from './commands/vim/index.js'import { feature } from 'bun:bundle'// Dead code elimination: conditional imports/* eslint-disable @typescript-eslint/no-require-imports */const proactive = feature('PROACTIVE') || feature('KAIROS') ? require('./commands/proactive.js').default : nullconst briefCommand = feature('KAIROS') || feature('KAIROS_BRIEF') ? require('./commands/brief.js').default : nullconst assistantCommand = feature('KAIROS') ? require('./commands/assistant/index.js').default : nullconst bridge = feature('BRIDGE_MODE') ? require('./commands/bridge/index.js').default : nullconst remoteControlServerCommand = feature('DAEMON') && feature('BRIDGE_MODE') ? require('./commands/remoteControlServer/index.js').default : nullconst voiceCommand = feature('VOICE_MODE') ? require('./commands/voice/index.js').default : nullconst forceSnip = feature('HISTORY_SNIP') ? require('./commands/force-snip.js').default : nullconst workflowsCmd = feature('WORKFLOW_SCRIPTS') ? ( require('./commands/workflows/index.js') as typeof import('./commands/workflows/index.js') ).default : nullconst webCmd = feature('CCR_REMOTE_SETUP') ? ( require('./commands/remote-setup/index.js') as typeof import('./commands/remote-setup/index.js') ).default : nullconst clearSkillIndexCache = feature('EXPERIMENTAL_SKILL_SEARCH') ? ( require('./services/skillSearch/localSearch.js') as typeof import('./services/skillSearch/localSearch.js') ).clearSkillIndexCache : nullconst subscribePr = feature('KAIROS_GITHUB_WEBHOOKS') ? require('./commands/subscribe-pr.js').default : nullconst ultraplan = feature('ULTRAPLAN') ? require('./commands/ultraplan.js').default : nullconst torch = feature('TORCH') ? require('./commands/torch.js').default : nullconst peersCmd = feature('UDS_INBOX') ? ( require('./commands/peers/index.js') as typeof import('./commands/peers/index.js') ).default : nullconst forkCmd = feature('FORK_SUBAGENT') ? ( require('./commands/fork/index.js') as typeof import('./commands/fork/index.js') ).default : nullconst buddy = feature('BUDDY') ? ( require('./commands/buddy/index.js') as typeof import('./commands/buddy/index.js') ).default : null/* eslint-enable @typescript-eslint/no-require-imports */import thinkback from './commands/thinkback/index.js'import thinkbackPlay from './commands/thinkback-play/index.js'import permissions from './commands/permissions/index.js'import plan from './commands/plan/index.js'import fast from './commands/fast/index.js'import passes from './commands/passes/index.js'import privacySettings from './commands/privacy-settings/index.js'import hooks from './commands/hooks/index.js'import files from './commands/files/index.js'import branch from './commands/branch/index.js'import agents from './commands/agents/index.js'import plugin from './commands/plugin/index.js'import reloadPlugins from './commands/reload-plugins/index.js'import rewind from './commands/rewind/index.js'import heapDump from './commands/heapdump/index.js'import mockLimits from './commands/mock-limits/index.js'import bridgeKick from './commands/bridge-kick.js'import version from './commands/version.js'import summary from './commands/summary/index.js'import { resetLimits, resetLimitsNonInteractive,} from './commands/reset-limits/index.js'import antTrace from './commands/ant-trace/index.js'import perfIssue from './commands/perf-issue/index.js'import sandboxToggle from './commands/sandbox-toggle/index.js'import chrome from './commands/chrome/index.js'import stickers from './commands/stickers/index.js'import advisor from './commands/advisor.js'import { logError } from './utils/log.js'import { toError } from './utils/errors.js'import { logForDebugging } from './utils/debug.js'import { getSkillDirCommands, clearSkillCaches, getDynamicSkills,} from './skills/loadSkillsDir.js'import { getBundledSkills } from './skills/bundledSkills.js'import { getBuiltinPluginSkillCommands } from './plugins/builtinPlugins.js'import { getPluginCommands, clearPluginCommandCache, getPluginSkills, clearPluginSkillsCache,} from './utils/plugins/loadPluginCommands.js'import memoize from 'lodash-es/memoize.js'import { isUsing3PServices, isClaudeAISubscriber } from './utils/auth.js'import { isFirstPartyAnthropicBaseUrl } from './utils/model/providers.js'import env from './commands/env/index.js'import exit from './commands/exit/index.js'import exportCommand from './commands/export/index.js'import model from './commands/model/index.js'import tag from './commands/tag/index.js'import outputStyle from './commands/output-style/index.js'import remoteEnv from './commands/remote-env/index.js'import upgrade from './commands/upgrade/index.js'import { extraUsage, extraUsageNonInteractive,} from './commands/extra-usage/index.js'import rateLimitOptions from './commands/rate-limit-options/index.js'import statusline from './commands/statusline.js'import effort from './commands/effort/index.js'import stats from './commands/stats/index.js'// insights.ts is 113KB (3200 lines, includes diffLines/html rendering). Lazy// shim defers the heavy module until /insights is actually invoked.const usageReport: Command = { type: 'prompt', name: 'insights', description: 'Generate a report analyzing your Claude Code sessions', contentLength: 0, progressMessage: 'analyzing your sessions', source: 'builtin', async getPromptForCommand(args, context) { const real = (await import('./commands/insights.js')).default if (real.type !== 'prompt') throw new Error('unreachable') return real.getPromptForCommand(args, context) },}import oauthRefresh from './commands/oauth-refresh/index.js'import debugToolCall from './commands/debug-tool-call/index.js'import { getSettingSourceName } from './utils/settings/constants.js'import { type Command, getCommandName, isCommandEnabled,} from './types/command.js'
// Re-export types from the centralized locationexport type { Command, CommandBase, CommandResultDisplay, LocalCommandResult, LocalJSXCommandContext, PromptCommand, ResumeEntrypoint,} from './types/command.js'export { getCommandName, isCommandEnabled } from './types/command.js'
// Commands that get eliminated from the external buildexport const INTERNAL_ONLY_COMMANDS = [ backfillSessions, breakCache, bughunter, commit, commitPushPr, ctx_viz, goodClaude, issue, initVerifiers, ...(forceSnip ? [forceSnip] : []), mockLimits, bridgeKick, version, ...(ultraplan ? [ultraplan] : []), ...(subscribePr ? [subscribePr] : []), resetLimits, resetLimitsNonInteractive, onboarding, share, summary, teleport, antTrace, perfIssue, env, oauthRefresh, debugToolCall, agentsPlatform, autofixPr,].filter(Boolean)
// Declared as a function so that we don't run this until getCommands is called,// since underlying functions read from config, which can't be read at module initialization timeconst COMMANDS = memoize((): Command[] => [ addDir, advisor, agents, branch, btw, chrome, clear, color, compact, config, copy, desktop, context, contextNonInteractive, cost, diff, doctor, effort, exit, fast, files, heapDump, help, ide, init, keybindings, installGitHubApp, installSlackApp, mcp, memory, mobile, model, outputStyle, remoteEnv, plugin, pr_comments, releaseNotes, reloadPlugins, rename, resume, session, skills, stats, status, statusline, stickers, tag, theme, feedback, review, ultrareview, rewind, securityReview, terminalSetup, upgrade, extraUsage, extraUsageNonInteractive, rateLimitOptions, usage, usageReport, vim, ...(webCmd ? [webCmd] : []), ...(forkCmd ? [forkCmd] : []), ...(buddy ? [buddy] : []), ...(proactive ? [proactive] : []), ...(briefCommand ? [briefCommand] : []), ...(assistantCommand ? [assistantCommand] : []), ...(bridge ? [bridge] : []), ...(remoteControlServerCommand ? [remoteControlServerCommand] : []), ...(voiceCommand ? [voiceCommand] : []), thinkback, thinkbackPlay, permissions, plan, privacySettings, hooks, exportCommand, sandboxToggle, ...(!isUsing3PServices() ? [logout, login()] : []), passes, ...(peersCmd ? [peersCmd] : []), tasks, ...(workflowsCmd ? [workflowsCmd] : []), ...(torch ? [torch] : []), ...(process.env.USER_TYPE === 'ant' && !process.env.IS_DEMO ? INTERNAL_ONLY_COMMANDS : []),])
export const builtInCommandNames = memoize( (): Set<string> => new Set(COMMANDS().flatMap(_ => [_.name, ...(_.aliases ?? [])])),)
async function getSkills(cwd: string): Promise<{ skillDirCommands: Command[] pluginSkills: Command[] bundledSkills: Command[] builtinPluginSkills: Command[]}> { try { const [skillDirCommands, pluginSkills] = await Promise.all([ getSkillDirCommands(cwd).catch(err => { logError(toError(err)) logForDebugging( 'Skill directory commands failed to load, continuing without them', ) return [] }), getPluginSkills().catch(err => { logError(toError(err)) logForDebugging('Plugin skills failed to load, continuing without them') return [] }), ]) // Bundled skills are registered synchronously at startup const bundledSkills = getBundledSkills() // Built-in plugin skills come from enabled built-in plugins const builtinPluginSkills = getBuiltinPluginSkillCommands() logForDebugging( `getSkills returning: ${skillDirCommands.length} skill dir commands, ${pluginSkills.length} plugin skills, ${bundledSkills.length} bundled skills, ${builtinPluginSkills.length} builtin plugin skills`, ) return { skillDirCommands, pluginSkills, bundledSkills, builtinPluginSkills, } } catch (err) { // This should never happen since we catch at the Promise level, but defensive logError(toError(err)) logForDebugging('Unexpected error in getSkills, returning empty') return { skillDirCommands: [], pluginSkills: [], bundledSkills: [], builtinPluginSkills: [], } }}
/* eslint-disable @typescript-eslint/no-require-imports */const getWorkflowCommands = feature('WORKFLOW_SCRIPTS') ? ( require('./tools/WorkflowTool/createWorkflowCommand.js') as typeof import('./tools/WorkflowTool/createWorkflowCommand.js') ).getWorkflowCommands : null/* eslint-enable @typescript-eslint/no-require-imports */
/** * Filters commands by their declared `availability` (auth/provider requirement). * Commands without `availability` are treated as universal. * This runs before `isEnabled()` so that provider-gated commands are hidden * regardless of feature-flag state. * * Not memoized — auth state can change mid-session (e.g. after /login), * so this must be re-evaluated on every getCommands() call. */export function meetsAvailabilityRequirement(cmd: Command): boolean { if (!cmd.availability) return true for (const a of cmd.availability) { switch (a) { case 'claude-ai': if (isClaudeAISubscriber()) return true break case 'console': // Console API key user = direct 1P API customer (not 3P, not claude.ai). // Excludes 3P (Bedrock/Vertex/Foundry) who don't set ANTHROPIC_BASE_URL // and gateway users who proxy through a custom base URL. if ( !isClaudeAISubscriber() && !isUsing3PServices() && isFirstPartyAnthropicBaseUrl() ) return true break default: { const _exhaustive: never = a void _exhaustive break } } } return false}
/** * Loads all command sources (skills, plugins, workflows). Memoized by cwd * because loading is expensive (disk I/O, dynamic imports). */const loadAllCommands = memoize(async (cwd: string): Promise<Command[]> => { const [ { skillDirCommands, pluginSkills, bundledSkills, builtinPluginSkills }, pluginCommands, workflowCommands, ] = await Promise.all([ getSkills(cwd), getPluginCommands(), getWorkflowCommands ? getWorkflowCommands(cwd) : Promise.resolve([]), ])
return [ ...bundledSkills, ...builtinPluginSkills, ...skillDirCommands, ...workflowCommands, ...pluginCommands, ...pluginSkills, ...COMMANDS(), ]})
/** * Returns commands available to the current user. The expensive loading is * memoized, but availability and isEnabled checks run fresh every call so * auth changes (e.g. /login) take effect immediately. */export async function getCommands(cwd: string): Promise<Command[]> { const allCommands = await loadAllCommands(cwd)
// Get dynamic skills discovered during file operations const dynamicSkills = getDynamicSkills()
// Build base commands without dynamic skills const baseCommands = allCommands.filter( _ => meetsAvailabilityRequirement(_) && isCommandEnabled(_), )
if (dynamicSkills.length === 0) { return baseCommands }
// Dedupe dynamic skills - only add if not already present const baseCommandNames = new Set(baseCommands.map(c => c.name)) const uniqueDynamicSkills = dynamicSkills.filter( s => !baseCommandNames.has(s.name) && meetsAvailabilityRequirement(s) && isCommandEnabled(s), )
if (uniqueDynamicSkills.length === 0) { return baseCommands }
// Insert dynamic skills after plugin skills but before built-in commands const builtInNames = new Set(COMMANDS().map(c => c.name)) const insertIndex = baseCommands.findIndex(c => builtInNames.has(c.name))
if (insertIndex === -1) { return [...baseCommands, ...uniqueDynamicSkills] }
return [ ...baseCommands.slice(0, insertIndex), ...uniqueDynamicSkills, ...baseCommands.slice(insertIndex), ]}
/** * Clears only the memoization caches for commands, WITHOUT clearing skill caches. * Use this when dynamic skills are added to invalidate cached command lists. */export function clearCommandMemoizationCaches(): void { loadAllCommands.cache?.clear?.() getSkillToolCommands.cache?.clear?.() getSlashCommandToolSkills.cache?.clear?.() // getSkillIndex in skillSearch/localSearch.ts is a separate memoization layer // built ON TOP of getSkillToolCommands/getCommands. Clearing only the inner // caches is a no-op for the outer — lodash memoize returns the cached result // without ever reaching the cleared inners. Must clear it explicitly. clearSkillIndexCache?.()}
export function clearCommandsCache(): void { clearCommandMemoizationCaches() clearPluginCommandCache() clearPluginSkillsCache() clearSkillCaches()}
/** * Filter AppState.mcp.commands to MCP-provided skills (prompt-type, * model-invocable, loaded from MCP). These live outside getCommands() so * callers that need MCP skills in their skill index thread them through * separately. */export function getMcpSkillCommands( mcpCommands: readonly Command[],): readonly Command[] { if (feature('MCP_SKILLS')) { return mcpCommands.filter( cmd => cmd.type === 'prompt' && cmd.loadedFrom === 'mcp' && !cmd.disableModelInvocation, ) } return []}
// SkillTool shows ALL prompt-based commands that the model can invoke// This includes both skills (from /skills/) and commands (from /commands/)export const getSkillToolCommands = memoize( async (cwd: string): Promise<Command[]> => { const allCommands = await getCommands(cwd) return allCommands.filter( cmd => cmd.type === 'prompt' && !cmd.disableModelInvocation && cmd.source !== 'builtin' && // Always include skills from /skills/ dirs, bundled skills, and legacy /commands/ entries // (they all get an auto-derived description from the first line if frontmatter is missing). // Plugin/MCP commands still require an explicit description to appear in the listing. (cmd.loadedFrom === 'bundled' || cmd.loadedFrom === 'skills' || cmd.loadedFrom === 'commands_DEPRECATED' || cmd.hasUserSpecifiedDescription || cmd.whenToUse), ) },)
// Filters commands to include only skills. Skills are commands that provide// specialized capabilities for the model to use. They are identified by// loadedFrom being 'skills', 'plugin', or 'bundled', or having disableModelInvocation set.export const getSlashCommandToolSkills = memoize( async (cwd: string): Promise<Command[]> => { try { const allCommands = await getCommands(cwd) return allCommands.filter( cmd => cmd.type === 'prompt' && cmd.source !== 'builtin' && (cmd.hasUserSpecifiedDescription || cmd.whenToUse) && (cmd.loadedFrom === 'skills' || cmd.loadedFrom === 'plugin' || cmd.loadedFrom === 'bundled' || cmd.disableModelInvocation), ) } catch (error) { logError(toError(error)) // Return empty array rather than throwing - skills are non-critical // This prevents skill loading failures from breaking the entire system logForDebugging('Returning empty skills array due to load failure') return [] } },)
/** * Commands that are safe to use in remote mode (--remote). * These only affect local TUI state and don't depend on local filesystem, * git, shell, IDE, MCP, or other local execution context. * * Used in two places: * 1. Pre-filtering commands in main.tsx before REPL renders (prevents race with CCR init) * 2. Preserving local-only commands in REPL's handleRemoteInit after CCR filters */export const REMOTE_SAFE_COMMANDS: Set<Command> = new Set([ session, // Shows QR code / URL for remote session exit, // Exit the TUI clear, // Clear screen help, // Show help theme, // Change terminal theme color, // Change agent color vim, // Toggle vim mode cost, // Show session cost (local cost tracking) usage, // Show usage info copy, // Copy last message btw, // Quick note feedback, // Send feedback plan, // Plan mode toggle keybindings, // Keybinding management statusline, // Status line toggle stickers, // Stickers mobile, // Mobile QR code])
/** * Builtin commands of type 'local' that ARE safe to execute when received * over the Remote Control bridge. These produce text output that streams * back to the mobile/web client and have no terminal-only side effects. * * 'local-jsx' commands are blocked by type (they render Ink UI) and * 'prompt' commands are allowed by type (they expand to text sent to the * model) — this set only gates 'local' commands. * * When adding a new 'local' command that should work from mobile, add it * here. Default is blocked. */export const BRIDGE_SAFE_COMMANDS: Set<Command> = new Set( [ compact, // Shrink context — useful mid-session from a phone clear, // Wipe transcript cost, // Show session cost summary, // Summarize conversation releaseNotes, // Show changelog files, // List tracked files ].filter((c): c is Command => c !== null),)
/** * Whether a slash command is safe to execute when its input arrived over the * Remote Control bridge (mobile/web client). * * PR #19134 blanket-blocked all slash commands from bridge inbound because * `/model` from iOS was popping the local Ink picker. This predicate relaxes * that with an explicit allowlist: 'prompt' commands (skills) expand to text * and are safe by construction; 'local' commands need an explicit opt-in via * BRIDGE_SAFE_COMMANDS; 'local-jsx' commands render Ink UI and stay blocked. */export function isBridgeSafeCommand(cmd: Command): boolean { if (cmd.type === 'local-jsx') return false if (cmd.type === 'prompt') return true return BRIDGE_SAFE_COMMANDS.has(cmd)}
/** * Filter commands to only include those safe for remote mode. * Used to pre-filter commands when rendering the REPL in --remote mode, * preventing local-only commands from being briefly available before * the CCR init message arrives. */export function filterCommandsForRemoteMode(commands: Command[]): Command[] { return commands.filter(cmd => REMOTE_SAFE_COMMANDS.has(cmd))}
export function findCommand( commandName: string, commands: Command[],): Command | undefined { return commands.find( _ => _.name === commandName || getCommandName(_) === commandName || _.aliases?.includes(commandName), )}
export function hasCommand(commandName: string, commands: Command[]): boolean { return findCommand(commandName, commands) !== undefined}
export function getCommand(commandName: string, commands: Command[]): Command { const command = findCommand(commandName, commands) if (!command) { throw ReferenceError( `Command ${commandName} not found. Available commands: ${commands .map(_ => { const name = getCommandName(_) return _.aliases ? `${name} (aliases: ${_.aliases.join(', ')})` : name }) .sort((a, b) => a.localeCompare(b)) .join(', ')}`, ) }
return command}
/** * Formats a command's description with its source annotation for user-facing UI. * Use this in typeahead, help screens, and other places where users need to see * where a command comes from. * * For model-facing prompts (like SkillTool), use cmd.description directly. */export function formatDescriptionWithSource(cmd: Command): string { if (cmd.type !== 'prompt') { return cmd.description }
if (cmd.kind === 'workflow') { return `${cmd.description} (workflow)` }
if (cmd.source === 'plugin') { const pluginName = cmd.pluginInfo?.pluginManifest.name if (pluginName) { return `(${pluginName}) ${cmd.description}` } return `${cmd.description} (plugin)` }
if (cmd.source === 'builtin' || cmd.source === 'mcp') { return cmd.description }
if (cmd.source === 'bundled') { return `${cmd.description} (bundled)` }
return `${cmd.description} (${getSettingSourceName(cmd.source)})`}