import { readdir } from 'fs/promises' import { getCwd } from '../../utils/cwd.js' import { registerBundledSkill } from '../bundledSkills.js' // claudeApiContent.js bundles 247KB of .md strings. Lazy-load inside // getPromptForCommand so they only enter memory when /claude-api is invoked. type SkillContent = typeof import('./claudeApiContent.js') type DetectedLanguage = | 'python' | 'typescript' | 'java' | 'go' | 'ruby' | 'csharp' | 'php' | 'curl' const LANGUAGE_INDICATORS: Record = { python: ['.py', 'requirements.txt', 'pyproject.toml', 'setup.py', 'Pipfile'], typescript: ['.ts', '.tsx', 'tsconfig.json', 'package.json'], java: ['.java', 'pom.xml', 'build.gradle'], go: ['.go', 'go.mod'], ruby: ['.rb', 'Gemfile'], csharp: ['.cs', '.csproj'], php: ['.php', 'composer.json'], curl: [], } async function detectLanguage(): Promise { const cwd = getCwd() let entries: string[] try { entries = await readdir(cwd) } catch { return null } for (const [lang, indicators] of Object.entries(LANGUAGE_INDICATORS) as [ DetectedLanguage, string[], ][]) { if (indicators.length === 0) continue for (const indicator of indicators) { if (indicator.startsWith('.')) { if (entries.some(e => e.endsWith(indicator))) return lang } else { if (entries.includes(indicator)) return lang } } } return null } function getFilesForLanguage( lang: DetectedLanguage, content: SkillContent, ): string[] { return Object.keys(content.SKILL_FILES).filter( path => path.startsWith(`${lang}/`) || path.startsWith('shared/'), ) } function processContent(md: string, content: SkillContent): string { // Strip HTML comments. Loop to handle nested comments. let out = md let prev do { prev = out out = out.replace(/\n?/g, '') } while (out !== prev) out = out.replace( /\{\{(\w+)\}\}/g, (match, key: string) => (content.SKILL_MODEL_VARS as Record)[key] ?? match, ) return out } function buildInlineReference( filePaths: string[], content: SkillContent, ): string { const sections: string[] = [] for (const filePath of filePaths.sort()) { const md = content.SKILL_FILES[filePath] if (!md) continue sections.push( `\n${processContent(md, content).trim()}\n`, ) } return sections.join('\n\n') } const INLINE_READING_GUIDE = `## Reference Documentation The relevant documentation for your detected language is included below in \`\` tags. Each tag has a \`path\` attribute showing its original file path. Use this to find the right section: ### Quick Task Reference **Single text classification/summarization/extraction/Q&A:** → Refer to \`{lang}/claude-api/README.md\` **Chat UI or real-time response display:** → Refer to \`{lang}/claude-api/README.md\` + \`{lang}/claude-api/streaming.md\` **Long-running conversations (may exceed context window):** → Refer to \`{lang}/claude-api/README.md\` — see Compaction section **Prompt caching / optimize caching / "why is my cache hit rate low":** → Refer to \`shared/prompt-caching.md\` + \`{lang}/claude-api/README.md\` (Prompt Caching section) **Function calling / tool use / agents:** → Refer to \`{lang}/claude-api/README.md\` + \`shared/tool-use-concepts.md\` + \`{lang}/claude-api/tool-use.md\` **Batch processing (non-latency-sensitive):** → Refer to \`{lang}/claude-api/README.md\` + \`{lang}/claude-api/batches.md\` **File uploads across multiple requests:** → Refer to \`{lang}/claude-api/README.md\` + \`{lang}/claude-api/files-api.md\` **Agent with built-in tools (file/web/terminal) (Python & TypeScript only):** → Refer to \`{lang}/agent-sdk/README.md\` + \`{lang}/agent-sdk/patterns.md\` **Error handling:** → Refer to \`shared/error-codes.md\` **Latest docs via WebFetch:** → Refer to \`shared/live-sources.md\` for URLs` function buildPrompt( lang: DetectedLanguage | null, args: string, content: SkillContent, ): string { // Take the SKILL.md content up to the "Reading Guide" section const cleanPrompt = processContent(content.SKILL_PROMPT, content) const readingGuideIdx = cleanPrompt.indexOf('## Reading Guide') const basePrompt = readingGuideIdx !== -1 ? cleanPrompt.slice(0, readingGuideIdx).trimEnd() : cleanPrompt const parts: string[] = [basePrompt] if (lang) { const filePaths = getFilesForLanguage(lang, content) const readingGuide = INLINE_READING_GUIDE.replace(/\{lang\}/g, lang) parts.push(readingGuide) parts.push( '---\n\n## Included Documentation\n\n' + buildInlineReference(filePaths, content), ) } else { // No language detected — include all docs and let the model ask parts.push(INLINE_READING_GUIDE.replace(/\{lang\}/g, 'unknown')) parts.push( 'No project language was auto-detected. Ask the user which language they are using, then refer to the matching docs below.', ) parts.push( '---\n\n## Included Documentation\n\n' + buildInlineReference(Object.keys(content.SKILL_FILES), content), ) } // Preserve the "When to Use WebFetch" and "Common Pitfalls" sections const webFetchIdx = cleanPrompt.indexOf('## When to Use WebFetch') if (webFetchIdx !== -1) { parts.push(cleanPrompt.slice(webFetchIdx).trimEnd()) } if (args) { parts.push(`## User Request\n\n${args}`) } return parts.join('\n\n') } export function registerClaudeApiSkill(): void { registerBundledSkill({ name: 'claude-api', description: 'Build apps with the Claude API or Anthropic SDK.\n' + 'TRIGGER when: code imports `anthropic`/`@anthropic-ai/sdk`/`claude_agent_sdk`, or user asks to use Claude API, Anthropic SDKs, or Agent SDK.\n' + 'DO NOT TRIGGER when: code imports `openai`/other AI SDK, general programming, or ML/data-science tasks.', allowedTools: ['Read', 'Grep', 'Glob', 'WebFetch'], userInvocable: true, async getPromptForCommand(args) { const content = await import('./claudeApiContent.js') const lang = await detectLanguage() const prompt = buildPrompt(lang, args, content) return [{ type: 'text', text: prompt }] }, }) }