codexapp 0.1.51 → 0.1.53

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/dist-cli/index.js CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  // src/cli/index.ts
4
4
  import { createServer as createServer2 } from "http";
5
- import { chmodSync, createWriteStream, existsSync as existsSync5, mkdirSync } from "fs";
5
+ import { chmodSync, createWriteStream, existsSync as existsSync3, mkdirSync } from "fs";
6
6
  import { readFile as readFile4, stat as stat5, writeFile as writeFile4 } from "fs/promises";
7
- import { homedir as homedir4, networkInterfaces } from "os";
8
- import { isAbsolute as isAbsolute3, join as join6, resolve as resolve2 } from "path";
9
- import { spawn as spawn3 } from "child_process";
10
- import { createInterface as createInterface2 } from "readline/promises";
7
+ import { homedir as homedir3, networkInterfaces } from "os";
8
+ import { isAbsolute as isAbsolute3, join as join5, resolve as resolve2 } from "path";
9
+ import { spawn as spawn3, spawnSync } from "child_process";
10
+ import { createInterface } from "readline/promises";
11
11
  import { fileURLToPath as fileURLToPath2 } from "url";
12
12
  import { dirname as dirname3 } from "path";
13
13
  import { get as httpsGet } from "https";
@@ -16,8 +16,8 @@ import qrcode from "qrcode-terminal";
16
16
 
17
17
  // src/server/httpServer.ts
18
18
  import { fileURLToPath } from "url";
19
- import { dirname as dirname2, extname as extname3, isAbsolute as isAbsolute2, join as join5 } from "path";
20
- import { existsSync as existsSync4 } from "fs";
19
+ import { dirname as dirname2, extname as extname2, isAbsolute as isAbsolute2, join as join4 } from "path";
20
+ import { existsSync as existsSync2 } from "fs";
21
21
  import { writeFile as writeFile3, stat as stat4 } from "fs/promises";
22
22
  import express from "express";
23
23
 
@@ -25,13 +25,10 @@ import express from "express";
25
25
  import { spawn as spawn2 } from "child_process";
26
26
  import { randomBytes } from "crypto";
27
27
  import { mkdtemp as mkdtemp2, readFile as readFile2, mkdir as mkdir2, stat as stat2 } from "fs/promises";
28
- import { createReadStream } from "fs";
29
- import { request as httpRequest } from "http";
30
28
  import { request as httpsRequest } from "https";
31
- import { homedir as homedir3 } from "os";
29
+ import { homedir as homedir2 } from "os";
32
30
  import { tmpdir as tmpdir2 } from "os";
33
- import { basename as basename2, isAbsolute, join as join3, resolve } from "path";
34
- import { createInterface } from "readline";
31
+ import { basename, isAbsolute, join as join2, resolve } from "path";
35
32
  import { writeFile as writeFile2 } from "fs/promises";
36
33
 
37
34
  // src/server/skillsRoutes.ts
@@ -1120,84 +1117,6 @@ async function handleSkillsRoutes(req, res, url, context) {
1120
1117
  return false;
1121
1118
  }
1122
1119
 
