yzcode-cli 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/assistant/sessionHistory.ts +87 -0
  2. package/bootstrap/state.ts +1769 -0
  3. package/bridge/bridgeApi.ts +539 -0
  4. package/bridge/bridgeConfig.ts +48 -0
  5. package/bridge/bridgeDebug.ts +135 -0
  6. package/bridge/bridgeEnabled.ts +202 -0
  7. package/bridge/bridgeMain.ts +2999 -0
  8. package/bridge/bridgeMessaging.ts +461 -0
  9. package/bridge/bridgePermissionCallbacks.ts +43 -0
  10. package/bridge/bridgePointer.ts +210 -0
  11. package/bridge/bridgeStatusUtil.ts +163 -0
  12. package/bridge/bridgeUI.ts +530 -0
  13. package/bridge/capacityWake.ts +56 -0
  14. package/bridge/codeSessionApi.ts +168 -0
  15. package/bridge/createSession.ts +384 -0
  16. package/bridge/debugUtils.ts +141 -0
  17. package/bridge/envLessBridgeConfig.ts +165 -0
  18. package/bridge/flushGate.ts +71 -0
  19. package/bridge/inboundAttachments.ts +175 -0
  20. package/bridge/inboundMessages.ts +80 -0
  21. package/bridge/initReplBridge.ts +569 -0
  22. package/bridge/jwtUtils.ts +256 -0
  23. package/bridge/pollConfig.ts +110 -0
  24. package/bridge/pollConfigDefaults.ts +82 -0
  25. package/bridge/remoteBridgeCore.ts +1008 -0
  26. package/bridge/replBridge.ts +2406 -0
  27. package/bridge/replBridgeHandle.ts +36 -0
  28. package/bridge/replBridgeTransport.ts +370 -0
  29. package/bridge/sessionIdCompat.ts +57 -0
  30. package/bridge/sessionRunner.ts +550 -0
  31. package/bridge/trustedDevice.ts +210 -0
  32. package/bridge/types.ts +262 -0
  33. package/bridge/workSecret.ts +127 -0
  34. package/buddy/CompanionSprite.tsx +371 -0
  35. package/buddy/companion.ts +133 -0
  36. package/buddy/prompt.ts +36 -0
  37. package/buddy/sprites.ts +514 -0
  38. package/buddy/types.ts +148 -0
  39. package/buddy/useBuddyNotification.tsx +98 -0
  40. package/coordinator/coordinatorMode.ts +369 -0
  41. package/memdir/findRelevantMemories.ts +141 -0
  42. package/memdir/memdir.ts +507 -0
  43. package/memdir/memoryAge.ts +53 -0
  44. package/memdir/memoryScan.ts +94 -0
  45. package/memdir/memoryTypes.ts +271 -0
  46. package/memdir/paths.ts +278 -0
  47. package/memdir/teamMemPaths.ts +292 -0
  48. package/memdir/teamMemPrompts.ts +100 -0
  49. package/migrations/migrateAutoUpdatesToSettings.ts +61 -0
  50. package/migrations/migrateBypassPermissionsAcceptedToSettings.ts +40 -0
  51. package/migrations/migrateEnableAllProjectMcpServersToSettings.ts +118 -0
  52. package/migrations/migrateFennecToOpus.ts +45 -0
  53. package/migrations/migrateLegacyOpusToCurrent.ts +57 -0
  54. package/migrations/migrateOpusToOpus1m.ts +43 -0
  55. package/migrations/migrateReplBridgeEnabledToRemoteControlAtStartup.ts +22 -0
  56. package/migrations/migrateSonnet1mToSonnet45.ts +48 -0
  57. package/migrations/migrateSonnet45ToSonnet46.ts +67 -0
  58. package/migrations/resetAutoModeOptInForDefaultOffer.ts +51 -0
  59. package/migrations/resetProToOpusDefault.ts +51 -0
  60. package/native-ts/color-diff/index.ts +999 -0
  61. package/native-ts/file-index/index.ts +370 -0
  62. package/native-ts/yoga-layout/enums.ts +134 -0
  63. package/native-ts/yoga-layout/index.ts +2578 -0
  64. package/outputStyles/loadOutputStylesDir.ts +98 -0
  65. package/package.json +19 -2
  66. package/plugins/builtinPlugins.ts +159 -0
  67. package/plugins/bundled/index.ts +23 -0
  68. package/schemas/hooks.ts +222 -0
  69. package/screens/Doctor.tsx +575 -0
  70. package/screens/REPL.tsx +5006 -0
  71. package/screens/ResumeConversation.tsx +399 -0
  72. package/server/createDirectConnectSession.ts +88 -0
  73. package/server/directConnectManager.ts +213 -0
  74. package/server/types.ts +57 -0
  75. package/skills/bundled/batch.ts +124 -0
  76. package/skills/bundled/claudeApi.ts +196 -0
  77. package/skills/bundled/claudeApiContent.ts +75 -0
  78. package/skills/bundled/claudeInChrome.ts +34 -0
  79. package/skills/bundled/debug.ts +103 -0
  80. package/skills/bundled/index.ts +79 -0
  81. package/skills/bundled/keybindings.ts +339 -0
  82. package/skills/bundled/loop.ts +92 -0
  83. package/skills/bundled/loremIpsum.ts +282 -0
  84. package/skills/bundled/remember.ts +82 -0
  85. package/skills/bundled/scheduleRemoteAgents.ts +447 -0
  86. package/skills/bundled/simplify.ts +69 -0
  87. package/skills/bundled/skillify.ts +197 -0
  88. package/skills/bundled/stuck.ts +79 -0
  89. package/skills/bundled/updateConfig.ts +475 -0
  90. package/skills/bundled/verify/SKILL.md +3 -0
  91. package/skills/bundled/verify/examples/cli.md +3 -0
  92. package/skills/bundled/verify/examples/server.md +3 -0
  93. package/skills/bundled/verify.ts +30 -0
  94. package/skills/bundled/verifyContent.ts +13 -0
  95. package/skills/bundledSkills.ts +220 -0
  96. package/skills/loadSkillsDir.ts +1086 -0
  97. package/skills/mcpSkillBuilders.ts +44 -0
  98. package/tasks/DreamTask/DreamTask.ts +157 -0
  99. package/tasks/InProcessTeammateTask/InProcessTeammateTask.tsx +126 -0
  100. package/tasks/InProcessTeammateTask/types.ts +121 -0
  101. package/tasks/LocalAgentTask/LocalAgentTask.tsx +683 -0
  102. package/tasks/LocalMainSessionTask.ts +479 -0
  103. package/tasks/LocalShellTask/LocalShellTask.tsx +523 -0
  104. package/tasks/LocalShellTask/guards.ts +41 -0
  105. package/tasks/LocalShellTask/killShellTasks.ts +76 -0
  106. package/tasks/RemoteAgentTask/RemoteAgentTask.tsx +856 -0
  107. package/tasks/pillLabel.ts +82 -0
  108. package/tasks/stopTask.ts +100 -0
  109. package/tasks/types.ts +46 -0
  110. package/upstreamproxy/relay.ts +455 -0
  111. package/upstreamproxy/upstreamproxy.ts +285 -0
  112. package/vim/motions.ts +82 -0
  113. package/vim/operators.ts +556 -0
  114. package/vim/textObjects.ts +186 -0
  115. package/vim/transitions.ts +490 -0
  116. package/vim/types.ts +199 -0
  117. package/voice/voiceModeEnabled.ts +54 -0
