import { readdir, readFile, writeFile, cp } from 'fs/promises' import { join } from 'path' import { getMacroDefines } from './scripts/defines.ts' const outdir = 'dist' // Step 1: Clean output directory const { rmSync } = await import('fs') rmSync(outdir, { recursive: true, force: true }) // Default features that match the official CLI build. // Additional features can be enabled via FEATURE_=1 env vars. const DEFAULT_BUILD_FEATURES = [ 'AGENT_TRIGGERS_REMOTE', 'CHICAGO_MCP', 'VOICE_MODE', 'SHOT_STATS', 'PROMPT_CACHE_BREAK_DETECTION', 'TOKEN_BUDGET', // P0: local features 'AGENT_TRIGGERS', 'ULTRATHINK', 'BUILTIN_EXPLORE_PLAN_AGENTS', 'LODESTONE', // P1: API-dependent features 'EXTRACT_MEMORIES', 'VERIFICATION_AGENT', 'KAIROS_BRIEF', 'AWAY_SUMMARY', 'ULTRAPLAN', // P2: daemon + remote control server 'DAEMON', // ACP (Agent Client Protocol) agent mode 'ACP', // PR-package restored features 'WORKFLOW_SCRIPTS', 'HISTORY_SNIP', 'CONTEXT_COLLAPSE', 'MONITOR_TOOL', 'FORK_SUBAGENT', // 'UDS_INBOX', 'KAIROS', 'COORDINATOR_MODE', 'LAN_PIPES', 'BG_SESSIONS', 'TEMPLATES', // 'REVIEW_ARTIFACT', // API 请求无响应,需进一步排查 schema 兼容性 // P3: poor mode (disable extract_memories + prompt_suggestion) 'POOR', ] // Collect FEATURE_* env vars → Bun.build features const envFeatures = Object.keys(process.env) .filter(k => k.startsWith('FEATURE_')) .map(k => k.replace('FEATURE_', '')) const features = [...new Set([...DEFAULT_BUILD_FEATURES, ...envFeatures])] // Step 2: Bundle with splitting const result = await Bun.build({ entrypoints: ['src/entrypoints/cli.tsx'], outdir, target: 'bun', splitting: true, define: getMacroDefines(), features, }) if (!result.success) { console.error('Build failed:') for (const log of result.logs) { console.error(log) } process.exit(1) } // Step 3: Post-process — replace Bun-only `import.meta.require` with Node.js compatible version const files = await readdir(outdir) const IMPORT_META_REQUIRE = 'var __require = import.meta.require;' const COMPAT_REQUIRE = `var __require = typeof import.meta.require === "function" ? import.meta.require : (await import("module")).createRequire(import.meta.url);` let patched = 0 for (const file of files) { if (!file.endsWith('.js')) continue const filePath = join(outdir, file) const content = await readFile(filePath, 'utf-8') if (content.includes(IMPORT_META_REQUIRE)) { await writeFile( filePath, content.replace(IMPORT_META_REQUIRE, COMPAT_REQUIRE), ) patched++ } } // Also patch unguarded globalThis.Bun destructuring from third-party deps // (e.g. @anthropic-ai/sandbox-runtime) so Node.js doesn't crash at import time. let bunPatched = 0 const BUN_DESTRUCTURE = /var \{([^}]+)\} = globalThis\.Bun;?/g const BUN_DESTRUCTURE_SAFE = 'var {$1} = typeof globalThis.Bun !== "undefined" ? globalThis.Bun : {};' for (const file of files) { if (!file.endsWith('.js')) continue const filePath = join(outdir, file) const content = await readFile(filePath, 'utf-8') if (BUN_DESTRUCTURE.test(content)) { await writeFile( filePath, content.replace(BUN_DESTRUCTURE, BUN_DESTRUCTURE_SAFE), ) bunPatched++ } } BUN_DESTRUCTURE.lastIndex = 0 console.log( `Bundled ${result.outputs.length} files to ${outdir}/ (patched ${patched} for import.meta.require, ${bunPatched} for Bun destructure)`, ) // Step 4: Copy native .node addon files (audio-capture) const vendorDir = join(outdir, 'vendor', 'audio-capture') await cp('vendor/audio-capture', vendorDir, { recursive: true }) console.log(`Copied vendor/audio-capture/ → ${vendorDir}/`) // Step 5: Bundle download-ripgrep script as standalone JS for postinstall const rgScript = await Bun.build({ entrypoints: ['scripts/download-ripgrep.ts'], outdir, target: 'node', }) if (!rgScript.success) { console.error('Failed to bundle download-ripgrep script:') for (const log of rgScript.logs) { console.error(log) } // Non-fatal — postinstall fallback to bun run scripts/download-ripgrep.ts } else { console.log(`Bundled download-ripgrep script to ${outdir}/`) } // Step 6: Generate cli-bun and cli-node executable entry points const cliBun = join(outdir, 'cli-bun.js') const cliNode = join(outdir, 'cli-node.js') await writeFile(cliBun, '#!/usr/bin/env bun\nimport "./cli.js"\n') await writeFile(cliNode, '#!/usr/bin/env node\nimport "./cli.js"\n') // Make both executable const { chmodSync } = await import('fs') chmodSync(cliBun, 0o755) chmodSync(cliNode, 0o755) console.log(`Generated ${cliBun} (shebang: bun) and ${cliNode} (shebang: node)`)