* feat: enable 16 additional feature flags in open build
Activate features whose source is fully available in the mirror and
that have no Anthropic-internal infrastructure dependencies:
UI/UX: MESSAGE_ACTIONS, HISTORY_PICKER, QUICK_SEARCH, HOOK_PROMPTS
Reasoning: ULTRATHINK, TOKEN_BUDGET, SHOT_STATS
Agents: FORK_SUBAGENT, VERIFICATION_AGENT, MCP_SKILLS
Memory: EXTRACT_MEMORIES, AWAY_SUMMARY
Optimization: CACHED_MICROCOMPACT, PROMPT_CACHE_BREAK_DETECTION
Safety: TRANSCRIPT_CLASSIFIER
Debug: DUMP_SYSTEM_PROMPT
Also reorganize featureFlags into documented sections (disabled/upstream/new)
with inline comments explaining each flag's purpose.
* feat: add centralized GrowthBook defaults map for open build
Add _openBuildDefaults in the GrowthBook stub (no-telemetry-plugin.ts)
with all 66 runtime feature keys, organized by category with inline
comments describing each flag's purpose.
Override tengu_sedge_lantern (AWAY_SUMMARY) and tengu_hive_evidence
(VERIFICATION_AGENT) to true so these features work out of the box
without requiring manual ~/.claude/feature-flags.json setup.
Priority: feature-flags.json > _openBuildDefaults > upstream default
* feat: replace refusal language with positive security guidance
Remove refusal instructions from CYBER_RISK_INSTRUCTION since they are
redundant for Anthropic models (applied server-side) and useless for
uncensored models in multi-provider setups. Keep positive guidance for
security testing contexts and add red teaming support.
* Revert "feat: replace refusal language with positive security guidance"
This reverts commit 0463676a8f.
* fix: add EXTRACT_MEMORIES runtime gate overrides to open-build defaults
EXTRACT_MEMORIES was enabled at build-time but its runtime GrowthBook
gates (tengu_passport_quail, tengu_coral_fern) still defaulted to false,
preventing the feature from activating. Add both keys to
_openBuildDefaults so memory extraction works out of the box.
Also adds test coverage for _openBuildDefaults precedence behavior.
* docs: update GrowthBook runtime keys catalog to 88 keys
Expand the reference catalog in no-telemetry-plugin.ts from ~62 to 88
unique keys, covering all tengu_* call sites found in src/. Adds 27
previously undocumented keys including VSCode gates, dynamic configs
(auto-mode, cron, bridge), security gates, and KAIROS cron keys.
Adds "not exhaustive" disclaimer as suggested by Copilot reviewer.
Reorganizes categories with section dividers for readability.
493 lines
21 KiB
TypeScript
493 lines
21 KiB
TypeScript
/**
|
|
* OpenClaude build script — bundles the TypeScript source into a single
|
|
* distributable JS file using Bun's bundler.
|
|
*
|
|
* Handles:
|
|
* - bun:bundle feature() flags for the open build
|
|
* - MACRO.* globals → inlined version/build-time constants
|
|
* - src/ path aliases
|
|
*/
|
|
|
|
import { readFileSync, readdirSync, writeFileSync } from 'fs'
|
|
import { join } from 'path'
|
|
import { noTelemetryPlugin } from './no-telemetry-plugin'
|
|
|
|
const pkg = JSON.parse(readFileSync('./package.json', 'utf-8'))
|
|
const version = pkg.version
|
|
|
|
// Feature flags for the open build.
|
|
// Most Anthropic-internal features stay off; open-build features can be
|
|
// selectively enabled here when their full source exists in the mirror.
|
|
const featureFlags: Record<string, boolean> = {
|
|
// ── Disabled: require Anthropic infrastructure or missing source ─────
|
|
VOICE_MODE: false, // Push-to-talk STT via claude.ai OAuth endpoint
|
|
PROACTIVE: false, // Autonomous agent mode (missing proactive/ module)
|
|
KAIROS: false, // Persistent assistant/session mode (cloud backend)
|
|
BRIDGE_MODE: false, // Remote desktop bridge via CCR infrastructure
|
|
DAEMON: false, // Background daemon process (stubbed in open build)
|
|
AGENT_TRIGGERS: false, // Scheduled remote agent triggers
|
|
ABLATION_BASELINE: false, // A/B testing harness for eval experiments
|
|
CONTEXT_COLLAPSE: false, // Context collapsing optimization (stubbed)
|
|
COMMIT_ATTRIBUTION: false, // Co-Authored-By metadata in git commits
|
|
UDS_INBOX: false, // Unix Domain Socket inter-session messaging
|
|
BG_SESSIONS: false, // Background sessions via tmux (stubbed)
|
|
WEB_BROWSER_TOOL: false, // Built-in browser automation (source not mirrored)
|
|
CHICAGO_MCP: false, // Computer-use MCP (native Swift modules stubbed)
|
|
COWORKER_TYPE_TELEMETRY: false, // Telemetry for agent/coworker type classification
|
|
|
|
// ── Enabled: upstream defaults ──────────────────────────────────────
|
|
COORDINATOR_MODE: true, // Multi-agent coordinator with worker delegation
|
|
BUILTIN_EXPLORE_PLAN_AGENTS: true, // Built-in Explore/Plan specialized subagents
|
|
BUDDY: true, // Buddy mode for paired programming
|
|
MONITOR_TOOL: true, // MCP server monitoring/streaming tool
|
|
TEAMMEM: true, // Team memory management
|
|
MESSAGE_ACTIONS: true, // Message action buttons in the UI
|
|
|
|
// ── Enabled: new activations ────────────────────────────────────────
|
|
DUMP_SYSTEM_PROMPT: true, // --dump-system-prompt CLI flag for debugging
|
|
CACHED_MICROCOMPACT: true, // Cache-aware tool result truncation optimization
|
|
AWAY_SUMMARY: true, // "While you were away" recap after 5min blur
|
|
TRANSCRIPT_CLASSIFIER: true, // Auto-approval classifier for safe tool uses
|
|
ULTRATHINK: true, // Deep thinking mode — type "ultrathink" to boost reasoning
|
|
TOKEN_BUDGET: true, // Token budget tracking with usage warnings
|
|
HISTORY_PICKER: true, // Enhanced interactive prompt history picker
|
|
QUICK_SEARCH: true, // Ctrl+G quick search across prompts
|
|
SHOT_STATS: true, // Shot distribution stats in session summary
|
|
EXTRACT_MEMORIES: true, // Auto-extract durable memories from conversations
|
|
FORK_SUBAGENT: true, // Implicit context-forking when omitting subagent_type
|
|
VERIFICATION_AGENT: true, // Built-in read-only agent for test/verification
|
|
MCP_SKILLS: true, // Discover skills dynamically from MCP server resources
|
|
PROMPT_CACHE_BREAK_DETECTION: true, // Detect & log unexpected prompt cache invalidations
|
|
HOOK_PROMPTS: true, // Allow tools to request interactive user prompts
|
|
}
|
|
|
|
// ── Pre-process: replace feature() calls with boolean literals ──────
|
|
// Bun v1.3.9+ resolves `import { feature } from 'bun:bundle'` natively
|
|
// before plugins can intercept it via onResolve. The bun: namespace is
|
|
// handled by Bun's C++ resolver which runs before the JS plugin phase,
|
|
// so the previous onResolve/onLoad shim was silently ineffective — ALL
|
|
// feature() calls evaluated to false regardless of the featureFlags map.
|
|
//
|
|
// Fix: pre-process source files to strip the bun:bundle import and
|
|
// replace feature('FLAG') calls with their boolean literal. Files are
|
|
// modified in-place before Bun.build() and restored in a finally block.
|
|
|
|
// Match feature('FLAG') calls, including multi-line: feature(\n 'FLAG',\n)
|
|
const featureCallRe = /\bfeature\(\s*['"](\w+)['"][,\s]*\)/gs
|
|
const featureImportRe = /import\s*\{[^}]*\bfeature\b[^}]*\}\s*from\s*['"]bun:bundle['"];?\s*\n?/g
|
|
const modifiedFiles = new Map<string, string>() // path → original content
|
|
|
|
function preProcessFeatureFlags(dir: string) {
|
|
for (const ent of readdirSync(dir, { withFileTypes: true })) {
|
|
const full = join(dir, ent.name)
|
|
if (ent.isDirectory()) { preProcessFeatureFlags(full); continue }
|
|
if (!/\.(ts|tsx)$/.test(ent.name)) continue
|
|
|
|
const raw = readFileSync(full, 'utf-8')
|
|
if (!raw.includes('feature(')) continue
|
|
|
|
let contents = raw
|
|
contents = contents.replace(featureImportRe, '')
|
|
contents = contents.replace(featureCallRe, (_match, name) =>
|
|
String((featureFlags as Record<string, boolean>)[name] ?? false),
|
|
)
|
|
|
|
if (contents !== raw) {
|
|
modifiedFiles.set(full, raw)
|
|
writeFileSync(full, contents)
|
|
}
|
|
}
|
|
}
|
|
|
|
function restoreModifiedFiles() {
|
|
for (const [path, original] of modifiedFiles) {
|
|
writeFileSync(path, original)
|
|
}
|
|
modifiedFiles.clear()
|
|
}
|
|
|
|
preProcessFeatureFlags(join(import.meta.dir, '..', 'src'))
|
|
const numModified = modifiedFiles.size
|
|
|
|
// Restore source files on abrupt termination (Ctrl+C, kill, etc.)
|
|
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
|
|
process.on(signal, () => {
|
|
restoreModifiedFiles()
|
|
process.exit(signal === 'SIGINT' ? 130 : 143)
|
|
})
|
|
}
|
|
|
|
try {
|
|
|
|
const result = await Bun.build({
|
|
entrypoints: ['./src/entrypoints/cli.tsx'],
|
|
outdir: './dist',
|
|
target: 'node',
|
|
format: 'esm',
|
|
splitting: false,
|
|
sourcemap: 'external',
|
|
minify: false,
|
|
naming: 'cli.mjs',
|
|
define: {
|
|
// MACRO.* build-time constants
|
|
// Keep the internal compatibility version high enough to pass
|
|
// first-party minimum-version guards, but expose the real package
|
|
// version separately in Open Claude branding.
|
|
'MACRO.VERSION': JSON.stringify('99.0.0'),
|
|
'MACRO.DISPLAY_VERSION': JSON.stringify(version),
|
|
'MACRO.BUILD_TIME': JSON.stringify(new Date().toISOString()),
|
|
'MACRO.ISSUES_EXPLAINER':
|
|
JSON.stringify('report the issue at https://github.com/anthropics/claude-code/issues'),
|
|
'MACRO.PACKAGE_URL': JSON.stringify('@gitlawb/openclaude'),
|
|
'MACRO.NATIVE_PACKAGE_URL': 'undefined',
|
|
},
|
|
plugins: [
|
|
noTelemetryPlugin,
|
|
{
|
|
name: 'bun-bundle-shim',
|
|
setup(build) {
|
|
const internalFeatureStubModules = new Map([
|
|
[
|
|
'../daemon/workerRegistry.js',
|
|
'export async function runDaemonWorker() { throw new Error("Daemon worker is unavailable in the open build."); }',
|
|
],
|
|
[
|
|
'../daemon/main.js',
|
|
'export async function daemonMain() { throw new Error("Daemon mode is unavailable in the open build."); }',
|
|
],
|
|
[
|
|
'../cli/bg.js',
|
|
`
|
|
export async function psHandler() { throw new Error("Background sessions are unavailable in the open build."); }
|
|
export async function logsHandler() { throw new Error("Background sessions are unavailable in the open build."); }
|
|
export async function attachHandler() { throw new Error("Background sessions are unavailable in the open build."); }
|
|
export async function killHandler() { throw new Error("Background sessions are unavailable in the open build."); }
|
|
export async function handleBgFlag() { throw new Error("Background sessions are unavailable in the open build."); }
|
|
`,
|
|
],
|
|
[
|
|
'../cli/handlers/templateJobs.js',
|
|
'export async function templatesMain() { throw new Error("Template jobs are unavailable in the open build."); }',
|
|
],
|
|
[
|
|
'../environment-runner/main.js',
|
|
'export async function environmentRunnerMain() { throw new Error("Environment runner is unavailable in the open build."); }',
|
|
],
|
|
[
|
|
'../self-hosted-runner/main.js',
|
|
'export async function selfHostedRunnerMain() { throw new Error("Self-hosted runner is unavailable in the open build."); }',
|
|
],
|
|
] as const)
|
|
|
|
// bun:bundle feature() replacement is handled by the source
|
|
// pre-processing step above (see preProcessFeatureFlags).
|
|
// The previous onResolve/onLoad shim was ineffective in Bun
|
|
// v1.3.9+ because the bun: namespace is resolved natively
|
|
// before the JS plugin phase runs.
|
|
|
|
build.onResolve(
|
|
{ filter: /^\.\.\/(daemon\/workerRegistry|daemon\/main|cli\/bg|cli\/handlers\/templateJobs|environment-runner\/main|self-hosted-runner\/main)\.js$/ },
|
|
args => {
|
|
if (!internalFeatureStubModules.has(args.path)) return null
|
|
return {
|
|
path: args.path,
|
|
namespace: 'internal-feature-stub',
|
|
}
|
|
},
|
|
)
|
|
build.onLoad(
|
|
{ filter: /.*/, namespace: 'internal-feature-stub' },
|
|
args => ({
|
|
contents:
|
|
internalFeatureStubModules.get(args.path) ??
|
|
'export {}',
|
|
loader: 'js',
|
|
}),
|
|
)
|
|
|
|
// Resolve react/compiler-runtime to the standalone package
|
|
build.onResolve({ filter: /^react\/compiler-runtime$/ }, () => ({
|
|
path: 'react/compiler-runtime',
|
|
namespace: 'react-compiler-shim',
|
|
}))
|
|
build.onLoad(
|
|
{ filter: /.*/, namespace: 'react-compiler-shim' },
|
|
() => ({
|
|
contents: `export function c(size) { return new Array(size).fill(Symbol.for('react.memo_cache_sentinel')); }`,
|
|
loader: 'js',
|
|
}),
|
|
)
|
|
|
|
// NOTE: @opentelemetry/* kept as external deps (too many named exports to stub)
|
|
|
|
// Resolve native addon and missing snapshot imports to stubs
|
|
for (const mod of [
|
|
'audio-capture-napi',
|
|
'audio-capture.node',
|
|
'image-processor-napi',
|
|
'modifiers-napi',
|
|
'url-handler-napi',
|
|
'color-diff-napi',
|
|
'@anthropic-ai/mcpb',
|
|
'@ant/claude-for-chrome-mcp',
|
|
'@anthropic-ai/sandbox-runtime',
|
|
'asciichart',
|
|
'plist',
|
|
'cacache',
|
|
'fuse',
|
|
'code-excerpt',
|
|
'stack-utils',
|
|
]) {
|
|
build.onResolve({ filter: new RegExp(`^${mod}$`) }, () => ({
|
|
path: mod,
|
|
namespace: 'native-stub',
|
|
}))
|
|
}
|
|
build.onLoad(
|
|
{ filter: /.*/, namespace: 'native-stub' },
|
|
() => ({
|
|
// Comprehensive stub that handles any named export via Proxy
|
|
contents: `
|
|
const noop = () => null;
|
|
const noopClass = class {};
|
|
const handler = {
|
|
get(_, prop) {
|
|
if (prop === '__esModule') return true;
|
|
if (prop === 'default') return new Proxy({}, handler);
|
|
if (prop === 'ExportResultCode') return { SUCCESS: 0, FAILED: 1 };
|
|
if (prop === 'resourceFromAttributes') return () => ({});
|
|
if (prop === 'SandboxRuntimeConfigSchema') return { parse: () => ({}) };
|
|
return noop;
|
|
}
|
|
};
|
|
const stub = new Proxy(noop, handler);
|
|
export default stub;
|
|
export const __stub = true;
|
|
// Named exports for all known imports
|
|
export const SandboxViolationStore = null;
|
|
export const SandboxManager = new Proxy({}, { get: () => noop });
|
|
export const SandboxRuntimeConfigSchema = { parse: () => ({}) };
|
|
export const BROWSER_TOOLS = [];
|
|
export const getMcpConfigForManifest = noop;
|
|
export const ColorDiff = null;
|
|
export const ColorFile = null;
|
|
export const getSyntaxTheme = noop;
|
|
export const plot = noop;
|
|
export const createClaudeForChromeMcpServer = noop;
|
|
// OpenTelemetry exports
|
|
export const ExportResultCode = { SUCCESS: 0, FAILED: 1 };
|
|
export const resourceFromAttributes = noop;
|
|
export const Resource = noopClass;
|
|
export const SimpleSpanProcessor = noopClass;
|
|
export const BatchSpanProcessor = noopClass;
|
|
export const NodeTracerProvider = noopClass;
|
|
export const BasicTracerProvider = noopClass;
|
|
export const OTLPTraceExporter = noopClass;
|
|
export const OTLPLogExporter = noopClass;
|
|
export const OTLPMetricExporter = noopClass;
|
|
export const PrometheusExporter = noopClass;
|
|
export const LoggerProvider = noopClass;
|
|
export const SimpleLogRecordProcessor = noopClass;
|
|
export const BatchLogRecordProcessor = noopClass;
|
|
export const MeterProvider = noopClass;
|
|
export const PeriodicExportingMetricReader = noopClass;
|
|
export const trace = { getTracer: () => ({ startSpan: () => ({ end: noop, setAttribute: noop, setStatus: noop, recordException: noop }) }) };
|
|
export const context = { active: noop, with: (_, fn) => fn() };
|
|
export const SpanStatusCode = { OK: 0, ERROR: 1, UNSET: 2 };
|
|
export const ATTR_SERVICE_NAME = 'service.name';
|
|
export const ATTR_SERVICE_VERSION = 'service.version';
|
|
export const SEMRESATTRS_SERVICE_NAME = 'service.name';
|
|
export const SEMRESATTRS_SERVICE_VERSION = 'service.version';
|
|
export const AggregationTemporality = { CUMULATIVE: 0, DELTA: 1 };
|
|
export const DataPointType = { HISTOGRAM: 0, SUM: 1, GAUGE: 2 };
|
|
export const InstrumentType = { COUNTER: 0, HISTOGRAM: 1, UP_DOWN_COUNTER: 2 };
|
|
export const PushMetricExporter = noopClass;
|
|
export const SeverityNumber = {};
|
|
`,
|
|
loader: 'js',
|
|
}),
|
|
)
|
|
|
|
// Resolve .md and .txt file imports to empty string stubs
|
|
build.onResolve({ filter: /\.(md|txt)$/ }, (args) => ({
|
|
path: args.path,
|
|
namespace: 'text-stub',
|
|
}))
|
|
build.onLoad(
|
|
{ filter: /.*/, namespace: 'text-stub' },
|
|
() => ({
|
|
contents: `export default '';`,
|
|
loader: 'js',
|
|
}),
|
|
)
|
|
|
|
// Pre-scan: find all missing modules that need stubbing
|
|
// (Bun's onResolve corrupts module graph even when returning null,
|
|
// so we use exact-match resolvers instead of catch-all patterns)
|
|
const fs = require('fs')
|
|
const pathMod = require('path')
|
|
const srcDir = pathMod.resolve(__dirname, '..', 'src')
|
|
const missingModules = new Set<string>()
|
|
const missingModuleExports = new Map<string, Set<string>>()
|
|
|
|
// Known missing external packages
|
|
for (const pkg of [
|
|
'@ant/computer-use-mcp',
|
|
'@ant/computer-use-mcp/sentinelApps',
|
|
'@ant/computer-use-mcp/types',
|
|
'@ant/computer-use-swift',
|
|
'@ant/computer-use-input',
|
|
]) {
|
|
missingModules.add(pkg)
|
|
}
|
|
|
|
// Scan source to find imports that can't resolve
|
|
function scanForMissingImports() {
|
|
function checkAndRegister(specifier: string, fileDir: string, namedPart: string) {
|
|
const names = namedPart.split(',')
|
|
.map((s: string) => s.trim().replace(/^type\s+/, ''))
|
|
.filter((s: string) => s && !s.startsWith('type '))
|
|
|
|
// Check src/tasks/ non-relative imports
|
|
if (specifier.startsWith('src/tasks/')) {
|
|
const resolved = pathMod.resolve(__dirname, '..', specifier)
|
|
const candidates = [
|
|
resolved,
|
|
`${resolved}.ts`, `${resolved}.tsx`,
|
|
resolved.replace(/\.js$/, '.ts'), resolved.replace(/\.js$/, '.tsx'),
|
|
pathMod.join(resolved, 'index.ts'), pathMod.join(resolved, 'index.tsx'),
|
|
]
|
|
if (!candidates.some((c: string) => fs.existsSync(c))) {
|
|
missingModules.add(specifier)
|
|
}
|
|
}
|
|
// Check relative .js imports
|
|
else if (specifier.endsWith('.js') && (specifier.startsWith('./') || specifier.startsWith('../'))) {
|
|
const resolved = pathMod.resolve(fileDir, specifier)
|
|
const tsVariant = resolved.replace(/\.js$/, '.ts')
|
|
const tsxVariant = resolved.replace(/\.js$/, '.tsx')
|
|
if (!fs.existsSync(resolved) && !fs.existsSync(tsVariant) && !fs.existsSync(tsxVariant)) {
|
|
missingModules.add(specifier)
|
|
}
|
|
}
|
|
|
|
// Track named exports for missing modules
|
|
if (names.length > 0) {
|
|
if (!missingModuleExports.has(specifier)) missingModuleExports.set(specifier, new Set())
|
|
for (const n of names) missingModuleExports.get(specifier)!.add(n)
|
|
}
|
|
}
|
|
|
|
function walk(dir: string) {
|
|
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
const full = pathMod.join(dir, ent.name)
|
|
if (ent.isDirectory()) { walk(full); continue }
|
|
if (!/\.(ts|tsx)$/.test(ent.name)) continue
|
|
const rawCode: string = fs.readFileSync(full, 'utf-8')
|
|
const fileDir = pathMod.dirname(full)
|
|
|
|
// Strip comments before scanning for imports/requires.
|
|
// The regex scanner matches require()/import() patterns
|
|
// inside JSDoc comments, causing false-positive missing
|
|
// module detection that breaks the build with noop stubs.
|
|
const code = rawCode
|
|
.replace(/\/\*[\s\S]*?\*\//g, '') // block comments
|
|
.replace(/\/\/.*$/gm, '') // line comments
|
|
|
|
// Collect static imports: import { X } from '...'
|
|
for (const m of code.matchAll(/import\s+(?:\{([^}]*)\}|(\w+))?\s*(?:,\s*\{([^}]*)\})?\s*from\s+['"](.*?)['"]/g)) {
|
|
checkAndRegister(m[4], fileDir, m[1] || m[3] || '')
|
|
}
|
|
|
|
// Collect dynamic requires: require('...') — these are used
|
|
// behind feature() gates and become live when flags are enabled.
|
|
for (const m of code.matchAll(/require\(\s*['"](\.\.?\/[^'"]+)['"]\s*\)/g)) {
|
|
checkAndRegister(m[1], fileDir, '')
|
|
}
|
|
|
|
// Collect dynamic imports: import('...')
|
|
for (const m of code.matchAll(/import\(\s*['"](\.\.?\/[^'"]+)['"]\s*\)/g)) {
|
|
checkAndRegister(m[1], fileDir, '')
|
|
}
|
|
}
|
|
}
|
|
walk(srcDir)
|
|
}
|
|
scanForMissingImports()
|
|
|
|
// Register exact-match resolvers for each missing module
|
|
for (const mod of missingModules) {
|
|
const escaped = mod.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
build.onResolve({ filter: new RegExp(`^${escaped}$`) }, () => ({
|
|
path: mod,
|
|
namespace: 'missing-module-stub',
|
|
}))
|
|
}
|
|
|
|
build.onLoad(
|
|
{ filter: /.*/, namespace: 'missing-module-stub' },
|
|
(args) => {
|
|
const names = missingModuleExports.get(args.path) ?? new Set()
|
|
const exports = [...names].map(n => `export const ${n} = noop;`).join('\n')
|
|
return {
|
|
contents: `
|
|
const noop = () => null;
|
|
export default noop;
|
|
${exports}
|
|
`,
|
|
loader: 'js',
|
|
}
|
|
},
|
|
)
|
|
},
|
|
},
|
|
],
|
|
external: [
|
|
// OpenTelemetry — too many named exports to stub, kept external
|
|
'@opentelemetry/api',
|
|
'@opentelemetry/api-logs',
|
|
'@opentelemetry/core',
|
|
'@opentelemetry/exporter-trace-otlp-grpc',
|
|
'@opentelemetry/exporter-trace-otlp-http',
|
|
'@opentelemetry/exporter-trace-otlp-proto',
|
|
'@opentelemetry/exporter-logs-otlp-http',
|
|
'@opentelemetry/exporter-logs-otlp-proto',
|
|
'@opentelemetry/exporter-logs-otlp-grpc',
|
|
'@opentelemetry/exporter-metrics-otlp-proto',
|
|
'@opentelemetry/exporter-metrics-otlp-grpc',
|
|
'@opentelemetry/exporter-metrics-otlp-http',
|
|
'@opentelemetry/exporter-prometheus',
|
|
'@opentelemetry/resources',
|
|
'@opentelemetry/sdk-trace-base',
|
|
'@opentelemetry/sdk-trace-node',
|
|
'@opentelemetry/sdk-logs',
|
|
'@opentelemetry/sdk-metrics',
|
|
'@opentelemetry/semantic-conventions',
|
|
// Native image processing
|
|
'sharp',
|
|
// Cloud provider SDKs
|
|
'@aws-sdk/client-bedrock',
|
|
'@aws-sdk/client-bedrock-runtime',
|
|
'@aws-sdk/client-sts',
|
|
'@aws-sdk/credential-providers',
|
|
'@azure/identity',
|
|
'google-auth-library',
|
|
],
|
|
})
|
|
|
|
if (!result.success) {
|
|
console.error('Build failed:')
|
|
for (const log of result.logs) {
|
|
console.error(log)
|
|
}
|
|
process.exitCode = 1
|
|
} else {
|
|
console.log(`✓ Built openclaude v${version} → dist/cli.mjs`)
|
|
}
|
|
|
|
} finally {
|
|
// Always restore source files, even if Bun.build() throws
|
|
restoreModifiedFiles()
|
|
console.log(` 🔄 feature-flags: pre-processed ${numModified} files (restored)`)
|
|
}
|