codexapp 0.1.25 → 0.1.27

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-cli/index.js CHANGED
@@ -2,22 +2,21 @@
2
2
 
3
3
  // src/cli/index.ts
4
4
  import { createServer as createServer2 } from "http";
5
- import { chmodSync, createWriteStream, existsSync as existsSync2, mkdirSync } from "fs";
6
- import { readFile as readFile2 } from "fs/promises";
5
+ import { existsSync as existsSync2 } from "fs";
6
+ import { readFile as readFile3 } from "fs/promises";
7
7
  import { homedir as homedir2 } from "os";
8
- import { join as join3 } from "path";
8
+ import { join as join4 } from "path";
9
9
  import { spawn as spawn2, spawnSync } from "child_process";
10
10
  import { fileURLToPath as fileURLToPath2 } from "url";
11
- import { dirname as dirname2 } from "path";
12
- import { get as httpsGet } from "https";
11
+ import { dirname as dirname3 } from "path";
13
12
  import { Command } from "commander";
14
13
  import qrcode from "qrcode-terminal";
15
14
 
16
15
  // src/server/httpServer.ts
17
16
  import { fileURLToPath } from "url";
18
- import { dirname, extname, isAbsolute as isAbsolute2, join as join2 } from "path";
17
+ import { dirname as dirname2, extname as extname2, isAbsolute as isAbsolute2, join as join3 } from "path";
19
18
  import { existsSync } from "fs";
20
- import { readdir as readdir2, stat as stat2 } from "fs/promises";
19
+ import { writeFile as writeFile2, stat as stat3 } from "fs/promises";
21
20
  import express from "express";
22
21
 
23
22
  // src/server/codexAppServerBridge.ts
@@ -1578,32 +1577,74 @@ function createAuthSession(password) {
1578
1577
  };
1579
1578
  }
1580
1579
 
