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-DuLxR4eK.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-CB76aP9Z.css">
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 = randomBytes(32).toString("hex");
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));