codex-multi-auth 0.1.0 → 0.1.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.
Files changed (124) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +223 -98
  3. package/assets/readme-hero.svg +31 -31
  4. package/config/README.md +30 -87
  5. package/config/{opencode-legacy.json → codex-legacy.json} +571 -571
  6. package/config/{opencode-modern.json → codex-modern.json} +241 -239
  7. package/config/{minimal-opencode.json → minimal-codex.json} +15 -13
  8. package/config/schema/config.schema.json +21 -0
  9. package/dist/index.d.ts +5 -5
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +23 -20
  12. package/dist/index.js.map +1 -1
  13. package/dist/lib/accounts.d.ts +1 -1
  14. package/dist/lib/accounts.d.ts.map +1 -1
  15. package/dist/lib/auth/auth.d.ts +1 -1
  16. package/dist/lib/auth/auth.js +1 -1
  17. package/dist/lib/cli.d.ts +1 -1
  18. package/dist/lib/cli.js +4 -4
  19. package/dist/lib/cli.js.map +1 -1
  20. package/dist/lib/codex-manager/settings-hub.d.ts +6 -0
  21. package/dist/lib/codex-manager/settings-hub.d.ts.map +1 -0
  22. package/dist/lib/codex-manager/settings-hub.js +1786 -0
  23. package/dist/lib/codex-manager/settings-hub.js.map +1 -0
  24. package/dist/lib/codex-manager.d.ts.map +1 -1
  25. package/dist/lib/codex-manager.js +5 -1637
  26. package/dist/lib/codex-manager.js.map +1 -1
  27. package/dist/lib/config.d.ts.map +1 -1
  28. package/dist/lib/config.js +20 -11
  29. package/dist/lib/config.js.map +1 -1
  30. package/dist/lib/context-overflow.d.ts +1 -1
  31. package/dist/lib/context-overflow.js +11 -11
  32. package/dist/lib/oauth-success.html +1 -1
  33. package/dist/lib/prompts/codex-host-bridge.d.ts +19 -0
  34. package/dist/lib/prompts/codex-host-bridge.d.ts.map +1 -0
  35. package/dist/lib/prompts/{codex-opencode-bridge.js → codex-host-bridge.js} +143 -143
  36. package/dist/lib/prompts/codex-host-bridge.js.map +1 -0
  37. package/dist/lib/prompts/codex.d.ts +2 -2
  38. package/dist/lib/prompts/codex.d.ts.map +1 -1
  39. package/dist/lib/prompts/codex.js +3 -3
  40. package/dist/lib/prompts/host-codex-prompt.d.ts +25 -0
  41. package/dist/lib/prompts/host-codex-prompt.d.ts.map +1 -0
  42. package/dist/lib/prompts/{opencode-codex.js → host-codex-prompt.js} +136 -47
  43. package/dist/lib/prompts/host-codex-prompt.js.map +1 -0
  44. package/dist/lib/recovery/storage.d.ts +2 -2
  45. package/dist/lib/recovery/storage.js +2 -2
  46. package/dist/lib/recovery/types.d.ts +1 -1
  47. package/dist/lib/recovery/types.js +1 -1
  48. package/dist/lib/recovery.d.ts +1 -1
  49. package/dist/lib/recovery.d.ts.map +1 -1
  50. package/dist/lib/recovery.js +1 -4
  51. package/dist/lib/recovery.js.map +1 -1
  52. package/dist/lib/request/fetch-helpers.d.ts +3 -3
  53. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  54. package/dist/lib/request/fetch-helpers.js +6 -2
  55. package/dist/lib/request/fetch-helpers.js.map +1 -1
  56. package/dist/lib/request/helpers/input-utils.d.ts +2 -2
  57. package/dist/lib/request/helpers/input-utils.d.ts.map +1 -1
  58. package/dist/lib/request/helpers/input-utils.js +14 -14
  59. package/dist/lib/request/helpers/input-utils.js.map +1 -1
  60. package/dist/lib/request/helpers/model-map.d.ts +2 -2
  61. package/dist/lib/request/helpers/model-map.js +2 -2
  62. package/dist/lib/request/request-transformer.d.ts +12 -12
  63. package/dist/lib/request/request-transformer.d.ts.map +1 -1
  64. package/dist/lib/request/request-transformer.js +23 -24
  65. package/dist/lib/request/request-transformer.js.map +1 -1
  66. package/dist/lib/runtime-paths.d.ts +4 -4
  67. package/dist/lib/runtime-paths.d.ts.map +1 -1
  68. package/dist/lib/runtime-paths.js +57 -13
  69. package/dist/lib/runtime-paths.js.map +1 -1
  70. package/dist/lib/storage/paths.d.ts +11 -0
  71. package/dist/lib/storage/paths.d.ts.map +1 -1
  72. package/dist/lib/storage/paths.js +167 -5
  73. package/dist/lib/storage/paths.js.map +1 -1
  74. package/dist/lib/storage.d.ts +1 -0
  75. package/dist/lib/storage.d.ts.map +1 -1
  76. package/dist/lib/storage.js +290 -81
  77. package/dist/lib/storage.js.map +1 -1
  78. package/dist/lib/tools/hashline-tools.d.ts +1 -1
  79. package/dist/lib/tools/hashline-tools.d.ts.map +1 -1
  80. package/dist/lib/tools/hashline-tools.js +1 -1
  81. package/dist/lib/tools/hashline-tools.js.map +1 -1
  82. package/dist/lib/types.d.ts +1 -1
  83. package/dist/lib/types.d.ts.map +1 -1
  84. package/dist/lib/ui/copy.d.ts +6 -6
  85. package/dist/lib/ui/copy.js +6 -6
  86. package/dist/lib/ui/copy.js.map +1 -1
  87. package/node_modules/@codex-ai/plugin/dist/index.d.ts +2 -0
  88. package/node_modules/@codex-ai/plugin/dist/index.js +2 -0
  89. package/node_modules/@codex-ai/plugin/dist/tool.d.ts +42 -0
  90. package/node_modules/@codex-ai/plugin/dist/tool.js +29 -0
  91. package/node_modules/@codex-ai/plugin/package.json +9 -0
  92. package/package.json +30 -16
  93. package/scripts/audit-dev-allowlist.js +128 -128
  94. package/scripts/bench-format/{opencode.mjs → codex-host.mjs} +206 -205
  95. package/scripts/bench-format/hashline-v2.mjs +642 -642
  96. package/scripts/bench-format/models.mjs +111 -105
  97. package/scripts/bench-format/render.mjs +496 -496
  98. package/scripts/bench-format/stats.mjs +54 -54
  99. package/scripts/bench-format/tasks.mjs +151 -151
  100. package/scripts/benchmark-edit-formats.mjs +1162 -1161
  101. package/scripts/benchmark-render-dashboard.mjs +49 -49
  102. package/scripts/codex.js +40 -2
  103. package/scripts/copy-oauth-success.js +37 -37
  104. package/scripts/install-codex-auth-utils.js +49 -0
  105. package/scripts/{install-opencode-codex-auth.js → install-codex-auth.js} +220 -193
  106. package/scripts/repo-hygiene.js +320 -0
  107. package/scripts/test-all-models.sh +7 -7
  108. package/scripts/test-model-matrix.js +475 -423
  109. package/scripts/validate-model-map.sh +7 -7
  110. package/vendor/codex-ai-plugin/dist/index.d.ts +2 -0
  111. package/vendor/codex-ai-plugin/dist/index.js +2 -0
  112. package/vendor/codex-ai-plugin/dist/tool.d.ts +42 -0
  113. package/vendor/codex-ai-plugin/dist/tool.js +29 -0
  114. package/vendor/codex-ai-plugin/package.json +9 -0
  115. package/vendor/codex-ai-sdk/dist/index.d.ts +4 -0
  116. package/vendor/codex-ai-sdk/dist/index.js +2 -0
  117. package/vendor/codex-ai-sdk/package.json +8 -0
  118. package/assets/opencode-logo-ornate-dark.svg +0 -18
  119. package/dist/lib/prompts/codex-opencode-bridge.d.ts +0 -19
  120. package/dist/lib/prompts/codex-opencode-bridge.d.ts.map +0 -1
  121. package/dist/lib/prompts/codex-opencode-bridge.js.map +0 -1
  122. package/dist/lib/prompts/opencode-codex.d.ts +0 -25
  123. package/dist/lib/prompts/opencode-codex.d.ts.map +0 -1
  124. package/dist/lib/prompts/opencode-codex.js.map +0 -1