1581
- // src/server/httpServer.ts
1582
- import { WebSocketServer } from "ws";
1583
- var __dirname = dirname(fileURLToPath(import.meta.url));
1584
- var distDir = join2(__dirname, "..", "dist");
1585
- var spaEntryFile = join2(distDir, "index.html");
1586
- var IMAGE_CONTENT_TYPES = {
1587
- ".avif": "image/avif",
1588
- ".bmp": "image/bmp",
1589
- ".gif": "image/gif",
1590
- ".jpeg": "image/jpeg",
1591
- ".jpg": "image/jpeg",
1592
- ".png": "image/png",
1593
- ".svg": "image/svg+xml",
1594
- ".webp": "image/webp"
1595
- };
1596
- function normalizeLocalImagePath(rawPath) {
1597
- const trimmed = rawPath.trim();
1598
- if (!trimmed) return "";
1599
- if (trimmed.startsWith("file://")) {
1600
- try {
1601
- return decodeURIComponent(trimmed.replace(/^file:\/\//u, ""));
1602
- } catch {
1603
- return trimmed.replace(/^file:\/\//u, "");
1604
- }
1580
+ // src/server/localBrowseUi.ts
1581
+ import { dirname, extname, join as join2 } from "path";
1582
+ import { readFile as readFile2, readdir as readdir2, stat as stat2 } from "fs/promises";
1583
+ var TEXT_EDITABLE_EXTENSIONS = /* @__PURE__ */ new Set([
1584
+ ".txt",
1585
+ ".md",
1586
+ ".json",
1587
+ ".js",
1588
+ ".ts",
1589
+ ".tsx",
1590
+ ".jsx",
1591
+ ".css",
1592
+ ".scss",
1593
+ ".html",
1594
+ ".htm",
1595
+ ".xml",
1596
+ ".yml",
1597
+ ".yaml",
1598
+ ".log",
1599
+ ".csv",
1600
+ ".env",
1601
+ ".py",
1602
+ ".sh",
1603
+ ".toml",
1604
+ ".ini",
1605
+ ".conf",
1606
+ ".sql"
1607
+ ]);
1608
+ function languageForPath(pathValue) {
1609
+ const extension = extname(pathValue).toLowerCase();
1610
+ switch (extension) {
1611
+ case ".js":
1612
+ return "javascript";
1613
+ case ".ts":
1614
+ return "typescript";
1615
+ case ".jsx":
1616
+ return "javascript";
1617
+ case ".tsx":
1618
+ return "typescript";
1619
+ case ".py":
1620
+ return "python";
1621
+ case ".sh":
1622
+ return "sh";
1623
+ case ".css":
1624
+ case ".scss":
1625
+ return "css";
1626
+ case ".html":
1627
+ case ".htm":
1628
+ return "html";
1629
+ case ".json":
1630
+ return "json";
1631
+ case ".md":
1632
+ return "markdown";
1633
+ case ".yaml":
1634
+ case ".yml":
1635
+ return "yaml";
1636
+ case ".xml":
1637
+ return "xml";
1638
+ case ".sql":
1639
+ return "sql";
1640
+ case ".toml":
1641
+ return "ini";
1642
+ case ".ini":
1643
+ case ".conf":
1644
+ return "ini";
1645
+ default:
1646
+ return "plaintext";
1605
1647
  }
1606
- return trimmed;
1607
1648
  }
1608
1649
  function normalizeLocalPath(rawPath) {
1609
1650
  const trimmed = rawPath.trim();
@@ -1625,39 +1666,72 @@ function decodeBrowsePath(rawPath) {
1625
1666
  return rawPath;
1626
1667
  }
1627
1668
  }
1669
+ function isTextEditablePath(pathValue) {
1670
+ return TEXT_EDITABLE_EXTENSIONS.has(extname(pathValue).toLowerCase());
1671
+ }
1628
1672
  function escapeHtml(value) {
1629
1673
  return value.replace(/&/gu, "&amp;").replace(/</gu, "&lt;").replace(/>/gu, "&gt;").replace(/"/gu, "&quot;").replace(/'/gu, "&#39;");
1630
1674
  }
1631
1675
  function toBrowseHref(pathValue) {
1632
1676
  return `/codex-local-browse${encodeURI(pathValue)}`;
1633
1677
  }
1634
- async function renderDirectoryListing(res, localPath) {
1678
+ function toEditHref(pathValue) {
1679
+ return `/codex-local-edit${encodeURI(pathValue)}`;
1680
+ }
1681
+ function escapeForInlineScriptString(value) {
1682
+ return JSON.stringify(value).replace(/<\//gu, "<\\/").replace(/<!--/gu, "<\\!--").replace(/\u2028/gu, "\\u2028").replace(/\u2029/gu, "\\u2029");
1683
+ }
1684
+ async function getDirectoryItems(localPath) {
1635
1685
  const entries = await readdir2(localPath, { withFileTypes: true });
1636
- const sorted = entries.slice().sort((a, b) => {
1637
- if (a.isDirectory() && !b.isDirectory()) return -1;
1638
- if (!a.isDirectory() && b.isDirectory()) return 1;
1686
+ const withMeta = await Promise.all(entries.map(async (entry) => {
1687
+ const entryPath = join2(localPath, entry.name);
1688
+ const entryStat = await stat2(entryPath);
1689
+ return {
1690
+ name: entry.name,
1691
+ path: entryPath,
1692
+ isDirectory: entry.isDirectory(),
1693
+ editable: !entry.isDirectory() && isTextEditablePath(entryPath),
1694
+ mtimeMs: entryStat.mtimeMs
1695
+ };
1696
+ }));
1697
+ return withMeta.sort((a, b) => {
1698
+ if (b.mtimeMs !== a.mtimeMs) return b.mtimeMs - a.mtimeMs;
1699
+ if (a.isDirectory && !b.isDirectory) return -1;
1700
+ if (!a.isDirectory && b.isDirectory) return 1;
1639
1701
  return a.name.localeCompare(b.name);
1640
1702
  });
1703
+ }
1704
+ async function createDirectoryListingHtml(localPath) {
1705
+ const items = await getDirectoryItems(localPath);
1641
1706
  const parentPath = dirname(localPath);
1642
- const rows = sorted.map((entry) => {
1643
- const entryPath = join2(localPath, entry.name);
1644
- const suffix = entry.isDirectory() ? "/" : "";
1645
- return `<li><a href="${escapeHtml(toBrowseHref(entryPath))}">${escapeHtml(entry.name)}${suffix}</a></li>`;
1707
+ const rows = items.map((item) => {
1708
+ const suffix = item.isDirectory ? "/" : "";
1709
+ const editAction = item.editable ? ` <a class="icon-btn" aria-label="Edit ${escapeHtml(item.name)}" href="${escapeHtml(toEditHref(item.path))}" title="Edit">\u270F\uFE0F</a>` : "";
1710
+ return `<li class="file-row"><a class="file-link" href="${escapeHtml(toBrowseHref(item.path))}">${escapeHtml(item.name)}${suffix}</a>${editAction}</li>`;
1646
1711
  }).join("\n");
1647
1712
  const parentLink = localPath !== parentPath ? `<p><a href="${escapeHtml(toBrowseHref(parentPath))}">..</a></p>` : "";
1648
- const html = `<!doctype html>
1713
+ return `<!doctype html>
1649
1714
  <html lang="en">
1650
1715
  <head>
1651
1716
  <meta charset="utf-8" />
1652
1717
  <meta name="viewport" content="width=device-width, initial-scale=1" />
1653
1718
  <title>Index of ${escapeHtml(localPath)}</title>
1654
1719
  <style>
1655
- body { font-family: ui-monospace, Menlo, Monaco, monospace; margin: 24px; background: #0b1020; color: #dbe6ff; }
1720
+ body { font-family: ui-monospace, Menlo, Monaco, monospace; margin: 16px; background: #0b1020; color: #dbe6ff; }
1656
1721
  a { color: #8cc2ff; text-decoration: none; }
1657
1722
  a:hover { text-decoration: underline; }
1658
- ul { list-style: none; padding: 0; margin: 12px 0 0; }
1659
- li { padding: 3px 0; }
1723
+ ul { list-style: none; padding: 0; margin: 12px 0 0; display: flex; flex-direction: column; gap: 8px; }
1724
+ .file-row { display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: center; gap: 10px; }
1725
+ .file-link { display: block; padding: 10px 12px; border: 1px solid #28405f; border-radius: 10px; background: #0f1b33; overflow-wrap: anywhere; }
1726
+ .icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 42px; height: 42px; border: 1px solid #36557a; border-radius: 10px; background: #162643; text-decoration: none; }
1727
+ .icon-btn:hover { filter: brightness(1.08); text-decoration: none; }
1660
1728
  h1 { font-size: 18px; margin: 0; word-break: break-all; }
1729
+ @media (max-width: 640px) {
1730
+ body { margin: 12px; }
1731
+ .file-row { gap: 8px; }
1732
+ .file-link { font-size: 15px; padding: 12px; }
1733
+ .icon-btn { width: 44px; height: 44px; }
1734
+ }
1661
1735
  </style>
1662
1736
  </head>
1663
1737
  <body>
@@ -1666,7 +1740,107 @@ async function renderDirectoryListing(res, localPath) {
1666
1740
  <ul>${rows}</ul>
1667
1741
  </body>
1668
1742
  </html>`;
1669
- res.status(200).type("text/html; charset=utf-8").send(html);
1743
+ }
1744
+ async function createTextEditorHtml(localPath) {
1745
+ const content = await readFile2(localPath, "utf8");
1746
+ const parentPath = dirname(localPath);
1747
+ const language = languageForPath(localPath);
1748
+ const safeContentLiteral = escapeForInlineScriptString(content);
1749
+ return `<!doctype html>
1750
+ <html lang="en">
1751
+ <head>
1752
+ <meta charset="utf-8" />
1753
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
1754
+ <title>Edit ${escapeHtml(localPath)}</title>
1755
+ <style>
1756
+ html, body { width: 100%; height: 100%; margin: 0; }
1757
+ body { font-family: ui-monospace, Menlo, Monaco, monospace; background: #0b1020; color: #dbe6ff; display: flex; flex-direction: column; overflow: hidden; }
1758
+ .toolbar { position: sticky; top: 0; z-index: 10; display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; background: #0b1020; border-bottom: 1px solid #243a5a; }
1759
+ .row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
1760
+ button, a { background: #1b2a4a; color: #dbe6ff; border: 1px solid #345; padding: 6px 10px; border-radius: 6px; text-decoration: none; cursor: pointer; }
1761
+ button:hover, a:hover { filter: brightness(1.08); }
1762
+ #editor { flex: 1 1 auto; min-height: 0; width: 100%; border: none; overflow: hidden; }
1763
+ #status { margin-left: 8px; color: #8cc2ff; }
1764
+ .ace_editor { background: #07101f !important; color: #dbe6ff !important; width: 100% !important; height: 100% !important; }
1765
+ .ace_gutter { background: #07101f !important; color: #6f8eb5 !important; }
1766
+ .ace_marker-layer .ace_active-line { background: #10213c !important; }
1767
+ .ace_marker-layer .ace_selection { background: rgba(140, 194, 255, 0.3) !important; }
1768
+ .meta { opacity: 0.9; font-size: 12px; overflow-wrap: anywhere; }
1769
+ </style>
1770
+ </head>
1771
+ <body>
1772
+ <div class="toolbar">
1773
+ <div class="row">
1774
+ <a href="${escapeHtml(toBrowseHref(parentPath))}">Back</a>
1775
+ <button id="saveBtn" type="button">Save</button>
1776
+ <span id="status"></span>
1777
+ </div>
1778
+ <div class="meta">${escapeHtml(localPath)} \xB7 ${escapeHtml(language)}</div>
1779
+ </div>
1780
+ <div id="editor"></div>
1781
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.2/ace.js"></script>
1782
+ <script>
1783
+ const saveBtn = document.getElementById('saveBtn');
1784
+ const status = document.getElementById('status');
1785
+ const editor = ace.edit('editor');
1786
+ editor.setTheme('ace/theme/tomorrow_night');
1787
+ editor.session.setMode('ace/mode/${escapeHtml(language)}');
1788
+ editor.setValue(${safeContentLiteral}, -1);
1789
+ editor.setOptions({
1790
+ fontSize: '13px',
1791
+ wrap: true,
1792
+ showPrintMargin: false,
1793
+ useSoftTabs: true,
1794
+ tabSize: 2,
1795
+ behavioursEnabled: true,
1796
+ });
1797
+ editor.resize();
1798
+
1799
+ saveBtn.addEventListener('click', async () => {
1800
+ status.textContent = 'Saving...';
1801
+ const response = await fetch(location.pathname, {
1802
+ method: 'PUT',
1803
+ headers: { 'Content-Type': 'text/plain; charset=utf-8' },
1804
+ body: editor.getValue(),
1805
+ });
1806
+ status.textContent = response.ok ? 'Saved' : 'Save failed';
1807
+ });
1808
+ </script>
1809
+ </body>
1810
+ </html>`;
1811
+ }
1812
+
1813
+ // src/server/httpServer.ts
1814
+ import { WebSocketServer } from "ws";
1815
+ var __dirname = dirname2(fileURLToPath(import.meta.url));
1816
+ var distDir = join3(__dirname, "..", "dist");
1817
+ var spaEntryFile = join3(distDir, "index.html");
1818
+ var IMAGE_CONTENT_TYPES = {
1819
+ ".avif": "image/avif",
1820
+ ".bmp": "image/bmp",
1821
+ ".gif": "image/gif",
1822
+ ".jpeg": "image/jpeg",
1823
+ ".jpg": "image/jpeg",
1824
+ ".png": "image/png",
1825
+ ".svg": "image/svg+xml",
1826
+ ".webp": "image/webp"
1827
+ };
1828
+ function normalizeLocalImagePath(rawPath) {
1829
+ const trimmed = rawPath.trim();
1830
+ if (!trimmed) return "";
1831
+ if (trimmed.startsWith("file://")) {
1832
+ try {
1833
+ return decodeURIComponent(trimmed.replace(/^file:\/\//u, ""));
1834
+ } catch {
1835
+ return trimmed.replace(/^file:\/\//u, "");
1836
+ }
1837
+ }
1838
+ return trimmed;
1839
+ }
1840
+ function readWildcardPathParam(value) {
1841
+ if (typeof value === "string") return value;
1842
+ if (Array.isArray(value)) return value.join("/");
1843
+ return "";
1670
1844
  }
1671
1845
  function createServer(options = {}) {
1672
1846
  const app = express();
@@ -1683,7 +1857,7 @@ function createServer(options = {}) {
1683
1857
  res.status(400).json({ error: "Expected absolute local file path." });
1684
1858
  return;
1685
1859
  }
1686
- const contentType = IMAGE_CONTENT_TYPES[extname(localPath).toLowerCase()];
1860
+ const contentType = IMAGE_CONTENT_TYPES[extname2(localPath).toLowerCase()];
1687
1861
  if (!contentType) {
1688
1862
  res.status(415).json({ error: "Unsupported image type." });
1689
1863
  return;
@@ -1710,17 +1884,18 @@ function createServer(options = {}) {
1710
1884
  });
1711
1885
  });
1712
1886
  app.get("/codex-local-browse/*path", async (req, res) => {
1713
- const rawPath = typeof req.params.path === "string" ? req.params.path : "";
1887
+ const rawPath = readWildcardPathParam(req.params.path);
1714
1888
  const localPath = decodeBrowsePath(`/${rawPath}`);
1715
1889
  if (!localPath || !isAbsolute2(localPath)) {
1716
1890
  res.status(400).json({ error: "Expected absolute local file path." });
1717
1891
  return;
1718
1892
  }
1719
1893
  try {
1720
- const fileStat = await stat2(localPath);
1894
+ const fileStat = await stat3(localPath);
1721
1895
  res.setHeader("Cache-Control", "private, no-store");
1722
1896
  if (fileStat.isDirectory()) {
1723
- await renderDirectoryListing(res, localPath);
1897
+ const html = await createDirectoryListingHtml(localPath);
1898
+ res.status(200).type("text/html; charset=utf-8").send(html);
1724
1899
  return;
1725
1900
  }
1726
1901
  res.sendFile(localPath, { dotfiles: "allow" }, (error) => {
@@ -1731,6 +1906,44 @@ function createServer(options = {}) {
1731
1906
  res.status(404).json({ error: "File not found." });
1732
1907
  }
1733
1908
  });
1909
+ app.get("/codex-local-edit/*path", async (req, res) => {
1910
+ const rawPath = readWildcardPathParam(req.params.path);
1911
+ const localPath = decodeBrowsePath(`/${rawPath}`);
1912
+ if (!localPath || !isAbsolute2(localPath)) {
1913
+ res.status(400).json({ error: "Expected absolute local file path." });
1914
+ return;
1915
+ }
1916
+ try {
1917
+ const fileStat = await stat3(localPath);
1918
+ if (!fileStat.isFile()) {
1919
+ res.status(400).json({ error: "Expected file path." });
1920
+ return;
1921
+ }
1922
+ const html = await createTextEditorHtml(localPath);
1923
+ res.status(200).type("text/html; charset=utf-8").send(html);
1924
+ } catch {
1925
+ res.status(404).json({ error: "File not found." });
1926
+ }
1927
+ });
1928
+ app.put("/codex-local-edit/*path", express.text({ type: "*/*", limit: "10mb" }), async (req, res) => {
1929
+ const rawPath = readWildcardPathParam(req.params.path);
1930
+ const localPath = decodeBrowsePath(`/${rawPath}`);
1931
+ if (!localPath || !isAbsolute2(localPath)) {
1932
+ res.status(400).json({ error: "Expected absolute local file path." });
1933
+ return;
1934
+ }
1935
+ if (!isTextEditablePath(localPath)) {
1936
+ res.status(415).json({ error: "Only text-like files are editable." });
1937
+ return;
1938
+ }
1939
+ const body = typeof req.body === "string" ? req.body : "";
1940
+ try {
1941
+ await writeFile2(localPath, body, "utf8");
1942
+ res.status(200).json({ ok: true });
1943
+ } catch {
1944
+ res.status(404).json({ error: "File not found." });
1945
+ }
1946
+ });
1734
1947
  const hasFrontendAssets = existsSync(spaEntryFile);
1735
1948
  if (hasFrontendAssets) {
1736
1949
  app.use(express.static(distDir));
@@ -1802,11 +2015,11 @@ function generatePassword() {
1802
2015
 
1803
2016
  // src/cli/index.ts
1804
2017
  var program = new Command().name("codexui").description("Web interface for Codex app-server");
1805
- var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
2018
+ var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
1806
2019
  async function readCliVersion() {
1807
2020
  try {
1808
- const packageJsonPath = join3(__dirname2, "..", "package.json");
1809
- const raw = await readFile2(packageJsonPath, "utf8");
2021
+ const packageJsonPath = join4(__dirname2, "..", "package.json");
2022
+ const raw = await readFile3(packageJsonPath, "utf8");
1810
2023
  const parsed = JSON.parse(raw);
1811
2024
  return typeof parsed.version === "string" ? parsed.version : "unknown";
1812
2025
  } catch {
@@ -1831,13 +2044,13 @@ function runWithStatus(command, args) {
1831
2044
  return result.status ?? -1;
1832
2045
  }
1833
2046
  function getUserNpmPrefix() {
1834
- return join3(homedir2(), ".npm-global");
2047
+ return join4(homedir2(), ".npm-global");
1835
2048
  }
1836
2049
  function resolveCodexCommand() {
1837
2050
  if (canRun("codex", ["--version"])) {
1838
2051
  return "codex";
1839
2052
  }
1840
- const userCandidate = join3(getUserNpmPrefix(), "bin", "codex");
2053
+ const userCandidate = join4(getUserNpmPrefix(), "bin", "codex");
1841
2054
  if (existsSync2(userCandidate) && canRun(userCandidate, ["--version"])) {
1842
2055
  return userCandidate;
1843
2056
  }
@@ -1845,88 +2058,15 @@ function resolveCodexCommand() {
1845
2058
  if (!prefix) {
1846
2059
  return null;
1847
2060
  }
1848
- const candidate = join3(prefix, "bin", "codex");
2061
+ const candidate = join4(prefix, "bin", "codex");
1849
2062
  if (existsSync2(candidate) && canRun(candidate, ["--version"])) {
1850
2063
  return candidate;
1851
2064
  }
1852
2065
  return null;
1853
2066
  }
1854
- function resolveCloudflaredCommand() {
1855
- if (canRun("cloudflared", ["--version"])) {
1856
- return "cloudflared";
1857
- }
1858
- const localCandidate = join3(homedir2(), ".local", "bin", "cloudflared");
1859
- if (existsSync2(localCandidate) && canRun(localCandidate, ["--version"])) {
1860
- return localCandidate;
1861
- }
1862
- return null;
1863
- }
1864
- function mapCloudflaredLinuxArch(arch) {
1865
- if (arch === "x64") {
1866
- return "amd64";
1867
- }
1868
- if (arch === "arm64") {
1869
- return "arm64";
1870
- }
1871
- return null;
1872
- }
1873
- function downloadFile(url, destination) {
1874
- return new Promise((resolve2, reject) => {
1875
- const request = (currentUrl) => {
1876
- httpsGet(currentUrl, (response) => {
1877
- const code = response.statusCode ?? 0;
1878
- if (code >= 300 && code < 400 && response.headers.location) {
1879
- response.resume();
1880
- request(response.headers.location);
1881
- return;
1882
- }
1883
- if (code !== 200) {
1884
- response.resume();
1885
- reject(new Error(`Download failed with HTTP status ${String(code)}`));
1886
- return;
1887
- }
1888
- const file = createWriteStream(destination, { mode: 493 });
1889
- response.pipe(file);
1890
- file.on("finish", () => {
1891
- file.close();
1892
- resolve2();
1893
- });
1894
- file.on("error", reject);
1895
- }).on("error", reject);
1896
- };
1897
- request(url);
1898
- });
1899
- }
1900
- async function ensureCloudflaredInstalledLinux() {
1901
- const current = resolveCloudflaredCommand();
1902
- if (current) {
1903
- return current;
1904
- }
1905
- if (process.platform !== "linux") {
1906
- return null;
1907
- }
1908
- const mappedArch = mapCloudflaredLinuxArch(process.arch);
1909
- if (!mappedArch) {
1910
- throw new Error(`cloudflared auto-install is not supported for Linux architecture: ${process.arch}`);
1911
- }
1912
- const userBinDir = join3(homedir2(), ".local", "bin");
1913
- mkdirSync(userBinDir, { recursive: true });
1914
- const destination = join3(userBinDir, "cloudflared");
1915
- const downloadUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${mappedArch}`;
1916
- console.log("\ncloudflared not found. Installing to ~/.local/bin...\n");
1917
- await downloadFile(downloadUrl, destination);
1918
- chmodSync(destination, 493);
1919
- process.env.PATH = `${userBinDir}:${process.env.PATH ?? ""}`;
1920
- const installed = resolveCloudflaredCommand();
1921
- if (!installed) {
1922
- throw new Error("cloudflared download completed but executable is still not available");
1923
- }
1924
- console.log("\ncloudflared installed.\n");
1925
- return installed;
1926
- }
1927
2067
  function hasCodexAuth() {
1928
- const codexHome = process.env.CODEX_HOME?.trim() || join3(homedir2(), ".codex");
1929
- return existsSync2(join3(codexHome, "auth.json"));
2068
+ const codexHome = process.env.CODEX_HOME?.trim() || join4(homedir2(), ".codex");
2069
+ return existsSync2(join4(codexHome, "auth.json"));
1930
2070
  }
1931
2071
  function ensureCodexInstalled() {
1932
2072
  let codexCommand = resolveCodexCommand();
@@ -1944,7 +2084,7 @@ function ensureCodexInstalled() {
1944
2084
  Global npm install requires elevated permissions. Retrying with --prefix ${userPrefix}...
1945
2085
  `);
1946
2086
  runOrFail("npm", ["install", "-g", "--prefix", userPrefix, pkg], `${label} (user prefix)`);
1947
- process.env.PATH = `${join3(userPrefix, "bin")}:${process.env.PATH ?? ""}`;
2087
+ process.env.PATH = `${join4(userPrefix, "bin")}:${process.env.PATH ?? ""}`;
1948
2088
  };
1949
2089
  if (isTermuxRuntime()) {
1950
2090
  console.log("\nCodex CLI not found. Installing Termux-compatible Codex CLI from npm...\n");
@@ -2005,9 +2145,9 @@ function parseCloudflaredUrl(chunk) {
2005
2145
  }
2006
2146
  return urlMatch[urlMatch.length - 1] ?? null;
2007
2147
  }
2008
- async function startCloudflaredTunnel(command, localPort) {
2148
+ async function startCloudflaredTunnel(localPort) {
2009
2149
  return new Promise((resolve2, reject) => {
2010
- const child = spawn2(command, ["tunnel", "--url", `http://localhost:${String(localPort)}`], {
2150
+ const child = spawn2("cloudflared", ["tunnel", "--url", `http://localhost:${String(localPort)}`], {
2011
2151
  stdio: ["ignore", "pipe", "pipe"]
2012
2152
  });
2013
2153
  const timeout = setTimeout(() => {
@@ -2080,8 +2220,7 @@ async function startServer(options) {
2080
2220
  let tunnelUrl = null;
2081
2221
  if (options.tunnel) {
2082
2222
  try {
2083
- const cloudflaredCommand = await ensureCloudflaredInstalledLinux() ?? "cloudflared";
2084
- const tunnel = await startCloudflaredTunnel(cloudflaredCommand, port);
2223
+ const tunnel = await startCloudflaredTunnel(port);
2085
2224
  tunnelChild = tunnel.process;
2086
2225
  tunnelUrl = tunnel.url;
2087
2226
  } catch (error) {