codexapp 0.1.9 → 0.1.11
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/README.md +9 -0
- package/dist/assets/index-CKknL24Z.css +1 -0
- package/dist/assets/{index-DrAmX48U.js → index-DMpyfM80.js} +18 -18
- package/dist/index.html +2 -2
- package/dist-cli/index.js +216 -49
- package/dist-cli/index.js.map +1 -1
- package/package.json +9 -5
- package/dist/assets/index-BIm_B5t3.css +0 -1
package/dist/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Codex Web Local</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-DMpyfM80.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CKknL24Z.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body class="bg-slate-950">
|
|
11
11
|
<div id="app"></div>
|
package/dist-cli/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { createServer as createServer2 } from "http";
|
|
5
5
|
import { existsSync } from "fs";
|
|
6
|
+
import { accessSync, constants } from "fs";
|
|
6
7
|
import { readFile as readFile2 } from "fs/promises";
|
|
7
8
|
import { homedir as homedir2 } from "os";
|
|
8
9
|
import { join as join3 } from "path";
|
|
@@ -10,6 +11,7 @@ import { spawn as spawn2, spawnSync } from "child_process";
|
|
|
10
11
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11
12
|
import { dirname as dirname2 } from "path";
|
|
12
13
|
import { Command } from "commander";
|
|
14
|
+
import qrcode from "qrcode-terminal";
|
|
13
15
|
|
|
14
16
|
// src/server/httpServer.ts
|
|
15
17
|
import { fileURLToPath } from "url";
|
|
@@ -1163,13 +1165,9 @@ function createCodexBridgeMiddleware() {
|
|
|
1163
1165
|
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
1164
1166
|
res.setHeader("Connection", "keep-alive");
|
|
1165
1167
|
res.setHeader("X-Accel-Buffering", "no");
|
|
1166
|
-
const unsubscribe =
|
|
1168
|
+
const unsubscribe = middleware.subscribeNotifications((notification) => {
|
|
1167
1169
|
if (res.writableEnded || res.destroyed) return;
|
|
1168
|
-
|
|
1169
|
-
...notification,
|
|
1170
|
-
atIso: (/* @__PURE__ */ new Date()).toISOString()
|
|
1171
|
-
};
|
|
1172
|
-
res.write(`data: ${JSON.stringify(payload)}
|
|
1170
|
+
res.write(`data: ${JSON.stringify(notification)}
|
|
1173
1171
|
|
|
1174
1172
|
`);
|
|
1175
1173
|
});
|
|
@@ -1200,20 +1198,20 @@ data: ${JSON.stringify({ ok: true })}
|
|
|
1200
1198
|
middleware.dispose = () => {
|
|
1201
1199
|
appServer.dispose();
|
|
1202
1200
|
};
|
|
1201
|
+
middleware.subscribeNotifications = (listener) => {
|
|
1202
|
+
return appServer.onNotification((notification) => {
|
|
1203
|
+
listener({
|
|
1204
|
+
...notification,
|
|
1205
|
+
atIso: (/* @__PURE__ */ new Date()).toISOString()
|
|
1206
|
+
});
|
|
1207
|
+
});
|
|
1208
|
+
};
|
|
1203
1209
|
return middleware;
|
|
1204
1210
|
}
|
|
1205
1211
|
|
|
1206
1212
|
// src/server/authMiddleware.ts
|
|
1207
1213
|
import { randomBytes, timingSafeEqual } from "crypto";
|
|
1208
1214
|
var TOKEN_COOKIE = "codex_web_local_token";
|
|
1209
|
-
function isLocalhostRequest(req) {
|
|
1210
|
-
const remote = req.socket.remoteAddress ?? "";
|
|
1211
|
-
if (remote === "127.0.0.1" || remote === "::1" || remote === "::ffff:127.0.0.1") {
|
|
1212
|
-
return true;
|
|
1213
|
-
}
|
|
1214
|
-
const host = (req.headers.host ?? "").toLowerCase();
|
|
1215
|
-
return host.startsWith("localhost:") || host === "localhost" || host.startsWith("127.0.0.1:");
|
|
1216
|
-
}
|
|
1217
1215
|
function constantTimeCompare(a, b) {
|
|
1218
1216
|
const bufA = Buffer.from(a);
|
|
1219
1217
|
const bufB = Buffer.from(b);
|
|
@@ -1232,6 +1230,22 @@ function parseCookies(header) {
|
|
|
1232
1230
|
}
|
|
1233
1231
|
return cookies;
|
|
1234
1232
|
}
|
|
1233
|
+
function isLocalhostRemote(remote) {
|
|
1234
|
+
return remote === "127.0.0.1" || remote === "::1" || remote === "::ffff:127.0.0.1";
|
|
1235
|
+
}
|
|
1236
|
+
function isLocalhostHost(host) {
|
|
1237
|
+
const normalized = host.toLowerCase();
|
|
1238
|
+
return normalized.startsWith("localhost:") || normalized === "localhost" || normalized.startsWith("127.0.0.1:");
|
|
1239
|
+
}
|
|
1240
|
+
function isAuthorizedByRequestLike(remoteAddress, hostHeader, cookieHeader, validTokens) {
|
|
1241
|
+
const remote = remoteAddress ?? "";
|
|
1242
|
+
if (isLocalhostRemote(remote) || isLocalhostHost(hostHeader ?? "")) {
|
|
1243
|
+
return true;
|
|
1244
|
+
}
|
|
1245
|
+
const cookies = parseCookies(cookieHeader);
|
|
1246
|
+
const token = cookies[TOKEN_COOKIE];
|
|
1247
|
+
return Boolean(token && validTokens.has(token));
|
|
1248
|
+
}
|
|
1235
1249
|
var LOGIN_PAGE_HTML = `<!DOCTYPE html>
|
|
1236
1250
|
<html lang="en">
|
|
1237
1251
|
<head>
|
|
@@ -1273,10 +1287,10 @@ form.addEventListener('submit',async e=>{
|
|
|
1273
1287
|
</script>
|
|
1274
1288
|
</body>
|
|
1275
1289
|
</html>`;
|
|
1276
|
-
function
|
|
1290
|
+
function createAuthSession(password) {
|
|
1277
1291
|
const validTokens = /* @__PURE__ */ new Set();
|
|
1278
|
-
|
|
1279
|
-
if (
|
|
1292
|
+
const middleware = (req, res, next) => {
|
|
1293
|
+
if (isAuthorizedByRequestLike(req.socket.remoteAddress, req.headers.host, req.headers.cookie, validTokens)) {
|
|
1280
1294
|
next();
|
|
1281
1295
|
return;
|
|
1282
1296
|
}
|
|
@@ -1294,9 +1308,9 @@ function createAuthMiddleware(password) {
|
|
|
1294
1308
|
res.status(401).json({ error: "Invalid password" });
|
|
1295
1309
|
return;
|
|
1296
1310
|
}
|
|
1297
|
-
const
|
|
1298
|
-
validTokens.add(
|
|
1299
|
-
res.setHeader("Set-Cookie", `${TOKEN_COOKIE}=${
|
|
1311
|
+
const token = randomBytes(32).toString("hex");
|
|
1312
|
+
validTokens.add(token);
|
|
1313
|
+
res.setHeader("Set-Cookie", `${TOKEN_COOKIE}=${token}; Path=/; HttpOnly; SameSite=Strict`);
|
|
1300
1314
|
res.json({ ok: true });
|
|
1301
1315
|
} catch {
|
|
1302
1316
|
res.status(400).json({ error: "Invalid request body" });
|
|
@@ -1304,18 +1318,17 @@ function createAuthMiddleware(password) {
|
|
|
1304
1318
|
});
|
|
1305
1319
|
return;
|
|
1306
1320
|
}
|
|
1307
|
-
const cookies = parseCookies(req.headers.cookie);
|
|
1308
|
-
const token = cookies[TOKEN_COOKIE];
|
|
1309
|
-
if (token && validTokens.has(token)) {
|
|
1310
|
-
next();
|
|
1311
|
-
return;
|
|
1312
|
-
}
|
|
1313
1321
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
1314
1322
|
res.status(200).send(LOGIN_PAGE_HTML);
|
|
1315
1323
|
};
|
|
1324
|
+
return {
|
|
1325
|
+
middleware,
|
|
1326
|
+
isRequestAuthorized: (req) => isAuthorizedByRequestLike(req.socket.remoteAddress, req.headers.host, req.headers.cookie, validTokens)
|
|
1327
|
+
};
|
|
1316
1328
|
}
|
|
1317
1329
|
|
|
1318
1330
|
// src/server/httpServer.ts
|
|
1331
|
+
import { WebSocketServer } from "ws";
|
|
1319
1332
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1320
1333
|
var distDir = join2(__dirname, "..", "dist");
|
|
1321
1334
|
var IMAGE_CONTENT_TYPES = {
|
|
@@ -1343,8 +1356,9 @@ function normalizeLocalImagePath(rawPath) {
|
|
|
1343
1356
|
function createServer(options = {}) {
|
|
1344
1357
|
const app = express();
|
|
1345
1358
|
const bridge = createCodexBridgeMiddleware();
|
|
1346
|
-
|
|
1347
|
-
|
|
1359
|
+
const authSession = options.password ? createAuthSession(options.password) : null;
|
|
1360
|
+
if (authSession) {
|
|
1361
|
+
app.use(authSession.middleware);
|
|
1348
1362
|
}
|
|
1349
1363
|
app.use(bridge);
|
|
1350
1364
|
app.get("/codex-local-image", (req, res) => {
|
|
@@ -1372,7 +1386,33 @@ function createServer(options = {}) {
|
|
|
1372
1386
|
});
|
|
1373
1387
|
return {
|
|
1374
1388
|
app,
|
|
1375
|
-
dispose: () => bridge.dispose()
|
|
1389
|
+
dispose: () => bridge.dispose(),
|
|
1390
|
+
attachWebSocket: (server) => {
|
|
1391
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
1392
|
+
server.on("upgrade", (req, socket, head) => {
|
|
1393
|
+
const url = new URL(req.url ?? "", "http://localhost");
|
|
1394
|
+
if (url.pathname !== "/codex-api/ws") {
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
if (authSession && !authSession.isRequestAuthorized(req)) {
|
|
1398
|
+
socket.write("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
|
|
1399
|
+
socket.destroy();
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
1403
|
+
wss.emit("connection", ws, req);
|
|
1404
|
+
});
|
|
1405
|
+
});
|
|
1406
|
+
wss.on("connection", (ws) => {
|
|
1407
|
+
ws.send(JSON.stringify({ method: "ready", params: { ok: true }, atIso: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
1408
|
+
const unsubscribe = bridge.subscribeNotifications((notification) => {
|
|
1409
|
+
if (ws.readyState !== 1) return;
|
|
1410
|
+
ws.send(JSON.stringify(notification));
|
|
1411
|
+
});
|
|
1412
|
+
ws.on("close", unsubscribe);
|
|
1413
|
+
ws.on("error", unsubscribe);
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1376
1416
|
};
|
|
1377
1417
|
}
|
|
1378
1418
|
|
|
@@ -1423,6 +1463,42 @@ function runWithStatus(command, args) {
|
|
|
1423
1463
|
function getUserNpmPrefix() {
|
|
1424
1464
|
return join3(homedir2(), ".npm-global");
|
|
1425
1465
|
}
|
|
1466
|
+
function installGlobalNpmPackageWithFallback(pkg, label) {
|
|
1467
|
+
const npmPrefix = spawnSync("npm", ["config", "get", "prefix"], {
|
|
1468
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1469
|
+
encoding: "utf8"
|
|
1470
|
+
}).stdout?.trim();
|
|
1471
|
+
const globalPrefixWritable = Boolean(npmPrefix && (() => {
|
|
1472
|
+
try {
|
|
1473
|
+
accessSync(npmPrefix, constants.W_OK);
|
|
1474
|
+
return true;
|
|
1475
|
+
} catch {
|
|
1476
|
+
return false;
|
|
1477
|
+
}
|
|
1478
|
+
})());
|
|
1479
|
+
if (!globalPrefixWritable && !isTermuxRuntime()) {
|
|
1480
|
+
const userPrefix2 = getUserNpmPrefix();
|
|
1481
|
+
console.log(`
|
|
1482
|
+
Global npm prefix is not writable. Installing with --prefix ${userPrefix2}...
|
|
1483
|
+
`);
|
|
1484
|
+
runOrFail("npm", ["install", "-g", "--prefix", userPrefix2, pkg], `${label} (user prefix)`);
|
|
1485
|
+
process.env.PATH = `${join3(userPrefix2, "bin")}:${process.env.PATH ?? ""}`;
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
const status = runWithStatus("npm", ["install", "-g", pkg]);
|
|
1489
|
+
if (status === 0) {
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
if (isTermuxRuntime()) {
|
|
1493
|
+
throw new Error(`${label} failed with exit code ${String(status)}`);
|
|
1494
|
+
}
|
|
1495
|
+
const userPrefix = getUserNpmPrefix();
|
|
1496
|
+
console.log(`
|
|
1497
|
+
Global npm install requires elevated permissions. Retrying with --prefix ${userPrefix}...
|
|
1498
|
+
`);
|
|
1499
|
+
runOrFail("npm", ["install", "-g", "--prefix", userPrefix, pkg], `${label} (user prefix)`);
|
|
1500
|
+
process.env.PATH = `${join3(userPrefix, "bin")}:${process.env.PATH ?? ""}`;
|
|
1501
|
+
}
|
|
1426
1502
|
function resolveCodexCommand() {
|
|
1427
1503
|
if (canRun("codex", ["--version"])) {
|
|
1428
1504
|
return "codex";
|
|
@@ -1448,32 +1524,17 @@ function hasCodexAuth() {
|
|
|
1448
1524
|
function ensureCodexInstalled() {
|
|
1449
1525
|
let codexCommand = resolveCodexCommand();
|
|
1450
1526
|
if (!codexCommand) {
|
|
1451
|
-
const installWithFallback = (pkg, label) => {
|
|
1452
|
-
const status = runWithStatus("npm", ["install", "-g", pkg]);
|
|
1453
|
-
if (status === 0) {
|
|
1454
|
-
return;
|
|
1455
|
-
}
|
|
1456
|
-
if (isTermuxRuntime()) {
|
|
1457
|
-
throw new Error(`${label} failed with exit code ${String(status)}`);
|
|
1458
|
-
}
|
|
1459
|
-
const userPrefix = getUserNpmPrefix();
|
|
1460
|
-
console.log(`
|
|
1461
|
-
Global npm install requires elevated permissions. Retrying with --prefix ${userPrefix}...
|
|
1462
|
-
`);
|
|
1463
|
-
runOrFail("npm", ["install", "-g", "--prefix", userPrefix, pkg], `${label} (user prefix)`);
|
|
1464
|
-
process.env.PATH = `${join3(userPrefix, "bin")}:${process.env.PATH ?? ""}`;
|
|
1465
|
-
};
|
|
1466
1527
|
if (isTermuxRuntime()) {
|
|
1467
1528
|
console.log("\nCodex CLI not found. Installing Termux-compatible Codex CLI from npm...\n");
|
|
1468
|
-
|
|
1529
|
+
installGlobalNpmPackageWithFallback("@mmmbuto/codex-cli-termux", "Codex CLI install");
|
|
1469
1530
|
codexCommand = resolveCodexCommand();
|
|
1470
1531
|
if (!codexCommand) {
|
|
1471
1532
|
console.log("\nTermux npm package did not expose `codex`. Installing official CLI fallback...\n");
|
|
1472
|
-
|
|
1533
|
+
installGlobalNpmPackageWithFallback("@openai/codex", "Codex CLI fallback install");
|
|
1473
1534
|
}
|
|
1474
1535
|
} else {
|
|
1475
1536
|
console.log("\nCodex CLI not found. Installing official Codex CLI from npm...\n");
|
|
1476
|
-
|
|
1537
|
+
installGlobalNpmPackageWithFallback("@openai/codex", "Codex CLI install");
|
|
1477
1538
|
}
|
|
1478
1539
|
codexCommand = resolveCodexCommand();
|
|
1479
1540
|
if (!codexCommand && !isTermuxRuntime()) {
|
|
@@ -1489,6 +1550,37 @@ Global npm install requires elevated permissions. Retrying with --prefix ${userP
|
|
|
1489
1550
|
}
|
|
1490
1551
|
return codexCommand;
|
|
1491
1552
|
}
|
|
1553
|
+
function resolveCloudflaredCommand() {
|
|
1554
|
+
if (canRun("cloudflared", ["--version"])) {
|
|
1555
|
+
return "cloudflared";
|
|
1556
|
+
}
|
|
1557
|
+
const userCandidate = join3(getUserNpmPrefix(), "bin", "cloudflared");
|
|
1558
|
+
if (existsSync(userCandidate) && canRun(userCandidate, ["--version"])) {
|
|
1559
|
+
return userCandidate;
|
|
1560
|
+
}
|
|
1561
|
+
const prefix = process.env.PREFIX?.trim();
|
|
1562
|
+
if (!prefix) {
|
|
1563
|
+
return null;
|
|
1564
|
+
}
|
|
1565
|
+
const candidate = join3(prefix, "bin", "cloudflared");
|
|
1566
|
+
if (existsSync(candidate) && canRun(candidate, ["--version"])) {
|
|
1567
|
+
return candidate;
|
|
1568
|
+
}
|
|
1569
|
+
return null;
|
|
1570
|
+
}
|
|
1571
|
+
function ensureCloudflaredInstalled() {
|
|
1572
|
+
let cloudflaredCommand = resolveCloudflaredCommand();
|
|
1573
|
+
if (!cloudflaredCommand) {
|
|
1574
|
+
console.log("\ncloudflared not found. Installing from npm...\n");
|
|
1575
|
+
installGlobalNpmPackageWithFallback("cloudflared", "cloudflared install");
|
|
1576
|
+
cloudflaredCommand = resolveCloudflaredCommand();
|
|
1577
|
+
if (!cloudflaredCommand) {
|
|
1578
|
+
throw new Error("cloudflared install completed but binary is still not available in PATH");
|
|
1579
|
+
}
|
|
1580
|
+
console.log("\ncloudflared installed.\n");
|
|
1581
|
+
}
|
|
1582
|
+
return cloudflaredCommand;
|
|
1583
|
+
}
|
|
1492
1584
|
function resolvePassword(input) {
|
|
1493
1585
|
if (input === false) {
|
|
1494
1586
|
return void 0;
|
|
@@ -1515,6 +1607,49 @@ function openBrowser(url) {
|
|
|
1515
1607
|
});
|
|
1516
1608
|
child.unref();
|
|
1517
1609
|
}
|
|
1610
|
+
function parseCloudflaredUrl(chunk) {
|
|
1611
|
+
const urlMatch = chunk.match(/https:\/\/[a-zA-Z0-9-]+\.trycloudflare\.com/g);
|
|
1612
|
+
if (!urlMatch || urlMatch.length === 0) {
|
|
1613
|
+
return null;
|
|
1614
|
+
}
|
|
1615
|
+
return urlMatch[urlMatch.length - 1] ?? null;
|
|
1616
|
+
}
|
|
1617
|
+
async function startCloudflaredTunnel(cloudflaredCommand, localPort) {
|
|
1618
|
+
return new Promise((resolve2, reject) => {
|
|
1619
|
+
const child = spawn2(cloudflaredCommand, ["tunnel", "--url", `http://localhost:${String(localPort)}`], {
|
|
1620
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1621
|
+
});
|
|
1622
|
+
const timeout = setTimeout(() => {
|
|
1623
|
+
child.kill("SIGTERM");
|
|
1624
|
+
reject(new Error("Timed out waiting for cloudflared tunnel URL"));
|
|
1625
|
+
}, 2e4);
|
|
1626
|
+
const handleData = (value) => {
|
|
1627
|
+
const text = String(value);
|
|
1628
|
+
const parsedUrl = parseCloudflaredUrl(text);
|
|
1629
|
+
if (!parsedUrl) {
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
clearTimeout(timeout);
|
|
1633
|
+
child.stdout?.off("data", handleData);
|
|
1634
|
+
child.stderr?.off("data", handleData);
|
|
1635
|
+
resolve2({ process: child, url: parsedUrl });
|
|
1636
|
+
};
|
|
1637
|
+
const onError = (error) => {
|
|
1638
|
+
clearTimeout(timeout);
|
|
1639
|
+
reject(new Error(`Failed to start cloudflared: ${error.message}`));
|
|
1640
|
+
};
|
|
1641
|
+
child.once("error", onError);
|
|
1642
|
+
child.stdout?.on("data", handleData);
|
|
1643
|
+
child.stderr?.on("data", handleData);
|
|
1644
|
+
child.once("exit", (code) => {
|
|
1645
|
+
if (code === 0) {
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
clearTimeout(timeout);
|
|
1649
|
+
reject(new Error(`cloudflared exited before providing a URL (code ${String(code)})`));
|
|
1650
|
+
});
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1518
1653
|
function listenWithFallback(server, startPort) {
|
|
1519
1654
|
return new Promise((resolve2, reject) => {
|
|
1520
1655
|
const attempt = (port) => {
|
|
@@ -1546,9 +1681,28 @@ async function startServer(options) {
|
|
|
1546
1681
|
}
|
|
1547
1682
|
const requestedPort = parseInt(options.port, 10);
|
|
1548
1683
|
const password = resolvePassword(options.password);
|
|
1549
|
-
const { app, dispose } = createServer({ password });
|
|
1684
|
+
const { app, dispose, attachWebSocket } = createServer({ password });
|
|
1550
1685
|
const server = createServer2(app);
|
|
1686
|
+
attachWebSocket(server);
|
|
1551
1687
|
const port = await listenWithFallback(server, requestedPort);
|
|
1688
|
+
let tunnelChild = null;
|
|
1689
|
+
let tunnelUrl = null;
|
|
1690
|
+
if (options.tunnel) {
|
|
1691
|
+
if (isTermuxRuntime()) {
|
|
1692
|
+
console.log("\n[cloudflared] Tunnel is disabled on Termux.");
|
|
1693
|
+
} else {
|
|
1694
|
+
try {
|
|
1695
|
+
const cloudflaredCommand = ensureCloudflaredInstalled();
|
|
1696
|
+
const tunnel = await startCloudflaredTunnel(cloudflaredCommand, port);
|
|
1697
|
+
tunnelChild = tunnel.process;
|
|
1698
|
+
tunnelUrl = tunnel.url;
|
|
1699
|
+
} catch (error) {
|
|
1700
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1701
|
+
console.warn(`
|
|
1702
|
+
[cloudflared] Tunnel not started: ${message}`);
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1552
1706
|
const lines = [
|
|
1553
1707
|
"",
|
|
1554
1708
|
"Codex Web Local is running!",
|
|
@@ -1563,12 +1717,25 @@ async function startServer(options) {
|
|
|
1563
1717
|
if (password) {
|
|
1564
1718
|
lines.push(` Password: ${password}`);
|
|
1565
1719
|
}
|
|
1720
|
+
if (tunnelUrl) {
|
|
1721
|
+
lines.push(` Tunnel: ${tunnelUrl}`);
|
|
1722
|
+
lines.push("");
|
|
1723
|
+
lines.push(" Tunnel QR code:");
|
|
1724
|
+
lines.push(` URL: ${tunnelUrl}`);
|
|
1725
|
+
}
|
|
1566
1726
|
printTermuxKeepAlive(lines);
|
|
1567
1727
|
lines.push("");
|
|
1568
1728
|
console.log(lines.join("\n"));
|
|
1729
|
+
if (tunnelUrl) {
|
|
1730
|
+
qrcode.generate(tunnelUrl, { small: true });
|
|
1731
|
+
console.log("");
|
|
1732
|
+
}
|
|
1569
1733
|
openBrowser(`http://localhost:${String(port)}`);
|
|
1570
1734
|
function shutdown() {
|
|
1571
1735
|
console.log("\nShutting down...");
|
|
1736
|
+
if (tunnelChild && !tunnelChild.killed) {
|
|
1737
|
+
tunnelChild.kill("SIGTERM");
|
|
1738
|
+
}
|
|
1572
1739
|
server.close(() => {
|
|
1573
1740
|
dispose();
|
|
1574
1741
|
process.exit(0);
|
|
@@ -1586,7 +1753,7 @@ async function runLogin() {
|
|
|
1586
1753
|
console.log("\nStarting `codex login`...\n");
|
|
1587
1754
|
runOrFail(codexCommand, ["login"], "Codex login");
|
|
1588
1755
|
}
|
|
1589
|
-
program.option("-p, --port <port>", "port to listen on", "5999").option("--password <pass>", "set a specific password").option("--no-password", "disable password protection").action(async (opts) => {
|
|
1756
|
+
program.option("-p, --port <port>", "port to listen on", "5999").option("--password <pass>", "set a specific password").option("--no-password", "disable password protection").option("--tunnel", "start cloudflared tunnel", true).option("--no-tunnel", "disable cloudflared tunnel startup").action(async (opts) => {
|
|
1590
1757
|
await startServer(opts);
|
|
1591
1758
|
});
|
|
1592
1759
|
program.command("login").description("Install/check Codex CLI and run `codex login`").action(runLogin);
|