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/assets/index-cSjdFqX4.js +48 -0
- package/dist/assets/index-sxLu4gFb.css +1 -0
- package/dist/index.html +2 -2
- package/dist-cli/index.js +274 -135
- package/dist-cli/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/index-Bhfj4wvl.js +0 -48
- package/dist/assets/index-CBIbfWDP.css +0 -1
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 {
|
|
6
|
-
import { readFile as
|
|
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
|
|
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
|
|
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
|
|
17
|
+
import { dirname as dirname2, extname as extname2, isAbsolute as isAbsolute2, join as join3 } from "path";
|
|
19
18
|
import { existsSync } from "fs";
|
|
20
|
-
import {
|
|
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/
|
|
1582
|
-
import {
|
|
1583
|
-
|
|
1584
|
-
var
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
".
|
|
1588
|
-
".
|
|
1589
|
-
".
|
|
1590
|
-
".
|
|
1591
|
-
".
|
|
1592
|
-
".
|
|
1593
|
-
".
|
|
1594
|
-
".
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
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, "&").replace(/</gu, "<").replace(/>/gu, ">").replace(/"/gu, """).replace(/'/gu, "'");
|
|
1630
1674
|
}
|
|
1631
1675
|
function toBrowseHref(pathValue) {
|
|
1632
1676
|
return `/codex-local-browse${encodeURI(pathValue)}`;
|
|
1633
1677
|
}
|
|
1634
|
-
|
|
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
|
|
1637
|
-
|
|
1638
|
-
|
|
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 =
|
|
1643
|
-
const
|
|
1644
|
-
const
|
|
1645
|
-
return `<li><a href="${escapeHtml(toBrowseHref(
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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[
|
|
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 =
|
|
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
|
|
1894
|
+
const fileStat = await stat3(localPath);
|
|
1721
1895
|
res.setHeader("Cache-Control", "private, no-store");
|
|
1722
1896
|
if (fileStat.isDirectory()) {
|
|
1723
|
-
await
|
|
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 =
|
|
2018
|
+
var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
1806
2019
|
async function readCliVersion() {
|
|
1807
2020
|
try {
|
|
1808
|
-
const packageJsonPath =
|
|
1809
|
-
const raw = await
|
|
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
|
|
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 =
|
|
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 =
|
|
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() ||
|
|
1929
|
-
return existsSync2(
|
|
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 = `${
|
|
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(
|
|
2148
|
+
async function startCloudflaredTunnel(localPort) {
|
|
2009
2149
|
return new Promise((resolve2, reject) => {
|
|
2010
|
-
const child = spawn2(
|
|
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
|
|
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) {
|