codex-plus-patcher 0.6.0 → 0.7.1
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 +3 -1
- package/package.json +5 -3
- package/src/cli.js +88 -0
- package/src/core/dev-mode.js +274 -0
- package/src/core/plugin-audit.js +1267 -0
- package/src/patches/26.623.41415-4505.js +44 -0
- package/src/patches/index.js +8 -1
- package/src/patches/lib/common-patches.js +411 -248
- package/src/patches/lib/hooks/about.js +7 -0
- package/src/patches/lib/hooks/diagnostics.js +7 -0
- package/src/patches/lib/hooks/mermaid.js +7 -0
- package/src/patches/lib/hooks/message-composer.js +7 -0
- package/src/patches/lib/hooks/native-main.js +7 -0
- package/src/patches/lib/hooks/project-selector.js +12 -0
- package/src/patches/lib/hooks/review.js +9 -0
- package/src/patches/lib/hooks/settings-commands.js +13 -0
- package/src/patches/lib/hooks/sidebar.js +7 -0
- package/src/patches/lib/hooks/thread-header.js +7 -0
- package/src/patches/lib/hooks/worker.js +7 -0
- package/src/patches/lib/project-selector-shortcut-patch.js +84 -8
- package/src/runtime/api/about.js +16 -0
- package/src/runtime/api/commands.js +85 -0
- package/src/runtime/api/composer.js +14 -0
- package/src/runtime/api/diagnostics.js +38 -0
- package/src/runtime/api/errors.js +20 -0
- package/src/runtime/api/index.js +82 -0
- package/src/runtime/api/mermaid.js +14 -0
- package/src/runtime/api/message.js +14 -0
- package/src/runtime/api/modules.js +57 -0
- package/src/runtime/api/native.js +14 -0
- package/src/runtime/api/patches.js +31 -0
- package/src/runtime/api/review.js +20 -0
- package/src/runtime/api/settings.js +76 -0
- package/src/runtime/api/sidebar.js +24 -0
- package/src/runtime/api/styles.js +28 -0
- package/src/runtime/api/threadHeader.js +41 -0
- package/src/runtime/assets.js +59 -18
- package/src/runtime/host/messageComposer.js +16 -0
- package/src/runtime/host/nativeMain.js +159 -0
- package/src/runtime/host/projectSelector.js +58 -0
- package/src/runtime/host/review.js +62 -0
- package/src/runtime/host/sidebar.js +21 -0
- package/src/runtime/host/threadHeader.js +9 -0
- package/src/runtime/{worker.js → host/worker.js} +7 -0
- package/src/runtime/plugins/mermaidFullscreen.js +19 -6
- package/src/runtime/plugins/nestedRepositories.js +72 -11
- package/src/runtime/plugins/projectColors.js +94 -7
- package/src/runtime/plugins/projectSelectorShortcut.js +67 -12
- package/src/runtime/plugins/sidebarNameBlur.js +1 -1
- package/src/runtime/runtime.js +23 -441
package/README.md
CHANGED
|
@@ -39,7 +39,9 @@ The generated app includes a readable Codex Plus runtime under
|
|
|
39
39
|
`webview/assets/codex-plus/`. Versioned ASAR patches install the runtime,
|
|
40
40
|
built-in plugins, and the small Codex core hooks those plugins use. See
|
|
41
41
|
[Runtime Plugin Support](docs/plugin-support.md) for the currently supported
|
|
42
|
-
plugin interfaces.
|
|
42
|
+
plugin interfaces and [Plugin Architecture](docs/plugin-architecture.md) for
|
|
43
|
+
the layer rules. Use [Plugin Debugging](docs/plugin-debugging.md) for the
|
|
44
|
+
side-by-side dev launch and live proof workflow.
|
|
43
45
|
|
|
44
46
|
## How It Works
|
|
45
47
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-plus-patcher",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
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": {
|
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
"LICENSE"
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
|
-
"test": "node --test",
|
|
24
|
-
"check": "node scripts/check-syntax.js"
|
|
23
|
+
"test": "node --test tests/*.test.js",
|
|
24
|
+
"check": "node scripts/check-syntax.js",
|
|
25
|
+
"check:pr": "node scripts/safe-automerge-pr.js --check",
|
|
26
|
+
"pr:automerge": "node scripts/safe-automerge-pr.js"
|
|
25
27
|
},
|
|
26
28
|
"engines": {
|
|
27
29
|
"node": ">=20"
|
package/src/cli.js
CHANGED
|
@@ -2,7 +2,23 @@
|
|
|
2
2
|
const os = require("node:os");
|
|
3
3
|
const path = require("node:path");
|
|
4
4
|
|
|
5
|
+
const {
|
|
6
|
+
createAuditProgress,
|
|
7
|
+
DEFAULT_PORT: DEFAULT_AUDIT_PORT,
|
|
8
|
+
DEFAULT_TARGET: DEFAULT_AUDIT_TARGET,
|
|
9
|
+
formatAuditJson,
|
|
10
|
+
formatAuditResult,
|
|
11
|
+
runAudit,
|
|
12
|
+
} = require("./core/plugin-audit");
|
|
5
13
|
const { readAsar, walkFiles } = require("./core/asar");
|
|
14
|
+
const {
|
|
15
|
+
DEFAULT_DEV_HOME,
|
|
16
|
+
DEFAULT_ELECTRON_USER_DATA,
|
|
17
|
+
formatLaunchDevResult,
|
|
18
|
+
formatSyncDevHomeResult,
|
|
19
|
+
launchDevApp,
|
|
20
|
+
syncDevHome,
|
|
21
|
+
} = require("./core/dev-mode");
|
|
6
22
|
const { patchCodexApp } = require("./core/patch-engine");
|
|
7
23
|
const { resolveReleasePatchDirectory } = require("./core/release");
|
|
8
24
|
const { patchSets: builtInPatchSets } = require("./patches");
|
|
@@ -18,15 +34,28 @@ function parseArgs(argv) {
|
|
|
18
34
|
command: argv.length === 0 ? "help" : "apply",
|
|
19
35
|
source: "/Applications/Codex.app",
|
|
20
36
|
target: path.join(os.homedir(), "Applications", "Codex Plus.app"),
|
|
37
|
+
sourceHome: path.join(os.homedir(), ".codex"),
|
|
38
|
+
devHome: DEFAULT_DEV_HOME,
|
|
39
|
+
electronUserDataPath: DEFAULT_ELECTRON_USER_DATA,
|
|
21
40
|
mode: "builtin",
|
|
22
41
|
releaseAsset: "codex-plus-patches.tgz",
|
|
23
42
|
releaseTag: "latest",
|
|
24
43
|
dryRun: false,
|
|
25
44
|
json: false,
|
|
26
45
|
debug: false,
|
|
46
|
+
apply: true,
|
|
47
|
+
launch: true,
|
|
48
|
+
keepOpen: false,
|
|
49
|
+
includeNativeOpenProbes: false,
|
|
50
|
+
noProgress: false,
|
|
51
|
+
quiet: false,
|
|
27
52
|
};
|
|
28
53
|
const rest = [...argv];
|
|
29
54
|
if (rest[0] && !rest[0].startsWith("--")) args.command = rest.shift();
|
|
55
|
+
if (args.command === "audit-plugins") {
|
|
56
|
+
args.target = DEFAULT_AUDIT_TARGET;
|
|
57
|
+
args.remoteDebuggingPort = DEFAULT_AUDIT_PORT;
|
|
58
|
+
}
|
|
30
59
|
for (let index = 0; index < rest.length; index += 1) {
|
|
31
60
|
const arg = rest[index];
|
|
32
61
|
const next = () => {
|
|
@@ -36,6 +65,13 @@ function parseArgs(argv) {
|
|
|
36
65
|
};
|
|
37
66
|
if (arg === "--source") args.source = path.resolve(expandPath(next()));
|
|
38
67
|
else if (arg === "--target") args.target = path.resolve(expandPath(next()));
|
|
68
|
+
else if (arg === "--source-home") args.sourceHome = path.resolve(expandPath(next()));
|
|
69
|
+
else if (arg === "--dev-home") args.devHome = path.resolve(expandPath(next()));
|
|
70
|
+
else if (arg === "--electron-user-data") args.electronUserDataPath = path.resolve(expandPath(next()));
|
|
71
|
+
else if (arg === "--remote-debugging-port" || arg === "--port") {
|
|
72
|
+
const value = next();
|
|
73
|
+
args.remoteDebuggingPort = args.command === "audit-plugins" ? Number(value) : value;
|
|
74
|
+
}
|
|
39
75
|
else if (arg === "--asar") args.asar = path.resolve(expandPath(next()));
|
|
40
76
|
else if (arg === "--file") args.file = next();
|
|
41
77
|
else if (arg === "--contains") args.contains = next();
|
|
@@ -45,6 +81,12 @@ function parseArgs(argv) {
|
|
|
45
81
|
else if (arg === "--release-tag") args.releaseTag = next();
|
|
46
82
|
else if (arg === "--release-asset") args.releaseAsset = next();
|
|
47
83
|
else if (arg === "--dry-run") args.dryRun = true;
|
|
84
|
+
else if (arg === "--no-apply") args.apply = false;
|
|
85
|
+
else if (arg === "--no-launch") args.launch = false;
|
|
86
|
+
else if (arg === "--keep-open") args.keepOpen = true;
|
|
87
|
+
else if (arg === "--include-native-open-probes") args.includeNativeOpenProbes = true;
|
|
88
|
+
else if (arg === "--no-progress") args.noProgress = true;
|
|
89
|
+
else if (arg === "--quiet") args.quiet = true;
|
|
48
90
|
else if (arg === "--debug") args.debug = true;
|
|
49
91
|
else if (arg === "--json" || arg === "--format=json") args.json = true;
|
|
50
92
|
else if (arg === "--format") {
|
|
@@ -62,6 +104,9 @@ function helpText() {
|
|
|
62
104
|
return `Usage:
|
|
63
105
|
codex-plus-patcher
|
|
64
106
|
codex-plus-patcher apply [options]
|
|
107
|
+
codex-plus-patcher audit-plugins [--json] [--quiet] [--no-progress] [--keep-open] [--include-native-open-probes]
|
|
108
|
+
codex-plus-patcher dev-sync [--source-home <path>] [--dev-home <path>] [--json]
|
|
109
|
+
codex-plus-patcher launch-dev --target <path> [--dev-home <path>] [--electron-user-data <path>] [--remote-debugging-port <port>] [--json]
|
|
65
110
|
codex-plus-patcher menu-diagnostics --asar <path> [--json]
|
|
66
111
|
codex-plus-patcher asar-list --asar <path> [--contains <text>] [--json]
|
|
67
112
|
codex-plus-patcher asar-cat --asar <path> --file <asar-path> [--json]
|
|
@@ -69,6 +114,12 @@ function helpText() {
|
|
|
69
114
|
Options:
|
|
70
115
|
--source <path> Source Codex.app. Default: /Applications/Codex.app
|
|
71
116
|
--target <path> Target Codex Plus.app. Default: ~/Applications/Codex Plus.app
|
|
117
|
+
--source-home <path> Original Codex home for dev-sync. Default: ~/.codex
|
|
118
|
+
--dev-home <path> Isolated CODEX_HOME for dev mode. Default: ./work/codex-plus-dev-home
|
|
119
|
+
--electron-user-data <path>
|
|
120
|
+
Isolated Electron userData for launch-dev. Default: ./work/codex-plus-electron-user-data
|
|
121
|
+
--remote-debugging-port <port>
|
|
122
|
+
Remote debugging port passed to launch-dev or audit-plugins
|
|
72
123
|
--asar <path> app.asar path for ASAR readback commands
|
|
73
124
|
--file <asar-path> Packed file path for asar-cat
|
|
74
125
|
--contains <text> Filter asar-list paths by substring
|
|
@@ -78,6 +129,13 @@ Options:
|
|
|
78
129
|
--release-tag <tag> Release mode tag. Default: latest
|
|
79
130
|
--release-asset <name> Release mode asset. Default: codex-plus-patches.tgz
|
|
80
131
|
--dry-run Select and report the patch without copying/signing
|
|
132
|
+
--no-apply Reuse an existing audit target without applying patches
|
|
133
|
+
--no-launch Attach to an existing audit app instead of launching
|
|
134
|
+
--keep-open Leave the audit-launched app open after probes finish
|
|
135
|
+
--include-native-open-probes
|
|
136
|
+
Also open DevTools and Mermaid viewer windows during audit probes
|
|
137
|
+
--no-progress Suppress audit progress and print only the final summary
|
|
138
|
+
--quiet Print minimal audit output
|
|
81
139
|
--debug Print stack traces for CLI errors
|
|
82
140
|
--json Print the machine-readable result
|
|
83
141
|
`;
|
|
@@ -327,6 +385,28 @@ async function main() {
|
|
|
327
385
|
process.stdout.write(args.json ? `${JSON.stringify(result, null, 2)}\n` : formatMenuDiagnosticsResult(result));
|
|
328
386
|
return;
|
|
329
387
|
}
|
|
388
|
+
if (args.command === "dev-sync") {
|
|
389
|
+
const result = syncDevHome(args);
|
|
390
|
+
process.stdout.write(args.json ? `${JSON.stringify(result, null, 2)}\n` : formatSyncDevHomeResult(result));
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (args.command === "launch-dev") {
|
|
394
|
+
const result = launchDevApp({
|
|
395
|
+
targetApp: args.target,
|
|
396
|
+
devHome: args.devHome,
|
|
397
|
+
electronUserDataPath: args.electronUserDataPath,
|
|
398
|
+
remoteDebuggingPort: args.remoteDebuggingPort,
|
|
399
|
+
});
|
|
400
|
+
process.stdout.write(args.json ? `${JSON.stringify(result, null, 2)}\n` : formatLaunchDevResult(result));
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (args.command === "audit-plugins") {
|
|
404
|
+
const progress = await createAuditProgress(args);
|
|
405
|
+
const result = await runAudit(args, { progress });
|
|
406
|
+
process.stdout.write(args.json ? formatAuditJson(result) : formatAuditResult(result, args));
|
|
407
|
+
if (!result.ok) process.exitCode = 1;
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
330
410
|
if (args.command !== "apply") throw new Error(`Unknown command: ${args.command}`);
|
|
331
411
|
|
|
332
412
|
const patchSets = await loadPatchSets(args);
|
|
@@ -357,18 +437,26 @@ if (require.main === module) {
|
|
|
357
437
|
|
|
358
438
|
module.exports = {
|
|
359
439
|
createApplyProgress,
|
|
440
|
+
createAuditProgress,
|
|
360
441
|
expandPath,
|
|
361
442
|
formatAsarCatResult,
|
|
362
443
|
formatAsarListResult,
|
|
444
|
+
formatAuditJson,
|
|
445
|
+
formatAuditResult,
|
|
363
446
|
formatError,
|
|
447
|
+
formatLaunchDevResult,
|
|
364
448
|
formatMenuDiagnosticsResult,
|
|
365
449
|
formatResult,
|
|
450
|
+
formatSyncDevHomeResult,
|
|
366
451
|
helpText,
|
|
367
452
|
listAsarFiles,
|
|
368
453
|
loadPatchSets,
|
|
454
|
+
launchDevApp,
|
|
369
455
|
menuDiagnostics,
|
|
370
456
|
parseArgs,
|
|
371
457
|
readAsarFile,
|
|
372
458
|
requirePatchSetModule,
|
|
459
|
+
runAudit,
|
|
373
460
|
shouldShowApplyProgress,
|
|
461
|
+
syncDevHome,
|
|
374
462
|
};
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
const childProcess = require("node:child_process");
|
|
2
|
+
const fs = require("node:fs");
|
|
3
|
+
const os = require("node:os");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
|
|
6
|
+
const { patchAsar } = require("./asar");
|
|
7
|
+
const { setPlistBuddyValue } = require("./plist");
|
|
8
|
+
|
|
9
|
+
const ASAR_PATH_IN_BUNDLE = "Contents/Resources/app.asar";
|
|
10
|
+
const RUNTIME_MANIFEST_FILE = "webview/assets/codex-plus/runtime-manifest.js";
|
|
11
|
+
const DEFAULT_DEV_HOME = path.resolve("work/codex-plus-dev-home");
|
|
12
|
+
const DEFAULT_ELECTRON_USER_DATA = path.resolve("work/codex-plus-electron-user-data");
|
|
13
|
+
const DEV_MODE_WARNING =
|
|
14
|
+
"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
|
+
const COPY_ENTRIES = [
|
|
17
|
+
"config.toml",
|
|
18
|
+
"auth.json",
|
|
19
|
+
".codex-global-state.json",
|
|
20
|
+
"models_cache.json",
|
|
21
|
+
"version.json",
|
|
22
|
+
"installation_id",
|
|
23
|
+
"history.jsonl",
|
|
24
|
+
"session_index.jsonl",
|
|
25
|
+
"AGENTS.md",
|
|
26
|
+
"rules",
|
|
27
|
+
"skills",
|
|
28
|
+
"plugins",
|
|
29
|
+
"vendor_imports",
|
|
30
|
+
"chrome-native-hosts.json",
|
|
31
|
+
"chrome-native-hosts-v2.json",
|
|
32
|
+
"computer-use/config.json",
|
|
33
|
+
];
|
|
34
|
+
const SQLITE_SNAPSHOT_ENTRIES = ["state_5.sqlite", "sqlite/state_5.sqlite"];
|
|
35
|
+
const EXCLUDED_DEV_STATE_ENTRIES = [
|
|
36
|
+
"sqlite",
|
|
37
|
+
"cache",
|
|
38
|
+
"log",
|
|
39
|
+
"tmp",
|
|
40
|
+
"process_manager",
|
|
41
|
+
"generated_images",
|
|
42
|
+
"attachments",
|
|
43
|
+
"shell_snapshots",
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
function isSqlitePath(filePath) {
|
|
47
|
+
const base = path.basename(filePath);
|
|
48
|
+
return base.includes(".sqlite") || base.endsWith(".sqlite-wal") || base.endsWith(".sqlite-shm");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function assertSafeDevHome(devHome, sourceHome) {
|
|
52
|
+
const resolvedDevHome = path.resolve(devHome);
|
|
53
|
+
const resolvedSourceHome = path.resolve(sourceHome);
|
|
54
|
+
if (resolvedDevHome === resolvedSourceHome) throw new Error("--dev-home must not be the same as --source-home");
|
|
55
|
+
if (resolvedDevHome === os.homedir() || resolvedDevHome === path.join(os.homedir(), ".codex")) {
|
|
56
|
+
throw new Error("--dev-home must not point at the user's real home or ~/.codex");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function copyEntry({ sourceHome, devHome, relativePath, fsImpl = fs }) {
|
|
61
|
+
const source = path.join(sourceHome, relativePath);
|
|
62
|
+
const target = path.join(devHome, relativePath);
|
|
63
|
+
if (!fsImpl.existsSync(source)) return null;
|
|
64
|
+
if (isSqlitePath(source) || relativePath.split(path.sep).includes("sqlite")) return null;
|
|
65
|
+
fsImpl.mkdirSync(path.dirname(target), { recursive: true });
|
|
66
|
+
fsImpl.rmSync(target, { recursive: true, force: true });
|
|
67
|
+
fsImpl.cpSync(source, target, { recursive: true, force: true, dereference: false });
|
|
68
|
+
return relativePath;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function scrubDevGlobalState(devHome, fsImpl = fs) {
|
|
72
|
+
const statePath = path.join(devHome, ".codex-global-state.json");
|
|
73
|
+
if (!fsImpl.existsSync(statePath)) return false;
|
|
74
|
+
const state = JSON.parse(fsImpl.readFileSync(statePath, "utf8"));
|
|
75
|
+
const atomState = state["electron-persisted-atom-state"];
|
|
76
|
+
if (atomState == null || typeof atomState !== "object") return false;
|
|
77
|
+
if (!Object.prototype.hasOwnProperty.call(atomState, "composer-prompt-drafts-v1")) return false;
|
|
78
|
+
delete atomState["composer-prompt-drafts-v1"];
|
|
79
|
+
fsImpl.writeFileSync(statePath, `${JSON.stringify(state)}\n`);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function cleanExcludedDevState(devHome, fsImpl = fs) {
|
|
84
|
+
for (const relativePath of EXCLUDED_DEV_STATE_ENTRIES) {
|
|
85
|
+
fsImpl.rmSync(path.join(devHome, relativePath), { recursive: true, force: true });
|
|
86
|
+
}
|
|
87
|
+
if (!fsImpl.existsSync(devHome)) return;
|
|
88
|
+
for (const entry of fsImpl.readdirSync(devHome)) {
|
|
89
|
+
if (isSqlitePath(entry) || entry.endsWith(".db")) {
|
|
90
|
+
fsImpl.rmSync(path.join(devHome, entry), { recursive: true, force: true });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function sqliteLiteral(value) {
|
|
96
|
+
return `'${String(value).replaceAll("'", "''")}'`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function snapshotSqlite({ sourceHome, devHome, relativePath, fsImpl = fs, execFileSync = childProcess.execFileSync }) {
|
|
100
|
+
const source = path.join(sourceHome, relativePath);
|
|
101
|
+
const target = path.join(devHome, relativePath);
|
|
102
|
+
if (!fsImpl.existsSync(source)) return null;
|
|
103
|
+
fsImpl.mkdirSync(path.dirname(target), { recursive: true });
|
|
104
|
+
fsImpl.rmSync(target, { force: true });
|
|
105
|
+
fsImpl.rmSync(`${target}-wal`, { force: true });
|
|
106
|
+
fsImpl.rmSync(`${target}-shm`, { force: true });
|
|
107
|
+
execFileSync("sqlite3", [source, `VACUUM INTO ${sqliteLiteral(target)}`], { stdio: "pipe" });
|
|
108
|
+
return relativePath;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function linkSharedDirectory({ sourceHome, devHome, relativePath, fsImpl = fs }) {
|
|
112
|
+
const source = path.join(sourceHome, relativePath);
|
|
113
|
+
const target = path.join(devHome, relativePath);
|
|
114
|
+
fsImpl.rmSync(target, { recursive: true, force: true });
|
|
115
|
+
if (!fsImpl.existsSync(source)) return null;
|
|
116
|
+
fsImpl.symlinkSync(source, target, "dir");
|
|
117
|
+
return { source, target };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function syncDevHome({
|
|
121
|
+
sourceHome = path.join(os.homedir(), ".codex"),
|
|
122
|
+
devHome = DEFAULT_DEV_HOME,
|
|
123
|
+
fsImpl = fs,
|
|
124
|
+
execFileSync = childProcess.execFileSync,
|
|
125
|
+
} = {}) {
|
|
126
|
+
const resolvedSourceHome = path.resolve(sourceHome);
|
|
127
|
+
const resolvedDevHome = path.resolve(devHome);
|
|
128
|
+
assertSafeDevHome(resolvedDevHome, resolvedSourceHome);
|
|
129
|
+
|
|
130
|
+
fsImpl.mkdirSync(resolvedDevHome, { recursive: true });
|
|
131
|
+
cleanExcludedDevState(resolvedDevHome, fsImpl);
|
|
132
|
+
|
|
133
|
+
const copied = [];
|
|
134
|
+
for (const relativePath of COPY_ENTRIES) {
|
|
135
|
+
const copiedPath = copyEntry({
|
|
136
|
+
sourceHome: resolvedSourceHome,
|
|
137
|
+
devHome: resolvedDevHome,
|
|
138
|
+
relativePath,
|
|
139
|
+
fsImpl,
|
|
140
|
+
});
|
|
141
|
+
if (copiedPath) copied.push(copiedPath);
|
|
142
|
+
}
|
|
143
|
+
const scrubbedGlobalState = scrubDevGlobalState(resolvedDevHome, fsImpl);
|
|
144
|
+
|
|
145
|
+
const sqliteSnapshots = [];
|
|
146
|
+
for (const relativePath of SQLITE_SNAPSHOT_ENTRIES) {
|
|
147
|
+
const snapshotPath = snapshotSqlite({
|
|
148
|
+
sourceHome: resolvedSourceHome,
|
|
149
|
+
devHome: resolvedDevHome,
|
|
150
|
+
relativePath,
|
|
151
|
+
fsImpl,
|
|
152
|
+
execFileSync,
|
|
153
|
+
});
|
|
154
|
+
if (snapshotPath) sqliteSnapshots.push(snapshotPath);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const worktrees = linkSharedDirectory({
|
|
158
|
+
sourceHome: resolvedSourceHome,
|
|
159
|
+
devHome: resolvedDevHome,
|
|
160
|
+
relativePath: "worktrees",
|
|
161
|
+
fsImpl,
|
|
162
|
+
});
|
|
163
|
+
const sessions = linkSharedDirectory({
|
|
164
|
+
sourceHome: resolvedSourceHome,
|
|
165
|
+
devHome: resolvedDevHome,
|
|
166
|
+
relativePath: "sessions",
|
|
167
|
+
fsImpl,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
sourceHome: resolvedSourceHome,
|
|
172
|
+
devHome: resolvedDevHome,
|
|
173
|
+
copied,
|
|
174
|
+
scrubbedGlobalState,
|
|
175
|
+
sqliteSnapshots,
|
|
176
|
+
worktrees,
|
|
177
|
+
sessions,
|
|
178
|
+
warning: DEV_MODE_WARNING,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function buildLaunchDev({ targetApp, devHome = DEFAULT_DEV_HOME, electronUserDataPath = DEFAULT_ELECTRON_USER_DATA, remoteDebuggingPort } = {}) {
|
|
183
|
+
if (!targetApp) throw new Error("--target is required");
|
|
184
|
+
const appBinary = path.join(path.resolve(targetApp), "Contents/MacOS/Codex");
|
|
185
|
+
const resolvedDevHome = path.resolve(devHome);
|
|
186
|
+
const resolvedElectronUserDataPath = path.resolve(electronUserDataPath);
|
|
187
|
+
const args = [`--user-data-dir=${resolvedElectronUserDataPath}`];
|
|
188
|
+
if (remoteDebuggingPort != null) args.push(`--remote-debugging-port=${remoteDebuggingPort}`);
|
|
189
|
+
return {
|
|
190
|
+
command: appBinary,
|
|
191
|
+
args,
|
|
192
|
+
env: {
|
|
193
|
+
CODEX_HOME: resolvedDevHome,
|
|
194
|
+
CODEX_ELECTRON_USER_DATA_PATH: resolvedElectronUserDataPath,
|
|
195
|
+
},
|
|
196
|
+
warning: DEV_MODE_WARNING,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function markDevRuntimeConfig(targetApp, { patchAsarImpl = patchAsar, setPlistBuddyValueImpl = setPlistBuddyValue } = {}) {
|
|
201
|
+
const target = path.resolve(targetApp);
|
|
202
|
+
const asarPath = path.join(target, ASAR_PATH_IN_BUNDLE);
|
|
203
|
+
const patchedAsarSha = patchAsarImpl(asarPath, [
|
|
204
|
+
[RUNTIME_MANIFEST_FILE, (text) => {
|
|
205
|
+
const match = text.match(/^window\.__CodexPlusRuntimeConfig=({.*?});/);
|
|
206
|
+
if (!match) throw new Error("Could not find Codex Plus runtime config in runtime manifest");
|
|
207
|
+
const config = JSON.parse(match[1]);
|
|
208
|
+
config.devModeStatsigFallback = true;
|
|
209
|
+
return text.replace(match[0], `window.__CodexPlusRuntimeConfig=${JSON.stringify(config)};`);
|
|
210
|
+
}],
|
|
211
|
+
]);
|
|
212
|
+
setPlistBuddyValueImpl(
|
|
213
|
+
path.join(target, "Contents/Info.plist"),
|
|
214
|
+
":ElectronAsarIntegrity:Resources/app.asar:hash",
|
|
215
|
+
patchedAsarSha,
|
|
216
|
+
);
|
|
217
|
+
return { asar: asarPath, patchedAsarSha };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function launchDevApp({ spawn = childProcess.spawn, env = process.env, markDevRuntimeConfigImpl = markDevRuntimeConfig, ...options } = {}) {
|
|
221
|
+
const launch = buildLaunchDev(options);
|
|
222
|
+
fs.mkdirSync(launch.env.CODEX_HOME, { recursive: true });
|
|
223
|
+
fs.mkdirSync(launch.env.CODEX_ELECTRON_USER_DATA_PATH, { recursive: true });
|
|
224
|
+
const devRuntimeConfig = markDevRuntimeConfigImpl(options.targetApp);
|
|
225
|
+
const child = spawn(launch.command, launch.args, {
|
|
226
|
+
detached: true,
|
|
227
|
+
env: { ...env, ...launch.env },
|
|
228
|
+
stdio: "ignore",
|
|
229
|
+
});
|
|
230
|
+
child.unref();
|
|
231
|
+
return { ...launch, devRuntimeConfig, pid: child.pid };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function formatSyncDevHomeResult(result) {
|
|
235
|
+
const lines = [
|
|
236
|
+
"Codex Plus dev home synced.",
|
|
237
|
+
`Source home: ${result.sourceHome}`,
|
|
238
|
+
`Dev home: ${result.devHome}`,
|
|
239
|
+
`Copied: ${result.copied.length === 0 ? "(none)" : result.copied.join(", ")}`,
|
|
240
|
+
`Scrubbed writable state: ${result.scrubbedGlobalState ? "composer prompt drafts" : "(none)"}`,
|
|
241
|
+
`SQLite snapshots: ${result.sqliteSnapshots?.length ? result.sqliteSnapshots.join(", ") : "(none)"}`,
|
|
242
|
+
result.worktrees ? `Worktrees: ${result.worktrees.target} -> ${result.worktrees.source}` : "Worktrees: (missing)",
|
|
243
|
+
result.sessions ? `Sessions: ${result.sessions.target} -> ${result.sessions.source}` : "Sessions: (missing)",
|
|
244
|
+
`Warning: ${result.warning}`,
|
|
245
|
+
];
|
|
246
|
+
return `${lines.join("\n")}\n`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function formatLaunchDevResult(result) {
|
|
250
|
+
const lines = [
|
|
251
|
+
"Codex Plus dev app launched.",
|
|
252
|
+
`Command: ${result.command}`,
|
|
253
|
+
`Args: ${result.args.length === 0 ? "(none)" : result.args.join(" ")}`,
|
|
254
|
+
`CODEX_HOME: ${result.env.CODEX_HOME}`,
|
|
255
|
+
`CODEX_ELECTRON_USER_DATA_PATH: ${result.env.CODEX_ELECTRON_USER_DATA_PATH}`,
|
|
256
|
+
];
|
|
257
|
+
if (result.pid != null) lines.push(`PID: ${result.pid}`);
|
|
258
|
+
lines.push(`Warning: ${result.warning}`);
|
|
259
|
+
return `${lines.join("\n")}\n`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
module.exports = {
|
|
263
|
+
COPY_ENTRIES,
|
|
264
|
+
DEFAULT_DEV_HOME,
|
|
265
|
+
DEFAULT_ELECTRON_USER_DATA,
|
|
266
|
+
DEV_MODE_WARNING,
|
|
267
|
+
SQLITE_SNAPSHOT_ENTRIES,
|
|
268
|
+
buildLaunchDev,
|
|
269
|
+
formatLaunchDevResult,
|
|
270
|
+
formatSyncDevHomeResult,
|
|
271
|
+
launchDevApp,
|
|
272
|
+
markDevRuntimeConfig,
|
|
273
|
+
syncDevHome,
|
|
274
|
+
};
|