codexapp 0.1.21 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.html
CHANGED
|
@@ -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-Dnsj8sPe.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C-HWk5QU.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body class="bg-slate-950">
|
|
11
11
|
<div id="app"></div>
|
package/dist-cli/index.js
CHANGED
|
@@ -14,17 +14,18 @@ import qrcode from "qrcode-terminal";
|
|
|
14
14
|
|
|
15
15
|
// src/server/httpServer.ts
|
|
16
16
|
import { fileURLToPath } from "url";
|
|
17
|
-
import { dirname, extname, isAbsolute as isAbsolute2, join as join2 } from "path";
|
|
17
|
+
import { basename as basename2, dirname, extname, isAbsolute as isAbsolute2, join as join2 } from "path";
|
|
18
18
|
import { existsSync } from "fs";
|
|
19
19
|
import express from "express";
|
|
20
20
|
|
|
21
21
|
// src/server/codexAppServerBridge.ts
|
|
22
22
|
import { spawn } from "child_process";
|
|
23
|
+
import { randomBytes } from "crypto";
|
|
23
24
|
import { mkdtemp, readFile, readdir, rm, mkdir, stat } from "fs/promises";
|
|
24
25
|
import { request as httpsRequest } from "https";
|
|
25
26
|
import { homedir } from "os";
|
|
26
27
|
import { tmpdir } from "os";
|
|
27
|
-
import { isAbsolute, join, resolve } from "path";
|
|
28
|
+
import { basename, isAbsolute, join, resolve } from "path";
|
|
28
29
|
import { writeFile } from "fs/promises";
|
|
29
30
|
function asRecord(value) {
|
|
30
31
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -121,6 +122,55 @@ async function runCommand(command, args, options = {}) {
|
|
|
121
122
|
});
|
|
122
123
|
});
|
|
123
124
|
}
|
|
125
|
+
function isMissingHeadError(error) {
|
|
126
|
+
const message = getErrorMessage(error, "").toLowerCase();
|
|
127
|
+
return message.includes("not a valid object name: 'head'") || message.includes("not a valid object name: head");
|
|
128
|
+
}
|
|
129
|
+
function isNotGitRepositoryError(error) {
|
|
130
|
+
const message = getErrorMessage(error, "").toLowerCase();
|
|
131
|
+
return message.includes("not a git repository") || message.includes("fatal: not a git repository");
|
|
132
|
+
}
|
|
133
|
+
async function ensureRepoHasInitialCommit(repoRoot) {
|
|
134
|
+
const agentsPath = join(repoRoot, "AGENTS.md");
|
|
135
|
+
try {
|
|
136
|
+
await stat(agentsPath);
|
|
137
|
+
} catch {
|
|
138
|
+
await writeFile(agentsPath, "", "utf8");
|
|
139
|
+
}
|
|
140
|
+
await runCommand("git", ["add", "AGENTS.md"], { cwd: repoRoot });
|
|
141
|
+
await runCommand(
|
|
142
|
+
"git",
|
|
143
|
+
["-c", "user.name=Codex", "-c", "user.email=codex@local", "commit", "-m", "Initialize repository for worktree support"],
|
|
144
|
+
{ cwd: repoRoot }
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
async function runCommandCapture(command, args, options = {}) {
|
|
148
|
+
return await new Promise((resolveOutput, reject) => {
|
|
149
|
+
const proc = spawn(command, args, {
|
|
150
|
+
cwd: options.cwd,
|
|
151
|
+
env: process.env,
|
|
152
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
153
|
+
});
|
|
154
|
+
let stdout = "";
|
|
155
|
+
let stderr = "";
|
|
156
|
+
proc.stdout.on("data", (chunk) => {
|
|
157
|
+
stdout += chunk.toString();
|
|
158
|
+
});
|
|
159
|
+
proc.stderr.on("data", (chunk) => {
|
|
160
|
+
stderr += chunk.toString();
|
|
161
|
+
});
|
|
162
|
+
proc.on("error", reject);
|
|
163
|
+
proc.on("close", (code) => {
|
|
164
|
+
if (code === 0) {
|
|
165
|
+
resolveOutput(stdout.trim());
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const details = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
|
|
169
|
+
const suffix = details.length > 0 ? `: ${details}` : "";
|
|
170
|
+
reject(new Error(`Command failed (${command} ${args.join(" ")})${suffix}`));
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
124
174
|
async function detectUserSkillsDir(appServer) {
|
|
125
175
|
try {
|
|
126
176
|
const result = await appServer.rpc("skills/list", {});
|
|
@@ -908,6 +958,76 @@ function createCodexBridgeMiddleware() {
|
|
|
908
958
|
setJson(res, 200, { data: { path: homedir() } });
|
|
909
959
|
return;
|
|
910
960
|
}
|
|
961
|
+
if (req.method === "POST" && url.pathname === "/codex-api/worktree/create") {
|
|
962
|
+
const payload = asRecord(await readJsonBody(req));
|
|
963
|
+
const rawSourceCwd = typeof payload?.sourceCwd === "string" ? payload.sourceCwd.trim() : "";
|
|
964
|
+
if (!rawSourceCwd) {
|
|
965
|
+
setJson(res, 400, { error: "Missing sourceCwd" });
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
const sourceCwd = isAbsolute(rawSourceCwd) ? rawSourceCwd : resolve(rawSourceCwd);
|
|
969
|
+
try {
|
|
970
|
+
const sourceInfo = await stat(sourceCwd);
|
|
971
|
+
if (!sourceInfo.isDirectory()) {
|
|
972
|
+
setJson(res, 400, { error: "sourceCwd is not a directory" });
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
} catch {
|
|
976
|
+
setJson(res, 404, { error: "sourceCwd does not exist" });
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
try {
|
|
980
|
+
let gitRoot = "";
|
|
981
|
+
try {
|
|
982
|
+
gitRoot = await runCommandCapture("git", ["rev-parse", "--show-toplevel"], { cwd: sourceCwd });
|
|
983
|
+
} catch (error) {
|
|
984
|
+
if (!isNotGitRepositoryError(error)) throw error;
|
|
985
|
+
await runCommand("git", ["init"], { cwd: sourceCwd });
|
|
986
|
+
gitRoot = await runCommandCapture("git", ["rev-parse", "--show-toplevel"], { cwd: sourceCwd });
|
|
987
|
+
}
|
|
988
|
+
const repoName = basename(gitRoot) || "repo";
|
|
989
|
+
const worktreesRoot = join(getCodexHomeDir(), "worktrees");
|
|
990
|
+
await mkdir(worktreesRoot, { recursive: true });
|
|
991
|
+
let worktreeId = "";
|
|
992
|
+
let worktreeParent = "";
|
|
993
|
+
let worktreeCwd = "";
|
|
994
|
+
for (let attempt = 0; attempt < 12; attempt += 1) {
|
|
995
|
+
const candidate = randomBytes(2).toString("hex");
|
|
996
|
+
const parent = join(worktreesRoot, candidate);
|
|
997
|
+
try {
|
|
998
|
+
await stat(parent);
|
|
999
|
+
continue;
|
|
1000
|
+
} catch {
|
|
1001
|
+
worktreeId = candidate;
|
|
1002
|
+
worktreeParent = parent;
|
|
1003
|
+
worktreeCwd = join(parent, repoName);
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if (!worktreeId || !worktreeParent || !worktreeCwd) {
|
|
1008
|
+
throw new Error("Failed to allocate a unique worktree id");
|
|
1009
|
+
}
|
|
1010
|
+
const branch = `codex/${worktreeId}`;
|
|
1011
|
+
await mkdir(worktreeParent, { recursive: true });
|
|
1012
|
+
try {
|
|
1013
|
+
await runCommand("git", ["worktree", "add", "-b", branch, worktreeCwd, "HEAD"], { cwd: gitRoot });
|
|
1014
|
+
} catch (error) {
|
|
1015
|
+
if (!isMissingHeadError(error)) throw error;
|
|
1016
|
+
await ensureRepoHasInitialCommit(gitRoot);
|
|
1017
|
+
await runCommand("git", ["worktree", "add", "-b", branch, worktreeCwd, "HEAD"], { cwd: gitRoot });
|
|
1018
|
+
}
|
|
1019
|
+
setJson(res, 200, {
|
|
1020
|
+
data: {
|
|
1021
|
+
cwd: worktreeCwd,
|
|
1022
|
+
branch,
|
|
1023
|
+
gitRoot
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
setJson(res, 500, { error: getErrorMessage(error, "Failed to create worktree") });
|
|
1028
|
+
}
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
911
1031
|
if (req.method === "PUT" && url.pathname === "/codex-api/workspace-roots-state") {
|
|
912
1032
|
const payload = await readJsonBody(req);
|
|
913
1033
|
const record = asRecord(payload);
|
|
@@ -1210,7 +1330,7 @@ data: ${JSON.stringify({ ok: true })}
|
|
|
1210
1330
|
}
|
|
1211
1331
|
|
|
1212
1332
|
// src/server/authMiddleware.ts
|
|
1213
|
-
import { randomBytes, timingSafeEqual } from "crypto";
|
|
1333
|
+
import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
1214
1334
|
var TOKEN_COOKIE = "codex_web_local_token";
|
|
1215
1335
|
function constantTimeCompare(a, b) {
|
|
1216
1336
|
const bufA = Buffer.from(a);
|
|
@@ -1308,7 +1428,7 @@ function createAuthSession(password) {
|
|
|
1308
1428
|
res.status(401).json({ error: "Invalid password" });
|
|
1309
1429
|
return;
|
|
1310
1430
|
}
|
|
1311
|
-
const token =
|
|
1431
|
+
const token = randomBytes2(32).toString("hex");
|
|
1312
1432
|
validTokens.add(token);
|
|
1313
1433
|
res.setHeader("Set-Cookie", `${TOKEN_COOKIE}=${token}; Path=/; HttpOnly; SameSite=Strict`);
|
|
1314
1434
|
res.json({ ok: true });
|
|
@@ -1354,6 +1474,18 @@ function normalizeLocalImagePath(rawPath) {
|
|
|
1354
1474
|
}
|
|
1355
1475
|
return trimmed;
|
|
1356
1476
|
}
|
|
1477
|
+
function normalizeLocalPath(rawPath) {
|
|
1478
|
+
const trimmed = rawPath.trim();
|
|
1479
|
+
if (!trimmed) return "";
|
|
1480
|
+
if (trimmed.startsWith("file://")) {
|
|
1481
|
+
try {
|
|
1482
|
+
return decodeURIComponent(trimmed.replace(/^file:\/\//u, ""));
|
|
1483
|
+
} catch {
|
|
1484
|
+
return trimmed.replace(/^file:\/\//u, "");
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
return trimmed;
|
|
1488
|
+
}
|
|
1357
1489
|
function createServer(options = {}) {
|
|
1358
1490
|
const app = express();
|
|
1359
1491
|
const bridge = createCodexBridgeMiddleware();
|
|
@@ -1381,6 +1513,19 @@ function createServer(options = {}) {
|
|
|
1381
1513
|
if (!res.headersSent) res.status(404).json({ error: "Image file not found." });
|
|
1382
1514
|
});
|
|
1383
1515
|
});
|
|
1516
|
+
app.get("/codex-local-file", (req, res) => {
|
|
1517
|
+
const rawPath = typeof req.query.path === "string" ? req.query.path : "";
|
|
1518
|
+
const localPath = normalizeLocalPath(rawPath);
|
|
1519
|
+
if (!localPath || !isAbsolute2(localPath)) {
|
|
1520
|
+
res.status(400).json({ error: "Expected absolute local file path." });
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
res.setHeader("Cache-Control", "private, no-store");
|
|
1524
|
+
res.download(localPath, basename2(localPath), { dotfiles: "allow" }, (error) => {
|
|
1525
|
+
if (!error) return;
|
|
1526
|
+
if (!res.headersSent) res.status(404).json({ error: "File not found." });
|
|
1527
|
+
});
|
|
1528
|
+
});
|
|
1384
1529
|
const hasFrontendAssets = existsSync(spaEntryFile);
|
|
1385
1530
|
if (hasFrontendAssets) {
|
|
1386
1531
|
app.use(express.static(distDir));
|