codexapp 0.1.48 → 0.1.50
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-DULPXBmy.js +1428 -0
- package/dist/assets/index-uy5QW_sJ.css +1 -0
- package/dist/index.html +2 -2
- package/dist-cli/index.js +234 -2
- package/dist-cli/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/index-C55jtDWV.css +0 -1
- package/dist/assets/index-DhcIXLYw.js +0 -1428
package/dist-cli/index.js
CHANGED
|
@@ -1273,6 +1273,54 @@ function scoreFileCandidate(path, query) {
|
|
|
1273
1273
|
if (lowerPath.includes(lowerQuery)) return 4;
|
|
1274
1274
|
return 10;
|
|
1275
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 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
|
+
}
|
|
1276
1324
|
async function listFilesWithRipgrep(cwd) {
|
|
1277
1325
|
return await new Promise((resolve3, reject) => {
|
|
1278
1326
|
const proc = spawn2("rg", ["--files", "--hidden", "-g", "!.git", "-g", "!node_modules"], {
|
|
@@ -1380,6 +1428,33 @@ async function runCommandCapture(command, args, options = {}) {
|
|
|
1380
1428
|
});
|
|
1381
1429
|
});
|
|
1382
1430
|
}
|
|
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
|
+
}
|
|
1383
1458
|
function normalizeStringArray(value) {
|
|
1384
1459
|
if (!Array.isArray(value)) return [];
|
|
1385
1460
|
const normalized = [];
|
|
@@ -1400,6 +1475,30 @@ function normalizeStringRecord(value) {
|
|
|
1400
1475
|
}
|
|
1401
1476
|
return next;
|
|
1402
1477
|
}
|
|
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
|
+
}
|
|
1403
1502
|
function getCodexAuthPath() {
|
|
1404
1503
|
return join3(getCodexHomeDir2(), "auth.json");
|
|
1405
1504
|
}
|
|
@@ -2251,6 +2350,19 @@ function createCodexBridgeMiddleware() {
|
|
|
2251
2350
|
setJson2(res, 200, { data: { path: homedir3() } });
|
|
2252
2351
|
return;
|
|
2253
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
|
+
}
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2254
2366
|
if (req.method === "POST" && url.pathname === "/codex-api/worktree/create") {
|
|
2255
2367
|
const payload = asRecord2(await readJsonBody(req));
|
|
2256
2368
|
const rawSourceCwd = typeof payload?.sourceCwd === "string" ? payload.sourceCwd.trim() : "";
|
|
@@ -2321,6 +2433,99 @@ function createCodexBridgeMiddleware() {
|
|
|
2321
2433
|
}
|
|
2322
2434
|
return;
|
|
2323
2435
|
}
|
|
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
|
+
}
|
|
2324
2529
|
if (req.method === "PUT" && url.pathname === "/codex-api/workspace-roots-state") {
|
|
2325
2530
|
const payload = await readJsonBody(req);
|
|
2326
2531
|
const record = asRecord2(payload);
|
|
@@ -3185,6 +3390,22 @@ function generatePassword() {
|
|
|
3185
3390
|
// src/cli/index.ts
|
|
3186
3391
|
var program = new Command().name("codexui").description("Web interface for Codex app-server");
|
|
3187
3392
|
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
|
+
}
|
|
3188
3409
|
async function readCliVersion() {
|
|
3189
3410
|
try {
|
|
3190
3411
|
const packageJsonPath = join6(__dirname2, "..", "package.json");
|
|
@@ -3286,6 +3507,14 @@ async function ensureCloudflaredInstalledLinux() {
|
|
|
3286
3507
|
return installed;
|
|
3287
3508
|
}
|
|
3288
3509
|
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
|
+
}
|
|
3289
3518
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
3290
3519
|
console.warn("\n[cloudflared] cloudflared is missing and terminal is non-interactive, skipping install.");
|
|
3291
3520
|
return false;
|
|
@@ -3304,6 +3533,9 @@ async function resolveCloudflaredForTunnel() {
|
|
|
3304
3533
|
if (current) {
|
|
3305
3534
|
return current;
|
|
3306
3535
|
}
|
|
3536
|
+
if (process.platform === "win32") {
|
|
3537
|
+
return null;
|
|
3538
|
+
}
|
|
3307
3539
|
const installApproved = await shouldInstallCloudflaredInteractively();
|
|
3308
3540
|
if (!installApproved) {
|
|
3309
3541
|
return null;
|
|
@@ -3311,7 +3543,7 @@ async function resolveCloudflaredForTunnel() {
|
|
|
3311
3543
|
return ensureCloudflaredInstalledLinux();
|
|
3312
3544
|
}
|
|
3313
3545
|
function hasCodexAuth() {
|
|
3314
|
-
const codexHome =
|
|
3546
|
+
const codexHome = getCodexHomePath();
|
|
3315
3547
|
return existsSync5(join6(codexHome, "auth.json"));
|
|
3316
3548
|
}
|
|
3317
3549
|
function ensureCodexInstalled() {
|
|
@@ -3471,7 +3703,7 @@ function listenWithFallback(server, startPort) {
|
|
|
3471
3703
|
});
|
|
3472
3704
|
}
|
|
3473
3705
|
function getCodexGlobalStatePath2() {
|
|
3474
|
-
const codexHome =
|
|
3706
|
+
const codexHome = getCodexHomePath();
|
|
3475
3707
|
return join6(codexHome, ".codex-global-state.json");
|
|
3476
3708
|
}
|
|
3477
3709
|
function normalizeUniqueStrings(value) {
|