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/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-Bqk3qCGk.js"></script>
16
- <link rel="stylesheet" crossorigin href="/assets/index-CfRa3ma1.css">
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 existsSync5, mkdirSync } from "fs";
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 homedir6, networkInterfaces } from "os";
8
- import { isAbsolute as isAbsolute4, join as join9, resolve as resolve3 } from "path";
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 dirname5 } from "path";
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 dirname4, extname as extname3, isAbsolute as isAbsolute3, join as join8 } from "path";
220
- import { existsSync as existsSync4 } from "fs";
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)) return trimmed;
4860
+ if (isInlineDataUrl(trimmed)) {
4861
+ return /^data:image\//iu.test(trimmed) ? trimmed : null;
4862
+ }
4837
4863
  const compact = trimmed.replace(/\s+/gu, "");
4838
- if (!/^[A-Za-z0-9+/]+={0,2}$/u.test(compact)) return null;
4839
- return `data:${mimeType};base64,${compact}`;
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
- ...record,
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
- return Boolean(token && validTokens.has(token));
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 = /* @__PURE__ */ new Set();
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
- validTokens.add(token);
8644
- res.setHeader("Set-Cookie", `${TOKEN_COOKIE}=${token}; Path=/; HttpOnly; SameSite=Strict`);
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(400).json({ error: "Invalid request body" });
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
- validTokens.add(token);
8657
- res.setHeader("Set-Cookie", `${TOKEN_COOKIE}=${token}; Path=/; HttpOnly; SameSite=Strict`);
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 dirname3, extname as extname2, join as join7 } from "path";
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 = join7(localPath, entry.name);
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 join7(parentPath, normalizedName);
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: join7(localPath, entry.name)
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: dirname3(localPath),
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 = dirname3(localPath);
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 = dirname3(localPath);
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 = dirname4(fileURLToPath(import.meta.url));
9062
- var distDir = join8(__dirname, "..", "dist");
9063
- var spaEntryFile = join8(distDir, "index.html");
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 = existsSync4(spaEntryFile);
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 = dirname5(fileURLToPath2(import.meta.url));
9510
+ var __dirname2 = dirname6(fileURLToPath2(import.meta.url));
9303
9511
  var hasPromptedCloudflaredInstall = false;
9304
9512
  function getCodexHomePath() {
9305
- return process.env.CODEX_HOME?.trim() || join9(homedir6(), ".codex");
9513
+ return process.env.CODEX_HOME?.trim() || join10(homedir7(), ".codex");
9306
9514
  }
9307
9515
  function getCloudflaredPromptMarkerPath() {
9308
- return join9(getCodexHomePath(), ".cloudflared-install-prompted");
9516
+ return join10(getCodexHomePath(), ".cloudflared-install-prompted");
9309
9517
  }
9310
9518
  function hasPromptedCloudflaredInstallPersisted() {
9311
- return existsSync5(getCloudflaredPromptMarkerPath());
9519
+ return existsSync6(getCloudflaredPromptMarkerPath());
9312
9520
  }
9313
9521
  async function persistCloudflaredInstallPrompted() {
9314
9522
  const codexHome = getCodexHomePath();
9315
- mkdirSync(codexHome, { recursive: true });
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 = join9(__dirname2, "..", "package.json");
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 = join9(homedir6(), ".local", "bin", "cloudflared");
9347
- if (existsSync5(localCandidate) && canRunCommand(localCandidate, ["--version"])) {
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 = join9(homedir6(), ".local", "bin");
9401
- mkdirSync(userBinDir, { recursive: true });
9402
- const destination = join9(userBinDir, "cloudflared");
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 existsSync5(join9(codexHome, "auth.json"));
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 join9(codexHome, ".codex-global-state.json");
9851
+ return join10(codexHome, ".codex-global-state.json");
9644
9852
  }
9645
9853
  function normalizeUniqueStrings(value) {
9646
9854
  if (!Array.isArray(value)) return [];