codexapp 0.1.4 → 0.1.8

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-Crq14xIZ.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-BBAMY_B8.css">
7
+ <script type="module" crossorigin src="/assets/index-DrAmX48U.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-BIm_B5t3.css">
9
9
  </head>
10
10
  <body class="bg-slate-950">
11
11
  <div id="app"></div>
package/dist-cli/index.js CHANGED
@@ -46,6 +46,45 @@ function setJson(res, statusCode, payload) {
46
46
  res.setHeader("Content-Type", "application/json; charset=utf-8");
47
47
  res.end(JSON.stringify(payload));
48
48
  }
49
+ function scoreFileCandidate(path, query) {
50
+ if (!query) return 0;
51
+ const lowerPath = path.toLowerCase();
52
+ const lowerQuery = query.toLowerCase();
53
+ const baseName = lowerPath.slice(lowerPath.lastIndexOf("/") + 1);
54
+ if (baseName === lowerQuery) return 0;
55
+ if (baseName.startsWith(lowerQuery)) return 1;
56
+ if (baseName.includes(lowerQuery)) return 2;
57
+ if (lowerPath.includes(`/${lowerQuery}`)) return 3;
58
+ if (lowerPath.includes(lowerQuery)) return 4;
59
+ return 10;
60
+ }
61
+ async function listFilesWithRipgrep(cwd) {
62
+ return await new Promise((resolve2, reject) => {
63
+ const proc = spawn("rg", ["--files", "--hidden", "-g", "!.git", "-g", "!node_modules"], {
64
+ cwd,
65
+ env: process.env,
66
+ stdio: ["ignore", "pipe", "pipe"]
67
+ });
68
+ let stdout = "";
69
+ let stderr = "";
70
+ proc.stdout.on("data", (chunk) => {
71
+ stdout += chunk.toString();
72
+ });
73
+ proc.stderr.on("data", (chunk) => {
74
+ stderr += chunk.toString();
75
+ });
76
+ proc.on("error", reject);
77
+ proc.on("close", (code) => {
78
+ if (code === 0) {
79
+ const rows = stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
80
+ resolve2(rows);
81
+ return;
82
+ }
83
+ const details = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
84
+ reject(new Error(details || "rg --files failed"));
85
+ });
86
+ });
87
+ }
49
88
  function getCodexHomeDir() {
50
89
  const codexHome = process.env.CODEX_HOME?.trim();
51
90
  return codexHome && codexHome.length > 0 ? codexHome : join(homedir(), ".codex");
@@ -365,6 +404,75 @@ async function readRawBody(req) {
365
404
  }
366
405
  return Buffer.concat(chunks);
367
406
  }
