codexapp 0.1.27 → 0.1.28
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-Bhfj4wvl.js +48 -0
- package/dist/assets/index-CBIbfWDP.css +1 -0
- package/dist/index.html +2 -2
- package/dist-cli/index.js +164 -280
- package/dist-cli/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/index-cSjdFqX4.js +0 -48
- package/dist/assets/index-sxLu4gFb.css +0 -1
package/dist-cli/index.js
CHANGED
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { createServer as createServer2 } from "http";
|
|
5
|
-
import { existsSync as existsSync2 } from "fs";
|
|
6
|
-
import { readFile as
|
|
7
|
-
import { homedir as homedir2 } from "os";
|
|
8
|
-
import { join as
|
|
5
|
+
import { chmodSync, createWriteStream, existsSync as existsSync2, mkdirSync } from "fs";
|
|
6
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
7
|
+
import { homedir as homedir2, networkInterfaces } from "os";
|
|
8
|
+
import { join as join3 } from "path";
|
|
9
9
|
import { spawn as spawn2, spawnSync } from "child_process";
|
|
10
10
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11
|
-
import { dirname as
|
|
11
|
+
import { dirname as dirname2 } from "path";
|
|
12
|
+
import { get as httpsGet } from "https";
|
|
12
13
|
import { Command } from "commander";
|
|
13
14
|
import qrcode from "qrcode-terminal";
|
|
14
15
|
|
|
15
16
|
// src/server/httpServer.ts
|
|
16
17
|
import { fileURLToPath } from "url";
|
|
17
|
-
import { dirname
|
|
18
|
+
import { dirname, extname, isAbsolute as isAbsolute2, join as join2 } from "path";
|
|
18
19
|
import { existsSync } from "fs";
|
|
19
|
-
import {
|
|
20
|
+
import { readdir as readdir2, stat as stat2 } from "fs/promises";
|
|
20
21
|
import express from "express";
|
|
21
22
|
|
|
22
23
|
// src/server/codexAppServerBridge.ts
|
|
@@ -1577,74 +1578,32 @@ function createAuthSession(password) {
|
|
|
1577
1578
|
};
|
|
1578
1579
|
}
|
|
1579
1580
|
|
|
1580
|
-
// src/server/
|
|
1581
|
-
import {
|
|
1582
|
-
|
|
1583
|
-
var
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
".
|
|
1587
|
-
".
|
|
1588
|
-
".
|
|
1589
|
-
".
|
|
1590
|
-
".
|
|
1591
|
-
".
|
|
1592
|
-
".
|
|
1593
|
-
".
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
"
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
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";
|
|
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
|
+
}
|
|
1647
1605
|
}
|
|
1606
|
+
return trimmed;
|
|
1648
1607
|
}
|
|
1649
1608
|
function normalizeLocalPath(rawPath) {
|
|
1650
1609
|
const trimmed = rawPath.trim();
|
|
@@ -1666,72 +1625,39 @@ function decodeBrowsePath(rawPath) {
|
|
|
1666
1625
|
return rawPath;
|
|
1667
1626
|
}
|
|
1668
1627
|
}
|
|
1669
|
-
function isTextEditablePath(pathValue) {
|
|
1670
|
-
return TEXT_EDITABLE_EXTENSIONS.has(extname(pathValue).toLowerCase());
|
|
1671
|
-
}
|
|
1672
1628
|
function escapeHtml(value) {
|
|
1673
1629
|
return value.replace(/&/gu, "&").replace(/</gu, "<").replace(/>/gu, ">").replace(/"/gu, """).replace(/'/gu, "'");
|
|
1674
1630
|
}
|
|
1675
1631
|
function toBrowseHref(pathValue) {
|
|
1676
1632
|
return `/codex-local-browse${encodeURI(pathValue)}`;
|
|
1677
1633
|
}
|
|
1678
|
-
function
|
|
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) {
|
|
1634
|
+
async function renderDirectoryListing(res, localPath) {
|
|
1685
1635
|
const entries = await readdir2(localPath, { withFileTypes: true });
|
|
1686
|
-
const
|
|
1687
|
-
|
|
1688
|
-
|
|
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;
|
|
1636
|
+
const sorted = entries.slice().sort((a, b) => {
|
|
1637
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
1638
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
1701
1639
|
return a.name.localeCompare(b.name);
|
|
1702
1640
|
});
|
|
1703
|
-
}
|
|
1704
|
-
async function createDirectoryListingHtml(localPath) {
|
|
1705
|
-
const items = await getDirectoryItems(localPath);
|
|
1706
1641
|
const parentPath = dirname(localPath);
|
|
1707
|
-
const rows =
|
|
1708
|
-
const
|
|
1709
|
-
const
|
|
1710
|
-
return `<li
|
|
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>`;
|
|
1711
1646
|
}).join("\n");
|
|
1712
1647
|
const parentLink = localPath !== parentPath ? `<p><a href="${escapeHtml(toBrowseHref(parentPath))}">..</a></p>` : "";
|
|
1713
|
-
|
|
1648
|
+
const html = `<!doctype html>
|
|
1714
1649
|
<html lang="en">
|
|
1715
1650
|
<head>
|
|
1716
1651
|
<meta charset="utf-8" />
|
|
1717
1652
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1718
1653
|
<title>Index of ${escapeHtml(localPath)}</title>
|
|
1719
1654
|
<style>
|
|
1720
|
-
body { font-family: ui-monospace, Menlo, Monaco, monospace; margin:
|
|
1655
|
+
body { font-family: ui-monospace, Menlo, Monaco, monospace; margin: 24px; background: #0b1020; color: #dbe6ff; }
|
|
1721
1656
|
a { color: #8cc2ff; text-decoration: none; }
|
|
1722
1657
|
a:hover { text-decoration: underline; }
|
|
1723
|
-
ul { list-style: none; padding: 0; margin: 12px 0 0;
|
|
1724
|
-
|
|
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; }
|
|
1658
|
+
ul { list-style: none; padding: 0; margin: 12px 0 0; }
|
|
1659
|
+
li { padding: 3px 0; }
|
|
1728
1660
|
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
|
-
}
|
|
1735
1661
|
</style>
|
|
1736
1662
|
</head>
|
|
1737
1663
|
<body>
|
|
@@ -1740,107 +1666,7 @@ async function createDirectoryListingHtml(localPath) {
|
|
|
1740
1666
|
<ul>${rows}</ul>
|
|
1741
1667
|
</body>
|
|
1742
1668
|
</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 "";
|
|
1669
|
+
res.status(200).type("text/html; charset=utf-8").send(html);
|
|
1844
1670
|
}
|
|
1845
1671
|
function createServer(options = {}) {
|
|
1846
1672
|
const app = express();
|
|
@@ -1857,7 +1683,7 @@ function createServer(options = {}) {
|
|
|
1857
1683
|
res.status(400).json({ error: "Expected absolute local file path." });
|
|
1858
1684
|
return;
|
|
1859
1685
|
}
|
|
1860
|
-
const contentType = IMAGE_CONTENT_TYPES[
|
|
1686
|
+
const contentType = IMAGE_CONTENT_TYPES[extname(localPath).toLowerCase()];
|
|
1861
1687
|
if (!contentType) {
|
|
1862
1688
|
res.status(415).json({ error: "Unsupported image type." });
|
|
1863
1689
|
return;
|
|
@@ -1884,18 +1710,17 @@ function createServer(options = {}) {
|
|
|
1884
1710
|
});
|
|
1885
1711
|
});
|
|
1886
1712
|
app.get("/codex-local-browse/*path", async (req, res) => {
|
|
1887
|
-
const rawPath =
|
|
1713
|
+
const rawPath = typeof req.params.path === "string" ? req.params.path : "";
|
|
1888
1714
|
const localPath = decodeBrowsePath(`/${rawPath}`);
|
|
1889
1715
|
if (!localPath || !isAbsolute2(localPath)) {
|
|
1890
1716
|
res.status(400).json({ error: "Expected absolute local file path." });
|
|
1891
1717
|
return;
|
|
1892
1718
|
}
|
|
1893
1719
|
try {
|
|
1894
|
-
const fileStat = await
|
|
1720
|
+
const fileStat = await stat2(localPath);
|
|
1895
1721
|
res.setHeader("Cache-Control", "private, no-store");
|
|
1896
1722
|
if (fileStat.isDirectory()) {
|
|
1897
|
-
|
|
1898
|
-
res.status(200).type("text/html; charset=utf-8").send(html);
|
|
1723
|
+
await renderDirectoryListing(res, localPath);
|
|
1899
1724
|
return;
|
|
1900
1725
|
}
|
|
1901
1726
|
res.sendFile(localPath, { dotfiles: "allow" }, (error) => {
|
|
@@ -1906,44 +1731,6 @@ function createServer(options = {}) {
|
|
|
1906
1731
|
res.status(404).json({ error: "File not found." });
|
|
1907
1732
|
}
|
|
1908
1733
|
});
|
|
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
|
-
});
|
|
1947
1734
|
const hasFrontendAssets = existsSync(spaEntryFile);
|
|
1948
1735
|
if (hasFrontendAssets) {
|
|
1949
1736
|
app.use(express.static(distDir));
|
|
@@ -2015,11 +1802,11 @@ function generatePassword() {
|
|
|
2015
1802
|
|
|
2016
1803
|
// src/cli/index.ts
|
|
2017
1804
|
var program = new Command().name("codexui").description("Web interface for Codex app-server");
|
|
2018
|
-
var __dirname2 =
|
|
1805
|
+
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
2019
1806
|
async function readCliVersion() {
|
|
2020
1807
|
try {
|
|
2021
|
-
const packageJsonPath =
|
|
2022
|
-
const raw = await
|
|
1808
|
+
const packageJsonPath = join3(__dirname2, "..", "package.json");
|
|
1809
|
+
const raw = await readFile2(packageJsonPath, "utf8");
|
|
2023
1810
|
const parsed = JSON.parse(raw);
|
|
2024
1811
|
return typeof parsed.version === "string" ? parsed.version : "unknown";
|
|
2025
1812
|
} catch {
|
|
@@ -2044,13 +1831,13 @@ function runWithStatus(command, args) {
|
|
|
2044
1831
|
return result.status ?? -1;
|
|
2045
1832
|
}
|
|
2046
1833
|
function getUserNpmPrefix() {
|
|
2047
|
-
return
|
|
1834
|
+
return join3(homedir2(), ".npm-global");
|
|
2048
1835
|
}
|
|
2049
1836
|
function resolveCodexCommand() {
|
|
2050
1837
|
if (canRun("codex", ["--version"])) {
|
|
2051
1838
|
return "codex";
|
|
2052
1839
|
}
|
|
2053
|
-
const userCandidate =
|
|
1840
|
+
const userCandidate = join3(getUserNpmPrefix(), "bin", "codex");
|
|
2054
1841
|
if (existsSync2(userCandidate) && canRun(userCandidate, ["--version"])) {
|
|
2055
1842
|
return userCandidate;
|
|
2056
1843
|
}
|
|
@@ -2058,15 +1845,88 @@ function resolveCodexCommand() {
|
|
|
2058
1845
|
if (!prefix) {
|
|
2059
1846
|
return null;
|
|
2060
1847
|
}
|
|
2061
|
-
const candidate =
|
|
1848
|
+
const candidate = join3(prefix, "bin", "codex");
|
|
2062
1849
|
if (existsSync2(candidate) && canRun(candidate, ["--version"])) {
|
|
2063
1850
|
return candidate;
|
|
2064
1851
|
}
|
|
2065
1852
|
return null;
|
|
2066
1853
|
}
|
|
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
|
+
}
|
|
2067
1927
|
function hasCodexAuth() {
|
|
2068
|
-
const codexHome = process.env.CODEX_HOME?.trim() ||
|
|
2069
|
-
return existsSync2(
|
|
1928
|
+
const codexHome = process.env.CODEX_HOME?.trim() || join3(homedir2(), ".codex");
|
|
1929
|
+
return existsSync2(join3(codexHome, "auth.json"));
|
|
2070
1930
|
}
|
|
2071
1931
|
function ensureCodexInstalled() {
|
|
2072
1932
|
let codexCommand = resolveCodexCommand();
|
|
@@ -2084,7 +1944,7 @@ function ensureCodexInstalled() {
|
|
|
2084
1944
|
Global npm install requires elevated permissions. Retrying with --prefix ${userPrefix}...
|
|
2085
1945
|
`);
|
|
2086
1946
|
runOrFail("npm", ["install", "-g", "--prefix", userPrefix, pkg], `${label} (user prefix)`);
|
|
2087
|
-
process.env.PATH = `${
|
|
1947
|
+
process.env.PATH = `${join3(userPrefix, "bin")}:${process.env.PATH ?? ""}`;
|
|
2088
1948
|
};
|
|
2089
1949
|
if (isTermuxRuntime()) {
|
|
2090
1950
|
console.log("\nCodex CLI not found. Installing Termux-compatible Codex CLI from npm...\n");
|
|
@@ -2145,9 +2005,27 @@ function parseCloudflaredUrl(chunk) {
|
|
|
2145
2005
|
}
|
|
2146
2006
|
return urlMatch[urlMatch.length - 1] ?? null;
|
|
2147
2007
|
}
|
|
2148
|
-
|
|
2008
|
+
function getAccessibleUrls(port) {
|
|
2009
|
+
const urls = /* @__PURE__ */ new Set([`http://localhost:${String(port)}`]);
|
|
2010
|
+
const interfaces = networkInterfaces();
|
|
2011
|
+
for (const entries of Object.values(interfaces)) {
|
|
2012
|
+
if (!entries) {
|
|
2013
|
+
continue;
|
|
2014
|
+
}
|
|
2015
|
+
for (const entry of entries) {
|
|
2016
|
+
if (entry.internal) {
|
|
2017
|
+
continue;
|
|
2018
|
+
}
|
|
2019
|
+
if (entry.family === "IPv4") {
|
|
2020
|
+
urls.add(`http://${entry.address}:${String(port)}`);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
return Array.from(urls);
|
|
2025
|
+
}
|
|
2026
|
+
async function startCloudflaredTunnel(command, localPort) {
|
|
2149
2027
|
return new Promise((resolve2, reject) => {
|
|
2150
|
-
const child = spawn2(
|
|
2028
|
+
const child = spawn2(command, ["tunnel", "--url", `http://localhost:${String(localPort)}`], {
|
|
2151
2029
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2152
2030
|
});
|
|
2153
2031
|
const timeout = setTimeout(() => {
|
|
@@ -2198,7 +2076,7 @@ function listenWithFallback(server, startPort) {
|
|
|
2198
2076
|
};
|
|
2199
2077
|
server.once("error", onError);
|
|
2200
2078
|
server.once("listening", onListening);
|
|
2201
|
-
server.listen(port);
|
|
2079
|
+
server.listen(port, "0.0.0.0");
|
|
2202
2080
|
};
|
|
2203
2081
|
attempt(startPort);
|
|
2204
2082
|
});
|
|
@@ -2220,7 +2098,8 @@ async function startServer(options) {
|
|
|
2220
2098
|
let tunnelUrl = null;
|
|
2221
2099
|
if (options.tunnel) {
|
|
2222
2100
|
try {
|
|
2223
|
-
const
|
|
2101
|
+
const cloudflaredCommand = await ensureCloudflaredInstalledLinux() ?? "cloudflared";
|
|
2102
|
+
const tunnel = await startCloudflaredTunnel(cloudflaredCommand, port);
|
|
2224
2103
|
tunnelChild = tunnel.process;
|
|
2225
2104
|
tunnelUrl = tunnel.url;
|
|
2226
2105
|
} catch (error) {
|
|
@@ -2235,8 +2114,15 @@ async function startServer(options) {
|
|
|
2235
2114
|
` Version: ${version}`,
|
|
2236
2115
|
" GitHub: https://github.com/friuns2/codexui",
|
|
2237
2116
|
"",
|
|
2238
|
-
`
|
|
2117
|
+
` Bind: http://0.0.0.0:${String(port)}`
|
|
2239
2118
|
];
|
|
2119
|
+
const accessUrls = getAccessibleUrls(port);
|
|
2120
|
+
if (accessUrls.length > 0) {
|
|
2121
|
+
lines.push(` Local: ${accessUrls[0]}`);
|
|
2122
|
+
for (const accessUrl of accessUrls.slice(1)) {
|
|
2123
|
+
lines.push(` Network: ${accessUrl}`);
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2240
2126
|
if (port !== requestedPort) {
|
|
2241
2127
|
lines.push(` Requested port ${String(requestedPort)} was unavailable; using ${String(port)}.`);
|
|
2242
2128
|
}
|
|
@@ -2245,9 +2131,7 @@ async function startServer(options) {
|
|
|
2245
2131
|
}
|
|
2246
2132
|
if (tunnelUrl) {
|
|
2247
2133
|
lines.push(` Tunnel: ${tunnelUrl}`);
|
|
2248
|
-
lines.push("");
|
|
2249
|
-
lines.push(" Tunnel QR code:");
|
|
2250
|
-
lines.push(` URL: ${tunnelUrl}`);
|
|
2134
|
+
lines.push(" Tunnel QR code below");
|
|
2251
2135
|
}
|
|
2252
2136
|
printTermuxKeepAlive(lines);
|
|
2253
2137
|
lines.push("");
|