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
11 changes: 7 additions & 4 deletions packages/installer/src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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[];
Expand All @@ -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);
Expand All @@ -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 [];
Expand Down
19 changes: 18 additions & 1 deletion packages/installer/test/platform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down