codexui-android 0.1.97 → 0.1.98
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/DirectoryHub-CAcNjRYF.css +1 -0
- package/dist/assets/DirectoryHub-D6h4lJqw.js +2 -0
- package/dist/assets/{ReviewPane-DuPX5OZA.css → ReviewPane-BJuXPOZb.css} +1 -1
- package/dist/assets/ReviewPane-BwO2MHvI.js +1 -0
- package/dist/assets/ThreadConversation-CsHUh1AP.js +39 -0
- package/dist/assets/{ThreadConversation-3WaRKicR.css → ThreadConversation-J3tJ8-9X.css} +1 -1
- package/dist/assets/ThreadTerminalPanel-BTHfTRnz.js +38 -0
- package/dist/assets/{ThreadTerminalPanel-CGTJQ1BI.css → ThreadTerminalPanel-pVx2Zv0U.css} +1 -1
- package/dist/assets/index-23FodFfK.js +64 -0
- package/dist/assets/index-Wz7G59hk.css +1 -0
- package/dist/index.html +2 -2
- package/dist-cli/index.js +259 -51
- package/dist-cli/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/ReviewPane-CUrlR8zS.js +0 -1
- package/dist/assets/SkillsHub-BxltnLjE.js +0 -2
- package/dist/assets/SkillsHub-CTnWejwn.css +0 -1
- package/dist/assets/ThreadConversation-BNvqi1ws.js +0 -39
- package/dist/assets/ThreadTerminalPanel-DjP4TSjB.js +0 -38
- package/dist/assets/index-Bqk3qCGk.js +0 -64
- package/dist/assets/index-CfRa3ma1.css +0 -1
package/dist/index.html
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
<link rel="icon" type="image/png" sizes="192x192" href="/icons/pwa-192x192.png" />
|
|
13
13
|
<link rel="apple-touch-icon" sizes="180x180" href="/icons/apple-touch-icon.png" />
|
|
14
14
|
<title>Codex Web</title>
|
|
15
|
-
<script type="module" crossorigin src="/assets/index-
|
|
16
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
15
|
+
<script type="module" crossorigin src="/assets/index-23FodFfK.js"></script>
|
|
16
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Wz7G59hk.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body class="bg-slate-950">
|
|
19
19
|
<div id="app"></div>
|
package/dist-cli/index.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { createServer as createServer2 } from "http";
|
|
5
|
-
import { chmodSync as chmodSync2, createWriteStream, existsSync as
|
|
5
|
+
import { chmodSync as chmodSync2, createWriteStream, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
|
|
6
6
|
import { readFile as readFile5, stat as stat7, writeFile as writeFile6 } from "fs/promises";
|
|
7
|
-
import { homedir as
|
|
8
|
-
import { isAbsolute as isAbsolute4, join as
|
|
7
|
+
import { homedir as homedir7, networkInterfaces } from "os";
|
|
8
|
+
import { isAbsolute as isAbsolute4, join as join10, resolve as resolve3 } from "path";
|
|
9
9
|
import { spawn as spawn5 } from "child_process";
|
|
10
10
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
11
11
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
12
|
-
import { dirname as
|
|
12
|
+
import { dirname as dirname6 } from "path";
|
|
13
13
|
import { get as httpsGet } from "https";
|
|
14
14
|
import { Command } from "commander";
|
|
15
15
|
import qrcode from "qrcode-terminal";
|
|
@@ -216,8 +216,8 @@ function parseApprovalPolicy(value) {
|
|
|
216
216
|
|
|
217
217
|
// src/server/httpServer.ts
|
|
218
218
|
import { fileURLToPath } from "url";
|
|
219
|
-
import { dirname as
|
|
220
|
-
import { existsSync as
|
|
219
|
+
import { dirname as dirname5, extname as extname3, isAbsolute as isAbsolute3, join as join9 } from "path";
|
|
220
|
+
import { existsSync as existsSync5 } from "fs";
|
|
221
221
|
import { writeFile as writeFile5, stat as stat6 } from "fs/promises";
|
|
222
222
|
import express from "express";
|
|
223
223
|
|
|
@@ -4830,13 +4830,42 @@ function asRecord5(value) {
|
|
|
4830
4830
|
function isInlineDataUrl(value) {
|
|
4831
4831
|
return /^data:/iu.test(value.trim());
|
|
4832
4832
|
}
|
|
4833
|
+
function inferImageMimeTypeFromBytes(bytes) {
|
|
4834
|
+
if (bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71 && bytes[4] === 13 && bytes[5] === 10 && bytes[6] === 26 && bytes[7] === 10) {
|
|
4835
|
+
return "image/png";
|
|
4836
|
+
}
|
|
4837
|
+
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
|
|
4838
|
+
return "image/jpeg";
|
|
4839
|
+
}
|
|
4840
|
+
if (bytes.length >= 12 && bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80) {
|
|
4841
|
+
return "image/webp";
|
|
4842
|
+
}
|
|
4843
|
+
if (bytes.length >= 6 && bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 56 && (bytes[4] === 55 || bytes[4] === 57) && bytes[5] === 97) {
|
|
4844
|
+
return "image/gif";
|
|
4845
|
+
}
|
|
4846
|
+
return null;
|
|
4847
|
+
}
|
|
4848
|
+
function inferImageMimeTypeFromBase64(value) {
|
|
4849
|
+
const compact = value.trim().replace(/\s+/gu, "");
|
|
4850
|
+
if (compact.length < 32 || !/^[A-Za-z0-9+/]+={0,2}$/u.test(compact)) return null;
|
|
4851
|
+
try {
|
|
4852
|
+
return inferImageMimeTypeFromBytes(Buffer.from(compact.slice(0, 64), "base64"));
|
|
4853
|
+
} catch {
|
|
4854
|
+
return null;
|
|
4855
|
+
}
|
|
4856
|
+
}
|
|
4833
4857
|
function normalizeBase64ImageDataUrl(value, mimeType) {
|
|
4834
4858
|
const trimmed = value.trim();
|
|
4835
4859
|
if (!trimmed) return null;
|
|
4836
|
-
if (isInlineDataUrl(trimmed))
|
|
4860
|
+
if (isInlineDataUrl(trimmed)) {
|
|
4861
|
+
return /^data:image\//iu.test(trimmed) ? trimmed : null;
|
|
4862
|
+
}
|
|
4837
4863
|
const compact = trimmed.replace(/\s+/gu, "");
|
|
4838
|
-
|
|
4839
|
-
return
|
|
4864
|
+
const inferredMimeType = inferImageMimeTypeFromBase64(compact);
|
|
4865
|
+
if (!inferredMimeType) return null;
|
|
4866
|
+
const normalizedMimeType = mimeType.trim().toLowerCase();
|
|
4867
|
+
const finalMimeType = normalizedMimeType.startsWith("image/") && normalizedMimeType !== "image/*" ? normalizedMimeType : inferredMimeType;
|
|
4868
|
+
return `data:${finalMimeType};base64,${compact}`;
|
|
4840
4869
|
}
|
|
4841
4870
|
function extensionFromMimeType(mimeType) {
|
|
4842
4871
|
const normalized = mimeType.trim().toLowerCase();
|
|
@@ -4888,6 +4917,30 @@ async function persistInlineDataUrlToLocalFile(dataUrl, baseName) {
|
|
|
4888
4917
|
function toLocalImageProxyUrl(path) {
|
|
4889
4918
|
return `/codex-local-image?path=${encodeURIComponent(path)}`;
|
|
4890
4919
|
}
|
|
4920
|
+
var INLINE_IMAGE_FIELD_NAMES = /* @__PURE__ */ new Set([
|
|
4921
|
+
"b64_json",
|
|
4922
|
+
"image",
|
|
4923
|
+
"image_url",
|
|
4924
|
+
"images",
|
|
4925
|
+
"result",
|
|
4926
|
+
"url"
|
|
4927
|
+
]);
|
|
4928
|
+
function isPotentialInlineImageField(fieldName) {
|
|
4929
|
+
return typeof fieldName === "string" && INLINE_IMAGE_FIELD_NAMES.has(fieldName);
|
|
4930
|
+
}
|
|
4931
|
+
async function sanitizeInlineImageString(value, context) {
|
|
4932
|
+
if (!isPotentialInlineImageField(context.fieldName)) {
|
|
4933
|
+
return { value, changed: false };
|
|
4934
|
+
}
|
|
4935
|
+
const dataUrl = normalizeBase64ImageDataUrl(value, "image/*");
|
|
4936
|
+
if (!dataUrl) return { value, changed: false };
|
|
4937
|
+
const localUrl = await persistInlineDataUrlToLocalFile(
|
|
4938
|
+
dataUrl,
|
|
4939
|
+
`inline-image-${context.turnId}-${context.itemId}-${context.fieldName}-${String(context.blockIndex)}`
|
|
4940
|
+
);
|
|
4941
|
+
if (!localUrl) return { value, changed: false };
|
|
4942
|
+
return { value: toLocalImageProxyUrl(localUrl), changed: true };
|
|
4943
|
+
}
|
|
4891
4944
|
async function sanitizeInlineUserContentBlock(block, context) {
|
|
4892
4945
|
const record = asRecord5(block);
|
|
4893
4946
|
if (!record) return block;
|
|
@@ -4896,10 +4949,16 @@ async function sanitizeInlineUserContentBlock(block, context) {
|
|
|
4896
4949
|
if (imageUrl && isInlineDataUrl(imageUrl)) {
|
|
4897
4950
|
const localUrl = await persistInlineDataUrlToLocalFile(imageUrl, `inline-image-${context.turnId}-${context.itemId}-${String(context.blockIndex)}`);
|
|
4898
4951
|
if (localUrl) {
|
|
4952
|
+
const nextRecord = { ...record };
|
|
4953
|
+
if (typeof record.url === "string") {
|
|
4954
|
+
nextRecord.url = toLocalImageProxyUrl(localUrl);
|
|
4955
|
+
}
|
|
4956
|
+
if (typeof record.image_url === "string") {
|
|
4957
|
+
nextRecord.image_url = toLocalImageProxyUrl(localUrl);
|
|
4958
|
+
}
|
|
4899
4959
|
return {
|
|
4900
|
-
...
|
|
4901
|
-
type: "image"
|
|
4902
|
-
url: toLocalImageProxyUrl(localUrl)
|
|
4960
|
+
...nextRecord,
|
|
4961
|
+
type: "image"
|
|
4903
4962
|
};
|
|
4904
4963
|
}
|
|
4905
4964
|
const target = toAttachmentLinkTarget(record, `inline-image/${context.turnId}/${context.itemId}/${String(context.blockIndex)}`);
|
|
@@ -4947,6 +5006,9 @@ async function sanitizeInlinePayloadDeep(value, context) {
|
|
|
4947
5006
|
if (maybeBlock !== value) {
|
|
4948
5007
|
return { value: maybeBlock, changed: true };
|
|
4949
5008
|
}
|
|
5009
|
+
if (typeof value === "string") {
|
|
5010
|
+
return sanitizeInlineImageString(value, context);
|
|
5011
|
+
}
|
|
4950
5012
|
if (Array.isArray(value)) {
|
|
4951
5013
|
let changed2 = false;
|
|
4952
5014
|
const nextArray = [];
|
|
@@ -4954,7 +5016,8 @@ async function sanitizeInlinePayloadDeep(value, context) {
|
|
|
4954
5016
|
const nested = await sanitizeInlinePayloadDeep(value[index], {
|
|
4955
5017
|
turnId: context.turnId,
|
|
4956
5018
|
itemId: context.itemId,
|
|
4957
|
-
blockIndex: index
|
|
5019
|
+
blockIndex: index,
|
|
5020
|
+
fieldName: context.fieldName
|
|
4958
5021
|
});
|
|
4959
5022
|
if (nested.changed) changed2 = true;
|
|
4960
5023
|
nextArray.push(nested.value);
|
|
@@ -4969,7 +5032,8 @@ async function sanitizeInlinePayloadDeep(value, context) {
|
|
|
4969
5032
|
const nested = await sanitizeInlinePayloadDeep(nestedValue, {
|
|
4970
5033
|
turnId: context.turnId,
|
|
4971
5034
|
itemId: context.itemId,
|
|
4972
|
-
blockIndex: context.blockIndex
|
|
5035
|
+
blockIndex: context.blockIndex,
|
|
5036
|
+
fieldName: key
|
|
4973
5037
|
});
|
|
4974
5038
|
if (nested.changed) changed = true;
|
|
4975
5039
|
nextRecord[key] = nested.value;
|
|
@@ -6651,6 +6715,46 @@ async function proxyTranscribe(body, contentType, authToken, accountId) {
|
|
|
6651
6715
|
}
|
|
6652
6716
|
return result;
|
|
6653
6717
|
}
|
|
6718
|
+
function parseConnectorLogoUrl(rawUrl) {
|
|
6719
|
+
const trimmed = rawUrl.trim();
|
|
6720
|
+
if (!trimmed.startsWith("connectors://")) return null;
|
|
6721
|
+
const rest = trimmed.slice("connectors://".length);
|
|
6722
|
+
const connectorId = (rest.split(/[/?#]/u)[0] ?? "").trim();
|
|
6723
|
+
if (!connectorId) return null;
|
|
6724
|
+
const query = rest.includes("?") ? rest.slice(rest.indexOf("?") + 1).split("#")[0] ?? "" : "";
|
|
6725
|
+
const theme = new URLSearchParams(query).get("theme")?.toLowerCase() === "dark" ? "dark" : "light";
|
|
6726
|
+
return { connectorId, theme };
|
|
6727
|
+
}
|
|
6728
|
+
async function fetchConnectorLogo(rawUrl) {
|
|
6729
|
+
const parsed = parseConnectorLogoUrl(rawUrl);
|
|
6730
|
+
if (!parsed) throw new Error("Unsupported connector logo URL");
|
|
6731
|
+
const auth = await readCodexAuth();
|
|
6732
|
+
if (!auth) throw new Error("No auth token available for connector logo");
|
|
6733
|
+
const endpoint = `https://chatgpt.com/backend-api/aip/connectors/${encodeURIComponent(parsed.connectorId)}/logo?theme=${parsed.theme}`;
|
|
6734
|
+
const response = await fetch(endpoint, {
|
|
6735
|
+
headers: {
|
|
6736
|
+
Authorization: `Bearer ${auth.accessToken}`,
|
|
6737
|
+
originator: "Codex Desktop",
|
|
6738
|
+
"User-Agent": `Codex Desktop/0.1.0 (${process.platform}; ${process.arch})`,
|
|
6739
|
+
...auth.accountId ? { "ChatGPT-Account-Id": auth.accountId } : {}
|
|
6740
|
+
},
|
|
6741
|
+
signal: AbortSignal.timeout(1e4)
|
|
6742
|
+
});
|
|
6743
|
+
if (!response.ok) throw new Error(`Connector logo fetch failed (${response.status})`);
|
|
6744
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
6745
|
+
if (contentType.includes("application/json")) {
|
|
6746
|
+
const payload = asRecord5(await response.json());
|
|
6747
|
+
const body = asRecord5(payload?.body);
|
|
6748
|
+
const base64 = readNonEmptyString(body?.base64);
|
|
6749
|
+
const nestedContentType = readNonEmptyString(body?.contentType) ?? readNonEmptyString(body?.content_type);
|
|
6750
|
+
if (!base64 || !nestedContentType) throw new Error("Connector logo response was missing image data");
|
|
6751
|
+
return { contentType: nestedContentType, body: Buffer.from(base64, "base64") };
|
|
6752
|
+
}
|
|
6753
|
+
return {
|
|
6754
|
+
contentType: contentType || "image/png",
|
|
6755
|
+
body: Buffer.from(await response.arrayBuffer())
|
|
6756
|
+
};
|
|
6757
|
+
}
|
|
6654
6758
|
var STREAM_EVENT_BUFFER_LIMIT = 400;
|
|
6655
6759
|
var MERGEABLE_ITEM_TYPES = /* @__PURE__ */ new Set([
|
|
6656
6760
|
"commandExecution",
|
|
@@ -7844,6 +7948,23 @@ function createCodexBridgeMiddleware() {
|
|
|
7844
7948
|
res.end(upstream.body);
|
|
7845
7949
|
return;
|
|
7846
7950
|
}
|
|
7951
|
+
if (req.method === "GET" && url.pathname === "/codex-api/connector-logo") {
|
|
7952
|
+
const src = url.searchParams.get("src")?.trim() ?? "";
|
|
7953
|
+
if (!src) {
|
|
7954
|
+
setJson4(res, 400, { error: "Missing src" });
|
|
7955
|
+
return;
|
|
7956
|
+
}
|
|
7957
|
+
try {
|
|
7958
|
+
const logo = await fetchConnectorLogo(src);
|
|
7959
|
+
res.statusCode = 200;
|
|
7960
|
+
res.setHeader("Content-Type", logo.contentType);
|
|
7961
|
+
res.setHeader("Cache-Control", "private, max-age=3600");
|
|
7962
|
+
res.end(logo.body);
|
|
7963
|
+
} catch (error) {
|
|
7964
|
+
setJson4(res, 502, { error: getErrorMessage5(error, "Failed to fetch connector logo") });
|
|
7965
|
+
}
|
|
7966
|
+
return;
|
|
7967
|
+
}
|
|
7847
7968
|
if (req.method === "POST" && url.pathname === "/codex-api/server-requests/respond") {
|
|
7848
7969
|
const payload = await readJsonBody(req);
|
|
7849
7970
|
await appServer.respondToServerRequest(payload);
|
|
@@ -8517,7 +8638,13 @@ data: ${JSON.stringify({ ok: true })}
|
|
|
8517
8638
|
|
|
8518
8639
|
// src/server/authMiddleware.ts
|
|
8519
8640
|
import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
8641
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
8642
|
+
import { homedir as homedir6 } from "os";
|
|
8643
|
+
import { dirname as dirname3, join as join7 } from "path";
|
|
8520
8644
|
var TOKEN_COOKIE = "portal_session";
|
|
8645
|
+
var SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
8646
|
+
var SESSION_STORE_FILE = "webui-auth-sessions.json";
|
|
8647
|
+
var MAX_PERSISTED_TOKENS = 128;
|
|
8521
8648
|
function constantTimeCompare(a, b) {
|
|
8522
8649
|
const bufA = Buffer.from(a);
|
|
8523
8650
|
const bufB = Buffer.from(b);
|
|
@@ -8565,6 +8692,69 @@ function isTrustedTailscaleIPv6(remote) {
|
|
|
8565
8692
|
function isTrustedTailscaleRemote(remote) {
|
|
8566
8693
|
return isTrustedTailscaleIPv4(remote) || isTrustedTailscaleIPv6(remote);
|
|
8567
8694
|
}
|
|
8695
|
+
function getCodexHomeDir4() {
|
|
8696
|
+
const codexHome = process.env.CODEX_HOME?.trim();
|
|
8697
|
+
return codexHome && codexHome.length > 0 ? codexHome : join7(homedir6(), ".codex");
|
|
8698
|
+
}
|
|
8699
|
+
function getSessionStorePath() {
|
|
8700
|
+
return join7(getCodexHomeDir4(), SESSION_STORE_FILE);
|
|
8701
|
+
}
|
|
8702
|
+
function readPersistedSessions() {
|
|
8703
|
+
const sessionStorePath = getSessionStorePath();
|
|
8704
|
+
if (!existsSync4(sessionStorePath)) return /* @__PURE__ */ new Map();
|
|
8705
|
+
try {
|
|
8706
|
+
const raw = readFileSync3(sessionStorePath, "utf8");
|
|
8707
|
+
const parsed = JSON.parse(raw);
|
|
8708
|
+
const now = Date.now();
|
|
8709
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
8710
|
+
for (const entry of parsed.tokens ?? []) {
|
|
8711
|
+
const token = typeof entry?.value === "string" ? entry.value : "";
|
|
8712
|
+
const expiresAt = typeof entry?.expiresAt === "number" ? entry.expiresAt : 0;
|
|
8713
|
+
if (!token || !Number.isFinite(expiresAt) || expiresAt <= now) continue;
|
|
8714
|
+
sessions.set(token, expiresAt);
|
|
8715
|
+
}
|
|
8716
|
+
return sessions;
|
|
8717
|
+
} catch {
|
|
8718
|
+
return /* @__PURE__ */ new Map();
|
|
8719
|
+
}
|
|
8720
|
+
}
|
|
8721
|
+
function persistSessions(validTokens) {
|
|
8722
|
+
const sessionStorePath = getSessionStorePath();
|
|
8723
|
+
mkdirSync(dirname3(sessionStorePath), { recursive: true });
|
|
8724
|
+
const tokens = Array.from(validTokens.entries()).sort((left, right) => right[1] - left[1]).slice(0, MAX_PERSISTED_TOKENS).map(([value, expiresAt]) => ({ value, expiresAt }));
|
|
8725
|
+
const tmpPath = `${sessionStorePath}.tmp`;
|
|
8726
|
+
writeFileSync2(tmpPath, `${JSON.stringify({ tokens }, null, 2)}
|
|
8727
|
+
`, { encoding: "utf8", mode: 384 });
|
|
8728
|
+
renameSync(tmpPath, sessionStorePath);
|
|
8729
|
+
}
|
|
8730
|
+
function tryPersistSessions(validTokens) {
|
|
8731
|
+
try {
|
|
8732
|
+
persistSessions(validTokens);
|
|
8733
|
+
} catch (error) {
|
|
8734
|
+
console.warn("[auth] failed to persist login sessions:", error);
|
|
8735
|
+
}
|
|
8736
|
+
}
|
|
8737
|
+
function pruneExpiredSessions(validTokens) {
|
|
8738
|
+
const now = Date.now();
|
|
8739
|
+
let changed = false;
|
|
8740
|
+
for (const [token, expiresAt] of validTokens.entries()) {
|
|
8741
|
+
if (expiresAt > now) continue;
|
|
8742
|
+
validTokens.delete(token);
|
|
8743
|
+
changed = true;
|
|
8744
|
+
}
|
|
8745
|
+
return changed;
|
|
8746
|
+
}
|
|
8747
|
+
function buildSessionCookie(token, expiresAt) {
|
|
8748
|
+
const maxAgeSeconds = Math.max(0, Math.floor((expiresAt - Date.now()) / 1e3));
|
|
8749
|
+
return [
|
|
8750
|
+
`${TOKEN_COOKIE}=${token}`,
|
|
8751
|
+
"Path=/",
|
|
8752
|
+
"HttpOnly",
|
|
8753
|
+
"SameSite=Lax",
|
|
8754
|
+
`Max-Age=${String(maxAgeSeconds)}`,
|
|
8755
|
+
`Expires=${new Date(expiresAt).toUTCString()}`
|
|
8756
|
+
].join("; ");
|
|
8757
|
+
}
|
|
8568
8758
|
function isAuthorizedByRequestLike(remoteAddress, hostHeader, cookieHeader, validTokens) {
|
|
8569
8759
|
const remote = remoteAddress ?? "";
|
|
8570
8760
|
if (isLocalhostRemote(remote) && isLocalhostHost(hostHeader ?? "")) {
|
|
@@ -8575,7 +8765,9 @@ function isAuthorizedByRequestLike(remoteAddress, hostHeader, cookieHeader, vali
|
|
|
8575
8765
|
}
|
|
8576
8766
|
const cookies = parseCookies(cookieHeader);
|
|
8577
8767
|
const token = cookies[TOKEN_COOKIE];
|
|
8578
|
-
|
|
8768
|
+
if (!token) return false;
|
|
8769
|
+
const expiresAt = validTokens.get(token);
|
|
8770
|
+
return typeof expiresAt === "number" && expiresAt > Date.now();
|
|
8579
8771
|
}
|
|
8580
8772
|
var LOGIN_PAGE_HTML = `<!DOCTYPE html>
|
|
8581
8773
|
<html lang="en">
|
|
@@ -8619,8 +8811,14 @@ form.addEventListener('submit',async e=>{
|
|
|
8619
8811
|
</body>
|
|
8620
8812
|
</html>`;
|
|
8621
8813
|
function createAuthSession(password) {
|
|
8622
|
-
const validTokens =
|
|
8814
|
+
const validTokens = readPersistedSessions();
|
|
8815
|
+
if (pruneExpiredSessions(validTokens)) {
|
|
8816
|
+
tryPersistSessions(validTokens);
|
|
8817
|
+
}
|
|
8623
8818
|
const middleware = (req, res, next) => {
|
|
8819
|
+
if (pruneExpiredSessions(validTokens)) {
|
|
8820
|
+
tryPersistSessions(validTokens);
|
|
8821
|
+
}
|
|
8624
8822
|
if (isAuthorizedByRequestLike(req.socket.remoteAddress, req.headers.host, req.headers.cookie, validTokens)) {
|
|
8625
8823
|
next();
|
|
8626
8824
|
return;
|
|
@@ -8632,19 +8830,27 @@ function createAuthSession(password) {
|
|
|
8632
8830
|
body += chunk;
|
|
8633
8831
|
});
|
|
8634
8832
|
req.on("end", () => {
|
|
8833
|
+
let parsed;
|
|
8834
|
+
try {
|
|
8835
|
+
parsed = JSON.parse(body);
|
|
8836
|
+
} catch {
|
|
8837
|
+
res.status(400).json({ error: "Invalid request body" });
|
|
8838
|
+
return;
|
|
8839
|
+
}
|
|
8840
|
+
const provided = typeof parsed.password === "string" ? parsed.password : "";
|
|
8841
|
+
if (!constantTimeCompare(provided, password)) {
|
|
8842
|
+
res.status(401).json({ error: "Invalid password" });
|
|
8843
|
+
return;
|
|
8844
|
+
}
|
|
8635
8845
|
try {
|
|
8636
|
-
const parsed = JSON.parse(body);
|
|
8637
|
-
const provided = typeof parsed.password === "string" ? parsed.password : "";
|
|
8638
|
-
if (!constantTimeCompare(provided, password)) {
|
|
8639
|
-
res.status(401).json({ error: "Invalid password" });
|
|
8640
|
-
return;
|
|
8641
|
-
}
|
|
8642
8846
|
const token = randomBytes2(32).toString("hex");
|
|
8643
|
-
|
|
8644
|
-
|
|
8847
|
+
const expiresAt = Date.now() + SESSION_TTL_MS;
|
|
8848
|
+
validTokens.set(token, expiresAt);
|
|
8849
|
+
tryPersistSessions(validTokens);
|
|
8850
|
+
res.setHeader("Set-Cookie", buildSessionCookie(token, expiresAt));
|
|
8645
8851
|
res.json({ ok: true });
|
|
8646
8852
|
} catch {
|
|
8647
|
-
res.status(
|
|
8853
|
+
res.status(500).json({ error: "Failed to create login session" });
|
|
8648
8854
|
}
|
|
8649
8855
|
});
|
|
8650
8856
|
return;
|
|
@@ -8653,8 +8859,10 @@ function createAuthSession(password) {
|
|
|
8653
8859
|
const provided = req.path.slice("/password=".length);
|
|
8654
8860
|
if (constantTimeCompare(provided, password)) {
|
|
8655
8861
|
const token = randomBytes2(32).toString("hex");
|
|
8656
|
-
|
|
8657
|
-
|
|
8862
|
+
const expiresAt = Date.now() + SESSION_TTL_MS;
|
|
8863
|
+
validTokens.set(token, expiresAt);
|
|
8864
|
+
tryPersistSessions(validTokens);
|
|
8865
|
+
res.setHeader("Set-Cookie", buildSessionCookie(token, expiresAt));
|
|
8658
8866
|
res.redirect(302, "/");
|
|
8659
8867
|
return;
|
|
8660
8868
|
}
|
|
@@ -8669,7 +8877,7 @@ function createAuthSession(password) {
|
|
|
8669
8877
|
}
|
|
8670
8878
|
|
|
8671
8879
|
// src/server/localBrowseUi.ts
|
|
8672
|
-
import { dirname as
|
|
8880
|
+
import { dirname as dirname4, extname as extname2, join as join8 } from "path";
|
|
8673
8881
|
import { open, readFile as readFile4, readdir as readdir3, stat as stat5 } from "fs/promises";
|
|
8674
8882
|
var TEXT_EDITABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
8675
8883
|
".txt",
|
|
@@ -8817,7 +9025,7 @@ function escapeForInlineScriptString(value) {
|
|
|
8817
9025
|
async function getDirectoryItems(localPath) {
|
|
8818
9026
|
const entries = await readdir3(localPath, { withFileTypes: true });
|
|
8819
9027
|
const withMeta = await Promise.all(entries.map(async (entry) => {
|
|
8820
|
-
const entryPath =
|
|
9028
|
+
const entryPath = join8(localPath, entry.name);
|
|
8821
9029
|
const entryStat = await stat5(entryPath);
|
|
8822
9030
|
const editable = !entry.isDirectory() && await isTextEditableFile(entryPath);
|
|
8823
9031
|
return {
|
|
@@ -8838,7 +9046,7 @@ async function getDirectoryItems(localPath) {
|
|
|
8838
9046
|
function projectCreationTargetPath(parentPath, newProjectName) {
|
|
8839
9047
|
const normalizedName = normalizeNewProjectName(newProjectName);
|
|
8840
9048
|
if (!normalizedName) return "";
|
|
8841
|
-
return
|
|
9049
|
+
return join8(parentPath, normalizedName);
|
|
8842
9050
|
}
|
|
8843
9051
|
function projectCreationButtonLabel(newProjectName) {
|
|
8844
9052
|
const normalizedName = normalizeNewProjectName(newProjectName);
|
|
@@ -8867,7 +9075,7 @@ async function getLocalDirectoryListing(localPath, options = {}) {
|
|
|
8867
9075
|
const entries = await readdir3(localPath, { withFileTypes: true });
|
|
8868
9076
|
const directories = entries.filter((entry) => entry.isDirectory()).map((entry) => ({
|
|
8869
9077
|
name: entry.name,
|
|
8870
|
-
path:
|
|
9078
|
+
path: join8(localPath, entry.name)
|
|
8871
9079
|
})).filter((entry) => options.showHidden === true || !isHiddenName(entry.name)).sort((a, b) => {
|
|
8872
9080
|
const aHidden = isHiddenName(a.name);
|
|
8873
9081
|
const bHidden = isHiddenName(b.name);
|
|
@@ -8876,14 +9084,14 @@ async function getLocalDirectoryListing(localPath, options = {}) {
|
|
|
8876
9084
|
});
|
|
8877
9085
|
return {
|
|
8878
9086
|
path: localPath,
|
|
8879
|
-
parentPath:
|
|
9087
|
+
parentPath: dirname4(localPath),
|
|
8880
9088
|
entries: directories
|
|
8881
9089
|
};
|
|
8882
9090
|
}
|
|
8883
9091
|
async function createDirectoryListingHtml(localPath, options) {
|
|
8884
9092
|
const newProjectName = normalizeNewProjectName(options?.newProjectName ?? "");
|
|
8885
9093
|
const items = await getDirectoryItems(localPath);
|
|
8886
|
-
const parentPath =
|
|
9094
|
+
const parentPath = dirname4(localPath);
|
|
8887
9095
|
const rows = items.map((item) => {
|
|
8888
9096
|
const suffix = item.isDirectory ? "/" : "";
|
|
8889
9097
|
const editAction = item.editable ? ` <a class="icon-btn" aria-label="Edit ${escapeHtml2(item.name)}" href="${escapeHtml2(toEditHref(item.path, newProjectName))}" title="Edit">\u270F\uFE0F</a>` : "";
|
|
@@ -8989,7 +9197,7 @@ async function createDirectoryListingHtml(localPath, options) {
|
|
|
8989
9197
|
}
|
|
8990
9198
|
async function createTextEditorHtml(localPath) {
|
|
8991
9199
|
const content = await readFile4(localPath, "utf8");
|
|
8992
|
-
const parentPath =
|
|
9200
|
+
const parentPath = dirname4(localPath);
|
|
8993
9201
|
const language = languageForPath(localPath);
|
|
8994
9202
|
const safeContentLiteral = escapeForInlineScriptString(content);
|
|
8995
9203
|
return `<!doctype html>
|
|
@@ -9058,9 +9266,9 @@ async function createTextEditorHtml(localPath) {
|
|
|
9058
9266
|
|
|
9059
9267
|
// src/server/httpServer.ts
|
|
9060
9268
|
import { WebSocketServer } from "ws";
|
|
9061
|
-
var __dirname =
|
|
9062
|
-
var distDir =
|
|
9063
|
-
var spaEntryFile =
|
|
9269
|
+
var __dirname = dirname5(fileURLToPath(import.meta.url));
|
|
9270
|
+
var distDir = join9(__dirname, "..", "dist");
|
|
9271
|
+
var spaEntryFile = join9(distDir, "index.html");
|
|
9064
9272
|
var IMAGE_CONTENT_TYPES = {
|
|
9065
9273
|
".avif": "image/avif",
|
|
9066
9274
|
".bmp": "image/bmp",
|
|
@@ -9229,7 +9437,7 @@ function createServer(options = {}) {
|
|
|
9229
9437
|
res.status(404).json({ error: "File not found." });
|
|
9230
9438
|
}
|
|
9231
9439
|
});
|
|
9232
|
-
const hasFrontendAssets =
|
|
9440
|
+
const hasFrontendAssets = existsSync5(spaEntryFile);
|
|
9233
9441
|
if (hasFrontendAssets) {
|
|
9234
9442
|
app.use(express.static(distDir));
|
|
9235
9443
|
}
|
|
@@ -9299,26 +9507,26 @@ function generatePassword() {
|
|
|
9299
9507
|
|
|
9300
9508
|
// src/cli/index.ts
|
|
9301
9509
|
var program = new Command().name("codexui").description("Web interface for Codex app-server");
|
|
9302
|
-
var __dirname2 =
|
|
9510
|
+
var __dirname2 = dirname6(fileURLToPath2(import.meta.url));
|
|
9303
9511
|
var hasPromptedCloudflaredInstall = false;
|
|
9304
9512
|
function getCodexHomePath() {
|
|
9305
|
-
return process.env.CODEX_HOME?.trim() ||
|
|
9513
|
+
return process.env.CODEX_HOME?.trim() || join10(homedir7(), ".codex");
|
|
9306
9514
|
}
|
|
9307
9515
|
function getCloudflaredPromptMarkerPath() {
|
|
9308
|
-
return
|
|
9516
|
+
return join10(getCodexHomePath(), ".cloudflared-install-prompted");
|
|
9309
9517
|
}
|
|
9310
9518
|
function hasPromptedCloudflaredInstallPersisted() {
|
|
9311
|
-
return
|
|
9519
|
+
return existsSync6(getCloudflaredPromptMarkerPath());
|
|
9312
9520
|
}
|
|
9313
9521
|
async function persistCloudflaredInstallPrompted() {
|
|
9314
9522
|
const codexHome = getCodexHomePath();
|
|
9315
|
-
|
|
9523
|
+
mkdirSync2(codexHome, { recursive: true });
|
|
9316
9524
|
await writeFile6(getCloudflaredPromptMarkerPath(), `${Date.now()}
|
|
9317
9525
|
`, "utf8");
|
|
9318
9526
|
}
|
|
9319
9527
|
async function readCliVersion() {
|
|
9320
9528
|
try {
|
|
9321
|
-
const packageJsonPath =
|
|
9529
|
+
const packageJsonPath = join10(__dirname2, "..", "package.json");
|
|
9322
9530
|
const raw = await readFile5(packageJsonPath, "utf8");
|
|
9323
9531
|
const parsed = JSON.parse(raw);
|
|
9324
9532
|
return typeof parsed.version === "string" ? parsed.version : "unknown";
|
|
@@ -9343,8 +9551,8 @@ function resolveCloudflaredCommand() {
|
|
|
9343
9551
|
if (canRunCommand("cloudflared", ["--version"])) {
|
|
9344
9552
|
return "cloudflared";
|
|
9345
9553
|
}
|
|
9346
|
-
const localCandidate =
|
|
9347
|
-
if (
|
|
9554
|
+
const localCandidate = join10(homedir7(), ".local", "bin", "cloudflared");
|
|
9555
|
+
if (existsSync6(localCandidate) && canRunCommand(localCandidate, ["--version"])) {
|
|
9348
9556
|
return localCandidate;
|
|
9349
9557
|
}
|
|
9350
9558
|
return null;
|
|
@@ -9397,9 +9605,9 @@ async function ensureCloudflaredInstalledLinux() {
|
|
|
9397
9605
|
if (!mappedArch) {
|
|
9398
9606
|
throw new Error(`cloudflared auto-install is not supported for Linux architecture: ${process.arch}`);
|
|
9399
9607
|
}
|
|
9400
|
-
const userBinDir =
|
|
9401
|
-
|
|
9402
|
-
const destination =
|
|
9608
|
+
const userBinDir = join10(homedir7(), ".local", "bin");
|
|
9609
|
+
mkdirSync2(userBinDir, { recursive: true });
|
|
9610
|
+
const destination = join10(userBinDir, "cloudflared");
|
|
9403
9611
|
const downloadUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${mappedArch}`;
|
|
9404
9612
|
console.log("\ncloudflared not found. Installing to ~/.local/bin...\n");
|
|
9405
9613
|
await downloadFile(downloadUrl, destination);
|
|
@@ -9450,7 +9658,7 @@ async function resolveCloudflaredForTunnel() {
|
|
|
9450
9658
|
}
|
|
9451
9659
|
function hasCodexAuth() {
|
|
9452
9660
|
const codexHome = getCodexHomePath();
|
|
9453
|
-
return
|
|
9661
|
+
return existsSync6(join10(codexHome, "auth.json"));
|
|
9454
9662
|
}
|
|
9455
9663
|
function ensureCodexInstalled() {
|
|
9456
9664
|
let codexCommand = resolveCodexCommand();
|
|
@@ -9640,7 +9848,7 @@ function listenWithFallback(server, startPort) {
|
|
|
9640
9848
|
}
|
|
9641
9849
|
function getCodexGlobalStatePath2() {
|
|
9642
9850
|
const codexHome = getCodexHomePath();
|
|
9643
|
-
return
|
|
9851
|
+
return join10(codexHome, ".codex-global-state.json");
|
|
9644
9852
|
}
|
|
9645
9853
|
function normalizeUniqueStrings(value) {
|
|
9646
9854
|
if (!Array.isArray(value)) return [];
|