From 275b15b412008fb7350df6df920ffa819865d6ac Mon Sep 17 00:00:00 2001 From: probmetrics Date: Wed, 15 Jul 2026 10:02:18 +0800 Subject: [PATCH] Detect ChatGPT.app on macOS --- packages/installer/src/platform.ts | 11 +++++++---- packages/installer/test/platform.test.ts | 19 ++++++++++++++++++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/installer/src/platform.ts b/packages/installer/src/platform.ts index a3eb46a..2ce0287 100644 --- a/packages/installer/src/platform.ts +++ b/packages/installer/src/platform.ts @@ -31,6 +31,7 @@ export interface CodexInstall { const MAC_DEFAULT = "/Applications/Codex.app"; const MAC_BETA_DEFAULT = "/Applications/Codex (Beta).app"; +const MAC_CHATGPT_DEFAULT = "/Applications/ChatGPT.app"; export function detectPlatform(): Platform { const p = platform(); @@ -50,8 +51,10 @@ function locateMac(override?: string): CodexInstall { override, MAC_DEFAULT, MAC_BETA_DEFAULT, + MAC_CHATGPT_DEFAULT, join(homedir(), "Applications", "Codex.app"), join(homedir(), "Applications", "Codex (Beta).app"), + join(homedir(), "Applications", "ChatGPT.app"), ...findMacCodexApps("/Applications"), ...findMacCodexApps(join(homedir(), "Applications")), ].filter(Boolean) as string[]; @@ -60,10 +63,10 @@ function locateMac(override?: string): CodexInstall { if (!appRoot) { throw new Error( `[!] Codex App Not Found\n\n` + - `Ensure Codex.app or Codex (Beta).app is installed in /Applications or ~/Applications.\n` + + `Ensure ChatGPT.app, Codex.app, or Codex (Beta).app is installed in /Applications or ~/Applications.\n` + `Tried:\n ${unique(candidates).join("\n ")}\n\n` + `If Codex is somewhere else, rerun with:\n` + - ` codex-plusplus install --app /path/to/Codex.app`, + ` codex-plusplus install --app /path/to/ChatGPT.app`, ); } const info = readMacAppInfo(appRoot); @@ -90,11 +93,11 @@ function locateMac(override?: string): CodexInstall { }; } -function findMacCodexApps(dir: string): string[] { +export function findMacCodexApps(dir: string): string[] { if (!existsSync(dir)) return []; try { return readdirSync(dir) - .filter((name) => /\.app$/i.test(name) && /\bcodex\b/i.test(name)) + .filter((name) => /\.app$/i.test(name) && /\b(?:codex|chatgpt)\b/i.test(name)) .map((name) => join(dir, name)); } catch { return []; diff --git a/packages/installer/test/platform.test.ts b/packages/installer/test/platform.test.ts index 0bb0430..0f6dce2 100644 --- a/packages/installer/test/platform.test.ts +++ b/packages/installer/test/platform.test.ts @@ -3,14 +3,31 @@ import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSyn import { tmpdir } from "node:os"; import { join } from "node:path"; import test from "node:test"; -import { inferCodexChannel, locateCodex, resolveLinuxInstall } from "../src/platform"; +import { findMacCodexApps, inferCodexChannel, locateCodex, resolveLinuxInstall } from "../src/platform"; test("inferCodexChannel detects stable and beta metadata", () => { assert.equal(inferCodexChannel("com.openai.codex", "Codex"), "stable"); + assert.equal(inferCodexChannel("com.openai.codex", "ChatGPT"), "stable"); assert.equal(inferCodexChannel("com.openai.codex.beta", "Codex (Beta)"), "beta"); assert.equal(inferCodexChannel(null, "Codex (Beta)"), "beta"); }); +test("findMacCodexApps discovers Codex and ChatGPT app names", { skip: process.platform !== "darwin" }, () => { + const root = mkdtempSync(join(tmpdir(), "codexpp-platform-")); + try { + for (const name of ["Codex.app", "ChatGPT.app", "Other.app", "ChatGPT.txt"]) { + mkdirSync(join(root, name)); + } + + assert.deepEqual( + findMacCodexApps(root).sort(), + [join(root, "ChatGPT.app"), join(root, "Codex.app")].sort(), + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + test("locateCodex reads beta bundle metadata from override path on macOS", { skip: process.platform !== "darwin" }, () => { const root = mkdtempSync(join(tmpdir(), "codexpp-platform-")); try {