63 KiB
Claude Code — Special Systems: Buddy, Memory, Keybindings, Skills, Voice, Plugins & More
Table of Contents
- Buddy (Companion/Tamagotchi) System
- Memory Directory (memdir) System
- Keybindings System
- Skills System
- Voice System
- Plugins System
- Output Styles
- Hooks Schema
- Native-TypeScript Ports (native-ts/)
- MoreRight Hook (moreright/)
- Migrations
- Core Type Definitions (types/)
- Remote Session System (remote/)
1. Buddy (Companion/Tamagotchi) System
1.1 System Overview
The Buddy system is a virtual companion ("tamagotchi") that appears as an ASCII art sprite beside the user's prompt input. Each user receives a deterministic companion whose appearance (species, eyes, hat, rarity, shiny status, stats) is derived from their user ID via a seeded PRNG — not stored. The companion's "soul" (name and personality) is generated by the AI and persisted in config. The feature is gated behind a BUDDY feature flag and was planned to launch April 1–7, 2026 as a "teaser window."
Source directory: src/buddy/
Files:
buddy/types.ts— Type definitions, species/eyes/hats/stats constants, rarity weightsbuddy/companion.ts— PRNG, rolling, and companion reconstruction logicbuddy/sprites.ts— ASCII art sprite frames for all 18 speciesbuddy/prompt.ts— Companion introduction text injection into system promptbuddy/useBuddyNotification.tsx— React hook for startup teaser notificationbuddy/CompanionSprite.tsx— React component rendering the animated sprite + speech bubble
1.2 Architecture
The system splits companion data into two parts:
- Bones (
CompanionBones): Deterministic visual/stat data derived fromhash(userId + SALT). Never stored; always recomputed on read. This prevents users from editing their config to claim a legendary rarity. - Soul (
CompanionSoul): AI-generated name and personality, persisted toconfig.companionasStoredCompanion. - Full Companion (
Companion = CompanionBones & CompanionSoul & { hatchedAt: number }): assembled at read time bygetCompanion().
The rolling process uses a single pass through a seeded PRNG (mulberry32) to deterministically select:
- Rarity tier (weighted random)
- Species
- Eye style
- Hat (none if common)
- Shiny flag (1% probability)
- Stats (one peak stat, one dump stat, rest scattered; floor scales with rarity)
inspirationSeed(passed to AI for soul generation)
1.3 Species Encoding (Canary Bypass)
Critical detail: Species name strings are encoded as String.fromCharCode(...) literals to avoid triggering a build-time string scan that checks for model codenames (excluded-strings.txt). One species name collides with an internal model codename canary. All 18 species use this encoding uniformly.
// In buddy/types.ts:
const c = String.fromCharCode
export const duck = c(0x64,0x75,0x63,0x6b) as 'duck'
// ... all 18 species encoded this way
1.4 PRNG: Mulberry32
Algorithm:
function mulberry32(seed: number): () => number {
let a = seed >>> 0
return function () {
a |= 0
a = (a + 0x6d2b79f5) | 0
let t = Math.imul(a ^ (a >>> 15), 1 | a)
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
}
}
- Tiny 32-bit PRNG with good statistical distribution
- Seeded from
hashString(userId + SALT)whereSALT = 'friend-2026-401' hashStringuses Bun's native hash when available, falls back to FNV-1a (32-bit)
Caching: The roll() function memoizes the last result keyed on userId + SALT, since it is called from three hot paths: the 500ms sprite tick, per-keystroke PromptInput rendering, and the per-turn observer.
1.5 Data Structures
Rarity
export const RARITIES = ['common', 'uncommon', 'rare', 'epic', 'legendary'] as const
export type Rarity = (typeof RARITIES)[number]
RARITY_WEIGHTS
export const RARITY_WEIGHTS = {
common: 60, // 60%
uncommon: 25, // 25%
rare: 10, // 10%
epic: 4, // 4%
legendary: 1, // 1%
} as const
RARITY_FLOOR (stat minimums per rarity)
const RARITY_FLOOR: Record<Rarity, number> = {
common: 5,
uncommon: 15,
rare: 25,
epic: 35,
legendary: 50,
}
RARITY_STARS (UI display)
export const RARITY_STARS = {
common: '★',
uncommon: '★★',
rare: '★★★',
epic: '★★★★',
legendary: '★★★★★',
}
RARITY_COLORS (maps to Theme keys)
export const RARITY_COLORS = {
common: 'inactive',
uncommon: 'success',
rare: 'permission',
epic: 'autoAccept',
legendary: 'warning',
}
Species (18 total)
export const SPECIES = [
duck, goose, blob, cat, dragon, octopus, owl, penguin,
turtle, snail, ghost, axolotl, capybara, cactus, robot,
rabbit, mushroom, chonk,
] as const
export type Species = (typeof SPECIES)[number]
Eye (6 styles)
export const EYES = ['·', '✦', '×', '◉', '@', '°'] as const
Hat (8 types)
export const HATS = [
'none', 'crown', 'tophat', 'propeller',
'halo', 'wizard', 'beanie', 'tinyduck',
] as const
StatName (5 stats)
export const STAT_NAMES = ['DEBUGGING', 'PATIENCE', 'CHAOS', 'WISDOM', 'SNARK'] as const
CompanionBones (deterministic, never stored)
export type CompanionBones = {
rarity: Rarity
species: Species
eye: Eye
hat: Hat
shiny: boolean
stats: Record<StatName, number> // 1–100 per stat
}
CompanionSoul (AI-generated, stored)
export type CompanionSoul = {
name: string
personality: string
}
StoredCompanion (what persists in config)
export type StoredCompanion = CompanionSoul & { hatchedAt: number }
Companion (runtime assembled)
export type Companion = CompanionBones & CompanionSoul & { hatchedAt: number }
Roll (output of rollFrom)
export type Roll = {
bones: CompanionBones
inspirationSeed: number // Passed to AI for deterministic soul generation
}
1.6 Stat Rolling Algorithm
function rollStats(rng, rarity): Record<StatName, number> {
const floor = RARITY_FLOOR[rarity] // 5–50 depending on rarity
const peak = pick(rng, STAT_NAMES) // one stat gets +50 bonus
let dump = pick(rng, STAT_NAMES) // different stat, penalized
while (dump === peak) dump = pick(rng, STAT_NAMES)
for (name of STAT_NAMES) {
if (name === peak) stats[name] = min(100, floor + 50 + rng()*30) // 55–130, capped 100
else if (name === dump) stats[name] = max(1, floor - 10 + rng()*15) // 1–20 (at common)
else stats[name] = floor + rng()*40 // spread
}
}
1.7 Sprite System
Each species has 3 animation frames of ASCII art, each 5 lines tall, 12 chars wide. The {E} placeholder in frame templates is substituted with the companion's eye character.
Frame structure:
- Line 0: Hat slot (blank if no hat, or ambient detail like
~for smoke,*for sparks) - Lines 1–4: Body art
Hat rendering: Hats are placed on line 0 only if it is blank. If all frames have a blank line 0, the blank line is stripped (saves terminal row when no hat).
Hat ASCII art (in HAT_LINES):
| Hat | ASCII |
|---|---|
| none | (empty) |
| crown | \^^^/ |
| tophat | [___] |
| propeller | -+- |
| halo | ( ) |
| wizard | /^\ |
| beanie | (___) |
| tinyduck | ,> |
Idle animation sequence:
const IDLE_SEQUENCE = [0, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 2, 0, 0, 0]
// -1 = blink on frame 0 (special)
// TICK_MS = 500ms per step
Pet animation: After /buddy pet, hearts float upward for 5 ticks (~2.5 seconds):
const PET_HEARTS = [
` ${H} ${H} `,
` ${H} ${H} ${H} `,
` ${H} ${H} ${H} `,
`${H} ${H} ${H} `,
'· · · '
]
1.8 All Exports
buddy/companion.ts
| Export | Type | Description |
|---|---|---|
Roll |
type | { bones: CompanionBones; inspirationSeed: number } |
roll(userId: string) |
() => Roll |
Deterministic roll (memoized per userId+SALT) |
rollWithSeed(seed: string) |
() => Roll |
Roll from arbitrary seed string |
companionUserId() |
() => string |
Returns oauthAccount UUID or userID or 'anon' |
getCompanion() |
() => Companion | undefined |
Assembles companion from config + roll |
buddy/sprites.ts
| Export | Type | Description |
|---|---|---|
renderSprite(bones, frame?) |
(CompanionBones, number?) => string[] |
Returns array of lines for the sprite |
spriteFrameCount(species) |
(Species) => number |
Number of frames for a species (all are 3) |
renderFace(bones) |
(CompanionBones) => string |
Short face string for inline display |
buddy/prompt.ts
| Export | Type | Description |
|---|---|---|
companionIntroText(name, species) |
(string, string) => string |
System prompt text for companion introduction |
getCompanionIntroAttachment(messages?) |
(Message[]?) => Attachment[] |
Returns intro attachment if not yet injected |
buddy/useBuddyNotification.tsx
| Export | Type | Description |
|---|---|---|
isBuddyTeaserWindow() |
() => boolean |
True if local date is April 1–7, 2026 |
isBuddyLive() |
() => boolean |
True if local date is April 2026 or later |
useBuddyNotification() |
() => void |
React hook: shows rainbow /buddy startup hint |
findBuddyTriggerPositions(text) |
(string) => Array<{start,end}> |
Find /buddy occurrences in text |
1.9 Configuration
config.companion— StoresStoredCompanion(name, personality, hatchedAt)config.companionMuted— When true, suppresses companion intro injection- Feature flag:
feature('BUDDY')— gates all buddy functionality - SALT constant:
'friend-2026-401'— mixed into hash to prevent replay attacks
2. Memory Directory (memdir) System
2.1 System Overview
The memory system provides Claude with persistent, file-based memory across sessions. Memory is stored as markdown files with YAML frontmatter in a per-project directory. It encompasses:
- Auto memory (
~/.claude/projects/<sanitized-git-root>/memory/) — per-user, per-project - Team memory (
<auto-mem-path>/team/) — shared across contributors (feature-gated) - Memory scanning — reads frontmatter headers to build a manifest without loading full content
- Relevance selection — uses a Sonnet model call to pick the most relevant files (up to 5) for a given query
- Freshness warnings — age-based staleness caveats injected as
<system-reminder>tags
Source directory: src/memdir/
2.2 Memory Directory Structure
~/.claude/
projects/
<sanitized-git-root>/
memory/
MEMORY.md -- entrypoint index (always loaded)
<topic-file>.md -- individual memory files with frontmatter
team/ -- team-shared memories (TEAMMEM feature)
MEMORY.md
<topic-file>.md
logs/ -- KAIROS mode: append-only daily logs
YYYY/
MM/
YYYY-MM-DD.md
2.3 Memory Types Taxonomy
All memory falls into four types (stored in frontmatter type: field):
| Type | Scope (combined mode) | Description |
|---|---|---|
user |
Always private | User's role, goals, knowledge, preferences |
feedback |
Private (default) or team for project-wide conventions | How-to-approach-work guidance from corrections and confirmations |
project |
Strongly bias team | Ongoing work, goals, bugs, incidents not derivable from code |
reference |
Usually team | Pointers to external systems (Linear, Grafana, Slack) |
What NOT to save:
- Code patterns, architecture, file structure (derivable by reading code)
- Git history, recent changes (use
git log) - Debugging solutions/fix recipes (the fix is in the code)
- Anything in CLAUDE.md
- Ephemeral task details, in-progress work
2.4 Memory Frontmatter Format
---
name: {{memory name}}
description: {{one-line description — used to decide relevance in future conversations, so be specific}}
type: {{user, feedback, project, reference}}
---
{{memory content}}
For feedback and project types, the body should include:
- Rule/fact as the lead
**Why:**line with the reason**How to apply:**line with when/where it kicks in
2.5 Architecture: Data Flow
User query arrives
│
▼
scanMemoryFiles(memoryDir) -- reads frontmatter of all .md files
│ returns MemoryHeader[]
▼
filterOut(alreadySurfaced) -- skip files shown in prior turns
│
▼
selectRelevantMemories(query, ...) -- sideQuery to Sonnet with manifest
│ returns up to 5 filenames
▼
Return RelevantMemory[] (path + mtimeMs)
│
▼
Caller injects file contents -- reads full file content
Caller adds freshness warnings -- memoryFreshnessNote(mtimeMs)
2.6 Memory Scanning (memoryScan.ts)
scanMemoryFiles(memoryDir, signal)
readdir(memoryDir, { recursive: true })to find all.mdfiles- Excludes
MEMORY.md(entrypoint — already in system prompt) - Reads only first
FRONTMATTER_MAX_LINES = 30lines of each file - Parses frontmatter for
descriptionandtype - Sorts newest-first by
mtimeMs - Capped at
MAX_MEMORY_FILES = 200 - Returns
MemoryHeader[]
MemoryHeader type:
export type MemoryHeader = {
filename: string
filePath: string
mtimeMs: number
description: string | null
type: MemoryType | undefined
}
formatMemoryManifest(memories)
Formats headers as text manifest for the selector prompt:
- [user] user_role.md (2026-01-15T12:00:00Z): user is a senior Go engineer focused on observability
- [feedback] no_mocks.md (2026-01-10T09:30:00Z): integration tests must use real database
2.7 Relevance Selection (findRelevantMemories.ts)
System prompt used for selection:
You are selecting memories that will be useful to Claude Code as it processes a user's query.
Return a list of filenames for the memories that will clearly be useful (up to 5).
Only include memories you are certain will be helpful. Be selective and discerning.
- If unsure, do not include it.
- If no memories are clearly useful, return an empty list.
- If recently-used tools are provided, do not select reference docs for those tools
(skip API usage docs, DO keep warnings/gotchas about active tools).
sideQuery parameters:
- Model:
getDefaultSonnetModel() - Max tokens: 256
- Output format: JSON schema
{ selected_memories: string[] } - Query source:
'memdir_relevance'
findRelevantMemories signature:
export async function findRelevantMemories(
query: string,
memoryDir: string,
signal: AbortSignal,
recentTools: readonly string[] = [],
alreadySurfaced: ReadonlySet<string> = new Set(),
): Promise<RelevantMemory[]>
export type RelevantMemory = {
path: string
mtimeMs: number
}
2.8 Memory Freshness System (memoryAge.ts)
All functions are pure, accepting mtimeMs timestamps:
| Export | Signature | Description |
|---|---|---|
memoryAgeDays(mtimeMs) |
(number) => number |
Floor-rounded days since mtime, clamped to ≥0 |
memoryAge(mtimeMs) |
(number) => string |
Human-readable: 'today', 'yesterday', 'N days ago' |
memoryFreshnessText(mtimeMs) |
(number) => string |
Staleness caveat text ('' for ≤1 day, warns for older) |
memoryFreshnessNote(mtimeMs) |
(number) => string |
<system-reminder> wrapped freshness text |
Staleness warning text (for memories >1 day old):
This memory is N days old. Memories are point-in-time observations, not live state —
claims about code behavior or file:line citations may be outdated.
Verify against current code before asserting as fact.
2.9 Memory Path Resolution (paths.ts)
isAutoMemoryEnabled() — Priority chain:
CLAUDE_CODE_DISABLE_AUTO_MEMORYenv var (truthy → OFF, falsy-defined → ON)CLAUDE_CODE_SIMPLE(--bare mode) → OFFCLAUDE_CODE_REMOTEwithoutCLAUDE_CODE_REMOTE_MEMORY_DIR→ OFFsettings.autoMemoryEnabled(project-level opt-out)- Default: enabled
getAutoMemPath() — Resolution order (memoized by getProjectRoot()):
CLAUDE_COWORK_MEMORY_PATH_OVERRIDEenv var (Cowork spaces)autoMemoryDirectoryin settings (policy > flag > local > user sources; supports~/expansion)<memoryBase>/projects/<sanitized-git-root>/memory/memoryBase=CLAUDE_CODE_REMOTE_MEMORY_DIRor~/.claude
Security validations in validateMemoryPath():
- Rejects relative paths (not
isAbsolute) - Rejects root/near-root (length < 3)
- Rejects Windows drive-root (
C:) - Rejects UNC paths (
\\server\share,//) - Rejects null bytes
- Normalizes NFC
getAutoMemDailyLogPath(date?) — KAIROS mode daily log path:
<autoMemPath>/logs/YYYY/MM/YYYY-MM-DD.md
All path exports:
| Export | Description |
|---|---|
isAutoMemoryEnabled() |
Check if auto-memory is on |
isExtractModeActive() |
Whether background memory extraction agent will run |
getMemoryBaseDir() |
Base dir: env override or ~/.claude |
getAutoMemPath() |
Full memory directory path (memoized) |
getAutoMemDailyLogPath(date?) |
Daily log path for KAIROS mode |
getAutoMemEntrypoint() |
MEMORY.md path inside auto-mem dir |
isAutoMemPath(absolutePath) |
Check if path is inside auto-memory dir |
hasAutoMemPathOverride() |
Check if Cowork env override is active |
2.10 Team Memory Paths (teamMemPaths.ts)
Team memory lives at <autoMemPath>/team/. Heavy path traversal protection:
PathTraversalError — Custom error class thrown on injection attempts.
sanitizePathKey(key) — Rejects:
- Null bytes
- URL-encoded traversals (e.g.
%2e%2e%2f) - Unicode normalization attacks (fullwidth
../→../) - Backslashes
- Absolute paths
validateTeamMemWritePath(filePath) — Two-pass validation:
path.resolve()to eliminate..segments, check string containmentrealpathDeepestExisting()to follow symlinks and verify real containment
realpathDeepestExisting(absolutePath) — Walks up directory tree until realpath() succeeds, handles dangling symlinks (detects via lstat) and symlink loops (ELOOP).
All exports:
| Export | Description |
|---|---|
PathTraversalError |
Error class for traversal attempts |
isTeamMemoryEnabled() |
GrowthBook gate tengu_herring_clock + auto-memory enabled |
getTeamMemPath() |
<autoMemPath>/team/ |
getTeamMemEntrypoint() |
<autoMemPath>/team/MEMORY.md |
isTeamMemPath(filePath) |
String-level containment check |
isTeamMemFile(filePath) |
Team enabled + path contained |
validateTeamMemWritePath(filePath) |
Full symlink-safe write validation |
validateTeamMemKey(relativeKey) |
Sanitize relative key + validate write path |
2.11 Memory Prompt Building (memdir.ts)
Constants:
export const ENTRYPOINT_NAME = 'MEMORY.md'
export const MAX_ENTRYPOINT_LINES = 200
export const MAX_ENTRYPOINT_BYTES = 25_000
truncateEntrypointContent(raw) — Line-truncates first, then byte-truncates at last newline before cap. Appends warning message naming which cap(s) fired.
buildMemoryLines(displayName, memoryDir, extraGuidelines?, skipIndex?) — Builds behavioral instructions without MEMORY.md content. Used in system prompt.
buildMemoryPrompt({ displayName, memoryDir, extraGuidelines? }) — Same as buildMemoryLines but includes MEMORY.md content. Used by agent memory.
buildSearchingPastContextSection(autoMemDir) — Conditionally adds a "Searching past context" section with grep commands (gated on tengu_coral_fern GrowthBook feature).
ensureMemoryDirExists(memoryDir) — Idempotent mkdir (recursive); logs non-EEXIST errors without throwing.
loadMemoryPrompt() — Top-level dispatcher:
- KAIROS + kairosActive →
buildAssistantDailyLogPrompt() - TEAMMEM + team enabled →
buildCombinedMemoryPrompt() - Auto enabled →
buildMemoryLines()joined - Otherwise →
null
2.12 Memory Type Constants (memoryTypes.ts)
All exports used in system prompt construction:
| Export | Type | Description |
|---|---|---|
MEMORY_TYPES |
readonly string[] |
['user', 'feedback', 'project', 'reference'] |
MemoryType |
type | Union of the four type strings |
parseMemoryType(raw) |
(unknown) => MemoryType | undefined |
Parses frontmatter value |
TYPES_SECTION_COMBINED |
readonly string[] |
Prompt section for dual-directory mode |
TYPES_SECTION_INDIVIDUAL |
readonly string[] |
Prompt section for single-directory mode |
WHAT_NOT_TO_SAVE_SECTION |
readonly string[] |
Exclusion list for memory content |
MEMORY_DRIFT_CAVEAT |
string |
Single bullet warning about memory staleness |
WHEN_TO_ACCESS_SECTION |
readonly string[] |
When to read memories |
TRUSTING_RECALL_SECTION |
readonly string[] |
Verification guidance before recommending from memory |
MEMORY_FRONTMATTER_EXAMPLE |
readonly string[] |
Example frontmatter block for the prompt |
2.13 Team Memory Prompts (teamMemPrompts.ts)
buildCombinedMemoryPrompt(extraGuidelines?, skipIndex?) — Builds the combined prompt when both auto and team memory are enabled. Includes:
- Both directory paths
- Two-scope explanation (private vs team)
- Combined
TYPES_SECTION_COMBINEDwith<scope>tags - Dual MEMORY.md index instructions
3. Keybindings System
3.1 System Overview
The keybindings system provides a fully configurable keyboard shortcut layer for Claude Code. Users can customize bindings via ~/.claude/keybindings.json (gated on tengu_keybinding_customization_release GrowthBook feature). The system supports:
- Single-keystroke bindings
- Multi-keystroke chords (e.g.,
ctrl+x ctrl+k) - Context-scoped bindings (Chat, Global, Confirmation, etc.)
- Null-unbinding (set action to
nullto disable a default) - Command bindings (
command:helpexecutes slash commands) - Hot-reload via
chokidarfile watcher
Source directory: src/keybindings/
3.2 Architecture
DEFAULT_BINDINGS (KeybindingBlock[])
│
▼ parseBindings()
ParsedBinding[] <── default bindings parsed at startup
│
▼ merge with user bindings (user appended after, so user wins)
ParsedBinding[] <── merged bindings (last-wins for same key+context)
│
▼
KeybindingProvider (React context)
│
├── resolveKeyWithChordState() ◄── called on every keypress
│ ├── chord_started → pending state
│ ├── chord_cancelled → reset
│ ├── match → invoke action
│ ├── unbound → swallow event
│ └── none → pass through
│
└── useKeybinding(action, context, handler) ◄── component hook
3.3 Keybinding Contexts
18 contexts defined in KEYBINDING_CONTEXTS:
| Context | Activation |
|---|---|
Global |
Active everywhere, regardless of focus |
Chat |
When the chat input is focused |
Autocomplete |
When autocomplete menu is visible |
Confirmation |
When a confirmation/permission dialog is shown |
Help |
When the help overlay is open |
Transcript |
When viewing the transcript |
HistorySearch |
When searching command history (ctrl+r) |
Task |
When a task/agent is running in the foreground |
ThemePicker |
When the theme picker is open |
Settings |
When the settings menu is open |
Tabs |
When tab navigation is active |
Attachments |
When navigating image attachments in a select dialog |
Footer |
When footer indicators are focused |
MessageSelector |
When the message selector (rewind) is open |
DiffDialog |
When the diff dialog is open |
ModelPicker |
When the model picker is open |
Select |
When a select/list component is focused |
Plugin |
When the plugin dialog is open |
3.4 Default Bindings (defaultBindings.ts)
Platform-specific keys:
IMAGE_PASTE_KEY:alt+v(Windows),ctrl+v(others)MODE_CYCLE_KEY:meta+m(Windows without VT mode),shift+tab(others)- VT mode support check: Node ≥22.17.0 or ≥24.2.0, Bun ≥1.2.23
Complete default bindings by context:
Global context:
| Key | Action |
|---|---|
ctrl+c |
app:interrupt (non-rebindable) |
ctrl+d |
app:exit (non-rebindable) |
ctrl+l |
app:redraw |
ctrl+t |
app:toggleTodos |
ctrl+o |
app:toggleTranscript |
ctrl+shift+b |
app:toggleBrief (KAIROS/KAIROS_BRIEF only) |
ctrl+shift+o |
app:toggleTeammatePreview |
ctrl+r |
history:search |
ctrl+shift+f / cmd+shift+f |
app:globalSearch (QUICK_SEARCH feature) |
ctrl+shift+p / cmd+shift+p |
app:quickOpen (QUICK_SEARCH feature) |
meta+j |
app:toggleTerminal (TERMINAL_PANEL feature) |
Chat context:
| Key | Action |
|---|---|
escape |
chat:cancel |
ctrl+x ctrl+k |
chat:killAgents (chord) |
shift+tab / meta+m |
chat:cycleMode |
meta+p |
chat:modelPicker |
meta+o |
chat:fastMode |
meta+t |
chat:thinkingToggle |
enter |
chat:submit |
up |
history:previous |
down |
history:next |
ctrl+_ / ctrl+shift+- |
chat:undo |
ctrl+x ctrl+e / ctrl+g |
chat:externalEditor |
ctrl+s |
chat:stash |
ctrl+v / alt+v |
chat:imagePaste |
shift+up |
chat:messageActions (MESSAGE_ACTIONS feature) |
space |
voice:pushToTalk (VOICE_MODE feature) |
Autocomplete:
| Key | Action |
|---|---|
tab |
autocomplete:accept |
escape |
autocomplete:dismiss |
up / down |
autocomplete:previous / autocomplete:next |
Confirmation:
| Key | Action |
|---|---|
y / enter |
confirm:yes |
n / escape |
confirm:no |
up / down |
confirm:previous / confirm:next |
tab |
confirm:nextField |
space |
confirm:toggle |
shift+tab |
confirm:cycleMode |
ctrl+e |
confirm:toggleExplanation |
ctrl+d |
permission:toggleDebug |
Scroll:
| Key | Action |
|---|---|
pageup / pagedown |
scroll:pageUp / scroll:pageDown |
wheelup / wheeldown |
scroll:lineUp / scroll:lineDown |
ctrl+home / ctrl+end |
scroll:top / scroll:bottom |
ctrl+shift+c / cmd+c |
selection:copy |
Task:
| Key | Action |
|---|---|
ctrl+b |
task:background |
3.5 Parser (parser.ts)
parseKeystroke(input: string): ParsedKeystroke
Splits on +, recognizes modifiers (case-insensitive aliases):
ctrl/controlalt/opt/optionshiftmetacmd/command/super/win- Special keys:
esc→escape,return→enter,space→,↑↓←→
parseChord(input: string): Chord
Splits space-separated steps into ParsedKeystroke[]. Special case: lone space " " is the space key, not a separator.
keystrokeToString(ks: ParsedKeystroke): string
Canonical string representation (for internal use).
keystrokeToDisplayString(ks, platform?): string
Platform-appropriate display: opt on macOS, alt elsewhere; cmd on macOS, super elsewhere.
parseBindings(blocks: KeybindingBlock[]): ParsedBinding[]
Converts KeybindingBlock[] (raw config) to flat ParsedBinding[].
3.6 Matching (match.ts)
getKeyName(input: string, key: Key): string | null
Maps Ink's boolean key flags to string names (key.escape → 'escape', key.upArrow → 'up', single char → lowercased, etc.).
matchesKeystroke(input, key, target): boolean
- Extracts key name and modifiers from Ink's Key object
- Quirk:
key.meta = truewhen escape is pressed in Ink (legacy terminal behavior). Ignored for escape key matching itself. - Alt and meta are treated as aliases in Ink (terminal limitation):
target.alt || target.metamatched againstkey.meta. - Super/cmd is distinct — only arrives via kitty keyboard protocol.
matchesBinding(input, key, binding): boolean
Single-keystroke binding match only (chord length must be 1).
3.7 Resolver (resolver.ts)
resolveKey(input, key, activeContexts, bindings): ResolveResult
Pure function for single-keystroke resolution:
- Last matching binding wins (for user overrides)
- Returns
{ type: 'match', action },{ type: 'none' }, or{ type: 'unbound' }
resolveKeyWithChordState(input, key, activeContexts, bindings, pending): ChordResolveResult
Multi-keystroke chord resolution with state:
- Cancels on escape when in a chord
- Groups chord candidates by
chordToString(chord)key to handle null-unbinding properly (a null override on a chord prevents its prefix from entering chord-wait) - Returns additional types:
{ type: 'chord_started', pending },{ type: 'chord_cancelled' }
ChordResolveResult:
type ChordResolveResult =
| { type: 'match'; action: string }
| { type: 'none' }
| { type: 'unbound' }
| { type: 'chord_started'; pending: ParsedKeystroke[] }
| { type: 'chord_cancelled' }
getBindingDisplayText(action, context, bindings): string | undefined
Searches bindings in reverse order (last wins) for the given action+context.
keystrokesEqual(a, b): boolean
Compares with alt/meta collapsed into one logical modifier.
3.8 Type Definitions (types.ts — inferred)
type KeybindingContextName = (typeof KEYBINDING_CONTEXTS)[number]
type ParsedKeystroke = {
key: string
ctrl: boolean
alt: boolean
shift: boolean
meta: boolean
super: boolean
}
type Chord = ParsedKeystroke[]
type ParsedBinding = {
chord: Chord
action: string | null // null = unbound
context: KeybindingContextName
}
type KeybindingBlock = {
context: string
bindings: Record<string, string | null>
}
3.9 Schema (schema.ts)
Zod v4 schemas for keybindings.json validation:
KeybindingBlockSchema — Validates { context, bindings: { key: action | null } }. Bindings accept:
- Known action strings from
KEYBINDING_ACTIONS command:prefix bindings (/^command:[a-zA-Z0-9:\-_]+$/)null(to unbind)
KeybindingsSchema — Wraps with optional $schema and $docs metadata:
{
"$schema": "https://www.schemastore.org/claude-code-keybindings.json",
"$docs": "https://code.claude.com/docs/en/keybindings",
"bindings": [...]
}
Complete KEYBINDING_ACTIONS list (80 actions):
Categories: app:*, history:*, chat:*, autocomplete:*, confirm:*, tabs:*, transcript:*, historySearch:*, task:*, theme:*, help:*, attachments:*, footer:*, messageSelector:*, messageActions:*, diff:*, modelPicker:*, select:*, plugin:*, permission:*, settings:*, voice:*
3.10 Validation (validate.ts)
KeybindingWarningType: 'parse_error' | 'duplicate' | 'reserved' | 'invalid_context' | 'invalid_action'
KeybindingWarning:
type KeybindingWarning = {
type: KeybindingWarningType
severity: 'error' | 'warning'
message: string
key?: string
context?: string
action?: string
suggestion?: string
}
Validation checks:
validateUserConfig(blocks)— validates block structure (context string, bindings object)checkDuplicates(blocks)— within-context duplicate keys (normalized comparison)checkReservedShortcuts(bindings)— againstNON_REBINDABLEand platformTERMINAL_RESERVED/MACOS_RESERVEDcheckDuplicateKeysInJson(jsonString)— raw JSON string scan (JSON.parse silently drops earlier duplicates)- Special case:
voice:pushToTalkwith bare alphabetic key warns (prints into input during warmup) command:bindings must be inChatcontext
3.11 Reserved Shortcuts (reservedShortcuts.ts)
NON_REBINDABLE (error severity):
ctrl+c— interrupt/exit (hardcoded double-press logic)ctrl+d— exit (hardcoded)ctrl+m— identical to Enter in terminals (both send CR)
TERMINAL_RESERVED:
ctrl+z— Unix SIGTSTP (warning)ctrl+\— terminal SIGQUIT (error)
MACOS_RESERVED (macOS only, all errors):
cmd+c/v/x/q/w/tab/space
Note: ctrl+s (XOFF) and ctrl+q (XON) are intentionally NOT in reserved list — modern terminals disable flow control and Claude Code uses ctrl+s for stash.
3.12 User Bindings Loader (loadUserBindings.ts)
File location: ~/.claude/keybindings.json
isKeybindingCustomizationEnabled() — GrowthBook gate: tengu_keybinding_customization_release.
File watching: Uses chokidar with:
stabilityThreshold: 500ms— waits for write to stabilizepollInterval: 200msatomic: true- Watches parent directory, not just the file (handles file creation)
loadKeybindings() / loadKeybindingsSyncWithWarnings() — Load+merge+validate. Returns { bindings: ParsedBinding[], warnings: KeybindingWarning[] }.
All exports:
| Export | Description |
|---|---|
isKeybindingCustomizationEnabled() |
GrowthBook gate check |
getKeybindingsPath() |
~/.claude/keybindings.json |
loadKeybindings() |
Async load (used in watcher) |
loadKeybindingsSync() |
Sync load (React useState initializer) |
loadKeybindingsSyncWithWarnings() |
Sync with warnings |
initializeKeybindingWatcher() |
Set up chokidar watcher |
disposeKeybindingWatcher() |
Tear down watcher |
subscribeToKeybindingChanges |
Subscribe to reload events |
getCachedKeybindingWarnings() |
Current cached warnings |
resetKeybindingLoaderForTesting() |
Test reset |
3.13 Template Generator (template.ts)
generateKeybindingsTemplate() — Generates a well-documented template JSON file with all default bindings. Filters out NON_REBINDABLE shortcuts to avoid /doctor warnings. Includes $schema and $docs metadata.
3.14 Shortcut Display (shortcutFormat.ts)
getShortcutDisplay(action, context, fallback) — Non-React shortcut display helper. Logs tengu_keybinding_fallback_used analytics event (once per action+context) when the binding is not found. Falls back to hardcoded string during migration.
4. Skills System
4.1 System Overview
Skills are Claude's slash commands that execute AI prompts or local logic. They come from multiple sources:
- Bundled skills — compiled into the binary, available to all users
- Disk-based skills — markdown files in
.claude/skills/directories - Plugin skills — provided by installed plugins
- MCP skills — generated from MCP server tool definitions
Source directory: src/skills/
4.2 Bundled Skills Registry (bundledSkills.ts)
BundledSkillDefinition type:
type BundledSkillDefinition = {
name: string
description: string
aliases?: string[]
whenToUse?: string
argumentHint?: string
allowedTools?: string[]
model?: string
disableModelInvocation?: boolean
userInvocable?: boolean
isEnabled?: () => boolean
hooks?: HooksSettings
context?: 'inline' | 'fork' // fork = run as sub-agent
agent?: string
files?: Record<string, string> // reference files extracted to disk on first use
getPromptForCommand: (args: string, context: ToolUseContext) => Promise<ContentBlockParam[]>
}
File extraction (files field): When a skill has files, they are extracted to a per-process nonce directory on first invocation using O_NOFOLLOW | O_EXCL | O_CREAT flags (mode 0o600, directories mode 0o700). A Base directory for this skill: <dir> prefix is prepended to the skill prompt so the model can read these files.
Registry API:
| Export | Description |
|---|---|
registerBundledSkill(definition) |
Register a bundled skill at startup |
getBundledSkills() |
Get copy of all registered bundled skills |
clearBundledSkills() |
Clear registry (for testing) |
getBundledSkillExtractDir(skillName) |
Deterministic extract directory |
4.3 Bundled Skill Initialization (bundled/index.ts)
initBundledSkills() registers the following skills:
| Skill | Feature Gate | Description |
|---|---|---|
updateConfig |
— | Update Claude Code configuration |
keybindings |
— | Manage keyboard shortcuts |
verify |
— | Verify work / run checks |
debug |
— | Debug assistance |
loremIpsum |
— | Lorem ipsum text generation |
skillify |
— | Create new skills |
remember |
— | Explicitly save to memory |
simplify |
— | Simplify code/text |
batch |
— | Batch operations |
stuck |
— | Help when stuck |
dream |
KAIROS | KAIROS_DREAM |
Nightly memory distillation |
hunter |
REVIEW_ARTIFACT |
Code review |
loop |
AGENT_TRIGGERS |
Recurring agent triggers |
scheduleRemoteAgents |
AGENT_TRIGGERS_REMOTE |
Schedule remote agent runs |
claudeApi |
BUILDING_CLAUDE_APPS |
Claude API reference skill |
claudeInChrome |
shouldAutoEnableClaudeInChrome() |
Claude in Chrome integration |
runSkillGenerator |
RUN_SKILL_GENERATOR |
Skill generation tool |
4.4 Skills Directory Loader (loadSkillsDir.ts)
LoadedFrom type:
type LoadedFrom =
| 'commands_DEPRECATED'
| 'skills'
| 'plugin'
| 'managed'
| 'bundled'
| 'mcp'
getSkillsPath(source, dir) — Returns the config directory path for a given source and subdirectory (skills or commands).
The loader handles:
- Frontmatter parsing (
name,description,argument-hint,when-to-use,allowed-tools,model,disableNonInteractive,hooks,context,agent,effort,paths) - Shell execution in prompts (backtick commands in frontmatter)
- Gitignore integration
- Argument substitution (
$ARGUMENTS, named args$ARG_NAME) isRestrictedToPluginOnly()policy check- Multi-level discovery: project dirs up to home + user config
5. Voice System
5.1 System Overview
Voice mode enables hold-to-talk interaction with Claude Code using a push-to-talk model. Voice requires:
- OAuth authentication (uses
voice_streamendpoint on claude.ai) - GrowthBook feature flag
VOICE_MODE - Kill-switch gate:
tengu_amber_quartz_disabled(negative flag)
Source: src/voice/voiceModeEnabled.ts
5.2 API
| Export | Description |
|---|---|
isVoiceGrowthBookEnabled() |
feature('VOICE_MODE') && !tengu_amber_quartz_disabled |
hasVoiceAuth() |
OAuth provider + valid access token exists |
isVoiceModeEnabled() |
Full check: hasVoiceAuth() && isVoiceGrowthBookEnabled() |
Why OAuth required:
- Voice uses
voice_streamendpoint on claude.ai - Not available with API keys, Bedrock, Vertex, or Foundry
isAnthropicAuthEnabled()checks the provider,getClaudeAIOAuthTokens()verifies a token actually exists
Kill-switch design:
- Flag default is
false= not killed - A missing/stale disk cache reads as "not killed" → voice works immediately on fresh installs
- Emergency kill: flip
tengu_amber_quartz_disabledtotrue
Default binding: space → voice:pushToTalk (defined in defaultBindings.ts under VOICE_MODE feature gate)
Validation warning: Binding voice:pushToTalk to a bare alphabetic key (no modifiers) raises a warning because the key prints into the input during the activation warmup period. Recommended: space or a modifier combo like meta+k.
6. Plugins System
6.1 System Overview
Plugins extend Claude Code with additional skills, hooks, MCP servers, LSP servers, and output styles. There are two plugin categories:
- Built-in plugins — ship with the CLI, appear in
/pluginUI, user-toggleable - Marketplace plugins — installed from GitHub repos via
/plugin install
Source: src/plugins/
6.2 Built-in Plugin Registry (builtinPlugins.ts)
Built-in plugins use the ID format {name}@builtin.
BuiltinPluginDefinition type (from types/plugin.ts):
type BuiltinPluginDefinition = {
name: string
description: string
version?: string
skills?: BundledSkillDefinition[]
hooks?: HooksSettings
mcpServers?: Record<string, McpServerConfig>
isAvailable?: () => boolean
defaultEnabled?: boolean // defaults to true
}
LoadedPlugin type:
type LoadedPlugin = {
name: string
manifest: PluginManifest
path: string
source: string
repository: string
enabled?: boolean
isBuiltin?: boolean
sha?: string
commandsPath?: string
commandsPaths?: string[]
commandsMetadata?: Record<string, CommandMetadata>
agentsPath?: string
agentsPaths?: string[]
skillsPath?: string
skillsPaths?: string[]
outputStylesPath?: string
outputStylesPaths?: string[]
hooksConfig?: HooksSettings
mcpServers?: Record<string, McpServerConfig>
lspServers?: Record<string, LspServerConfig>
settings?: Record<string, unknown>
}
getBuiltinPlugins() — Returns { enabled: LoadedPlugin[], disabled: LoadedPlugin[] }. Enabled state priority: user setting > defaultEnabled > true. Plugins with isAvailable() === false are omitted entirely.
All exports:
| Export | Description |
|---|---|
BUILTIN_MARKETPLACE_NAME |
'builtin' — sentinel marketplace name |
registerBuiltinPlugin(definition) |
Register at startup |
isBuiltinPluginId(pluginId) |
Check if ends with @builtin |
getBuiltinPluginDefinition(name) |
Get definition by name |
getBuiltinPlugins() |
Get enabled/disabled split |
getBuiltinPluginSkillCommands() |
Get skills from enabled plugins as Commands |
clearBuiltinPlugins() |
Test reset |
6.3 Built-in Plugin Initialization (bundled/index.ts)
export function initBuiltinPlugins(): void {
// No built-in plugins registered yet — scaffolding for migrating
// bundled skills that should be user-toggleable.
}
The file is scaffolding — as of the code snapshot, no built-in plugins have been registered. The infrastructure is complete for migrating bundled skills into the toggleable plugin system.
6.4 Plugin Error Types (types/plugin.ts)
PluginError is a large discriminated union with 20+ error variants:
| Type | Key fields |
|---|---|
path-not-found |
path, component |
git-auth-failed |
gitUrl, authType: 'ssh' | 'https' |
git-timeout |
gitUrl, operation: 'clone' | 'pull' |
network-error |
url, details? |
manifest-parse-error |
manifestPath, parseError |
manifest-validation-error |
manifestPath, validationErrors[] |
plugin-not-found |
pluginId, marketplace |
marketplace-not-found |
marketplace, availableMarketplaces[] |
marketplace-load-failed |
marketplace, reason |
mcp-config-invalid |
serverName, validationError |
mcp-server-suppressed-duplicate |
serverName, duplicateOf |
lsp-config-invalid |
serverName, validationError |
lsp-server-start-failed |
serverName, reason |
lsp-server-crashed |
exitCode, signal? |
lsp-request-timeout |
method, timeoutMs |
lsp-request-failed |
method, error |
marketplace-blocked-by-policy |
marketplace, blockedByBlocklist?, allowedSources[] |
dependency-unsatisfied |
dependency, reason: 'not-enabled' | 'not-found' |
plugin-cache-miss |
installPath |
hook-load-failed |
hookPath, reason |
component-load-failed |
component, path, reason |
mcpb-download-failed |
url, reason |
mcpb-extract-failed |
mcpbPath, reason |
mcpb-invalid-manifest |
mcpbPath, validationError |
generic-error |
error |
getPluginErrorMessage(error) — Returns a human-readable string for any PluginError variant.
PluginLoadResult:
type PluginLoadResult = {
enabled: LoadedPlugin[]
disabled: LoadedPlugin[]
errors: PluginError[]
}
7. Output Styles
7.1 System Overview
Output styles are markdown files that define custom formatting instructions for Claude's responses. Loaded from output-styles/ subdirectories in project .claude/ and user ~/.claude/ directories.
Source: src/outputStyles/loadOutputStylesDir.ts
7.2 Configuration
- Location:
.claude/output-styles/*.md(project) and~/.claude/output-styles/*.md(user) - File naming:
filename.md→ style namefilename - Frontmatter fields:
name: Override the style name (defaults to filename without.md)description: Description shown in output style pickerkeep-coding-instructions: Boolean — whether to preserve coding-specific instructions (true/falseor boolean)force-for-plugin: Only valid for plugin output styles; ignored (with warning) on regular styles
7.3 API
getOutputStyleDirStyles(cwd: string): Promise<OutputStyleConfig[]> — Memoized async loader. Scans output-styles subdirectory across project hierarchy and user config. Returns OutputStyleConfig[].
OutputStyleConfig (from constants/outputStyles.ts):
type OutputStyleConfig = {
name: string
description: string
prompt: string // file content (trimmed)
source: string // setting source (userSettings, localSettings, etc.)
keepCodingInstructions?: boolean
}
clearOutputStyleCaches() — Clears memoized getOutputStyleDirStyles, loadMarkdownFilesForSubdir, and plugin output style caches.
8. Hooks Schema
8.1 System Overview
Hooks execute side effects at specific lifecycle events (PreToolUse, PostToolUse, PostResponse, etc.). The schema defines four hook types in a discriminated union, with conditional execution via if conditions.
Source: src/schemas/hooks.ts
8.2 Hook Types
BashCommandHook (type: 'command')
{
type: 'command'
command: string // Shell command to execute
if?: string // Permission rule syntax filter (e.g., "Bash(git *)")
shell?: 'bash' | 'powershell'
timeout?: number // Seconds
statusMessage?: string // Spinner message
once?: boolean // Run once, then remove
async?: boolean // Non-blocking background execution
asyncRewake?: boolean // Background + wake model on exit code 2
}
PromptHook (type: 'prompt')
{
type: 'prompt'
prompt: string // LLM prompt (use $ARGUMENTS for hook input JSON)
if?: string
timeout?: number
model?: string // Default: small fast model
statusMessage?: string
once?: boolean
}
HttpHook (type: 'http')
{
type: 'http'
url: string // POST destination
if?: string
timeout?: number
headers?: Record<string, string> // May use $VAR_NAME interpolation
allowedEnvVars?: string[] // Explicit allowlist for env var interpolation
statusMessage?: string
once?: boolean
}
AgentHook (type: 'agent')
{
type: 'agent'
prompt: string // Verification prompt (use $ARGUMENTS for hook input JSON)
if?: string
timeout?: number // Default: 60 seconds
model?: string // Default: Haiku
statusMessage?: string
once?: boolean
}
Note on AgentHook: Must NOT use .transform() in the Zod schema — the schema is used in parseSettingsFile, and round-tripping a transformed function through JSON.stringify silently drops the prompt field.
8.3 Hook Matcher and Settings Structure
type HookMatcher = {
matcher?: string // e.g., "Write", "Bash(git *)"
hooks: HookCommand[]
}
type HooksSettings = Partial<Record<HookEvent, HookMatcher[]>>
IfConditionSchema: Shared if field uses permission rule syntax (Bash(git *), Read(*.ts)) to filter hook execution before spawning. Evaluated against tool_name and tool_input.
8.4 Exports
| Export | Description |
|---|---|
HookCommandSchema |
Discriminated union of all 4 hook command types |
HookMatcherSchema |
{ matcher?, hooks[] } |
HooksSchema |
Partial<Record<HookEvent, HookMatcher[]>> |
HookCommand |
Inferred type from schema |
BashCommandHook |
Extract<HookCommand, { type: 'command' }> |
PromptHook |
Extract<HookCommand, { type: 'prompt' }> |
AgentHook |
Extract<HookCommand, { type: 'agent' }> |
HttpHook |
Extract<HookCommand, { type: 'http' }> |
HookMatcher |
Inferred from matcher schema |
HooksSettings |
Partial<Record<HookEvent, HookMatcher[]>> |
9. Native-TypeScript Ports (native-ts/)
The native-ts/ directory contains pure-TypeScript ports of Rust NAPI native modules. These are used when the native modules cannot be loaded (platforms without precompiled binaries, etc.).
9.1 Color Diff (native-ts/color-diff/index.ts)
Purpose: Port of vendor/color-diff-src — syntax-highlighted word-level diff rendering for the diff view.
Implementation: Uses highlight.js (lazy-loaded to defer ~50MB grammar registration) and the diff npm package's diffArrays.
Semantic differences from native:
- Syntax highlighting via highlight.js instead of syntect/bat
- Scope colors approximate syntect output, but plain identifiers and
=:operators render in default fg (no scope in hljs grammar) BAT_THEMEenv is a stub (always returns default theme)- Output structure (line numbers, markers, backgrounds, word-diff) is identical
Lazy loading pattern:
let cachedHljs: HLJSApi | null = null
function hljs(): HLJSApi {
if (cachedHljs) return cachedHljs
const mod = require('highlight.js')
// Handles both ESM default export and CJS module-is-API interop
cachedHljs = 'default' in mod && mod.default ? mod.default : mod
return cachedHljs!
}
Key types:
type Hunk = {
oldStart: number
oldLines: number
newStart: number
newLines: number
lines: string[]
}
type SyntaxTheme = { ... }
9.2 File Index (native-ts/file-index/index.ts)
Purpose: Port of vendor/file-index-src — high-performance fuzzy file path search, replacing nucleo (Rust).
Algorithm: Approximates fzf-v2/nucleo scoring with:
- Bitmap reject: paths missing any needle letter rejected in O(1)
- Greedy-earliest position scan via
indexOf(SIMD-accelerated in JSC/V8) - Scoring constants:
| Constant | Value | Description |
|---|---|---|
SCORE_MATCH |
16 | Base score per matched char |
BONUS_BOUNDARY |
8 | Bonus for path boundary match (/ \ - _ . space) |
BONUS_CAMEL |
6 | Bonus for camelCase boundary |
BONUS_CONSECUTIVE |
4 | Bonus for consecutive matches |
BONUS_FIRST_CHAR |
8 | Bonus for matching first char |
PENALTY_GAP_START |
3 | Penalty for starting a gap |
PENALTY_GAP_EXTENSION |
1 | Penalty per gap character |
Test file penalty: Paths containing "test" get a 1.05× score penalty (capped at 1.0).
Score semantics: Lower = better. score = position_in_results / result_count, so best match is 0.0.
Smart case: Lowercase query → case-insensitive; any uppercase → case-sensitive.
FileIndex class:
class FileIndex {
loadFromFileList(fileList: string[]): void
loadFromFileListAsync(fileList: string[]): { queryable: Promise<void>; done: Promise<void> }
search(query: string, limit: number): SearchResult[]
}
type SearchResult = {
path: string
score: number
}
Async loading: loadFromFileListAsync yields to event loop every CHUNK_MS = 4ms of sync work. Resolves queryable after first chunk (partial results immediately searchable). For a 270k-path list: ~5–10ms to first queryable state.
Top-level cache: Stores 100 most-common top-level path segments for empty-query results.
TOP_LEVEL_CACHE_LIMIT = 100, MAX_QUERY_LEN = 64
9.3 Yoga Layout (native-ts/yoga-layout/)
Purpose: Pure-TypeScript port of Meta's Yoga flexbox engine (yoga-layout/load), used by Ink's layout engine.
enums.ts — Yoga constants as const objects:
| Enum | Values |
|---|---|
Align |
Auto(0), FlexStart(1), Center(2), FlexEnd(3), Stretch(4), Baseline(5), SpaceBetween(6), SpaceAround(7), SpaceEvenly(8) |
BoxSizing |
BorderBox(0), ContentBox(1) |
Dimension |
Width(0), Height(1) |
Direction |
Inherit(0), LTR(1), RTL(2) |
Display |
Flex(0), None(1), Contents(2) |
Edge |
Left(0), Top(1), Right(2), Bottom(3), ... |
Errata |
None(0), StretchFlexBasis(1), All(2147483647) |
ExperimentalFeature |
WebFlexBasis(0) |
FlexDirection |
Column(0), ColumnReverse(1), Row(2), RowReverse(3) |
Gutter |
Column(0), Row(1), All(2) |
Justify |
FlexStart(0), Center(1), FlexEnd(2), SpaceBetween(3), SpaceAround(4), SpaceEvenly(5) |
MeasureMode |
Undefined(0), Exactly(1), AtMost(2) |
NodeType |
Default(0), Text(1) |
Overflow |
Visible(0), Hidden(1), Scroll(2) |
PositionType |
Static(0), Relative(1), Absolute(2) |
Unit |
Undefined(0), Point(1), Percent(2), Auto(3) |
Wrap |
NoWrap(0), Wrap(1), WrapReverse(2) |
index.ts — Subset of Yoga implemented:
- flex-direction (row/column + reverse)
- flex-grow / flex-shrink / flex-basis
- align-items / align-self (all except baseline in practice)
- justify-content (all 6 values)
- margin / padding / border / gap
- width / height / min / max (point, percent, auto)
- position: relative / absolute
- display: flex / none / contents
- measure functions (text nodes)
- margin: auto
- flex-wrap + align-content
- baseline alignment (spec parity, not used by Ink)
Not implemented: aspect-ratio, box-sizing: content-box, RTL direction
10. MoreRight Hook (moreright/)
10.1 System Overview
useMoreRight is a React hook that exists as a stub for external builds. The real implementation is internal-only (ant users). The stub returns no-op implementations of the hook interface.
Source: src/moreright/useMoreRight.tsx
10.2 Interface
export function useMoreRight(_args: {
enabled: boolean
setMessages: (action: M[] | ((prev: M[]) => M[])) => void
inputValue: string
setInputValue: (s: string) => void
setToolJSX: (args: M) => void
}): {
onBeforeQuery: (input: string, all: M[], n: number) => Promise<boolean>
onTurnComplete: (all: M[], aborted: boolean) => Promise<void>
render: () => null
}
Stub behavior:
onBeforeQueryalways returnstrue(allow query)onTurnCompleteis a no-oprenderreturnsnull
The real internal implementation provides pre-query preprocessing and post-turn processing functionality whose details are redacted from the external build.
11. Migrations
Migrations run at startup to update stored config/settings to current formats. All migrations are idempotent (safe to re-run).
11.1 Migration Overview
| Migration | Source | Target | Condition |
|---|---|---|---|
migrateAutoUpdatesToSettings |
globalConfig.autoUpdates: false |
userSettings.env.DISABLE_AUTOUPDATER: '1' |
Only when user-preference disabled (not native protection) |
migrateBypassPermissionsAcceptedToSettings |
globalConfig.bypassPermissionsModeAccepted |
userSettings.skipDangerousModePermissionPrompt: true |
globalConfig field present |
migrateEnableAllProjectMcpServersToSettings |
projectConfig.enableAllProjectMcpServers etc. |
localSettings |
Any of the 3 fields present |
migrateFennecToOpus |
userSettings.model fennec aliases |
Opus 4.6 aliases | ant users only |
migrateLegacyOpusToCurrent |
Explicit Opus 4.0/4.1 model strings | 'opus' alias |
1P provider + legacy remap enabled |
migrateOpusToOpus1m |
userSettings.model: 'opus' |
'opus[1m]' |
isOpus1mMergeEnabled() |
migrateReplBridgeEnabledToRemoteControlAtStartup |
globalConfig.replBridgeEnabled |
globalConfig.remoteControlAtStartup |
Old key exists, new key absent |
migrateSonnet1mToSonnet45 |
userSettings.model: 'sonnet[1m]' |
'sonnet-4-5-20250929[1m]' |
Once (sonnet1m45MigrationComplete flag) |
migrateSonnet45ToSonnet46 |
Explicit Sonnet 4.5 strings | 'sonnet' or 'sonnet[1m]' |
1P + Pro/Max/TeamPremium |
resetAutoModeOptInForDefaultOffer |
userSettings.skipAutoPermissionPrompt |
Clear the setting | TRANSCRIPT_CLASSIFIER + specific conditions |
resetProToOpusDefault |
Default model settings | Sets migration timestamp | 1P Pro users |
11.2 Model Naming Evolution (revealed by migrations)
The migration history reveals the complete model codename/alias evolution:
Opus lineage:
fennec-latest— internal codename for early Opus 4.xclaude-opus-4-0/claude-opus-4-20250514— Opus 4.0 releaseclaude-opus-4-1/claude-opus-4-1-20250805— Opus 4.1 releaseopus-4-5-fast— Opus 4.5 fast variantopus— Current Opus 4.6 aliasopus[1m]— Opus 4.6 with 1M context window
Sonnet lineage:
sonnet[1m]— Early Sonnet with 1M context (targeted Sonnet 4.5)claude-sonnet-4-5-20250929/sonnet-4-5-20250929— Sonnet 4.5 explicitclaude-sonnet-4-5-20250929[1m]/sonnet-4-5-20250929[1m]— Sonnet 4.5 with 1Msonnet— Current Sonnet 4.6 aliassonnet[1m]— Sonnet 4.6 with 1M context (re-aliased)
Internal codenames:
fennec-latest→ Opus 4.x series (pre-release internal name)fennec-fast-latest→ Opus fast variant
11.3 Migration Details
migrateAutoUpdatesToSettings
- Only migrates when
globalConfig.autoUpdates === falseANDautoUpdatesProtectedForNative !== true - Adds
DISABLE_AUTOUPDATER: '1'touserSettings.env - Sets
process.env.DISABLE_AUTOUPDATER = '1'immediately - Removes
autoUpdatesandautoUpdatesProtectedForNativefrom globalConfig
migrateFennecToOpus (ant-only)
Alias mapping:
fennec-latest[1m]→opus[1m]fennec-latest→opusfennec-fast-latest/opus-4-5-fast→opus[1m]+fastMode: true
migrateLegacyOpusToCurrent (1P only)
Migrates: claude-opus-4-20250514, claude-opus-4-1-20250805, claude-opus-4-0, claude-opus-4-1 → 'opus'
Sets legacyOpusMigrationTimestamp for one-time notification.
migrateOpusToOpus1m
Migrates 'opus' → 'opus[1m]' for eligible Max/Team Premium 1P users.
Idempotent: only acts when userSettings.model === 'opus' exactly.
If the migrated value equals the current default, clears model setting instead.
migrateSonnet1mToSonnet45
Run once (tracked by sonnet1m45MigrationComplete in globalConfig).
Also migrates the in-memory mainLoopModelOverride if set.
migrateSonnet45ToSonnet46
Migrates:
claude-sonnet-4-5-20250929/sonnet-4-5-20250929→'sonnet'claude-sonnet-4-5-20250929[1m]/sonnet-4-5-20250929[1m]→'sonnet[1m]'Setssonnet45To46MigrationTimestampfor notification (skipped for new users withnumStartups <= 1).
migrateReplBridgeEnabledToRemoteControlAtStartup
Renames replBridgeEnabled (internal implementation detail) to remoteControlAtStartup (user-facing key). Only acts when old key exists and new key is absent.
resetAutoModeOptInForDefaultOffer
One-shot (guarded by hasResetAutoModeOptInForDefaultOffer flag).
Clears skipAutoPermissionPrompt for users who accepted the old 2-option dialog but don't have auto as their default mode. Re-surfaces the new dialog with the "make it my default" option.
Only runs when getAutoModeEnabledState() === 'enabled' (not 'opt-in').
resetProToOpusDefault
Sets migration timestamp for Pro subscribers on 1P with no custom model setting (UI notification that Opus is now the default). For all other users, simply marks migration complete.
12. Core Type Definitions (types/)
12.1 Branded ID Types (types/ids.ts)
Prevents mixing SessionId and AgentId at compile time:
type SessionId = string & { readonly __brand: 'SessionId' }
type AgentId = string & { readonly __brand: 'AgentId' }
AgentId format: a + optional <label>- + 16 hex chars
Pattern: /^a(?:.+-)?[0-9a-f]{16}$/
| Export | Description |
|---|---|
SessionId |
Branded session ID type |
AgentId |
Branded agent ID type |
asSessionId(id) |
Cast string to SessionId |
asAgentId(id) |
Cast string to AgentId |
toAgentId(s) |
Validate + brand as AgentId, or null |
12.2 Command Types (types/command.ts)
PromptCommand — Skill/command that generates a prompt:
type PromptCommand = {
type: 'prompt'
progressMessage: string
contentLength: number
argNames?: string[]
allowedTools?: string[]
model?: string
source: SettingSource | 'builtin' | 'mcp' | 'plugin' | 'bundled'
pluginInfo?: { pluginManifest: PluginManifest; repository: string }
disableNonInteractive?: boolean
hooks?: HooksSettings
skillRoot?: string
context?: 'inline' | 'fork'
agent?: string
effort?: EffortValue
paths?: string[] // glob patterns — skill only visible after model touches matching files
getPromptForCommand(args, context): Promise<ContentBlockParam[]>
}
Execution contexts:
'inline'(default): Skill content expands into current conversation'fork': Skill runs as sub-agent with separate context and token budget
12.3 Plugin Types (types/plugin.ts)
See Section 6.4 for LoadedPlugin, BuiltinPluginDefinition, PluginError, and PluginLoadResult.
13. Remote Session System (remote/)
13.1 System Overview
The remote session system manages multi-user and remote-connected Claude Code sessions, bridging between the local CLI process and remote session infrastructure.
Source: src/remote/
Files:
SessionsWebSocket.ts— WebSocket client for server-sent session eventsRemoteSessionManager.ts— Session state management and reconnection logicremotePermissionBridge.ts— Permission request routing for remote sessionssdkMessageAdapter.ts— Adapts SDK message format to internal message format
13.2 Component Roles
SessionsWebSocket — Maintains a WebSocket connection to the remote session server. Handles:
- Reconnection with exponential backoff
- Session event routing
- Auth token refresh integration
RemoteSessionManager — Top-level orchestrator for remote sessions:
- Manages session lifecycle (create, attach, detach, resume)
- Tracks active remote sessions
- Routes permission requests to appropriate sessions
- Handles session backgrounding/foregrounding
remotePermissionBridge — Bridges permission requests from remote sessions back to the local UI:
- Permission requests originating in remote worker sessions are forwarded to the interactive CLI
- Responses are sent back to the worker via the session infrastructure
sdkMessageAdapter — Converts between SDK wire-format messages and the internal Message type used by the UI layer.
End of spec document.