codexapp 0.1.44 → 0.1.46
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/assets/index-D2tyBrlG.css +1 -0
- package/dist/assets/index-ol-gi5Ys.js +1428 -0
- package/dist/index.html +2 -2
- package/dist-cli/index.js +330 -74
- package/dist-cli/index.js.map +1 -1
- package/package.json +4 -1
- package/dist/assets/index-BBu62h6l.js +0 -1425
- package/dist/assets/index-Dw2NQxoK.css +0 -1
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
|
|
6
|
-
import { readFile as readFile4 } from "fs/promises";
|
|
5
|
+
import { chmodSync, createWriteStream, existsSync as existsSync4, mkdirSync } from "fs";
|
|
6
|
+
import { readFile as readFile4, stat as stat5, writeFile as writeFile4 } from "fs/promises";
|
|
7
7
|
import { homedir as homedir3, networkInterfaces } from "os";
|
|
8
|
-
import { join as join5 } from "path";
|
|
8
|
+
import { isAbsolute as isAbsolute3, join as join5, resolve as resolve2 } from "path";
|
|
9
9
|
import { spawn as spawn3, spawnSync } from "child_process";
|
|
10
|
-
import { createInterface } from "readline/promises";
|
|
10
|
+
import { createInterface as createInterface2 } 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";
|
|
@@ -17,7 +17,7 @@ import qrcode from "qrcode-terminal";
|
|
|
17
17
|
// src/server/httpServer.ts
|
|
18
18
|
import { fileURLToPath } from "url";
|
|
19
19
|
import { dirname as dirname2, extname as extname2, isAbsolute as isAbsolute2, join as join4 } from "path";
|
|
20
|
-
import { existsSync as
|
|
20
|
+
import { existsSync as existsSync3 } from "fs";
|
|
21
21
|
import { writeFile as writeFile3, stat as stat4 } from "fs/promises";
|
|
22
22
|
import express from "express";
|
|
23
23
|
|
|
@@ -25,10 +25,13 @@ 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";
|
|
28
30
|
import { request as httpsRequest } from "https";
|
|
29
31
|
import { homedir as homedir2 } from "os";
|
|
30
32
|
import { tmpdir as tmpdir2 } from "os";
|
|
31
33
|
import { basename, isAbsolute, join as join2, resolve } from "path";
|
|
34
|
+
import { createInterface } from "readline";
|
|
32
35
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
33
36
|
|
|
34
37
|
// src/server/skillsRoutes.ts
|
|
@@ -68,7 +71,7 @@ function getSkillsInstallDir() {
|
|
|
68
71
|
return join(getCodexHomeDir(), "skills");
|
|
69
72
|
}
|
|
70
73
|
async function runCommand(command, args, options = {}) {
|
|
71
|
-
await new Promise((
|
|
74
|
+
await new Promise((resolve3, reject) => {
|
|
72
75
|
const proc = spawn(command, args, {
|
|
73
76
|
cwd: options.cwd,
|
|
74
77
|
env: process.env,
|
|
@@ -85,7 +88,7 @@ async function runCommand(command, args, options = {}) {
|
|
|
85
88
|
proc.on("error", reject);
|
|
86
89
|
proc.on("close", (code) => {
|
|
87
90
|
if (code === 0) {
|
|
88
|
-
|
|
91
|
+
resolve3();
|
|
89
92
|
return;
|
|
90
93
|
}
|
|
91
94
|
const details = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
|
|
@@ -95,7 +98,7 @@ async function runCommand(command, args, options = {}) {
|
|
|
95
98
|
});
|
|
96
99
|
}
|
|
97
100
|
async function runCommandWithOutput(command, args, options = {}) {
|
|
98
|
-
return await new Promise((
|
|
101
|
+
return await new Promise((resolve3, reject) => {
|
|
99
102
|
const proc = spawn(command, args, {
|
|
100
103
|
cwd: options.cwd,
|
|
101
104
|
env: process.env,
|
|
@@ -112,7 +115,7 @@ async function runCommandWithOutput(command, args, options = {}) {
|
|
|
112
115
|
proc.on("error", reject);
|
|
113
116
|
proc.on("close", (code) => {
|
|
114
117
|
if (code === 0) {
|
|
115
|
-
|
|
118
|
+
resolve3(stdout.trim());
|
|
116
119
|
return;
|
|
117
120
|
}
|
|
118
121
|
const details = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
|
|
@@ -157,9 +160,9 @@ async function getGhToken() {
|
|
|
157
160
|
proc.stdout.on("data", (d) => {
|
|
158
161
|
out += d.toString();
|
|
159
162
|
});
|
|
160
|
-
return new Promise((
|
|
161
|
-
proc.on("close", (code) =>
|
|
162
|
-
proc.on("error", () =>
|
|
163
|
+
return new Promise((resolve3) => {
|
|
164
|
+
proc.on("close", (code) => resolve3(code === 0 ? out.trim() : null));
|
|
165
|
+
proc.on("error", () => resolve3(null));
|
|
163
166
|
});
|
|
164
167
|
} catch {
|
|
165
168
|
return null;
|
|
@@ -398,7 +401,7 @@ async function ensurePrivateForkFromUpstream(token, username, repoName) {
|
|
|
398
401
|
ready = true;
|
|
399
402
|
break;
|
|
400
403
|
}
|
|
401
|
-
await new Promise((
|
|
404
|
+
await new Promise((resolve3) => setTimeout(resolve3, 1e3));
|
|
402
405
|
}
|
|
403
406
|
if (!ready) throw new Error("Private mirror repo was created but is not available yet");
|
|
404
407
|
if (!created) return;
|
|
@@ -1193,7 +1196,7 @@ function scoreFileCandidate(path, query) {
|
|
|
1193
1196
|
return 10;
|
|
1194
1197
|
}
|
|
1195
1198
|
async function listFilesWithRipgrep(cwd) {
|
|
1196
|
-
return await new Promise((
|
|
1199
|
+
return await new Promise((resolve3, reject) => {
|
|
1197
1200
|
const proc = spawn2("rg", ["--files", "--hidden", "-g", "!.git", "-g", "!node_modules"], {
|
|
1198
1201
|
cwd,
|
|
1199
1202
|
env: process.env,
|
|
@@ -1211,7 +1214,7 @@ async function listFilesWithRipgrep(cwd) {
|
|
|
1211
1214
|
proc.on("close", (code) => {
|
|
1212
1215
|
if (code === 0) {
|
|
1213
1216
|
const rows = stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1214
|
-
|
|
1217
|
+
resolve3(rows);
|
|
1215
1218
|
return;
|
|
1216
1219
|
}
|
|
1217
1220
|
const details = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
|
|
@@ -1224,7 +1227,7 @@ function getCodexHomeDir2() {
|
|
|
1224
1227
|
return codexHome && codexHome.length > 0 ? codexHome : join2(homedir2(), ".codex");
|
|
1225
1228
|
}
|
|
1226
1229
|
async function runCommand2(command, args, options = {}) {
|
|
1227
|
-
await new Promise((
|
|
1230
|
+
await new Promise((resolve3, reject) => {
|
|
1228
1231
|
const proc = spawn2(command, args, {
|
|
1229
1232
|
cwd: options.cwd,
|
|
1230
1233
|
env: process.env,
|
|
@@ -1241,7 +1244,7 @@ async function runCommand2(command, args, options = {}) {
|
|
|
1241
1244
|
proc.on("error", reject);
|
|
1242
1245
|
proc.on("close", (code) => {
|
|
1243
1246
|
if (code === 0) {
|
|
1244
|
-
|
|
1247
|
+
resolve3();
|
|
1245
1248
|
return;
|
|
1246
1249
|
}
|
|
1247
1250
|
const details = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
|
|
@@ -1273,7 +1276,7 @@ async function ensureRepoHasInitialCommit(repoRoot) {
|
|
|
1273
1276
|
);
|
|
1274
1277
|
}
|
|
1275
1278
|
async function runCommandCapture(command, args, options = {}) {
|
|
1276
|
-
return await new Promise((
|
|
1279
|
+
return await new Promise((resolve3, reject) => {
|
|
1277
1280
|
const proc = spawn2(command, args, {
|
|
1278
1281
|
cwd: options.cwd,
|
|
1279
1282
|
env: process.env,
|
|
@@ -1290,7 +1293,7 @@ async function runCommandCapture(command, args, options = {}) {
|
|
|
1290
1293
|
proc.on("error", reject);
|
|
1291
1294
|
proc.on("close", (code) => {
|
|
1292
1295
|
if (code === 0) {
|
|
1293
|
-
|
|
1296
|
+
resolve3(stdout.trim());
|
|
1294
1297
|
return;
|
|
1295
1298
|
}
|
|
1296
1299
|
const details = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
|
|
@@ -1326,9 +1329,10 @@ async function readCodexAuth() {
|
|
|
1326
1329
|
try {
|
|
1327
1330
|
const raw = await readFile2(getCodexAuthPath(), "utf8");
|
|
1328
1331
|
const auth = JSON.parse(raw);
|
|
1332
|
+
const apiKey = auth.OPENAI_API_KEY || process.env.OPENAI_API_KEY || void 0;
|
|
1329
1333
|
const token = auth.tokens?.access_token;
|
|
1330
|
-
if (!token) return null;
|
|
1331
|
-
return { accessToken: token, accountId: auth.tokens?.account_id ?? void 0 };
|
|
1334
|
+
if (!token && !apiKey) return null;
|
|
1335
|
+
return { accessToken: token ?? "", accountId: auth.tokens?.account_id ?? void 0, apiKey };
|
|
1332
1336
|
} catch {
|
|
1333
1337
|
return null;
|
|
1334
1338
|
}
|
|
@@ -1336,10 +1340,18 @@ async function readCodexAuth() {
|
|
|
1336
1340
|
function getCodexGlobalStatePath() {
|
|
1337
1341
|
return join2(getCodexHomeDir2(), ".codex-global-state.json");
|
|
1338
1342
|
}
|
|
1343
|
+
function getCodexSessionIndexPath() {
|
|
1344
|
+
return join2(getCodexHomeDir2(), "session_index.jsonl");
|
|
1345
|
+
}
|
|
1339
1346
|
var MAX_THREAD_TITLES = 500;
|
|
1347
|
+
var EMPTY_THREAD_TITLE_CACHE = { titles: {}, order: [] };
|
|
1348
|
+
var sessionIndexThreadTitleCacheState = {
|
|
1349
|
+
fileSignature: null,
|
|
1350
|
+
cache: EMPTY_THREAD_TITLE_CACHE
|
|
1351
|
+
};
|
|
1340
1352
|
function normalizeThreadTitleCache(value) {
|
|
1341
1353
|
const record = asRecord2(value);
|
|
1342
|
-
if (!record) return
|
|
1354
|
+
if (!record) return EMPTY_THREAD_TITLE_CACHE;
|
|
1343
1355
|
const rawTitles = asRecord2(record.titles);
|
|
1344
1356
|
const titles = {};
|
|
1345
1357
|
if (rawTitles) {
|
|
@@ -1363,6 +1375,47 @@ function removeFromThreadTitleCache(cache, id) {
|
|
|
1363
1375
|
const { [id]: _, ...titles } = cache.titles;
|
|
1364
1376
|
return { titles, order: cache.order.filter((o) => o !== id) };
|
|
1365
1377
|
}
|
|
1378
|
+
function normalizeSessionIndexThreadTitle(value) {
|
|
1379
|
+
const record = asRecord2(value);
|
|
1380
|
+
if (!record) return null;
|
|
1381
|
+
const id = typeof record.id === "string" ? record.id.trim() : "";
|
|
1382
|
+
const title = typeof record.thread_name === "string" ? record.thread_name.trim() : "";
|
|
1383
|
+
const updatedAtIso = typeof record.updated_at === "string" ? record.updated_at.trim() : "";
|
|
1384
|
+
const updatedAtMs = updatedAtIso ? Date.parse(updatedAtIso) : Number.NaN;
|
|
1385
|
+
if (!id || !title) return null;
|
|
1386
|
+
return {
|
|
1387
|
+
id,
|
|
1388
|
+
title,
|
|
1389
|
+
updatedAtMs: Number.isFinite(updatedAtMs) ? updatedAtMs : 0
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
function trimThreadTitleCache(cache) {
|
|
1393
|
+
const titles = { ...cache.titles };
|
|
1394
|
+
const order = cache.order.filter((id) => {
|
|
1395
|
+
if (!titles[id]) return false;
|
|
1396
|
+
return true;
|
|
1397
|
+
}).slice(0, MAX_THREAD_TITLES);
|
|
1398
|
+
for (const id of Object.keys(titles)) {
|
|
1399
|
+
if (!order.includes(id)) {
|
|
1400
|
+
delete titles[id];
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
return { titles, order };
|
|
1404
|
+
}
|
|
1405
|
+
function mergeThreadTitleCaches(base, overlay) {
|
|
1406
|
+
const titles = { ...base.titles, ...overlay.titles };
|
|
1407
|
+
const order = [];
|
|
1408
|
+
for (const id of [...overlay.order, ...base.order]) {
|
|
1409
|
+
if (!titles[id] || order.includes(id)) continue;
|
|
1410
|
+
order.push(id);
|
|
1411
|
+
}
|
|
1412
|
+
for (const id of Object.keys(titles)) {
|
|
1413
|
+
if (!order.includes(id)) {
|
|
1414
|
+
order.push(id);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
return trimThreadTitleCache({ titles, order });
|
|
1418
|
+
}
|
|
1366
1419
|
async function readThreadTitleCache() {
|
|
1367
1420
|
const statePath = getCodexGlobalStatePath();
|
|
1368
1421
|
try {
|
|
@@ -1370,7 +1423,7 @@ async function readThreadTitleCache() {
|
|
|
1370
1423
|
const payload = asRecord2(JSON.parse(raw)) ?? {};
|
|
1371
1424
|
return normalizeThreadTitleCache(payload["thread-titles"]);
|
|
1372
1425
|
} catch {
|
|
1373
|
-
return
|
|
1426
|
+
return EMPTY_THREAD_TITLE_CACHE;
|
|
1374
1427
|
}
|
|
1375
1428
|
}
|
|
1376
1429
|
async function writeThreadTitleCache(cache) {
|
|
@@ -1385,6 +1438,69 @@ async function writeThreadTitleCache(cache) {
|
|
|
1385
1438
|
payload["thread-titles"] = cache;
|
|
1386
1439
|
await writeFile2(statePath, JSON.stringify(payload), "utf8");
|
|
1387
1440
|
}
|
|
1441
|
+
function getSessionIndexFileSignature(stats) {
|
|
1442
|
+
return `${String(stats.mtimeMs)}:${String(stats.size)}`;
|
|
1443
|
+
}
|
|
1444
|
+
async function parseThreadTitlesFromSessionIndex(sessionIndexPath) {
|
|
1445
|
+
const latestById = /* @__PURE__ */ new Map();
|
|
1446
|
+
const input = createReadStream(sessionIndexPath, { encoding: "utf8" });
|
|
1447
|
+
const lines = createInterface({
|
|
1448
|
+
input,
|
|
1449
|
+
crlfDelay: Infinity
|
|
1450
|
+
});
|
|
1451
|
+
try {
|
|
1452
|
+
for await (const line of lines) {
|
|
1453
|
+
const trimmed = line.trim();
|
|
1454
|
+
if (!trimmed) continue;
|
|
1455
|
+
try {
|
|
1456
|
+
const entry = normalizeSessionIndexThreadTitle(JSON.parse(trimmed));
|
|
1457
|
+
if (!entry) continue;
|
|
1458
|
+
const previous = latestById.get(entry.id);
|
|
1459
|
+
if (!previous || entry.updatedAtMs >= previous.updatedAtMs) {
|
|
1460
|
+
latestById.set(entry.id, entry);
|
|
1461
|
+
}
|
|
1462
|
+
} catch {
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
} finally {
|
|
1466
|
+
lines.close();
|
|
1467
|
+
input.close();
|
|
1468
|
+
}
|
|
1469
|
+
const entries = Array.from(latestById.values()).sort((first, second) => second.updatedAtMs - first.updatedAtMs);
|
|
1470
|
+
const titles = {};
|
|
1471
|
+
const order = [];
|
|
1472
|
+
for (const entry of entries) {
|
|
1473
|
+
titles[entry.id] = entry.title;
|
|
1474
|
+
order.push(entry.id);
|
|
1475
|
+
}
|
|
1476
|
+
return trimThreadTitleCache({ titles, order });
|
|
1477
|
+
}
|
|
1478
|
+
async function readThreadTitlesFromSessionIndex() {
|
|
1479
|
+
const sessionIndexPath = getCodexSessionIndexPath();
|
|
1480
|
+
try {
|
|
1481
|
+
const stats = await stat2(sessionIndexPath);
|
|
1482
|
+
const fileSignature = getSessionIndexFileSignature(stats);
|
|
1483
|
+
if (sessionIndexThreadTitleCacheState.fileSignature === fileSignature) {
|
|
1484
|
+
return sessionIndexThreadTitleCacheState.cache;
|
|
1485
|
+
}
|
|
1486
|
+
const cache = await parseThreadTitlesFromSessionIndex(sessionIndexPath);
|
|
1487
|
+
sessionIndexThreadTitleCacheState = { fileSignature, cache };
|
|
1488
|
+
return cache;
|
|
1489
|
+
} catch {
|
|
1490
|
+
sessionIndexThreadTitleCacheState = {
|
|
1491
|
+
fileSignature: "missing",
|
|
1492
|
+
cache: EMPTY_THREAD_TITLE_CACHE
|
|
1493
|
+
};
|
|
1494
|
+
return sessionIndexThreadTitleCacheState.cache;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
async function readMergedThreadTitleCache() {
|
|
1498
|
+
const [sessionIndexCache, persistedCache] = await Promise.all([
|
|
1499
|
+
readThreadTitlesFromSessionIndex(),
|
|
1500
|
+
readThreadTitleCache()
|
|
1501
|
+
]);
|
|
1502
|
+
return mergeThreadTitleCaches(persistedCache, sessionIndexCache);
|
|
1503
|
+
}
|
|
1388
1504
|
async function readWorkspaceRootsState() {
|
|
1389
1505
|
const statePath = getCodexGlobalStatePath();
|
|
1390
1506
|
let payload = {};
|
|
@@ -1498,32 +1614,93 @@ function handleFileUpload(req, res) {
|
|
|
1498
1614
|
setJson2(res, 500, { error: getErrorMessage2(err, "Upload stream error") });
|
|
1499
1615
|
});
|
|
1500
1616
|
}
|
|
1501
|
-
|
|
1502
|
-
const
|
|
1617
|
+
function httpPost(url, headers, body) {
|
|
1618
|
+
const doRequest = url.startsWith("http://") ? httpRequest : httpsRequest;
|
|
1619
|
+
return new Promise((resolve3, reject) => {
|
|
1620
|
+
const req = doRequest(url, { method: "POST", headers }, (res) => {
|
|
1621
|
+
const chunks = [];
|
|
1622
|
+
res.on("data", (c) => chunks.push(c));
|
|
1623
|
+
res.on("end", () => resolve3({ status: res.statusCode ?? 500, body: Buffer.concat(chunks).toString("utf8") }));
|
|
1624
|
+
res.on("error", reject);
|
|
1625
|
+
});
|
|
1626
|
+
req.on("error", reject);
|
|
1627
|
+
req.write(body);
|
|
1628
|
+
req.end();
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
var curlImpersonateAvailable = null;
|
|
1632
|
+
function curlImpersonatePost(url, headers, body) {
|
|
1633
|
+
return new Promise((resolve3, reject) => {
|
|
1634
|
+
const args = ["-s", "-w", "\n%{http_code}", "-X", "POST", url];
|
|
1635
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
1636
|
+
if (k.toLowerCase() === "content-length") continue;
|
|
1637
|
+
args.push("-H", `${k}: ${String(v)}`);
|
|
1638
|
+
}
|
|
1639
|
+
args.push("--data-binary", "@-");
|
|
1640
|
+
const proc = spawn2("curl-impersonate-chrome", args, {
|
|
1641
|
+
env: { ...process.env, CURL_IMPERSONATE: "chrome116" },
|
|
1642
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1643
|
+
});
|
|
1644
|
+
const chunks = [];
|
|
1645
|
+
proc.stdout.on("data", (c) => chunks.push(c));
|
|
1646
|
+
proc.on("error", (e) => {
|
|
1647
|
+
curlImpersonateAvailable = false;
|
|
1648
|
+
reject(e);
|
|
1649
|
+
});
|
|
1650
|
+
proc.on("close", (code) => {
|
|
1651
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
1652
|
+
const lastNewline = raw.lastIndexOf("\n");
|
|
1653
|
+
const statusStr = lastNewline >= 0 ? raw.slice(lastNewline + 1).trim() : "";
|
|
1654
|
+
const responseBody = lastNewline >= 0 ? raw.slice(0, lastNewline) : raw;
|
|
1655
|
+
const status = parseInt(statusStr, 10) || (code === 0 ? 200 : 500);
|
|
1656
|
+
curlImpersonateAvailable = true;
|
|
1657
|
+
resolve3({ status, body: responseBody });
|
|
1658
|
+
});
|
|
1659
|
+
proc.stdin.write(body);
|
|
1660
|
+
proc.stdin.end();
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
var TRANSCRIBE_RELAY_URL = process.env.TRANSCRIBE_RELAY_URL || "http://127.0.0.1:1090/relay-transcribe";
|
|
1664
|
+
async function tryRelay(headers, body) {
|
|
1665
|
+
try {
|
|
1666
|
+
const resp = await httpPost(TRANSCRIBE_RELAY_URL, headers, body);
|
|
1667
|
+
if (resp.status !== 0) return resp;
|
|
1668
|
+
} catch {
|
|
1669
|
+
}
|
|
1670
|
+
return null;
|
|
1671
|
+
}
|
|
1672
|
+
async function proxyTranscribe(body, contentType, authToken, accountId, apiKey) {
|
|
1673
|
+
const chatgptHeaders = {
|
|
1503
1674
|
"Content-Type": contentType,
|
|
1504
1675
|
"Content-Length": body.length,
|
|
1505
|
-
Authorization: `Bearer ${authToken}`,
|
|
1676
|
+
Authorization: `Bearer ${authToken || apiKey || ""}`,
|
|
1506
1677
|
originator: "Codex Desktop",
|
|
1507
1678
|
"User-Agent": `Codex Desktop/0.1.0 (${process.platform}; ${process.arch})`
|
|
1508
1679
|
};
|
|
1509
|
-
if (accountId)
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1680
|
+
if (accountId) chatgptHeaders["ChatGPT-Account-Id"] = accountId;
|
|
1681
|
+
const postFn = curlImpersonateAvailable !== false ? curlImpersonatePost : httpPost;
|
|
1682
|
+
let result;
|
|
1683
|
+
try {
|
|
1684
|
+
result = await postFn("https://chatgpt.com/backend-api/transcribe", chatgptHeaders, body);
|
|
1685
|
+
} catch {
|
|
1686
|
+
result = await httpPost("https://chatgpt.com/backend-api/transcribe", chatgptHeaders, body);
|
|
1687
|
+
}
|
|
1688
|
+
if (result.status === 403 && result.body.includes("cf_chl")) {
|
|
1689
|
+
if (curlImpersonateAvailable !== false && postFn !== curlImpersonatePost) {
|
|
1690
|
+
try {
|
|
1691
|
+
const ciResult = await curlImpersonatePost("https://chatgpt.com/backend-api/transcribe", chatgptHeaders, body);
|
|
1692
|
+
if (ciResult.status !== 403) return ciResult;
|
|
1693
|
+
} catch {
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
const relayed = await tryRelay(chatgptHeaders, body);
|
|
1697
|
+
if (relayed && relayed.status !== 403) return relayed;
|
|
1698
|
+
if (apiKey) {
|
|
1699
|
+
return httpPost("https://api.openai.com/v1/audio/transcriptions", { ...chatgptHeaders, Authorization: `Bearer ${apiKey}` }, body);
|
|
1700
|
+
}
|
|
1701
|
+
return { status: 503, body: JSON.stringify({ error: "Transcription blocked by Cloudflare. Install curl-impersonate-chrome, start relay, or set OPENAI_API_KEY." }) };
|
|
1702
|
+
}
|
|
1703
|
+
return result;
|
|
1527
1704
|
}
|
|
1528
1705
|
var AppServerProcess = class {
|
|
1529
1706
|
constructor() {
|
|
@@ -1670,8 +1847,8 @@ var AppServerProcess = class {
|
|
|
1670
1847
|
async call(method, params) {
|
|
1671
1848
|
this.start();
|
|
1672
1849
|
const id = this.nextId++;
|
|
1673
|
-
return new Promise((
|
|
1674
|
-
this.pending.set(id, { resolve:
|
|
1850
|
+
return new Promise((resolve3, reject) => {
|
|
1851
|
+
this.pending.set(id, { resolve: resolve3, reject });
|
|
1675
1852
|
this.sendLine({
|
|
1676
1853
|
jsonrpc: "2.0",
|
|
1677
1854
|
id,
|
|
@@ -1772,7 +1949,7 @@ var MethodCatalog = class {
|
|
|
1772
1949
|
this.notificationCache = null;
|
|
1773
1950
|
}
|
|
1774
1951
|
async runGenerateSchemaCommand(outDir) {
|
|
1775
|
-
await new Promise((
|
|
1952
|
+
await new Promise((resolve3, reject) => {
|
|
1776
1953
|
const process2 = spawn2("codex", ["app-server", "generate-json-schema", "--out", outDir], {
|
|
1777
1954
|
stdio: ["ignore", "ignore", "pipe"]
|
|
1778
1955
|
});
|
|
@@ -1784,7 +1961,7 @@ var MethodCatalog = class {
|
|
|
1784
1961
|
process2.on("error", reject);
|
|
1785
1962
|
process2.on("exit", (code) => {
|
|
1786
1963
|
if (code === 0) {
|
|
1787
|
-
|
|
1964
|
+
resolve3();
|
|
1788
1965
|
return;
|
|
1789
1966
|
}
|
|
1790
1967
|
reject(new Error(stderr.trim() || `generate-json-schema exited with code ${String(code)}`));
|
|
@@ -1974,7 +2151,7 @@ function createCodexBridgeMiddleware() {
|
|
|
1974
2151
|
}
|
|
1975
2152
|
const rawBody = await readRawBody(req);
|
|
1976
2153
|
const incomingCt = req.headers["content-type"] ?? "application/octet-stream";
|
|
1977
|
-
const upstream = await proxyTranscribe(rawBody, incomingCt, auth.accessToken, auth.accountId);
|
|
2154
|
+
const upstream = await proxyTranscribe(rawBody, incomingCt, auth.accessToken, auth.accountId, auth.apiKey);
|
|
1978
2155
|
res.statusCode = upstream.status;
|
|
1979
2156
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
1980
2157
|
res.end(upstream.body);
|
|
@@ -2200,7 +2377,7 @@ function createCodexBridgeMiddleware() {
|
|
|
2200
2377
|
return;
|
|
2201
2378
|
}
|
|
2202
2379
|
if (req.method === "GET" && url.pathname === "/codex-api/thread-titles") {
|
|
2203
|
-
const cache = await
|
|
2380
|
+
const cache = await readMergedThreadTitleCache();
|
|
2204
2381
|
setJson2(res, 200, { data: cache });
|
|
2205
2382
|
return;
|
|
2206
2383
|
}
|
|
@@ -2815,7 +2992,7 @@ function createServer(options = {}) {
|
|
|
2815
2992
|
res.status(404).json({ error: "File not found." });
|
|
2816
2993
|
}
|
|
2817
2994
|
});
|
|
2818
|
-
const hasFrontendAssets =
|
|
2995
|
+
const hasFrontendAssets = existsSync3(spaEntryFile);
|
|
2819
2996
|
if (hasFrontendAssets) {
|
|
2820
2997
|
app.use(express.static(distDir));
|
|
2821
2998
|
}
|
|
@@ -2921,7 +3098,7 @@ function resolveCodexCommand() {
|
|
|
2921
3098
|
return "codex";
|
|
2922
3099
|
}
|
|
2923
3100
|
const userCandidate = join5(getUserNpmPrefix(), "bin", "codex");
|
|
2924
|
-
if (
|
|
3101
|
+
if (existsSync4(userCandidate) && canRun(userCandidate, ["--version"])) {
|
|
2925
3102
|
return userCandidate;
|
|
2926
3103
|
}
|
|
2927
3104
|
const prefix = process.env.PREFIX?.trim();
|
|
@@ -2929,7 +3106,7 @@ function resolveCodexCommand() {
|
|
|
2929
3106
|
return null;
|
|
2930
3107
|
}
|
|
2931
3108
|
const candidate = join5(prefix, "bin", "codex");
|
|
2932
|
-
if (
|
|
3109
|
+
if (existsSync4(candidate) && canRun(candidate, ["--version"])) {
|
|
2933
3110
|
return candidate;
|
|
2934
3111
|
}
|
|
2935
3112
|
return null;
|
|
@@ -2939,7 +3116,7 @@ function resolveCloudflaredCommand() {
|
|
|
2939
3116
|
return "cloudflared";
|
|
2940
3117
|
}
|
|
2941
3118
|
const localCandidate = join5(homedir3(), ".local", "bin", "cloudflared");
|
|
2942
|
-
if (
|
|
3119
|
+
if (existsSync4(localCandidate) && canRun(localCandidate, ["--version"])) {
|
|
2943
3120
|
return localCandidate;
|
|
2944
3121
|
}
|
|
2945
3122
|
return null;
|
|
@@ -2954,7 +3131,7 @@ function mapCloudflaredLinuxArch(arch) {
|
|
|
2954
3131
|
return null;
|
|
2955
3132
|
}
|
|
2956
3133
|
function downloadFile(url, destination) {
|
|
2957
|
-
return new Promise((
|
|
3134
|
+
return new Promise((resolve3, reject) => {
|
|
2958
3135
|
const request = (currentUrl) => {
|
|
2959
3136
|
httpsGet(currentUrl, (response) => {
|
|
2960
3137
|
const code = response.statusCode ?? 0;
|
|
@@ -2972,7 +3149,7 @@ function downloadFile(url, destination) {
|
|
|
2972
3149
|
response.pipe(file);
|
|
2973
3150
|
file.on("finish", () => {
|
|
2974
3151
|
file.close();
|
|
2975
|
-
|
|
3152
|
+
resolve3();
|
|
2976
3153
|
});
|
|
2977
3154
|
file.on("error", reject);
|
|
2978
3155
|
}).on("error", reject);
|
|
@@ -3012,7 +3189,7 @@ async function shouldInstallCloudflaredInteractively() {
|
|
|
3012
3189
|
console.warn("\n[cloudflared] cloudflared is missing and terminal is non-interactive, skipping install.");
|
|
3013
3190
|
return false;
|
|
3014
3191
|
}
|
|
3015
|
-
const prompt =
|
|
3192
|
+
const prompt = createInterface2({ input: process.stdin, output: process.stdout });
|
|
3016
3193
|
try {
|
|
3017
3194
|
const answer = await prompt.question("cloudflared is not installed. Install it now to ~/.local/bin? [y/N] ");
|
|
3018
3195
|
const normalized = answer.trim().toLowerCase();
|
|
@@ -3034,7 +3211,7 @@ async function resolveCloudflaredForTunnel() {
|
|
|
3034
3211
|
}
|
|
3035
3212
|
function hasCodexAuth() {
|
|
3036
3213
|
const codexHome = process.env.CODEX_HOME?.trim() || join5(homedir3(), ".codex");
|
|
3037
|
-
return
|
|
3214
|
+
return existsSync4(join5(codexHome, "auth.json"));
|
|
3038
3215
|
}
|
|
3039
3216
|
function ensureCodexInstalled() {
|
|
3040
3217
|
let codexCommand = resolveCodexCommand();
|
|
@@ -3115,24 +3292,27 @@ function parseCloudflaredUrl(chunk) {
|
|
|
3115
3292
|
}
|
|
3116
3293
|
function getAccessibleUrls(port) {
|
|
3117
3294
|
const urls = /* @__PURE__ */ new Set([`http://localhost:${String(port)}`]);
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
}
|
|
3123
|
-
for (const entry of entries) {
|
|
3124
|
-
if (entry.internal) {
|
|
3295
|
+
try {
|
|
3296
|
+
const interfaces = networkInterfaces();
|
|
3297
|
+
for (const entries of Object.values(interfaces)) {
|
|
3298
|
+
if (!entries) {
|
|
3125
3299
|
continue;
|
|
3126
3300
|
}
|
|
3127
|
-
|
|
3128
|
-
|
|
3301
|
+
for (const entry of entries) {
|
|
3302
|
+
if (entry.internal) {
|
|
3303
|
+
continue;
|
|
3304
|
+
}
|
|
3305
|
+
if (entry.family === "IPv4") {
|
|
3306
|
+
urls.add(`http://${entry.address}:${String(port)}`);
|
|
3307
|
+
}
|
|
3129
3308
|
}
|
|
3130
3309
|
}
|
|
3310
|
+
} catch {
|
|
3131
3311
|
}
|
|
3132
3312
|
return Array.from(urls);
|
|
3133
3313
|
}
|
|
3134
3314
|
async function startCloudflaredTunnel(command, localPort) {
|
|
3135
|
-
return new Promise((
|
|
3315
|
+
return new Promise((resolve3, reject) => {
|
|
3136
3316
|
const child = spawn3(command, ["tunnel", "--url", `http://localhost:${String(localPort)}`], {
|
|
3137
3317
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3138
3318
|
});
|
|
@@ -3149,7 +3329,7 @@ async function startCloudflaredTunnel(command, localPort) {
|
|
|
3149
3329
|
clearTimeout(timeout);
|
|
3150
3330
|
child.stdout?.off("data", handleData);
|
|
3151
3331
|
child.stderr?.off("data", handleData);
|
|
3152
|
-
|
|
3332
|
+
resolve3({ process: child, url: parsedUrl });
|
|
3153
3333
|
};
|
|
3154
3334
|
const onError = (error) => {
|
|
3155
3335
|
clearTimeout(timeout);
|
|
@@ -3168,7 +3348,7 @@ async function startCloudflaredTunnel(command, localPort) {
|
|
|
3168
3348
|
});
|
|
3169
3349
|
}
|
|
3170
3350
|
function listenWithFallback(server, startPort) {
|
|
3171
|
-
return new Promise((
|
|
3351
|
+
return new Promise((resolve3, reject) => {
|
|
3172
3352
|
const attempt = (port) => {
|
|
3173
3353
|
const onError = (error) => {
|
|
3174
3354
|
server.off("listening", onListening);
|
|
@@ -3180,7 +3360,7 @@ function listenWithFallback(server, startPort) {
|
|
|
3180
3360
|
};
|
|
3181
3361
|
const onListening = () => {
|
|
3182
3362
|
server.off("error", onError);
|
|
3183
|
-
|
|
3363
|
+
resolve3(port);
|
|
3184
3364
|
};
|
|
3185
3365
|
server.once("error", onError);
|
|
3186
3366
|
server.once("listening", onListening);
|
|
@@ -3189,8 +3369,72 @@ function listenWithFallback(server, startPort) {
|
|
|
3189
3369
|
attempt(startPort);
|
|
3190
3370
|
});
|
|
3191
3371
|
}
|
|
3372
|
+
function getCodexGlobalStatePath2() {
|
|
3373
|
+
const codexHome = process.env.CODEX_HOME?.trim() || join5(homedir3(), ".codex");
|
|
3374
|
+
return join5(codexHome, ".codex-global-state.json");
|
|
3375
|
+
}
|
|
3376
|
+
function normalizeUniqueStrings(value) {
|
|
3377
|
+
if (!Array.isArray(value)) return [];
|
|
3378
|
+
const next = [];
|
|
3379
|
+
for (const item of value) {
|
|
3380
|
+
if (typeof item !== "string") continue;
|
|
3381
|
+
const trimmed = item.trim();
|
|
3382
|
+
if (!trimmed || next.includes(trimmed)) continue;
|
|
3383
|
+
next.push(trimmed);
|
|
3384
|
+
}
|
|
3385
|
+
return next;
|
|
3386
|
+
}
|
|
3387
|
+
async function persistLaunchProject(projectPath) {
|
|
3388
|
+
const trimmed = projectPath.trim();
|
|
3389
|
+
if (!trimmed) return;
|
|
3390
|
+
const normalizedPath = isAbsolute3(trimmed) ? trimmed : resolve2(trimmed);
|
|
3391
|
+
const directoryInfo = await stat5(normalizedPath);
|
|
3392
|
+
if (!directoryInfo.isDirectory()) {
|
|
3393
|
+
throw new Error(`Not a directory: ${normalizedPath}`);
|
|
3394
|
+
}
|
|
3395
|
+
const statePath = getCodexGlobalStatePath2();
|
|
3396
|
+
let payload = {};
|
|
3397
|
+
try {
|
|
3398
|
+
const raw = await readFile4(statePath, "utf8");
|
|
3399
|
+
const parsed = JSON.parse(raw);
|
|
3400
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
3401
|
+
payload = parsed;
|
|
3402
|
+
}
|
|
3403
|
+
} catch {
|
|
3404
|
+
payload = {};
|
|
3405
|
+
}
|
|
3406
|
+
const roots = normalizeUniqueStrings(payload["electron-saved-workspace-roots"]);
|
|
3407
|
+
const activeRoots = normalizeUniqueStrings(payload["active-workspace-roots"]);
|
|
3408
|
+
payload["electron-saved-workspace-roots"] = [
|
|
3409
|
+
normalizedPath,
|
|
3410
|
+
...roots.filter((value) => value !== normalizedPath)
|
|
3411
|
+
];
|
|
3412
|
+
payload["active-workspace-roots"] = [
|
|
3413
|
+
normalizedPath,
|
|
3414
|
+
...activeRoots.filter((value) => value !== normalizedPath)
|
|
3415
|
+
];
|
|
3416
|
+
await writeFile4(statePath, JSON.stringify(payload), "utf8");
|
|
3417
|
+
}
|
|
3418
|
+
async function addProjectOnly(projectPath) {
|
|
3419
|
+
const trimmed = projectPath.trim();
|
|
3420
|
+
if (!trimmed) {
|
|
3421
|
+
throw new Error("Missing project path");
|
|
3422
|
+
}
|
|
3423
|
+
await persistLaunchProject(trimmed);
|
|
3424
|
+
}
|
|
3192
3425
|
async function startServer(options) {
|
|
3193
3426
|
const version = await readCliVersion();
|
|
3427
|
+
const projectPath = options.projectPath?.trim() ?? "";
|
|
3428
|
+
if (projectPath.length > 0) {
|
|
3429
|
+
try {
|
|
3430
|
+
await persistLaunchProject(projectPath);
|
|
3431
|
+
} catch (error) {
|
|
3432
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3433
|
+
console.warn(`
|
|
3434
|
+
[project] Could not open launch project: ${message}
|
|
3435
|
+
`);
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3194
3438
|
const codexCommand = ensureCodexInstalled() ?? resolveCodexCommand();
|
|
3195
3439
|
if (!hasCodexAuth() && codexCommand) {
|
|
3196
3440
|
console.log("\nCodex is not logged in. Starting `codex login`...\n");
|
|
@@ -3251,7 +3495,7 @@ async function startServer(options) {
|
|
|
3251
3495
|
qrcode.generate(tunnelUrl, { small: true });
|
|
3252
3496
|
console.log("");
|
|
3253
3497
|
}
|
|
3254
|
-
openBrowser(`http://localhost:${String(port)}`);
|
|
3498
|
+
if (options.open) openBrowser(`http://localhost:${String(port)}`);
|
|
3255
3499
|
function shutdown() {
|
|
3256
3500
|
console.log("\nShutting down...");
|
|
3257
3501
|
if (tunnelChild && !tunnelChild.killed) {
|
|
@@ -3274,8 +3518,20 @@ async function runLogin() {
|
|
|
3274
3518
|
console.log("\nStarting `codex login`...\n");
|
|
3275
3519
|
runOrFail(codexCommand, ["login"], "Codex login");
|
|
3276
3520
|
}
|
|
3277
|
-
program.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 (opts) => {
|
|
3278
|
-
|
|
3521
|
+
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) => {
|
|
3522
|
+
const rawArgv = process.argv.slice(2);
|
|
3523
|
+
const openProjectFlagIndex = rawArgv.findIndex((arg) => arg === "--open-project" || arg.startsWith("--open-project="));
|
|
3524
|
+
let openProjectOnly = (opts.openProject ?? "").trim();
|
|
3525
|
+
if (!openProjectOnly && openProjectFlagIndex >= 0 && projectPath?.trim()) {
|
|
3526
|
+
openProjectOnly = projectPath.trim();
|
|
3527
|
+
}
|
|
3528
|
+
if (openProjectOnly.length > 0) {
|
|
3529
|
+
await addProjectOnly(openProjectOnly);
|
|
3530
|
+
console.log(`Added project: ${openProjectOnly}`);
|
|
3531
|
+
return;
|
|
3532
|
+
}
|
|
3533
|
+
const launchProject = (projectPath ?? "").trim();
|
|
3534
|
+
await startServer({ ...opts, projectPath: launchProject });
|
|
3279
3535
|
});
|
|
3280
3536
|
program.command("login").description("Install/check Codex CLI and run `codex login`").action(runLogin);
|
|
3281
3537
|
program.command("help").description("Show codexui command help").action(() => {
|