@christiandoxa/prodex 0.186.0 → 0.188.0

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
@@ -4,7 +4,7 @@
4
4
 
5
5
  Use multiple Codex accounts and supported provider backends from one command line. OpenAI/Codex profiles get quota-aware routing and auto-rotation; provider adapters let `prodex s` launch the Codex front end against Gemini, Anthropic, Copilot, DeepSeek, and local OpenAI-compatible servers.
6
6
 
7
- ![Prodex overview](assets/prodex-overview.png)
7
+ ![Prodex overview](https://github.com/christiandoxa/prodex/releases/download/assets/prodex-overview.png)
8
8
 
9
9
  ## Contents
10
10
 
@@ -567,8 +567,11 @@ prodex run
567
567
  prodex run --profile main
568
568
  prodex run --dry-run
569
569
  prodex exec "review this repo"
570
+ prodex delete 019c9e3d-45a0-7ad0-a6ee-b194ac2d44f9
570
571
  ```
571
572
 
573
+ Codex-owned TUI commands such as `/usage`, `/goal`, `/import`, and `/delete` stay upstream Codex behavior. Prodex preserves their request metadata through the proxy and does not add a competing command surface. The CLI form `prodex delete <session>` passes through to Codex and, after a successful delete, prunes matching Prodex session affinity metadata.
574
+
572
575
  </details>
573
576
 
574
577
  <details>
@@ -788,7 +791,7 @@ Gemini CLI compatibility helpers accept inline `gemini_memory` / `gemini_policy`
788
791
 
789
792
  Before Codex launches, the Gemini provider projects Gemini CLI settings and extension surfaces into the active `CODEX_HOME`: system/global/project `mcpServers` and extension `mcpServers` become generated Codex `[mcp_servers.gemini_*]` entries with settings taking precedence over extension servers of the same Gemini name; system/global/project and extension command hooks are merged into `hooks.json` for Codex `/hooks` review; `~/.gemini/commands`, project `.gemini/commands`, and extension `commands/*.toml` become Codex custom prompts with Gemini command aliases preserved where possible; extension `skills/*/SKILL.md` are copied into generated Codex skill folders under `.agents/skills`; and extension `agents/*.md` become generated Codex custom agents under `agents/*.toml`. Built-in `/prompts:gemini-refresh`, `/prompts:gemini-memory-show`, `/prompts:gemini-memory-refresh`, `/prompts:gemini-memory-inbox`, `/prompts:gemini-remember`, `/prompts:gemini-checkpoint-create`, `/prompts:gemini-checkpoint-restore`, `/prompts:gemini-checkpoint-export`, and `/prompts:gemini-rewind` cover reload/admin, memory, and checkpoint workflows. Generated helper scripts in `CODEX_HOME/bin` include `prodex-gemini-refresh`, `prodex-gemini-checkpoint-create`, and `prodex-gemini-checkpoint-restore`. Set `PRODEX_GEMINI_EXTENSIONS=none` or an allow-list of extension names to control extension loading, `PRODEX_GEMINI_EXTENSION_DIRS` to add extension roots, or `PRODEX_GEMINI_DISABLE_CLI_COMPAT=1` to skip the launch-time Codex surface projection.
790
793
 
791
- Gemini Live realtime websocket sessions translate Codex audio, transcript, text, function-call, function-result, interruption, cancellation, housekeeping, and turn-completion events to and from Gemini `BidiGenerateContent`; one Gemini auth/profile is selected before upgrade and remains fixed for the session. `PRODEX_GEMINI_LIVE_MODEL` overrides the default Live model, while `PRODEX_GEMINI_LIVE_URL` is available for a custom or test Live endpoint. `prodex doctor --runtime` recognizes provider bridge and Gemini markers such as `local_rewrite_provider_model_fallback`, `local_rewrite_gemini_quota_rotate`, `local_rewrite_gemini_invalid_stream_retry`, and `local_rewrite_gemini_live_error`.
794
+ Gemini Live realtime websocket translation remains available for compatible callers and credentialed adapter tests, mapping Codex audio, transcript, text, function-call, function-result, interruption, cancellation, housekeeping, and turn-completion events to and from Gemini `BidiGenerateContent`; one Gemini auth/profile is selected before upgrade and remains fixed for the session. Codex 0.140.0 removed the upstream TUI voice controls, so this bridge should not be treated as a normal Codex TUI voice feature. `PRODEX_GEMINI_LIVE_MODEL` overrides the default Live model, while `PRODEX_GEMINI_LIVE_URL` is available for a custom or test Live endpoint. `prodex doctor --runtime` recognizes provider bridge and Gemini markers such as `local_rewrite_provider_model_fallback`, `local_rewrite_gemini_quota_rotate`, `local_rewrite_gemini_invalid_stream_retry`, and `local_rewrite_gemini_live_error`.
792
795
 
793
796
  Run `npm run test:gemini-schema` after changing Gemini request, response, SSE, semantic compact, exact-output, tool-schema, or Live translation. Run `PRODEX_LIVE_GEMINI=1 npm run test:gemini-live` for a credentialed end-to-end Gemini adapter smoke request; set `PRODEX_BIN` or `PRODEX_LIVE_GEMINI_MODEL` to override the binary or model. Add `PRODEX_LIVE_GEMINI_EXTENDED=1` for command-output-only, file edit, `apply_patch`, reference-repo clone/inspection, optional-tool update discipline, semantic compact, and explicit `exec resume` checks. Add `PRODEX_LIVE_GEMINI_MCP=1` and/or `PRODEX_LIVE_GEMINI_MULTIMODAL=1` when the local environment should also exercise MCP and image-input paths.
794
797
 
@@ -964,7 +967,9 @@ On Unix-like systems, this is usually:
964
967
  ~/.codex
965
968
  ```
966
969
 
967
- In practice, profile `history.jsonl`, `sessions`, `archived_sessions`, `config.toml`, `managed_config.toml`, `environments.toml`, plugins, skills, app-server plugin state, memory-extension state, remote-control enrollment, and Codex runtime SQLite files such as `state_*`, `goals_*`, `logs_*`, and `memories_*` link to the same Codex home that direct Codex uses.
970
+ In practice, profile `history.jsonl`, `sessions`, `archived_sessions`, `config.toml`, `managed_config.toml`, `environments.toml`, `.credentials.json`, plugins, skills, app-server plugin state, memory-extension state, remote-control enrollment, and Codex runtime SQLite files such as `state_*`, `goals_*`, `logs_*`, and `memories_*` link to the same Codex home that direct Codex uses.
971
+
972
+ Codex 0.140.0 defaults CLI auth credentials to the file store, so managed Prodex profiles continue to keep `auth.json` isolated per profile, including OpenAI, API-key, and Bedrock API-key auth JSON. MCP OAuth defaults to Codex `auto`; when it falls back to the file store, `.credentials.json` is shared with direct Codex. OS keyring-backed MCP OAuth credentials remain Codex/OS-owned and are not part of Prodex profile export bundles.
968
973
 
969
974
  Codex cloud-managed config bundle caches are identity/account scoped and remain profile-local. System-level Codex requirements and managed config files remain owned by upstream Codex and the operating system.
970
975
 
@@ -8,6 +8,45 @@ const { createRequire } = require("node:module");
8
8
 
9
9
  const requireFromHere = createRequire(__filename);
10
10
 
11
+ const PLATFORM_TARGETS = {
12
+ linux: {
13
+ x64: {
14
+ packageName: "@openai/codex-linux-x64",
15
+ targetTriple: "x86_64-unknown-linux-musl",
16
+ binaryFileName: "codex",
17
+ },
18
+ arm64: {
19
+ packageName: "@openai/codex-linux-arm64",
20
+ targetTriple: "aarch64-unknown-linux-musl",
21
+ binaryFileName: "codex",
22
+ },
23
+ },
24
+ darwin: {
25
+ x64: {
26
+ packageName: "@openai/codex-darwin-x64",
27
+ targetTriple: "x86_64-apple-darwin",
28
+ binaryFileName: "codex",
29
+ },
30
+ arm64: {
31
+ packageName: "@openai/codex-darwin-arm64",
32
+ targetTriple: "aarch64-apple-darwin",
33
+ binaryFileName: "codex",
34
+ },
35
+ },
36
+ win32: {
37
+ x64: {
38
+ packageName: "@openai/codex-win32-x64",
39
+ targetTriple: "x86_64-pc-windows-msvc",
40
+ binaryFileName: "codex.exe",
41
+ },
42
+ arm64: {
43
+ packageName: "@openai/codex-win32-arm64",
44
+ targetTriple: "aarch64-pc-windows-msvc",
45
+ binaryFileName: "codex.exe",
46
+ },
47
+ },
48
+ };
49
+
11
50
  function resolveCodexBin() {
12
51
  let packageJsonPath;
13
52
  try {
@@ -32,14 +71,58 @@ function resolveCodexBin() {
32
71
  return path.resolve(path.dirname(packageJsonPath), "bin", "codex.js");
33
72
  }
34
73
 
74
+ function explainBundledNativeCodexPermissionIssue() {
75
+ const platformTarget = PLATFORM_TARGETS[process.platform]?.[process.arch];
76
+ if (!platformTarget || process.platform === "win32") {
77
+ return;
78
+ }
79
+
80
+ let platformPackageJsonPath;
81
+ try {
82
+ platformPackageJsonPath = requireFromHere.resolve(`${platformTarget.packageName}/package.json`);
83
+ } catch {
84
+ return;
85
+ }
86
+
87
+ const nativeBinaryPath = path.join(
88
+ path.dirname(platformPackageJsonPath),
89
+ "vendor",
90
+ platformTarget.targetTriple,
91
+ "bin",
92
+ platformTarget.binaryFileName,
93
+ );
94
+ if (!fs.existsSync(nativeBinaryPath)) {
95
+ return;
96
+ }
97
+ try {
98
+ fs.accessSync(nativeBinaryPath, fs.constants.X_OK);
99
+ } catch {
100
+ process.stderr.write(
101
+ [
102
+ `Bundled Codex native binary is not executable: ${nativeBinaryPath}`,
103
+ "Reinstall @christiandoxa/prodex with optional dependencies enabled, or set PRODEX_CODEX_BIN to an existing Codex CLI.",
104
+ "",
105
+ ].join("\n"),
106
+ );
107
+ process.exit(126);
108
+ }
109
+ }
110
+
35
111
  const codexBin = resolveCodexBin();
112
+ explainBundledNativeCodexPermissionIssue();
36
113
  const child = spawn(process.execPath, [codexBin, ...process.argv.slice(2)], {
37
114
  env: process.env,
38
115
  stdio: "inherit",
39
116
  });
40
117
 
41
118
  child.on("error", (error) => {
42
- process.stderr.write(`${error.message}\n`);
119
+ if (error && error.code === "EACCES") {
120
+ process.stderr.write(
121
+ `${error.message}\nSet PRODEX_CODEX_BIN to an existing Codex CLI or reinstall @christiandoxa/prodex.\n`,
122
+ );
123
+ } else {
124
+ process.stderr.write(`${error.message}\n`);
125
+ }
43
126
  process.exit(1);
44
127
  });
45
128
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@christiandoxa/prodex",
3
- "version": "0.186.0",
3
+ "version": "0.188.0",
4
4
  "description": "Safe multi-account auto-rotate for Codex CLI with isolated CODEX_HOME profiles",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -16,12 +16,12 @@
16
16
  "@openai/codex": "latest"
17
17
  },
18
18
  "optionalDependencies": {
19
- "@christiandoxa/prodex-linux-x64": "0.186.0",
20
- "@christiandoxa/prodex-linux-arm64": "0.186.0",
21
- "@christiandoxa/prodex-darwin-x64": "0.186.0",
22
- "@christiandoxa/prodex-darwin-arm64": "0.186.0",
23
- "@christiandoxa/prodex-win32-x64": "0.186.0",
24
- "@christiandoxa/prodex-win32-arm64": "0.186.0"
19
+ "@christiandoxa/prodex-linux-x64": "0.188.0",
20
+ "@christiandoxa/prodex-linux-arm64": "0.188.0",
21
+ "@christiandoxa/prodex-darwin-x64": "0.188.0",
22
+ "@christiandoxa/prodex-darwin-arm64": "0.188.0",
23
+ "@christiandoxa/prodex-win32-x64": "0.188.0",
24
+ "@christiandoxa/prodex-win32-arm64": "0.188.0"
25
25
  },
26
26
  "engines": {
27
27
  "node": ">=18"
package/prodex CHANGED
@@ -46,6 +46,60 @@ const packageRoot = path.dirname(requireFromHere.resolve("./package.json"));
46
46
  const codexShimPath = path.join(packageRoot, "lib", "codex-shim.cjs");
47
47
  const platformPackage = PLATFORM_PACKAGES[process.platform]?.[process.arch];
48
48
 
49
+ function candidateCommandNames(command) {
50
+ if (process.platform !== "win32") {
51
+ return [command];
52
+ }
53
+ const extensions = (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD")
54
+ .split(";")
55
+ .filter(Boolean);
56
+ const lowerCommand = command.toLowerCase();
57
+ if (extensions.some((extension) => lowerCommand.endsWith(extension.toLowerCase()))) {
58
+ return [command];
59
+ }
60
+ return [command, ...extensions.map((extension) => `${command}${extension.toLowerCase()}`)];
61
+ }
62
+
63
+ function isExecutableFile(filePath) {
64
+ try {
65
+ const stats = fs.statSync(filePath);
66
+ if (!stats.isFile()) {
67
+ return false;
68
+ }
69
+ fs.accessSync(filePath, fs.constants.X_OK);
70
+ return true;
71
+ } catch {
72
+ return false;
73
+ }
74
+ }
75
+
76
+ function resolveCommandFromPath(command, pathValue) {
77
+ const hasPathSeparator = command.includes("/") || command.includes("\\");
78
+ if (path.isAbsolute(command) || hasPathSeparator) {
79
+ return isExecutableFile(command) ? command : null;
80
+ }
81
+
82
+ for (const entry of (pathValue || "").split(path.delimiter)) {
83
+ if (!entry) {
84
+ continue;
85
+ }
86
+ for (const candidateName of candidateCommandNames(command)) {
87
+ const candidate = path.join(entry, candidateName);
88
+ if (isExecutableFile(candidate)) {
89
+ return candidate;
90
+ }
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+
96
+ function resolveExternalCodexBin() {
97
+ if (process.env.PRODEX_CODEX_BIN) {
98
+ return process.env.PRODEX_CODEX_BIN;
99
+ }
100
+ return resolveCommandFromPath("codex", process.env.PATH || "");
101
+ }
102
+
49
103
  if (!platformPackage) {
50
104
  process.stderr.write(
51
105
  `Unsupported platform for @christiandoxa/prodex: ${process.platform} ${process.arch}\n`,
@@ -75,34 +129,42 @@ if (!fs.existsSync(nativeBinaryPath)) {
75
129
  process.exit(1);
76
130
  }
77
131
 
78
- const shimDir = fs.mkdtempSync(path.join(os.tmpdir(), "prodex-codex-"));
79
- const codexCmd = path.join(shimDir, "codex");
80
- const codexCmdWindows = path.join(shimDir, "codex.cmd");
81
-
82
- fs.writeFileSync(
83
- codexCmd,
84
- `#!/bin/sh\nexec node ${JSON.stringify(codexShimPath)} "$@"\n`,
85
- { mode: 0o755 },
86
- );
87
- fs.writeFileSync(
88
- codexCmdWindows,
89
- `@echo off\r\nnode ${JSON.stringify(codexShimPath)} %*\r\n`,
90
- { mode: 0o755 },
91
- );
92
-
93
- const cleanup = () => {
94
- try {
95
- fs.rmSync(shimDir, { recursive: true, force: true });
96
- } catch {
97
- // Best-effort cleanup only.
98
- }
99
- };
132
+ let cleanup = () => {};
133
+ let codexBin = resolveExternalCodexBin();
134
+ let childPath = process.env.PATH ?? "";
135
+
136
+ if (!codexBin) {
137
+ const shimDir = fs.mkdtempSync(path.join(os.tmpdir(), "prodex-codex-"));
138
+ const codexCmd = path.join(shimDir, "codex");
139
+ const codexCmdWindows = path.join(shimDir, "codex.cmd");
140
+
141
+ fs.writeFileSync(
142
+ codexCmd,
143
+ `#!/bin/sh\nexec node ${JSON.stringify(codexShimPath)} "$@"\n`,
144
+ { mode: 0o755 },
145
+ );
146
+ fs.writeFileSync(
147
+ codexCmdWindows,
148
+ `@echo off\r\nnode ${JSON.stringify(codexShimPath)} %*\r\n`,
149
+ { mode: 0o755 },
150
+ );
151
+
152
+ childPath = `${shimDir}${path.delimiter}${childPath}`;
153
+ codexBin = "codex";
154
+ cleanup = () => {
155
+ try {
156
+ fs.rmSync(shimDir, { recursive: true, force: true });
157
+ } catch {
158
+ // Best-effort cleanup only.
159
+ }
160
+ };
161
+ }
100
162
 
101
163
  const child = spawn(nativeBinaryPath, process.argv.slice(2), {
102
164
  env: {
103
165
  ...process.env,
104
- PATH: `${shimDir}${path.delimiter}${process.env.PATH ?? ""}`,
105
- PRODEX_CODEX_BIN: "codex",
166
+ PATH: childPath,
167
+ PRODEX_CODEX_BIN: codexBin,
106
168
  npm_package_name: packageJson.name,
107
169
  npm_package_version: packageJson.version,
108
170
  },