Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 145 additions & 5 deletions packages/installer/assets/runtime/main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/installer/assets/runtime/main.js.map

Large diffs are not rendered by default.

143 changes: 142 additions & 1 deletion packages/installer/assets/runtime/preload.js

Large diffs are not rendered by default.

139 changes: 139 additions & 0 deletions packages/installer/assets/runtime/preload/settings-injector.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Large diffs are not rendered by default.

191 changes: 185 additions & 6 deletions packages/runtime/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ const SIGNED_CODEX_BACKUP = join(userRoot, "backup", "Codex.app");
const CODEX_PLUSPLUS_VERSION = "0.1.3";
const CODEX_PLUSPLUS_REPO = "b-nnett/codex-plusplus";
const CODEX_WINDOW_SERVICES_KEY = "__codexpp_window_services__";
const DEFAULT_CDP_STABLE_PORT = 9222;
const DEFAULT_CDP_BETA_PORT = 9223;
const CDP_PORT_MIN = 1;
const CDP_PORT_MAX = 65535;
const REMOTE_DEBUGGING_SWITCH = "remote-debugging-port";

mkdirSync(LOG_DIR, { recursive: true });
mkdirSync(TWEAKS_DIR, { recursive: true });
Expand All @@ -57,26 +62,45 @@ mkdirSync(TWEAKS_DIR, { recursive: true });
// because it's a Chromium command-line switch processed before app init.
//
// Off by default. Set CODEXPP_REMOTE_DEBUG=1 (optionally CODEXPP_REMOTE_DEBUG_PORT)
// to turn it on. Must be appended before `app` becomes ready; we're at module
// top-level so that's fine.
if (process.env.CODEXPP_REMOTE_DEBUG === "1") {
const port = process.env.CODEXPP_REMOTE_DEBUG_PORT ?? "9222";
app.commandLine.appendSwitch("remote-debugging-port", port);
log("info", `remote debugging enabled on port ${port}`);
// or enable it from Codex++ Settings. Must be appended before `app` becomes
// ready; we're at module top-level so that's fine.
const startupCdp = resolveStartupCdpConfig();
if (startupCdp.enabled && !hasRemoteDebuggingSwitch()) {
app.commandLine.appendSwitch(REMOTE_DEBUGGING_SWITCH, String(startupCdp.port));
log("info", `remote debugging enabled on port ${startupCdp.port} via ${startupCdp.source}`);
}

interface PersistedState {
codexPlusPlus?: {
autoUpdate?: boolean;
safeMode?: boolean;
updateCheck?: CodexPlusPlusUpdateCheck;
cdp?: CodexCdpConfig;
};
/** Per-tweak enable flags. Missing entries default to enabled. */
tweaks?: Record<string, { enabled?: boolean }>;
/** Cached GitHub release checks. Runtime never auto-installs updates. */
tweakUpdateChecks?: Record<string, TweakUpdateCheck>;
}

interface CodexCdpConfig {
enabled?: boolean;
port?: number;
}

interface CodexCdpStatus {
enabled: boolean;
active: boolean;
configuredPort: number;
activePort: number | null;
restartRequired: boolean;
source: "argv" | "env" | "config" | "off";
jsonListUrl: string | null;
jsonVersionUrl: string | null;
launchCommand: string;
appRoot: string | null;
}

interface CodexPlusPlusUpdateCheck {
checkedAt: string;
currentVersion: string;
Expand Down Expand Up @@ -121,6 +145,15 @@ function setCodexPlusPlusAutoUpdate(enabled: boolean): void {
s.codexPlusPlus.autoUpdate = enabled;
writeState(s);
}
function setCodexCdpConfig(config: CodexCdpConfig): void {
const s = readState();
s.codexPlusPlus ??= {};
s.codexPlusPlus.cdp = {
enabled: config.enabled === true,
port: normalizeCdpPort(config.port),
};
writeState(s);
}
function isCodexPlusPlusSafeModeEnabled(): boolean {
return readState().codexPlusPlus?.safeMode === true;
}
Expand Down Expand Up @@ -149,6 +182,133 @@ function readInstallerState(): InstallerState | null {
}
}

function readEarlyCdpConfig(): CodexCdpConfig {
try {
const parsed = JSON.parse(readFileSync(CONFIG_FILE, "utf8")) as PersistedState;
return parsed.codexPlusPlus?.cdp ?? {};
} catch {
return {};
}
}

function resolveStartupCdpConfig(): { enabled: boolean; port: number; source: CodexCdpStatus["source"] } {
const argvPort = getActiveRemoteDebuggingPort();
if (argvPort !== null) {
return { enabled: true, port: argvPort, source: "argv" };
}

if (process.env.CODEXPP_REMOTE_DEBUG === "1") {
return {
enabled: true,
port: normalizeCdpPort(readNumber(process.env.CODEXPP_REMOTE_DEBUG_PORT)),
source: "env",
};
}

const cdp = readEarlyCdpConfig();
if (cdp.enabled === true) {
return {
enabled: true,
port: normalizeCdpPort(cdp.port),
source: "config",
};
}

return { enabled: false, port: normalizeCdpPort(cdp.port), source: "off" };
}

function hasRemoteDebuggingSwitch(): boolean {
try {
if (app.commandLine.hasSwitch(REMOTE_DEBUGGING_SWITCH)) return true;
} catch {}
return getActiveRemoteDebuggingPort() !== null;
}

function getActiveRemoteDebuggingPort(): number | null {
try {
const fromApp = app.commandLine.getSwitchValue(REMOTE_DEBUGGING_SWITCH);
const parsed = readNumber(fromApp);
if (isValidCdpPort(parsed)) return parsed;
} catch {}

for (let i = 0; i < process.argv.length; i++) {
const arg = process.argv[i];
if (arg.startsWith(`--${REMOTE_DEBUGGING_SWITCH}=`)) {
const parsed = readNumber(arg.slice(`--${REMOTE_DEBUGGING_SWITCH}=`.length));
if (isValidCdpPort(parsed)) return parsed;
}
if (arg === `--${REMOTE_DEBUGGING_SWITCH}`) {
const parsed = readNumber(process.argv[i + 1]);
if (isValidCdpPort(parsed)) return parsed;
}
}

return null;
}

function getCodexCdpStatus(): CodexCdpStatus {
const state = readState();
const configured = state.codexPlusPlus?.cdp ?? {};
const enabled = configured.enabled === true;
const configuredPort = normalizeCdpPort(configured.port);
const activePort = getActiveRemoteDebuggingPort();
const active = activePort !== null;
const startup = resolveStartupCdpConfig();
const urlPort = activePort ?? configuredPort;
const appRoot = readInstallerState()?.appRoot ?? null;

return {
enabled,
active,
configuredPort,
activePort,
restartRequired: enabled && activePort !== configuredPort,
source: active ? startup.source : enabled ? "config" : "off",
jsonListUrl: active ? cdpUrl(urlPort, "json/list") : null,
jsonVersionUrl: active ? cdpUrl(urlPort, "json/version") : null,
launchCommand: buildCdpLaunchCommand(appRoot, configuredPort),
appRoot,
};
}

function cdpUrl(port: number, path: string): string {
return `http://127.0.0.1:${port}/${path}`;
}

function buildCdpLaunchCommand(appRoot: string | null, port: number): string {
const appPath = appRoot ?? "/Applications/Codex.app";
return `open -na ${shellQuote(appPath)} --args --remote-debugging-port=${port}`;
}

function shellQuote(value: string): string {
return `'${value.replace(/'/g, `'\\''`)}'`;
}

function normalizeCdpPort(port: unknown): number {
const parsed = typeof port === "number" ? port : readNumber(String(port ?? ""));
return isValidCdpPort(parsed) ? parsed : defaultCdpPort();
}

function defaultCdpPort(): number {
const appRoot = readInstallerState()?.appRoot ?? "";
let appName = "";
try {
appName = app.getName();
} catch {}
return /\bbeta\b/i.test(`${appRoot} ${appName}`) ? DEFAULT_CDP_BETA_PORT : DEFAULT_CDP_STABLE_PORT;
}

function isValidCdpPort(port: number | null): port is number {
return port !== null && Number.isInteger(port) && port >= CDP_PORT_MIN && port <= CDP_PORT_MAX;
}

function readNumber(value: unknown): number | null {
if (typeof value === "number") return value;
if (typeof value !== "string" || value.trim() === "") return null;
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
}

function log(level: "info" | "warn" | "error", ...args: unknown[]): void {
const line = `[${new Date().toISOString()}] [${level}] ${args
.map((a) => (typeof a === "string" ? a : JSON.stringify(a)))
Expand Down Expand Up @@ -463,6 +623,13 @@ ipcMain.handle("codexpp:set-auto-update", (_e, enabled: boolean) => {
return { autoUpdate: isCodexPlusPlusAutoUpdateEnabled() };
});

ipcMain.handle("codexpp:get-cdp-status", () => getCodexCdpStatus());

ipcMain.handle("codexpp:set-cdp-config", (_e, config: CodexCdpConfig) => {
setCodexCdpConfig(config);
return getCodexCdpStatus();
});

ipcMain.handle("codexpp:check-codexpp-update", async (_e, force?: boolean) => {
return ensureCodexPlusPlusUpdateCheck(force === true);
});
Expand Down Expand Up @@ -568,6 +735,18 @@ ipcMain.handle("codexpp:open-external", (_e, url: string) => {
shell.openExternal(parsed.toString()).catch(() => {});
});

ipcMain.handle("codexpp:open-cdp-url", (_e, url: string) => {
const parsed = new URL(url);
const isLocalHttp =
parsed.protocol === "http:" &&
["127.0.0.1", "localhost", "::1"].includes(parsed.hostname) &&
(parsed.pathname === "/json/list" || parsed.pathname === "/json/version");
if (!isLocalHttp) {
throw new Error("only local CDP /json/list and /json/version URLs can be opened");
}
shell.openExternal(parsed.toString()).catch(() => {});
});

ipcMain.handle("codexpp:copy-text", (_e, text: string) => {
clipboard.writeText(String(text));
return true;
Expand Down
Loading