커맨드 시스템은 사용자가 `/commit`, `/mcp`, `/review` 처럼 슬래시(`/`)로 시작하는 명령어를 입력할 때 이를 해석하고 실행하는 계층이다. 단순한 UI 단축키가 아니라 Claude Code의 모든 사용자 대면 기능을 조율하는 중앙 디스패처 역할을 수행한다.
**역할 요약:**
- **등록**: 내장(built-in) 커맨드, 스킬(skill) 디렉터리, 플러그인, MCP 서버, 워크플로 스크립트를 단일 커맨드 풀로 통합
- **필터링**: 인증 공급자 가용성(availability), feature flag(`bun:bundle`의 `feature()`), 환경 변수 조건을 거쳐 현재 세션에서 사용 가능한 커맨드 목록을 결정
- **실행**: 커맨드 타입(`prompt` / `local` / `local-jsx`)에 따라 서로 다른 실행 경로로 위임
- **캐시 관리**: `lodash-es/memoize`를 사용하여 디스크 I/O가 수반되는 동적 로딩을 메모이제이션
**핵심 파일:**
| 파일 | 역할 |
|------|------|
| `src/commands.ts` | 레지스트리, 필터링, 공개 API 전체 |
| `src/types/command.ts` | `Command` 타입 계층 정의 |
| `src/commands/*/index.ts` | 개별 커맨드 구현체 (100개 이상) |
context?: 'inline' | 'fork' // 'fork': 별도 서브에이전트로 실행
agent?: string // fork 컨텍스트에서 사용할 에이전트 타입
paths?: string[] // 이 glob 패턴에 해당하는 파일 접근 후에만 표시
hooks?: HooksSettings // 스킬 호출 시 등록할 훅
}
```
#### LocalCommand 추가 필드
```typescript
type LocalCommand = {
supportsNonInteractive: boolean // CI/비대화형 모드 지원 여부
load: () => Promise<LocalCommandModule>
}
```
#### LocalJSXCommand 추가 필드
```typescript
type LocalJSXCommand = {
load: () => Promise<LocalJSXCommandModule>
// load()는 항상 지연 임포트(dynamic import)로 구현
// → 무거운 UI 의존성을 커맨드 호출 시점까지 번들에서 분리
}
```
---
## 4. 커맨드 레지스트리
### 4.1 직접 import (정적 로딩)
`commands.ts` 상단에서 약 55개 커맨드를 ES 모듈 정적 임포트로 등록한다. 이 커맨드들은 번들 시점에 포함되며 런타임 조건 없이 항상 사용 가능하다.
```typescript
import commit from './commands/commit.js'
import compact from './commands/compact/index.js'
import mcp from './commands/mcp/index.js'
// ... (약 55개)
```
각 커맨드 파일(예: `src/commands/mcp/index.ts`)은 커맨드 객체를 정의하고 무거운 구현체는 `load: () => import('./mcp.js')` 형태로 지연 임포트한다. 결과적으로 **메타데이터는 즉시**, **실제 로직은 필요 시** 로드된다.
### 4.2 조건부 require (feature flag 게이팅)
Bun 번들러의 `feature()` 함수와 CommonJS `require()`를 조합하여 특정 기능이 활성화된 경우에만 커맨드를 포함시킨다. `feature()`는 빌드 시 데드 코드 제거(dead code elimination)를 가능하게 한다.
null이 된 커맨드는 스프레드 연산자(`...(voiceCommand ? [voiceCommand] : [])`)로 `COMMANDS()` 배열에서 자동 제외된다.
### 4.3 사용자 타입 게이팅
`USER_TYPE === 'ant'`인 경우에만 `INTERNAL_ONLY_COMMANDS`가 `COMMANDS()`에 포함된다. 이 배열에는 `backfillSessions`, `bughunter`, `commit`, `goodClaude`, `autofixPr` 등 Anthropic 내부 개발자 전용 커맨드 약 20개가 포함된다.
나중에 추가된 커맨드가 이름 충돌 시 덮어쓰지 않도록 중복 제거는 `getCommands()`의 Set 기반 필터링으로 처리된다.
### 4.6 지연 로딩 특례: /insights
113KB에 달하는 `insights.ts` 모듈은 정적 임포트 없이 `commands.ts` 내부에서 직접 인라인 `prompt` 타입 커맨드로 정의되어 있다.
```typescript
const usageReport: Command = {
type: 'prompt',
name: 'insights',
// ...
async getPromptForCommand(args, context) {
const real = (await import('./commands/insights.js')).default
return real.getPromptForCommand(args, context)
},
}
```
이 패턴은 `getPromptForCommand()` 호출 시점에만 무거운 모듈을 로드하는 지연 심(lazy shim)이다.
---
## 5. 커맨드 실행 흐름
### 5.1 커맨드 탐색
```typescript
function findCommand(commandName: string, commands: Command[]): Command | undefined {
return commands.find(
_ =>
_.name === commandName ||
getCommandName(_) === commandName || // userFacingName() 우선
_.aliases?.includes(commandName),
)
}
```
`getCommandName()`은 `cmd.userFacingName?.()`을 먼저 시도하여 플러그인이 접두사를 제거한 이름을 표시할 수 있도록 한다. 탐색 실패 시 `getCommand()`가 사용 가능한 모든 커맨드 이름을 나열한 `ReferenceError`를 던진다.
### 5.2 prompt 타입 실행 흐름
```
사용자 입력 /commit
→ findCommand('commit') → commit 커맨드 객체
→ getPromptForCommand(args, context) 호출
→ executeShellCommandsInPrompt() 로 !`git status` 등 셸 명령 미리 실행
→ ContentBlockParam[] 반환
→ 반환된 프롬프트가 현재 대화에 삽입되어 LLM에 전달
→ LLM이 allowedTools 범위 내에서 도구 호출 수행
```
`allowedTools` 필드는 해당 커맨드 실행 중에 사용 가능한 도구를 제한한다. `/commit`의 경우 `['Bash(git add:*)', 'Bash(git status:*)', 'Bash(git commit:*)']`로 git 관련 작업만 허용한다.
| `/session` (= `/remote`) | local-jsx | 원격 세션 URL/QR 코드 표시 |
| `/resume` | local-jsx | 이전 세션 재개 |
| `/tasks` | local-jsx | 백그라운드 태스크 관리 |
| `/agents` | local-jsx | 에이전트 목록 조회 |
| `/export` | local-jsx | 대화 내보내기 |
| `/tag` | local-jsx | 세션 태그 관리 |
### 6.6 AI 기능
| 커맨드 | 타입 | 설명 |
|--------|------|------|
| `/plan` | local-jsx | 플랜 모드 활성화/세션 플랜 조회 |
| `/thinkback` | local-jsx | 사고 과정 재검토 |
| `/thinkback-play` | local-jsx | 사고 재생 |
| `/fast` | local-jsx | 빠른 응답 모드 |
| `/passes` | local-jsx | 멀티 패스 실행 설정 |
| `/advisor` | prompt | 코드 개선 조언 |
| `/bughunter` | local-jsx | 버그 탐색 (내부 전용) |
### 6.7 설치 / 셋업
| 커맨드 | 타입 | 설명 |
|--------|------|------|
| `/init` | prompt | 프로젝트 CLAUDE.md 초기화 |
| `/doctor` | local-jsx | 설치 상태 진단 |
| `/ide` | local-jsx | IDE 익스텐션 설치 |
| `/terminal-setup` | local-jsx | 터미널 환경 설정 |
| `/install-github-app` | local-jsx | GitHub 앱 설치 |
| `/install-slack-app` | local-jsx | Slack 앱 설치 |
| `/upgrade` | local-jsx | Claude Code 업그레이드 |
| `/desktop` | local-jsx | 데스크탑 앱 설정 |
| `/mobile` | local-jsx | 모바일 QR 코드 |
| `/chrome` | local-jsx | Chrome 익스텐션 설정 |
### 6.8 인증 / 요금
| 커맨드 | 타입 | 설명 |
|--------|------|------|
| `/login` | local-jsx | 인증 (3P 서비스 비사용 시만 표시) |
| `/logout` | local | 로그아웃 |
| `/cost` | local | 세션 비용 조회 |
| `/usage` | local-jsx | 사용량 정보 |
| `/extra-usage` | local-jsx | 추가 사용량 구매 |
| `/rate-limit-options` | local-jsx | 요금 한도 설정 |
| `/insights` | prompt | 세션 분석 리포트 (지연 로드) |
### 6.9 유틸리티 / 기타
| 커맨드 | 타입 | 설명 |
|--------|------|------|
| `/help` | local-jsx | 도움말 |
| `/exit` | local | 종료 |
| `/status` | local-jsx | 시스템 상태 |
| `/statusline` | local | 상태 표시줄 토글 |
| `/copy` | local | 마지막 메시지 복사 |
| `/feedback` | local-jsx | 피드백 전송 |
| `/color` | local-jsx | 에이전트 색상 변경 |
| `/stickers` | local-jsx | 스티커 |
| `/btw` | local-jsx | 빠른 노트 |
| `/rename` | local-jsx | 세션 이름 변경 |
| `/stats` | local-jsx | 통계 조회 |
| `/release-notes` | local-jsx | 변경 이력 |
| `/privacy-settings` | local-jsx | 개인정보 설정 |
| `/add-dir` | local-jsx | 작업 디렉터리 추가 |
---
## 7. 주요 설계 결정
### 7.1 타입 유니언 방식의 실행 다형성
커맨드 실행은 상속 계층이 아닌 구조적 타입 유니언(`prompt | local | local-jsx`)으로 구현된다. 각 타입은 서로 다른 함수 시그니처(`getPromptForCommand` / `call`)를 가지며, 호출 측에서 `cmd.type` 판별식으로 분기한다. 이 방식은 타입 안전성을 유지하면서도 새로운 실행 타입을 추가할 때 기존 커맨드를 수정하지 않아도 된다는 장점이 있다.
### 7.2 메모이제이션과 신선도의 균형
`loadAllCommands(cwd)`는 `lodash-es/memoize`로 메모이제이션되어 디스크 I/O 비용을 절감한다. 반면 `meetsAvailabilityRequirement()`는 의도적으로 메모이제이션하지 않아 `/login` 이후 인증 상태 변경이 즉시 반영된다. 캐시 계층(memoize) 위에 신선한 필터링 계층을 두는 이중 구조가 핵심이다.
### 7.3 bun:bundle feature()를 활용한 데드 코드 제거
`feature('VOICE_MODE')` 같은 호출은 Bun 번들러가 빌드 시 평가하여 비활성 브랜치를 번들에서 완전히 제거한다. 외부 배포본에서 `INTERNAL_ONLY_COMMANDS`에 해당하는 코드가 포함되지 않는 것도 같은 원리다. `/* eslint-disable @typescript-eslint/no-require-imports */` 주석과 함께 CommonJS `require()`를 사용하는 이유가 여기에 있다: ES 모듈 정적 임포트는 번들러가 조건부 제거를 적용할 수 없기 때문이다.
### 7.4 원격 모드와 브리지 모드의 이중 안전 목록
`REMOTE_SAFE_COMMANDS`(18개)와 `BRIDGE_SAFE_COMMANDS`(6개)는 두 가지 다른 원격 실행 컨텍스트를 각각 제어한다. `REMOTE_SAFE_COMMANDS`는 CCR 초기화 전 TUI 사전 필터링에 사용되고, `BRIDGE_SAFE_COMMANDS`는 iOS/웹 클라이언트에서 들어오는 슬래시 커맨드의 안전 실행에 사용된다. `local-jsx` 타입은 Ink UI를 렌더링하므로 브리지에서 원천 차단된다(`isBridgeSafeCommand()`).
### 7.5 스킬과 커맨드의 통합 레지스트리
스킬(skill)은 `type: 'prompt'`인 커맨드의 하위 집합으로, `loadedFrom` 필드(`'skills'`, `'bundled'`, `'plugin'`)와 `disableModelInvocation` 유무로 구분된다. `getSkillToolCommands()`는 모델이 직접 호출할 수 있는 커맨드를, `getSlashCommandToolSkills()`는 슬래시 커맨드 형태로 사용자가 호출하는 스킬을 필터링한다. 이 통합 구조 덕분에 동일한 커맨드 객체가 자동완성 UI와 모델 SkillTool 양쪽에서 재사용된다.
### 7.6 formatDescriptionWithSource()의 UI/모델 분리
커맨드 설명은 사용자 대면 UI(자동완성, 도움말)와 모델 대면 프롬프트(SkillTool)에서 다르게 표시된다. UI에서는 `formatDescriptionWithSource()`가 출처 주석(`(plugin)`, `(bundled)`, `(workflow)`)을 추가하여 사용자가 커맨드 출처를 인식할 수 있게 한다. 모델 프롬프트에서는 `cmd.description`을 직접 사용하여 불필요한 메타 정보 없이 깔끔한 설명을 전달한다.
---
## Navigation
- 이전: [Tool 시스템: 도구 레지스트리 & 실행 파이프라인](./tool-system.md)
- 다음: [권한 시스템: 도구 실행 승인 흐름](./permission-system.md)