@codex-native/sdk 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs ADDED
@@ -0,0 +1,1658 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ Codex,
4
+ attachLspDiagnostics,
5
+ convertRustEventToThreadEvent,
6
+ fastEmbedInit,
7
+ getNativeBinding,
8
+ reverieIndexSemantic,
9
+ runApplyPatch
10
+ } from "./chunk-ZTUGAPWF.mjs";
11
+
12
+ // src/cli/index.ts
13
+ import process4 from "process";
14
+ import { parseArgs as parseArgs2 } from "util";
15
+ import { fileURLToPath } from "url";
16
+
17
+ // package.json
18
+ var package_default = {
19
+ name: "@codex-native/sdk",
20
+ version: "0.0.2",
21
+ description: "Native NAPI-based Codex SDK - complete standalone implementation.",
22
+ main: "dist/index.cjs",
23
+ module: "dist/index.mjs",
24
+ types: "dist/index.d.ts",
25
+ exports: {
26
+ ".": {
27
+ types: "./dist/index.d.ts",
28
+ import: "./dist/index.mjs",
29
+ require: "./dist/index.cjs"
30
+ }
31
+ },
32
+ bin: {
33
+ "codex-native": "dist/cli.cjs"
34
+ },
35
+ files: [
36
+ "dist",
37
+ "README.md",
38
+ "npm",
39
+ "*.node"
40
+ ],
41
+ dependencies: {
42
+ "@modelcontextprotocol/sdk": "^1.22.0",
43
+ "@opencode-ai/sdk": "^1.0.68",
44
+ "@toon-format/toon": "^1.0.0",
45
+ pyright: "^1.1.386",
46
+ "typescript-language-server": "^4.3.3",
47
+ "vscode-jsonrpc": "^8.2.1",
48
+ "vscode-languageserver-types": "^3.17.5"
49
+ },
50
+ scripts: {
51
+ clean: "rm -rf dist *.node npm target/release/*.node && cargo clean --release",
52
+ "clean:all": "rm -rf dist *.node npm target node_modules && cargo clean",
53
+ build: "npm run build:rust && npm run build:napi && npm run build:ts && npm run verify:build",
54
+ "build:ci": "npm run build:napi && npm run build:ts && npm run verify:build",
55
+ "build:rust": "cargo fmt && cargo clippy --release --features napi-bindings -- -D warnings",
56
+ "build:napi": "napi build --platform --release --features napi-bindings",
57
+ "build:napi:debug": "napi build --platform --features napi-bindings",
58
+ "build:ts": "tsup",
59
+ "verify:build": `node -e "const fs=require('fs');const hasNode=fs.readdirSync('.').some(f=>f.endsWith('.node'));const hasDist=fs.existsSync('dist/index.mjs')&&fs.existsSync('dist/index.d.ts');if(!hasNode||!hasDist){console.error('Build incomplete: missing .node or dist files');process.exit(1)}console.log('\u2713 Build verification passed')"`,
60
+ "create-npm-dirs": "napi create-npm-dirs",
61
+ artifacts: "napi artifacts",
62
+ prepublishOnly: "napi prepublish -t npm",
63
+ pretest: `node -e "const fs=require('fs');const hasNode=fs.readdirSync('.').some(f=>f.endsWith('.node'));if(!hasNode){process.exitCode=1;console.error('missing .node binary; run npm run build:ci');} else {console.log('\u2713 native binary present; skipping rebuild');}"`,
64
+ test: "node ./scripts/run-jest.mjs --runInBand",
65
+ "test:watch": "node ./scripts/run-jest.mjs --watch",
66
+ coverage: "node ./scripts/run-jest.mjs --coverage",
67
+ typecheck: "tsc --noEmit",
68
+ lint: "cargo fmt --check && cargo clippy --release --features napi-bindings -- -D warnings && npm run typecheck",
69
+ version: "napi version",
70
+ "release:patch": "npm version patch && npm run release",
71
+ "release:minor": "npm version minor && npm run release",
72
+ "release:major": "npm version major && npm run release",
73
+ "publish:platforms": "npm run create-npm-dirs && node scripts/publish-platform-packages.mjs",
74
+ release: "npm run build && npm run test && npm run publish:platforms && node scripts/publish-sdk.mjs",
75
+ "release:dry": "npm run build && npm run test && npm publish --dry-run"
76
+ },
77
+ napi: {
78
+ binaryName: "codex_native",
79
+ targets: [
80
+ "aarch64-apple-darwin",
81
+ "x86_64-apple-darwin",
82
+ "aarch64-unknown-linux-gnu",
83
+ "x86_64-unknown-linux-gnu",
84
+ "aarch64-unknown-linux-musl",
85
+ "x86_64-unknown-linux-musl",
86
+ "aarch64-pc-windows-msvc",
87
+ "x86_64-pc-windows-msvc"
88
+ ]
89
+ },
90
+ optionalDependencies: {
91
+ "@codex-native/sdk-darwin-arm64": "0.0.2",
92
+ "@codex-native/sdk-darwin-x64": "0.0.2",
93
+ "@codex-native/sdk-linux-arm64-gnu": "0.0.2",
94
+ "@codex-native/sdk-linux-arm64-musl": "0.0.2",
95
+ "@codex-native/sdk-linux-x64-gnu": "0.0.2",
96
+ "@codex-native/sdk-linux-x64-musl": "0.0.2",
97
+ "@codex-native/sdk-win32-arm64-msvc": "0.0.2",
98
+ "@codex-native/sdk-win32-x64-msvc": "0.0.2"
99
+ },
100
+ engines: {
101
+ node: ">=18"
102
+ },
103
+ publishConfig: {
104
+ access: "public"
105
+ },
106
+ devDependencies: {
107
+ "@babel/core": "7.25.2",
108
+ "@babel/preset-env": "7.25.4",
109
+ "@babel/preset-typescript": "7.28.5",
110
+ "@jest/globals": "^29.7.0",
111
+ "@napi-rs/cli": "^3.4.1",
112
+ "@openai/agents": "^0.3.0",
113
+ "@types/jest": "29.5.11",
114
+ "@types/node": "^22.0.0",
115
+ "babel-jest": "29.7.0",
116
+ jest: "^29.7.0",
117
+ "ts-jest": "^29.1.1",
118
+ tsup: "^8.5.0",
119
+ tsx: "^4.20.6",
120
+ typescript: "^5.7.2",
121
+ zod: "^3.25.76"
122
+ }
123
+ };
124
+
125
+ // src/cli/config.ts
126
+ import fs from "fs";
127
+ import fsPromises from "fs/promises";
128
+ import path from "path";
129
+ import { createRequire } from "module";
130
+ import { pathToFileURL } from "url";
131
+ var requireFromThisModule = createRequire(import.meta.url);
132
+ var CONFIG_CANDIDATES = [
133
+ "codex.config.js",
134
+ "codex.config.cjs",
135
+ "codex.config.mjs",
136
+ "codex.config.ts",
137
+ "codex.js",
138
+ ".codexrc.js",
139
+ ".codexrc.cjs",
140
+ ".codexrc.mjs",
141
+ ".codexrc.json"
142
+ ];
143
+ async function loadCliConfig(options) {
144
+ const warnings = [];
145
+ const discovery = await resolveConfigPath(options);
146
+ const configPath = discovery?.path ?? null;
147
+ let config = null;
148
+ if (discovery) {
149
+ const loadResult = await loadConfig(discovery, warnings);
150
+ config = loadResult ?? null;
151
+ if (config && typeof config !== "object") {
152
+ warnings.push(
153
+ `Config at ${discovery.path} must export an object. Received ${typeof config}.`
154
+ );
155
+ config = null;
156
+ }
157
+ }
158
+ const plugins = await resolvePlugins({
159
+ config,
160
+ configPath,
161
+ cliPluginPaths: options.pluginPaths ?? [],
162
+ cwd: options.cwd,
163
+ warnings
164
+ });
165
+ return {
166
+ configPath,
167
+ config,
168
+ plugins,
169
+ warnings
170
+ };
171
+ }
172
+ async function resolveConfigPath(options) {
173
+ if (options.explicitConfigPath) {
174
+ const explicitPath = path.resolve(options.cwd, options.explicitConfigPath);
175
+ if (!fs.existsSync(explicitPath)) {
176
+ throw new Error(`Config file not found at ${explicitPath}`);
177
+ }
178
+ return classifyPath(explicitPath);
179
+ }
180
+ if (options.noConfig) {
181
+ return null;
182
+ }
183
+ let currentDir = path.resolve(options.cwd);
184
+ const visited = /* @__PURE__ */ new Set();
185
+ while (!visited.has(currentDir)) {
186
+ visited.add(currentDir);
187
+ for (const candidate of CONFIG_CANDIDATES) {
188
+ const candidatePath = path.join(currentDir, candidate);
189
+ if (fs.existsSync(candidatePath) && fs.statSync(candidatePath).isFile()) {
190
+ return classifyPath(candidatePath);
191
+ }
192
+ }
193
+ const packageJsonPath = path.join(currentDir, "package.json");
194
+ if (fs.existsSync(packageJsonPath) && fs.statSync(packageJsonPath).isFile()) {
195
+ const manifest = await readJson(packageJsonPath);
196
+ if (manifest && manifest.codexNative != null) {
197
+ return { path: packageJsonPath, type: "package-json", field: "codexNative" };
198
+ }
199
+ }
200
+ const parentDir = path.dirname(currentDir);
201
+ if (parentDir === currentDir) {
202
+ break;
203
+ }
204
+ currentDir = parentDir;
205
+ }
206
+ return null;
207
+ }
208
+ async function loadConfig(discovery, warnings) {
209
+ if (discovery.type === "package-json") {
210
+ const manifest = await readJson(discovery.path);
211
+ if (!manifest) {
212
+ warnings.push(`Failed to parse ${discovery.path}; ignoring config.`);
213
+ return null;
214
+ }
215
+ const raw = manifest[discovery.field];
216
+ if (typeof raw === "string") {
217
+ const baseDir = path.dirname(discovery.path);
218
+ const nestedPath = path.resolve(baseDir, raw);
219
+ if (!fs.existsSync(nestedPath)) {
220
+ throw new Error(
221
+ `Config path "${raw}" referenced by ${discovery.field} in ${discovery.path} was not found.`
222
+ );
223
+ }
224
+ return loadConfig({ path: nestedPath, type: "file" }, warnings);
225
+ }
226
+ if (typeof raw === "object" && raw !== null) {
227
+ return raw;
228
+ }
229
+ warnings.push(
230
+ `The ${discovery.field} field in ${discovery.path} must be an object or path string.`
231
+ );
232
+ return null;
233
+ }
234
+ const ext = path.extname(discovery.path).toLowerCase();
235
+ if (ext === ".json") {
236
+ const json = await readJson(discovery.path);
237
+ if (json === null) {
238
+ warnings.push(`Failed to parse JSON config at ${discovery.path}`);
239
+ }
240
+ return json;
241
+ }
242
+ if (ext === ".js" || ext === ".cjs") {
243
+ return extractModuleDefault(await loadCommonJsModule(discovery.path));
244
+ }
245
+ if (ext === ".mjs") {
246
+ return extractModuleDefault(await importModule(discovery.path));
247
+ }
248
+ if (ext === ".ts") {
249
+ return extractModuleDefault(await loadTypeScriptModule(discovery.path, warnings));
250
+ }
251
+ throw new Error(`Unsupported config extension "${ext}" at ${discovery.path}`);
252
+ }
253
+ async function resolvePlugins(params) {
254
+ const plugins = [];
255
+ const { config, configPath, cliPluginPaths, cwd, warnings } = params;
256
+ const configDir = configPath ? path.dirname(configPath) : cwd;
257
+ const rawConfigPlugins = config?.plugins;
258
+ const configPlugins = Array.isArray(rawConfigPlugins) ? rawConfigPlugins : [];
259
+ for (const spec of configPlugins) {
260
+ if (typeof spec === "string") {
261
+ const loaded = await loadPlugin(spec, configDir, "config", warnings);
262
+ if (loaded) {
263
+ plugins.push(loaded);
264
+ }
265
+ } else if (spec != null) {
266
+ plugins.push({
267
+ source: "config",
268
+ spec: "<inline>",
269
+ plugin: spec
270
+ });
271
+ }
272
+ }
273
+ for (const spec of cliPluginPaths) {
274
+ const loaded = await loadPlugin(spec, cwd, "cli", warnings);
275
+ if (loaded) {
276
+ plugins.push(loaded);
277
+ }
278
+ }
279
+ return plugins;
280
+ }
281
+ async function loadPlugin(spec, baseDir, source, warnings) {
282
+ try {
283
+ const resolved = resolveModule(spec, baseDir);
284
+ const moduleExports = await loadModuleForPath(resolved);
285
+ return {
286
+ source,
287
+ spec,
288
+ resolvedPath: resolved,
289
+ plugin: extractModuleDefault(moduleExports)
290
+ };
291
+ } catch (err) {
292
+ warnings.push(`Failed to load plugin "${spec}": ${err.message}`);
293
+ return null;
294
+ }
295
+ }
296
+ async function loadModuleForPath(modulePath) {
297
+ const ext = path.extname(modulePath).toLowerCase();
298
+ if (ext === ".cjs") {
299
+ return loadCommonJsModule(modulePath);
300
+ }
301
+ if (ext === ".mjs") {
302
+ return importModule(modulePath);
303
+ }
304
+ if (ext === ".ts") {
305
+ return loadTypeScriptModule(modulePath);
306
+ }
307
+ if (ext === ".json") {
308
+ return readJson(modulePath);
309
+ }
310
+ if (ext === ".js") {
311
+ try {
312
+ return loadCommonJsModule(modulePath);
313
+ } catch (err) {
314
+ if (err instanceof Error && err.message.includes("ERR_REQUIRE_ESM")) {
315
+ return importModule(modulePath);
316
+ }
317
+ throw err;
318
+ }
319
+ }
320
+ return loadCommonJsModule(modulePath);
321
+ }
322
+ async function loadCommonJsModule(modulePath) {
323
+ return requireFromThisModule(modulePath);
324
+ }
325
+ async function importModule(modulePath) {
326
+ const href = pathToFileURL(modulePath).href;
327
+ return import(href);
328
+ }
329
+ async function loadTypeScriptModule(modulePath, warnings) {
330
+ try {
331
+ const { register } = requireFromThisModule("tsx/cjs/api");
332
+ const unregister = register({ transpileOnly: true });
333
+ try {
334
+ return requireFromThisModule(modulePath);
335
+ } finally {
336
+ await maybeCall(unregister);
337
+ }
338
+ } catch (cjsError) {
339
+ try {
340
+ const tsxEsmSpecifier = "tsx/esm/api";
341
+ const apiModule = await import(tsxEsmSpecifier);
342
+ const unregister = typeof apiModule.register === "function" ? apiModule.register({ transpileOnly: true }) : apiModule.default({ transpileOnly: true });
343
+ try {
344
+ return importModule(modulePath);
345
+ } finally {
346
+ await maybeCall(unregister);
347
+ }
348
+ } catch (esmError) {
349
+ const message = [
350
+ `Failed to load TypeScript module ${modulePath}.`,
351
+ 'Install the "tsx" package or convert the config to JavaScript.'
352
+ ].join(" ");
353
+ if (warnings) {
354
+ warnings.push(message);
355
+ return null;
356
+ }
357
+ throw new Error(message);
358
+ }
359
+ }
360
+ }
361
+ async function readJson(filePath) {
362
+ try {
363
+ const raw = await fsPromises.readFile(filePath, "utf8");
364
+ return JSON.parse(raw);
365
+ } catch {
366
+ return null;
367
+ }
368
+ }
369
+ function extractModuleDefault(module) {
370
+ if (module && typeof module === "object" && "default" in module) {
371
+ const value = module.default;
372
+ if (value !== void 0) {
373
+ return value;
374
+ }
375
+ }
376
+ return module;
377
+ }
378
+ function resolveModule(specifier, baseDir) {
379
+ if (path.isAbsolute(specifier)) {
380
+ return specifier;
381
+ }
382
+ return requireFromThisModule.resolve(specifier, { paths: [baseDir] });
383
+ }
384
+ function classifyPath(filePath) {
385
+ if (path.basename(filePath) === "package.json") {
386
+ return { path: filePath, type: "package-json", field: "codexNative" };
387
+ }
388
+ return { path: filePath, type: "file" };
389
+ }
390
+ async function maybeCall(candidate) {
391
+ if (typeof candidate === "function") {
392
+ await Promise.resolve(candidate());
393
+ }
394
+ }
395
+
396
+ // src/cli/run.ts
397
+ import fs2 from "fs";
398
+ import fsPromises2 from "fs/promises";
399
+ import path3 from "path";
400
+ import process2 from "process";
401
+
402
+ // src/cli/optionParsers.ts
403
+ var SANDBOX_MODE_VALUES = ["read-only", "workspace-write", "danger-full-access"];
404
+ var APPROVAL_MODE_VALUES = ["never", "on-request", "on-failure", "untrusted"];
405
+ function isSandboxMode(value) {
406
+ return SANDBOX_MODE_VALUES.includes(value);
407
+ }
408
+ function isApprovalMode(value) {
409
+ return APPROVAL_MODE_VALUES.includes(value);
410
+ }
411
+ function parseSandboxModeFlag(value, origin) {
412
+ if (value === void 0) {
413
+ return void 0;
414
+ }
415
+ if (isSandboxMode(value)) {
416
+ return value;
417
+ }
418
+ throw new Error(
419
+ `Invalid sandbox mode "${value}" from ${origin}. Valid values: ${SANDBOX_MODE_VALUES.join(
420
+ ", "
421
+ )}.`
422
+ );
423
+ }
424
+ function parseApprovalModeFlag(value, origin) {
425
+ if (value === void 0) {
426
+ return void 0;
427
+ }
428
+ if (isApprovalMode(value)) {
429
+ return value;
430
+ }
431
+ throw new Error(
432
+ `Invalid approval mode "${value}" from ${origin}. Valid values: ${APPROVAL_MODE_VALUES.join(
433
+ ", "
434
+ )}.`
435
+ );
436
+ }
437
+
438
+ // src/cli/hooks.ts
439
+ function emitWarnings(warnings, fromIndex = 0) {
440
+ for (let i = fromIndex; i < warnings.length; i += 1) {
441
+ const message = warnings[i];
442
+ process.stderr.write(`[codex-native] Warning: ${message}
443
+ `);
444
+ }
445
+ }
446
+ async function runBeforeStartHooks(hooks, context, warnings) {
447
+ for (const hook of hooks) {
448
+ try {
449
+ await hook.callback(context);
450
+ } catch (error) {
451
+ warnings.push(
452
+ `beforeStart hook "${hook.source}" threw: ${error.message ?? String(error)}`
453
+ );
454
+ }
455
+ }
456
+ }
457
+ async function runEventHooks(hooks, event, context, warnings) {
458
+ for (const hook of hooks) {
459
+ try {
460
+ await hook.callback(event, context);
461
+ } catch (error) {
462
+ warnings.push(`onEvent hook "${hook.source}" threw: ${error.message ?? String(error)}`);
463
+ }
464
+ }
465
+ }
466
+
467
+ // src/cli/elevatedDefaults.ts
468
+ import path2 from "path";
469
+ var FULL_ACCESS_SANDBOX = "workspace-write";
470
+ var FULL_ACCESS_APPROVAL = "never";
471
+ function applyElevatedRunDefaults(request, cwd) {
472
+ const workingDirectory = resolveWorkingDirectory(request.workingDirectory, cwd);
473
+ request.workingDirectory = workingDirectory;
474
+ ensureSandboxModes(request);
475
+ request.workspaceWriteOptions = ensureWorkspaceWriteOptions(
476
+ request.workspaceWriteOptions,
477
+ workingDirectory
478
+ );
479
+ }
480
+ function applyElevatedTuiDefaults(params) {
481
+ const { request, thread, cwd } = params;
482
+ const workingDirectory = resolveWorkingDirectory(
483
+ request.workingDirectory ?? thread.workingDirectory,
484
+ cwd
485
+ );
486
+ request.workingDirectory = workingDirectory;
487
+ thread.workingDirectory = workingDirectory;
488
+ ensureSandboxModes(request);
489
+ thread.sandboxMode = request.sandboxMode ?? thread.sandboxMode ?? FULL_ACCESS_SANDBOX;
490
+ thread.approvalMode = request.approvalMode ?? thread.approvalMode ?? FULL_ACCESS_APPROVAL;
491
+ thread.workspaceWriteOptions = ensureWorkspaceWriteOptions(
492
+ thread.workspaceWriteOptions,
493
+ workingDirectory
494
+ );
495
+ }
496
+ function ensureSandboxModes(target) {
497
+ if (!target.sandboxMode) {
498
+ target.sandboxMode = FULL_ACCESS_SANDBOX;
499
+ }
500
+ if (!target.approvalMode) {
501
+ target.approvalMode = FULL_ACCESS_APPROVAL;
502
+ }
503
+ }
504
+ function ensureWorkspaceWriteOptions(options, workingDirectory) {
505
+ const resolved = path2.resolve(workingDirectory);
506
+ const writableRoots = new Set(options?.writableRoots ?? []);
507
+ writableRoots.add(resolved);
508
+ return {
509
+ ...options,
510
+ networkAccess: options?.networkAccess ?? true,
511
+ writableRoots: Array.from(writableRoots)
512
+ };
513
+ }
514
+ function resolveWorkingDirectory(candidate, cwd) {
515
+ if (!candidate || candidate.trim().length === 0) {
516
+ return path2.resolve(cwd);
517
+ }
518
+ return path2.isAbsolute(candidate) ? candidate : path2.resolve(cwd, candidate);
519
+ }
520
+
521
+ // src/cli/lspBridge.ts
522
+ var RunCommandThreadRelay = class {
523
+ constructor(binding, initialThreadId) {
524
+ this.binding = binding;
525
+ this.threadId = initialThreadId ?? null;
526
+ }
527
+ listeners = /* @__PURE__ */ new Set();
528
+ threadId;
529
+ onEvent(listener) {
530
+ this.listeners.add(listener);
531
+ return () => {
532
+ this.listeners.delete(listener);
533
+ };
534
+ }
535
+ async sendBackgroundEvent(message) {
536
+ const trimmed = typeof message === "string" ? message.trim() : "";
537
+ if (!trimmed) {
538
+ throw new Error("Background event message must be a non-empty string");
539
+ }
540
+ if (!this.threadId) {
541
+ throw new Error("Cannot emit a background event before the thread has started");
542
+ }
543
+ if (typeof this.binding.emitBackgroundEvent !== "function") {
544
+ throw new Error("emitBackgroundEvent is not available in this build");
545
+ }
546
+ await this.binding.emitBackgroundEvent({ threadId: this.threadId, message: trimmed });
547
+ }
548
+ handleEvent(event) {
549
+ if (event.type === "thread.started" && typeof event.thread_id === "string") {
550
+ this.threadId = event.thread_id;
551
+ }
552
+ for (const listener of this.listeners) {
553
+ try {
554
+ listener(event);
555
+ } catch (error) {
556
+ console.warn("[codex-native] LSP listener failed", error);
557
+ }
558
+ }
559
+ }
560
+ setThreadId(id) {
561
+ if (id) {
562
+ this.threadId = id;
563
+ }
564
+ }
565
+ };
566
+ function createRunCommandLspBridge(params) {
567
+ try {
568
+ const relay = new RunCommandThreadRelay(params.binding, params.initialThreadId);
569
+ const detach = attachLspDiagnostics(relay, {
570
+ workingDirectory: params.workingDirectory,
571
+ waitForDiagnostics: true
572
+ });
573
+ relay.setThreadId(params.initialThreadId);
574
+ return {
575
+ handleEvent: (event) => relay.handleEvent(event),
576
+ dispose: () => detach()
577
+ };
578
+ } catch (error) {
579
+ const message = error instanceof Error ? error.message : String(error);
580
+ console.warn(`[codex-native] Failed to initialize LSP diagnostics bridge: ${message}`);
581
+ return null;
582
+ }
583
+ }
584
+
585
+ // src/cli/run.ts
586
+ async function executeRunCommand(argv, context) {
587
+ const { combinedConfig } = context;
588
+ emitWarnings(combinedConfig.warnings);
589
+ const warningCount = combinedConfig.warnings.length;
590
+ const prompt = await resolvePrompt(argv, combinedConfig.runDefaults.prompt, context.cwd);
591
+ const request = await buildRunRequest({
592
+ prompt,
593
+ argv,
594
+ combinedDefaults: combinedConfig.runDefaults,
595
+ cwd: context.cwd
596
+ });
597
+ if (!request.skipGitRepoCheck) {
598
+ await assertTrustedDirectory(request.workingDirectory);
599
+ }
600
+ validateModel(request.model, request.oss === true);
601
+ const hookContext = {
602
+ command: "run",
603
+ cwd: context.cwd,
604
+ options: argv
605
+ };
606
+ await runBeforeStartHooks(combinedConfig.beforeStartHooks, hookContext, combinedConfig.warnings);
607
+ const binding = getNativeBinding();
608
+ if (!binding) {
609
+ throw new Error("Native N-API binding is not available.");
610
+ }
611
+ const queue = new AsyncQueue();
612
+ let conversationId = null;
613
+ const lspBridge = createRunCommandLspBridge({
614
+ binding,
615
+ workingDirectory: request.workingDirectory ?? context.cwd,
616
+ initialThreadId: request.threadId
617
+ });
618
+ const handleEvent = async (eventJson) => {
619
+ if (!eventJson) {
620
+ return;
621
+ }
622
+ process2.stdout.write(eventJson);
623
+ process2.stdout.write("\n");
624
+ let eventPayload = eventJson;
625
+ try {
626
+ eventPayload = JSON.parse(eventJson);
627
+ } catch {
628
+ }
629
+ conversationId ??= extractConversationId(eventPayload);
630
+ const threadEvent = toThreadEvent(eventPayload);
631
+ if (threadEvent && lspBridge) {
632
+ lspBridge.handleEvent(threadEvent);
633
+ }
634
+ await runEventHooks(
635
+ combinedConfig.onEventHooks,
636
+ eventPayload,
637
+ hookContext,
638
+ combinedConfig.warnings
639
+ );
640
+ };
641
+ let runPromise = Promise.resolve();
642
+ runPromise = binding.runThreadStream(request, (err, eventJson) => {
643
+ if (err) {
644
+ queue.fail(err);
645
+ return;
646
+ }
647
+ queue.push(eventJson ?? null);
648
+ }).then(
649
+ () => queue.end(),
650
+ (error) => {
651
+ queue.fail(error);
652
+ }
653
+ );
654
+ let loopError;
655
+ try {
656
+ for await (const eventJson of queue) {
657
+ try {
658
+ await handleEvent(eventJson);
659
+ } catch (error) {
660
+ combinedConfig.warnings.push(
661
+ `Event handler failed: ${error.message ?? String(error)}`
662
+ );
663
+ }
664
+ }
665
+ await runPromise;
666
+ } catch (error) {
667
+ loopError = error;
668
+ throw error;
669
+ } finally {
670
+ queue.end();
671
+ if (loopError) {
672
+ await runPromise.catch(() => {
673
+ });
674
+ }
675
+ if (lspBridge) {
676
+ lspBridge.dispose();
677
+ }
678
+ }
679
+ if (conversationId) {
680
+ process2.stdout.write(`
681
+ To resume, run: codex-native tui --resume ${conversationId}
682
+ `);
683
+ }
684
+ emitWarnings(combinedConfig.warnings, warningCount);
685
+ }
686
+ function toThreadEvent(payload) {
687
+ if (!payload || typeof payload !== "object") {
688
+ return null;
689
+ }
690
+ try {
691
+ return convertRustEventToThreadEvent(payload);
692
+ } catch {
693
+ return null;
694
+ }
695
+ }
696
+ async function resolvePrompt(argv, defaultPrompt, cwd) {
697
+ if (argv.prompt && argv.prompt.trim().length > 0) {
698
+ return argv.prompt;
699
+ }
700
+ if (defaultPrompt && defaultPrompt.trim().length > 0) {
701
+ return defaultPrompt;
702
+ }
703
+ const stdinPrompt = await readPromptFromStdin();
704
+ if (stdinPrompt && stdinPrompt.trim().length > 0) {
705
+ return stdinPrompt;
706
+ }
707
+ if (argv.threadId) {
708
+ return "";
709
+ }
710
+ const baseMessage = "No prompt provided. Supply a prompt or pipe one via stdin.";
711
+ if (process2.stdin.isTTY) {
712
+ throw new Error(baseMessage);
713
+ }
714
+ throw new Error(baseMessage);
715
+ }
716
+ async function buildRunRequest(params) {
717
+ const { prompt, argv, combinedDefaults, cwd } = params;
718
+ const request = {
719
+ ...combinedDefaults,
720
+ prompt
721
+ };
722
+ if (combinedDefaults.images) {
723
+ request.images = [...combinedDefaults.images];
724
+ }
725
+ if (combinedDefaults.workspaceWriteOptions) {
726
+ request.workspaceWriteOptions = { ...combinedDefaults.workspaceWriteOptions };
727
+ }
728
+ if (argv.model !== void 0) request.model = argv.model;
729
+ if (argv.oss !== void 0) request.oss = argv.oss;
730
+ const sandboxMode = parseSandboxModeFlag(argv.sandbox, "--sandbox");
731
+ if (sandboxMode !== void 0) {
732
+ request.sandboxMode = sandboxMode;
733
+ }
734
+ const approvalMode = parseApprovalModeFlag(argv.approval, "--approval");
735
+ if (approvalMode !== void 0) {
736
+ request.approvalMode = approvalMode;
737
+ }
738
+ if (argv.threadId !== void 0) request.threadId = argv.threadId;
739
+ if (argv.baseUrl !== void 0) request.baseUrl = argv.baseUrl;
740
+ if (argv.apiKey !== void 0) request.apiKey = argv.apiKey;
741
+ if (argv.linuxSandboxPath !== void 0) request.linuxSandboxPath = argv.linuxSandboxPath;
742
+ if (argv.fullAuto !== void 0) request.fullAuto = argv.fullAuto;
743
+ if (argv.skipGitRepoCheck !== void 0) request.skipGitRepoCheck = argv.skipGitRepoCheck;
744
+ if (argv.cd !== void 0) request.workingDirectory = argv.cd;
745
+ if (argv.reviewMode !== void 0) request.reviewMode = argv.reviewMode;
746
+ if (argv.reviewHint !== void 0) request.reviewHint = argv.reviewHint;
747
+ const images = [
748
+ ...Array.isArray(request.images) ? request.images : [],
749
+ ...argv.image ?? []
750
+ ];
751
+ request.images = images.length > 0 ? images : void 0;
752
+ if (argv.schema) {
753
+ request.outputSchema = await readJsonFile(argv.schema);
754
+ }
755
+ applyElevatedRunDefaults(request, cwd);
756
+ return request;
757
+ }
758
+ async function readJsonFile(filePath) {
759
+ const absolute = path3.resolve(process2.cwd(), filePath);
760
+ const data = await fsPromises2.readFile(absolute, "utf8");
761
+ try {
762
+ return JSON.parse(data);
763
+ } catch (error) {
764
+ throw new Error(
765
+ `Failed to parse JSON schema from ${absolute}: ${error.message ?? error}`
766
+ );
767
+ }
768
+ }
769
+ async function readPromptFromStdin() {
770
+ if (process2.stdin.isTTY) {
771
+ return null;
772
+ }
773
+ const chunks = [];
774
+ for await (const chunk of process2.stdin) {
775
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
776
+ }
777
+ if (chunks.length === 0) {
778
+ return null;
779
+ }
780
+ return Buffer.concat(chunks).toString("utf8").trimEnd();
781
+ }
782
+ function extractConversationId(eventPayload) {
783
+ if (!eventPayload || typeof eventPayload !== "object") {
784
+ return null;
785
+ }
786
+ const record = eventPayload;
787
+ if (typeof record.session_id === "string") {
788
+ return record.session_id;
789
+ }
790
+ const sessionConfigured = record.SessionConfigured ?? record.sessionConfigured;
791
+ if (sessionConfigured && typeof sessionConfigured === "object") {
792
+ const configuredSessionId = sessionConfigured.session_id;
793
+ if (typeof configuredSessionId === "string") {
794
+ return configuredSessionId;
795
+ }
796
+ }
797
+ const nestedSession = typeof record.session === "object" && record.session ? record.session.id : void 0;
798
+ if (typeof nestedSession === "string") {
799
+ return nestedSession;
800
+ }
801
+ return null;
802
+ }
803
+ function validateModel(model, oss) {
804
+ if (!model) return;
805
+ const trimmed = String(model).trim();
806
+ if (oss) {
807
+ if (!trimmed.startsWith("gpt-oss:")) {
808
+ throw new Error(
809
+ `Invalid model "${trimmed}" for OSS mode. Use models prefixed with "gpt-oss:", e.g. "gpt-oss:20b".`
810
+ );
811
+ }
812
+ return;
813
+ }
814
+ const allowed = /* @__PURE__ */ new Set([
815
+ // GPT models
816
+ "gpt-5",
817
+ "gpt-5-codex",
818
+ "gpt-5-codex-mini",
819
+ "gpt-5.1",
820
+ "gpt-5.1-codex",
821
+ "gpt-5.1-codex-mini",
822
+ // Claude models
823
+ "claude-sonnet-4-5-20250929",
824
+ "claude-sonnet-4-20250514",
825
+ "claude-opus-4-20250514"
826
+ ]);
827
+ if (!allowed.has(trimmed) && !trimmed.startsWith("claude-") && !trimmed.startsWith("gpt-")) {
828
+ throw new Error(
829
+ `Invalid model "${trimmed}". Supported models: ${Array.from(allowed).map((m) => `"${m}"`).join(", ")}, or any model starting with "claude-" or "gpt-".`
830
+ );
831
+ }
832
+ }
833
+ async function assertTrustedDirectory(workingDirectory) {
834
+ const directory = workingDirectory ? path3.resolve(workingDirectory) : process2.cwd();
835
+ if (await findGitRoot(directory)) {
836
+ return;
837
+ }
838
+ throw new Error(
839
+ "Not inside a trusted directory and --skip-git-repo-check was not specified."
840
+ );
841
+ }
842
+ async function findGitRoot(startDir) {
843
+ let current = path3.resolve(startDir);
844
+ while (true) {
845
+ const gitPath = path3.join(current, ".git");
846
+ if (fs2.existsSync(gitPath)) {
847
+ try {
848
+ const stats = await fsPromises2.stat(gitPath);
849
+ if (stats.isDirectory() || stats.isFile()) {
850
+ return current;
851
+ }
852
+ } catch {
853
+ }
854
+ }
855
+ const parent = path3.dirname(current);
856
+ if (parent === current) {
857
+ break;
858
+ }
859
+ current = parent;
860
+ }
861
+ return null;
862
+ }
863
+ var AsyncQueue = class {
864
+ buffer = [];
865
+ waiters = [];
866
+ ended = false;
867
+ error;
868
+ push(value) {
869
+ if (this.ended) return;
870
+ if (value === null) {
871
+ return;
872
+ }
873
+ if (this.waiters.length > 0) {
874
+ const waiter = this.waiters.shift();
875
+ waiter.resolve({ value, done: false });
876
+ return;
877
+ }
878
+ this.buffer.push(value);
879
+ }
880
+ end() {
881
+ if (this.ended) return;
882
+ this.ended = true;
883
+ const waiters = this.waiters;
884
+ this.waiters = [];
885
+ for (const waiter of waiters) {
886
+ waiter.resolve({ value: void 0, done: true });
887
+ }
888
+ }
889
+ fail(error) {
890
+ if (this.ended) return;
891
+ this.error = error;
892
+ this.ended = true;
893
+ const waiters = this.waiters;
894
+ this.waiters = [];
895
+ for (const waiter of waiters) {
896
+ waiter.reject(error);
897
+ }
898
+ }
899
+ async next() {
900
+ if (this.buffer.length > 0) {
901
+ const value = this.buffer.shift();
902
+ return { value, done: false };
903
+ }
904
+ if (this.error) {
905
+ return Promise.reject(this.error);
906
+ }
907
+ if (this.ended) {
908
+ return { value: void 0, done: true };
909
+ }
910
+ return new Promise((resolve, reject) => {
911
+ this.waiters.push({ resolve, reject });
912
+ });
913
+ }
914
+ [Symbol.asyncIterator]() {
915
+ return this;
916
+ }
917
+ };
918
+
919
+ // src/cli/tui.ts
920
+ import process3 from "process";
921
+ async function executeTuiCommand(argv, context) {
922
+ if (!process3.stdout.isTTY || !process3.stdin.isTTY) {
923
+ throw new Error("The interactive TUI requires an interactive terminal (TTY).");
924
+ }
925
+ const { combinedConfig } = context;
926
+ emitWarnings(combinedConfig.warnings);
927
+ const warningCount = combinedConfig.warnings.length;
928
+ const { request, thread: threadOptions } = buildTuiConfig({
929
+ argv,
930
+ defaults: combinedConfig.tuiDefaults,
931
+ cwd: context.cwd
932
+ });
933
+ applyElevatedTuiDefaults({ request, thread: threadOptions, cwd: context.cwd });
934
+ const hookContext = {
935
+ command: "tui",
936
+ cwd: context.cwd,
937
+ options: argv
938
+ };
939
+ await runBeforeStartHooks(combinedConfig.beforeStartHooks, hookContext, combinedConfig.warnings);
940
+ const codex = new Codex({
941
+ baseUrl: request.baseUrl,
942
+ apiKey: request.apiKey,
943
+ preserveRegisteredTools: true
944
+ });
945
+ const thread = codex.startThread(threadOptions);
946
+ const exitInfo = await thread.tui(request);
947
+ if (exitInfo.conversationId) {
948
+ process3.stdout.write(`
949
+ Conversation ID: ${exitInfo.conversationId}
950
+ `);
951
+ }
952
+ if (exitInfo.updateAction) {
953
+ process3.stdout.write(
954
+ `Update available (${exitInfo.updateAction.kind}): ${exitInfo.updateAction.command}
955
+ `
956
+ );
957
+ }
958
+ emitWarnings(combinedConfig.warnings, warningCount);
959
+ }
960
+ function buildTuiConfig(params) {
961
+ const { argv, defaults, cwd } = params;
962
+ const request = {
963
+ ...defaults
964
+ };
965
+ if (argv.prompt !== void 0) request.prompt = argv.prompt;
966
+ if (argv.model !== void 0) request.model = argv.model;
967
+ if (argv.oss !== void 0) request.oss = argv.oss;
968
+ const sandboxMode = parseSandboxModeFlag(argv.sandbox, "--sandbox");
969
+ if (sandboxMode !== void 0) {
970
+ request.sandboxMode = sandboxMode;
971
+ }
972
+ const approvalMode = parseApprovalModeFlag(argv.approval, "--approval");
973
+ if (approvalMode !== void 0) {
974
+ request.approvalMode = approvalMode;
975
+ }
976
+ if (argv.resume !== void 0) request.resumeSessionId = argv.resume;
977
+ if (argv.resumeLast !== void 0) request.resumeLast = argv.resumeLast;
978
+ if (argv.resumePicker !== void 0) request.resumePicker = argv.resumePicker;
979
+ if (argv.fullAuto !== void 0) request.fullAuto = argv.fullAuto;
980
+ if (argv.dangerouslyBypassApprovalsAndSandbox !== void 0) {
981
+ request.dangerouslyBypassApprovalsAndSandbox = argv.dangerouslyBypassApprovalsAndSandbox;
982
+ }
983
+ if (argv.cd !== void 0) request.workingDirectory = argv.cd;
984
+ if (argv.configProfile !== void 0) request.configProfile = argv.configProfile;
985
+ if (argv.webSearch !== void 0) request.webSearch = argv.webSearch;
986
+ if (argv.linuxSandboxPath !== void 0) request.linuxSandboxPath = argv.linuxSandboxPath;
987
+ if (argv.baseUrl !== void 0) request.baseUrl = argv.baseUrl;
988
+ if (argv.apiKey !== void 0) request.apiKey = argv.apiKey;
989
+ if (argv.configOverrides) {
990
+ const defaultsOverrides = Array.isArray(request.configOverrides) ? [...request.configOverrides] : [];
991
+ request.configOverrides = [...defaultsOverrides, ...argv.configOverrides];
992
+ }
993
+ if (argv.addDir) {
994
+ const defaultsAddDir = Array.isArray(request.addDir) ? [...request.addDir] : [];
995
+ request.addDir = [...defaultsAddDir, ...argv.addDir];
996
+ }
997
+ if (argv.image) {
998
+ const defaultsImages = Array.isArray(request.images) ? [...request.images] : [];
999
+ request.images = [...defaultsImages, ...argv.image];
1000
+ }
1001
+ const thread = {
1002
+ model: request.model,
1003
+ oss: request.oss,
1004
+ sandboxMode: request.sandboxMode,
1005
+ approvalMode: request.approvalMode,
1006
+ workingDirectory: request.workingDirectory ?? cwd,
1007
+ skipGitRepoCheck: false
1008
+ };
1009
+ return { request, thread };
1010
+ }
1011
+
1012
+ // src/cli/reverie.ts
1013
+ import path4 from "path";
1014
+ import os from "os";
1015
+ import { parseArgs } from "util";
1016
+ var DEFAULT_MODEL = "mixedbread-ai/mxbai-embed-large-v1";
1017
+ var INDEX_OPTION_DEFS = {
1018
+ "codex-home": { type: "string" },
1019
+ "project-root": { type: "string" },
1020
+ limit: { type: "string" },
1021
+ "max-candidates": { type: "string" },
1022
+ "batch-size": { type: "string" },
1023
+ normalize: { type: "boolean" },
1024
+ cache: { type: "boolean" },
1025
+ "embed-model": { type: "string" },
1026
+ "embed-cache-dir": { type: "string" },
1027
+ "embed-max-length": { type: "string" },
1028
+ "no-progress": { type: "boolean" },
1029
+ "skip-embed-init": { type: "boolean" }
1030
+ };
1031
+ async function executeReverieCommand(args) {
1032
+ const [first, ...rest] = args;
1033
+ const isFlag = first?.startsWith("-");
1034
+ const command = !first || isFlag ? "index" : first;
1035
+ const tail = !first || isFlag ? args : rest;
1036
+ if (command !== "index") {
1037
+ throw new Error(`Unknown reverie command '${command}'. Supported subcommands: index`);
1038
+ }
1039
+ await runReverieIndex(tail);
1040
+ }
1041
+ async function runReverieIndex(args) {
1042
+ const { values } = parseArgs({ args, options: INDEX_OPTION_DEFS, allowPositionals: false, strict: true });
1043
+ const codexHome = resolveCodexHome(values["codex-home"]);
1044
+ const projectRoot = resolveProjectRoot(values["project-root"]);
1045
+ const limit = parseOptionalInt(values.limit);
1046
+ const maxCandidates = parseOptionalInt(values["max-candidates"]);
1047
+ const batchSize = parseOptionalInt(values["batch-size"]);
1048
+ const embedMaxLength = parseOptionalInt(values["embed-max-length"]);
1049
+ const normalize = typeof values.normalize === "boolean" ? values.normalize : void 0;
1050
+ const cache = typeof values.cache === "boolean" ? values.cache : void 0;
1051
+ const embedModel = typeof values["embed-model"] === "string" ? values["embed-model"] : DEFAULT_MODEL;
1052
+ const embedCacheDir = typeof values["embed-cache-dir"] === "string" ? values["embed-cache-dir"] : void 0;
1053
+ const showDownloadProgress = values["no-progress"] ? false : true;
1054
+ const skipEmbedInit = values["skip-embed-init"] === true;
1055
+ if (!skipEmbedInit) {
1056
+ await fastEmbedInit({
1057
+ model: embedModel,
1058
+ cacheDir: embedCacheDir ? path4.resolve(embedCacheDir) : defaultCacheDir(),
1059
+ maxLength: embedMaxLength ?? void 0,
1060
+ showDownloadProgress
1061
+ });
1062
+ }
1063
+ const options = {
1064
+ limit,
1065
+ maxCandidates,
1066
+ projectRoot,
1067
+ batchSize,
1068
+ normalize,
1069
+ cache
1070
+ };
1071
+ console.log(`\u{1F4C2} Codex home: ${codexHome}`);
1072
+ console.log(`\u{1F4C1} Project root: ${projectRoot}`);
1073
+ const stats = await reverieIndexSemantic(codexHome, options);
1074
+ console.log(
1075
+ `\u2705 Indexed ${stats.documentsEmbedded} conversation(s) across ${stats.batches} batch(es); cache warmed at ${projectRoot}`
1076
+ );
1077
+ }
1078
+ function resolveCodexHome(explicit) {
1079
+ if (explicit) {
1080
+ return path4.resolve(explicit);
1081
+ }
1082
+ if (process.env.CODEX_HOME) {
1083
+ return process.env.CODEX_HOME;
1084
+ }
1085
+ const home = os.homedir() || process.cwd();
1086
+ return path4.join(home, ".codex");
1087
+ }
1088
+ function resolveProjectRoot(explicit) {
1089
+ if (explicit) {
1090
+ return path4.resolve(explicit);
1091
+ }
1092
+ return process.cwd();
1093
+ }
1094
+ function parseOptionalInt(value) {
1095
+ if (typeof value === "string" && value.trim().length > 0) {
1096
+ const parsed = Number(value);
1097
+ if (!Number.isNaN(parsed)) {
1098
+ return parsed;
1099
+ }
1100
+ }
1101
+ return void 0;
1102
+ }
1103
+ function defaultCacheDir() {
1104
+ if (process.env.CODEX_EMBED_CACHE) {
1105
+ return path4.resolve(process.env.CODEX_EMBED_CACHE);
1106
+ }
1107
+ return path4.join(os.tmpdir(), "codex-embed-cache");
1108
+ }
1109
+
1110
+ // src/cli/runtime.ts
1111
+ async function buildCombinedConfig(params) {
1112
+ const { cwd, config } = params;
1113
+ const warnings = [...config.warnings];
1114
+ const combined = {
1115
+ runDefaults: {},
1116
+ tuiDefaults: {},
1117
+ tools: [],
1118
+ interceptors: [],
1119
+ approval: void 0,
1120
+ beforeStartHooks: [],
1121
+ onEventHooks: [],
1122
+ warnings,
1123
+ allowReservedInterceptors: false
1124
+ };
1125
+ const pluginContext = { cwd, configPath: config.configPath };
1126
+ if (config.config) {
1127
+ accumulateConfig({
1128
+ combined,
1129
+ config: config.config,
1130
+ source: config.configPath ?? "config",
1131
+ warnings
1132
+ });
1133
+ }
1134
+ for (const loaded of config.plugins) {
1135
+ const pluginConfig = await evaluatePlugin(loaded, pluginContext, warnings);
1136
+ if (pluginConfig) {
1137
+ accumulateConfig({
1138
+ combined,
1139
+ config: pluginConfig,
1140
+ source: loaded.spec,
1141
+ warnings
1142
+ });
1143
+ }
1144
+ }
1145
+ combined.warnings = warnings;
1146
+ return combined;
1147
+ }
1148
+ function applyNativeRegistrations(combined) {
1149
+ const binding = getNativeBinding();
1150
+ if (!binding) {
1151
+ throw new Error("Native binding is not available.");
1152
+ }
1153
+ binding.clearRegisteredTools();
1154
+ const seenTools = /* @__PURE__ */ new Set();
1155
+ for (const tool of combined.tools) {
1156
+ const { handler, ...info } = tool;
1157
+ const name = String(info.name);
1158
+ if (seenTools.has(name)) {
1159
+ combined.warnings.push(`Duplicate tool "${name}" ignored (first definition wins).`);
1160
+ continue;
1161
+ }
1162
+ seenTools.add(name);
1163
+ binding.registerTool(info, handler);
1164
+ }
1165
+ if (combined.approval && typeof binding.registerApprovalCallback === "function") {
1166
+ binding.registerApprovalCallback(combined.approval.handler);
1167
+ }
1168
+ const RESERVED = /* @__PURE__ */ new Set(["local_shell", "exec_command", "apply_patch", "web_search"]);
1169
+ const seenInterceptors = /* @__PURE__ */ new Set();
1170
+ for (const interceptor of combined.interceptors) {
1171
+ const name = interceptor.toolName;
1172
+ if (RESERVED.has(name) && !combined.allowReservedInterceptors) {
1173
+ combined.warnings.push(
1174
+ `Interceptor for "${name}" ignored: reserved for approval gating. Use approvals() hook instead.`
1175
+ );
1176
+ continue;
1177
+ }
1178
+ if (seenInterceptors.has(name)) {
1179
+ combined.warnings.push(
1180
+ `Multiple interceptors for "${name}" detected; only the first will be used.`
1181
+ );
1182
+ continue;
1183
+ }
1184
+ seenInterceptors.add(name);
1185
+ binding.registerToolInterceptor(interceptor.toolName, interceptor.handler);
1186
+ }
1187
+ }
1188
+ async function evaluatePlugin(loaded, context, warnings) {
1189
+ const { plugin, spec } = loaded;
1190
+ try {
1191
+ if (typeof plugin === "function") {
1192
+ const result = await plugin(context);
1193
+ return coerceConfig(result, spec, warnings);
1194
+ }
1195
+ if (plugin && typeof plugin === "object") {
1196
+ const candidate = plugin;
1197
+ if (typeof candidate.setup === "function") {
1198
+ await candidate.setup(context);
1199
+ }
1200
+ if (typeof candidate.config === "function") {
1201
+ return coerceConfig(await candidate.config(context), spec, warnings);
1202
+ }
1203
+ if (candidate.config) {
1204
+ return coerceConfig(candidate.config, spec, warnings);
1205
+ }
1206
+ return coerceConfig(candidate, spec, warnings);
1207
+ }
1208
+ return coerceConfig(plugin, spec, warnings);
1209
+ } catch (error) {
1210
+ warnings.push(`Plugin "${spec}" threw an error: ${error.message}`);
1211
+ return null;
1212
+ }
1213
+ }
1214
+ function coerceConfig(value, source, warnings) {
1215
+ if (!value) {
1216
+ return null;
1217
+ }
1218
+ if (typeof value === "object") {
1219
+ return value;
1220
+ }
1221
+ warnings.push(`Plugin "${source}" did not return a config object.`);
1222
+ return null;
1223
+ }
1224
+ function accumulateConfig(params) {
1225
+ const { combined, config, source, warnings } = params;
1226
+ if (config.defaults?.run) {
1227
+ combined.runDefaults = { ...combined.runDefaults, ...config.defaults.run };
1228
+ }
1229
+ if (config.defaults?.tui) {
1230
+ combined.tuiDefaults = { ...combined.tuiDefaults, ...config.defaults.tui };
1231
+ }
1232
+ if (Array.isArray(config.tools)) {
1233
+ for (const tool of config.tools) {
1234
+ if (!tool || typeof tool !== "object" || typeof tool.handler !== "function") {
1235
+ warnings.push(`Invalid tool definition supplied by "${source}".`);
1236
+ continue;
1237
+ }
1238
+ combined.tools.push(tool);
1239
+ }
1240
+ }
1241
+ if (Array.isArray(config.interceptors)) {
1242
+ for (const interceptor of config.interceptors) {
1243
+ if (!interceptor || typeof interceptor !== "object" || typeof interceptor.toolName !== "string" || typeof interceptor.handler !== "function") {
1244
+ warnings.push(`Invalid interceptor definition supplied by "${source}".`);
1245
+ continue;
1246
+ }
1247
+ combined.interceptors.push(interceptor);
1248
+ }
1249
+ }
1250
+ if (config.approvals) {
1251
+ if (typeof config.approvals !== "function") {
1252
+ warnings.push(`Approval callback from "${source}" must be a function.`);
1253
+ } else {
1254
+ if (combined.approval) {
1255
+ warnings.push(
1256
+ `Approval callback from "${source}" overrides handler from "${combined.approval.source}".`
1257
+ );
1258
+ }
1259
+ combined.approval = { source, handler: config.approvals };
1260
+ }
1261
+ }
1262
+ if (config.hooks) {
1263
+ addHooks(combined, config.hooks, source, warnings);
1264
+ }
1265
+ if (config.allowReservedInterceptors === true) {
1266
+ combined.allowReservedInterceptors = true;
1267
+ }
1268
+ }
1269
+ function addHooks(combined, hooks, source, warnings) {
1270
+ if (hooks.beforeStart) {
1271
+ const beforeStartCallbacks = Array.isArray(hooks.beforeStart) ? hooks.beforeStart : [hooks.beforeStart];
1272
+ for (const callback of beforeStartCallbacks) {
1273
+ if (typeof callback !== "function") {
1274
+ warnings.push(`beforeStart hook from "${source}" must be a function.`);
1275
+ continue;
1276
+ }
1277
+ combined.beforeStartHooks.push({ source, callback });
1278
+ }
1279
+ }
1280
+ if (hooks.onEvent) {
1281
+ const eventCallbacks = Array.isArray(hooks.onEvent) ? hooks.onEvent : [hooks.onEvent];
1282
+ for (const callback of eventCallbacks) {
1283
+ if (typeof callback !== "function") {
1284
+ warnings.push(`onEvent hook from "${source}" must be a function.`);
1285
+ continue;
1286
+ }
1287
+ combined.onEventHooks.push({ source, callback });
1288
+ }
1289
+ }
1290
+ }
1291
+
1292
+ // src/cli/index.ts
1293
+ var VERSION = package_default.version;
1294
+ var SANDBOX_CHOICES = ["read-only", "workspace-write", "danger-full-access"];
1295
+ var APPROVAL_CHOICES = ["never", "on-request", "on-failure", "untrusted"];
1296
+ var APPLY_PATCH_FLAG = "--codex-run-as-apply-patch";
1297
+ var CLI_ENTRYPOINT_ENV = "CODEX_NODE_CLI_ENTRYPOINT";
1298
+ try {
1299
+ const entrypoint = fileURLToPath(import.meta.url);
1300
+ process4.env[CLI_ENTRYPOINT_ENV] = entrypoint;
1301
+ } catch {
1302
+ if (process4.argv[1]) {
1303
+ process4.env[CLI_ENTRYPOINT_ENV] = process4.argv[1];
1304
+ }
1305
+ }
1306
+ var GLOBAL_OPTION_DEFS = {
1307
+ config: { type: "string" },
1308
+ "no-config": { type: "boolean" },
1309
+ plugin: { type: "string", multiple: true }
1310
+ };
1311
+ var RUN_OPTION_DEFS = {
1312
+ model: { type: "string" },
1313
+ oss: { type: "boolean" },
1314
+ sandbox: { type: "string" },
1315
+ approval: { type: "string" },
1316
+ schema: { type: "string" },
1317
+ "thread-id": { type: "string" },
1318
+ "base-url": { type: "string" },
1319
+ "api-key": { type: "string" },
1320
+ "linux-sandbox-path": { type: "string" },
1321
+ "full-auto": { type: "boolean" },
1322
+ "skip-git-repo-check": { type: "boolean" },
1323
+ cd: { type: "string" },
1324
+ image: { type: "string", multiple: true },
1325
+ "review-mode": { type: "boolean" },
1326
+ "review-hint": { type: "string" }
1327
+ };
1328
+ var TUI_OPTION_DEFS = {
1329
+ model: { type: "string" },
1330
+ oss: { type: "boolean" },
1331
+ sandbox: { type: "string" },
1332
+ approval: { type: "string" },
1333
+ resume: { type: "string" },
1334
+ "resume-last": { type: "boolean" },
1335
+ "resume-picker": { type: "boolean" },
1336
+ "full-auto": { type: "boolean" },
1337
+ "dangerously-bypass-approvals-and-sandbox": { type: "boolean" },
1338
+ cd: { type: "string" },
1339
+ "config-profile": { type: "string" },
1340
+ "config-overrides": { type: "string", multiple: true },
1341
+ "add-dir": { type: "string", multiple: true },
1342
+ image: { type: "string", multiple: true },
1343
+ "web-search": { type: "boolean" },
1344
+ "linux-sandbox-path": { type: "string" },
1345
+ "base-url": { type: "string" },
1346
+ "api-key": { type: "string" }
1347
+ };
1348
+ async function main() {
1349
+ const rawArgs = process4.argv.slice(2);
1350
+ if (maybeHandleApplyPatch(rawArgs)) {
1351
+ return;
1352
+ }
1353
+ if (hasFlag(rawArgs, "--version") || hasFlag(rawArgs, "-v")) {
1354
+ printVersion();
1355
+ return;
1356
+ }
1357
+ const generalHelpRequested = hasFlag(rawArgs, "--help") || hasFlag(rawArgs, "-h");
1358
+ if (generalHelpRequested && !hasExplicitCommand(rawArgs)) {
1359
+ printGeneralHelp();
1360
+ return;
1361
+ }
1362
+ const { command, args } = selectCommand(rawArgs);
1363
+ if (hasCommandHelpFlag(args)) {
1364
+ printCommandHelp(command);
1365
+ return;
1366
+ }
1367
+ if (command === "reverie-index") {
1368
+ await executeReverieCommand(args);
1369
+ return;
1370
+ }
1371
+ const options = command === "tui" ? parseTuiCommand(args) : parseRunCommand(args);
1372
+ validateOptionChoices(command, options);
1373
+ const context = await createContext(options);
1374
+ if (command === "tui") {
1375
+ await executeTuiCommand(options, context);
1376
+ } else {
1377
+ await executeRunCommand(options, context);
1378
+ }
1379
+ }
1380
+ function maybeHandleApplyPatch(args) {
1381
+ if (args.length === 0 || args[0] !== APPLY_PATCH_FLAG) {
1382
+ return false;
1383
+ }
1384
+ const patch = args[1];
1385
+ if (!patch) {
1386
+ console.error(`${APPLY_PATCH_FLAG} requires a patch argument.`);
1387
+ process4.exitCode = 1;
1388
+ return true;
1389
+ }
1390
+ try {
1391
+ runApplyPatch(patch);
1392
+ } catch (error) {
1393
+ const message = error instanceof Error ? error.message : String(error);
1394
+ console.error(`apply_patch failed: ${message}`);
1395
+ process4.exitCode = 1;
1396
+ }
1397
+ return true;
1398
+ }
1399
+ function selectCommand(argv) {
1400
+ if (argv.length > 0) {
1401
+ const [first, ...rest] = argv;
1402
+ if (first === "tui") {
1403
+ return { command: "tui", args: rest };
1404
+ }
1405
+ if (first === "run") {
1406
+ return { command: "run", args: rest };
1407
+ }
1408
+ if (first === "reverie") {
1409
+ if (rest.length === 0) {
1410
+ return { command: "reverie-index", args: [] };
1411
+ }
1412
+ return { command: "reverie-index", args: rest };
1413
+ }
1414
+ }
1415
+ if (argv.length === 0) {
1416
+ const isInteractive2 = process4.stdout.isTTY && process4.stdin.isTTY;
1417
+ return { command: isInteractive2 ? "tui" : "run", args: [] };
1418
+ }
1419
+ const isInteractive = process4.stdout.isTTY && process4.stdin.isTTY;
1420
+ return { command: isInteractive ? "tui" : "run", args: argv };
1421
+ }
1422
+ function hasExplicitCommand(argv) {
1423
+ if (argv.length === 0) {
1424
+ return false;
1425
+ }
1426
+ const first = argv[0];
1427
+ return first === "tui" || first === "run" || first === "reverie";
1428
+ }
1429
+ function parseRunCommand(args) {
1430
+ const { values, positionals } = parseArgs2({
1431
+ args,
1432
+ options: { ...GLOBAL_OPTION_DEFS, ...RUN_OPTION_DEFS },
1433
+ allowPositionals: true,
1434
+ strict: true
1435
+ });
1436
+ const options = camelCaseKeys(values);
1437
+ const runOptions = {
1438
+ ...options
1439
+ };
1440
+ if (!runOptions.prompt && positionals.length > 0) {
1441
+ runOptions.prompt = positionals[0];
1442
+ }
1443
+ return runOptions;
1444
+ }
1445
+ function parseTuiCommand(args) {
1446
+ const { values, positionals } = parseArgs2({
1447
+ args,
1448
+ options: { ...GLOBAL_OPTION_DEFS, ...TUI_OPTION_DEFS },
1449
+ allowPositionals: true,
1450
+ strict: true
1451
+ });
1452
+ const options = camelCaseKeys(values);
1453
+ const tuiOptions = {
1454
+ ...options
1455
+ };
1456
+ if (!tuiOptions.prompt && positionals.length > 0) {
1457
+ tuiOptions.prompt = positionals[0];
1458
+ }
1459
+ return tuiOptions;
1460
+ }
1461
+ async function createContext(options) {
1462
+ const cwd = process4.cwd();
1463
+ const configOptions = {
1464
+ cwd,
1465
+ explicitConfigPath: options.config,
1466
+ noConfig: options.noConfig,
1467
+ pluginPaths: normalizeStringArray(options.plugin)
1468
+ };
1469
+ const config = await loadCliConfig(configOptions);
1470
+ const combinedConfig = await buildCombinedConfig({ cwd, config });
1471
+ applyNativeRegistrations(combinedConfig);
1472
+ return { cwd, config, combinedConfig };
1473
+ }
1474
+ function normalizeStringArray(value) {
1475
+ if (Array.isArray(value)) {
1476
+ return value.map((item) => String(item));
1477
+ }
1478
+ if (typeof value === "string") {
1479
+ return [value];
1480
+ }
1481
+ return [];
1482
+ }
1483
+ function camelCaseKeys(record) {
1484
+ return Object.entries(record).reduce((acc, [key, value]) => {
1485
+ acc[toCamelCase(key)] = value;
1486
+ return acc;
1487
+ }, {});
1488
+ }
1489
+ function toCamelCase(value) {
1490
+ return value.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
1491
+ }
1492
+ function hasFlag(args, flag) {
1493
+ return args.includes(flag);
1494
+ }
1495
+ function hasCommandHelpFlag(args) {
1496
+ return hasFlag(args, "--help") || hasFlag(args, "-h");
1497
+ }
1498
+ function printVersion() {
1499
+ console.log(VERSION);
1500
+ }
1501
+ function printGeneralHelp() {
1502
+ console.log(`codex-native v${VERSION}
1503
+
1504
+ Usage:
1505
+ codex-native [options] [prompt]
1506
+ codex-native run [options] [prompt]
1507
+ codex-native tui [options] [prompt]
1508
+
1509
+ Default behavior:
1510
+ Running 'codex-native' without arguments launches the interactive TUI.
1511
+ Use 'codex-native run <prompt>' for non-interactive exec mode.
1512
+
1513
+ Commands:
1514
+ (default) Launch the interactive TUI (with optional initial prompt)
1515
+ run Run Codex in non-interactive exec mode
1516
+ tui Explicitly launch the interactive TUI
1517
+ reverie index Pre-compute reverie embeddings for the current repo
1518
+
1519
+ Global options:
1520
+ --config <path> Path to codex.config.js (or similar)
1521
+ --no-config Skip automatic config discovery
1522
+ --plugin <path> Additional plugin module (repeatable)
1523
+
1524
+ Run options:
1525
+ --model <slug> Model slug to use
1526
+ --oss Use the built-in OSS provider
1527
+ --sandbox <mode> ${SANDBOX_CHOICES.join(" | ")}
1528
+ --approval <policy> ${APPROVAL_CHOICES.join(" | ")}
1529
+ --schema <file> Path to final-output JSON schema
1530
+ --thread-id <id> Resume an existing thread
1531
+ --base-url <url> Override the Codex API base URL
1532
+ --api-key <key> API key for Codex requests
1533
+ --linux-sandbox-path Path to codex-linux-sandbox binary
1534
+ --full-auto Enable workspace-write auto approvals
1535
+ --skip-git-repo-check Skip git repository validation
1536
+ --cd <path> Working directory for the run
1537
+ --image <path> Attach an image (repeatable)
1538
+ --review-mode Enable review mode
1539
+ --review-hint <text> Hint text for review mode
1540
+
1541
+ TUI options:
1542
+ --model <slug> Model slug to use
1543
+ --oss Use the built-in OSS provider
1544
+ --sandbox <mode> ${SANDBOX_CHOICES.join(" | ")}
1545
+ --approval <policy> ${APPROVAL_CHOICES.join(" | ")}
1546
+ --resume <id> Resume a saved session by id
1547
+ --resume-last Resume the most recent saved session
1548
+ --resume-picker Show the resume picker on startup
1549
+ --full-auto Enable workspace-write auto approvals
1550
+ --dangerously-bypass-approvals-and-sandbox
1551
+ Disable approvals and sandboxing (unsafe)
1552
+ --cd <path> Working directory for the session
1553
+ --config-profile <name> Config profile to activate
1554
+ --config-overrides <kv> Config overrides (key=value, repeatable)
1555
+ --add-dir <path> Additional writable directory (repeatable)
1556
+ --image <path> Attach an image (repeatable)
1557
+ --web-search Enable web search tool
1558
+ --linux-sandbox-path Path to codex-linux-sandbox binary
1559
+ --base-url <url> Override the Codex API base URL
1560
+ --api-key <key> API key for Codex requests
1561
+ `);
1562
+ }
1563
+ function printCommandHelp(command) {
1564
+ if (command === "reverie-index") {
1565
+ console.log(`codex-native reverie index [options]
1566
+
1567
+ Options:
1568
+ --codex-home <path> Override CODEX_HOME (defaults to ~/.codex)
1569
+ --project-root <path> Project root for scoping + embedding cache (default: cwd)
1570
+ --limit <n> Maximum conversations to index (default: 10)
1571
+ --max-candidates <n> Scan window before filtering (default: 80)
1572
+ --batch-size <n> Batch size forwarded to FastEmbed
1573
+ --normalize Force vector normalization (default: embed config)
1574
+ --no-normalize Disable normalization
1575
+ --cache / --no-cache Override embedding cache behavior
1576
+ --embed-model <name> FastEmbed model (default: BAAI/bge-large-en-v1.5)
1577
+ --embed-cache-dir <dir> Cache directory (defaults to $CODEX_EMBED_CACHE or system tmp)
1578
+ --embed-max-length <n> Override FastEmbed max token length
1579
+ --no-progress Hide FastEmbed download progress
1580
+ --skip-embed-init Assume fastEmbedInit was already called in this process
1581
+ `);
1582
+ return;
1583
+ }
1584
+ if (command === "tui") {
1585
+ console.log(`codex-native tui [options] [prompt]
1586
+
1587
+ Options:
1588
+ --model <slug> Model slug to use
1589
+ --oss Use the built-in OSS provider
1590
+ --sandbox <mode> ${SANDBOX_CHOICES.join(" | ")}
1591
+ --approval <policy> ${APPROVAL_CHOICES.join(" | ")}
1592
+ --resume <id> Resume a saved session by id
1593
+ --resume-last Resume the most recent saved session
1594
+ --resume-picker Show the resume picker on startup
1595
+ --full-auto Enable workspace-write auto approvals
1596
+ --dangerously-bypass-approvals-and-sandbox
1597
+ Disable approvals and sandboxing (unsafe)
1598
+ --cd <path> Working directory for the session
1599
+ --config-profile <name> Config profile to activate
1600
+ --config-overrides <kv> Config overrides (key=value, repeatable)
1601
+ --add-dir <path> Additional writable directory (repeatable)
1602
+ --image <path> Attach an image (repeatable)
1603
+ --web-search Enable web search tool
1604
+ --linux-sandbox-path Path to codex-linux-sandbox binary
1605
+ --base-url <url> Override the Codex API base URL
1606
+ --api-key <key> API key for Codex requests
1607
+ `);
1608
+ } else {
1609
+ console.log(`codex-native run [options] [prompt]
1610
+
1611
+ Options:
1612
+ --model <slug> Model slug to use
1613
+ --oss Use the built-in OSS provider
1614
+ --sandbox <mode> ${SANDBOX_CHOICES.join(" | ")}
1615
+ --approval <policy> ${APPROVAL_CHOICES.join(" | ")}
1616
+ --schema <file> Path to final-output JSON schema
1617
+ --thread-id <id> Resume an existing thread
1618
+ --base-url <url> Override the Codex API base URL
1619
+ --api-key <key> API key for Codex requests
1620
+ --linux-sandbox-path Path to codex-linux-sandbox binary
1621
+ --full-auto Enable workspace-write auto approvals
1622
+ --skip-git-repo-check Skip git repository validation
1623
+ --cd <path> Working directory for the run
1624
+ --image <path> Attach an image (repeatable)
1625
+ --review-mode Enable review mode
1626
+ --review-hint <text> Hint text for review mode
1627
+ `);
1628
+ }
1629
+ }
1630
+ function validateOptionChoices(command, options) {
1631
+ const sandbox = options.sandbox;
1632
+ if (sandbox && !SANDBOX_CHOICES.includes(sandbox)) {
1633
+ throw new Error(
1634
+ `Invalid sandbox mode "${sandbox}". Valid modes: ${SANDBOX_CHOICES.join(", ")}.`
1635
+ );
1636
+ }
1637
+ const approval = options.approval;
1638
+ if (approval && !APPROVAL_CHOICES.includes(approval)) {
1639
+ throw new Error(
1640
+ `Invalid approval policy "${approval}". Valid policies: ${APPROVAL_CHOICES.join(", ")}.`
1641
+ );
1642
+ }
1643
+ }
1644
+ function logError(error) {
1645
+ if (error instanceof Error) {
1646
+ console.error(error.message);
1647
+ if (process4.env.CODEX_NATIVE_DEBUG) {
1648
+ console.error(error.stack);
1649
+ }
1650
+ } else {
1651
+ console.error(String(error));
1652
+ }
1653
+ }
1654
+ main().catch((error) => {
1655
+ logError(error);
1656
+ process4.exitCode = 1;
1657
+ });
1658
+ //# sourceMappingURL=cli.mjs.map