codexapp 0.1.47 → 0.1.49
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-Batjlg_n.js +1428 -0
- package/dist/assets/index-uy5QW_sJ.css +1 -0
- package/dist/index.html +2 -2
- package/dist-cli/index.js +361 -71
- package/dist-cli/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/index-DGYijDgu.css +0 -1
- package/dist/assets/index-dkC99kKB.js +0 -1442
package/dist-cli/index.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { createServer as createServer2 } from "http";
|
|
5
|
-
import { chmodSync, createWriteStream, existsSync as
|
|
5
|
+
import { chmodSync, createWriteStream, existsSync as existsSync5, mkdirSync } from "fs";
|
|
6
6
|
import { readFile as readFile4, stat as stat5, writeFile as writeFile4 } from "fs/promises";
|
|
7
|
-
import { homedir as
|
|
8
|
-
import { isAbsolute as isAbsolute3, join as
|
|
9
|
-
import { spawn as spawn3
|
|
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
10
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
11
11
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
12
12
|
import { dirname as dirname3 } from "path";
|
|
@@ -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
|
|
20
|
-
import { existsSync as
|
|
19
|
+
import { dirname as dirname2, extname as extname3, isAbsolute as isAbsolute2, join as join5 } from "path";
|
|
20
|
+
import { existsSync as existsSync4 } from "fs";
|
|
21
21
|
import { writeFile as writeFile3, stat as stat4 } from "fs/promises";
|
|
22
22
|
import express from "express";
|
|
23
23
|
|
|
@@ -28,9 +28,9 @@ import { mkdtemp as mkdtemp2, readFile as readFile2, mkdir as mkdir2, stat as st
|
|
|
28
28
|
import { createReadStream } from "fs";
|
|
29
29
|
import { request as httpRequest } from "http";
|
|
30
30
|
import { request as httpsRequest } from "https";
|
|
31
|
-
import { homedir as
|
|
31
|
+
import { homedir as homedir3 } from "os";
|
|
32
32
|
import { tmpdir as tmpdir2 } from "os";
|
|
33
|
-
import { basename, isAbsolute, join as
|
|
33
|
+
import { basename as basename2, isAbsolute, join as join3, resolve } from "path";
|
|
34
34
|
import { createInterface } from "readline";
|
|
35
35
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
36
36
|
|
|
@@ -1120,6 +1120,84 @@ async function handleSkillsRoutes(req, res, url, context) {
|
|
|
1120
1120
|
return false;
|
|
1121
1121
|
}
|
|
1122
1122
|
|
|
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
|
+
|
|
1123
1201
|
// src/server/codexAppServerBridge.ts
|
|
1124
1202
|
function asRecord2(value) {
|
|
1125
1203
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -1195,6 +1273,53 @@ function scoreFileCandidate(path, query) {
|
|
|
1195
1273
|
if (lowerPath.includes(lowerQuery)) return 4;
|
|
1196
1274
|
return 10;
|
|
1197
1275
|
}
|
|
1276
|
+
function decodeHtmlEntities(value) {
|
|
1277
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(///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 hrefMatch = row.match(/href="\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)"/);
|
|
1288
|
+
if (!hrefMatch) continue;
|
|
1289
|
+
const fullName = hrefMatch[1] ?? "";
|
|
1290
|
+
if (!fullName || items.some((item) => item.fullName === fullName)) continue;
|
|
1291
|
+
const descriptionMatch = row.match(/<p[^>]*>([\s\S]*?)<\/p>/);
|
|
1292
|
+
const languageMatch = row.match(/programmingLanguage[^>]*>\s*([\s\S]*?)\s*<\/span>/);
|
|
1293
|
+
const starsMatch = row.match(/href="\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/stargazers"[\s\S]*?>([\s\S]*?)<\/a>/);
|
|
1294
|
+
const starsText = stripHtml(starsMatch?.[1] ?? "").replace(/,/g, "");
|
|
1295
|
+
const stars = Number.parseInt(starsText, 10);
|
|
1296
|
+
items.push({
|
|
1297
|
+
id: seq,
|
|
1298
|
+
fullName,
|
|
1299
|
+
url: `https://github.com/${fullName}`,
|
|
1300
|
+
description: stripHtml(descriptionMatch?.[1] ?? ""),
|
|
1301
|
+
language: stripHtml(languageMatch?.[1] ?? ""),
|
|
1302
|
+
stars: Number.isFinite(stars) ? stars : 0
|
|
1303
|
+
});
|
|
1304
|
+
seq += 1;
|
|
1305
|
+
if (items.length >= limit) break;
|
|
1306
|
+
}
|
|
1307
|
+
return items;
|
|
1308
|
+
}
|
|
1309
|
+
async function fetchGithubTrending(since, limit) {
|
|
1310
|
+
const endpoint = `https://github.com/trending?since=${since}`;
|
|
1311
|
+
const response = await fetch(endpoint, {
|
|
1312
|
+
headers: {
|
|
1313
|
+
"User-Agent": "codex-web-local",
|
|
1314
|
+
Accept: "text/html"
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
if (!response.ok) {
|
|
1318
|
+
throw new Error(`GitHub trending fetch failed (${response.status})`);
|
|
1319
|
+
}
|
|
1320
|
+
const html = await response.text();
|
|
1321
|
+
return parseGithubTrendingHtml(html, limit);
|
|
1322
|
+
}
|
|
1198
1323
|
async function listFilesWithRipgrep(cwd) {
|
|
1199
1324
|
return await new Promise((resolve3, reject) => {
|
|
1200
1325
|
const proc = spawn2("rg", ["--files", "--hidden", "-g", "!.git", "-g", "!node_modules"], {
|
|
@@ -1224,7 +1349,7 @@ async function listFilesWithRipgrep(cwd) {
|
|
|
1224
1349
|
}
|
|
1225
1350
|
function getCodexHomeDir2() {
|
|
1226
1351
|
const codexHome = process.env.CODEX_HOME?.trim();
|
|
1227
|
-
return codexHome && codexHome.length > 0 ? codexHome :
|
|
1352
|
+
return codexHome && codexHome.length > 0 ? codexHome : join3(homedir3(), ".codex");
|
|
1228
1353
|
}
|
|
1229
1354
|
async function runCommand2(command, args, options = {}) {
|
|
1230
1355
|
await new Promise((resolve3, reject) => {
|
|
@@ -1262,7 +1387,7 @@ function isNotGitRepositoryError(error) {
|
|
|
1262
1387
|
return message.includes("not a git repository") || message.includes("fatal: not a git repository");
|
|
1263
1388
|
}
|
|
1264
1389
|
async function ensureRepoHasInitialCommit(repoRoot) {
|
|
1265
|
-
const agentsPath =
|
|
1390
|
+
const agentsPath = join3(repoRoot, "AGENTS.md");
|
|
1266
1391
|
try {
|
|
1267
1392
|
await stat2(agentsPath);
|
|
1268
1393
|
} catch {
|
|
@@ -1302,6 +1427,33 @@ async function runCommandCapture(command, args, options = {}) {
|
|
|
1302
1427
|
});
|
|
1303
1428
|
});
|
|
1304
1429
|
}
|
|
1430
|
+
async function runCommandWithOutput2(command, args, options = {}) {
|
|
1431
|
+
return await new Promise((resolve3, reject) => {
|
|
1432
|
+
const proc = spawn2(command, args, {
|
|
1433
|
+
cwd: options.cwd,
|
|
1434
|
+
env: process.env,
|
|
1435
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1436
|
+
});
|
|
1437
|
+
let stdout = "";
|
|
1438
|
+
let stderr = "";
|
|
1439
|
+
proc.stdout.on("data", (chunk) => {
|
|
1440
|
+
stdout += chunk.toString();
|
|
1441
|
+
});
|
|
1442
|
+
proc.stderr.on("data", (chunk) => {
|
|
1443
|
+
stderr += chunk.toString();
|
|
1444
|
+
});
|
|
1445
|
+
proc.on("error", reject);
|
|
1446
|
+
proc.on("close", (code) => {
|
|
1447
|
+
if (code === 0) {
|
|
1448
|
+
resolve3(stdout.trim());
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
const details = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
|
|
1452
|
+
const suffix = details.length > 0 ? `: ${details}` : "";
|
|
1453
|
+
reject(new Error(`Command failed (${command} ${args.join(" ")})${suffix}`));
|
|
1454
|
+
});
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1305
1457
|
function normalizeStringArray(value) {
|
|
1306
1458
|
if (!Array.isArray(value)) return [];
|
|
1307
1459
|
const normalized = [];
|
|
@@ -1322,8 +1474,32 @@ function normalizeStringRecord(value) {
|
|
|
1322
1474
|
}
|
|
1323
1475
|
return next;
|
|
1324
1476
|
}
|
|
1477
|
+
function normalizeCommitMessage(value) {
|
|
1478
|
+
if (typeof value !== "string") return "";
|
|
1479
|
+
const normalized = value.replace(/\r\n?/gu, "\n").split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n").trim();
|
|
1480
|
+
return normalized.slice(0, 2e3);
|
|
1481
|
+
}
|
|
1482
|
+
async function hasGitWorkingTreeChanges(cwd) {
|
|
1483
|
+
const status = await runCommandWithOutput2("git", ["status", "--porcelain"], { cwd });
|
|
1484
|
+
return status.trim().length > 0;
|
|
1485
|
+
}
|
|
1486
|
+
async function findCommitByExactMessage(cwd, message) {
|
|
1487
|
+
const normalizedTarget = normalizeCommitMessage(message);
|
|
1488
|
+
if (!normalizedTarget) return "";
|
|
1489
|
+
const raw = await runCommandWithOutput2("git", ["log", "--format=%H%x1f%B%x1e"], { cwd });
|
|
1490
|
+
const entries = raw.split("");
|
|
1491
|
+
for (const entry of entries) {
|
|
1492
|
+
if (!entry.trim()) continue;
|
|
1493
|
+
const [shaRaw, bodyRaw] = entry.split("");
|
|
1494
|
+
const sha = (shaRaw ?? "").trim();
|
|
1495
|
+
const body = normalizeCommitMessage(bodyRaw ?? "");
|
|
1496
|
+
if (!sha) continue;
|
|
1497
|
+
if (body === normalizedTarget) return sha;
|
|
1498
|
+
}
|
|
1499
|
+
return "";
|
|
1500
|
+
}
|
|
1325
1501
|
function getCodexAuthPath() {
|
|
1326
|
-
return
|
|
1502
|
+
return join3(getCodexHomeDir2(), "auth.json");
|
|
1327
1503
|
}
|
|
1328
1504
|
async function readCodexAuth() {
|
|
1329
1505
|
try {
|
|
@@ -1337,10 +1513,10 @@ async function readCodexAuth() {
|
|
|
1337
1513
|
}
|
|
1338
1514
|
}
|
|
1339
1515
|
function getCodexGlobalStatePath() {
|
|
1340
|
-
return
|
|
1516
|
+
return join3(getCodexHomeDir2(), ".codex-global-state.json");
|
|
1341
1517
|
}
|
|
1342
1518
|
function getCodexSessionIndexPath() {
|
|
1343
|
-
return
|
|
1519
|
+
return join3(getCodexHomeDir2(), "session_index.jsonl");
|
|
1344
1520
|
}
|
|
1345
1521
|
var MAX_THREAD_TITLES = 500;
|
|
1346
1522
|
var EMPTY_THREAD_TITLE_CACHE = { titles: {}, order: [] };
|
|
@@ -1599,10 +1775,10 @@ function handleFileUpload(req, res) {
|
|
|
1599
1775
|
setJson2(res, 400, { error: "No file in request" });
|
|
1600
1776
|
return;
|
|
1601
1777
|
}
|
|
1602
|
-
const uploadDir =
|
|
1778
|
+
const uploadDir = join3(tmpdir2(), "codex-web-uploads");
|
|
1603
1779
|
await mkdir2(uploadDir, { recursive: true });
|
|
1604
|
-
const destDir = await mkdtemp2(
|
|
1605
|
-
const destPath =
|
|
1780
|
+
const destDir = await mkdtemp2(join3(uploadDir, "f-"));
|
|
1781
|
+
const destPath = join3(destDir, fileName);
|
|
1606
1782
|
await writeFile2(destPath, fileData);
|
|
1607
1783
|
setJson2(res, 200, { path: destPath });
|
|
1608
1784
|
} catch (err) {
|
|
@@ -1709,7 +1885,8 @@ var AppServerProcess = class {
|
|
|
1709
1885
|
start() {
|
|
1710
1886
|
if (this.process) return;
|
|
1711
1887
|
this.stopping = false;
|
|
1712
|
-
const
|
|
1888
|
+
const invocation = getSpawnInvocation(resolveCodexCommand() ?? "codex", this.appServerArgs);
|
|
1889
|
+
const proc = spawn2(invocation.command, invocation.args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
1713
1890
|
this.process = proc;
|
|
1714
1891
|
proc.stdout.setEncoding("utf8");
|
|
1715
1892
|
proc.stdout.on("data", (chunk) => {
|
|
@@ -1935,7 +2112,8 @@ var MethodCatalog = class {
|
|
|
1935
2112
|
}
|
|
1936
2113
|
async runGenerateSchemaCommand(outDir) {
|
|
1937
2114
|
await new Promise((resolve3, reject) => {
|
|
1938
|
-
const
|
|
2115
|
+
const invocation = getSpawnInvocation(resolveCodexCommand() ?? "codex", ["app-server", "generate-json-schema", "--out", outDir]);
|
|
2116
|
+
const process2 = spawn2(invocation.command, invocation.args, {
|
|
1939
2117
|
stdio: ["ignore", "ignore", "pipe"]
|
|
1940
2118
|
});
|
|
1941
2119
|
let stderr = "";
|
|
@@ -1991,9 +2169,9 @@ var MethodCatalog = class {
|
|
|
1991
2169
|
if (this.methodCache) {
|
|
1992
2170
|
return this.methodCache;
|
|
1993
2171
|
}
|
|
1994
|
-
const outDir = await mkdtemp2(
|
|
2172
|
+
const outDir = await mkdtemp2(join3(tmpdir2(), "codex-web-local-schema-"));
|
|
1995
2173
|
await this.runGenerateSchemaCommand(outDir);
|
|
1996
|
-
const clientRequestPath =
|
|
2174
|
+
const clientRequestPath = join3(outDir, "ClientRequest.json");
|
|
1997
2175
|
const raw = await readFile2(clientRequestPath, "utf8");
|
|
1998
2176
|
const parsed = JSON.parse(raw);
|
|
1999
2177
|
const methods = this.extractMethodsFromClientRequest(parsed);
|
|
@@ -2004,9 +2182,9 @@ var MethodCatalog = class {
|
|
|
2004
2182
|
if (this.notificationCache) {
|
|
2005
2183
|
return this.notificationCache;
|
|
2006
2184
|
}
|
|
2007
|
-
const outDir = await mkdtemp2(
|
|
2185
|
+
const outDir = await mkdtemp2(join3(tmpdir2(), "codex-web-local-schema-"));
|
|
2008
2186
|
await this.runGenerateSchemaCommand(outDir);
|
|
2009
|
-
const serverNotificationPath =
|
|
2187
|
+
const serverNotificationPath = join3(outDir, "ServerNotification.json");
|
|
2010
2188
|
const raw = await readFile2(serverNotificationPath, "utf8");
|
|
2011
2189
|
const parsed = JSON.parse(raw);
|
|
2012
2190
|
const methods = this.extractMethodsFromServerNotification(parsed);
|
|
@@ -2168,7 +2346,20 @@ function createCodexBridgeMiddleware() {
|
|
|
2168
2346
|
return;
|
|
2169
2347
|
}
|
|
2170
2348
|
if (req.method === "GET" && url.pathname === "/codex-api/home-directory") {
|
|
2171
|
-
setJson2(res, 200, { data: { path:
|
|
2349
|
+
setJson2(res, 200, { data: { path: homedir3() } });
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
if (req.method === "GET" && url.pathname === "/codex-api/github-trending") {
|
|
2353
|
+
const sinceRaw = (url.searchParams.get("since") ?? "").trim().toLowerCase();
|
|
2354
|
+
const since = sinceRaw === "weekly" ? "weekly" : sinceRaw === "monthly" ? "monthly" : "daily";
|
|
2355
|
+
const limitRaw = Number.parseInt((url.searchParams.get("limit") ?? "6").trim(), 10);
|
|
2356
|
+
const limit = Number.isFinite(limitRaw) ? Math.max(1, Math.min(10, limitRaw)) : 6;
|
|
2357
|
+
try {
|
|
2358
|
+
const data = await fetchGithubTrending(since, limit);
|
|
2359
|
+
setJson2(res, 200, { data });
|
|
2360
|
+
} catch (error) {
|
|
2361
|
+
setJson2(res, 502, { error: getErrorMessage2(error, "Failed to fetch GitHub trending") });
|
|
2362
|
+
}
|
|
2172
2363
|
return;
|
|
2173
2364
|
}
|
|
2174
2365
|
if (req.method === "POST" && url.pathname === "/codex-api/worktree/create") {
|
|
@@ -2198,22 +2389,22 @@ function createCodexBridgeMiddleware() {
|
|
|
2198
2389
|
await runCommand2("git", ["init"], { cwd: sourceCwd });
|
|
2199
2390
|
gitRoot = await runCommandCapture("git", ["rev-parse", "--show-toplevel"], { cwd: sourceCwd });
|
|
2200
2391
|
}
|
|
2201
|
-
const repoName =
|
|
2202
|
-
const worktreesRoot =
|
|
2392
|
+
const repoName = basename2(gitRoot) || "repo";
|
|
2393
|
+
const worktreesRoot = join3(getCodexHomeDir2(), "worktrees");
|
|
2203
2394
|
await mkdir2(worktreesRoot, { recursive: true });
|
|
2204
2395
|
let worktreeId = "";
|
|
2205
2396
|
let worktreeParent = "";
|
|
2206
2397
|
let worktreeCwd = "";
|
|
2207
2398
|
for (let attempt = 0; attempt < 12; attempt += 1) {
|
|
2208
2399
|
const candidate = randomBytes(2).toString("hex");
|
|
2209
|
-
const parent =
|
|
2400
|
+
const parent = join3(worktreesRoot, candidate);
|
|
2210
2401
|
try {
|
|
2211
2402
|
await stat2(parent);
|
|
2212
2403
|
continue;
|
|
2213
2404
|
} catch {
|
|
2214
2405
|
worktreeId = candidate;
|
|
2215
2406
|
worktreeParent = parent;
|
|
2216
|
-
worktreeCwd =
|
|
2407
|
+
worktreeCwd = join3(parent, repoName);
|
|
2217
2408
|
break;
|
|
2218
2409
|
}
|
|
2219
2410
|
}
|
|
@@ -2241,6 +2432,99 @@ function createCodexBridgeMiddleware() {
|
|
|
2241
2432
|
}
|
|
2242
2433
|
return;
|
|
2243
2434
|
}
|
|
2435
|
+
if (req.method === "POST" && url.pathname === "/codex-api/worktree/auto-commit") {
|
|
2436
|
+
const payload = asRecord2(await readJsonBody(req));
|
|
2437
|
+
const rawCwd = typeof payload?.cwd === "string" ? payload.cwd.trim() : "";
|
|
2438
|
+
const commitMessage = normalizeCommitMessage(payload?.message);
|
|
2439
|
+
if (!rawCwd) {
|
|
2440
|
+
setJson2(res, 400, { error: "Missing cwd" });
|
|
2441
|
+
return;
|
|
2442
|
+
}
|
|
2443
|
+
if (!commitMessage) {
|
|
2444
|
+
setJson2(res, 400, { error: "Missing message" });
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2447
|
+
const cwd = isAbsolute(rawCwd) ? rawCwd : resolve(rawCwd);
|
|
2448
|
+
try {
|
|
2449
|
+
const cwdInfo = await stat2(cwd);
|
|
2450
|
+
if (!cwdInfo.isDirectory()) {
|
|
2451
|
+
setJson2(res, 400, { error: "cwd is not a directory" });
|
|
2452
|
+
return;
|
|
2453
|
+
}
|
|
2454
|
+
} catch {
|
|
2455
|
+
setJson2(res, 404, { error: "cwd does not exist" });
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
try {
|
|
2459
|
+
await runCommandCapture("git", ["rev-parse", "--is-inside-work-tree"], { cwd });
|
|
2460
|
+
const beforeStatus = await runCommandWithOutput2("git", ["status", "--porcelain"], { cwd });
|
|
2461
|
+
if (!beforeStatus.trim()) {
|
|
2462
|
+
setJson2(res, 200, { data: { committed: false } });
|
|
2463
|
+
return;
|
|
2464
|
+
}
|
|
2465
|
+
await runCommand2("git", ["add", "-A"], { cwd });
|
|
2466
|
+
const stagedStatus = await runCommandWithOutput2("git", ["diff", "--cached", "--name-only"], { cwd });
|
|
2467
|
+
if (!stagedStatus.trim()) {
|
|
2468
|
+
setJson2(res, 200, { data: { committed: false } });
|
|
2469
|
+
return;
|
|
2470
|
+
}
|
|
2471
|
+
await runCommand2("git", ["commit", "-m", commitMessage], { cwd });
|
|
2472
|
+
setJson2(res, 200, { data: { committed: true } });
|
|
2473
|
+
} catch (error) {
|
|
2474
|
+
setJson2(res, 500, { error: getErrorMessage2(error, "Failed to auto-commit worktree changes") });
|
|
2475
|
+
}
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2478
|
+
if (req.method === "POST" && url.pathname === "/codex-api/worktree/rollback-to-message") {
|
|
2479
|
+
const payload = asRecord2(await readJsonBody(req));
|
|
2480
|
+
const rawCwd = typeof payload?.cwd === "string" ? payload.cwd.trim() : "";
|
|
2481
|
+
const commitMessage = normalizeCommitMessage(payload?.message);
|
|
2482
|
+
if (!rawCwd) {
|
|
2483
|
+
setJson2(res, 400, { error: "Missing cwd" });
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
if (!commitMessage) {
|
|
2487
|
+
setJson2(res, 400, { error: "Missing message" });
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
const cwd = isAbsolute(rawCwd) ? rawCwd : resolve(rawCwd);
|
|
2491
|
+
try {
|
|
2492
|
+
const cwdInfo = await stat2(cwd);
|
|
2493
|
+
if (!cwdInfo.isDirectory()) {
|
|
2494
|
+
setJson2(res, 400, { error: "cwd is not a directory" });
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2497
|
+
} catch {
|
|
2498
|
+
setJson2(res, 404, { error: "cwd does not exist" });
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
try {
|
|
2502
|
+
await runCommandCapture("git", ["rev-parse", "--is-inside-work-tree"], { cwd });
|
|
2503
|
+
const commitSha = await findCommitByExactMessage(cwd, commitMessage);
|
|
2504
|
+
if (!commitSha) {
|
|
2505
|
+
setJson2(res, 404, { error: "No matching commit found for this user message" });
|
|
2506
|
+
return;
|
|
2507
|
+
}
|
|
2508
|
+
let resetTargetSha = "";
|
|
2509
|
+
try {
|
|
2510
|
+
resetTargetSha = await runCommandCapture("git", ["rev-parse", `${commitSha}^`], { cwd });
|
|
2511
|
+
} catch {
|
|
2512
|
+
setJson2(res, 409, { error: "Cannot rollback: matched commit has no parent commit" });
|
|
2513
|
+
return;
|
|
2514
|
+
}
|
|
2515
|
+
let stashed = false;
|
|
2516
|
+
if (await hasGitWorkingTreeChanges(cwd)) {
|
|
2517
|
+
const stashMessage = `codex-auto-stash-before-rollback-${Date.now()}`;
|
|
2518
|
+
await runCommand2("git", ["stash", "push", "-u", "-m", stashMessage], { cwd });
|
|
2519
|
+
stashed = true;
|
|
2520
|
+
}
|
|
2521
|
+
await runCommand2("git", ["reset", "--hard", resetTargetSha], { cwd });
|
|
2522
|
+
setJson2(res, 200, { data: { reset: true, commitSha, resetTargetSha, stashed } });
|
|
2523
|
+
} catch (error) {
|
|
2524
|
+
setJson2(res, 500, { error: getErrorMessage2(error, "Failed to rollback worktree to user message commit") });
|
|
2525
|
+
}
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2244
2528
|
if (req.method === "PUT" && url.pathname === "/codex-api/workspace-roots-state") {
|
|
2245
2529
|
const payload = await readJsonBody(req);
|
|
2246
2530
|
const record = asRecord2(payload);
|
|
@@ -2318,7 +2602,7 @@ function createCodexBridgeMiddleware() {
|
|
|
2318
2602
|
let index = 1;
|
|
2319
2603
|
while (index < 1e5) {
|
|
2320
2604
|
const candidateName = `New Project (${String(index)})`;
|
|
2321
|
-
const candidatePath =
|
|
2605
|
+
const candidatePath = join3(normalizedBasePath, candidateName);
|
|
2322
2606
|
try {
|
|
2323
2607
|
await stat2(candidatePath);
|
|
2324
2608
|
index += 1;
|
|
@@ -2564,7 +2848,7 @@ function createAuthSession(password) {
|
|
|
2564
2848
|
}
|
|
2565
2849
|
|
|
2566
2850
|
// src/server/localBrowseUi.ts
|
|
2567
|
-
import { dirname, extname, join as
|
|
2851
|
+
import { dirname, extname as extname2, join as join4 } from "path";
|
|
2568
2852
|
import { open, readFile as readFile3, readdir as readdir3, stat as stat3 } from "fs/promises";
|
|
2569
2853
|
var TEXT_EDITABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2570
2854
|
".txt",
|
|
@@ -2595,7 +2879,7 @@ var TEXT_EDITABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
2595
2879
|
".ps1"
|
|
2596
2880
|
]);
|
|
2597
2881
|
function languageForPath(pathValue) {
|
|
2598
|
-
const extension =
|
|
2882
|
+
const extension = extname2(pathValue).toLowerCase();
|
|
2599
2883
|
switch (extension) {
|
|
2600
2884
|
case ".js":
|
|
2601
2885
|
return "javascript";
|
|
@@ -2656,7 +2940,7 @@ function decodeBrowsePath(rawPath) {
|
|
|
2656
2940
|
}
|
|
2657
2941
|
}
|
|
2658
2942
|
function isTextEditablePath(pathValue) {
|
|
2659
|
-
return TEXT_EDITABLE_EXTENSIONS.has(
|
|
2943
|
+
return TEXT_EDITABLE_EXTENSIONS.has(extname2(pathValue).toLowerCase());
|
|
2660
2944
|
}
|
|
2661
2945
|
function looksLikeTextBuffer(buffer) {
|
|
2662
2946
|
if (buffer.length === 0) return true;
|
|
@@ -2702,7 +2986,7 @@ function escapeForInlineScriptString(value) {
|
|
|
2702
2986
|
async function getDirectoryItems(localPath) {
|
|
2703
2987
|
const entries = await readdir3(localPath, { withFileTypes: true });
|
|
2704
2988
|
const withMeta = await Promise.all(entries.map(async (entry) => {
|
|
2705
|
-
const entryPath =
|
|
2989
|
+
const entryPath = join4(localPath, entry.name);
|
|
2706
2990
|
const entryStat = await stat3(entryPath);
|
|
2707
2991
|
const editable = !entry.isDirectory() && await isTextEditableFile(entryPath);
|
|
2708
2992
|
return {
|
|
@@ -2889,8 +3173,8 @@ async function createTextEditorHtml(localPath) {
|
|
|
2889
3173
|
// src/server/httpServer.ts
|
|
2890
3174
|
import { WebSocketServer } from "ws";
|
|
2891
3175
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
2892
|
-
var distDir =
|
|
2893
|
-
var spaEntryFile =
|
|
3176
|
+
var distDir = join5(__dirname, "..", "dist");
|
|
3177
|
+
var spaEntryFile = join5(distDir, "index.html");
|
|
2894
3178
|
var IMAGE_CONTENT_TYPES = {
|
|
2895
3179
|
".avif": "image/avif",
|
|
2896
3180
|
".bmp": "image/bmp",
|
|
@@ -2947,7 +3231,7 @@ function createServer(options = {}) {
|
|
|
2947
3231
|
res.status(400).json({ error: "Expected absolute local file path." });
|
|
2948
3232
|
return;
|
|
2949
3233
|
}
|
|
2950
|
-
const contentType = IMAGE_CONTENT_TYPES[
|
|
3234
|
+
const contentType = IMAGE_CONTENT_TYPES[extname3(localPath).toLowerCase()];
|
|
2951
3235
|
if (!contentType) {
|
|
2952
3236
|
res.status(415).json({ error: "Unsupported image type." });
|
|
2953
3237
|
return;
|
|
@@ -3034,7 +3318,7 @@ function createServer(options = {}) {
|
|
|
3034
3318
|
res.status(404).json({ error: "File not found." });
|
|
3035
3319
|
}
|
|
3036
3320
|
});
|
|
3037
|
-
const hasFrontendAssets =
|
|
3321
|
+
const hasFrontendAssets = existsSync4(spaEntryFile);
|
|
3038
3322
|
if (hasFrontendAssets) {
|
|
3039
3323
|
app.use(express.static(distDir));
|
|
3040
3324
|
}
|
|
@@ -3105,9 +3389,25 @@ function generatePassword() {
|
|
|
3105
3389
|
// src/cli/index.ts
|
|
3106
3390
|
var program = new Command().name("codexui").description("Web interface for Codex app-server");
|
|
3107
3391
|
var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
3392
|
+
var hasPromptedCloudflaredInstall = false;
|
|
3393
|
+
function getCodexHomePath() {
|
|
3394
|
+
return process.env.CODEX_HOME?.trim() || join6(homedir4(), ".codex");
|
|
3395
|
+
}
|
|
3396
|
+
function getCloudflaredPromptMarkerPath() {
|
|
3397
|
+
return join6(getCodexHomePath(), ".cloudflared-install-prompted");
|
|
3398
|
+
}
|
|
3399
|
+
function hasPromptedCloudflaredInstallPersisted() {
|
|
3400
|
+
return existsSync5(getCloudflaredPromptMarkerPath());
|
|
3401
|
+
}
|
|
3402
|
+
async function persistCloudflaredInstallPrompted() {
|
|
3403
|
+
const codexHome = getCodexHomePath();
|
|
3404
|
+
mkdirSync(codexHome, { recursive: true });
|
|
3405
|
+
await writeFile4(getCloudflaredPromptMarkerPath(), `${Date.now()}
|
|
3406
|
+
`, "utf8");
|
|
3407
|
+
}
|
|
3108
3408
|
async function readCliVersion() {
|
|
3109
3409
|
try {
|
|
3110
|
-
const packageJsonPath =
|
|
3410
|
+
const packageJsonPath = join6(__dirname2, "..", "package.json");
|
|
3111
3411
|
const raw = await readFile4(packageJsonPath, "utf8");
|
|
3112
3412
|
const parsed = JSON.parse(raw);
|
|
3113
3413
|
return typeof parsed.version === "string" ? parsed.version : "unknown";
|
|
@@ -3119,46 +3419,25 @@ function isTermuxRuntime() {
|
|
|
3119
3419
|
return Boolean(process.env.TERMUX_VERSION || process.env.PREFIX?.includes("/com.termux/"));
|
|
3120
3420
|
}
|
|
3121
3421
|
function canRun(command, args = []) {
|
|
3122
|
-
const result =
|
|
3123
|
-
return result
|
|
3422
|
+
const result = canRunCommand(command, args);
|
|
3423
|
+
return result;
|
|
3124
3424
|
}
|
|
3125
3425
|
function runOrFail(command, args, label) {
|
|
3126
|
-
const result =
|
|
3426
|
+
const result = spawnSyncCommand(command, args, { stdio: "inherit" });
|
|
3127
3427
|
if (result.status !== 0) {
|
|
3128
3428
|
throw new Error(`${label} failed with exit code ${String(result.status ?? -1)}`);
|
|
3129
3429
|
}
|
|
3130
3430
|
}
|
|
3131
3431
|
function runWithStatus(command, args) {
|
|
3132
|
-
const result =
|
|
3432
|
+
const result = spawnSyncCommand(command, args, { stdio: "inherit" });
|
|
3133
3433
|
return result.status ?? -1;
|
|
3134
3434
|
}
|
|
3135
|
-
function getUserNpmPrefix() {
|
|
3136
|
-
return join5(homedir3(), ".npm-global");
|
|
3137
|
-
}
|
|
3138
|
-
function resolveCodexCommand() {
|
|
3139
|
-
if (canRun("codex", ["--version"])) {
|
|
3140
|
-
return "codex";
|
|
3141
|
-
}
|
|
3142
|
-
const userCandidate = join5(getUserNpmPrefix(), "bin", "codex");
|
|
3143
|
-
if (existsSync4(userCandidate) && canRun(userCandidate, ["--version"])) {
|
|
3144
|
-
return userCandidate;
|
|
3145
|
-
}
|
|
3146
|
-
const prefix = process.env.PREFIX?.trim();
|
|
3147
|
-
if (!prefix) {
|
|
3148
|
-
return null;
|
|
3149
|
-
}
|
|
3150
|
-
const candidate = join5(prefix, "bin", "codex");
|
|
3151
|
-
if (existsSync4(candidate) && canRun(candidate, ["--version"])) {
|
|
3152
|
-
return candidate;
|
|
3153
|
-
}
|
|
3154
|
-
return null;
|
|
3155
|
-
}
|
|
3156
3435
|
function resolveCloudflaredCommand() {
|
|
3157
3436
|
if (canRun("cloudflared", ["--version"])) {
|
|
3158
3437
|
return "cloudflared";
|
|
3159
3438
|
}
|
|
3160
|
-
const localCandidate =
|
|
3161
|
-
if (
|
|
3439
|
+
const localCandidate = join6(homedir4(), ".local", "bin", "cloudflared");
|
|
3440
|
+
if (existsSync5(localCandidate) && canRun(localCandidate, ["--version"])) {
|
|
3162
3441
|
return localCandidate;
|
|
3163
3442
|
}
|
|
3164
3443
|
return null;
|
|
@@ -3211,9 +3490,9 @@ async function ensureCloudflaredInstalledLinux() {
|
|
|
3211
3490
|
if (!mappedArch) {
|
|
3212
3491
|
throw new Error(`cloudflared auto-install is not supported for Linux architecture: ${process.arch}`);
|
|
3213
3492
|
}
|
|
3214
|
-
const userBinDir =
|
|
3493
|
+
const userBinDir = join6(homedir4(), ".local", "bin");
|
|
3215
3494
|
mkdirSync(userBinDir, { recursive: true });
|
|
3216
|
-
const destination =
|
|
3495
|
+
const destination = join6(userBinDir, "cloudflared");
|
|
3217
3496
|
const downloadUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${mappedArch}`;
|
|
3218
3497
|
console.log("\ncloudflared not found. Installing to ~/.local/bin...\n");
|
|
3219
3498
|
await downloadFile(downloadUrl, destination);
|
|
@@ -3227,6 +3506,14 @@ async function ensureCloudflaredInstalledLinux() {
|
|
|
3227
3506
|
return installed;
|
|
3228
3507
|
}
|
|
3229
3508
|
async function shouldInstallCloudflaredInteractively() {
|
|
3509
|
+
if (hasPromptedCloudflaredInstall || hasPromptedCloudflaredInstallPersisted()) {
|
|
3510
|
+
return false;
|
|
3511
|
+
}
|
|
3512
|
+
hasPromptedCloudflaredInstall = true;
|
|
3513
|
+
await persistCloudflaredInstallPrompted();
|
|
3514
|
+
if (process.platform === "win32") {
|
|
3515
|
+
return false;
|
|
3516
|
+
}
|
|
3230
3517
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
3231
3518
|
console.warn("\n[cloudflared] cloudflared is missing and terminal is non-interactive, skipping install.");
|
|
3232
3519
|
return false;
|
|
@@ -3245,6 +3532,9 @@ async function resolveCloudflaredForTunnel() {
|
|
|
3245
3532
|
if (current) {
|
|
3246
3533
|
return current;
|
|
3247
3534
|
}
|
|
3535
|
+
if (process.platform === "win32") {
|
|
3536
|
+
return null;
|
|
3537
|
+
}
|
|
3248
3538
|
const installApproved = await shouldInstallCloudflaredInteractively();
|
|
3249
3539
|
if (!installApproved) {
|
|
3250
3540
|
return null;
|
|
@@ -3252,8 +3542,8 @@ async function resolveCloudflaredForTunnel() {
|
|
|
3252
3542
|
return ensureCloudflaredInstalledLinux();
|
|
3253
3543
|
}
|
|
3254
3544
|
function hasCodexAuth() {
|
|
3255
|
-
const codexHome =
|
|
3256
|
-
return
|
|
3545
|
+
const codexHome = getCodexHomePath();
|
|
3546
|
+
return existsSync5(join6(codexHome, "auth.json"));
|
|
3257
3547
|
}
|
|
3258
3548
|
function ensureCodexInstalled() {
|
|
3259
3549
|
let codexCommand = resolveCodexCommand();
|
|
@@ -3271,7 +3561,7 @@ function ensureCodexInstalled() {
|
|
|
3271
3561
|
Global npm install requires elevated permissions. Retrying with --prefix ${userPrefix}...
|
|
3272
3562
|
`);
|
|
3273
3563
|
runOrFail("npm", ["install", "-g", "--prefix", userPrefix, pkg], `${label} (user prefix)`);
|
|
3274
|
-
process.env.PATH = `${
|
|
3564
|
+
process.env.PATH = `${join6(userPrefix, "bin")}:${process.env.PATH ?? ""}`;
|
|
3275
3565
|
};
|
|
3276
3566
|
if (isTermuxRuntime()) {
|
|
3277
3567
|
console.log("\nCodex CLI not found. Installing Termux-compatible Codex CLI from npm...\n");
|
|
@@ -3412,8 +3702,8 @@ function listenWithFallback(server, startPort) {
|
|
|
3412
3702
|
});
|
|
3413
3703
|
}
|
|
3414
3704
|
function getCodexGlobalStatePath2() {
|
|
3415
|
-
const codexHome =
|
|
3416
|
-
return
|
|
3705
|
+
const codexHome = getCodexHomePath();
|
|
3706
|
+
return join6(codexHome, ".codex-global-state.json");
|
|
3417
3707
|
}
|
|
3418
3708
|
function normalizeUniqueStrings(value) {
|
|
3419
3709
|
if (!Array.isArray(value)) return [];
|