407
+ function bufferIndexOf(buf, needle, start = 0) {
408
+ for (let i = start; i <= buf.length - needle.length; i++) {
409
+ let match = true;
410
+ for (let j = 0; j < needle.length; j++) {
411
+ if (buf[i + j] !== needle[j]) {
412
+ match = false;
413
+ break;
414
+ }
415
+ }
416
+ if (match) return i;
417
+ }
418
+ return -1;
419
+ }
420
+ function handleFileUpload(req, res) {
421
+ const chunks = [];
422
+ req.on("data", (chunk) => chunks.push(chunk));
423
+ req.on("end", async () => {
424
+ try {
425
+ const body = Buffer.concat(chunks);
426
+ const contentType = req.headers["content-type"] ?? "";
427
+ const boundaryMatch = contentType.match(/boundary=(.+)/i);
428
+ if (!boundaryMatch) {
429
+ setJson(res, 400, { error: "Missing multipart boundary" });
430
+ return;
431
+ }
432
+ const boundary = boundaryMatch[1];
433
+ const boundaryBuf = Buffer.from(`--${boundary}`);
434
+ const parts = [];
435
+ let searchStart = 0;
436
+ while (searchStart < body.length) {
437
+ const idx = body.indexOf(boundaryBuf, searchStart);
438
+ if (idx < 0) break;
439
+ if (searchStart > 0) parts.push(body.subarray(searchStart, idx));
440
+ searchStart = idx + boundaryBuf.length;
441
+ if (body[searchStart] === 13 && body[searchStart + 1] === 10) searchStart += 2;
442
+ }
443
+ let fileName = "uploaded-file";
444
+ let fileData = null;
445
+ const headerSep = Buffer.from("\r\n\r\n");
446
+ for (const part of parts) {
447
+ const headerEnd = bufferIndexOf(part, headerSep);
448
+ if (headerEnd < 0) continue;
449
+ const headers = part.subarray(0, headerEnd).toString("utf8");
450
+ const fnMatch = headers.match(/filename="([^"]+)"/i);
451
+ if (!fnMatch) continue;
452
+ fileName = fnMatch[1].replace(/[/\\]/g, "_");
453
+ let end = part.length;
454
+ if (end >= 2 && part[end - 2] === 13 && part[end - 1] === 10) end -= 2;
455
+ fileData = part.subarray(headerEnd + 4, end);
456
+ break;
457
+ }
458
+ if (!fileData) {
459
+ setJson(res, 400, { error: "No file in request" });
460
+ return;
461
+ }
462
+ const uploadDir = join(tmpdir(), "codex-web-uploads");
463
+ await mkdir(uploadDir, { recursive: true });
464
+ const destDir = await mkdtemp(join(uploadDir, "f-"));
465
+ const destPath = join(destDir, fileName);
466
+ await writeFile(destPath, fileData);
467
+ setJson(res, 200, { path: destPath });
468
+ } catch (err) {
469
+ setJson(res, 500, { error: getErrorMessage(err, "Upload failed") });
470
+ }
471
+ });
472
+ req.on("error", (err) => {
473
+ setJson(res, 500, { error: getErrorMessage(err, "Upload stream error") });
474
+ });
475
+ }
368
476
  async function proxyTranscribe(body, contentType, authToken, accountId) {
369
477
  const headers = {
370
478
  "Content-Type": contentType,
@@ -403,11 +511,18 @@ var AppServerProcess = class {
403
511
  this.pending = /* @__PURE__ */ new Map();
404
512
  this.notificationListeners = /* @__PURE__ */ new Set();
405
513
  this.pendingServerRequests = /* @__PURE__ */ new Map();
514
+ this.appServerArgs = [
515
+ "app-server",
516
+ "-c",
517
+ 'approval_policy="never"',
518
+ "-c",
519
+ 'sandbox_mode="danger-full-access"'
520
+ ];
406
521
  }
407
522
  start() {
408
523
  if (this.process) return;
409
524
  this.stopping = false;
410
- const proc = spawn("codex", ["app-server"], { stdio: ["pipe", "pipe", "pipe"] });
525
+ const proc = spawn("codex", this.appServerArgs, { stdio: ["pipe", "pipe", "pipe"] });
411
526
  this.process = proc;
412
527
  proc.stdout.setEncoding("utf8");
413
528
  proc.stdout.on("data", (chunk) => {
@@ -733,6 +848,10 @@ function createCodexBridgeMiddleware() {
733
848
  return;
734
849
  }
735
850
  const url = new URL(req.url, "http://localhost");
851
+ if (req.method === "POST" && url.pathname === "/codex-api/upload-file") {
852
+ handleFileUpload(req, res);
853
+ return;
854
+ }
736
855
  if (req.method === "POST" && url.pathname === "/codex-api/rpc") {
737
856
  const payload = await readJsonBody(req);
738
857
  const body = asRecord(payload);
@@ -783,6 +902,10 @@ function createCodexBridgeMiddleware() {
783
902
  setJson(res, 200, { data: state });
784
903
  return;
785
904
  }
905
+ if (req.method === "GET" && url.pathname === "/codex-api/home-directory") {
906
+ setJson(res, 200, { data: { path: homedir() } });
907
+ return;
908
+ }
786
909
  if (req.method === "PUT" && url.pathname === "/codex-api/workspace-roots-state") {
787
910
  const payload = await readJsonBody(req);
788
911
  const record = asRecord(payload);
@@ -873,6 +996,36 @@ function createCodexBridgeMiddleware() {
873
996
  setJson(res, 500, { error: "Failed to compute project name suggestion" });
874
997
  return;
875
998
  }
999
+ if (req.method === "POST" && url.pathname === "/codex-api/composer-file-search") {
1000
+ const payload = asRecord(await readJsonBody(req));
1001
+ const rawCwd = typeof payload?.cwd === "string" ? payload.cwd.trim() : "";
1002
+ const query = typeof payload?.query === "string" ? payload.query.trim() : "";
1003
+ const limitRaw = typeof payload?.limit === "number" ? payload.limit : 20;
1004
+ const limit = Math.max(1, Math.min(100, Math.floor(limitRaw)));
1005
+ if (!rawCwd) {
1006
+ setJson(res, 400, { error: "Missing cwd" });
1007
+ return;
1008
+ }
1009
+ const cwd = isAbsolute(rawCwd) ? rawCwd : resolve(rawCwd);
1010
+ try {
1011
+ const info = await stat(cwd);
1012
+ if (!info.isDirectory()) {
1013
+ setJson(res, 400, { error: "cwd is not a directory" });
1014
+ return;
1015
+ }
1016
+ } catch {
1017
+ setJson(res, 404, { error: "cwd does not exist" });
1018
+ return;
1019
+ }
1020
+ try {
1021
+ const files = await listFilesWithRipgrep(cwd);
1022
+ const scored = files.map((path) => ({ path, score: scoreFileCandidate(path, query) })).filter((row) => query.length === 0 || row.score < 10).sort((a, b) => a.score - b.score || a.path.localeCompare(b.path)).slice(0, limit).map((row) => ({ path: row.path }));
1023
+ setJson(res, 200, { data: scored });
1024
+ } catch (error) {
1025
+ setJson(res, 500, { error: getErrorMessage(error, "Failed to search files") });
1026
+ }
1027
+ return;
1028
+ }
876
1029
  if (req.method === "GET" && url.pathname === "/codex-api/thread-titles") {
877
1030
  const cache = await readThreadTitleCache();
878
1031
  setJson(res, 200, { data: cache });
@@ -1263,10 +1416,21 @@ function runOrFail(command, args, label) {
1263
1416
  throw new Error(`${label} failed with exit code ${String(result.status ?? -1)}`);
1264
1417
  }
1265
1418
  }
1419
+ function runWithStatus(command, args) {
1420
+ const result = spawnSync(command, args, { stdio: "inherit" });
1421
+ return result.status ?? -1;
1422
+ }
1423
+ function getUserNpmPrefix() {
1424
+ return join3(homedir2(), ".npm-global");
1425
+ }
1266
1426
  function resolveCodexCommand() {
1267
1427
  if (canRun("codex", ["--version"])) {
1268
1428
  return "codex";
1269
1429
  }
1430
+ const userCandidate = join3(getUserNpmPrefix(), "bin", "codex");
1431
+ if (existsSync(userCandidate) && canRun(userCandidate, ["--version"])) {
1432
+ return userCandidate;
1433
+ }
1270
1434
  const prefix = process.env.PREFIX?.trim();
1271
1435
  if (!prefix) {
1272
1436
  return null;
@@ -1281,18 +1445,41 @@ function hasCodexAuth() {
1281
1445
  const codexHome = process.env.CODEX_HOME?.trim() || join3(homedir2(), ".codex");
1282
1446
  return existsSync(join3(codexHome, "auth.json"));
1283
1447
  }
1284
- function ensureTermuxCodexInstalled() {
1285
- if (!isTermuxRuntime()) {
1286
- return resolveCodexCommand();
1287
- }
1448
+ function ensureCodexInstalled() {
1288
1449
  let codexCommand = resolveCodexCommand();
1289
1450
  if (!codexCommand) {
1290
- console.log("\nCodex CLI not found. Installing Termux-compatible Codex CLI from npm...\n");
1291
- runOrFail("npm", ["install", "-g", "@mmmbuto/codex-cli-termux"], "Codex CLI install");
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
+ if (isTermuxRuntime()) {
1467
+ console.log("\nCodex CLI not found. Installing Termux-compatible Codex CLI from npm...\n");
1468
+ installWithFallback("@mmmbuto/codex-cli-termux", "Codex CLI install");
1469
+ codexCommand = resolveCodexCommand();
1470
+ if (!codexCommand) {
1471
+ console.log("\nTermux npm package did not expose `codex`. Installing official CLI fallback...\n");
1472
+ installWithFallback("@openai/codex", "Codex CLI fallback install");
1473
+ }
1474
+ } else {
1475
+ console.log("\nCodex CLI not found. Installing official Codex CLI from npm...\n");
1476
+ installWithFallback("@openai/codex", "Codex CLI install");
1477
+ }
1292
1478
  codexCommand = resolveCodexCommand();
1293
- if (!codexCommand) {
1294
- console.log("\nTermux npm package did not expose `codex`. Installing official CLI fallback...\n");
1295
- runOrFail("npm", ["install", "-g", "@openai/codex"], "Codex CLI fallback install");
1479
+ if (!codexCommand && !isTermuxRuntime()) {
1480
+ throw new Error("Official Codex CLI install completed but binary is still not available in PATH");
1481
+ }
1482
+ if (!codexCommand && isTermuxRuntime()) {
1296
1483
  codexCommand = resolveCodexCommand();
1297
1484
  }
1298
1485
  if (!codexCommand) {
@@ -1352,7 +1539,7 @@ function listenWithFallback(server, startPort) {
1352
1539
  }
1353
1540
  async function startServer(options) {
1354
1541
  const version = await readCliVersion();
1355
- const codexCommand = ensureTermuxCodexInstalled() ?? resolveCodexCommand();
1542
+ const codexCommand = ensureCodexInstalled() ?? resolveCodexCommand();
1356
1543
  if (!hasCodexAuth() && codexCommand) {
1357
1544
  console.log("\nCodex is not logged in. Starting `codex login`...\n");
1358
1545
  runOrFail(codexCommand, ["login"], "Codex login");
@@ -1394,14 +1581,14 @@ async function startServer(options) {
1394
1581
  process.on("SIGTERM", shutdown);
1395
1582
  }
1396
1583
  async function runLogin() {
1397
- const codexCommand = ensureTermuxCodexInstalled() ?? "codex";
1584
+ const codexCommand = ensureCodexInstalled() ?? "codex";
1398
1585
  console.log("\nStarting `codex login`...\n");
1399
1586
  runOrFail(codexCommand, ["login"], "Codex login");
1400
1587
  }
1401
- program.option("-p, --port <port>", "port to listen on", "3000").option("--password <pass>", "set a specific password").option("--no-password", "disable password protection").action(async (opts) => {
1588
+ 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) => {
1402
1589
  await startServer(opts);
1403
1590
  });
1404
- program.command("login").description("Install/check Codex CLI in Termux and run `codex login`").action(runLogin);
1591
+ program.command("login").description("Install/check Codex CLI and run `codex login`").action(runLogin);
1405
1592
  program.command("help").description("Show codexui command help").action(() => {
1406
1593
  program.outputHelp();
1407
1594
  });