1123
- // src/utils/commandInvocation.ts
1124
- import { spawnSync } from "child_process";
1125
- import { existsSync as existsSync2 } from "fs";
1126
- import { homedir as homedir2 } from "os";
1127
- import { basename, extname, join as join2 } from "path";
1128
- var WINDOWS_CMD_NAMES = /* @__PURE__ */ new Set(["codex", "npm", "npx"]);
1129
- function quoteCmdExeArg(value) {
1130
- const normalized = value.replace(/"/g, '""');
1131
- if (!/[\s"]/u.test(normalized)) {
1132
- return normalized;
1133
- }
1134
- return `"${normalized}"`;
1135
- }
1136
- function needsCmdExeWrapper(command) {
1137
- if (process.platform !== "win32") {
1138
- return false;
1139
- }
1140
- const lowerCommand = command.toLowerCase();
1141
- const baseName = basename(lowerCommand);
1142
- if (/\.(cmd|bat)$/i.test(baseName)) {
1143
- return true;
1144
- }
1145
- if (extname(baseName)) {
1146
- return false;
1147
- }
1148
- return WINDOWS_CMD_NAMES.has(baseName);
1149
- }
1150
- function getSpawnInvocation(command, args = []) {
1151
- if (needsCmdExeWrapper(command)) {
1152
- return {
1153
- command: "cmd.exe",
1154
- args: ["/d", "/s", "/c", [quoteCmdExeArg(command), ...args.map((arg) => quoteCmdExeArg(arg))].join(" ")]
1155
- };
1156
- }
1157
- return { command, args };
1158
- }
1159
- function spawnSyncCommand(command, args = [], options = {}) {
1160
- const invocation = getSpawnInvocation(command, args);
1161
- return spawnSync(invocation.command, invocation.args, options);
1162
- }
1163
- function canRunCommand(command, args = []) {
1164
- const result = spawnSyncCommand(command, args, { stdio: "ignore" });
1165
- return result.status === 0;
1166
- }
1167
- function getUserNpmPrefix() {
1168
- return join2(homedir2(), ".npm-global");
1169
- }
1170
- function resolveCodexCommand() {
1171
- if (canRunCommand("codex", ["--version"])) {
1172
- return "codex";
1173
- }
1174
- if (process.platform === "win32") {
1175
- const windowsCandidates = [
1176
- process.env.APPDATA ? join2(process.env.APPDATA, "npm", "codex.cmd") : "",
1177
- join2(homedir2(), ".local", "bin", "codex.cmd"),
1178
- join2(getUserNpmPrefix(), "bin", "codex.cmd")
1179
- ].filter(Boolean);
1180
- for (const candidate2 of windowsCandidates) {
1181
- if (existsSync2(candidate2) && canRunCommand(candidate2, ["--version"])) {
1182
- return candidate2;
1183
- }
1184
- }
1185
- }
1186
- const userCandidate = join2(getUserNpmPrefix(), "bin", "codex");
1187
- if (existsSync2(userCandidate) && canRunCommand(userCandidate, ["--version"])) {
1188
- return userCandidate;
1189
- }
1190
- const prefix = process.env.PREFIX?.trim();
1191
- if (!prefix) {
1192
- return null;
1193
- }
1194
- const candidate = join2(prefix, "bin", "codex");
1195
- if (existsSync2(candidate) && canRunCommand(candidate, ["--version"])) {
1196
- return candidate;
1197
- }
1198
- return null;
1199
- }
1200
-
1201
1120
  // src/server/codexAppServerBridge.ts
1202
1121
  function asRecord2(value) {
1203
1122
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -1273,54 +1192,6 @@ function scoreFileCandidate(path, query) {
1273
1192
  if (lowerPath.includes(lowerQuery)) return 4;
1274
1193
  return 10;
1275
1194
  }
1276
- function decodeHtmlEntities(value) {
1277
- return value.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&#x2F;/gi, "/");
1278
- }
1279
- function stripHtml(value) {
1280
- return decodeHtmlEntities(value.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim());
1281
- }
1282
- function parseGithubTrendingHtml(html, limit) {
1283
- const rows = html.match(/<article[\s\S]*?<\/article>/g) ?? [];
1284
- const items = [];
1285
- let seq = Date.now();
1286
- for (const row of rows) {
1287
- const repoBlockMatch = row.match(/<h2[\s\S]*?<\/h2>/);
1288
- const hrefMatch = repoBlockMatch?.[0]?.match(/href="\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)"/);
1289
- if (!hrefMatch) continue;
1290
- const fullName = hrefMatch[1] ?? "";
1291
- if (!fullName || items.some((item) => item.fullName === fullName)) continue;
1292
- const descriptionMatch = row.match(/<p[^>]*class="[^"]*col-9[^"]*"[^>]*>([\s\S]*?)<\/p>/) ?? row.match(/<p[^>]*class="[^"]*color-fg-muted[^"]*"[^>]*>([\s\S]*?)<\/p>/) ?? row.match(/<p[^>]*>([\s\S]*?)<\/p>/);
1293
- const languageMatch = row.match(/programmingLanguage[^>]*>\s*([\s\S]*?)\s*<\/span>/);
1294
- const starsMatch = row.match(/href="\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/stargazers"[\s\S]*?>([\s\S]*?)<\/a>/);
1295
- const starsText = stripHtml(starsMatch?.[1] ?? "").replace(/,/g, "");
1296
- const stars = Number.parseInt(starsText, 10);
1297
- items.push({
1298
- id: seq,
1299
- fullName,
1300
- url: `https://github.com/${fullName}`,
1301
- description: stripHtml(descriptionMatch?.[1] ?? ""),
1302
- language: stripHtml(languageMatch?.[1] ?? ""),
1303
- stars: Number.isFinite(stars) ? stars : 0
1304
- });
1305
- seq += 1;
1306
- if (items.length >= limit) break;
1307
- }
1308
- return items;
1309
- }
1310
- async function fetchGithubTrending(since, limit) {
1311
- const endpoint = `https://github.com/trending?since=${since}`;
1312
- const response = await fetch(endpoint, {
1313
- headers: {
1314
- "User-Agent": "codex-web-local",
1315
- Accept: "text/html"
1316
- }
1317
- });
1318
- if (!response.ok) {
1319
- throw new Error(`GitHub trending fetch failed (${response.status})`);
1320
- }
1321
- const html = await response.text();
1322
- return parseGithubTrendingHtml(html, limit);
1323
- }
1324
1195
  async function listFilesWithRipgrep(cwd) {
1325
1196
  return await new Promise((resolve3, reject) => {
1326
1197
  const proc = spawn2("rg", ["--files", "--hidden", "-g", "!.git", "-g", "!node_modules"], {
@@ -1350,7 +1221,7 @@ async function listFilesWithRipgrep(cwd) {
1350
1221
  }
1351
1222
  function getCodexHomeDir2() {
1352
1223
  const codexHome = process.env.CODEX_HOME?.trim();
1353
- return codexHome && codexHome.length > 0 ? codexHome : join3(homedir3(), ".codex");
1224
+ return codexHome && codexHome.length > 0 ? codexHome : join2(homedir2(), ".codex");
1354
1225
  }
1355
1226
  async function runCommand2(command, args, options = {}) {
1356
1227
  await new Promise((resolve3, reject) => {
@@ -1388,7 +1259,7 @@ function isNotGitRepositoryError(error) {
1388
1259
  return message.includes("not a git repository") || message.includes("fatal: not a git repository");
1389
1260
  }
1390
1261
  async function ensureRepoHasInitialCommit(repoRoot) {
1391
- const agentsPath = join3(repoRoot, "AGENTS.md");
1262
+ const agentsPath = join2(repoRoot, "AGENTS.md");
1392
1263
  try {
1393
1264
  await stat2(agentsPath);
1394
1265
  } catch {
@@ -1428,33 +1299,6 @@ async function runCommandCapture(command, args, options = {}) {
1428
1299
  });
1429
1300
  });
1430
1301
  }
1431
- async function runCommandWithOutput2(command, args, options = {}) {
1432
- return await new Promise((resolve3, reject) => {
1433
- const proc = spawn2(command, args, {
1434
- cwd: options.cwd,
1435
- env: process.env,
1436
- stdio: ["ignore", "pipe", "pipe"]
1437
- });
1438
- let stdout = "";
1439
- let stderr = "";
1440
- proc.stdout.on("data", (chunk) => {
1441
- stdout += chunk.toString();
1442
- });
1443
- proc.stderr.on("data", (chunk) => {
1444
- stderr += chunk.toString();
1445
- });
1446
- proc.on("error", reject);
1447
- proc.on("close", (code) => {
1448
- if (code === 0) {
1449
- resolve3(stdout.trim());
1450
- return;
1451
- }
1452
- const details = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
1453
- const suffix = details.length > 0 ? `: ${details}` : "";
1454
- reject(new Error(`Command failed (${command} ${args.join(" ")})${suffix}`));
1455
- });
1456
- });
1457
- }
1458
1302
  function normalizeStringArray(value) {
1459
1303
  if (!Array.isArray(value)) return [];
1460
1304
  const normalized = [];
@@ -1475,32 +1319,8 @@ function normalizeStringRecord(value) {
1475
1319
  }
1476
1320
  return next;
1477
1321
  }
1478
- function normalizeCommitMessage(value) {
1479
- if (typeof value !== "string") return "";
1480
- const normalized = value.replace(/\r\n?/gu, "\n").split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n").trim();
1481
- return normalized.slice(0, 2e3);
1482
- }
1483
- async function hasGitWorkingTreeChanges(cwd) {
1484
- const status = await runCommandWithOutput2("git", ["status", "--porcelain"], { cwd });
1485
- return status.trim().length > 0;
1486
- }
1487
- async function findCommitByExactMessage(cwd, message) {
1488
- const normalizedTarget = normalizeCommitMessage(message);
1489
- if (!normalizedTarget) return "";
1490
- const raw = await runCommandWithOutput2("git", ["log", "--format=%H%x1f%B%x1e"], { cwd });
1491
- const entries = raw.split("");
1492
- for (const entry of entries) {
1493
- if (!entry.trim()) continue;
1494
- const [shaRaw, bodyRaw] = entry.split("");
1495
- const sha = (shaRaw ?? "").trim();
1496
- const body = normalizeCommitMessage(bodyRaw ?? "");
1497
- if (!sha) continue;
1498
- if (body === normalizedTarget) return sha;
1499
- }
1500
- return "";
1501
- }
1502
1322
  function getCodexAuthPath() {
1503
- return join3(getCodexHomeDir2(), "auth.json");
1323
+ return join2(getCodexHomeDir2(), "auth.json");
1504
1324
  }
1505
1325
  async function readCodexAuth() {
1506
1326
  try {
@@ -1514,20 +1334,12 @@ async function readCodexAuth() {
1514
1334
  }
1515
1335
  }
1516
1336
  function getCodexGlobalStatePath() {
1517
- return join3(getCodexHomeDir2(), ".codex-global-state.json");
1518
- }
1519
- function getCodexSessionIndexPath() {
1520
- return join3(getCodexHomeDir2(), "session_index.jsonl");
1337
+ return join2(getCodexHomeDir2(), ".codex-global-state.json");
1521
1338
  }
1522
1339
  var MAX_THREAD_TITLES = 500;
1523
- var EMPTY_THREAD_TITLE_CACHE = { titles: {}, order: [] };
1524
- var sessionIndexThreadTitleCacheState = {
1525
- fileSignature: null,
1526
- cache: EMPTY_THREAD_TITLE_CACHE
1527
- };
1528
1340
  function normalizeThreadTitleCache(value) {
1529
1341
  const record = asRecord2(value);
1530
- if (!record) return EMPTY_THREAD_TITLE_CACHE;
1342
+ if (!record) return { titles: {}, order: [] };
1531
1343
  const rawTitles = asRecord2(record.titles);
1532
1344
  const titles = {};
1533
1345
  if (rawTitles) {
@@ -1551,47 +1363,6 @@ function removeFromThreadTitleCache(cache, id) {
1551
1363
  const { [id]: _, ...titles } = cache.titles;
1552
1364
  return { titles, order: cache.order.filter((o) => o !== id) };
1553
1365
  }
1554
- function normalizeSessionIndexThreadTitle(value) {
1555
- const record = asRecord2(value);
1556
- if (!record) return null;
1557
- const id = typeof record.id === "string" ? record.id.trim() : "";
1558
- const title = typeof record.thread_name === "string" ? record.thread_name.trim() : "";
1559
- const updatedAtIso = typeof record.updated_at === "string" ? record.updated_at.trim() : "";
1560
- const updatedAtMs = updatedAtIso ? Date.parse(updatedAtIso) : Number.NaN;
1561
- if (!id || !title) return null;
1562
- return {
1563
- id,
1564
- title,
1565
- updatedAtMs: Number.isFinite(updatedAtMs) ? updatedAtMs : 0
1566
- };
1567
- }
1568
- function trimThreadTitleCache(cache) {
1569
- const titles = { ...cache.titles };
1570
- const order = cache.order.filter((id) => {
1571
- if (!titles[id]) return false;
1572
- return true;
1573
- }).slice(0, MAX_THREAD_TITLES);
1574
- for (const id of Object.keys(titles)) {
1575
- if (!order.includes(id)) {
1576
- delete titles[id];
1577
- }
1578
- }
1579
- return { titles, order };
1580
- }
1581
- function mergeThreadTitleCaches(base, overlay) {
1582
- const titles = { ...base.titles, ...overlay.titles };
1583
- const order = [];
1584
- for (const id of [...overlay.order, ...base.order]) {
1585
- if (!titles[id] || order.includes(id)) continue;
1586
- order.push(id);
1587
- }
1588
- for (const id of Object.keys(titles)) {
1589
- if (!order.includes(id)) {
1590
- order.push(id);
1591
- }
1592
- }
1593
- return trimThreadTitleCache({ titles, order });
1594
- }
1595
1366
  async function readThreadTitleCache() {
1596
1367
  const statePath = getCodexGlobalStatePath();
1597
1368
  try {
@@ -1599,7 +1370,7 @@ async function readThreadTitleCache() {
1599
1370
  const payload = asRecord2(JSON.parse(raw)) ?? {};
1600
1371
  return normalizeThreadTitleCache(payload["thread-titles"]);
1601
1372
  } catch {
1602
- return EMPTY_THREAD_TITLE_CACHE;
1373
+ return { titles: {}, order: [] };
1603
1374
  }
1604
1375
  }
1605
1376
  async function writeThreadTitleCache(cache) {
@@ -1614,69 +1385,6 @@ async function writeThreadTitleCache(cache) {
1614
1385
  payload["thread-titles"] = cache;
1615
1386
  await writeFile2(statePath, JSON.stringify(payload), "utf8");
1616
1387
  }
1617
- function getSessionIndexFileSignature(stats) {
1618
- return `${String(stats.mtimeMs)}:${String(stats.size)}`;
1619
- }
1620
- async function parseThreadTitlesFromSessionIndex(sessionIndexPath) {
1621
- const latestById = /* @__PURE__ */ new Map();
1622
- const input = createReadStream(sessionIndexPath, { encoding: "utf8" });
1623
- const lines = createInterface({
1624
- input,
1625
- crlfDelay: Infinity
1626
- });
1627
- try {
1628
- for await (const line of lines) {
1629
- const trimmed = line.trim();
1630
- if (!trimmed) continue;
1631
- try {
1632
- const entry = normalizeSessionIndexThreadTitle(JSON.parse(trimmed));
1633
- if (!entry) continue;
1634
- const previous = latestById.get(entry.id);
1635
- if (!previous || entry.updatedAtMs >= previous.updatedAtMs) {
1636
- latestById.set(entry.id, entry);
1637
- }
1638
- } catch {
1639
- }
1640
- }
1641
- } finally {
1642
- lines.close();
1643
- input.close();
1644
- }
1645
- const entries = Array.from(latestById.values()).sort((first, second) => second.updatedAtMs - first.updatedAtMs);
1646
- const titles = {};
1647
- const order = [];
1648
- for (const entry of entries) {
1649
- titles[entry.id] = entry.title;
1650
- order.push(entry.id);
1651
- }
1652
- return trimThreadTitleCache({ titles, order });
1653
- }
1654
- async function readThreadTitlesFromSessionIndex() {
1655
- const sessionIndexPath = getCodexSessionIndexPath();
1656
- try {
1657
- const stats = await stat2(sessionIndexPath);
1658
- const fileSignature = getSessionIndexFileSignature(stats);
1659
- if (sessionIndexThreadTitleCacheState.fileSignature === fileSignature) {
1660
- return sessionIndexThreadTitleCacheState.cache;
1661
- }
1662
- const cache = await parseThreadTitlesFromSessionIndex(sessionIndexPath);
1663
- sessionIndexThreadTitleCacheState = { fileSignature, cache };
1664
- return cache;
1665
- } catch {
1666
- sessionIndexThreadTitleCacheState = {
1667
- fileSignature: "missing",
1668
- cache: EMPTY_THREAD_TITLE_CACHE
1669
- };
1670
- return sessionIndexThreadTitleCacheState.cache;
1671
- }
1672
- }
1673
- async function readMergedThreadTitleCache() {
1674
- const [sessionIndexCache, persistedCache] = await Promise.all([
1675
- readThreadTitlesFromSessionIndex(),
1676
- readThreadTitleCache()
1677
- ]);
1678
- return mergeThreadTitleCaches(persistedCache, sessionIndexCache);
1679
- }
1680
1388
  async function readWorkspaceRootsState() {
1681
1389
  const statePath = getCodexGlobalStatePath();
1682
1390
  let payload = {};
@@ -1776,10 +1484,10 @@ function handleFileUpload(req, res) {
1776
1484
  setJson2(res, 400, { error: "No file in request" });
1777
1485
  return;
1778
1486
  }
1779
- const uploadDir = join3(tmpdir2(), "codex-web-uploads");
1487
+ const uploadDir = join2(tmpdir2(), "codex-web-uploads");
1780
1488
  await mkdir2(uploadDir, { recursive: true });
1781
- const destDir = await mkdtemp2(join3(uploadDir, "f-"));
1782
- const destPath = join3(destDir, fileName);
1489
+ const destDir = await mkdtemp2(join2(uploadDir, "f-"));
1490
+ const destPath = join2(destDir, fileName);
1783
1491
  await writeFile2(destPath, fileData);
1784
1492
  setJson2(res, 200, { path: destPath });
1785
1493
  } catch (err) {
@@ -1790,79 +1498,32 @@ function handleFileUpload(req, res) {
1790
1498
  setJson2(res, 500, { error: getErrorMessage2(err, "Upload stream error") });
1791
1499
  });
1792
1500
  }
1793
- function httpPost(url, headers, body) {
1794
- const doRequest = url.startsWith("http://") ? httpRequest : httpsRequest;
1795
- return new Promise((resolve3, reject) => {
1796
- const req = doRequest(url, { method: "POST", headers }, (res) => {
1797
- const chunks = [];
1798
- res.on("data", (c) => chunks.push(c));
1799
- res.on("end", () => resolve3({ status: res.statusCode ?? 500, body: Buffer.concat(chunks).toString("utf8") }));
1800
- res.on("error", reject);
1801
- });
1802
- req.on("error", reject);
1803
- req.write(body);
1804
- req.end();
1805
- });
1806
- }
1807
- var curlImpersonateAvailable = null;
1808
- function curlImpersonatePost(url, headers, body) {
1809
- return new Promise((resolve3, reject) => {
1810
- const args = ["-s", "-w", "\n%{http_code}", "-X", "POST", url];
1811
- for (const [k, v] of Object.entries(headers)) {
1812
- if (k.toLowerCase() === "content-length") continue;
1813
- args.push("-H", `${k}: ${String(v)}`);
1814
- }
1815
- args.push("--data-binary", "@-");
1816
- const proc = spawn2("curl-impersonate-chrome", args, {
1817
- env: { ...process.env, CURL_IMPERSONATE: "chrome116" },
1818
- stdio: ["pipe", "pipe", "pipe"]
1819
- });
1820
- const chunks = [];
1821
- proc.stdout.on("data", (c) => chunks.push(c));
1822
- proc.on("error", (e) => {
1823
- curlImpersonateAvailable = false;
1824
- reject(e);
1825
- });
1826
- proc.on("close", (code) => {
1827
- const raw = Buffer.concat(chunks).toString("utf8");
1828
- const lastNewline = raw.lastIndexOf("\n");
1829
- const statusStr = lastNewline >= 0 ? raw.slice(lastNewline + 1).trim() : "";
1830
- const responseBody = lastNewline >= 0 ? raw.slice(0, lastNewline) : raw;
1831
- const status = parseInt(statusStr, 10) || (code === 0 ? 200 : 500);
1832
- curlImpersonateAvailable = true;
1833
- resolve3({ status, body: responseBody });
1834
- });
1835
- proc.stdin.write(body);
1836
- proc.stdin.end();
1837
- });
1838
- }
1839
1501
  async function proxyTranscribe(body, contentType, authToken, accountId) {
1840
- const chatgptHeaders = {
1502
+ const headers = {
1841
1503
  "Content-Type": contentType,
1842
1504
  "Content-Length": body.length,
1843
1505
  Authorization: `Bearer ${authToken}`,
1844
1506
  originator: "Codex Desktop",
1845
1507
  "User-Agent": `Codex Desktop/0.1.0 (${process.platform}; ${process.arch})`
1846
1508
  };
1847
- if (accountId) chatgptHeaders["ChatGPT-Account-Id"] = accountId;
1848
- const postFn = curlImpersonateAvailable !== false ? curlImpersonatePost : httpPost;
1849
- let result;
1850
- try {
1851
- result = await postFn("https://chatgpt.com/backend-api/transcribe", chatgptHeaders, body);
1852
- } catch {
1853
- result = await httpPost("https://chatgpt.com/backend-api/transcribe", chatgptHeaders, body);
1854
- }
1855
- if (result.status === 403 && result.body.includes("cf_chl")) {
1856
- if (curlImpersonateAvailable !== false && postFn !== curlImpersonatePost) {
1857
- try {
1858
- const ciResult = await curlImpersonatePost("https://chatgpt.com/backend-api/transcribe", chatgptHeaders, body);
1859
- if (ciResult.status !== 403) return ciResult;
1860
- } catch {
1861
- }
1862
- }
1863
- return { status: 503, body: JSON.stringify({ error: "Transcription blocked by Cloudflare. Install curl-impersonate-chrome." }) };
1509
+ if (accountId) {
1510
+ headers["ChatGPT-Account-Id"] = accountId;
1864
1511
  }
1865
- return result;
1512
+ return new Promise((resolve3, reject) => {
1513
+ const req = httpsRequest(
1514
+ "https://chatgpt.com/backend-api/transcribe",
1515
+ { method: "POST", headers },
1516
+ (res) => {
1517
+ const chunks = [];
1518
+ res.on("data", (c) => chunks.push(c));
1519
+ res.on("end", () => resolve3({ status: res.statusCode ?? 500, body: Buffer.concat(chunks).toString("utf8") }));
1520
+ res.on("error", reject);
1521
+ }
1522
+ );
1523
+ req.on("error", reject);
1524
+ req.write(body);
1525
+ req.end();
1526
+ });
1866
1527
  }
1867
1528
  var AppServerProcess = class {
1868
1529
  constructor() {
@@ -1886,8 +1547,7 @@ var AppServerProcess = class {
1886
1547
  start() {
1887
1548
  if (this.process) return;
1888
1549
  this.stopping = false;
1889
- const invocation = getSpawnInvocation(resolveCodexCommand() ?? "codex", this.appServerArgs);
1890
- const proc = spawn2(invocation.command, invocation.args, { stdio: ["pipe", "pipe", "pipe"] });
1550
+ const proc = spawn2("codex", this.appServerArgs, { stdio: ["pipe", "pipe", "pipe"] });
1891
1551
  this.process = proc;
1892
1552
  proc.stdout.setEncoding("utf8");
1893
1553
  proc.stdout.on("data", (chunk) => {
@@ -2113,8 +1773,7 @@ var MethodCatalog = class {
2113
1773
  }
2114
1774
  async runGenerateSchemaCommand(outDir) {
2115
1775
  await new Promise((resolve3, reject) => {
2116
- const invocation = getSpawnInvocation(resolveCodexCommand() ?? "codex", ["app-server", "generate-json-schema", "--out", outDir]);
2117
- const process2 = spawn2(invocation.command, invocation.args, {
1776
+ const process2 = spawn2("codex", ["app-server", "generate-json-schema", "--out", outDir], {
2118
1777
  stdio: ["ignore", "ignore", "pipe"]
2119
1778
  });
2120
1779
  let stderr = "";
@@ -2170,9 +1829,9 @@ var MethodCatalog = class {
2170
1829
  if (this.methodCache) {
2171
1830
  return this.methodCache;
2172
1831
  }
2173
- const outDir = await mkdtemp2(join3(tmpdir2(), "codex-web-local-schema-"));
1832
+ const outDir = await mkdtemp2(join2(tmpdir2(), "codex-web-local-schema-"));
2174
1833
  await this.runGenerateSchemaCommand(outDir);
2175
- const clientRequestPath = join3(outDir, "ClientRequest.json");
1834
+ const clientRequestPath = join2(outDir, "ClientRequest.json");
2176
1835
  const raw = await readFile2(clientRequestPath, "utf8");
2177
1836
  const parsed = JSON.parse(raw);
2178
1837
  const methods = this.extractMethodsFromClientRequest(parsed);
@@ -2183,9 +1842,9 @@ var MethodCatalog = class {
2183
1842
  if (this.notificationCache) {
2184
1843
  return this.notificationCache;
2185
1844
  }
2186
- const outDir = await mkdtemp2(join3(tmpdir2(), "codex-web-local-schema-"));
1845
+ const outDir = await mkdtemp2(join2(tmpdir2(), "codex-web-local-schema-"));
2187
1846
  await this.runGenerateSchemaCommand(outDir);
2188
- const serverNotificationPath = join3(outDir, "ServerNotification.json");
1847
+ const serverNotificationPath = join2(outDir, "ServerNotification.json");
2189
1848
  const raw = await readFile2(serverNotificationPath, "utf8");
2190
1849
  const parsed = JSON.parse(raw);
2191
1850
  const methods = this.extractMethodsFromServerNotification(parsed);
@@ -2347,20 +2006,7 @@ function createCodexBridgeMiddleware() {
2347
2006
  return;
2348
2007
  }
2349
2008
  if (req.method === "GET" && url.pathname === "/codex-api/home-directory") {
2350
- setJson2(res, 200, { data: { path: homedir3() } });
2351
- return;
2352
- }
2353
- if (req.method === "GET" && url.pathname === "/codex-api/github-trending") {
2354
- const sinceRaw = (url.searchParams.get("since") ?? "").trim().toLowerCase();
2355
- const since = sinceRaw === "weekly" ? "weekly" : sinceRaw === "monthly" ? "monthly" : "daily";
2356
- const limitRaw = Number.parseInt((url.searchParams.get("limit") ?? "6").trim(), 10);
2357
- const limit = Number.isFinite(limitRaw) ? Math.max(1, Math.min(10, limitRaw)) : 6;
2358
- try {
2359
- const data = await fetchGithubTrending(since, limit);
2360
- setJson2(res, 200, { data });
2361
- } catch (error) {
2362
- setJson2(res, 502, { error: getErrorMessage2(error, "Failed to fetch GitHub trending") });
2363
- }
2009
+ setJson2(res, 200, { data: { path: homedir2() } });
2364
2010
  return;
2365
2011
  }
2366
2012
  if (req.method === "POST" && url.pathname === "/codex-api/worktree/create") {
@@ -2390,22 +2036,22 @@ function createCodexBridgeMiddleware() {
2390
2036
  await runCommand2("git", ["init"], { cwd: sourceCwd });
2391
2037
  gitRoot = await runCommandCapture("git", ["rev-parse", "--show-toplevel"], { cwd: sourceCwd });
2392
2038
  }
2393
- const repoName = basename2(gitRoot) || "repo";
2394
- const worktreesRoot = join3(getCodexHomeDir2(), "worktrees");
2039
+ const repoName = basename(gitRoot) || "repo";
2040
+ const worktreesRoot = join2(getCodexHomeDir2(), "worktrees");
2395
2041
  await mkdir2(worktreesRoot, { recursive: true });
2396
2042
  let worktreeId = "";
2397
2043
  let worktreeParent = "";
2398
2044
  let worktreeCwd = "";
2399
2045
  for (let attempt = 0; attempt < 12; attempt += 1) {
2400
2046
  const candidate = randomBytes(2).toString("hex");
2401
- const parent = join3(worktreesRoot, candidate);
2047
+ const parent = join2(worktreesRoot, candidate);
2402
2048
  try {
2403
2049
  await stat2(parent);
2404
2050
  continue;
2405
2051
  } catch {
2406
2052
  worktreeId = candidate;
2407
2053
  worktreeParent = parent;
2408
- worktreeCwd = join3(parent, repoName);
2054
+ worktreeCwd = join2(parent, repoName);
2409
2055
  break;
2410
2056
  }
2411
2057
  }
@@ -2433,99 +2079,6 @@ function createCodexBridgeMiddleware() {
2433
2079
  }
2434
2080
  return;
2435
2081
  }
2436
- if (req.method === "POST" && url.pathname === "/codex-api/worktree/auto-commit") {
2437
- const payload = asRecord2(await readJsonBody(req));
2438
- const rawCwd = typeof payload?.cwd === "string" ? payload.cwd.trim() : "";
2439
- const commitMessage = normalizeCommitMessage(payload?.message);
2440
- if (!rawCwd) {
2441
- setJson2(res, 400, { error: "Missing cwd" });
2442
- return;
2443
- }
2444
- if (!commitMessage) {
2445
- setJson2(res, 400, { error: "Missing message" });
2446
- return;
2447
- }
2448
- const cwd = isAbsolute(rawCwd) ? rawCwd : resolve(rawCwd);
2449
- try {
2450
- const cwdInfo = await stat2(cwd);
2451
- if (!cwdInfo.isDirectory()) {
2452
- setJson2(res, 400, { error: "cwd is not a directory" });
2453
- return;
2454
- }
2455
- } catch {
2456
- setJson2(res, 404, { error: "cwd does not exist" });
2457
- return;
2458
- }
2459
- try {
2460
- await runCommandCapture("git", ["rev-parse", "--is-inside-work-tree"], { cwd });
2461
- const beforeStatus = await runCommandWithOutput2("git", ["status", "--porcelain"], { cwd });
2462
- if (!beforeStatus.trim()) {
2463
- setJson2(res, 200, { data: { committed: false } });
2464
- return;
2465
- }
2466
- await runCommand2("git", ["add", "-A"], { cwd });
2467
- const stagedStatus = await runCommandWithOutput2("git", ["diff", "--cached", "--name-only"], { cwd });
2468
- if (!stagedStatus.trim()) {
2469
- setJson2(res, 200, { data: { committed: false } });
2470
- return;
2471
- }
2472
- await runCommand2("git", ["commit", "-m", commitMessage], { cwd });
2473
- setJson2(res, 200, { data: { committed: true } });
2474
- } catch (error) {
2475
- setJson2(res, 500, { error: getErrorMessage2(error, "Failed to auto-commit worktree changes") });
2476
- }
2477
- return;
2478
- }
2479
- if (req.method === "POST" && url.pathname === "/codex-api/worktree/rollback-to-message") {
2480
- const payload = asRecord2(await readJsonBody(req));
2481
- const rawCwd = typeof payload?.cwd === "string" ? payload.cwd.trim() : "";
2482
- const commitMessage = normalizeCommitMessage(payload?.message);
2483
- if (!rawCwd) {
2484
- setJson2(res, 400, { error: "Missing cwd" });
2485
- return;
2486
- }
2487
- if (!commitMessage) {
2488
- setJson2(res, 400, { error: "Missing message" });
2489
- return;
2490
- }
2491
- const cwd = isAbsolute(rawCwd) ? rawCwd : resolve(rawCwd);
2492
- try {
2493
- const cwdInfo = await stat2(cwd);
2494
- if (!cwdInfo.isDirectory()) {
2495
- setJson2(res, 400, { error: "cwd is not a directory" });
2496
- return;
2497
- }
2498
- } catch {
2499
- setJson2(res, 404, { error: "cwd does not exist" });
2500
- return;
2501
- }
2502
- try {
2503
- await runCommandCapture("git", ["rev-parse", "--is-inside-work-tree"], { cwd });
2504
- const commitSha = await findCommitByExactMessage(cwd, commitMessage);
2505
- if (!commitSha) {
2506
- setJson2(res, 404, { error: "No matching commit found for this user message" });
2507
- return;
2508
- }
2509
- let resetTargetSha = "";
2510
- try {
2511
- resetTargetSha = await runCommandCapture("git", ["rev-parse", `${commitSha}^`], { cwd });
2512
- } catch {
2513
- setJson2(res, 409, { error: "Cannot rollback: matched commit has no parent commit" });
2514
- return;
2515
- }
2516
- let stashed = false;
2517
- if (await hasGitWorkingTreeChanges(cwd)) {
2518
- const stashMessage = `codex-auto-stash-before-rollback-${Date.now()}`;
2519
- await runCommand2("git", ["stash", "push", "-u", "-m", stashMessage], { cwd });
2520
- stashed = true;
2521
- }
2522
- await runCommand2("git", ["reset", "--hard", resetTargetSha], { cwd });
2523
- setJson2(res, 200, { data: { reset: true, commitSha, resetTargetSha, stashed } });
2524
- } catch (error) {
2525
- setJson2(res, 500, { error: getErrorMessage2(error, "Failed to rollback worktree to user message commit") });
2526
- }
2527
- return;
2528
- }
2529
2082
  if (req.method === "PUT" && url.pathname === "/codex-api/workspace-roots-state") {
2530
2083
  const payload = await readJsonBody(req);
2531
2084
  const record = asRecord2(payload);
@@ -2603,7 +2156,7 @@ function createCodexBridgeMiddleware() {
2603
2156
  let index = 1;
2604
2157
  while (index < 1e5) {
2605
2158
  const candidateName = `New Project (${String(index)})`;
2606
- const candidatePath = join3(normalizedBasePath, candidateName);
2159
+ const candidatePath = join2(normalizedBasePath, candidateName);
2607
2160
  try {
2608
2161
  await stat2(candidatePath);
2609
2162
  index += 1;
@@ -2647,7 +2200,7 @@ function createCodexBridgeMiddleware() {
2647
2200
  return;
2648
2201
  }
2649
2202
  if (req.method === "GET" && url.pathname === "/codex-api/thread-titles") {
2650
- const cache = await readMergedThreadTitleCache();
2203
+ const cache = await readThreadTitleCache();
2651
2204
  setJson2(res, 200, { data: cache });
2652
2205
  return;
2653
2206
  }
@@ -2849,7 +2402,7 @@ function createAuthSession(password) {
2849
2402
  }
2850
2403
 
2851
2404
  // src/server/localBrowseUi.ts
2852
- import { dirname, extname as extname2, join as join4 } from "path";
2405
+ import { dirname, extname, join as join3 } from "path";
2853
2406
  import { open, readFile as readFile3, readdir as readdir3, stat as stat3 } from "fs/promises";
2854
2407
  var TEXT_EDITABLE_EXTENSIONS = /* @__PURE__ */ new Set([
2855
2408
  ".txt",
@@ -2880,7 +2433,7 @@ var TEXT_EDITABLE_EXTENSIONS = /* @__PURE__ */ new Set([
2880
2433
  ".ps1"
2881
2434
  ]);
2882
2435
  function languageForPath(pathValue) {
2883
- const extension = extname2(pathValue).toLowerCase();
2436
+ const extension = extname(pathValue).toLowerCase();
2884
2437
  switch (extension) {
2885
2438
  case ".js":
2886
2439
  return "javascript";
@@ -2941,7 +2494,7 @@ function decodeBrowsePath(rawPath) {
2941
2494
  }
2942
2495
  }
2943
2496
  function isTextEditablePath(pathValue) {
2944
- return TEXT_EDITABLE_EXTENSIONS.has(extname2(pathValue).toLowerCase());
2497
+ return TEXT_EDITABLE_EXTENSIONS.has(extname(pathValue).toLowerCase());
2945
2498
  }
2946
2499
  function looksLikeTextBuffer(buffer) {
2947
2500
  if (buffer.length === 0) return true;
@@ -2987,7 +2540,7 @@ function escapeForInlineScriptString(value) {
2987
2540
  async function getDirectoryItems(localPath) {
2988
2541
  const entries = await readdir3(localPath, { withFileTypes: true });
2989
2542
  const withMeta = await Promise.all(entries.map(async (entry) => {
2990
- const entryPath = join4(localPath, entry.name);
2543
+ const entryPath = join3(localPath, entry.name);
2991
2544
  const entryStat = await stat3(entryPath);
2992
2545
  const editable = !entry.isDirectory() && await isTextEditableFile(entryPath);
2993
2546
  return {
@@ -3011,9 +2564,9 @@ async function createDirectoryListingHtml(localPath) {
3011
2564
  const rows = items.map((item) => {
3012
2565
  const suffix = item.isDirectory ? "/" : "";
3013
2566
  const editAction = item.editable ? ` <a class="icon-btn" aria-label="Edit ${escapeHtml(item.name)}" href="${escapeHtml(toEditHref(item.path))}" title="Edit">\u270F\uFE0F</a>` : "";
3014
- return `<li class="file-row"><a class="file-link" href="${escapeHtml(toBrowseHref(item.path))}">${escapeHtml(item.name)}${suffix}</a><span class="row-actions">${editAction}</span></li>`;
2567
+ return `<li class="file-row"><a class="file-link" href="${escapeHtml(toBrowseHref(item.path))}">${escapeHtml(item.name)}${suffix}</a>${editAction}</li>`;
3015
2568
  }).join("\n");
3016
- const parentLink = localPath !== parentPath ? `<a href="${escapeHtml(toBrowseHref(parentPath))}">..</a>` : "";
2569
+ const parentLink = localPath !== parentPath ? `<p><a href="${escapeHtml(toBrowseHref(parentPath))}">..</a></p>` : "";
3017
2570
  return `<!doctype html>
3018
2571
  <html lang="en">
3019
2572
  <head>
@@ -3027,27 +2580,8 @@ async function createDirectoryListingHtml(localPath) {
3027
2580
  ul { list-style: none; padding: 0; margin: 12px 0 0; display: flex; flex-direction: column; gap: 8px; }
3028
2581
  .file-row { display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: center; gap: 10px; }
3029
2582
  .file-link { display: block; padding: 10px 12px; border: 1px solid #28405f; border-radius: 10px; background: #0f1b33; overflow-wrap: anywhere; }
3030
- .header-actions { display: flex; align-items: center; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
3031
- .header-parent-link { color: #9ec8ff; font-size: 14px; padding: 8px 10px; border: 1px solid #2a4569; border-radius: 10px; background: #101f3a; }
3032
- .header-parent-link:hover { text-decoration: none; filter: brightness(1.08); }
3033
- .header-open-btn {
3034
- height: 42px;
3035
- padding: 0 14px;
3036
- border: 1px solid #4f8de0;
3037
- border-radius: 10px;
3038
- background: linear-gradient(135deg, #2e6ee6 0%, #3d8cff 100%);
3039
- color: #eef6ff;
3040
- font-weight: 700;
3041
- letter-spacing: 0.01em;
3042
- cursor: pointer;
3043
- box-shadow: 0 6px 18px rgba(33, 90, 199, 0.35);
3044
- }
3045
- .header-open-btn:hover { filter: brightness(1.08); }
3046
- .header-open-btn:disabled { opacity: 0.6; cursor: default; }
3047
- .row-actions { display: inline-flex; align-items: center; gap: 8px; min-width: 42px; justify-content: flex-end; }
3048
- .icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 42px; height: 42px; border: 1px solid #36557a; border-radius: 10px; background: #162643; color: #dbe6ff; text-decoration: none; cursor: pointer; }
2583
+ .icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 42px; height: 42px; border: 1px solid #36557a; border-radius: 10px; background: #162643; text-decoration: none; }
3049
2584
  .icon-btn:hover { filter: brightness(1.08); text-decoration: none; }
3050
- .status { margin: 10px 0 0; color: #8cc2ff; min-height: 1.25em; }
3051
2585
  h1 { font-size: 18px; margin: 0; word-break: break-all; }
3052
2586
  @media (max-width: 640px) {
3053
2587
  body { margin: 12px; }
@@ -3059,46 +2593,8 @@ async function createDirectoryListingHtml(localPath) {
3059
2593
  </head>
3060
2594
  <body>
3061
2595
  <h1>Index of ${escapeHtml(localPath)}</h1>
3062
- <div class="header-actions">
3063
- ${parentLink ? `<a class="header-parent-link" href="${escapeHtml(toBrowseHref(parentPath))}">..</a>` : ""}
3064
- <button class="header-open-btn open-folder-btn" type="button" aria-label="Open current folder in Codex" title="Open folder in Codex" data-path="${escapeHtml(localPath)}">Open folder in Codex</button>
3065
- </div>
3066
- <p id="status" class="status"></p>
2596
+ ${parentLink}
3067
2597
  <ul>${rows}</ul>
3068
- <script>
3069
- const status = document.getElementById('status');
3070
- document.addEventListener('click', async (event) => {
3071
- const target = event.target;
3072
- if (!(target instanceof Element)) return;
3073
- const button = target.closest('.open-folder-btn');
3074
- if (!(button instanceof HTMLButtonElement)) return;
3075
-
3076
- const path = button.getAttribute('data-path') || '';
3077
- if (!path) return;
3078
- button.disabled = true;
3079
- status.textContent = 'Opening folder in Codex...';
3080
- try {
3081
- const response = await fetch('/codex-api/project-root', {
3082
- method: 'POST',
3083
- headers: { 'Content-Type': 'application/json' },
3084
- body: JSON.stringify({
3085
- path,
3086
- createIfMissing: false,
3087
- label: '',
3088
- }),
3089
- });
3090
- if (!response.ok) {
3091
- status.textContent = 'Failed to open folder.';
3092
- button.disabled = false;
3093
- return;
3094
- }
3095
- window.location.assign('/#/');
3096
- } catch {
3097
- status.textContent = 'Failed to open folder.';
3098
- button.disabled = false;
3099
- }
3100
- });
3101
- </script>
3102
2598
  </body>
3103
2599
  </html>`;
3104
2600
  }
@@ -3174,8 +2670,8 @@ async function createTextEditorHtml(localPath) {
3174
2670
  // src/server/httpServer.ts
3175
2671
  import { WebSocketServer } from "ws";
3176
2672
  var __dirname = dirname2(fileURLToPath(import.meta.url));
3177
- var distDir = join5(__dirname, "..", "dist");
3178
- var spaEntryFile = join5(distDir, "index.html");
2673
+ var distDir = join4(__dirname, "..", "dist");
2674
+ var spaEntryFile = join4(distDir, "index.html");
3179
2675
  var IMAGE_CONTENT_TYPES = {
3180
2676
  ".avif": "image/avif",
3181
2677
  ".bmp": "image/bmp",
@@ -3232,7 +2728,7 @@ function createServer(options = {}) {
3232
2728
  res.status(400).json({ error: "Expected absolute local file path." });
3233
2729
  return;
3234
2730
  }
3235
- const contentType = IMAGE_CONTENT_TYPES[extname3(localPath).toLowerCase()];
2731
+ const contentType = IMAGE_CONTENT_TYPES[extname2(localPath).toLowerCase()];
3236
2732
  if (!contentType) {
3237
2733
  res.status(415).json({ error: "Unsupported image type." });
3238
2734
  return;
@@ -3319,7 +2815,7 @@ function createServer(options = {}) {
3319
2815
  res.status(404).json({ error: "File not found." });
3320
2816
  }
3321
2817
  });
3322
- const hasFrontendAssets = existsSync4(spaEntryFile);
2818
+ const hasFrontendAssets = existsSync2(spaEntryFile);
3323
2819
  if (hasFrontendAssets) {
3324
2820
  app.use(express.static(distDir));
3325
2821
  }
@@ -3390,25 +2886,9 @@ function generatePassword() {
3390
2886
  // src/cli/index.ts
3391
2887
  var program = new Command().name("codexui").description("Web interface for Codex app-server");
3392
2888
  var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
3393
- var hasPromptedCloudflaredInstall = false;
3394
- function getCodexHomePath() {
3395
- return process.env.CODEX_HOME?.trim() || join6(homedir4(), ".codex");
3396
- }
3397
- function getCloudflaredPromptMarkerPath() {
3398
- return join6(getCodexHomePath(), ".cloudflared-install-prompted");
3399
- }
3400
- function hasPromptedCloudflaredInstallPersisted() {
3401
- return existsSync5(getCloudflaredPromptMarkerPath());
3402
- }
3403
- async function persistCloudflaredInstallPrompted() {
3404
- const codexHome = getCodexHomePath();
3405
- mkdirSync(codexHome, { recursive: true });
3406
- await writeFile4(getCloudflaredPromptMarkerPath(), `${Date.now()}
3407
- `, "utf8");
3408
- }
3409
2889
  async function readCliVersion() {
3410
2890
  try {
3411
- const packageJsonPath = join6(__dirname2, "..", "package.json");
2891
+ const packageJsonPath = join5(__dirname2, "..", "package.json");
3412
2892
  const raw = await readFile4(packageJsonPath, "utf8");
3413
2893
  const parsed = JSON.parse(raw);
3414
2894
  return typeof parsed.version === "string" ? parsed.version : "unknown";
@@ -3420,25 +2900,46 @@ function isTermuxRuntime() {
3420
2900
  return Boolean(process.env.TERMUX_VERSION || process.env.PREFIX?.includes("/com.termux/"));
3421
2901
  }
3422
2902
  function canRun(command, args = []) {
3423
- const result = canRunCommand(command, args);
3424
- return result;
2903
+ const result = spawnSync(command, args, { stdio: "ignore" });
2904
+ return result.status === 0;
3425
2905
  }
3426
2906
  function runOrFail(command, args, label) {
3427
- const result = spawnSyncCommand(command, args, { stdio: "inherit" });
2907
+ const result = spawnSync(command, args, { stdio: "inherit" });
3428
2908
  if (result.status !== 0) {
3429
2909
  throw new Error(`${label} failed with exit code ${String(result.status ?? -1)}`);
3430
2910
  }
3431
2911
  }
3432
2912
  function runWithStatus(command, args) {
3433
- const result = spawnSyncCommand(command, args, { stdio: "inherit" });
2913
+ const result = spawnSync(command, args, { stdio: "inherit" });
3434
2914
  return result.status ?? -1;
3435
2915
  }
2916
+ function getUserNpmPrefix() {
2917
+ return join5(homedir3(), ".npm-global");
2918
+ }
2919
+ function resolveCodexCommand() {
2920
+ if (canRun("codex", ["--version"])) {
2921
+ return "codex";
2922
+ }
2923
+ const userCandidate = join5(getUserNpmPrefix(), "bin", "codex");
2924
+ if (existsSync3(userCandidate) && canRun(userCandidate, ["--version"])) {
2925
+ return userCandidate;
2926
+ }
2927
+ const prefix = process.env.PREFIX?.trim();
2928
+ if (!prefix) {
2929
+ return null;
2930
+ }
2931
+ const candidate = join5(prefix, "bin", "codex");
2932
+ if (existsSync3(candidate) && canRun(candidate, ["--version"])) {
2933
+ return candidate;
2934
+ }
2935
+ return null;
2936
+ }
3436
2937
  function resolveCloudflaredCommand() {
3437
2938
  if (canRun("cloudflared", ["--version"])) {
3438
2939
  return "cloudflared";
3439
2940
  }
3440
- const localCandidate = join6(homedir4(), ".local", "bin", "cloudflared");
3441
- if (existsSync5(localCandidate) && canRun(localCandidate, ["--version"])) {
2941
+ const localCandidate = join5(homedir3(), ".local", "bin", "cloudflared");
2942
+ if (existsSync3(localCandidate) && canRun(localCandidate, ["--version"])) {
3442
2943
  return localCandidate;
3443
2944
  }
3444
2945
  return null;
@@ -3491,9 +2992,9 @@ async function ensureCloudflaredInstalledLinux() {
3491
2992
  if (!mappedArch) {
3492
2993
  throw new Error(`cloudflared auto-install is not supported for Linux architecture: ${process.arch}`);
3493
2994
  }
3494
- const userBinDir = join6(homedir4(), ".local", "bin");
2995
+ const userBinDir = join5(homedir3(), ".local", "bin");
3495
2996
  mkdirSync(userBinDir, { recursive: true });
3496
- const destination = join6(userBinDir, "cloudflared");
2997
+ const destination = join5(userBinDir, "cloudflared");
3497
2998
  const downloadUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${mappedArch}`;
3498
2999
  console.log("\ncloudflared not found. Installing to ~/.local/bin...\n");
3499
3000
  await downloadFile(downloadUrl, destination);
@@ -3507,19 +3008,11 @@ async function ensureCloudflaredInstalledLinux() {
3507
3008
  return installed;
3508
3009
  }
3509
3010
  async function shouldInstallCloudflaredInteractively() {
3510
- if (hasPromptedCloudflaredInstall || hasPromptedCloudflaredInstallPersisted()) {
3511
- return false;
3512
- }
3513
- hasPromptedCloudflaredInstall = true;
3514
- await persistCloudflaredInstallPrompted();
3515
- if (process.platform === "win32") {
3516
- return false;
3517
- }
3518
3011
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
3519
3012
  console.warn("\n[cloudflared] cloudflared is missing and terminal is non-interactive, skipping install.");
3520
3013
  return false;
3521
3014
  }
3522
- const prompt = createInterface2({ input: process.stdin, output: process.stdout });
3015
+ const prompt = createInterface({ input: process.stdin, output: process.stdout });
3523
3016
  try {
3524
3017
  const answer = await prompt.question("cloudflared is not installed. Install it now to ~/.local/bin? [y/N] ");
3525
3018
  const normalized = answer.trim().toLowerCase();
@@ -3533,9 +3026,6 @@ async function resolveCloudflaredForTunnel() {
3533
3026
  if (current) {
3534
3027
  return current;
3535
3028
  }
3536
- if (process.platform === "win32") {
3537
- return null;
3538
- }
3539
3029
  const installApproved = await shouldInstallCloudflaredInteractively();
3540
3030
  if (!installApproved) {
3541
3031
  return null;
@@ -3543,8 +3033,8 @@ async function resolveCloudflaredForTunnel() {
3543
3033
  return ensureCloudflaredInstalledLinux();
3544
3034
  }
3545
3035
  function hasCodexAuth() {
3546
- const codexHome = getCodexHomePath();
3547
- return existsSync5(join6(codexHome, "auth.json"));
3036
+ const codexHome = process.env.CODEX_HOME?.trim() || join5(homedir3(), ".codex");
3037
+ return existsSync3(join5(codexHome, "auth.json"));
3548
3038
  }
3549
3039
  function ensureCodexInstalled() {
3550
3040
  let codexCommand = resolveCodexCommand();
@@ -3562,7 +3052,7 @@ function ensureCodexInstalled() {
3562
3052
  Global npm install requires elevated permissions. Retrying with --prefix ${userPrefix}...
3563
3053
  `);
3564
3054
  runOrFail("npm", ["install", "-g", "--prefix", userPrefix, pkg], `${label} (user prefix)`);
3565
- process.env.PATH = `${join6(userPrefix, "bin")}:${process.env.PATH ?? ""}`;
3055
+ process.env.PATH = `${join5(userPrefix, "bin")}:${process.env.PATH ?? ""}`;
3566
3056
  };
3567
3057
  if (isTermuxRuntime()) {
3568
3058
  console.log("\nCodex CLI not found. Installing Termux-compatible Codex CLI from npm...\n");
@@ -3625,22 +3115,19 @@ function parseCloudflaredUrl(chunk) {
3625
3115
  }
3626
3116
  function getAccessibleUrls(port) {
3627
3117
  const urls = /* @__PURE__ */ new Set([`http://localhost:${String(port)}`]);
3628
- try {
3629
- const interfaces = networkInterfaces();
3630
- for (const entries of Object.values(interfaces)) {
3631
- if (!entries) {
3118
+ const interfaces = networkInterfaces();
3119
+ for (const entries of Object.values(interfaces)) {
3120
+ if (!entries) {
3121
+ continue;
3122
+ }
3123
+ for (const entry of entries) {
3124
+ if (entry.internal) {
3632
3125
  continue;
3633
3126
  }
3634
- for (const entry of entries) {
3635
- if (entry.internal) {
3636
- continue;
3637
- }
3638
- if (entry.family === "IPv4") {
3639
- urls.add(`http://${entry.address}:${String(port)}`);
3640
- }
3127
+ if (entry.family === "IPv4") {
3128
+ urls.add(`http://${entry.address}:${String(port)}`);
3641
3129
  }
3642
3130
  }
3643
- } catch {
3644
3131
  }
3645
3132
  return Array.from(urls);
3646
3133
  }
@@ -3703,8 +3190,8 @@ function listenWithFallback(server, startPort) {
3703
3190
  });
3704
3191
  }
3705
3192
  function getCodexGlobalStatePath2() {
3706
- const codexHome = getCodexHomePath();
3707
- return join6(codexHome, ".codex-global-state.json");
3193
+ const codexHome = process.env.CODEX_HOME?.trim() || join5(homedir3(), ".codex");
3194
+ return join5(codexHome, ".codex-global-state.json");
3708
3195
  }
3709
3196
  function normalizeUniqueStrings(value) {
3710
3197
  if (!Array.isArray(value)) return [];
@@ -3828,7 +3315,7 @@ async function startServer(options) {
3828
3315
  qrcode.generate(tunnelUrl, { small: true });
3829
3316
  console.log("");
3830
3317
  }
3831
- if (options.open) openBrowser(`http://localhost:${String(port)}`);
3318
+ openBrowser(`http://localhost:${String(port)}`);
3832
3319
  function shutdown() {
3833
3320
  console.log("\nShutting down...");
3834
3321
  if (tunnelChild && !tunnelChild.killed) {
@@ -3851,7 +3338,7 @@ async function runLogin() {
3851
3338
  console.log("\nStarting `codex login`...\n");
3852
3339
  runOrFail(codexCommand, ["login"], "Codex login");
3853
3340
  }
3854
- program.argument("[projectPath]", "project directory to open on launch").option("--open-project <path>", "open project directory on launch (Codex desktop parity)").option("-p, --port <port>", "port to listen on", "5999").option("--password <pass>", "set a specific password").option("--no-password", "disable password protection").option("--tunnel", "start cloudflared tunnel", true).option("--no-tunnel", "disable cloudflared tunnel startup").option("--open", "open browser on startup", true).option("--no-open", "do not open browser on startup").action(async (projectPath, opts) => {
3341
+ program.argument("[projectPath]", "project directory to open on launch").option("--open-project <path>", "open project directory on launch (Codex desktop parity)").option("-p, --port <port>", "port to listen on", "5999").option("--password <pass>", "set a specific password").option("--no-password", "disable password protection").option("--tunnel", "start cloudflared tunnel", true).option("--no-tunnel", "disable cloudflared tunnel startup").action(async (projectPath, opts) => {
3855
3342
  const rawArgv = process.argv.slice(2);
3856
3343
  const openProjectFlagIndex = rawArgv.findIndex((arg) => arg === "--open-project" || arg.startsWith("--open-project="));
3857
3344
  let openProjectOnly = (opts.openProject ?? "").trim();