codexapp 0.1.46 → 0.1.48
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/README.md +34 -0
- package/dist/assets/index-C55jtDWV.css +1 -0
- package/dist/assets/index-DhcIXLYw.js +1428 -0
- package/dist/index.html +2 -2
- package/dist-cli/index.js +197 -96
- package/dist-cli/index.js.map +1 -1
- package/package.json +1 -3
- package/dist/assets/index-D2tyBrlG.css +0 -1
- package/dist/assets/index-ol-gi5Ys.js +0 -1428
package/dist/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Codex Web Local</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-DhcIXLYw.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C55jtDWV.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body class="bg-slate-950">
|
|
11
11
|
<div id="app"></div>
|
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;
|
|
@@ -1224,7 +1302,7 @@ async function listFilesWithRipgrep(cwd) {
|
|
|
1224
1302
|
}
|
|
1225
1303
|
function getCodexHomeDir2() {
|
|
1226
1304
|
const codexHome = process.env.CODEX_HOME?.trim();
|
|
1227
|
-
return codexHome && codexHome.length > 0 ? codexHome :
|
|
1305
|
+
return codexHome && codexHome.length > 0 ? codexHome : join3(homedir3(), ".codex");
|
|
1228
1306
|
}
|
|
1229
1307
|
async function runCommand2(command, args, options = {}) {
|
|
1230
1308
|
await new Promise((resolve3, reject) => {
|
|
@@ -1262,7 +1340,7 @@ function isNotGitRepositoryError(error) {
|
|
|
1262
1340
|
return message.includes("not a git repository") || message.includes("fatal: not a git repository");
|
|
1263
1341
|
}
|
|
1264
1342
|
async function ensureRepoHasInitialCommit(repoRoot) {
|
|
1265
|
-
const agentsPath =
|
|
1343
|
+
const agentsPath = join3(repoRoot, "AGENTS.md");
|
|
1266
1344
|
try {
|
|
1267
1345
|
await stat2(agentsPath);
|
|
1268
1346
|
} catch {
|
|
@@ -1323,25 +1401,24 @@ function normalizeStringRecord(value) {
|
|
|
1323
1401
|
return next;
|
|
1324
1402
|
}
|
|
1325
1403
|
function getCodexAuthPath() {
|
|
1326
|
-
return
|
|
1404
|
+
return join3(getCodexHomeDir2(), "auth.json");
|
|
1327
1405
|
}
|
|
1328
1406
|
async function readCodexAuth() {
|
|
1329
1407
|
try {
|
|
1330
1408
|
const raw = await readFile2(getCodexAuthPath(), "utf8");
|
|
1331
1409
|
const auth = JSON.parse(raw);
|
|
1332
|
-
const apiKey = auth.OPENAI_API_KEY || process.env.OPENAI_API_KEY || void 0;
|
|
1333
1410
|
const token = auth.tokens?.access_token;
|
|
1334
|
-
if (!token
|
|
1335
|
-
return { accessToken: token
|
|
1411
|
+
if (!token) return null;
|
|
1412
|
+
return { accessToken: token, accountId: auth.tokens?.account_id ?? void 0 };
|
|
1336
1413
|
} catch {
|
|
1337
1414
|
return null;
|
|
1338
1415
|
}
|
|
1339
1416
|
}
|
|
1340
1417
|
function getCodexGlobalStatePath() {
|
|
1341
|
-
return
|
|
1418
|
+
return join3(getCodexHomeDir2(), ".codex-global-state.json");
|
|
1342
1419
|
}
|
|
1343
1420
|
function getCodexSessionIndexPath() {
|
|
1344
|
-
return
|
|
1421
|
+
return join3(getCodexHomeDir2(), "session_index.jsonl");
|
|
1345
1422
|
}
|
|
1346
1423
|
var MAX_THREAD_TITLES = 500;
|
|
1347
1424
|
var EMPTY_THREAD_TITLE_CACHE = { titles: {}, order: [] };
|
|
@@ -1600,10 +1677,10 @@ function handleFileUpload(req, res) {
|
|
|
1600
1677
|
setJson2(res, 400, { error: "No file in request" });
|
|
1601
1678
|
return;
|
|
1602
1679
|
}
|
|
1603
|
-
const uploadDir =
|
|
1680
|
+
const uploadDir = join3(tmpdir2(), "codex-web-uploads");
|
|
1604
1681
|
await mkdir2(uploadDir, { recursive: true });
|
|
1605
|
-
const destDir = await mkdtemp2(
|
|
1606
|
-
const destPath =
|
|
1682
|
+
const destDir = await mkdtemp2(join3(uploadDir, "f-"));
|
|
1683
|
+
const destPath = join3(destDir, fileName);
|
|
1607
1684
|
await writeFile2(destPath, fileData);
|
|
1608
1685
|
setJson2(res, 200, { path: destPath });
|
|
1609
1686
|
} catch (err) {
|
|
@@ -1660,20 +1737,11 @@ function curlImpersonatePost(url, headers, body) {
|
|
|
1660
1737
|
proc.stdin.end();
|
|
1661
1738
|
});
|
|
1662
1739
|
}
|
|
1663
|
-
|
|
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) {
|
|
1740
|
+
async function proxyTranscribe(body, contentType, authToken, accountId) {
|
|
1673
1741
|
const chatgptHeaders = {
|
|
1674
1742
|
"Content-Type": contentType,
|
|
1675
1743
|
"Content-Length": body.length,
|
|
1676
|
-
Authorization: `Bearer ${authToken
|
|
1744
|
+
Authorization: `Bearer ${authToken}`,
|
|
1677
1745
|
originator: "Codex Desktop",
|
|
1678
1746
|
"User-Agent": `Codex Desktop/0.1.0 (${process.platform}; ${process.arch})`
|
|
1679
1747
|
};
|
|
@@ -1693,12 +1761,7 @@ async function proxyTranscribe(body, contentType, authToken, accountId, apiKey)
|
|
|
1693
1761
|
} catch {
|
|
1694
1762
|
}
|
|
1695
1763
|
}
|
|
1696
|
-
|
|
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." }) };
|
|
1764
|
+
return { status: 503, body: JSON.stringify({ error: "Transcription blocked by Cloudflare. Install curl-impersonate-chrome." }) };
|
|
1702
1765
|
}
|
|
1703
1766
|
return result;
|
|
1704
1767
|
}
|
|
@@ -1724,7 +1787,8 @@ var AppServerProcess = class {
|
|
|
1724
1787
|
start() {
|
|
1725
1788
|
if (this.process) return;
|
|
1726
1789
|
this.stopping = false;
|
|
1727
|
-
const
|
|
1790
|
+
const invocation = getSpawnInvocation(resolveCodexCommand() ?? "codex", this.appServerArgs);
|
|
1791
|
+
const proc = spawn2(invocation.command, invocation.args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
1728
1792
|
this.process = proc;
|
|
1729
1793
|
proc.stdout.setEncoding("utf8");
|
|
1730
1794
|
proc.stdout.on("data", (chunk) => {
|
|
@@ -1950,7 +2014,8 @@ var MethodCatalog = class {
|
|
|
1950
2014
|
}
|
|
1951
2015
|
async runGenerateSchemaCommand(outDir) {
|
|
1952
2016
|
await new Promise((resolve3, reject) => {
|
|
1953
|
-
const
|
|
2017
|
+
const invocation = getSpawnInvocation(resolveCodexCommand() ?? "codex", ["app-server", "generate-json-schema", "--out", outDir]);
|
|
2018
|
+
const process2 = spawn2(invocation.command, invocation.args, {
|
|
1954
2019
|
stdio: ["ignore", "ignore", "pipe"]
|
|
1955
2020
|
});
|
|
1956
2021
|
let stderr = "";
|
|
@@ -2006,9 +2071,9 @@ var MethodCatalog = class {
|
|
|
2006
2071
|
if (this.methodCache) {
|
|
2007
2072
|
return this.methodCache;
|
|
2008
2073
|
}
|
|
2009
|
-
const outDir = await mkdtemp2(
|
|
2074
|
+
const outDir = await mkdtemp2(join3(tmpdir2(), "codex-web-local-schema-"));
|
|
2010
2075
|
await this.runGenerateSchemaCommand(outDir);
|
|
2011
|
-
const clientRequestPath =
|
|
2076
|
+
const clientRequestPath = join3(outDir, "ClientRequest.json");
|
|
2012
2077
|
const raw = await readFile2(clientRequestPath, "utf8");
|
|
2013
2078
|
const parsed = JSON.parse(raw);
|
|
2014
2079
|
const methods = this.extractMethodsFromClientRequest(parsed);
|
|
@@ -2019,9 +2084,9 @@ var MethodCatalog = class {
|
|
|
2019
2084
|
if (this.notificationCache) {
|
|
2020
2085
|
return this.notificationCache;
|
|
2021
2086
|
}
|
|
2022
|
-
const outDir = await mkdtemp2(
|
|
2087
|
+
const outDir = await mkdtemp2(join3(tmpdir2(), "codex-web-local-schema-"));
|
|
2023
2088
|
await this.runGenerateSchemaCommand(outDir);
|
|
2024
|
-
const serverNotificationPath =
|
|
2089
|
+
const serverNotificationPath = join3(outDir, "ServerNotification.json");
|
|
2025
2090
|
const raw = await readFile2(serverNotificationPath, "utf8");
|
|
2026
2091
|
const parsed = JSON.parse(raw);
|
|
2027
2092
|
const methods = this.extractMethodsFromServerNotification(parsed);
|
|
@@ -2151,7 +2216,7 @@ function createCodexBridgeMiddleware() {
|
|
|
2151
2216
|
}
|
|
2152
2217
|
const rawBody = await readRawBody(req);
|
|
2153
2218
|
const incomingCt = req.headers["content-type"] ?? "application/octet-stream";
|
|
2154
|
-
const upstream = await proxyTranscribe(rawBody, incomingCt, auth.accessToken, auth.accountId
|
|
2219
|
+
const upstream = await proxyTranscribe(rawBody, incomingCt, auth.accessToken, auth.accountId);
|
|
2155
2220
|
res.statusCode = upstream.status;
|
|
2156
2221
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
2157
2222
|
res.end(upstream.body);
|
|
@@ -2183,7 +2248,7 @@ function createCodexBridgeMiddleware() {
|
|
|
2183
2248
|
return;
|
|
2184
2249
|
}
|
|
2185
2250
|
if (req.method === "GET" && url.pathname === "/codex-api/home-directory") {
|
|
2186
|
-
setJson2(res, 200, { data: { path:
|
|
2251
|
+
setJson2(res, 200, { data: { path: homedir3() } });
|
|
2187
2252
|
return;
|
|
2188
2253
|
}
|
|
2189
2254
|
if (req.method === "POST" && url.pathname === "/codex-api/worktree/create") {
|
|
@@ -2213,22 +2278,22 @@ function createCodexBridgeMiddleware() {
|
|
|
2213
2278
|
await runCommand2("git", ["init"], { cwd: sourceCwd });
|
|
2214
2279
|
gitRoot = await runCommandCapture("git", ["rev-parse", "--show-toplevel"], { cwd: sourceCwd });
|
|
2215
2280
|
}
|
|
2216
|
-
const repoName =
|
|
2217
|
-
const worktreesRoot =
|
|
2281
|
+
const repoName = basename2(gitRoot) || "repo";
|
|
2282
|
+
const worktreesRoot = join3(getCodexHomeDir2(), "worktrees");
|
|
2218
2283
|
await mkdir2(worktreesRoot, { recursive: true });
|
|
2219
2284
|
let worktreeId = "";
|
|
2220
2285
|
let worktreeParent = "";
|
|
2221
2286
|
let worktreeCwd = "";
|
|
2222
2287
|
for (let attempt = 0; attempt < 12; attempt += 1) {
|
|
2223
2288
|
const candidate = randomBytes(2).toString("hex");
|
|
2224
|
-
const parent =
|
|
2289
|
+
const parent = join3(worktreesRoot, candidate);
|
|
2225
2290
|
try {
|
|
2226
2291
|
await stat2(parent);
|
|
2227
2292
|
continue;
|
|
2228
2293
|
} catch {
|
|
2229
2294
|
worktreeId = candidate;
|
|
2230
2295
|
worktreeParent = parent;
|
|
2231
|
-
worktreeCwd =
|
|
2296
|
+
worktreeCwd = join3(parent, repoName);
|
|
2232
2297
|
break;
|
|
2233
2298
|
}
|
|
2234
2299
|
}
|
|
@@ -2333,7 +2398,7 @@ function createCodexBridgeMiddleware() {
|
|
|
2333
2398
|
let index = 1;
|
|
2334
2399
|
while (index < 1e5) {
|
|
2335
2400
|
const candidateName = `New Project (${String(index)})`;
|
|
2336
|
-
const candidatePath =
|
|
2401
|
+
const candidatePath = join3(normalizedBasePath, candidateName);
|
|
2337
2402
|
try {
|
|
2338
2403
|
await stat2(candidatePath);
|
|
2339
2404
|
index += 1;
|
|
@@ -2579,7 +2644,7 @@ function createAuthSession(password) {
|
|
|
2579
2644
|
}
|
|
2580
2645
|
|
|
2581
2646
|
// src/server/localBrowseUi.ts
|
|
2582
|
-
import { dirname, extname, join as
|
|
2647
|
+
import { dirname, extname as extname2, join as join4 } from "path";
|
|
2583
2648
|
import { open, readFile as readFile3, readdir as readdir3, stat as stat3 } from "fs/promises";
|
|
2584
2649
|
var TEXT_EDITABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2585
2650
|
".txt",
|
|
@@ -2610,7 +2675,7 @@ var TEXT_EDITABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
2610
2675
|
".ps1"
|
|
2611
2676
|
]);
|
|
2612
2677
|
function languageForPath(pathValue) {
|
|
2613
|
-
const extension =
|
|
2678
|
+
const extension = extname2(pathValue).toLowerCase();
|
|
2614
2679
|
switch (extension) {
|
|
2615
2680
|
case ".js":
|
|
2616
2681
|
return "javascript";
|
|
@@ -2671,7 +2736,7 @@ function decodeBrowsePath(rawPath) {
|
|
|
2671
2736
|
}
|
|
2672
2737
|
}
|
|
2673
2738
|
function isTextEditablePath(pathValue) {
|
|
2674
|
-
return TEXT_EDITABLE_EXTENSIONS.has(
|
|
2739
|
+
return TEXT_EDITABLE_EXTENSIONS.has(extname2(pathValue).toLowerCase());
|
|
2675
2740
|
}
|
|
2676
2741
|
function looksLikeTextBuffer(buffer) {
|
|
2677
2742
|
if (buffer.length === 0) return true;
|
|
@@ -2717,7 +2782,7 @@ function escapeForInlineScriptString(value) {
|
|
|
2717
2782
|
async function getDirectoryItems(localPath) {
|
|
2718
2783
|
const entries = await readdir3(localPath, { withFileTypes: true });
|
|
2719
2784
|
const withMeta = await Promise.all(entries.map(async (entry) => {
|
|
2720
|
-
const entryPath =
|
|
2785
|
+
const entryPath = join4(localPath, entry.name);
|
|
2721
2786
|
const entryStat = await stat3(entryPath);
|
|
2722
2787
|
const editable = !entry.isDirectory() && await isTextEditableFile(entryPath);
|
|
2723
2788
|
return {
|
|
@@ -2741,9 +2806,9 @@ async function createDirectoryListingHtml(localPath) {
|
|
|
2741
2806
|
const rows = items.map((item) => {
|
|
2742
2807
|
const suffix = item.isDirectory ? "/" : "";
|
|
2743
2808
|
const editAction = item.editable ? ` <a class="icon-btn" aria-label="Edit ${escapeHtml(item.name)}" href="${escapeHtml(toEditHref(item.path))}" title="Edit">\u270F\uFE0F</a>` : "";
|
|
2744
|
-
return `<li class="file-row"><a class="file-link" href="${escapeHtml(toBrowseHref(item.path))}">${escapeHtml(item.name)}${suffix}</a>${editAction}</li>`;
|
|
2809
|
+
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>`;
|
|
2745
2810
|
}).join("\n");
|
|
2746
|
-
const parentLink = localPath !== parentPath ? `<
|
|
2811
|
+
const parentLink = localPath !== parentPath ? `<a href="${escapeHtml(toBrowseHref(parentPath))}">..</a>` : "";
|
|
2747
2812
|
return `<!doctype html>
|
|
2748
2813
|
<html lang="en">
|
|
2749
2814
|
<head>
|
|
@@ -2757,8 +2822,27 @@ async function createDirectoryListingHtml(localPath) {
|
|
|
2757
2822
|
ul { list-style: none; padding: 0; margin: 12px 0 0; display: flex; flex-direction: column; gap: 8px; }
|
|
2758
2823
|
.file-row { display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: center; gap: 10px; }
|
|
2759
2824
|
.file-link { display: block; padding: 10px 12px; border: 1px solid #28405f; border-radius: 10px; background: #0f1b33; overflow-wrap: anywhere; }
|
|
2760
|
-
.
|
|
2825
|
+
.header-actions { display: flex; align-items: center; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
|
|
2826
|
+
.header-parent-link { color: #9ec8ff; font-size: 14px; padding: 8px 10px; border: 1px solid #2a4569; border-radius: 10px; background: #101f3a; }
|
|
2827
|
+
.header-parent-link:hover { text-decoration: none; filter: brightness(1.08); }
|
|
2828
|
+
.header-open-btn {
|
|
2829
|
+
height: 42px;
|
|
2830
|
+
padding: 0 14px;
|
|
2831
|
+
border: 1px solid #4f8de0;
|
|
2832
|
+
border-radius: 10px;
|
|
2833
|
+
background: linear-gradient(135deg, #2e6ee6 0%, #3d8cff 100%);
|
|
2834
|
+
color: #eef6ff;
|
|
2835
|
+
font-weight: 700;
|
|
2836
|
+
letter-spacing: 0.01em;
|
|
2837
|
+
cursor: pointer;
|
|
2838
|
+
box-shadow: 0 6px 18px rgba(33, 90, 199, 0.35);
|
|
2839
|
+
}
|
|
2840
|
+
.header-open-btn:hover { filter: brightness(1.08); }
|
|
2841
|
+
.header-open-btn:disabled { opacity: 0.6; cursor: default; }
|
|
2842
|
+
.row-actions { display: inline-flex; align-items: center; gap: 8px; min-width: 42px; justify-content: flex-end; }
|
|
2843
|
+
.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; }
|
|
2761
2844
|
.icon-btn:hover { filter: brightness(1.08); text-decoration: none; }
|
|
2845
|
+
.status { margin: 10px 0 0; color: #8cc2ff; min-height: 1.25em; }
|
|
2762
2846
|
h1 { font-size: 18px; margin: 0; word-break: break-all; }
|
|
2763
2847
|
@media (max-width: 640px) {
|
|
2764
2848
|
body { margin: 12px; }
|
|
@@ -2770,8 +2854,46 @@ async function createDirectoryListingHtml(localPath) {
|
|
|
2770
2854
|
</head>
|
|
2771
2855
|
<body>
|
|
2772
2856
|
<h1>Index of ${escapeHtml(localPath)}</h1>
|
|
2773
|
-
|
|
2857
|
+
<div class="header-actions">
|
|
2858
|
+
${parentLink ? `<a class="header-parent-link" href="${escapeHtml(toBrowseHref(parentPath))}">..</a>` : ""}
|
|
2859
|
+
<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>
|
|
2860
|
+
</div>
|
|
2861
|
+
<p id="status" class="status"></p>
|
|
2774
2862
|
<ul>${rows}</ul>
|
|
2863
|
+
<script>
|
|
2864
|
+
const status = document.getElementById('status');
|
|
2865
|
+
document.addEventListener('click', async (event) => {
|
|
2866
|
+
const target = event.target;
|
|
2867
|
+
if (!(target instanceof Element)) return;
|
|
2868
|
+
const button = target.closest('.open-folder-btn');
|
|
2869
|
+
if (!(button instanceof HTMLButtonElement)) return;
|
|
2870
|
+
|
|
2871
|
+
const path = button.getAttribute('data-path') || '';
|
|
2872
|
+
if (!path) return;
|
|
2873
|
+
button.disabled = true;
|
|
2874
|
+
status.textContent = 'Opening folder in Codex...';
|
|
2875
|
+
try {
|
|
2876
|
+
const response = await fetch('/codex-api/project-root', {
|
|
2877
|
+
method: 'POST',
|
|
2878
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2879
|
+
body: JSON.stringify({
|
|
2880
|
+
path,
|
|
2881
|
+
createIfMissing: false,
|
|
2882
|
+
label: '',
|
|
2883
|
+
}),
|
|
2884
|
+
});
|
|
2885
|
+
if (!response.ok) {
|
|
2886
|
+
status.textContent = 'Failed to open folder.';
|
|
2887
|
+
button.disabled = false;
|
|
2888
|
+
return;
|
|
2889
|
+
}
|
|
2890
|
+
window.location.assign('/#/');
|
|
2891
|
+
} catch {
|
|
2892
|
+
status.textContent = 'Failed to open folder.';
|
|
2893
|
+
button.disabled = false;
|
|
2894
|
+
}
|
|
2895
|
+
});
|
|
2896
|
+
</script>
|
|
2775
2897
|
</body>
|
|
2776
2898
|
</html>`;
|
|
2777
2899
|
}
|
|
@@ -2847,8 +2969,8 @@ async function createTextEditorHtml(localPath) {
|
|
|
2847
2969
|
// src/server/httpServer.ts
|
|
2848
2970
|
import { WebSocketServer } from "ws";
|
|
2849
2971
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
2850
|
-
var distDir =
|
|
2851
|
-
var spaEntryFile =
|
|
2972
|
+
var distDir = join5(__dirname, "..", "dist");
|
|
2973
|
+
var spaEntryFile = join5(distDir, "index.html");
|
|
2852
2974
|
var IMAGE_CONTENT_TYPES = {
|
|
2853
2975
|
".avif": "image/avif",
|
|
2854
2976
|
".bmp": "image/bmp",
|
|
@@ -2905,7 +3027,7 @@ function createServer(options = {}) {
|
|
|
2905
3027
|
res.status(400).json({ error: "Expected absolute local file path." });
|
|
2906
3028
|
return;
|
|
2907
3029
|
}
|
|
2908
|
-
const contentType = IMAGE_CONTENT_TYPES[
|
|
3030
|
+
const contentType = IMAGE_CONTENT_TYPES[extname3(localPath).toLowerCase()];
|
|
2909
3031
|
if (!contentType) {
|
|
2910
3032
|
res.status(415).json({ error: "Unsupported image type." });
|
|
2911
3033
|
return;
|
|
@@ -2992,7 +3114,7 @@ function createServer(options = {}) {
|
|
|
2992
3114
|
res.status(404).json({ error: "File not found." });
|
|
2993
3115
|
}
|
|
2994
3116
|
});
|
|
2995
|
-
const hasFrontendAssets =
|
|
3117
|
+
const hasFrontendAssets = existsSync4(spaEntryFile);
|
|
2996
3118
|
if (hasFrontendAssets) {
|
|
2997
3119
|
app.use(express.static(distDir));
|
|
2998
3120
|
}
|
|
@@ -3065,7 +3187,7 @@ var program = new Command().name("codexui").description("Web interface for Codex
|
|
|
3065
3187
|
var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
3066
3188
|
async function readCliVersion() {
|
|
3067
3189
|
try {
|
|
3068
|
-
const packageJsonPath =
|
|
3190
|
+
const packageJsonPath = join6(__dirname2, "..", "package.json");
|
|
3069
3191
|
const raw = await readFile4(packageJsonPath, "utf8");
|
|
3070
3192
|
const parsed = JSON.parse(raw);
|
|
3071
3193
|
return typeof parsed.version === "string" ? parsed.version : "unknown";
|
|
@@ -3077,46 +3199,25 @@ function isTermuxRuntime() {
|
|
|
3077
3199
|
return Boolean(process.env.TERMUX_VERSION || process.env.PREFIX?.includes("/com.termux/"));
|
|
3078
3200
|
}
|
|
3079
3201
|
function canRun(command, args = []) {
|
|
3080
|
-
const result =
|
|
3081
|
-
return result
|
|
3202
|
+
const result = canRunCommand(command, args);
|
|
3203
|
+
return result;
|
|
3082
3204
|
}
|
|
3083
3205
|
function runOrFail(command, args, label) {
|
|
3084
|
-
const result =
|
|
3206
|
+
const result = spawnSyncCommand(command, args, { stdio: "inherit" });
|
|
3085
3207
|
if (result.status !== 0) {
|
|
3086
3208
|
throw new Error(`${label} failed with exit code ${String(result.status ?? -1)}`);
|
|
3087
3209
|
}
|
|
3088
3210
|
}
|
|
3089
3211
|
function runWithStatus(command, args) {
|
|
3090
|
-
const result =
|
|
3212
|
+
const result = spawnSyncCommand(command, args, { stdio: "inherit" });
|
|
3091
3213
|
return result.status ?? -1;
|
|
3092
3214
|
}
|
|
3093
|
-
function getUserNpmPrefix() {
|
|
3094
|
-
return join5(homedir3(), ".npm-global");
|
|
3095
|
-
}
|
|
3096
|
-
function resolveCodexCommand() {
|
|
3097
|
-
if (canRun("codex", ["--version"])) {
|
|
3098
|
-
return "codex";
|
|
3099
|
-
}
|
|
3100
|
-
const userCandidate = join5(getUserNpmPrefix(), "bin", "codex");
|
|
3101
|
-
if (existsSync4(userCandidate) && canRun(userCandidate, ["--version"])) {
|
|
3102
|
-
return userCandidate;
|
|
3103
|
-
}
|
|
3104
|
-
const prefix = process.env.PREFIX?.trim();
|
|
3105
|
-
if (!prefix) {
|
|
3106
|
-
return null;
|
|
3107
|
-
}
|
|
3108
|
-
const candidate = join5(prefix, "bin", "codex");
|
|
3109
|
-
if (existsSync4(candidate) && canRun(candidate, ["--version"])) {
|
|
3110
|
-
return candidate;
|
|
3111
|
-
}
|
|
3112
|
-
return null;
|
|
3113
|
-
}
|
|
3114
3215
|
function resolveCloudflaredCommand() {
|
|
3115
3216
|
if (canRun("cloudflared", ["--version"])) {
|
|
3116
3217
|
return "cloudflared";
|
|
3117
3218
|
}
|
|
3118
|
-
const localCandidate =
|
|
3119
|
-
if (
|
|
3219
|
+
const localCandidate = join6(homedir4(), ".local", "bin", "cloudflared");
|
|
3220
|
+
if (existsSync5(localCandidate) && canRun(localCandidate, ["--version"])) {
|
|
3120
3221
|
return localCandidate;
|
|
3121
3222
|
}
|
|
3122
3223
|
return null;
|
|
@@ -3169,9 +3270,9 @@ async function ensureCloudflaredInstalledLinux() {
|
|
|
3169
3270
|
if (!mappedArch) {
|
|
3170
3271
|
throw new Error(`cloudflared auto-install is not supported for Linux architecture: ${process.arch}`);
|
|
3171
3272
|
}
|
|
3172
|
-
const userBinDir =
|
|
3273
|
+
const userBinDir = join6(homedir4(), ".local", "bin");
|
|
3173
3274
|
mkdirSync(userBinDir, { recursive: true });
|
|
3174
|
-
const destination =
|
|
3275
|
+
const destination = join6(userBinDir, "cloudflared");
|
|
3175
3276
|
const downloadUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${mappedArch}`;
|
|
3176
3277
|
console.log("\ncloudflared not found. Installing to ~/.local/bin...\n");
|
|
3177
3278
|
await downloadFile(downloadUrl, destination);
|
|
@@ -3210,8 +3311,8 @@ async function resolveCloudflaredForTunnel() {
|
|
|
3210
3311
|
return ensureCloudflaredInstalledLinux();
|
|
3211
3312
|
}
|
|
3212
3313
|
function hasCodexAuth() {
|
|
3213
|
-
const codexHome = process.env.CODEX_HOME?.trim() ||
|
|
3214
|
-
return
|
|
3314
|
+
const codexHome = process.env.CODEX_HOME?.trim() || join6(homedir4(), ".codex");
|
|
3315
|
+
return existsSync5(join6(codexHome, "auth.json"));
|
|
3215
3316
|
}
|
|
3216
3317
|
function ensureCodexInstalled() {
|
|
3217
3318
|
let codexCommand = resolveCodexCommand();
|
|
@@ -3229,7 +3330,7 @@ function ensureCodexInstalled() {
|
|
|
3229
3330
|
Global npm install requires elevated permissions. Retrying with --prefix ${userPrefix}...
|
|
3230
3331
|
`);
|
|
3231
3332
|
runOrFail("npm", ["install", "-g", "--prefix", userPrefix, pkg], `${label} (user prefix)`);
|
|
3232
|
-
process.env.PATH = `${
|
|
3333
|
+
process.env.PATH = `${join6(userPrefix, "bin")}:${process.env.PATH ?? ""}`;
|
|
3233
3334
|
};
|
|
3234
3335
|
if (isTermuxRuntime()) {
|
|
3235
3336
|
console.log("\nCodex CLI not found. Installing Termux-compatible Codex CLI from npm...\n");
|
|
@@ -3370,8 +3471,8 @@ function listenWithFallback(server, startPort) {
|
|
|
3370
3471
|
});
|
|
3371
3472
|
}
|
|
3372
3473
|
function getCodexGlobalStatePath2() {
|
|
3373
|
-
const codexHome = process.env.CODEX_HOME?.trim() ||
|
|
3374
|
-
return
|
|
3474
|
+
const codexHome = process.env.CODEX_HOME?.trim() || join6(homedir4(), ".codex");
|
|
3475
|
+
return join6(codexHome, ".codex-global-state.json");
|
|
3375
3476
|
}
|
|
3376
3477
|
function normalizeUniqueStrings(value) {
|
|
3377
3478
|
if (!Array.isArray(value)) return [];
|