@@ -0,0 +1,213 @@
1
+ /* eslint-disable eslint-plugin-n/no-unsupported-features/node-builtins */
2
+
3
+ import type { SDKMessage } from '../entrypoints/agentSdkTypes.js'
4
+ import type {
5
+ SDKControlPermissionRequest,
6
+ StdoutMessage,
7
+ } from '../entrypoints/sdk/controlTypes.js'
8
+ import type { RemotePermissionResponse } from '../remote/RemoteSessionManager.js'
9
+ import { logForDebugging } from '../utils/debug.js'
10
+ import { jsonParse, jsonStringify } from '../utils/slowOperations.js'
11
+ import type { RemoteMessageContent } from '../utils/teleport/api.js'
12
+
13
+ export type DirectConnectConfig = {
14
+ serverUrl: string
15
+ sessionId: string
16
+ wsUrl: string
17
+ authToken?: string
18
+ }
19
+
20
+ export type DirectConnectCallbacks = {
21
+ onMessage: (message: SDKMessage) => void
22
+ onPermissionRequest: (
23
+ request: SDKControlPermissionRequest,
24
+ requestId: string,
25
+ ) => void
26
+ onConnected?: () => void
27
+ onDisconnected?: () => void
28
+ onError?: (error: Error) => void
29
+ }
30
+
31
+ function isStdoutMessage(value: unknown): value is StdoutMessage {
32
+ return (
33
+ typeof value === 'object' &&
34
+ value !== null &&
35
+ 'type' in value &&
36
+ typeof value.type === 'string'
37
+ )
38
+ }
39
+
40
+ export class DirectConnectSessionManager {
41
+ private ws: WebSocket | null = null
42
+ private config: DirectConnectConfig
43
+ private callbacks: DirectConnectCallbacks
44
+
45
+ constructor(config: DirectConnectConfig, callbacks: DirectConnectCallbacks) {
46
+ this.config = config
47
+ this.callbacks = callbacks
48
+ }
49
+
50
+ connect(): void {
51
+ const headers: Record<string, string> = {}
52
+ if (this.config.authToken) {
53
+ headers['authorization'] = `Bearer ${this.config.authToken}`
54
+ }
55
+ // Bun's WebSocket supports headers option but the DOM typings don't
56
+ this.ws = new WebSocket(this.config.wsUrl, {
57
+ headers,
58
+ } as unknown as string[])
59
+
60
+ this.ws.addEventListener('open', () => {
61
+ this.callbacks.onConnected?.()
62
+ })
63
+
64
+ this.ws.addEventListener('message', event => {
65
+ const data = typeof event.data === 'string' ? event.data : ''
66
+ const lines = data.split('\n').filter((l: string) => l.trim())
67
+
68
+ for (const line of lines) {
69
+ let raw: unknown
70
+ try {
71
+ raw = jsonParse(line)
72
+ } catch {
73
+ continue
74
+ }
75
+
76
+ if (!isStdoutMessage(raw)) {
77
+ continue
78
+ }
79
+ const parsed = raw
80
+
81
+ // Handle control requests (permission requests)
82
+ if (parsed.type === 'control_request') {
83
+ if (parsed.request.subtype === 'can_use_tool') {
84
+ this.callbacks.onPermissionRequest(
85
+ parsed.request,
86
+ parsed.request_id,
87
+ )
88
+ } else {
89
+ // Send an error response for unrecognized subtypes so the
90
+ // server doesn't hang waiting for a reply that never comes.
91
+ logForDebugging(
92
+ `[DirectConnect] Unsupported control request subtype: ${parsed.request.subtype}`,
93
+ )
94
+ this.sendErrorResponse(
95
+ parsed.request_id,
96
+ `Unsupported control request subtype: ${parsed.request.subtype}`,
97
+ )
98
+ }
99
+ continue
100
+ }
101
+
102
+ // Forward SDK messages (assistant, result, system, etc.)
103
+ if (
104
+ parsed.type !== 'control_response' &&
105
+ parsed.type !== 'keep_alive' &&
106
+ parsed.type !== 'control_cancel_request' &&
107
+ parsed.type !== 'streamlined_text' &&
108
+ parsed.type !== 'streamlined_tool_use_summary' &&
109
+ !(parsed.type === 'system' && parsed.subtype === 'post_turn_summary')
110
+ ) {
111
+ this.callbacks.onMessage(parsed)
112
+ }
113
+ }
114
+ })
115
+
116
+ this.ws.addEventListener('close', () => {
117
+ this.callbacks.onDisconnected?.()
118
+ })
119
+
120
+ this.ws.addEventListener('error', () => {
121
+ this.callbacks.onError?.(new Error('WebSocket connection error'))
122
+ })
123
+ }
124
+
125
+ sendMessage(content: RemoteMessageContent): boolean {
126
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
127
+ return false
128
+ }
129
+
130
+ // Must match SDKUserMessage format expected by `--input-format stream-json`
131
+ const message = jsonStringify({
132
+ type: 'user',
133
+ message: {
134
+ role: 'user',
135
+ content: content,
136
+ },
137
+ parent_tool_use_id: null,
138
+ session_id: '',
139
+ })
140
+ this.ws.send(message)
141
+ return true
142
+ }
143
+
144
+ respondToPermissionRequest(
145
+ requestId: string,
146
+ result: RemotePermissionResponse,
147
+ ): void {
148
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
149
+ return
150
+ }
151
+
152
+ // Must match SDKControlResponse format expected by StructuredIO
153
+ const response = jsonStringify({
154
+ type: 'control_response',
155
+ response: {
156
+ subtype: 'success',
157
+ request_id: requestId,
158
+ response: {
159
+ behavior: result.behavior,
160
+ ...(result.behavior === 'allow'
161
+ ? { updatedInput: result.updatedInput }
162
+ : { message: result.message }),
163
+ },
164
+ },
165
+ })
166
+ this.ws.send(response)
167
+ }
168
+
169
+ /**
170
+ * Send an interrupt signal to cancel the current request
171
+ */
172
+ sendInterrupt(): void {
173
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
174
+ return
175
+ }
176
+
177
+ // Must match SDKControlRequest format expected by StructuredIO
178
+ const request = jsonStringify({
179
+ type: 'control_request',
180
+ request_id: crypto.randomUUID(),
181
+ request: {
182
+ subtype: 'interrupt',
183
+ },
184
+ })
185
+ this.ws.send(request)
186
+ }
187
+
188
+ private sendErrorResponse(requestId: string, error: string): void {
189
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
190
+ return
191
+ }
192
+ const response = jsonStringify({
193
+ type: 'control_response',
194
+ response: {
195
+ subtype: 'error',
196
+ request_id: requestId,
197
+ error,
198
+ },
199
+ })
200
+ this.ws.send(response)
201
+ }
202
+
203
+ disconnect(): void {
204
+ if (this.ws) {
205
+ this.ws.close()
206
+ this.ws = null
207
+ }
208
+ }
209
+
210
+ isConnected(): boolean {
211
+ return this.ws?.readyState === WebSocket.OPEN
212
+ }
213
+ }
@@ -0,0 +1,57 @@
1
+ import type { ChildProcess } from 'child_process'
2
+ import { z } from 'zod/v4'
3
+ import { lazySchema } from '../utils/lazySchema.js'
4
+
5
+ export const connectResponseSchema = lazySchema(() =>
6
+ z.object({
7
+ session_id: z.string(),
8
+ ws_url: z.string(),
9
+ work_dir: z.string().optional(),
10
+ }),
11
+ )
12
+
13
+ export type ServerConfig = {
14
+ port: number
15
+ host: string
16
+ authToken: string
17
+ unix?: string
18
+ /** Idle timeout for detached sessions (ms). 0 = never expire. */
19
+ idleTimeoutMs?: number
20
+ /** Maximum number of concurrent sessions. */
21
+ maxSessions?: number
22
+ /** Default workspace directory for sessions that don't specify cwd. */
23
+ workspace?: string
24
+ }
25
+
26
+ export type SessionState =
27
+ | 'starting'
28
+ | 'running'
29
+ | 'detached'
30
+ | 'stopping'
31
+ | 'stopped'
32
+
33
+ export type SessionInfo = {
34
+ id: string
35
+ status: SessionState
36
+ createdAt: number
37
+ workDir: string
38
+ process: ChildProcess | null
39
+ sessionKey?: string
40
+ }
41
+
42
+ /**
43
+ * Stable session key → session metadata. Persisted to ~/.claude/server-sessions.json
44
+ * so sessions can be resumed across server restarts.
45
+ */
46
+ export type SessionIndexEntry = {
47
+ /** Server-assigned session ID (matches the subprocess's claude session). */
48
+ sessionId: string
49
+ /** The claude transcript session ID for --resume. Same as sessionId for direct sessions. */
50
+ transcriptSessionId: string
51
+ cwd: string
52
+ permissionMode?: string
53
+ createdAt: number
54
+ lastActiveAt: number
55
+ }
56
+
57
+ export type SessionIndex = Record<string, SessionIndexEntry>
@@ -0,0 +1,124 @@
1
+ import { AGENT_TOOL_NAME } from '../../tools/AgentTool/constants.js'
2
+ import { ASK_USER_QUESTION_TOOL_NAME } from '../../tools/AskUserQuestionTool/prompt.js'
3
+ import { ENTER_PLAN_MODE_TOOL_NAME } from '../../tools/EnterPlanModeTool/constants.js'
4
+ import { EXIT_PLAN_MODE_TOOL_NAME } from '../../tools/ExitPlanModeTool/constants.js'
5
+ import { SKILL_TOOL_NAME } from '../../tools/SkillTool/constants.js'
6
+ import { getIsGit } from '../../utils/git.js'
7
+ import { registerBundledSkill } from '../bundledSkills.js'
8
+
9
+ const MIN_AGENTS = 5
10
+ const MAX_AGENTS = 30
11
+
12
+ const WORKER_INSTRUCTIONS = `After you finish implementing the change:
13
+ 1. **Simplify** — Invoke the \`${SKILL_TOOL_NAME}\` tool with \`skill: "simplify"\` to review and clean up your changes.
14
+ 2. **Run unit tests** — Run the project's test suite (check for package.json scripts, Makefile targets, or common commands like \`npm test\`, \`bun test\`, \`pytest\`, \`go test\`). If tests fail, fix them.
15
+ 3. **Test end-to-end** — Follow the e2e test recipe from the coordinator's prompt (below). If the recipe says to skip e2e for this unit, skip it.
16
+ 4. **Commit and push** — Commit all changes with a clear message, push the branch, and create a PR with \`gh pr create\`. Use a descriptive title. If \`gh\` is not available or the push fails, note it in your final message.
17
+ 5. **Report** — End with a single line: \`PR: <url>\` so the coordinator can track it. If no PR was created, end with \`PR: none — <reason>\`.`
18
+
19
+ function buildPrompt(instruction: string): string {
20
+ return `# Batch: Parallel Work Orchestration
21
+
22
+ You are orchestrating a large, parallelizable change across this codebase.
23
+
24
+ ## User Instruction
25
+
26
+ ${instruction}
27
+
28
+ ## Phase 1: Research and Plan (Plan Mode)
29
+
30
+ Call the \`${ENTER_PLAN_MODE_TOOL_NAME}\` tool now to enter plan mode, then:
31
+
32
+ 1. **Understand the scope.** Launch one or more subagents (in the foreground — you need their results) to deeply research what this instruction touches. Find all the files, patterns, and call sites that need to change. Understand the existing conventions so the migration is consistent.
33
+
34
+ 2. **Decompose into independent units.** Break the work into ${MIN_AGENTS}–${MAX_AGENTS} self-contained units. Each unit must:
35
+ - Be independently implementable in an isolated git worktree (no shared state with sibling units)
36
+ - Be mergeable on its own without depending on another unit's PR landing first
37
+ - Be roughly uniform in size (split large units, merge trivial ones)
38
+
39
+ Scale the count to the actual work: few files → closer to ${MIN_AGENTS}; hundreds of files → closer to ${MAX_AGENTS}. Prefer per-directory or per-module slicing over arbitrary file lists.
40
+
41
+ 3. **Determine the e2e test recipe.** Figure out how a worker can verify its change actually works end-to-end — not just that unit tests pass. Look for:
42
+ - A \`claude-in-chrome\` skill or browser-automation tool (for UI changes: click through the affected flow, screenshot the result)
43
+ - A \`tmux\` or CLI-verifier skill (for CLI changes: launch the app interactively, exercise the changed behavior)
44
+ - A dev-server + curl pattern (for API changes: start the server, hit the affected endpoints)
45
+ - An existing e2e/integration test suite the worker can run
46
+
47
+ If you cannot find a concrete e2e path, use the \`${ASK_USER_QUESTION_TOOL_NAME}\` tool to ask the user how to verify this change end-to-end. Offer 2–3 specific options based on what you found (e.g., "Screenshot via chrome extension", "Run \`bun run dev\` and curl the endpoint", "No e2e — unit tests are sufficient"). Do not skip this — the workers cannot ask the user themselves.
48
+
49
+ Write the recipe as a short, concrete set of steps that a worker can execute autonomously. Include any setup (start a dev server, build first) and the exact command/interaction to verify.
50
+
51
+ 4. **Write the plan.** In your plan file, include:
52
+ - A summary of what you found during research
53
+ - A numbered list of work units — for each: a short title, the list of files/directories it covers, and a one-line description of the change
54
+ - The e2e test recipe (or "skip e2e because …" if the user chose that)
55
+ - The exact worker instructions you will give each agent (the shared template)
56
+
57
+ 5. Call \`${EXIT_PLAN_MODE_TOOL_NAME}\` to present the plan for approval.
58
+
59
+ ## Phase 2: Spawn Workers (After Plan Approval)
60
+
61
+ Once the plan is approved, spawn one background agent per work unit using the \`${AGENT_TOOL_NAME}\` tool. **All agents must use \`isolation: "worktree"\` and \`run_in_background: true\`.** Launch them all in a single message block so they run in parallel.
62
+
63
+ For each agent, the prompt must be fully self-contained. Include:
64
+ - The overall goal (the user's instruction)
65
+ - This unit's specific task (title, file list, change description — copied verbatim from your plan)
66
+ - Any codebase conventions you discovered that the worker needs to follow
67
+ - The e2e test recipe from your plan (or "skip e2e because …")
68
+ - The worker instructions below, copied verbatim:
69
+
70
+ \`\`\`
71
+ ${WORKER_INSTRUCTIONS}
72
+ \`\`\`
73
+
74
+ Use \`subagent_type: "general-purpose"\` unless a more specific agent type fits.
75
+
76
+ ## Phase 3: Track Progress
77
+
78
+ After launching all workers, render an initial status table:
79
+
80
+ | # | Unit | Status | PR |
81
+ |---|------|--------|----|
82
+ | 1 | <title> | running | — |
83
+ | 2 | <title> | running | — |
84
+
85
+ As background-agent completion notifications arrive, parse the \`PR: <url>\` line from each agent's result and re-render the table with updated status (\`done\` / \`failed\`) and PR links. Keep a brief failure note for any agent that did not produce a PR.
86
+
87
+ When all agents have reported, render the final table and a one-line summary (e.g., "22/24 units landed as PRs").
88
+ `
89
+ }
90
+
91
+ const NOT_A_GIT_REPO_MESSAGE = `This is not a git repository. The \`/batch\` command requires a git repo because it spawns agents in isolated git worktrees and creates PRs from each. Initialize a repo first, or run this from inside an existing one.`
92
+
93
+ const MISSING_INSTRUCTION_MESSAGE = `Provide an instruction describing the batch change you want to make.
94
+
95
+ Examples:
96
+ /batch migrate from react to vue
97
+ /batch replace all uses of lodash with native equivalents
98
+ /batch add type annotations to all untyped function parameters`
99
+
100
+ export function registerBatchSkill(): void {
101
+ registerBundledSkill({
102
+ name: 'batch',
103
+ description:
104
+ 'Research and plan a large-scale change, then execute it in parallel across 5–30 isolated worktree agents that each open a PR.',
105
+ whenToUse:
106
+ 'Use when the user wants to make a sweeping, mechanical change across many files (migrations, refactors, bulk renames) that can be decomposed into independent parallel units.',
107
+ argumentHint: '<instruction>',
108
+ userInvocable: true,
109
+ disableModelInvocation: true,
110
+ async getPromptForCommand(args) {
111
+ const instruction = args.trim()
112
+ if (!instruction) {
113
+ return [{ type: 'text', text: MISSING_INSTRUCTION_MESSAGE }]
114
+ }
115
+
116
+ const isGit = await getIsGit()
117
+ if (!isGit) {
118
+ return [{ type: 'text', text: NOT_A_GIT_REPO_MESSAGE }]
119
+ }
120
+
121
+ return [{ type: 'text', text: buildPrompt(instruction) }]
122
+ },
123
+ })
124
+ }
@@ -0,0 +1,196 @@
1
+ import { readdir } from 'fs/promises'
2
+ import { getCwd } from '../../utils/cwd.js'
3
+ import { registerBundledSkill } from '../bundledSkills.js'
4
+
5
+ // claudeApiContent.js bundles 247KB of .md strings. Lazy-load inside
6
+ // getPromptForCommand so they only enter memory when /claude-api is invoked.
7
+ type SkillContent = typeof import('./claudeApiContent.js')
8
+
9
+ type DetectedLanguage =
10
+ | 'python'
11
+ | 'typescript'
12
+ | 'java'
13
+ | 'go'
14
+ | 'ruby'
15
+ | 'csharp'
16
+ | 'php'
17
+ | 'curl'
18
+
19
+ const LANGUAGE_INDICATORS: Record<DetectedLanguage, string[]> = {
20
+ python: ['.py', 'requirements.txt', 'pyproject.toml', 'setup.py', 'Pipfile'],
21
+ typescript: ['.ts', '.tsx', 'tsconfig.json', 'package.json'],
22
+ java: ['.java', 'pom.xml', 'build.gradle'],
23
+ go: ['.go', 'go.mod'],
24
+ ruby: ['.rb', 'Gemfile'],
25
+ csharp: ['.cs', '.csproj'],
26
+ php: ['.php', 'composer.json'],
27
+ curl: [],
28
+ }
29
+
30
+ async function detectLanguage(): Promise<DetectedLanguage | null> {
31
+ const cwd = getCwd()
32
+ let entries: string[]
33
+ try {
34
+ entries = await readdir(cwd)
35
+ } catch {
36
+ return null
37
+ }
38
+
39
+ for (const [lang, indicators] of Object.entries(LANGUAGE_INDICATORS) as [
40
+ DetectedLanguage,
41
+ string[],
42
+ ][]) {
43
+ if (indicators.length === 0) continue
44
+ for (const indicator of indicators) {
45
+ if (indicator.startsWith('.')) {
46
+ if (entries.some(e => e.endsWith(indicator))) return lang
47
+ } else {
48
+ if (entries.includes(indicator)) return lang
49
+ }
50
+ }
51
+ }
52
+ return null
53
+ }
54
+
55
+ function getFilesForLanguage(
56
+ lang: DetectedLanguage,
57
+ content: SkillContent,
58
+ ): string[] {
59
+ return Object.keys(content.SKILL_FILES).filter(
60
+ path => path.startsWith(`${lang}/`) || path.startsWith('shared/'),
61
+ )
62
+ }
63
+
64
+ function processContent(md: string, content: SkillContent): string {
65
+ // Strip HTML comments. Loop to handle nested comments.
66
+ let out = md
67
+ let prev
68
+ do {
69
+ prev = out
70
+ out = out.replace(/<!--[\s\S]*?-->\n?/g, '')
71
+ } while (out !== prev)
72
+
73
+ out = out.replace(
74
+ /\{\{(\w+)\}\}/g,
75
+ (match, key: string) =>
76
+ (content.SKILL_MODEL_VARS as Record<string, string>)[key] ?? match,
77
+ )
78
+ return out
79
+ }
80
+
81
+ function buildInlineReference(
82
+ filePaths: string[],
83
+ content: SkillContent,
84
+ ): string {
85
+ const sections: string[] = []
86
+ for (const filePath of filePaths.sort()) {
87
+ const md = content.SKILL_FILES[filePath]
88
+ if (!md) continue
89
+ sections.push(
90
+ `<doc path="${filePath}">\n${processContent(md, content).trim()}\n</doc>`,
91
+ )
92
+ }
93
+ return sections.join('\n\n')
94
+ }
95
+
96
+ const INLINE_READING_GUIDE = `## Reference Documentation
97
+
98
+ The relevant documentation for your detected language is included below in \`<doc>\` tags. Each tag has a \`path\` attribute showing its original file path. Use this to find the right section:
99
+
100
+ ### Quick Task Reference
101
+
102
+ **Single text classification/summarization/extraction/Q&A:**
103
+ → Refer to \`{lang}/claude-api/README.md\`
104
+
105
+ **Chat UI or real-time response display:**
106
+ → Refer to \`{lang}/claude-api/README.md\` + \`{lang}/claude-api/streaming.md\`
107
+
108
+ **Long-running conversations (may exceed context window):**
109
+ → Refer to \`{lang}/claude-api/README.md\` — see Compaction section
110
+
111
+ **Prompt caching / optimize caching / "why is my cache hit rate low":**
112
+ → Refer to \`shared/prompt-caching.md\` + \`{lang}/claude-api/README.md\` (Prompt Caching section)
113
+
114
+ **Function calling / tool use / agents:**
115
+ → Refer to \`{lang}/claude-api/README.md\` + \`shared/tool-use-concepts.md\` + \`{lang}/claude-api/tool-use.md\`
116
+
117
+ **Batch processing (non-latency-sensitive):**
118
+ → Refer to \`{lang}/claude-api/README.md\` + \`{lang}/claude-api/batches.md\`
119
+
120
+ **File uploads across multiple requests:**
121
+ → Refer to \`{lang}/claude-api/README.md\` + \`{lang}/claude-api/files-api.md\`
122
+
123
+ **Agent with built-in tools (file/web/terminal) (Python & TypeScript only):**
124
+ → Refer to \`{lang}/agent-sdk/README.md\` + \`{lang}/agent-sdk/patterns.md\`
125
+
126
+ **Error handling:**
127
+ → Refer to \`shared/error-codes.md\`
128
+
129
+ **Latest docs via WebFetch:**
130
+ → Refer to \`shared/live-sources.md\` for URLs`
131
+
132
+ function buildPrompt(
133
+ lang: DetectedLanguage | null,
134
+ args: string,
135
+ content: SkillContent,
136
+ ): string {
137
+ // Take the SKILL.md content up to the "Reading Guide" section
138
+ const cleanPrompt = processContent(content.SKILL_PROMPT, content)
139
+ const readingGuideIdx = cleanPrompt.indexOf('## Reading Guide')
140
+ const basePrompt =
141
+ readingGuideIdx !== -1
142
+ ? cleanPrompt.slice(0, readingGuideIdx).trimEnd()
143
+ : cleanPrompt
144
+
145
+ const parts: string[] = [basePrompt]
146
+
147
+ if (lang) {
148
+ const filePaths = getFilesForLanguage(lang, content)
149
+ const readingGuide = INLINE_READING_GUIDE.replace(/\{lang\}/g, lang)
150
+ parts.push(readingGuide)
151
+ parts.push(
152
+ '---\n\n## Included Documentation\n\n' +
153
+ buildInlineReference(filePaths, content),
154
+ )
155
+ } else {
156
+ // No language detected — include all docs and let the model ask
157
+ parts.push(INLINE_READING_GUIDE.replace(/\{lang\}/g, 'unknown'))
158
+ parts.push(
159
+ 'No project language was auto-detected. Ask the user which language they are using, then refer to the matching docs below.',
160
+ )
161
+ parts.push(
162
+ '---\n\n## Included Documentation\n\n' +
163
+ buildInlineReference(Object.keys(content.SKILL_FILES), content),
164
+ )
165
+ }
166
+
167
+ // Preserve the "When to Use WebFetch" and "Common Pitfalls" sections
168
+ const webFetchIdx = cleanPrompt.indexOf('## When to Use WebFetch')
169
+ if (webFetchIdx !== -1) {
170
+ parts.push(cleanPrompt.slice(webFetchIdx).trimEnd())
171
+ }
172
+
173
+ if (args) {
174
+ parts.push(`## User Request\n\n${args}`)
175
+ }
176
+
177
+ return parts.join('\n\n')
178
+ }
179
+
180
+ export function registerClaudeApiSkill(): void {
181
+ registerBundledSkill({
182
+ name: 'claude-api',
183
+ description:
184
+ 'Build apps with the Claude API or Anthropic SDK.\n' +
185
+ 'TRIGGER when: code imports `anthropic`/`@anthropic-ai/sdk`/`claude_agent_sdk`, or user asks to use Claude API, Anthropic SDKs, or Agent SDK.\n' +
186
+ 'DO NOT TRIGGER when: code imports `openai`/other AI SDK, general programming, or ML/data-science tasks.',
187
+ allowedTools: ['Read', 'Grep', 'Glob', 'WebFetch'],
188
+ userInvocable: true,
189
+ async getPromptForCommand(args) {
190
+ const content = await import('./claudeApiContent.js')
191
+ const lang = await detectLanguage()
192
+ const prompt = buildPrompt(lang, args, content)
193
+ return [{ type: 'text', text: prompt }]
194
+ },
195
+ })
196
+ }
@@ -0,0 +1,75 @@
1
+ // Content for the claude-api bundled skill.
2
+ // Each .md file is inlined as a string at build time via Bun's text loader.
3
+
4
+ import csharpClaudeApi from './claude-api/csharp/claude-api.md'
5
+ import curlExamples from './claude-api/curl/examples.md'
6
+ import goClaudeApi from './claude-api/go/claude-api.md'
7
+ import javaClaudeApi from './claude-api/java/claude-api.md'
8
+ import phpClaudeApi from './claude-api/php/claude-api.md'
9
+ import pythonAgentSdkPatterns from './claude-api/python/agent-sdk/patterns.md'
10
+ import pythonAgentSdkReadme from './claude-api/python/agent-sdk/README.md'
11
+ import pythonClaudeApiBatches from './claude-api/python/claude-api/batches.md'
12
+ import pythonClaudeApiFilesApi from './claude-api/python/claude-api/files-api.md'
13
+ import pythonClaudeApiReadme from './claude-api/python/claude-api/README.md'
14
+ import pythonClaudeApiStreaming from './claude-api/python/claude-api/streaming.md'
15
+ import pythonClaudeApiToolUse from './claude-api/python/claude-api/tool-use.md'
16
+ import rubyClaudeApi from './claude-api/ruby/claude-api.md'
17
+ import skillPrompt from './claude-api/SKILL.md'
18
+ import sharedErrorCodes from './claude-api/shared/error-codes.md'
19
+ import sharedLiveSources from './claude-api/shared/live-sources.md'
20
+ import sharedModels from './claude-api/shared/models.md'
21
+ import sharedPromptCaching from './claude-api/shared/prompt-caching.md'
22
+ import sharedToolUseConcepts from './claude-api/shared/tool-use-concepts.md'
23
+ import typescriptAgentSdkPatterns from './claude-api/typescript/agent-sdk/patterns.md'
24
+ import typescriptAgentSdkReadme from './claude-api/typescript/agent-sdk/README.md'
25
+ import typescriptClaudeApiBatches from './claude-api/typescript/claude-api/batches.md'
26
+ import typescriptClaudeApiFilesApi from './claude-api/typescript/claude-api/files-api.md'
27
+ import typescriptClaudeApiReadme from './claude-api/typescript/claude-api/README.md'
28
+ import typescriptClaudeApiStreaming from './claude-api/typescript/claude-api/streaming.md'
29
+ import typescriptClaudeApiToolUse from './claude-api/typescript/claude-api/tool-use.md'
30
+
31
+ // @[MODEL LAUNCH]: Update the model IDs/names below. These are substituted into {{VAR}}
32
+ // placeholders in the .md files at runtime before the skill prompt is sent.
33
+ // After updating these constants, manually update the two files that still hardcode models:
34
+ // - claude-api/SKILL.md (Current Models pricing table)
35
+ // - claude-api/shared/models.md (full model catalog with legacy versions and alias mappings)
36
+ export const SKILL_MODEL_VARS = {
37
+ OPUS_ID: 'claude-opus-4-6',
38
+ OPUS_NAME: 'Claude Opus 4.6',
39
+ SONNET_ID: 'claude-sonnet-4-6',
40
+ SONNET_NAME: 'Claude Sonnet 4.6',
41
+ HAIKU_ID: 'claude-haiku-4-5',
42
+ HAIKU_NAME: 'Claude Haiku 4.5',
43
+ // Previous Sonnet ID — used in "do not append date suffixes" example in SKILL.md.
44
+ PREV_SONNET_ID: 'claude-sonnet-4-5',
45
+ } satisfies Record<string, string>
46
+
47
+ export const SKILL_PROMPT: string = skillPrompt
48
+
49
+ export const SKILL_FILES: Record<string, string> = {
50
+ 'csharp/claude-api.md': csharpClaudeApi,
51
+ 'curl/examples.md': curlExamples,
52
+ 'go/claude-api.md': goClaudeApi,
53
+ 'java/claude-api.md': javaClaudeApi,
54
+ 'php/claude-api.md': phpClaudeApi,
55
+ 'python/agent-sdk/README.md': pythonAgentSdkReadme,
56
+ 'python/agent-sdk/patterns.md': pythonAgentSdkPatterns,
57
+ 'python/claude-api/README.md': pythonClaudeApiReadme,
58
+ 'python/claude-api/batches.md': pythonClaudeApiBatches,
59
+ 'python/claude-api/files-api.md': pythonClaudeApiFilesApi,
60
+ 'python/claude-api/streaming.md': pythonClaudeApiStreaming,
61
+ 'python/claude-api/tool-use.md': pythonClaudeApiToolUse,
62
+ 'ruby/claude-api.md': rubyClaudeApi,
63
+ 'shared/error-codes.md': sharedErrorCodes,
64
+ 'shared/live-sources.md': sharedLiveSources,
65
+ 'shared/models.md': sharedModels,
66
+ 'shared/prompt-caching.md': sharedPromptCaching,
67
+ 'shared/tool-use-concepts.md': sharedToolUseConcepts,
68
+ 'typescript/agent-sdk/README.md': typescriptAgentSdkReadme,
69
+ 'typescript/agent-sdk/patterns.md': typescriptAgentSdkPatterns,
70
+ 'typescript/claude-api/README.md': typescriptClaudeApiReadme,
71
+ 'typescript/claude-api/batches.md': typescriptClaudeApiBatches,
72
+ 'typescript/claude-api/files-api.md': typescriptClaudeApiFilesApi,
73
+ 'typescript/claude-api/streaming.md': typescriptClaudeApiStreaming,
74
+ 'typescript/claude-api/tool-use.md': typescriptClaudeApiToolUse,
75
+ }