@codexed/codex 0.121.0-codexed.3 → 0.121.0-codexed.5

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/README.md CHANGED
@@ -1 +1,60 @@
1
- Patched Codex CLI with persistent retry behavior.
1
+ <p align="center"><code>npm i -g @openai/codex</code><br />or <code>brew install --cask codex</code></p>
2
+ <p align="center"><strong>Codex CLI</strong> is a coding agent from OpenAI that runs locally on your computer.
3
+ <p align="center">
4
+ <img src="https://github.com/openai/codex/blob/main/.github/codex-cli-splash.png" alt="Codex CLI splash" width="80%" />
5
+ </p>
6
+ </br>
7
+ If you want Codex in your code editor (VS Code, Cursor, Windsurf), <a href="https://developers.openai.com/codex/ide">install in your IDE.</a>
8
+ </br>If you want the desktop app experience, run <code>codex app</code> or visit <a href="https://chatgpt.com/codex?app-landing-page=true">the Codex App page</a>.
9
+ </br>If you are looking for the <em>cloud-based agent</em> from OpenAI, <strong>Codex Web</strong>, go to <a href="https://chatgpt.com/codex">chatgpt.com/codex</a>.</p>
10
+
11
+ ---
12
+
13
+ ## Quickstart
14
+
15
+ ### Installing and running Codex CLI
16
+
17
+ Install globally with your preferred package manager:
18
+
19
+ ```shell
20
+ # Install using npm
21
+ npm install -g @openai/codex
22
+ ```
23
+
24
+ ```shell
25
+ # Install using Homebrew
26
+ brew install --cask codex
27
+ ```
28
+
29
+ Then simply run `codex` to get started.
30
+
31
+ <details>
32
+ <summary>You can also go to the <a href="https://github.com/openai/codex/releases/latest">latest GitHub Release</a> and download the appropriate binary for your platform.</summary>
33
+
34
+ Each GitHub Release contains many executables, but in practice, you likely want one of these:
35
+
36
+ - macOS
37
+ - Apple Silicon/arm64: `codex-aarch64-apple-darwin.tar.gz`
38
+ - x86_64 (older Mac hardware): `codex-x86_64-apple-darwin.tar.gz`
39
+ - Linux
40
+ - x86_64: `codex-x86_64-unknown-linux-musl.tar.gz`
41
+ - arm64: `codex-aarch64-unknown-linux-musl.tar.gz`
42
+
43
+ Each archive contains a single entry with the platform baked into the name (e.g., `codex-x86_64-unknown-linux-musl`), so you likely want to rename it to `codex` after extracting it.
44
+
45
+ </details>
46
+
47
+ ### Using Codex with your ChatGPT plan
48
+
49
+ Run `codex` and select **Sign in with ChatGPT**. We recommend signing into your ChatGPT account to use Codex as part of your Plus, Pro, Business, Edu, or Enterprise plan. [Learn more about what's included in your ChatGPT plan](https://help.openai.com/en/articles/11369540-codex-in-chatgpt).
50
+
51
+ You can also use Codex with an API key, but this requires [additional setup](https://developers.openai.com/codex/auth#sign-in-with-an-api-key).
52
+
53
+ ## Docs
54
+
55
+ - [**Codex Documentation**](https://developers.openai.com/codex)
56
+ - [**Contributing**](./docs/contributing.md)
57
+ - [**Installing & building**](./docs/install.md)
58
+ - [**Open source fund**](./docs/open-source-fund.md)
59
+
60
+ This repository is licensed under the [Apache-2.0 License](LICENSE).
package/bin/codex.js CHANGED
@@ -1,28 +1,35 @@
1
1
  #!/usr/bin/env node
2
+ // Unified entry point for the Codex CLI.
2
3
 
3
4
  import { spawn } from "node:child_process";
4
- import { existsSync } from "node:fs";
5
+ import { existsSync } from "fs";
5
6
  import { createRequire } from "node:module";
6
- import path from "node:path";
7
- import { fileURLToPath } from "node:url";
7
+ import path from "path";
8
+ import { fileURLToPath } from "url";
8
9
 
10
+ // __dirname equivalent in ESM
9
11
  const __filename = fileURLToPath(import.meta.url);
10
12
  const __dirname = path.dirname(__filename);
11
13
  const require = createRequire(import.meta.url);
12
14
 
13
15
  const PLATFORM_PACKAGE_BY_TARGET = {
14
- "x86_64-unknown-linux-musl": "@codexed/codex-linux-x64",
16
+ "x86_64-unknown-linux-gnu": "@codexed/codex-linux-x64",
15
17
  "aarch64-unknown-linux-musl": "@codexed/codex-linux-arm64",
18
+ "x86_64-apple-darwin": "@codexed/codex-darwin-x64",
19
+ "aarch64-apple-darwin": "@codexed/codex-darwin-arm64",
16
20
  "x86_64-pc-windows-msvc": "@codexed/codex-win32-x64",
21
+ "aarch64-pc-windows-msvc": "@codexed/codex-win32-arm64",
17
22
  };
18
23
 
24
+ const { platform, arch } = process;
25
+
19
26
  let targetTriple = null;
20
- switch (process.platform) {
27
+ switch (platform) {
21
28
  case "linux":
22
29
  case "android":
23
- switch (process.arch) {
30
+ switch (arch) {
24
31
  case "x64":
25
- targetTriple = "x86_64-unknown-linux-musl";
32
+ targetTriple = "x86_64-unknown-linux-gnu";
26
33
  break;
27
34
  case "arm64":
28
35
  targetTriple = "aarch64-unknown-linux-musl";
@@ -31,9 +38,28 @@ switch (process.platform) {
31
38
  break;
32
39
  }
33
40
  break;
41
+ case "darwin":
42
+ switch (arch) {
43
+ case "x64":
44
+ targetTriple = "x86_64-apple-darwin";
45
+ break;
46
+ case "arm64":
47
+ targetTriple = "aarch64-apple-darwin";
48
+ break;
49
+ default:
50
+ break;
51
+ }
52
+ break;
34
53
  case "win32":
35
- if (process.arch === "x64") {
36
- targetTriple = "x86_64-pc-windows-msvc";
54
+ switch (arch) {
55
+ case "x64":
56
+ targetTriple = "x86_64-pc-windows-msvc";
57
+ break;
58
+ case "arm64":
59
+ targetTriple = "aarch64-pc-windows-msvc";
60
+ break;
61
+ default:
62
+ break;
37
63
  }
38
64
  break;
39
65
  default:
@@ -41,21 +67,48 @@ switch (process.platform) {
41
67
  }
42
68
 
43
69
  if (!targetTriple) {
44
- throw new Error(
45
- `Unsupported platform: ${process.platform} (${process.arch}). ` +
46
- "Supported targets are linux/x64, linux/arm64, and win32/x64.",
47
- );
70
+ throw new Error(`Unsupported platform: ${platform} (${arch})`);
48
71
  }
49
72
 
50
73
  const platformPackage = PLATFORM_PACKAGE_BY_TARGET[targetTriple];
74
+ if (!platformPackage) {
75
+ throw new Error(`Unsupported target triple: ${targetTriple}`);
76
+ }
77
+
51
78
  const codexBinaryName = process.platform === "win32" ? "codex.exe" : "codex";
79
+ const localVendorRoot = path.join(__dirname, "..", "vendor");
80
+ const localBinaryPath = path.join(
81
+ localVendorRoot,
82
+ targetTriple,
83
+ "codex",
84
+ codexBinaryName,
85
+ );
52
86
 
53
87
  let vendorRoot;
54
88
  try {
55
89
  const packageJsonPath = require.resolve(`${platformPackage}/package.json`);
56
90
  vendorRoot = path.join(path.dirname(packageJsonPath), "vendor");
57
91
  } catch {
58
- const updateCommand = "npm install -g @codexed/codex@latest";
92
+ if (existsSync(localBinaryPath)) {
93
+ vendorRoot = localVendorRoot;
94
+ } else {
95
+ const packageManager = detectPackageManager();
96
+ const updateCommand =
97
+ packageManager === "bun"
98
+ ? "bun install -g @codexed/codex@latest"
99
+ : "npm install -g @codexed/codex@latest";
100
+ throw new Error(
101
+ `Missing optional dependency ${platformPackage}. Reinstall Codex: ${updateCommand}`,
102
+ );
103
+ }
104
+ }
105
+
106
+ if (!vendorRoot) {
107
+ const packageManager = detectPackageManager();
108
+ const updateCommand =
109
+ packageManager === "bun"
110
+ ? "bun install -g @codexed/codex@latest"
111
+ : "npm install -g @codexed/codex@latest";
59
112
  throw new Error(
60
113
  `Missing optional dependency ${platformPackage}. Reinstall Codex: ${updateCommand}`,
61
114
  );
@@ -63,17 +116,61 @@ try {
63
116
 
64
117
  const archRoot = path.join(vendorRoot, targetTriple);
65
118
  const binaryPath = path.join(archRoot, "codex", codexBinaryName);
66
- if (!existsSync(binaryPath)) {
67
- throw new Error(`Missing Codex binary at ${binaryPath}`);
119
+
120
+ // Use an asynchronous spawn instead of spawnSync so that Node is able to
121
+ // respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is
122
+ // executing. This allows us to forward those signals to the child process
123
+ // and guarantees that when either the child terminates or the parent
124
+ // receives a fatal signal, both processes exit in a predictable manner.
125
+
126
+ function getUpdatedPath(newDirs) {
127
+ const pathSep = process.platform === "win32" ? ";" : ":";
128
+ const existingPath = process.env.PATH || "";
129
+ const updatedPath = [
130
+ ...newDirs,
131
+ ...existingPath.split(pathSep).filter(Boolean),
132
+ ].join(pathSep);
133
+ return updatedPath;
134
+ }
135
+
136
+ /**
137
+ * Use heuristics to detect the package manager that was used to install Codex
138
+ * in order to give the user a hint about how to update it.
139
+ */
140
+ function detectPackageManager() {
141
+ const userAgent = process.env.npm_config_user_agent || "";
142
+ if (/\bbun\//.test(userAgent)) {
143
+ return "bun";
144
+ }
145
+
146
+ const execPath = process.env.npm_execpath || "";
147
+ if (execPath.includes("bun")) {
148
+ return "bun";
149
+ }
150
+
151
+ if (
152
+ __dirname.includes(".bun/install/global") ||
153
+ __dirname.includes(".bun\\install\\global")
154
+ ) {
155
+ return "bun";
156
+ }
157
+
158
+ return userAgent ? "npm" : null;
68
159
  }
69
160
 
161
+ const additionalDirs = [];
70
162
  const pathDir = path.join(archRoot, "path");
71
- const pathSep = process.platform === "win32" ? ";" : ":";
72
- const env = {
73
- ...process.env,
74
- PATH: [pathDir, process.env.PATH || ""].filter(Boolean).join(pathSep),
75
- CODEX_MANAGED_BY_NPM: "1",
76
- };
163
+ if (existsSync(pathDir)) {
164
+ additionalDirs.push(pathDir);
165
+ }
166
+ const updatedPath = getUpdatedPath(additionalDirs);
167
+
168
+ const env = { ...process.env, PATH: updatedPath };
169
+ const packageManagerEnvVar =
170
+ detectPackageManager() === "bun"
171
+ ? "CODEX_MANAGED_BY_BUN"
172
+ : "CODEX_MANAGED_BY_NPM";
173
+ env[packageManagerEnvVar] = "1";
77
174
 
78
175
  const child = spawn(binaryPath, process.argv.slice(2), {
79
176
  stdio: "inherit",
@@ -81,17 +178,26 @@ const child = spawn(binaryPath, process.argv.slice(2), {
81
178
  });
82
179
 
83
180
  child.on("error", (err) => {
181
+ // Typically triggered when the binary is missing or not executable.
182
+ // Re-throwing here will terminate the parent with a non-zero exit code
183
+ // while still printing a helpful stack trace.
184
+ // eslint-disable-next-line no-console
84
185
  console.error(err);
85
186
  process.exit(1);
86
187
  });
87
188
 
189
+ // Forward common termination signals to the child so that it shuts down
190
+ // gracefully. In the handler we temporarily disable the default behavior of
191
+ // exiting immediately; once the child has been signaled we simply wait for
192
+ // its exit event which will in turn terminate the parent (see below).
88
193
  const forwardSignal = (signal) => {
89
- if (!child.killed) {
90
- try {
91
- child.kill(signal);
92
- } catch {
93
- // Ignore child signal forwarding failures during teardown.
94
- }
194
+ if (child.killed) {
195
+ return;
196
+ }
197
+ try {
198
+ child.kill(signal);
199
+ } catch {
200
+ /* ignore */
95
201
  }
96
202
  };
97
203
 
@@ -99,6 +205,11 @@ const forwardSignal = (signal) => {
99
205
  process.on(sig, () => forwardSignal(sig));
100
206
  });
101
207
 
208
+ // When the child exits, mirror its termination reason in the parent so that
209
+ // shell scripts and other tooling observe the correct exit status.
210
+ // Wrap the lifetime of the child process in a Promise so that we can await
211
+ // its termination in a structured way. The Promise resolves with an object
212
+ // describing how the child exited: either via exit code or due to a signal.
102
213
  const childResult = await new Promise((resolve) => {
103
214
  child.on("exit", (code, signal) => {
104
215
  if (signal) {
@@ -110,6 +221,8 @@ const childResult = await new Promise((resolve) => {
110
221
  });
111
222
 
112
223
  if (childResult.type === "signal") {
224
+ // Re-emit the same signal so that the parent terminates with the expected
225
+ // semantics (this also sets the correct exit code of 128 + n).
113
226
  process.kill(process.pid, childResult.signal);
114
227
  } else {
115
228
  process.exit(childResult.exitCode);
package/package.json CHANGED
@@ -1,25 +1,28 @@
1
1
  {
2
2
  "name": "@codexed/codex",
3
- "version": "0.121.0-codexed.3",
3
+ "version": "0.121.0-codexed.5",
4
4
  "license": "Apache-2.0",
5
- "description": "Patched Codex CLI with persistent retry behavior.",
6
- "bin": {
7
- "codex": "bin/codex.js"
8
- },
9
5
  "type": "module",
10
6
  "engines": {
11
7
  "node": ">=16"
12
8
  },
13
9
  "files": [
14
10
  "bin",
11
+ "scripts",
15
12
  "README.md"
16
13
  ],
17
- "publishConfig": {
18
- "access": "public"
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/openai/codex.git",
17
+ "directory": "codex-cli"
18
+ },
19
+ "packageManager": "pnpm@10.29.3+sha512.498e1fb4cca5aa06c1dcf2611e6fafc50972ffe7189998c409e90de74566444298ffe43e6cd2acdc775ba1aa7cc5e092a8b7054c811ba8c5770f84693d33d2dc",
20
+ "scripts": {
21
+ "postinstall": "node scripts/postinstall.mjs"
19
22
  },
20
23
  "optionalDependencies": {
21
- "@codexed/codex-linux-x64": "0.121.0-codexed.3",
22
- "@codexed/codex-linux-arm64": "0.121.0-codexed.3",
23
- "@codexed/codex-win32-x64": "0.121.0-codexed.3"
24
+ "@codexed/codex-linux-x64": "0.121.0-codexed.4",
25
+ "@codexed/codex-linux-arm64": "0.121.0-codexed.4",
26
+ "@codexed/codex-win32-x64": "0.121.0-codexed.5"
24
27
  }
25
28
  }
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { chmodSync, mkdirSync, renameSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ const packageRoot = path.resolve(__dirname, "..");
11
+ const codexJs = path.join(packageRoot, "bin", "codex.js");
12
+
13
+ function isGlobalInstall() {
14
+ return (
15
+ process.env.npm_config_global === "true" ||
16
+ process.env.npm_config_location === "global"
17
+ );
18
+ }
19
+
20
+ function resolvePrefix() {
21
+ return process.env.npm_config_prefix || process.env.PREFIX || "/usr/local";
22
+ }
23
+
24
+ function replacePosixLauncher(prefix) {
25
+ const binDir = path.join(prefix, "bin");
26
+ const launcherPath = path.join(binDir, "codex");
27
+ const tmpPath = `${launcherPath}.codexed-tmp`;
28
+
29
+ mkdirSync(binDir, { recursive: true });
30
+ chmodSync(codexJs, 0o755);
31
+ rmSync(tmpPath, { force: true });
32
+ symlinkSync(codexJs, tmpPath);
33
+ rmSync(launcherPath, { force: true, recursive: true });
34
+ renameSync(tmpPath, launcherPath);
35
+ }
36
+
37
+ function replaceWindowsLauncher(prefix) {
38
+ const launcherDir = prefix;
39
+ const cmdPath = path.join(launcherDir, "codex.cmd");
40
+ const ps1Path = path.join(launcherDir, "codex.ps1");
41
+ const escapedCodexJs = codexJs.replace(/\\/g, "\\\\");
42
+
43
+ mkdirSync(launcherDir, { recursive: true });
44
+
45
+ writeFileSync(
46
+ cmdPath,
47
+ `@ECHO OFF\r\nnode "${escapedCodexJs}" %*\r\n`,
48
+ "utf8",
49
+ );
50
+ writeFileSync(
51
+ ps1Path,
52
+ `#!/usr/bin/env pwsh\nnode "${escapedCodexJs}" $args\n`,
53
+ "utf8",
54
+ );
55
+ }
56
+
57
+ if (!isGlobalInstall()) {
58
+ process.exit(0);
59
+ }
60
+
61
+ const prefix = resolvePrefix();
62
+
63
+ if (os.platform() === "win32") {
64
+ replaceWindowsLauncher(prefix);
65
+ } else {
66
+ replacePosixLauncher(prefix);
67
+ }