-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathbuild.mjs
More file actions
465 lines (431 loc) · 16.6 KB
/
Copy pathbuild.mjs
File metadata and controls
465 lines (431 loc) · 16.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
#!/usr/bin/env node
/**
* Tau Build Script (Node + esbuild)
*
* Bundles the CLI from TypeScript source into a single distributable JS
* file that runs on Node.js >=20. Mirrors the original Bun-based build
* but uses esbuild so contributors can build from source with only Node
* installed (Bun is not required).
*/
import { build } from 'esbuild'
import { spawnSync } from 'child_process'
import { existsSync, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs'
import { isAbsolute, join, resolve } from 'path'
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'))
const bundledRuntimePackages = new Set(['e2b', 'google-auth-library'])
const optionalExternalPackages = new Set([
'@anthropic-ai/bedrock-sdk',
'@anthropic-ai/foundry-sdk',
'@anthropic-ai/mcpb',
'@anthropic-ai/vertex-sdk',
'@aws-sdk/client-bedrock',
'@aws-sdk/client-sts',
'@azure/identity',
'@computer-use/nut-js',
'@opentelemetry/exporter-logs-otlp-grpc',
'@opentelemetry/exporter-logs-otlp-http',
'@opentelemetry/exporter-logs-otlp-proto',
'@opentelemetry/exporter-metrics-otlp-grpc',
'@opentelemetry/exporter-metrics-otlp-http',
'@opentelemetry/exporter-metrics-otlp-proto',
'@opentelemetry/exporter-prometheus',
'@opentelemetry/exporter-trace-otlp-grpc',
'@opentelemetry/exporter-trace-otlp-http',
'@opentelemetry/exporter-trace-otlp-proto',
'modifiers-napi',
'node-pty',
'yaml',
])
const runtimeDependencies = Object.keys(pkg.dependencies ?? {})
const externalRuntimePackages = [
...runtimeDependencies.filter(name => !bundledRuntimePackages.has(name)),
...optionalExternalPackages,
]
const externalRuntimePatterns = externalRuntimePackages.flatMap(name => [
name,
`${name}/*`,
])
// ─── Shim for missing internal/ant assets ──────────────────────────
if (!existsSync('./dist')) {
mkdirSync('./dist')
}
const shimPath = resolve(process.cwd(), 'dist/shim.js')
writeFileSync(
shimPath,
`
export const checkProtectedNamespace = () => false;
export const checkProtectedCluster = () => false;
export const checkManagedSettingsSecurity = () => {};
export const handleSecurityCheckResult = () => {};
export const TungstenTool = null;
export const SuggestBackgroundPRTool = null;
export const VerifyPlanExecutionTool = null;
export const isConnectorTextBlock = () => false;
export const AgentTool = null;
export const initBundledWorkflows = () => {};
export const WorkflowTool = null;
export const run = () => {};
export const DESCRIPTION = '';
export const PROMPT = '';
export const getToolUseSummary = () => '';
export const renderToolUseMessage = () => null;
export const renderToolUseRejectedMessage = () => null;
export const renderToolUseErrorMessage = () => null;
export const renderToolResultMessage = () => null;
export const createClaudeForChromeMcpServer = () => {};
export const BROWSER_TOOLS = [];
export const COMPUTER_USE_TOOLS = [];
export const DEFAULT_UPLOAD_CONCURRENCY = 1;
export const FILE_COUNT_LIMIT = 100;
export const OUTPUTS_SUBDIR = 'outputs';
export const buildComputerUseTools = () => [];
export const isCoordinatorMode = () => false;
export class SandboxManager {
constructor() {}
start() {}
stop() {}
isEnabled() { return false; }
getViolations() { return []; }
static isSupportedPlatform() { return false; }
static checkDependencies() { return { supported: false }; }
static wrapWithSandbox(command) { return command; }
static async initialize() {}
static updateConfig() {}
static reset() {}
static getFsReadConfig() { return {}; }
static getFsWriteConfig() { return {}; }
static getNetworkRestrictionConfig() { return {}; }
static getIgnoreViolations() { return false; }
static getAllowUnixSockets() { return true; }
static getAllowLocalBinding() { return true; }
static getEnableWeakerNestedSandbox() { return false; }
static getProxyPort() { return 0; }
static getSocksProxyPort() { return 0; }
static getLinuxHttpSocketPath() { return ''; }
static getLinuxSocksSocketPath() { return ''; }
static async waitForNetworkInitialization() {}
static getSandboxViolationStore() { return new SandboxViolationStore(); }
static annotateStderrWithSandboxFailures(stderr) { return stderr; }
static cleanupAfterCommand() {}
}
export class SandboxViolationStore { constructor() {} getViolations() { return []; } clear() {} }
export const SandboxRuntimeConfigSchema = { parse: (v) => v, safeParse: (v) => ({ success: true, data: v }) };
export const runChromeNativeHost = () => {};
export const runComputerUseMcpServer = () => {};
export const runDaemonWorker = () => {};
export const daemonMain = () => {};
export const templatesMain = () => {};
export const environmentRunnerMain = () => {};
export const selfHostedRunnerMain = () => {};
// Additional exports imported by stripped-out features. esbuild requires
// named exports be statically present; Bun's bundler was more forgiving.
export const WORKFLOW_TOOL_NAME = '';
export const API_RESIZE_PARAMS = {};
export const targetImageSize = () => ({ width: 0, height: 0 });
export const getSentinelCategory = () => null;
export const DEFAULT_GRANT_FLAGS = {};
export const bindSessionContext = (fn) => fn;
export const createComputerUseMcpServer = () => null;
const proxy = new Proxy({}, { get: () => () => {} });
export default proxy;
`,
)
// ─── esbuild bundle ────────────────────────────────────────────────
const tauPlugin = {
name: 'tau-build',
setup(build) {
// Shim bun:bundle — feature() returns false for all features in external
// builds (removes ant-internal code paths). Self-learning is interactive
// (the /learned command + an end-of-task offer baked into the memory
// guidance), so it does NOT use the EXTRACT_MEMORIES background fork.
build.onResolve({ filter: /^bun:bundle$/ }, () => ({
path: 'bun:bundle',
namespace: 'bun-bundle-shim',
}))
build.onLoad({ filter: /.*/, namespace: 'bun-bundle-shim' }, () => ({
contents: `export function feature(_name) { return false; }`,
loader: 'js',
}))
// Load .md files as text (used by skills/bundled/verifyContent.ts etc.)
build.onLoad({ filter: /\.md$/ }, args => {
try {
const contents = readFileSync(args.path, 'utf8')
return {
contents: `export default ${JSON.stringify(contents)};`,
loader: 'js',
}
} catch {
return { contents: `export default '';`, loader: 'js' }
}
})
// Universal resolver to handle .js → .ts/tsx mapping AND shimming missing files.
build.onResolve({ filter: /.*/ }, args => {
if (
args.path.startsWith('node:') ||
args.namespace === 'bun-bundle-shim'
) {
return
}
// Handle color-diff-napi shim first
if (args.path === 'color-diff-napi') {
return { path: resolve(process.cwd(), 'src/native-ts/color-diff/index.ts') }
}
// react/compiler-runtime is provided by React 19+ — let it resolve
// from node_modules as an external package.
if (args.path === 'react/compiler-runtime') {
return
}
let absPath
if (args.path.startsWith('src/')) {
absPath = resolve(process.cwd(), args.path)
} else if (args.path.startsWith('.') || isAbsolute(args.path)) {
absPath = resolve(args.resolveDir, args.path)
} else if (
args.path.startsWith('@ant/') ||
args.path.startsWith('@anthropic-ai/sandbox')
) {
return { path: shimPath }
} else {
return // Likely a node_module or external
}
const possiblePaths = [
absPath,
absPath.replace(/\.js$/, '.ts'),
absPath.replace(/\.js$/, '.tsx'),
absPath.replace(/\.js$/, '.d.ts'),
absPath.replace(/\.mjs$/, '.ts'),
absPath.replace(/\.mjs$/, '.tsx'),
absPath + '.ts',
absPath + '.tsx',
join(absPath, 'index.ts'),
join(absPath, 'index.tsx'),
join(absPath, 'index.js'),
]
for (const p of possiblePaths) {
try {
if (existsSync(p) && !lstatSync(p).isDirectory()) {
return { path: p }
}
} catch {
/* ignore */
}
}
// If it's a missing file within OUR source or assets, shim it.
const root = process.cwd()
if (absPath.startsWith(root) && !absPath.includes('node_modules')) {
return { path: shimPath }
}
})
},
}
const result = await build({
entryPoints: ['./src/entrypoints/cli.tsx'],
outfile: './dist/tau.mjs',
platform: 'node',
format: 'esm',
bundle: true,
minify: false,
sourcemap: 'linked',
splitting: false,
target: 'node20',
// Externalize declared runtime deps. Bundle selected JS-only integrations
// whose upstream dependency ranges still pull deprecated packages on install.
external: externalRuntimePatterns,
jsx: 'automatic',
jsxImportSource: 'react',
loader: {
'.ts': 'ts',
'.tsx': 'tsx',
},
// ESM output doesn't have `require` in scope, so esbuild replaces any
// `require(x)` calls in source with a throwing stub. Inject a real
// `require` built from `createRequire(import.meta.url)` at the top of
// the bundle so all the lazy-loaded modules (semver, PowerShellTool,
// etc.) work at runtime.
banner: {
js: `import { createRequire as __createRequireForESM } from 'node:module';\nconst require = __createRequireForESM(import.meta.url);`,
},
define: {
// Build-time MACRO constants
'MACRO.VERSION': JSON.stringify(pkg.version),
'MACRO.PACKAGE_URL': JSON.stringify(pkg.name),
'MACRO.NATIVE_PACKAGE_URL': JSON.stringify(pkg.name),
'MACRO.BUILD_TIME': JSON.stringify(new Date().toISOString()),
'MACRO.FEEDBACK_CHANNEL': JSON.stringify(
'https://github.com/AbdoKnbGit/tau/issues',
),
'MACRO.ISSUES_EXPLAINER': JSON.stringify(
'report the issue at https://github.com/AbdoKnbGit/tau/issues',
),
'process.env.USER_TYPE': JSON.stringify('external'),
},
plugins: [tauPlugin],
logLevel: 'warning',
})
if (result.errors?.length) {
console.error('Build failed:')
for (const err of result.errors) {
console.error(err)
}
process.exit(1)
}
// ─── Post-process bundle ───────────────────────────────────────────
const outPath = './dist/tau.mjs'
const code = readFileSync(outPath, 'utf8')
if (!code.startsWith('#!')) {
let patched = code
// Disable the config-reading guard — external builds don't need it.
// Bun's output had `!configReadingAllowed && true`; esbuild's output
// is `!configReadingAllowed && process.env.NODE_ENV !== "test"`. Match
// the `!configReadingAllowed &&` prefix regardless of what follows so
// the `&& …` chain short-circuits to false either way.
patched = patched.replace(/!configReadingAllowed\s*&&\s*/g, 'false && ')
// Disable remote version check — Tau has its own versioning
patched = patched.replace(
/async function assertMinVersion\(\)\s*\{/,
'async function assertMinVersion() { return;',
)
// Fix jsonc-parser ESM extensionless import issue → use UMD build
patched = patched.replace(
/from\s+"jsonc-parser[^"]*"/g,
'from "jsonc-parser/lib/umd/main.js"',
)
// Fix CJS/ESM interop: convert named imports from CJS npm packages to
// default import + destructure. Node.js strict ESM doesn't support
// named exports from CJS modules.
const builtins = new Set([
'crypto','fs','path','os','process','child_process','events','http',
'https','net','stream','tty','url','util','buffer','async_hooks',
'dns','readline','v8','zlib','assert','perf_hooks','worker_threads',
'string_decoder','tls','module','cluster','dgram','domain','punycode',
'querystring','timers','vm','wasi','inspector','diagnostics_channel',
'trace_events','console',
])
let shimCounter = 0
const cjsPackages = new Set([
'ajv','semver','shell-quote','qrcode','asciichart',
'vscode-jsonrpc',
'react','react-reconciler',
])
// Handle combined default + named imports: import Foo, { bar } from "pkg"
patched = patched.replace(
/import\s+(\w+)\s*,\s*\{([^}]+)\}\s*from\s*"([^"]+)"/g,
(_match, defName, names, mod) => {
if (
mod.startsWith('node:') ||
mod.startsWith('./') ||
mod.startsWith('../')
)
return _match
const pkgName = mod.startsWith('@')
? mod.split('/').slice(0, 2).join('/')
: mod.split('/')[0]
if (builtins.has(pkgName)) return _match
if (!cjsPackages.has(pkgName)) return _match
const fixedNames = names.replace(/\bas\b/g, ':')
return `import ${defName} from "${mod}"; const {${fixedNames}} = ${defName}`
},
)
// Handle pure named imports: import { bar } from "pkg"
patched = patched.replace(
/import\s*\{([^}]+)\}\s*from\s*"([^"]+)"/g,
(_match, names, mod) => {
if (
mod.startsWith('node:') ||
mod.startsWith('./') ||
mod.startsWith('../')
)
return _match
const pkgName = mod.startsWith('@')
? mod.split('/').slice(0, 2).join('/')
: mod.split('/')[0]
if (builtins.has(pkgName)) return _match
if (!cjsPackages.has(pkgName)) return _match
const varName = `__cjs${shimCounter++}`
const fixedNames = names.replace(/\bas\b/g, ':')
return `import ${varName} from "${mod}"; const {${fixedNames}} = ${varName}`
},
)
// Polyfill React.useEffectEvent — available in React canary/internal
// builds but not in stable React 19. Provides a stable function ref
// that always calls the latest callback (used by React Compiler output).
const useEffectEventPolyfill = `
import React from "react";
if (!React.useEffectEvent) {
React.useEffectEvent = function useEffectEvent(fn) {
const ref = React.useRef(fn);
ref.current = fn;
return React.useCallback(function () {
return ref.current.apply(void 0, arguments);
}, []);
};
}
`
writeFileSync(outPath, `#!/usr/bin/env node\n${useEffectEventPolyfill}${patched}`)
}
// ─── Launcher (bin entry) ──────────────────────────────────────────
//
// `tau` / `claudex` point at dist/cli.mjs, which is now a tiny
// dependency-free launcher: it verifies that every runtime dependency is
// actually present in node_modules (interrupted updates and Windows EPERM
// cleanup failures leave holes that otherwise surface later as raw
// "Cannot find module" crashes), self-heals via scripts/verify-deps.mjs,
// then loads the real bundle (dist/tau.mjs).
const launcher = `#!/usr/bin/env node
// Tau launcher - verifies the installed dependency tree, repairs incomplete
// installs, then starts the CLI. Set TAU_SKIP_PREFLIGHT=1 to bypass.
import { existsSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
const distDir = dirname(fileURLToPath(import.meta.url))
const packageRoot = dirname(distDir)
if (process.env.TAU_SKIP_PREFLIGHT !== '1') {
try {
const verifierPath = join(packageRoot, 'scripts', 'verify-deps.mjs')
if (existsSync(verifierPath)) {
const verifier = await import(pathToFileURL(verifierPath).href)
// Fast silent check first (<10ms); only show output when broken.
if (verifier.findMissingDeps(packageRoot).length > 0) {
process.stderr.write('[tau] Incomplete installation detected - repairing...\\n')
const ok = verifier.ensureDeps(packageRoot, { repair: true })
if (!ok) {
process.stderr.write('\\n' + verifier.manualFixInstructions(${JSON.stringify(pkg.name)}) + '\\n')
process.exit(1)
}
}
}
} catch {
// The preflight itself must never block startup; a genuinely broken
// tree still fails below with Node's own resolution error.
}
}
await import('./tau.mjs')
`
writeFileSync('./dist/cli.mjs', launcher)
// Stale artifact from builds that bundled directly to cli.mjs.
try { rmSync('./dist/cli.mjs.map', { force: true }) } catch { /* ignore */ }
// Report size
const outStat = readFileSync(outPath)
console.log(`✓ Built dist/tau.mjs (${(outStat.length / 1024 / 1024).toFixed(1)} MB) + dist/cli.mjs launcher`)
const nativeShellParserBuild = spawnSync(
process.execPath,
['scripts/build-native-shell-parser.mjs'],
{
stdio: 'inherit',
windowsHide: true,
},
)
if (nativeShellParserBuild.status !== 0) {
process.exit(nativeShellParserBuild.status ?? 1)
}
const nativeToolsBuild = spawnSync(
process.execPath,
['scripts/build-native-tools.mjs'],
{
stdio: 'inherit',
windowsHide: true,
},
)
if (nativeToolsBuild.status !== 0) {
process.exit(nativeToolsBuild.status ?? 1)
}