diff --git a/package.json b/package.json index 9634d2b..74c1254 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "claude": "src/entrypoints/cli.tsx" }, "scripts": { + "build": "bun scripts/build-bundle.ts", + "build:watch": "bun scripts/build-bundle.ts --watch", + "build:prod": "bun scripts/build-bundle.ts --minify", "typecheck": "tsc --noEmit", "lint": "biome check src/", "lint:fix": "biome check --write src/", @@ -65,6 +68,7 @@ "@types/semver": "^7.5.8", "@types/stack-utils": "^2.0.3", "@types/ws": "^8.5.0", + "esbuild": "^0.25.0", "typescript": "^5.7.0" }, "engines": { diff --git a/scripts/build-bundle.ts b/scripts/build-bundle.ts index 0a7ddb4..5cb8b5e 100644 --- a/scripts/build-bundle.ts +++ b/scripts/build-bundle.ts @@ -6,10 +6,17 @@ // Watch mode: bun scripts/build-bundle.ts --watch import * as esbuild from 'esbuild' -import { resolve } from 'path' -import { chmodSync, readFileSync } from 'fs' +import { resolve, dirname } from 'path' +import { chmodSync, readFileSync, existsSync } from 'fs' +import { fileURLToPath } from 'url' -const ROOT = resolve(import.meta.dir, '..') +// Bun: import.meta.dir — Node 21+: import.meta.dirname — fallback +const __dir: string = + (import.meta as any).dir ?? + (import.meta as any).dirname ?? + dirname(fileURLToPath(import.meta.url)) + +const ROOT = resolve(__dir, '..') const watch = process.argv.includes('--watch') const minify = process.argv.includes('--minify') const noSourcemap = process.argv.includes('--no-sourcemap') @@ -18,6 +25,44 @@ const noSourcemap = process.argv.includes('--no-sourcemap') const pkg = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')) const version = pkg.version || '0.0.0-dev' +// ── Plugin: resolve bare 'src/' imports (tsconfig baseUrl: ".") ── +// The codebase uses `import ... from 'src/foo/bar.js'` which relies on +// TypeScript's baseUrl resolution. This plugin maps those to real TS files. +const srcResolverPlugin: esbuild.Plugin = { + name: 'src-resolver', + setup(build) { + build.onResolve({ filter: /^src\// }, (args) => { + const basePath = resolve(ROOT, args.path) + + // Already exists as-is + if (existsSync(basePath)) { + return { path: basePath } + } + + // Strip .js/.jsx and try TypeScript extensions + const withoutExt = basePath.replace(/\.(js|jsx)$/, '') + for (const ext of ['.ts', '.tsx', '.js', '.jsx']) { + const candidate = withoutExt + ext + if (existsSync(candidate)) { + return { path: candidate } + } + } + + // Try as directory with index file + const dirPath = basePath.replace(/\.(js|jsx)$/, '') + for (const ext of ['.ts', '.tsx', '.js', '.jsx']) { + const candidate = resolve(dirPath, 'index' + ext) + if (existsSync(candidate)) { + return { path: candidate } + } + } + + // Let esbuild handle it (will error if truly missing) + return undefined + }) + }, +} + const buildOptions: esbuild.BuildOptions = { entryPoints: [resolve(ROOT, 'src/entrypoints/cli.tsx')], bundle: true, @@ -30,8 +75,10 @@ const buildOptions: esbuild.BuildOptions = { // Single-file output — no code splitting for CLI tools splitting: false, - // Inject the MACRO global before all other code - inject: [resolve(ROOT, 'src/shims/macro.ts')], + plugins: [srcResolverPlugin], + + // Use tsconfig for baseUrl / paths resolution (complements plugin above) + tsconfig: resolve(ROOT, 'tsconfig.json'), // Alias bun:bundle to our runtime shim alias: { @@ -50,6 +97,11 @@ const buildOptions: esbuild.BuildOptions = { 'node:*', // Native addons that can't be bundled 'fsevents', + 'sharp', + 'image-processor-napi', + // Anthropic-internal packages (not published externally) + '@anthropic-ai/sandbox-runtime', + '@anthropic-ai/claude-agent-sdk', ], jsx: 'automatic', @@ -64,9 +116,14 @@ const buildOptions: esbuild.BuildOptions = { treeShaking: true, // Define replacements — inline constants at build time - // Eliminates process.env.USER_TYPE === 'ant' branches (Anthropic-internal code) - // Sets NODE_ENV to production for production builds + // MACRO.* — originally inlined by Bun's bundler at compile time + // process.env.USER_TYPE — eliminates 'ant' (Anthropic-internal) code branches define: { + 'MACRO.VERSION': JSON.stringify(version), + 'MACRO.PACKAGE_URL': JSON.stringify('@anthropic-ai/claude-code'), + 'MACRO.ISSUES_EXPLAINER': JSON.stringify( + 'report issues at https://github.com/anthropics/claude-code/issues' + ), 'process.env.USER_TYPE': '"external"', 'process.env.NODE_ENV': minify ? '"production"' : '"development"', }, diff --git a/src/shims/bun-bundle.ts b/src/shims/bun-bundle.ts index 43817a3..50c04e9 100644 --- a/src/shims/bun-bundle.ts +++ b/src/shims/bun-bundle.ts @@ -6,6 +6,8 @@ const FEATURE_FLAGS: Record = { PROACTIVE: envBool('CLAUDE_CODE_PROACTIVE', false), KAIROS: envBool('CLAUDE_CODE_KAIROS', false), + KAIROS_BRIEF: envBool('CLAUDE_CODE_KAIROS_BRIEF', false), + KAIROS_GITHUB_WEBHOOKS: envBool('CLAUDE_CODE_KAIROS_GITHUB_WEBHOOKS', false), BRIDGE_MODE: envBool('CLAUDE_CODE_BRIDGE_MODE', false), DAEMON: envBool('CLAUDE_CODE_DAEMON', false), VOICE_MODE: envBool('CLAUDE_CODE_VOICE_MODE', false), @@ -15,6 +17,17 @@ const FEATURE_FLAGS: Record = { ABLATION_BASELINE: false, // always off for external builds DUMP_SYSTEM_PROMPT: envBool('CLAUDE_CODE_DUMP_SYSTEM_PROMPT', false), BG_SESSIONS: envBool('CLAUDE_CODE_BG_SESSIONS', false), + HISTORY_SNIP: envBool('CLAUDE_CODE_HISTORY_SNIP', false), + WORKFLOW_SCRIPTS: envBool('CLAUDE_CODE_WORKFLOW_SCRIPTS', false), + CCR_REMOTE_SETUP: envBool('CLAUDE_CODE_CCR_REMOTE_SETUP', false), + EXPERIMENTAL_SKILL_SEARCH: envBool('CLAUDE_CODE_EXPERIMENTAL_SKILL_SEARCH', false), + ULTRAPLAN: envBool('CLAUDE_CODE_ULTRAPLAN', false), + TORCH: envBool('CLAUDE_CODE_TORCH', false), + UDS_INBOX: envBool('CLAUDE_CODE_UDS_INBOX', false), + FORK_SUBAGENT: envBool('CLAUDE_CODE_FORK_SUBAGENT', false), + BUDDY: envBool('CLAUDE_CODE_BUDDY', false), + MCP_SKILLS: envBool('CLAUDE_CODE_MCP_SKILLS', false), + REACTIVE_COMPACT: envBool('CLAUDE_CODE_REACTIVE_COMPACT', false), } function envBool(key: string, fallback: boolean): boolean {