codex-plus-patcher 0.7.1 → 0.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-plus-patcher",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "private": false,
5
5
  "description": "Patch queue tool for building a local Codex Plus.app from an installed Codex.app.",
6
6
  "repository": {
package/src/cli.js CHANGED
@@ -13,6 +13,7 @@ const {
13
13
  const { readAsar, walkFiles } = require("./core/asar");
14
14
  const {
15
15
  DEFAULT_DEV_HOME,
16
+ DEFAULT_DEV_INSTANCE_ID,
16
17
  DEFAULT_ELECTRON_USER_DATA,
17
18
  formatLaunchDevResult,
18
19
  formatSyncDevHomeResult,
@@ -49,12 +50,14 @@ function parseArgs(argv) {
49
50
  includeNativeOpenProbes: false,
50
51
  noProgress: false,
51
52
  quiet: false,
53
+ devInstanceId: DEFAULT_DEV_INSTANCE_ID,
52
54
  };
53
55
  const rest = [...argv];
54
56
  if (rest[0] && !rest[0].startsWith("--")) args.command = rest.shift();
55
57
  if (args.command === "audit-plugins") {
56
58
  args.target = DEFAULT_AUDIT_TARGET;
57
59
  args.remoteDebuggingPort = DEFAULT_AUDIT_PORT;
60
+ args.devInstanceId = "audit";
58
61
  }
59
62
  for (let index = 0; index < rest.length; index += 1) {
60
63
  const arg = rest[index];
@@ -68,6 +71,7 @@ function parseArgs(argv) {
68
71
  else if (arg === "--source-home") args.sourceHome = path.resolve(expandPath(next()));
69
72
  else if (arg === "--dev-home") args.devHome = path.resolve(expandPath(next()));
70
73
  else if (arg === "--electron-user-data") args.electronUserDataPath = path.resolve(expandPath(next()));
74
+ else if (arg === "--dev-instance-id") args.devInstanceId = next();
71
75
  else if (arg === "--remote-debugging-port" || arg === "--port") {
72
76
  const value = next();
73
77
  args.remoteDebuggingPort = args.command === "audit-plugins" ? Number(value) : value;
@@ -120,6 +124,7 @@ Options:
120
124
  Isolated Electron userData for launch-dev. Default: ./work/codex-plus-electron-user-data
121
125
  --remote-debugging-port <port>
122
126
  Remote debugging port passed to launch-dev or audit-plugins
127
+ --dev-instance-id <id> Dev-only bundle identity suffix. Default: dev, or audit for audit-plugins
123
128
  --asar <path> app.asar path for ASAR readback commands
124
129
  --file <asar-path> Packed file path for asar-cat
125
130
  --contains <text> Filter asar-list paths by substring
@@ -396,6 +401,7 @@ async function main() {
396
401
  devHome: args.devHome,
397
402
  electronUserDataPath: args.electronUserDataPath,
398
403
  remoteDebuggingPort: args.remoteDebuggingPort,
404
+ devInstanceId: args.devInstanceId,
399
405
  });
400
406
  process.stdout.write(args.json ? `${JSON.stringify(result, null, 2)}\n` : formatLaunchDevResult(result));
401
407
  return;
package/src/core/asar.js CHANGED
@@ -11,9 +11,10 @@ function sha256File(file) {
11
11
 
12
12
  function readAsar(asarPath) {
13
13
  const buffer = fs.readFileSync(asarPath);
14
+ const headerSize = buffer.readUInt32LE(4);
14
15
  const jsonSize = buffer.readUInt32LE(12);
15
16
  const header = JSON.parse(buffer.subarray(16, 16 + jsonSize).toString("utf8"));
16
- return { buffer, dataStart: 16 + jsonSize, header };
17
+ return { buffer, dataStart: 8 + headerSize, header };
17
18
  }
18
19
 
19
20
  function walkFiles(node, prefix = "", out = []) {
@@ -99,13 +100,14 @@ function patchAsar(asarPath, fileTransforms, transformContext = {}) {
99
100
  }
100
101
 
101
102
  const json = Buffer.from(JSON.stringify(archive.header), "utf8");
103
+ const padding = Buffer.alloc((4 - (json.length % 4)) % 4);
102
104
  const header = Buffer.alloc(16);
103
105
  header.writeUInt32LE(4, 0);
104
- header.writeUInt32LE(json.length + 8, 4);
105
- header.writeUInt32LE(json.length + 4, 8);
106
+ header.writeUInt32LE(json.length + padding.length + 8, 4);
107
+ header.writeUInt32LE(json.length + padding.length + 4, 8);
106
108
  header.writeUInt32LE(json.length, 12);
107
109
 
108
- fs.writeFileSync(asarPath, Buffer.concat([header, json, ...dataBuffers]));
110
+ fs.writeFileSync(asarPath, Buffer.concat([header, json, padding, ...dataBuffers]));
109
111
  return sha256File(asarPath);
110
112
  }
111
113
 
@@ -4,12 +4,13 @@ const os = require("node:os");
4
4
  const path = require("node:path");
5
5
 
6
6
  const { patchAsar } = require("./asar");
7
- const { setPlistBuddyValue } = require("./plist");
7
+ const { replacePlistString, setPlistBuddyValue } = require("./plist");
8
8
 
9
9
  const ASAR_PATH_IN_BUNDLE = "Contents/Resources/app.asar";
10
10
  const RUNTIME_MANIFEST_FILE = "webview/assets/codex-plus/runtime-manifest.js";
11
11
  const DEFAULT_DEV_HOME = path.resolve("work/codex-plus-dev-home");
12
12
  const DEFAULT_ELECTRON_USER_DATA = path.resolve("work/codex-plus-electron-user-data");
13
+ const DEFAULT_DEV_INSTANCE_ID = "dev";
13
14
  const DEV_MODE_WARNING =
14
15
  "Dev mode shares the original Codex worktrees. Use it for UI/plugin validation; do not edit the same checkout from regular Codex and Codex Plus at the same time.";
15
16
 
@@ -179,11 +180,40 @@ function syncDevHome({
179
180
  };
180
181
  }
181
182
 
182
- function buildLaunchDev({ targetApp, devHome = DEFAULT_DEV_HOME, electronUserDataPath = DEFAULT_ELECTRON_USER_DATA, remoteDebuggingPort } = {}) {
183
+ function sanitizeDevInstanceId(devInstanceId) {
184
+ if (devInstanceId == null || devInstanceId === "") return null;
185
+ const sanitized = String(devInstanceId)
186
+ .trim()
187
+ .toLowerCase()
188
+ .replace(/[^a-z0-9-]+/g, "-")
189
+ .replace(/^-+|-+$/g, "");
190
+ if (!sanitized) throw new Error("--dev-instance-id must contain at least one letter or number");
191
+ return sanitized;
192
+ }
193
+
194
+ function devBundleIdentity(devInstanceId) {
195
+ const sanitized = sanitizeDevInstanceId(devInstanceId);
196
+ if (!sanitized) return null;
197
+ return {
198
+ id: sanitized,
199
+ bundleIdentifier: `com.openai.codex-plus.${sanitized}`,
200
+ displayName: `Codex Plus (${sanitized})`,
201
+ name: `Codex Plus ${sanitized}`,
202
+ };
203
+ }
204
+
205
+ function buildLaunchDev({
206
+ targetApp,
207
+ devHome = DEFAULT_DEV_HOME,
208
+ electronUserDataPath = DEFAULT_ELECTRON_USER_DATA,
209
+ remoteDebuggingPort,
210
+ devInstanceId = DEFAULT_DEV_INSTANCE_ID,
211
+ } = {}) {
183
212
  if (!targetApp) throw new Error("--target is required");
184
213
  const appBinary = path.join(path.resolve(targetApp), "Contents/MacOS/Codex");
185
214
  const resolvedDevHome = path.resolve(devHome);
186
215
  const resolvedElectronUserDataPath = path.resolve(electronUserDataPath);
216
+ const instanceIdentity = devBundleIdentity(devInstanceId);
187
217
  const args = [`--user-data-dir=${resolvedElectronUserDataPath}`];
188
218
  if (remoteDebuggingPort != null) args.push(`--remote-debugging-port=${remoteDebuggingPort}`);
189
219
  return {
@@ -193,6 +223,7 @@ function buildLaunchDev({ targetApp, devHome = DEFAULT_DEV_HOME, electronUserDat
193
223
  CODEX_HOME: resolvedDevHome,
194
224
  CODEX_ELECTRON_USER_DATA_PATH: resolvedElectronUserDataPath,
195
225
  },
226
+ instanceIdentity,
196
227
  warning: DEV_MODE_WARNING,
197
228
  };
198
229
  }
@@ -217,18 +248,46 @@ function markDevRuntimeConfig(targetApp, { patchAsarImpl = patchAsar, setPlistBu
217
248
  return { asar: asarPath, patchedAsarSha };
218
249
  }
219
250
 
220
- function launchDevApp({ spawn = childProcess.spawn, env = process.env, markDevRuntimeConfigImpl = markDevRuntimeConfig, ...options } = {}) {
251
+ function signDevApp(targetApp, execFileSync = childProcess.execFileSync) {
252
+ execFileSync("/usr/bin/codesign", ["--force", "--deep", "--sign", "-", path.resolve(targetApp)], { stdio: "pipe" });
253
+ return { signed: true };
254
+ }
255
+
256
+ function markDevBundleIdentity(
257
+ targetApp,
258
+ devInstanceId = DEFAULT_DEV_INSTANCE_ID,
259
+ { replacePlistStringImpl = replacePlistString } = {},
260
+ ) {
261
+ const identity = devBundleIdentity(devInstanceId);
262
+ if (!identity) return null;
263
+ const plistPath = path.join(path.resolve(targetApp), "Contents/Info.plist");
264
+ replacePlistStringImpl(plistPath, "CFBundleIdentifier", identity.bundleIdentifier);
265
+ replacePlistStringImpl(plistPath, "CFBundleDisplayName", identity.displayName);
266
+ replacePlistStringImpl(plistPath, "CFBundleName", identity.name);
267
+ return identity;
268
+ }
269
+
270
+ function launchDevApp({
271
+ spawn = childProcess.spawn,
272
+ env = process.env,
273
+ markDevRuntimeConfigImpl = markDevRuntimeConfig,
274
+ markDevBundleIdentityImpl = markDevBundleIdentity,
275
+ signDevAppImpl = signDevApp,
276
+ ...options
277
+ } = {}) {
221
278
  const launch = buildLaunchDev(options);
222
279
  fs.mkdirSync(launch.env.CODEX_HOME, { recursive: true });
223
280
  fs.mkdirSync(launch.env.CODEX_ELECTRON_USER_DATA_PATH, { recursive: true });
224
281
  const devRuntimeConfig = markDevRuntimeConfigImpl(options.targetApp);
282
+ const devBundle = markDevBundleIdentityImpl(options.targetApp, options.devInstanceId);
283
+ const devSignature = signDevAppImpl(options.targetApp);
225
284
  const child = spawn(launch.command, launch.args, {
226
285
  detached: true,
227
286
  env: { ...env, ...launch.env },
228
287
  stdio: "ignore",
229
288
  });
230
289
  child.unref();
231
- return { ...launch, devRuntimeConfig, pid: child.pid };
290
+ return { ...launch, devRuntimeConfig, devBundle, devSignature, pid: child.pid };
232
291
  }
233
292
 
234
293
  function formatSyncDevHomeResult(result) {
@@ -255,6 +314,7 @@ function formatLaunchDevResult(result) {
255
314
  `CODEX_ELECTRON_USER_DATA_PATH: ${result.env.CODEX_ELECTRON_USER_DATA_PATH}`,
256
315
  ];
257
316
  if (result.pid != null) lines.push(`PID: ${result.pid}`);
317
+ if (result.instanceIdentity) lines.push(`Bundle identity: ${result.instanceIdentity.bundleIdentifier}`);
258
318
  lines.push(`Warning: ${result.warning}`);
259
319
  return `${lines.join("\n")}\n`;
260
320
  }
@@ -262,13 +322,18 @@ function formatLaunchDevResult(result) {
262
322
  module.exports = {
263
323
  COPY_ENTRIES,
264
324
  DEFAULT_DEV_HOME,
325
+ DEFAULT_DEV_INSTANCE_ID,
265
326
  DEFAULT_ELECTRON_USER_DATA,
266
327
  DEV_MODE_WARNING,
267
328
  SQLITE_SNAPSHOT_ENTRIES,
268
329
  buildLaunchDev,
330
+ devBundleIdentity,
269
331
  formatLaunchDevResult,
270
332
  formatSyncDevHomeResult,
271
333
  launchDevApp,
334
+ markDevBundleIdentity,
272
335
  markDevRuntimeConfig,
336
+ sanitizeDevInstanceId,
337
+ signDevApp,
273
338
  syncDevHome,
274
339
  };