@@ -1,128 +1,128 @@
1
- #!/usr/bin/env node
2
-
3
- import { spawnSync } from "node:child_process";
4
-
5
- const ALLOWED_HIGH_OR_CRITICAL_PACKAGES = new Set([
6
- "eslint",
7
- "ajv",
8
- "@eslint-community/eslint-utils",
9
- "@typescript-eslint/eslint-plugin",
10
- "@typescript-eslint/parser",
11
- "@typescript-eslint/type-utils",
12
- "@typescript-eslint/typescript-estree",
13
- "@typescript-eslint/utils",
14
- "minimatch",
15
- ]);
16
-
17
- function summarizeVia(via) {
18
- if (!Array.isArray(via)) return [];
19
- return via
20
- .map((item) => {
21
- if (typeof item === "string") return item;
22
- if (!item || typeof item !== "object") return "unknown";
23
- const name = typeof item.name === "string" ? item.name : "unknown";
24
- const range = typeof item.range === "string" ? item.range : "";
25
- return range ? `${name}:${range}` : name;
26
- })
27
- .slice(0, 5);
28
- }
29
-
30
- const isWindows = process.platform === "win32";
31
- const command = isWindows ? process.env.ComSpec || "cmd.exe" : "npm";
32
- const commandArgs = isWindows
33
- ? ["/d", "/s", "/c", "npm audit --json"]
34
- : ["audit", "--json"];
35
- const audit = spawnSync(command, commandArgs, {
36
- encoding: "utf8",
37
- stdio: ["ignore", "pipe", "pipe"],
38
- env: {
39
- ...process.env,
40
- // npm run -s can suppress child npm JSON output; force a readable level.
41
- npm_config_loglevel:
42
- process.env.npm_config_loglevel === "silent"
43
- ? "notice"
44
- : process.env.npm_config_loglevel || "notice",
45
- },
46
- });
47
-
48
- const stdout = (audit.stdout ?? "").trim();
49
- const stderr = (audit.stderr ?? "").trim();
50
- const combined = [stdout, stderr].filter(Boolean).join("\n");
51
-
52
- if (!combined) {
53
- if ((audit.status ?? 1) === 0) {
54
- console.log("No vulnerabilities found in npm audit output.");
55
- process.exit(0);
56
- }
57
- console.error("Failed to read npm audit output.");
58
- process.exit(1);
59
- }
60
-
61
- // npm can emit human-readable success text (no JSON) on some versions/configs.
62
- if (!combined.includes("{") && /found 0 vulnerabilities/i.test(combined)) {
63
- console.log("No vulnerabilities found in npm audit output.");
64
- process.exit(0);
65
- }
66
-
67
- let auditJson;
68
- try {
69
- const jsonCandidate =
70
- (stdout.startsWith("{") ? stdout : "") ||
71
- (stderr.startsWith("{") ? stderr : "") ||
72
- combined.slice(combined.indexOf("{"));
73
- auditJson = JSON.parse(jsonCandidate.replace(/^\uFEFF/, ""));
74
- } catch (error) {
75
- console.error("Failed to parse npm audit JSON output.");
76
- if (stderr) {
77
- console.error(stderr);
78
- }
79
- console.error(error instanceof Error ? error.message : String(error));
80
- process.exit(1);
81
- }
82
-
83
- const vulnerabilities =
84
- auditJson && typeof auditJson === "object" && auditJson.vulnerabilities && typeof auditJson.vulnerabilities === "object"
85
- ? auditJson.vulnerabilities
86
- : {};
87
-
88
- const unexpected = [];
89
- const allowlisted = [];
90
-
91
- for (const [name, details] of Object.entries(vulnerabilities)) {
92
- if (!details || typeof details !== "object") continue;
93
- const severity = typeof details.severity === "string" ? details.severity : "unknown";
94
- if (severity !== "high" && severity !== "critical") continue;
95
-
96
- const entry = {
97
- name,
98
- severity,
99
- via: summarizeVia(details.via),
100
- fixAvailable: details.fixAvailable ?? false,
101
- };
102
-
103
- if (ALLOWED_HIGH_OR_CRITICAL_PACKAGES.has(name)) {
104
- allowlisted.push(entry);
105
- continue;
106
- }
107
- unexpected.push(entry);
108
- }
109
-
110
- if (unexpected.length > 0) {
111
- console.error("Unexpected high/critical vulnerabilities detected in dev dependency audit:");
112
- for (const entry of unexpected) {
113
- console.error(
114
- `- ${entry.name} (${entry.severity}) via ${entry.via.join(", ") || "unknown"} fixAvailable=${String(entry.fixAvailable)}`,
115
- );
116
- }
117
- process.exit(1);
118
- }
119
-
120
- if (allowlisted.length > 0) {
121
- console.warn("Allowlisted high/critical dev vulnerabilities detected:");
122
- for (const entry of allowlisted) {
123
- console.warn(
124
- `- ${entry.name} (${entry.severity}) via ${entry.via.join(", ") || "unknown"} fixAvailable=${String(entry.fixAvailable)}`,
125
- );
126
- }
127
- console.warn("No unexpected high/critical vulnerabilities found.");
128
- }
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+
5
+ const ALLOWED_HIGH_OR_CRITICAL_PACKAGES = new Set([
6
+ "eslint",
7
+ "ajv",
8
+ "@eslint-community/eslint-utils",
9
+ "@typescript-eslint/eslint-plugin",
10
+ "@typescript-eslint/parser",
11
+ "@typescript-eslint/type-utils",
12
+ "@typescript-eslint/typescript-estree",
13
+ "@typescript-eslint/utils",
14
+ "minimatch",
15
+ ]);
16
+
17
+ function summarizeVia(via) {
18
+ if (!Array.isArray(via)) return [];
19
+ return via
20
+ .map((item) => {
21
+ if (typeof item === "string") return item;
22
+ if (!item || typeof item !== "object") return "unknown";
23
+ const name = typeof item.name === "string" ? item.name : "unknown";
24
+ const range = typeof item.range === "string" ? item.range : "";
25
+ return range ? `${name}:${range}` : name;
26
+ })
27
+ .slice(0, 5);
28
+ }
29
+
30
+ const isWindows = process.platform === "win32";
31
+ const command = isWindows ? process.env.ComSpec || "cmd.exe" : "npm";
32
+ const commandArgs = isWindows
33
+ ? ["/d", "/s", "/c", "npm audit --json"]
34
+ : ["audit", "--json"];
35
+ const audit = spawnSync(command, commandArgs, {
36
+ encoding: "utf8",
37
+ stdio: ["ignore", "pipe", "pipe"],
38
+ env: {
39
+ ...process.env,
40
+ // npm run -s can suppress child npm JSON output; force a readable level.
41
+ npm_config_loglevel:
42
+ process.env.npm_config_loglevel === "silent"
43
+ ? "notice"
44
+ : process.env.npm_config_loglevel || "notice",
45
+ },
46
+ });
47
+
48
+ const stdout = (audit.stdout ?? "").trim();
49
+ const stderr = (audit.stderr ?? "").trim();
50
+ const combined = [stdout, stderr].filter(Boolean).join("\n");
51
+
52
+ if (!combined) {
53
+ if ((audit.status ?? 1) === 0) {
54
+ console.log("No vulnerabilities found in npm audit output.");
55
+ process.exit(0);
56
+ }
57
+ console.error("Failed to read npm audit output.");
58
+ process.exit(1);
59
+ }
60
+
61
+ // npm can emit human-readable success text (no JSON) on some versions/configs.
62
+ if (!combined.includes("{") && /found 0 vulnerabilities/i.test(combined)) {
63
+ console.log("No vulnerabilities found in npm audit output.");
64
+ process.exit(0);
65
+ }
66
+
67
+ let auditJson;
68
+ try {
69
+ const jsonCandidate =
70
+ (stdout.startsWith("{") ? stdout : "") ||
71
+ (stderr.startsWith("{") ? stderr : "") ||
72
+ combined.slice(combined.indexOf("{"));
73
+ auditJson = JSON.parse(jsonCandidate.replace(/^\uFEFF/, ""));
74
+ } catch (error) {
75
+ console.error("Failed to parse npm audit JSON output.");
76
+ if (stderr) {
77
+ console.error(stderr);
78
+ }
79
+ console.error(error instanceof Error ? error.message : String(error));
80
+ process.exit(1);
81
+ }
82
+
83
+ const vulnerabilities =
84
+ auditJson && typeof auditJson === "object" && auditJson.vulnerabilities && typeof auditJson.vulnerabilities === "object"
85
+ ? auditJson.vulnerabilities
86
+ : {};
87
+
88
+ const unexpected = [];
89
+ const allowlisted = [];
90
+
91
+ for (const [name, details] of Object.entries(vulnerabilities)) {
92
+ if (!details || typeof details !== "object") continue;
93
+ const severity = typeof details.severity === "string" ? details.severity : "unknown";
94
+ if (severity !== "high" && severity !== "critical") continue;
95
+
96
+ const entry = {
97
+ name,
98
+ severity,
99
+ via: summarizeVia(details.via),
100
+ fixAvailable: details.fixAvailable ?? false,
101
+ };
102
+
103
+ if (ALLOWED_HIGH_OR_CRITICAL_PACKAGES.has(name)) {
104
+ allowlisted.push(entry);
105
+ continue;
106
+ }
107
+ unexpected.push(entry);
108
+ }
109
+
110
+ if (unexpected.length > 0) {
111
+ console.error("Unexpected high/critical vulnerabilities detected in dev dependency audit:");
112
+ for (const entry of unexpected) {
113
+ console.error(
114
+ `- ${entry.name} (${entry.severity}) via ${entry.via.join(", ") || "unknown"} fixAvailable=${String(entry.fixAvailable)}`,
115
+ );
116
+ }
117
+ process.exit(1);
118
+ }
119
+
120
+ if (allowlisted.length > 0) {
121
+ console.warn("Allowlisted high/critical dev vulnerabilities detected:");
122
+ for (const entry of allowlisted) {
123
+ console.warn(
124
+ `- ${entry.name} (${entry.severity}) via ${entry.via.join(", ") || "unknown"} fixAvailable=${String(entry.fixAvailable)}`,
125
+ );
126
+ }
127
+ console.warn("No unexpected high/critical vulnerabilities found.");
128
+ }
@@ -1,205 +1,206 @@
1
- import { spawnSync } from "node:child_process";
2
- import { dirname, resolve } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
-
5
- const scriptDir = dirname(fileURLToPath(import.meta.url));
6
- const repoRoot = resolve(scriptDir, "..", "..");
7
-
8
- export function getRepoRoot() {
9
- return repoRoot;
10
- }
11
-
12
- export function resolveOpencodeExecutable() {
13
- const envOverride = process.env.OPENCODE_BIN;
14
- if (envOverride && envOverride.trim().length > 0) {
15
- const command = envOverride.trim();
16
- return { command, shell: /\.cmd$/i.test(command) };
17
- }
18
-
19
- if (process.platform !== "win32") {
20
- return { command: "opencode", shell: false };
21
- }
22
-
23
- const whereResult = spawnSync("where", ["opencode"], {
24
- encoding: "utf8",
25
- windowsHide: true,
26
- });
27
- const candidates = `${whereResult.stdout ?? ""}\n${whereResult.stderr ?? ""}`
28
- .split(/\r?\n/)
29
- .map((line) => line.trim())
30
- .filter(Boolean);
31
-
32
- if (candidates.length === 0) {
33
- return { command: "opencode", shell: false };
34
- }
35
-
36
- const exactExe = candidates.find((candidate) => /npm\\opencode\.exe$/i.test(candidate));
37
- if (exactExe) {
38
- return { command: exactExe, shell: false };
39
- }
40
-
41
- const exactCmd = candidates.find((candidate) => /npm\\opencode\.cmd$/i.test(candidate));
42
- if (exactCmd) {
43
- return { command: exactCmd, shell: true };
44
- }
45
-
46
- const anyCmd = candidates.find((candidate) => /\.cmd$/i.test(candidate));
47
- if (anyCmd) {
48
- return { command: anyCmd, shell: true };
49
- }
50
-
51
- return { command: candidates[0], shell: false };
52
- }
53
-
54
- export function parseNdjson(text) {
55
- const events = [];
56
- for (const line of text.split(/\r?\n/)) {
57
- const trimmed = line.trim();
58
- if (!trimmed) {
59
- continue;
60
- }
61
- try {
62
- events.push(JSON.parse(trimmed));
63
- } catch {
64
- // Ignore non-JSON lines emitted by wrappers (bun install, warnings, etc.).
65
- }
66
- }
67
- return events;
68
- }
69
-
70
- export function getToolEvents(events) {
71
- return events
72
- .filter((event) => event?.type === "tool_use" && event?.part?.type === "tool")
73
- .map((event) => ({
74
- tool: event.part.tool,
75
- input: event.part.state?.input ?? {},
76
- output: event.part.state?.output,
77
- status: event.part.state?.status,
78
- start: event.part.state?.time?.start,
79
- end: event.part.state?.time?.end,
80
- durationMs:
81
- typeof event.part.state?.time?.start === "number" &&
82
- typeof event.part.state?.time?.end === "number"
83
- ? event.part.state.time.end - event.part.state.time.start
84
- : null,
85
- }));
86
- }
87
-
88
- export function getSessionDuration(events) {
89
- const starts = events
90
- .filter((event) => event?.type === "step_start" && typeof event.timestamp === "number")
91
- .map((event) => event.timestamp);
92
- const finishes = events
93
- .filter((event) => event?.type === "step_finish" && typeof event.timestamp === "number")
94
- .map((event) => event.timestamp);
95
- if (starts.length === 0 || finishes.length === 0) {
96
- return null;
97
- }
98
- return Math.max(...finishes) - Math.min(...starts);
99
- }
100
-
101
- export function getTokenTotals(events) {
102
- const stepFinishes = events.filter((event) => event?.type === "step_finish" && event?.part?.tokens);
103
- if (stepFinishes.length === 0) {
104
- return null;
105
- }
106
- const total = {
107
- total: 0,
108
- input: 0,
109
- output: 0,
110
- reasoning: 0,
111
- cacheRead: 0,
112
- cacheWrite: 0,
113
- };
114
- for (const event of stepFinishes) {
115
- const tokens = event.part.tokens ?? {};
116
- const input = Number(tokens.input ?? 0);
117
- const output = Number(tokens.output ?? 0);
118
- const reasoning = Number(tokens.reasoning ?? 0);
119
- const explicitTotal = Number(tokens.total ?? NaN);
120
- total.total += Number.isFinite(explicitTotal) ? explicitTotal : input + output + reasoning;
121
- total.input += input;
122
- total.output += output;
123
- total.reasoning += reasoning;
124
- total.cacheRead += Number(tokens.cache?.read ?? 0);
125
- total.cacheWrite += Number(tokens.cache?.write ?? 0);
126
- }
127
- return total;
128
- }
129
-
130
- export function getTextOutput(events) {
131
- return events
132
- .filter((event) => event?.type === "text" && typeof event?.part?.text === "string")
133
- .map((event) => event.part.text)
134
- .join("\n");
135
- }
136
-
137
- export function getEventError(events) {
138
- const errorEvent = events.find((event) => event?.type === "error");
139
- if (!errorEvent) {
140
- return null;
141
- }
142
- return {
143
- name: errorEvent.error?.name ?? "UnknownError",
144
- message: errorEvent.error?.data?.message ?? errorEvent.error?.message ?? "Unknown error",
145
- };
146
- }
147
-
148
- export function runOpencodeJson({
149
- executable,
150
- prompt,
151
- model,
152
- variant,
153
- agent,
154
- cwd,
155
- homeDir,
156
- timeoutMs,
157
- extraEnv,
158
- }) {
159
- const startWall = Date.now();
160
- const args = ["run", "--format", "json", "--agent", agent, "--model", model];
161
- if (variant) {
162
- args.push("--variant", variant);
163
- }
164
- args.push(prompt);
165
-
166
- const child = spawnSync(executable.command, args, {
167
- cwd: cwd ?? repoRoot,
168
- encoding: "utf8",
169
- windowsHide: true,
170
- shell: executable.shell,
171
- timeout: timeoutMs,
172
- maxBuffer: 30 * 1024 * 1024,
173
- env: {
174
- ...process.env,
175
- ...(homeDir ? { HOME: homeDir, USERPROFILE: homeDir } : {}),
176
- ...extraEnv,
177
- },
178
- });
179
-
180
- const wallMs = Date.now() - startWall;
181
- const stdout = child.stdout ?? "";
182
- const stderr = child.stderr ?? "";
183
- const events = parseNdjson(stdout);
184
- const eventError = getEventError(events);
185
- const timedOut =
186
- child.error?.code === "ETIMEDOUT" ||
187
- child.signal === "SIGTERM" ||
188
- /timed out/i.test(String(child.error?.message ?? ""));
189
- if (child.error && !timedOut) {
190
- throw child.error;
191
- }
192
- const modelNotFound = /Model not found|ProviderModelNotFoundError/i.test(`${stdout}\n${stderr}`) || /Model not found/i.test(eventError?.message ?? "");
193
-
194
- return {
195
- status: child.status ?? 1,
196
- signal: child.signal ?? null,
197
- stdout,
198
- stderr,
199
- wallMs,
200
- events,
201
- eventError,
202
- timedOut,
203
- modelNotFound,
204
- };
205
- }
1
+ import { spawnSync } from "node:child_process";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
6
+ const repoRoot = resolve(scriptDir, "..", "..");
7
+
8
+ export function getRepoRoot() {
9
+ return repoRoot;
10
+ }
11
+
12
+ export function resolveCodexExecutable() {
13
+ const envOverride = process.env.CODEX_BIN;
14
+ if (envOverride && envOverride.trim().length > 0) {
15
+ const command = envOverride.trim();
16
+ return { command, shell: /\.cmd$/i.test(command) };
17
+ }
18
+
19
+ if (process.platform !== "win32") {
20
+ return { command: "codex", shell: false };
21
+ }
22
+
23
+ const whereResult = spawnSync("where", ["Codex"], {
24
+ encoding: "utf8",
25
+ windowsHide: true,
26
+ });
27
+ const candidates = `${whereResult.stdout ?? ""}`
28
+ .split(/\r?\n/)
29
+ .map((line) => line.trim())
30
+ .filter((line) => /^[A-Za-z]:\\.+\.(exe|cmd)$/i.test(line));
31
+
32
+ if (candidates.length === 0) {
33
+ return { command: "codex", shell: false };
34
+ }
35
+
36
+ const exactExe = candidates.find((candidate) => /npm\\Codex\.exe$/i.test(candidate));
37
+ if (exactExe) {
38
+ return { command: exactExe, shell: false };
39
+ }
40
+
41
+ const exactCmd = candidates.find((candidate) => /npm\\Codex\.cmd$/i.test(candidate));
42
+ if (exactCmd) {
43
+ return { command: exactCmd, shell: true };
44
+ }
45
+
46
+ const anyCmd = candidates.find((candidate) => /\.cmd$/i.test(candidate));
47
+ if (anyCmd) {
48
+ return { command: anyCmd, shell: true };
49
+ }
50
+
51
+ return { command: candidates[0], shell: false };
52
+ }
53
+
54
+ export function parseNdjson(text) {
55
+ const events = [];
56
+ for (const line of text.split(/\r?\n/)) {
57
+ const trimmed = line.trim();
58
+ if (!trimmed) {
59
+ continue;
60
+ }
61
+ try {
62
+ events.push(JSON.parse(trimmed));
63
+ } catch {
64
+ // Ignore non-JSON lines emitted by wrappers (bun install, warnings, etc.).
65
+ }
66
+ }
67
+ return events;
68
+ }
69
+
70
+ export function getToolEvents(events) {
71
+ return events
72
+ .filter((event) => event?.type === "tool_use" && event?.part?.type === "tool")
73
+ .map((event) => ({
74
+ tool: event.part.tool,
75
+ input: event.part.state?.input ?? {},
76
+ output: event.part.state?.output,
77
+ status: event.part.state?.status,
78
+ start: event.part.state?.time?.start,
79
+ end: event.part.state?.time?.end,
80
+ durationMs:
81
+ typeof event.part.state?.time?.start === "number" &&
82
+ typeof event.part.state?.time?.end === "number"
83
+ ? event.part.state.time.end - event.part.state.time.start
84
+ : null,
85
+ }));
86
+ }
87
+
88
+ export function getSessionDuration(events) {
89
+ const starts = events
90
+ .filter((event) => event?.type === "step_start" && typeof event.timestamp === "number")
91
+ .map((event) => event.timestamp);
92
+ const finishes = events
93
+ .filter((event) => event?.type === "step_finish" && typeof event.timestamp === "number")
94
+ .map((event) => event.timestamp);
95
+ if (starts.length === 0 || finishes.length === 0) {
96
+ return null;
97
+ }
98
+ return Math.max(...finishes) - Math.min(...starts);
99
+ }
100
+
101
+ export function getTokenTotals(events) {
102
+ const stepFinishes = events.filter((event) => event?.type === "step_finish" && event?.part?.tokens);
103
+ if (stepFinishes.length === 0) {
104
+ return null;
105
+ }
106
+ const total = {
107
+ total: 0,
108
+ input: 0,
109
+ output: 0,
110
+ reasoning: 0,
111
+ cacheRead: 0,
112
+ cacheWrite: 0,
113
+ };
114
+ for (const event of stepFinishes) {
115
+ const tokens = event.part.tokens ?? {};
116
+ const input = Number(tokens.input ?? 0);
117
+ const output = Number(tokens.output ?? 0);
118
+ const reasoning = Number(tokens.reasoning ?? 0);
119
+ const explicitTotal = Number(tokens.total ?? NaN);
120
+ total.total += Number.isFinite(explicitTotal) ? explicitTotal : input + output + reasoning;
121
+ total.input += input;
122
+ total.output += output;
123
+ total.reasoning += reasoning;
124
+ total.cacheRead += Number(tokens.cache?.read ?? 0);
125
+ total.cacheWrite += Number(tokens.cache?.write ?? 0);
126
+ }
127
+ return total;
128
+ }
129
+
130
+ export function getTextOutput(events) {
131
+ return events
132
+ .filter((event) => event?.type === "text" && typeof event?.part?.text === "string")
133
+ .map((event) => event.part.text)
134
+ .join("\n");
135
+ }
136
+
137
+ export function getEventError(events) {
138
+ const errorEvent = events.find((event) => event?.type === "error");
139
+ if (!errorEvent) {
140
+ return null;
141
+ }
142
+ return {
143
+ name: errorEvent.error?.name ?? "UnknownError",
144
+ message: errorEvent.error?.data?.message ?? errorEvent.error?.message ?? "Unknown error",
145
+ };
146
+ }
147
+
148
+ export function runCodexJson({
149
+ executable,
150
+ prompt,
151
+ model,
152
+ variant,
153
+ agent,
154
+ cwd,
155
+ homeDir,
156
+ timeoutMs,
157
+ extraEnv,
158
+ }) {
159
+ const startWall = Date.now();
160
+ const args = ["run", "--format", "json", "--agent", agent, "--model", model];
161
+ if (variant) {
162
+ args.push("--variant", variant);
163
+ }
164
+ args.push(prompt);
165
+
166
+ const child = spawnSync(executable.command, args, {
167
+ cwd: cwd ?? repoRoot,
168
+ encoding: "utf8",
169
+ windowsHide: true,
170
+ shell: executable.shell,
171
+ timeout: timeoutMs,
172
+ maxBuffer: 30 * 1024 * 1024,
173
+ env: {
174
+ ...process.env,
175
+ ...(homeDir ? { HOME: homeDir, USERPROFILE: homeDir } : {}),
176
+ ...extraEnv,
177
+ },
178
+ });
179
+
180
+ const wallMs = Date.now() - startWall;
181
+ const stdout = child.stdout ?? "";
182
+ const stderr = child.stderr ?? "";
183
+ const events = parseNdjson(stdout);
184
+ const eventError = getEventError(events);
185
+ const timedOut =
186
+ child.error?.code === "ETIMEDOUT" ||
187
+ child.signal === "SIGTERM" ||
188
+ /timed out/i.test(String(child.error?.message ?? ""));
189
+ if (child.error && !timedOut) {
190
+ throw child.error;
191
+ }
192
+ const modelNotFound = /Model not found|ProviderModelNotFoundError/i.test(`${stdout}\n${stderr}`) || /Model not found/i.test(eventError?.message ?? "");
193
+
194
+ return {
195
+ status: child.status ?? 1,
196
+ signal: child.signal ?? null,
197
+ stdout,
198
+ stderr,
199
+ wallMs,
200
+ events,
201
+ eventError,
202
+ timedOut,
203
+ modelNotFound,
204
+ };
205
+ }
206
+