codexapp 0.1.52 → 0.1.53

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-cli/index.js CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  // src/cli/index.ts
4
4
  import { createServer as createServer2 } from "http";
5
- import { chmodSync, createWriteStream, existsSync as existsSync5, mkdirSync } from "fs";
5
+ import { chmodSync, createWriteStream, existsSync as existsSync3, mkdirSync } from "fs";
6
6
  import { readFile as readFile4, stat as stat5, writeFile as writeFile4 } from "fs/promises";
7
- import { homedir as homedir4, networkInterfaces } from "os";
8
- import { isAbsolute as isAbsolute3, join as join6, resolve as resolve2 } from "path";
9
- import { spawn as spawn3 } from "child_process";
10
- import { createInterface as createInterface2 } from "readline/promises";
7
+ import { homedir as homedir3, networkInterfaces } from "os";
8
+ import { isAbsolute as isAbsolute3, join as join5, resolve as resolve2 } from "path";
9
+ import { spawn as spawn3, spawnSync } from "child_process";
10
+ import { createInterface } from "readline/promises";
11
11
  import { fileURLToPath as fileURLToPath2 } from "url";
12
12
  import { dirname as dirname3 } from "path";
13
13
  import { get as httpsGet } from "https";
@@ -16,8 +16,8 @@ import qrcode from "qrcode-terminal";
16
16
 
17
17
  // src/server/httpServer.ts
18
18
  import { fileURLToPath } from "url";
19
- import { dirname as dirname2, extname as extname3, isAbsolute as isAbsolute2, join as join5 } from "path";
20
- import { existsSync as existsSync4 } from "fs";
19
+ import { dirname as dirname2, extname as extname2, isAbsolute as isAbsolute2, join as join4 } from "path";
20
+ import { existsSync as existsSync2 } from "fs";
21
21
  import { writeFile as writeFile3, stat as stat4 } from "fs/promises";
22
22
  import express from "express";
23
23
 
@@ -25,13 +25,10 @@ import express from "express";
25
25
  import { spawn as spawn2 } from "child_process";
26
26
  import { randomBytes } from "crypto";
27
27
  import { mkdtemp as mkdtemp2, readFile as readFile2, mkdir as mkdir2, stat as stat2 } from "fs/promises";
28
- import { createReadStream } from "fs";
29
- import { request as httpRequest } from "http";
30
28
  import { request as httpsRequest } from "https";
31
- import { homedir as homedir3 } from "os";
29
+ import { homedir as homedir2 } from "os";
32
30
  import { tmpdir as tmpdir2 } from "os";
33
- import { basename as basename3, isAbsolute, join as join3, resolve } from "path";
34
- import { createInterface } from "readline";
31
+ import { basename, isAbsolute, join as join2, resolve } from "path";
35
32
  import { writeFile as writeFile2 } from "fs/promises";
36
33
 
37
34
  // src/server/skillsRoutes.ts
@@ -70,17 +67,6 @@ function getCodexHomeDir() {
70
67
  function getSkillsInstallDir() {
71
68
  return join(getCodexHomeDir(), "skills");
72
69
  }
73
- function resolveSkillInstallerScriptPath() {
74
- const candidates = [
75
- join(getCodexHomeDir(), "skills", ".system", "skill-installer", "scripts", "install-skill-from-github.py"),
76
- join(homedir(), ".codex", "skills", ".system", "skill-installer", "scripts", "install-skill-from-github.py"),
77
- join(homedir(), ".cursor", "skills", ".system", "skill-installer", "scripts", "install-skill-from-github.py")
78
- ];
79
- for (const candidate of candidates) {
80
- if (existsSync(candidate)) return candidate;
81
- }
82
- throw new Error(`Skill installer script not found. Checked: ${candidates.join(", ")}`);
83
- }
84
70
  async function runCommand(command, args, options = {}) {
85
71
  await new Promise((resolve3, reject) => {
86
72
  const proc = spawn(command, args, {
@@ -280,23 +266,6 @@ async function scanInstalledSkillsFromDisk() {
280
266
  }
281
267
  return map;
282
268
  }
283
- function extractSkillDescriptionFromMarkdown(markdown) {
284
- const lines = markdown.split(/\r?\n/);
285
- let inCodeFence = false;
286
- for (const rawLine of lines) {
287
- const line = rawLine.trim();
288
- if (line.startsWith("```")) {
289
- inCodeFence = !inCodeFence;
290
- continue;
291
- }
292
- if (inCodeFence || line.length === 0) continue;
293
- if (line.startsWith("#")) continue;
294
- if (line.startsWith(">")) continue;
295
- if (line.startsWith("- ") || line.startsWith("* ")) continue;
296
- return line;
297
- }
298
- return "";
299
- }
300
269
  function getSkillsSyncStatePath() {
301
270
  return join(getCodexHomeDir(), "skills-sync.json");
302
271
  }
@@ -1022,7 +991,7 @@ async function handleSkillsRoutes(req, res, url, context) {
1022
991
  }
1023
992
  const localDir = await detectUserSkillsDir(appServer);
1024
993
  await pullInstalledSkillsFolderFromRepo(state.githubToken, state.repoOwner, state.repoName);
1025
- const installerScript = resolveSkillInstallerScriptPath();
994
+ const installerScript = "/Users/igor/.cursor/skills/.system/skill-installer/scripts/install-skill-from-github.py";
1026
995
  const localSkills = await scanInstalledSkillsFromDisk();
1027
996
  for (const skill of remote) {
1028
997
  const owner = skill.owner || uniqueOwnerByName.get(skill.name) || "";
@@ -1069,29 +1038,15 @@ async function handleSkillsRoutes(req, res, url, context) {
1069
1038
  try {
1070
1039
  const owner = url.searchParams.get("owner") || "";
1071
1040
  const name = url.searchParams.get("name") || "";
1072
- const installed = url.searchParams.get("installed") === "true";
1073
- const skillPath = url.searchParams.get("path") || "";
1074
1041
  if (!owner || !name) {
1075
1042
  setJson(res, 400, { error: "Missing owner or name" });
1076
1043
  return true;
1077
1044
  }
1078
- if (installed) {
1079
- const installedMap = await scanInstalledSkillsFromDisk();
1080
- const installedInfo = installedMap.get(name);
1081
- const localSkillPath = installedInfo?.path || (skillPath ? skillPath.endsWith("/SKILL.md") ? skillPath : `${skillPath}/SKILL.md` : "");
1082
- if (localSkillPath) {
1083
- const content2 = await readFile(localSkillPath, "utf8");
1084
- const description2 = extractSkillDescriptionFromMarkdown(content2);
1085
- setJson(res, 200, { content: content2, description: description2, source: "local" });
1086
- return true;
1087
- }
1088
- }
1089
1045
  const rawUrl = `https://raw.githubusercontent.com/${HUB_SKILLS_OWNER}/${HUB_SKILLS_REPO}/main/skills/${owner}/${name}/SKILL.md`;
1090
1046
  const resp = await fetch(rawUrl);
1091
1047
  if (!resp.ok) throw new Error(`Failed to fetch SKILL.md: ${resp.status}`);
1092
1048
  const content = await resp.text();
1093
- const description = extractSkillDescriptionFromMarkdown(content);
1094
- setJson(res, 200, { content, description, source: "remote" });
1049
+ setJson(res, 200, { content });
1095
1050
  } catch (error) {
1096
1051
  setJson(res, 502, { error: getErrorMessage(error, "Failed to fetch SKILL.md") });
1097
1052
  }
@@ -1106,7 +1061,7 @@ async function handleSkillsRoutes(req, res, url, context) {
1106
1061
  setJson(res, 400, { error: "Missing owner or name" });
1107
1062
  return true;
1108
1063
  }
1109
- const installerScript = resolveSkillInstallerScriptPath();
1064
+ const installerScript = "/Users/igor/.cursor/skills/.system/skill-installer/scripts/install-skill-from-github.py";
1110
1065
  const installDest = await detectUserSkillsDir(appServer);
1111
1066
  await runCommand("python3", [
1112
1067
  installerScript,
@@ -1162,8 +1117,7 @@ async function handleSkillsRoutes(req, res, url, context) {
1162
1117
  return false;
1163
1118
  }
1164
1119
 
1165
- // src/server/telegramThreadBridge.ts
1166
- import { basename } from "path";
1120
+ // src/server/codexAppServerBridge.ts
1167
1121
  function asRecord2(value) {
1168
1122
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
1169
1123
  }
@@ -1181,464 +1135,21 @@ function getErrorMessage2(payload, fallback) {
1181
1135
  }
1182
1136
  return fallback;
1183
1137
  }
1184
- var TelegramThreadBridge = class {
1185
- constructor(appServer) {
1186
- this.threadIdByChatId = /* @__PURE__ */ new Map();
1187
- this.chatIdsByThreadId = /* @__PURE__ */ new Map();
1188
- this.lastForwardedTurnByThreadId = /* @__PURE__ */ new Map();
1189
- this.active = false;
1190
- this.pollingTask = null;
1191
- this.nextUpdateOffset = 0;
1192
- this.lastError = "";
1193
- this.appServer = appServer;
1194
- this.token = process.env.TELEGRAM_BOT_TOKEN?.trim() ?? "";
1195
- this.defaultCwd = process.env.TELEGRAM_DEFAULT_CWD?.trim() ?? process.cwd();
1196
- }
1197
- start() {
1198
- if (!this.token || this.active) return;
1199
- this.active = true;
1200
- void this.notifyOnlineForKnownChats().catch(() => {
1201
- });
1202
- this.pollingTask = this.pollLoop();
1203
- this.appServer.onNotification((notification) => {
1204
- void this.handleNotification(notification).catch(() => {
1205
- });
1206
- });
1207
- }
1208
- stop() {
1209
- this.active = false;
1210
- }
1211
- async pollLoop() {
1212
- while (this.active) {
1213
- try {
1214
- const updates = await this.getUpdates();
1215
- this.lastError = "";
1216
- for (const update of updates) {
1217
- const updateId = typeof update.update_id === "number" ? update.update_id : -1;
1218
- if (updateId >= 0) {
1219
- this.nextUpdateOffset = Math.max(this.nextUpdateOffset, updateId + 1);
1220
- }
1221
- await this.handleIncomingUpdate(update);
1222
- }
1223
- } catch (error) {
1224
- this.lastError = getErrorMessage2(error, "Telegram polling failed");
1225
- await new Promise((resolve3) => setTimeout(resolve3, 1500));
1226
- }
1227
- }
1228
- }
1229
- async getUpdates() {
1230
- if (!this.token) {
1231
- throw new Error("Telegram bot token is not configured");
1232
- }
1233
- const response = await fetch(this.apiUrl("getUpdates"), {
1234
- method: "POST",
1235
- headers: { "Content-Type": "application/json" },
1236
- body: JSON.stringify({
1237
- timeout: 45,
1238
- offset: this.nextUpdateOffset,
1239
- allowed_updates: ["message", "callback_query"]
1240
- })
1241
- });
1242
- const payload = asRecord2(await response.json());
1243
- const result = Array.isArray(payload?.result) ? payload.result : [];
1244
- return result;
1245
- }
1246
- apiUrl(method) {
1247
- return `https://api.telegram.org/bot${this.token}/${method}`;
1248
- }
1249
- configureToken(token) {
1250
- const normalizedToken = token.trim();
1251
- if (!normalizedToken) {
1252
- throw new Error("Telegram bot token is required");
1253
- }
1254
- this.token = normalizedToken;
1255
- }
1256
- getStatus() {
1257
- return {
1258
- configured: this.token.length > 0,
1259
- active: this.active,
1260
- mappedChats: this.threadIdByChatId.size,
1261
- mappedThreads: this.chatIdsByThreadId.size,
1262
- lastError: this.lastError
1263
- };
1264
- }
1265
- connectThread(threadId, chatId, token) {
1266
- const normalizedThreadId = threadId.trim();
1267
- if (!normalizedThreadId) {
1268
- throw new Error("threadId is required");
1269
- }
1270
- if (!Number.isFinite(chatId)) {
1271
- throw new Error("chatId must be a number");
1272
- }
1273
- if (typeof token === "string" && token.trim().length > 0) {
1274
- this.configureToken(token);
1275
- }
1276
- if (!this.token) {
1277
- throw new Error("Telegram bot token is not configured");
1278
- }
1279
- this.bindChatToThread(chatId, normalizedThreadId);
1280
- this.start();
1281
- void this.sendOnlineMessage(chatId).catch(() => {
1282
- });
1283
- }
1284
- async sendTelegramMessage(chatId, text, options = {}) {
1285
- const message = text.trim();
1286
- if (!message) return;
1287
- const payload = { chat_id: chatId, text: message };
1288
- if (options.replyMarkup) {
1289
- payload.reply_markup = options.replyMarkup;
1290
- }
1291
- await fetch(this.apiUrl("sendMessage"), {
1292
- method: "POST",
1293
- headers: { "Content-Type": "application/json" },
1294
- body: JSON.stringify(payload)
1295
- });
1296
- }
1297
- async sendOnlineMessage(chatId) {
1298
- await this.sendTelegramMessage(chatId, "Codex thread bridge went online.");
1299
- }
1300
- async notifyOnlineForKnownChats() {
1301
- const knownChatIds = Array.from(this.threadIdByChatId.keys());
1302
- for (const chatId of knownChatIds) {
1303
- await this.sendOnlineMessage(chatId);
1304
- }
1305
- }
1306
- async handleIncomingUpdate(update) {
1307
- if (update.callback_query) {
1308
- await this.handleCallbackQuery(update.callback_query);
1309
- return;
1310
- }
1311
- const message = update.message;
1312
- const chatId = message?.chat?.id;
1313
- const text = message?.text?.trim();
1314
- if (typeof chatId !== "number" || !text) return;
1315
- if (text === "/start") {
1316
- await this.sendThreadPicker(chatId);
1317
- return;
1318
- }
1319
- if (text === "/newthread") {
1320
- const threadId2 = await this.createThreadForChat(chatId);
1321
- await this.sendTelegramMessage(chatId, `Mapped to new thread: ${threadId2}`);
1322
- return;
1323
- }
1324
- const threadCommand = text.match(/^\/thread\s+(\S+)$/);
1325
- if (threadCommand) {
1326
- const threadId2 = threadCommand[1];
1327
- this.bindChatToThread(chatId, threadId2);
1328
- await this.sendTelegramMessage(chatId, `Mapped to thread: ${threadId2}`);
1329
- return;
1330
- }
1331
- const threadId = await this.ensureThreadForChat(chatId);
1332
- try {
1333
- await this.appServer.rpc("turn/start", {
1334
- threadId,
1335
- input: [{ type: "text", text }]
1336
- });
1337
- } catch (error) {
1338
- const message2 = getErrorMessage2(error, "Failed to forward message to thread");
1339
- await this.sendTelegramMessage(chatId, `Forward failed: ${message2}`);
1340
- }
1341
- }
1342
- async handleCallbackQuery(callbackQuery) {
1343
- const callbackId = typeof callbackQuery.id === "string" ? callbackQuery.id : "";
1344
- const data = typeof callbackQuery.data === "string" ? callbackQuery.data : "";
1345
- const chatId = callbackQuery.message?.chat?.id;
1346
- if (!callbackId) return;
1347
- if (!data.startsWith("thread:") || typeof chatId !== "number") {
1348
- await this.answerCallbackQuery(callbackId, "Invalid selection");
1349
- return;
1350
- }
1351
- const threadId = data.slice("thread:".length).trim();
1352
- if (!threadId) {
1353
- await this.answerCallbackQuery(callbackId, "Invalid thread id");
1354
- return;
1355
- }
1356
- this.bindChatToThread(chatId, threadId);
1357
- await this.answerCallbackQuery(callbackId, "Thread connected");
1358
- await this.sendTelegramMessage(chatId, `Connected to thread: ${threadId}`);
1359
- const history = await this.readThreadHistorySummary(threadId);
1360
- if (history) {
1361
- await this.sendTelegramMessage(chatId, history);
1362
- }
1363
- }
1364
- async answerCallbackQuery(callbackQueryId, text) {
1365
- await fetch(this.apiUrl("answerCallbackQuery"), {
1366
- method: "POST",
1367
- headers: { "Content-Type": "application/json" },
1368
- body: JSON.stringify({
1369
- callback_query_id: callbackQueryId,
1370
- text
1371
- })
1372
- });
1373
- }
1374
- async sendThreadPicker(chatId) {
1375
- const threads = await this.listRecentThreads();
1376
- if (threads.length === 0) {
1377
- await this.sendTelegramMessage(chatId, "No threads found. Send /newthread to create one.");
1378
- return;
1379
- }
1380
- const inlineKeyboard = threads.map((thread) => [
1381
- {
1382
- text: thread.title,
1383
- callback_data: `thread:${thread.id}`
1384
- }
1385
- ]);
1386
- await this.sendTelegramMessage(chatId, "Select a thread to connect:", {
1387
- replyMarkup: { inline_keyboard: inlineKeyboard }
1388
- });
1389
- }
1390
- async listRecentThreads() {
1391
- const payload = asRecord2(await this.appServer.rpc("thread/list", {
1392
- archived: false,
1393
- limit: 20,
1394
- sortKey: "updated_at"
1395
- }));
1396
- const rows = Array.isArray(payload?.data) ? payload.data : [];
1397
- const threads = [];
1398
- for (const row of rows) {
1399
- const record = asRecord2(row);
1400
- const id = typeof record?.id === "string" ? record.id.trim() : "";
1401
- if (!id) continue;
1402
- const name = typeof record?.name === "string" ? record.name.trim() : "";
1403
- const preview = typeof record?.preview === "string" ? record.preview.trim() : "";
1404
- const cwd = typeof record?.cwd === "string" ? record.cwd.trim() : "";
1405
- const projectName = cwd ? basename(cwd) : "project";
1406
- const threadTitle = (name || preview || id).replace(/\s+/g, " ").trim();
1407
- const title = `${projectName}/${threadTitle}`.slice(0, 64);
1408
- threads.push({ id, title });
1409
- }
1410
- return threads;
1411
- }
1412
- async createThreadForChat(chatId) {
1413
- const response = asRecord2(await this.appServer.rpc("thread/start", { cwd: this.defaultCwd }));
1414
- const thread = asRecord2(response?.thread);
1415
- const threadId = typeof thread?.id === "string" ? thread.id : "";
1416
- if (!threadId) {
1417
- throw new Error("thread/start did not return thread id");
1418
- }
1419
- this.bindChatToThread(chatId, threadId);
1420
- return threadId;
1421
- }
1422
- async ensureThreadForChat(chatId) {
1423
- const existing = this.threadIdByChatId.get(chatId);
1424
- if (existing) return existing;
1425
- return this.createThreadForChat(chatId);
1426
- }
1427
- bindChatToThread(chatId, threadId) {
1428
- const previousThreadId = this.threadIdByChatId.get(chatId);
1429
- if (previousThreadId && previousThreadId !== threadId) {
1430
- const previousSet = this.chatIdsByThreadId.get(previousThreadId);
1431
- previousSet?.delete(chatId);
1432
- if (previousSet && previousSet.size === 0) {
1433
- this.chatIdsByThreadId.delete(previousThreadId);
1434
- }
1435
- }
1436
- this.threadIdByChatId.set(chatId, threadId);
1437
- const chatIds = this.chatIdsByThreadId.get(threadId) ?? /* @__PURE__ */ new Set();
1438
- chatIds.add(chatId);
1439
- this.chatIdsByThreadId.set(threadId, chatIds);
1440
- }
1441
- extractThreadId(notification) {
1442
- const params = asRecord2(notification.params);
1443
- if (!params) return "";
1444
- const directThreadId = typeof params.threadId === "string" ? params.threadId : "";
1445
- if (directThreadId) return directThreadId;
1446
- const turn = asRecord2(params.turn);
1447
- const turnThreadId = typeof turn?.threadId === "string" ? turn.threadId : "";
1448
- return turnThreadId;
1449
- }
1450
- extractTurnId(notification) {
1451
- const params = asRecord2(notification.params);
1452
- if (!params) return "";
1453
- const directTurnId = typeof params.turnId === "string" ? params.turnId : "";
1454
- if (directTurnId) return directTurnId;
1455
- const turn = asRecord2(params.turn);
1456
- const turnId = typeof turn?.id === "string" ? turn.id : "";
1457
- return turnId;
1458
- }
1459
- async handleNotification(notification) {
1460
- if (notification.method !== "turn/completed") return;
1461
- const threadId = this.extractThreadId(notification);
1462
- if (!threadId) return;
1463
- const chatIds = this.chatIdsByThreadId.get(threadId);
1464
- if (!chatIds || chatIds.size === 0) return;
1465
- const turnId = this.extractTurnId(notification);
1466
- const lastForwardedTurnId = this.lastForwardedTurnByThreadId.get(threadId);
1467
- if (turnId && lastForwardedTurnId === turnId) return;
1468
- const assistantReply = await this.readLatestAssistantMessage(threadId);
1469
- if (!assistantReply) return;
1470
- for (const chatId of chatIds) {
1471
- await this.sendTelegramMessage(chatId, assistantReply);
1472
- }
1473
- if (turnId) {
1474
- this.lastForwardedTurnByThreadId.set(threadId, turnId);
1475
- }
1476
- }
1477
- async readLatestAssistantMessage(threadId) {
1478
- const response = asRecord2(await this.appServer.rpc("thread/read", { threadId, includeTurns: true }));
1479
- const thread = asRecord2(response?.thread);
1480
- const turns = Array.isArray(thread?.turns) ? thread.turns : [];
1481
- for (let turnIndex = turns.length - 1; turnIndex >= 0; turnIndex -= 1) {
1482
- const turn = asRecord2(turns[turnIndex]);
1483
- const items = Array.isArray(turn?.items) ? turn.items : [];
1484
- for (let itemIndex = items.length - 1; itemIndex >= 0; itemIndex -= 1) {
1485
- const item = asRecord2(items[itemIndex]);
1486
- if (item?.type === "agentMessage") {
1487
- const text = typeof item.text === "string" ? item.text.trim() : "";
1488
- if (text) return text;
1489
- }
1490
- }
1491
- }
1492
- return "";
1493
- }
1494
- async readThreadHistorySummary(threadId) {
1495
- const response = asRecord2(await this.appServer.rpc("thread/read", { threadId, includeTurns: true }));
1496
- const thread = asRecord2(response?.thread);
1497
- const turns = Array.isArray(thread?.turns) ? thread.turns : [];
1498
- const historyRows = [];
1499
- for (const turn of turns) {
1500
- const turnRecord = asRecord2(turn);
1501
- const items = Array.isArray(turnRecord?.items) ? turnRecord.items : [];
1502
- for (const item of items) {
1503
- const itemRecord = asRecord2(item);
1504
- const type = typeof itemRecord?.type === "string" ? itemRecord.type : "";
1505
- if (type === "userMessage") {
1506
- const content = Array.isArray(itemRecord?.content) ? itemRecord.content : [];
1507
- for (const block of content) {
1508
- const blockRecord = asRecord2(block);
1509
- if (blockRecord?.type === "text" && typeof blockRecord.text === "string" && blockRecord.text.trim()) {
1510
- historyRows.push(`User: ${blockRecord.text.trim()}`);
1511
- }
1512
- }
1513
- }
1514
- if (type === "agentMessage" && typeof itemRecord?.text === "string" && itemRecord.text.trim()) {
1515
- historyRows.push(`Assistant: ${itemRecord.text.trim()}`);
1516
- }
1517
- }
1518
- }
1519
- if (historyRows.length === 0) {
1520
- return "Thread has no message history yet.";
1521
- }
1522
- const tail = historyRows.slice(-12).join("\n\n");
1523
- const maxLen = 3800;
1524
- const summary = tail.length > maxLen ? tail.slice(tail.length - maxLen) : tail;
1525
- return `Recent history:
1526
-
1527
- ${summary}`;
1528
- }
1529
- };
1530
-
1531
- // src/utils/commandInvocation.ts
1532
- import { spawnSync } from "child_process";
1533
- import { existsSync as existsSync2 } from "fs";
1534
- import { homedir as homedir2 } from "os";
1535
- import { basename as basename2, extname, join as join2 } from "path";
1536
- var WINDOWS_CMD_NAMES = /* @__PURE__ */ new Set(["codex", "npm", "npx"]);
1537
- function quoteCmdExeArg(value) {
1538
- const normalized = value.replace(/"/g, '""');
1539
- if (!/[\s"]/u.test(normalized)) {
1540
- return normalized;
1541
- }
1542
- return `"${normalized}"`;
1543
- }
1544
- function needsCmdExeWrapper(command) {
1545
- if (process.platform !== "win32") {
1546
- return false;
1547
- }
1548
- const lowerCommand = command.toLowerCase();
1549
- const baseName = basename2(lowerCommand);
1550
- if (/\.(cmd|bat)$/i.test(baseName)) {
1551
- return true;
1552
- }
1553
- if (extname(baseName)) {
1554
- return false;
1555
- }
1556
- return WINDOWS_CMD_NAMES.has(baseName);
1557
- }
1558
- function getSpawnInvocation(command, args = []) {
1559
- if (needsCmdExeWrapper(command)) {
1560
- return {
1561
- command: "cmd.exe",
1562
- args: ["/d", "/s", "/c", [quoteCmdExeArg(command), ...args.map((arg) => quoteCmdExeArg(arg))].join(" ")]
1563
- };
1564
- }
1565
- return { command, args };
1566
- }
1567
- function spawnSyncCommand(command, args = [], options = {}) {
1568
- const invocation = getSpawnInvocation(command, args);
1569
- return spawnSync(invocation.command, invocation.args, options);
1570
- }
1571
- function canRunCommand(command, args = []) {
1572
- const result = spawnSyncCommand(command, args, { stdio: "ignore" });
1573
- return result.status === 0;
1574
- }
1575
- function getUserNpmPrefix() {
1576
- return join2(homedir2(), ".npm-global");
1577
- }
1578
- function resolveCodexCommand() {
1579
- if (canRunCommand("codex", ["--version"])) {
1580
- return "codex";
1581
- }
1582
- if (process.platform === "win32") {
1583
- const windowsCandidates = [
1584
- process.env.APPDATA ? join2(process.env.APPDATA, "npm", "codex.cmd") : "",
1585
- join2(homedir2(), ".local", "bin", "codex.cmd"),
1586
- join2(getUserNpmPrefix(), "bin", "codex.cmd")
1587
- ].filter(Boolean);
1588
- for (const candidate2 of windowsCandidates) {
1589
- if (existsSync2(candidate2) && canRunCommand(candidate2, ["--version"])) {
1590
- return candidate2;
1591
- }
1592
- }
1593
- }
1594
- const userCandidate = join2(getUserNpmPrefix(), "bin", "codex");
1595
- if (existsSync2(userCandidate) && canRunCommand(userCandidate, ["--version"])) {
1596
- return userCandidate;
1597
- }
1598
- const prefix = process.env.PREFIX?.trim();
1599
- if (!prefix) {
1600
- return null;
1601
- }
1602
- const candidate = join2(prefix, "bin", "codex");
1603
- if (existsSync2(candidate) && canRunCommand(candidate, ["--version"])) {
1604
- return candidate;
1605
- }
1606
- return null;
1607
- }
1608
-
1609
- // src/server/codexAppServerBridge.ts
1610
- function asRecord3(value) {
1611
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
1612
- }
1613
- function getErrorMessage3(payload, fallback) {
1614
- if (payload instanceof Error && payload.message.trim().length > 0) {
1615
- return payload.message;
1616
- }
1617
- const record = asRecord3(payload);
1618
- if (!record) return fallback;
1619
- const error = record.error;
1620
- if (typeof error === "string" && error.length > 0) return error;
1621
- const nestedError = asRecord3(error);
1622
- if (nestedError && typeof nestedError.message === "string" && nestedError.message.length > 0) {
1623
- return nestedError.message;
1624
- }
1625
- return fallback;
1626
- }
1627
1138
  function setJson2(res, statusCode, payload) {
1628
1139
  res.statusCode = statusCode;
1629
1140
  res.setHeader("Content-Type", "application/json; charset=utf-8");
1630
1141
  res.end(JSON.stringify(payload));
1631
1142
  }
1632
1143
  function extractThreadMessageText(threadReadPayload) {
1633
- const payload = asRecord3(threadReadPayload);
1634
- const thread = asRecord3(payload?.thread);
1144
+ const payload = asRecord2(threadReadPayload);
1145
+ const thread = asRecord2(payload?.thread);
1635
1146
  const turns = Array.isArray(thread?.turns) ? thread.turns : [];
1636
1147
  const parts = [];
1637
1148
  for (const turn of turns) {
1638
- const turnRecord = asRecord3(turn);
1149
+ const turnRecord = asRecord2(turn);
1639
1150
  const items = Array.isArray(turnRecord?.items) ? turnRecord.items : [];
1640
1151
  for (const item of items) {
1641
- const itemRecord = asRecord3(item);
1152
+ const itemRecord = asRecord2(item);
1642
1153
  const type = typeof itemRecord?.type === "string" ? itemRecord.type : "";
1643
1154
  if (type === "agentMessage" && typeof itemRecord?.text === "string" && itemRecord.text.trim().length > 0) {
1644
1155
  parts.push(itemRecord.text.trim());
@@ -1647,7 +1158,7 @@ function extractThreadMessageText(threadReadPayload) {
1647
1158
  if (type === "userMessage") {
1648
1159
  const content = Array.isArray(itemRecord?.content) ? itemRecord.content : [];
1649
1160
  for (const block of content) {
1650
- const blockRecord = asRecord3(block);
1161
+ const blockRecord = asRecord2(block);
1651
1162
  if (blockRecord?.type === "text" && typeof blockRecord.text === "string" && blockRecord.text.trim().length > 0) {
1652
1163
  parts.push(blockRecord.text.trim());
1653
1164
  }
@@ -1710,7 +1221,7 @@ async function listFilesWithRipgrep(cwd) {
1710
1221
  }
1711
1222
  function getCodexHomeDir2() {
1712
1223
  const codexHome = process.env.CODEX_HOME?.trim();
1713
- return codexHome && codexHome.length > 0 ? codexHome : join3(homedir3(), ".codex");
1224
+ return codexHome && codexHome.length > 0 ? codexHome : join2(homedir2(), ".codex");
1714
1225
  }
1715
1226
  async function runCommand2(command, args, options = {}) {
1716
1227
  await new Promise((resolve3, reject) => {
@@ -1740,15 +1251,15 @@ async function runCommand2(command, args, options = {}) {
1740
1251
  });
1741
1252
  }
1742
1253
  function isMissingHeadError(error) {
1743
- const message = getErrorMessage3(error, "").toLowerCase();
1254
+ const message = getErrorMessage2(error, "").toLowerCase();
1744
1255
  return message.includes("not a valid object name: 'head'") || message.includes("not a valid object name: head") || message.includes("invalid reference: head");
1745
1256
  }
1746
1257
  function isNotGitRepositoryError(error) {
1747
- const message = getErrorMessage3(error, "").toLowerCase();
1258
+ const message = getErrorMessage2(error, "").toLowerCase();
1748
1259
  return message.includes("not a git repository") || message.includes("fatal: not a git repository");
1749
1260
  }
1750
1261
  async function ensureRepoHasInitialCommit(repoRoot) {
1751
- const agentsPath = join3(repoRoot, "AGENTS.md");
1262
+ const agentsPath = join2(repoRoot, "AGENTS.md");
1752
1263
  try {
1753
1264
  await stat2(agentsPath);
1754
1265
  } catch {
@@ -1788,33 +1299,6 @@ async function runCommandCapture(command, args, options = {}) {
1788
1299
  });
1789
1300
  });
1790
1301
  }
1791
- async function runCommandWithOutput2(command, args, options = {}) {
1792
- return await new Promise((resolve3, reject) => {
1793
- const proc = spawn2(command, args, {
1794
- cwd: options.cwd,
1795
- env: process.env,
1796
- stdio: ["ignore", "pipe", "pipe"]
1797
- });
1798
- let stdout = "";
1799
- let stderr = "";
1800
- proc.stdout.on("data", (chunk) => {
1801
- stdout += chunk.toString();
1802
- });
1803
- proc.stderr.on("data", (chunk) => {
1804
- stderr += chunk.toString();
1805
- });
1806
- proc.on("error", reject);
1807
- proc.on("close", (code) => {
1808
- if (code === 0) {
1809
- resolve3(stdout.trim());
1810
- return;
1811
- }
1812
- const details = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
1813
- const suffix = details.length > 0 ? `: ${details}` : "";
1814
- reject(new Error(`Command failed (${command} ${args.join(" ")})${suffix}`));
1815
- });
1816
- });
1817
- }
1818
1302
  function normalizeStringArray(value) {
1819
1303
  if (!Array.isArray(value)) return [];
1820
1304
  const normalized = [];
@@ -1835,32 +1319,8 @@ function normalizeStringRecord(value) {
1835
1319
  }
1836
1320
  return next;
1837
1321
  }
1838
- function normalizeCommitMessage(value) {
1839
- if (typeof value !== "string") return "";
1840
- const normalized = value.replace(/\r\n?/gu, "\n").split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n").trim();
1841
- return normalized.slice(0, 2e3);
1842
- }
1843
- async function hasGitWorkingTreeChanges(cwd) {
1844
- const status = await runCommandWithOutput2("git", ["status", "--porcelain"], { cwd });
1845
- return status.trim().length > 0;
1846
- }
1847
- async function findCommitByExactMessage(cwd, message) {
1848
- const normalizedTarget = normalizeCommitMessage(message);
1849
- if (!normalizedTarget) return "";
1850
- const raw = await runCommandWithOutput2("git", ["log", "--format=%H%x1f%B%x1e"], { cwd });
1851
- const entries = raw.split("");
1852
- for (const entry of entries) {
1853
- if (!entry.trim()) continue;
1854
- const [shaRaw, bodyRaw] = entry.split("");
1855
- const sha = (shaRaw ?? "").trim();
1856
- const body = normalizeCommitMessage(bodyRaw ?? "");
1857
- if (!sha) continue;
1858
- if (body === normalizedTarget) return sha;
1859
- }
1860
- return "";
1861
- }
1862
1322
  function getCodexAuthPath() {
1863
- return join3(getCodexHomeDir2(), "auth.json");
1323
+ return join2(getCodexHomeDir2(), "auth.json");
1864
1324
  }
1865
1325
  async function readCodexAuth() {
1866
1326
  try {
@@ -1874,21 +1334,13 @@ async function readCodexAuth() {
1874
1334
  }
1875
1335
  }
1876
1336
  function getCodexGlobalStatePath() {
1877
- return join3(getCodexHomeDir2(), ".codex-global-state.json");
1878
- }
1879
- function getCodexSessionIndexPath() {
1880
- return join3(getCodexHomeDir2(), "session_index.jsonl");
1337
+ return join2(getCodexHomeDir2(), ".codex-global-state.json");
1881
1338
  }
1882
1339
  var MAX_THREAD_TITLES = 500;
1883
- var EMPTY_THREAD_TITLE_CACHE = { titles: {}, order: [] };
1884
- var sessionIndexThreadTitleCacheState = {
1885
- fileSignature: null,
1886
- cache: EMPTY_THREAD_TITLE_CACHE
1887
- };
1888
1340
  function normalizeThreadTitleCache(value) {
1889
- const record = asRecord3(value);
1890
- if (!record) return EMPTY_THREAD_TITLE_CACHE;
1891
- const rawTitles = asRecord3(record.titles);
1341
+ const record = asRecord2(value);
1342
+ if (!record) return { titles: {}, order: [] };
1343
+ const rawTitles = asRecord2(record.titles);
1892
1344
  const titles = {};
1893
1345
  if (rawTitles) {
1894
1346
  for (const [k, v] of Object.entries(rawTitles)) {
@@ -1911,55 +1363,14 @@ function removeFromThreadTitleCache(cache, id) {
1911
1363
  const { [id]: _, ...titles } = cache.titles;
1912
1364
  return { titles, order: cache.order.filter((o) => o !== id) };
1913
1365
  }
1914
- function normalizeSessionIndexThreadTitle(value) {
1915
- const record = asRecord3(value);
1916
- if (!record) return null;
1917
- const id = typeof record.id === "string" ? record.id.trim() : "";
1918
- const title = typeof record.thread_name === "string" ? record.thread_name.trim() : "";
1919
- const updatedAtIso = typeof record.updated_at === "string" ? record.updated_at.trim() : "";
1920
- const updatedAtMs = updatedAtIso ? Date.parse(updatedAtIso) : Number.NaN;
1921
- if (!id || !title) return null;
1922
- return {
1923
- id,
1924
- title,
1925
- updatedAtMs: Number.isFinite(updatedAtMs) ? updatedAtMs : 0
1926
- };
1927
- }
1928
- function trimThreadTitleCache(cache) {
1929
- const titles = { ...cache.titles };
1930
- const order = cache.order.filter((id) => {
1931
- if (!titles[id]) return false;
1932
- return true;
1933
- }).slice(0, MAX_THREAD_TITLES);
1934
- for (const id of Object.keys(titles)) {
1935
- if (!order.includes(id)) {
1936
- delete titles[id];
1937
- }
1938
- }
1939
- return { titles, order };
1940
- }
1941
- function mergeThreadTitleCaches(base, overlay) {
1942
- const titles = { ...base.titles, ...overlay.titles };
1943
- const order = [];
1944
- for (const id of [...overlay.order, ...base.order]) {
1945
- if (!titles[id] || order.includes(id)) continue;
1946
- order.push(id);
1947
- }
1948
- for (const id of Object.keys(titles)) {
1949
- if (!order.includes(id)) {
1950
- order.push(id);
1951
- }
1952
- }
1953
- return trimThreadTitleCache({ titles, order });
1954
- }
1955
1366
  async function readThreadTitleCache() {
1956
1367
  const statePath = getCodexGlobalStatePath();
1957
1368
  try {
1958
1369
  const raw = await readFile2(statePath, "utf8");
1959
- const payload = asRecord3(JSON.parse(raw)) ?? {};
1370
+ const payload = asRecord2(JSON.parse(raw)) ?? {};
1960
1371
  return normalizeThreadTitleCache(payload["thread-titles"]);
1961
1372
  } catch {
1962
- return EMPTY_THREAD_TITLE_CACHE;
1373
+ return { titles: {}, order: [] };
1963
1374
  }
1964
1375
  }
1965
1376
  async function writeThreadTitleCache(cache) {
@@ -1967,83 +1378,20 @@ async function writeThreadTitleCache(cache) {
1967
1378
  let payload = {};
1968
1379
  try {
1969
1380
  const raw = await readFile2(statePath, "utf8");
1970
- payload = asRecord3(JSON.parse(raw)) ?? {};
1381
+ payload = asRecord2(JSON.parse(raw)) ?? {};
1971
1382
  } catch {
1972
1383
  payload = {};
1973
1384
  }
1974
1385
  payload["thread-titles"] = cache;
1975
1386
  await writeFile2(statePath, JSON.stringify(payload), "utf8");
1976
1387
  }
1977
- function getSessionIndexFileSignature(stats) {
1978
- return `${String(stats.mtimeMs)}:${String(stats.size)}`;
1979
- }
1980
- async function parseThreadTitlesFromSessionIndex(sessionIndexPath) {
1981
- const latestById = /* @__PURE__ */ new Map();
1982
- const input = createReadStream(sessionIndexPath, { encoding: "utf8" });
1983
- const lines = createInterface({
1984
- input,
1985
- crlfDelay: Infinity
1986
- });
1987
- try {
1988
- for await (const line of lines) {
1989
- const trimmed = line.trim();
1990
- if (!trimmed) continue;
1991
- try {
1992
- const entry = normalizeSessionIndexThreadTitle(JSON.parse(trimmed));
1993
- if (!entry) continue;
1994
- const previous = latestById.get(entry.id);
1995
- if (!previous || entry.updatedAtMs >= previous.updatedAtMs) {
1996
- latestById.set(entry.id, entry);
1997
- }
1998
- } catch {
1999
- }
2000
- }
2001
- } finally {
2002
- lines.close();
2003
- input.close();
2004
- }
2005
- const entries = Array.from(latestById.values()).sort((first, second) => second.updatedAtMs - first.updatedAtMs);
2006
- const titles = {};
2007
- const order = [];
2008
- for (const entry of entries) {
2009
- titles[entry.id] = entry.title;
2010
- order.push(entry.id);
2011
- }
2012
- return trimThreadTitleCache({ titles, order });
2013
- }
2014
- async function readThreadTitlesFromSessionIndex() {
2015
- const sessionIndexPath = getCodexSessionIndexPath();
2016
- try {
2017
- const stats = await stat2(sessionIndexPath);
2018
- const fileSignature = getSessionIndexFileSignature(stats);
2019
- if (sessionIndexThreadTitleCacheState.fileSignature === fileSignature) {
2020
- return sessionIndexThreadTitleCacheState.cache;
2021
- }
2022
- const cache = await parseThreadTitlesFromSessionIndex(sessionIndexPath);
2023
- sessionIndexThreadTitleCacheState = { fileSignature, cache };
2024
- return cache;
2025
- } catch {
2026
- sessionIndexThreadTitleCacheState = {
2027
- fileSignature: "missing",
2028
- cache: EMPTY_THREAD_TITLE_CACHE
2029
- };
2030
- return sessionIndexThreadTitleCacheState.cache;
2031
- }
2032
- }
2033
- async function readMergedThreadTitleCache() {
2034
- const [sessionIndexCache, persistedCache] = await Promise.all([
2035
- readThreadTitlesFromSessionIndex(),
2036
- readThreadTitleCache()
2037
- ]);
2038
- return mergeThreadTitleCaches(persistedCache, sessionIndexCache);
2039
- }
2040
1388
  async function readWorkspaceRootsState() {
2041
1389
  const statePath = getCodexGlobalStatePath();
2042
1390
  let payload = {};
2043
1391
  try {
2044
1392
  const raw = await readFile2(statePath, "utf8");
2045
1393
  const parsed = JSON.parse(raw);
2046
- payload = asRecord3(parsed) ?? {};
1394
+ payload = asRecord2(parsed) ?? {};
2047
1395
  } catch {
2048
1396
  payload = {};
2049
1397
  }
@@ -2058,7 +1406,7 @@ async function writeWorkspaceRootsState(nextState) {
2058
1406
  let payload = {};
2059
1407
  try {
2060
1408
  const raw = await readFile2(statePath, "utf8");
2061
- payload = asRecord3(JSON.parse(raw)) ?? {};
1409
+ payload = asRecord2(JSON.parse(raw)) ?? {};
2062
1410
  } catch {
2063
1411
  payload = {};
2064
1412
  }
@@ -2067,36 +1415,6 @@ async function writeWorkspaceRootsState(nextState) {
2067
1415
  payload["active-workspace-roots"] = normalizeStringArray(nextState.active);
2068
1416
  await writeFile2(statePath, JSON.stringify(payload), "utf8");
2069
1417
  }
2070
- function normalizeTelegramBridgeConfig(value) {
2071
- const record = asRecord3(value);
2072
- if (!record) return { botToken: "" };
2073
- const botToken = typeof record.botToken === "string" ? record.botToken.trim() : "";
2074
- return { botToken };
2075
- }
2076
- async function readTelegramBridgeConfig() {
2077
- const statePath = getCodexGlobalStatePath();
2078
- try {
2079
- const raw = await readFile2(statePath, "utf8");
2080
- const payload = asRecord3(JSON.parse(raw)) ?? {};
2081
- return normalizeTelegramBridgeConfig(payload["telegram-bridge"]);
2082
- } catch {
2083
- return { botToken: "" };
2084
- }
2085
- }
2086
- async function writeTelegramBridgeConfig(nextState) {
2087
- const statePath = getCodexGlobalStatePath();
2088
- let payload = {};
2089
- try {
2090
- const raw = await readFile2(statePath, "utf8");
2091
- payload = asRecord3(JSON.parse(raw)) ?? {};
2092
- } catch {
2093
- payload = {};
2094
- }
2095
- payload["telegram-bridge"] = {
2096
- botToken: nextState.botToken.trim()
2097
- };
2098
- await writeFile2(statePath, JSON.stringify(payload), "utf8");
2099
- }
2100
1418
  async function readJsonBody(req) {
2101
1419
  const raw = await readRawBody(req);
2102
1420
  if (raw.length === 0) return null;
@@ -2166,93 +1484,46 @@ function handleFileUpload(req, res) {
2166
1484
  setJson2(res, 400, { error: "No file in request" });
2167
1485
  return;
2168
1486
  }
2169
- const uploadDir = join3(tmpdir2(), "codex-web-uploads");
1487
+ const uploadDir = join2(tmpdir2(), "codex-web-uploads");
2170
1488
  await mkdir2(uploadDir, { recursive: true });
2171
- const destDir = await mkdtemp2(join3(uploadDir, "f-"));
2172
- const destPath = join3(destDir, fileName);
1489
+ const destDir = await mkdtemp2(join2(uploadDir, "f-"));
1490
+ const destPath = join2(destDir, fileName);
2173
1491
  await writeFile2(destPath, fileData);
2174
1492
  setJson2(res, 200, { path: destPath });
2175
1493
  } catch (err) {
2176
- setJson2(res, 500, { error: getErrorMessage3(err, "Upload failed") });
1494
+ setJson2(res, 500, { error: getErrorMessage2(err, "Upload failed") });
2177
1495
  }
2178
1496
  });
2179
1497
  req.on("error", (err) => {
2180
- setJson2(res, 500, { error: getErrorMessage3(err, "Upload stream error") });
2181
- });
2182
- }
2183
- function httpPost(url, headers, body) {
2184
- const doRequest = url.startsWith("http://") ? httpRequest : httpsRequest;
2185
- return new Promise((resolve3, reject) => {
2186
- const req = doRequest(url, { method: "POST", headers }, (res) => {
2187
- const chunks = [];
2188
- res.on("data", (c) => chunks.push(c));
2189
- res.on("end", () => resolve3({ status: res.statusCode ?? 500, body: Buffer.concat(chunks).toString("utf8") }));
2190
- res.on("error", reject);
2191
- });
2192
- req.on("error", reject);
2193
- req.write(body);
2194
- req.end();
2195
- });
2196
- }
2197
- var curlImpersonateAvailable = null;
2198
- function curlImpersonatePost(url, headers, body) {
2199
- return new Promise((resolve3, reject) => {
2200
- const args = ["-s", "-w", "\n%{http_code}", "-X", "POST", url];
2201
- for (const [k, v] of Object.entries(headers)) {
2202
- if (k.toLowerCase() === "content-length") continue;
2203
- args.push("-H", `${k}: ${String(v)}`);
2204
- }
2205
- args.push("--data-binary", "@-");
2206
- const proc = spawn2("curl-impersonate-chrome", args, {
2207
- env: { ...process.env, CURL_IMPERSONATE: "chrome116" },
2208
- stdio: ["pipe", "pipe", "pipe"]
2209
- });
2210
- const chunks = [];
2211
- proc.stdout.on("data", (c) => chunks.push(c));
2212
- proc.on("error", (e) => {
2213
- curlImpersonateAvailable = false;
2214
- reject(e);
2215
- });
2216
- proc.on("close", (code) => {
2217
- const raw = Buffer.concat(chunks).toString("utf8");
2218
- const lastNewline = raw.lastIndexOf("\n");
2219
- const statusStr = lastNewline >= 0 ? raw.slice(lastNewline + 1).trim() : "";
2220
- const responseBody = lastNewline >= 0 ? raw.slice(0, lastNewline) : raw;
2221
- const status = parseInt(statusStr, 10) || (code === 0 ? 200 : 500);
2222
- curlImpersonateAvailable = true;
2223
- resolve3({ status, body: responseBody });
2224
- });
2225
- proc.stdin.write(body);
2226
- proc.stdin.end();
1498
+ setJson2(res, 500, { error: getErrorMessage2(err, "Upload stream error") });
2227
1499
  });
2228
1500
  }
2229
1501
  async function proxyTranscribe(body, contentType, authToken, accountId) {
2230
- const chatgptHeaders = {
1502
+ const headers = {
2231
1503
  "Content-Type": contentType,
2232
1504
  "Content-Length": body.length,
2233
1505
  Authorization: `Bearer ${authToken}`,
2234
1506
  originator: "Codex Desktop",
2235
1507
  "User-Agent": `Codex Desktop/0.1.0 (${process.platform}; ${process.arch})`
2236
1508
  };
2237
- if (accountId) chatgptHeaders["ChatGPT-Account-Id"] = accountId;
2238
- const postFn = curlImpersonateAvailable !== false ? curlImpersonatePost : httpPost;
2239
- let result;
2240
- try {
2241
- result = await postFn("https://chatgpt.com/backend-api/transcribe", chatgptHeaders, body);
2242
- } catch {
2243
- result = await httpPost("https://chatgpt.com/backend-api/transcribe", chatgptHeaders, body);
1509
+ if (accountId) {
1510
+ headers["ChatGPT-Account-Id"] = accountId;
2244
1511
  }
2245
- if (result.status === 403 && result.body.includes("cf_chl")) {
2246
- if (curlImpersonateAvailable !== false && postFn !== curlImpersonatePost) {
2247
- try {
2248
- const ciResult = await curlImpersonatePost("https://chatgpt.com/backend-api/transcribe", chatgptHeaders, body);
2249
- if (ciResult.status !== 403) return ciResult;
2250
- } catch {
2251
- }
2252
- }
2253
- return { status: 503, body: JSON.stringify({ error: "Transcription blocked by Cloudflare. Install curl-impersonate-chrome." }) };
2254
- }
2255
- return result;
1512
+ return new Promise((resolve3, reject) => {
1513
+ const req = httpsRequest(
1514
+ "https://chatgpt.com/backend-api/transcribe",
1515
+ { method: "POST", headers },
1516
+ (res) => {
1517
+ const chunks = [];
1518
+ res.on("data", (c) => chunks.push(c));
1519
+ res.on("end", () => resolve3({ status: res.statusCode ?? 500, body: Buffer.concat(chunks).toString("utf8") }));
1520
+ res.on("error", reject);
1521
+ }
1522
+ );
1523
+ req.on("error", reject);
1524
+ req.write(body);
1525
+ req.end();
1526
+ });
2256
1527
  }
2257
1528
  var AppServerProcess = class {
2258
1529
  constructor() {
@@ -2276,8 +1547,7 @@ var AppServerProcess = class {
2276
1547
  start() {
2277
1548
  if (this.process) return;
2278
1549
  this.stopping = false;
2279
- const invocation = getSpawnInvocation(resolveCodexCommand() ?? "codex", this.appServerArgs);
2280
- const proc = spawn2(invocation.command, invocation.args, { stdio: ["pipe", "pipe", "pipe"] });
1550
+ const proc = spawn2("codex", this.appServerArgs, { stdio: ["pipe", "pipe", "pipe"] });
2281
1551
  this.process = proc;
2282
1552
  proc.stdout.setEncoding("utf8");
2283
1553
  proc.stdout.on("data", (chunk) => {
@@ -2371,7 +1641,7 @@ var AppServerProcess = class {
2371
1641
  }
2372
1642
  this.pendingServerRequests.delete(requestId);
2373
1643
  this.sendServerRequestReply(requestId, reply);
2374
- const requestParams = asRecord3(pendingRequest.params);
1644
+ const requestParams = asRecord2(pendingRequest.params);
2375
1645
  const threadId = typeof requestParams?.threadId === "string" && requestParams.threadId.length > 0 ? requestParams.threadId : "";
2376
1646
  this.emitNotification({
2377
1647
  method: "server/request/resolved",
@@ -2440,7 +1710,7 @@ var AppServerProcess = class {
2440
1710
  }
2441
1711
  async respondToServerRequest(payload) {
2442
1712
  await this.ensureInitialized();
2443
- const body = asRecord3(payload);
1713
+ const body = asRecord2(payload);
2444
1714
  if (!body) {
2445
1715
  throw new Error("Invalid response payload: expected object");
2446
1716
  }
@@ -2448,7 +1718,7 @@ var AppServerProcess = class {
2448
1718
  if (typeof id !== "number" || !Number.isInteger(id)) {
2449
1719
  throw new Error('Invalid response payload: "id" must be an integer');
2450
1720
  }
2451
- const rawError = asRecord3(body.error);
1721
+ const rawError = asRecord2(body.error);
2452
1722
  if (rawError) {
2453
1723
  const message = typeof rawError.message === "string" && rawError.message.trim().length > 0 ? rawError.message.trim() : "Server request rejected by client";
2454
1724
  const code = typeof rawError.code === "number" && Number.isFinite(rawError.code) ? Math.trunc(rawError.code) : -32e3;
@@ -2503,8 +1773,7 @@ var MethodCatalog = class {
2503
1773
  }
2504
1774
  async runGenerateSchemaCommand(outDir) {
2505
1775
  await new Promise((resolve3, reject) => {
2506
- const invocation = getSpawnInvocation(resolveCodexCommand() ?? "codex", ["app-server", "generate-json-schema", "--out", outDir]);
2507
- const process2 = spawn2(invocation.command, invocation.args, {
1776
+ const process2 = spawn2("codex", ["app-server", "generate-json-schema", "--out", outDir], {
2508
1777
  stdio: ["ignore", "ignore", "pipe"]
2509
1778
  });
2510
1779
  let stderr = "";
@@ -2523,13 +1792,13 @@ var MethodCatalog = class {
2523
1792
  });
2524
1793
  }
2525
1794
  extractMethodsFromClientRequest(payload) {
2526
- const root = asRecord3(payload);
1795
+ const root = asRecord2(payload);
2527
1796
  const oneOf = Array.isArray(root?.oneOf) ? root.oneOf : [];
2528
1797
  const methods = /* @__PURE__ */ new Set();
2529
1798
  for (const entry of oneOf) {
2530
- const row = asRecord3(entry);
2531
- const properties = asRecord3(row?.properties);
2532
- const methodDef = asRecord3(properties?.method);
1799
+ const row = asRecord2(entry);
1800
+ const properties = asRecord2(row?.properties);
1801
+ const methodDef = asRecord2(properties?.method);
2533
1802
  const methodEnum = Array.isArray(methodDef?.enum) ? methodDef.enum : [];
2534
1803
  for (const item of methodEnum) {
2535
1804
  if (typeof item === "string" && item.length > 0) {
@@ -2540,13 +1809,13 @@ var MethodCatalog = class {
2540
1809
  return Array.from(methods).sort((a, b) => a.localeCompare(b));
2541
1810
  }
2542
1811
  extractMethodsFromServerNotification(payload) {
2543
- const root = asRecord3(payload);
1812
+ const root = asRecord2(payload);
2544
1813
  const oneOf = Array.isArray(root?.oneOf) ? root.oneOf : [];
2545
1814
  const methods = /* @__PURE__ */ new Set();
2546
1815
  for (const entry of oneOf) {
2547
- const row = asRecord3(entry);
2548
- const properties = asRecord3(row?.properties);
2549
- const methodDef = asRecord3(properties?.method);
1816
+ const row = asRecord2(entry);
1817
+ const properties = asRecord2(row?.properties);
1818
+ const methodDef = asRecord2(properties?.method);
2550
1819
  const methodEnum = Array.isArray(methodDef?.enum) ? methodDef.enum : [];
2551
1820
  for (const item of methodEnum) {
2552
1821
  if (typeof item === "string" && item.length > 0) {
@@ -2560,9 +1829,9 @@ var MethodCatalog = class {
2560
1829
  if (this.methodCache) {
2561
1830
  return this.methodCache;
2562
1831
  }
2563
- const outDir = await mkdtemp2(join3(tmpdir2(), "codex-web-local-schema-"));
1832
+ const outDir = await mkdtemp2(join2(tmpdir2(), "codex-web-local-schema-"));
2564
1833
  await this.runGenerateSchemaCommand(outDir);
2565
- const clientRequestPath = join3(outDir, "ClientRequest.json");
1834
+ const clientRequestPath = join2(outDir, "ClientRequest.json");
2566
1835
  const raw = await readFile2(clientRequestPath, "utf8");
2567
1836
  const parsed = JSON.parse(raw);
2568
1837
  const methods = this.extractMethodsFromClientRequest(parsed);
@@ -2573,9 +1842,9 @@ var MethodCatalog = class {
2573
1842
  if (this.notificationCache) {
2574
1843
  return this.notificationCache;
2575
1844
  }
2576
- const outDir = await mkdtemp2(join3(tmpdir2(), "codex-web-local-schema-"));
1845
+ const outDir = await mkdtemp2(join2(tmpdir2(), "codex-web-local-schema-"));
2577
1846
  await this.runGenerateSchemaCommand(outDir);
2578
- const serverNotificationPath = join3(outDir, "ServerNotification.json");
1847
+ const serverNotificationPath = join2(outDir, "ServerNotification.json");
2579
1848
  const raw = await readFile2(serverNotificationPath, "utf8");
2580
1849
  const parsed = JSON.parse(raw);
2581
1850
  const methods = this.extractMethodsFromServerNotification(parsed);
@@ -2588,11 +1857,9 @@ function getSharedBridgeState() {
2588
1857
  const globalScope = globalThis;
2589
1858
  const existing = globalScope[SHARED_BRIDGE_KEY];
2590
1859
  if (existing) return existing;
2591
- const appServer = new AppServerProcess();
2592
1860
  const created = {
2593
- appServer,
2594
- methodCatalog: new MethodCatalog(),
2595
- telegramBridge: new TelegramThreadBridge(appServer)
1861
+ appServer: new AppServerProcess(),
1862
+ methodCatalog: new MethodCatalog()
2596
1863
  };
2597
1864
  globalScope[SHARED_BRIDGE_KEY] = created;
2598
1865
  return created;
@@ -2601,7 +1868,7 @@ async function loadAllThreadsForSearch(appServer) {
2601
1868
  const threads = [];
2602
1869
  let cursor = null;
2603
1870
  do {
2604
- const response = asRecord3(await appServer.rpc("thread/list", {
1871
+ const response = asRecord2(await appServer.rpc("thread/list", {
2605
1872
  archived: false,
2606
1873
  limit: 100,
2607
1874
  sortKey: "updated_at",
@@ -2609,7 +1876,7 @@ async function loadAllThreadsForSearch(appServer) {
2609
1876
  }));
2610
1877
  const data = Array.isArray(response?.data) ? response.data : [];
2611
1878
  for (const row of data) {
2612
- const record = asRecord3(row);
1879
+ const record = asRecord2(row);
2613
1880
  const id = typeof record?.id === "string" ? record.id : "";
2614
1881
  if (!id) continue;
2615
1882
  const title = typeof record?.name === "string" && record.name.trim().length > 0 ? record.name.trim() : typeof record?.preview === "string" && record.preview.trim().length > 0 ? record.preview.trim() : "Untitled thread";
@@ -2658,7 +1925,7 @@ async function buildThreadSearchIndex(appServer) {
2658
1925
  return { docsById };
2659
1926
  }
2660
1927
  function createCodexBridgeMiddleware() {
2661
- const { appServer, methodCatalog, telegramBridge } = getSharedBridgeState();
1928
+ const { appServer, methodCatalog } = getSharedBridgeState();
2662
1929
  let threadSearchIndex = null;
2663
1930
  let threadSearchIndexPromise = null;
2664
1931
  async function getThreadSearchIndex() {
@@ -2674,12 +1941,6 @@ function createCodexBridgeMiddleware() {
2674
1941
  return threadSearchIndexPromise;
2675
1942
  }
2676
1943
  void initializeSkillsSyncOnStartup(appServer);
2677
- void readTelegramBridgeConfig().then((config) => {
2678
- if (!config.botToken) return;
2679
- telegramBridge.configureToken(config.botToken);
2680
- telegramBridge.start();
2681
- }).catch(() => {
2682
- });
2683
1944
  const middleware = async (req, res, next) => {
2684
1945
  try {
2685
1946
  if (!req.url) {
@@ -2696,7 +1957,7 @@ function createCodexBridgeMiddleware() {
2696
1957
  }
2697
1958
  if (req.method === "POST" && url.pathname === "/codex-api/rpc") {
2698
1959
  const payload = await readJsonBody(req);
2699
- const body = asRecord3(payload);
1960
+ const body = asRecord2(payload);
2700
1961
  if (!body || typeof body.method !== "string" || body.method.length === 0) {
2701
1962
  setJson2(res, 400, { error: "Invalid body: expected { method, params? }" });
2702
1963
  return;
@@ -2745,11 +2006,11 @@ function createCodexBridgeMiddleware() {
2745
2006
  return;
2746
2007
  }
2747
2008
  if (req.method === "GET" && url.pathname === "/codex-api/home-directory") {
2748
- setJson2(res, 200, { data: { path: homedir3() } });
2009
+ setJson2(res, 200, { data: { path: homedir2() } });
2749
2010
  return;
2750
2011
  }
2751
2012
  if (req.method === "POST" && url.pathname === "/codex-api/worktree/create") {
2752
- const payload = asRecord3(await readJsonBody(req));
2013
+ const payload = asRecord2(await readJsonBody(req));
2753
2014
  const rawSourceCwd = typeof payload?.sourceCwd === "string" ? payload.sourceCwd.trim() : "";
2754
2015
  if (!rawSourceCwd) {
2755
2016
  setJson2(res, 400, { error: "Missing sourceCwd" });
@@ -2775,22 +2036,22 @@ function createCodexBridgeMiddleware() {
2775
2036
  await runCommand2("git", ["init"], { cwd: sourceCwd });
2776
2037
  gitRoot = await runCommandCapture("git", ["rev-parse", "--show-toplevel"], { cwd: sourceCwd });
2777
2038
  }
2778
- const repoName = basename3(gitRoot) || "repo";
2779
- const worktreesRoot = join3(getCodexHomeDir2(), "worktrees");
2039
+ const repoName = basename(gitRoot) || "repo";
2040
+ const worktreesRoot = join2(getCodexHomeDir2(), "worktrees");
2780
2041
  await mkdir2(worktreesRoot, { recursive: true });
2781
2042
  let worktreeId = "";
2782
2043
  let worktreeParent = "";
2783
2044
  let worktreeCwd = "";
2784
2045
  for (let attempt = 0; attempt < 12; attempt += 1) {
2785
2046
  const candidate = randomBytes(2).toString("hex");
2786
- const parent = join3(worktreesRoot, candidate);
2047
+ const parent = join2(worktreesRoot, candidate);
2787
2048
  try {
2788
2049
  await stat2(parent);
2789
2050
  continue;
2790
2051
  } catch {
2791
2052
  worktreeId = candidate;
2792
2053
  worktreeParent = parent;
2793
- worktreeCwd = join3(parent, repoName);
2054
+ worktreeCwd = join2(parent, repoName);
2794
2055
  break;
2795
2056
  }
2796
2057
  }
@@ -2814,106 +2075,13 @@ function createCodexBridgeMiddleware() {
2814
2075
  }
2815
2076
  });
2816
2077
  } catch (error) {
2817
- setJson2(res, 500, { error: getErrorMessage3(error, "Failed to create worktree") });
2818
- }
2819
- return;
2820
- }
2821
- if (req.method === "POST" && url.pathname === "/codex-api/worktree/auto-commit") {
2822
- const payload = asRecord3(await readJsonBody(req));
2823
- const rawCwd = typeof payload?.cwd === "string" ? payload.cwd.trim() : "";
2824
- const commitMessage = normalizeCommitMessage(payload?.message);
2825
- if (!rawCwd) {
2826
- setJson2(res, 400, { error: "Missing cwd" });
2827
- return;
2828
- }
2829
- if (!commitMessage) {
2830
- setJson2(res, 400, { error: "Missing message" });
2831
- return;
2832
- }
2833
- const cwd = isAbsolute(rawCwd) ? rawCwd : resolve(rawCwd);
2834
- try {
2835
- const cwdInfo = await stat2(cwd);
2836
- if (!cwdInfo.isDirectory()) {
2837
- setJson2(res, 400, { error: "cwd is not a directory" });
2838
- return;
2839
- }
2840
- } catch {
2841
- setJson2(res, 404, { error: "cwd does not exist" });
2842
- return;
2843
- }
2844
- try {
2845
- await runCommandCapture("git", ["rev-parse", "--is-inside-work-tree"], { cwd });
2846
- const beforeStatus = await runCommandWithOutput2("git", ["status", "--porcelain"], { cwd });
2847
- if (!beforeStatus.trim()) {
2848
- setJson2(res, 200, { data: { committed: false } });
2849
- return;
2850
- }
2851
- await runCommand2("git", ["add", "-A"], { cwd });
2852
- const stagedStatus = await runCommandWithOutput2("git", ["diff", "--cached", "--name-only"], { cwd });
2853
- if (!stagedStatus.trim()) {
2854
- setJson2(res, 200, { data: { committed: false } });
2855
- return;
2856
- }
2857
- await runCommand2("git", ["commit", "-m", commitMessage], { cwd });
2858
- setJson2(res, 200, { data: { committed: true } });
2859
- } catch (error) {
2860
- setJson2(res, 500, { error: getErrorMessage3(error, "Failed to auto-commit worktree changes") });
2861
- }
2862
- return;
2863
- }
2864
- if (req.method === "POST" && url.pathname === "/codex-api/worktree/rollback-to-message") {
2865
- const payload = asRecord3(await readJsonBody(req));
2866
- const rawCwd = typeof payload?.cwd === "string" ? payload.cwd.trim() : "";
2867
- const commitMessage = normalizeCommitMessage(payload?.message);
2868
- if (!rawCwd) {
2869
- setJson2(res, 400, { error: "Missing cwd" });
2870
- return;
2871
- }
2872
- if (!commitMessage) {
2873
- setJson2(res, 400, { error: "Missing message" });
2874
- return;
2875
- }
2876
- const cwd = isAbsolute(rawCwd) ? rawCwd : resolve(rawCwd);
2877
- try {
2878
- const cwdInfo = await stat2(cwd);
2879
- if (!cwdInfo.isDirectory()) {
2880
- setJson2(res, 400, { error: "cwd is not a directory" });
2881
- return;
2882
- }
2883
- } catch {
2884
- setJson2(res, 404, { error: "cwd does not exist" });
2885
- return;
2886
- }
2887
- try {
2888
- await runCommandCapture("git", ["rev-parse", "--is-inside-work-tree"], { cwd });
2889
- const commitSha = await findCommitByExactMessage(cwd, commitMessage);
2890
- if (!commitSha) {
2891
- setJson2(res, 404, { error: "No matching commit found for this user message" });
2892
- return;
2893
- }
2894
- let resetTargetSha = "";
2895
- try {
2896
- resetTargetSha = await runCommandCapture("git", ["rev-parse", `${commitSha}^`], { cwd });
2897
- } catch {
2898
- setJson2(res, 409, { error: "Cannot rollback: matched commit has no parent commit" });
2899
- return;
2900
- }
2901
- let stashed = false;
2902
- if (await hasGitWorkingTreeChanges(cwd)) {
2903
- const stashMessage = `codex-auto-stash-before-rollback-${Date.now()}`;
2904
- await runCommand2("git", ["stash", "push", "-u", "-m", stashMessage], { cwd });
2905
- stashed = true;
2906
- }
2907
- await runCommand2("git", ["reset", "--hard", resetTargetSha], { cwd });
2908
- setJson2(res, 200, { data: { reset: true, commitSha, resetTargetSha, stashed } });
2909
- } catch (error) {
2910
- setJson2(res, 500, { error: getErrorMessage3(error, "Failed to rollback worktree to user message commit") });
2078
+ setJson2(res, 500, { error: getErrorMessage2(error, "Failed to create worktree") });
2911
2079
  }
2912
2080
  return;
2913
2081
  }
2914
2082
  if (req.method === "PUT" && url.pathname === "/codex-api/workspace-roots-state") {
2915
2083
  const payload = await readJsonBody(req);
2916
- const record = asRecord3(payload);
2084
+ const record = asRecord2(payload);
2917
2085
  if (!record) {
2918
2086
  setJson2(res, 400, { error: "Invalid body: expected object" });
2919
2087
  return;
@@ -2928,7 +2096,7 @@ function createCodexBridgeMiddleware() {
2928
2096
  return;
2929
2097
  }
2930
2098
  if (req.method === "POST" && url.pathname === "/codex-api/project-root") {
2931
- const payload = asRecord3(await readJsonBody(req));
2099
+ const payload = asRecord2(await readJsonBody(req));
2932
2100
  const rawPath = typeof payload?.path === "string" ? payload.path.trim() : "";
2933
2101
  const createIfMissing = payload?.createIfMissing === true;
2934
2102
  const label = typeof payload?.label === "string" ? payload.label : "";
@@ -2988,7 +2156,7 @@ function createCodexBridgeMiddleware() {
2988
2156
  let index = 1;
2989
2157
  while (index < 1e5) {
2990
2158
  const candidateName = `New Project (${String(index)})`;
2991
- const candidatePath = join3(normalizedBasePath, candidateName);
2159
+ const candidatePath = join2(normalizedBasePath, candidateName);
2992
2160
  try {
2993
2161
  await stat2(candidatePath);
2994
2162
  index += 1;
@@ -3002,7 +2170,7 @@ function createCodexBridgeMiddleware() {
3002
2170
  return;
3003
2171
  }
3004
2172
  if (req.method === "POST" && url.pathname === "/codex-api/composer-file-search") {
3005
- const payload = asRecord3(await readJsonBody(req));
2173
+ const payload = asRecord2(await readJsonBody(req));
3006
2174
  const rawCwd = typeof payload?.cwd === "string" ? payload.cwd.trim() : "";
3007
2175
  const query = typeof payload?.query === "string" ? payload.query.trim() : "";
3008
2176
  const limitRaw = typeof payload?.limit === "number" ? payload.limit : 20;
@@ -3027,17 +2195,17 @@ function createCodexBridgeMiddleware() {
3027
2195
  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 }));
3028
2196
  setJson2(res, 200, { data: scored });
3029
2197
  } catch (error) {
3030
- setJson2(res, 500, { error: getErrorMessage3(error, "Failed to search files") });
2198
+ setJson2(res, 500, { error: getErrorMessage2(error, "Failed to search files") });
3031
2199
  }
3032
2200
  return;
3033
2201
  }
3034
2202
  if (req.method === "GET" && url.pathname === "/codex-api/thread-titles") {
3035
- const cache = await readMergedThreadTitleCache();
2203
+ const cache = await readThreadTitleCache();
3036
2204
  setJson2(res, 200, { data: cache });
3037
2205
  return;
3038
2206
  }
3039
2207
  if (req.method === "POST" && url.pathname === "/codex-api/thread-search") {
3040
- const payload = asRecord3(await readJsonBody(req));
2208
+ const payload = asRecord2(await readJsonBody(req));
3041
2209
  const query = typeof payload?.query === "string" ? payload.query.trim() : "";
3042
2210
  const limitRaw = typeof payload?.limit === "number" ? payload.limit : 200;
3043
2211
  const limit = Math.max(1, Math.min(1e3, Math.floor(limitRaw)));
@@ -3051,7 +2219,7 @@ function createCodexBridgeMiddleware() {
3051
2219
  return;
3052
2220
  }
3053
2221
  if (req.method === "PUT" && url.pathname === "/codex-api/thread-titles") {
3054
- const payload = asRecord3(await readJsonBody(req));
2222
+ const payload = asRecord2(await readJsonBody(req));
3055
2223
  const id = typeof payload?.id === "string" ? payload.id : "";
3056
2224
  const title = typeof payload?.title === "string" ? payload.title : "";
3057
2225
  if (!id) {
@@ -3064,23 +2232,6 @@ function createCodexBridgeMiddleware() {
3064
2232
  setJson2(res, 200, { ok: true });
3065
2233
  return;
3066
2234
  }
3067
- if (req.method === "POST" && url.pathname === "/codex-api/telegram/configure-bot") {
3068
- const payload = asRecord3(await readJsonBody(req));
3069
- const botToken = typeof payload?.botToken === "string" ? payload.botToken.trim() : "";
3070
- if (!botToken) {
3071
- setJson2(res, 400, { error: "Missing botToken" });
3072
- return;
3073
- }
3074
- telegramBridge.configureToken(botToken);
3075
- telegramBridge.start();
3076
- await writeTelegramBridgeConfig({ botToken });
3077
- setJson2(res, 200, { ok: true });
3078
- return;
3079
- }
3080
- if (req.method === "GET" && url.pathname === "/codex-api/telegram/status") {
3081
- setJson2(res, 200, { data: telegramBridge.getStatus() });
3082
- return;
3083
- }
3084
2235
  if (req.method === "GET" && url.pathname === "/codex-api/events") {
3085
2236
  res.statusCode = 200;
3086
2237
  res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
@@ -3113,13 +2264,12 @@ data: ${JSON.stringify({ ok: true })}
3113
2264
  }
3114
2265
  next();
3115
2266
  } catch (error) {
3116
- const message = getErrorMessage3(error, "Unknown bridge error");
2267
+ const message = getErrorMessage2(error, "Unknown bridge error");
3117
2268
  setJson2(res, 502, { error: message });
3118
2269
  }
3119
2270
  };
3120
2271
  middleware.dispose = () => {
3121
2272
  threadSearchIndex = null;
3122
- telegramBridge.stop();
3123
2273
  appServer.dispose();
3124
2274
  };
3125
2275
  middleware.subscribeNotifications = (listener) => {
@@ -3252,7 +2402,7 @@ function createAuthSession(password) {
3252
2402
  }
3253
2403
 
3254
2404
  // src/server/localBrowseUi.ts
3255
- import { dirname, extname as extname2, join as join4 } from "path";
2405
+ import { dirname, extname, join as join3 } from "path";
3256
2406
  import { open, readFile as readFile3, readdir as readdir3, stat as stat3 } from "fs/promises";
3257
2407
  var TEXT_EDITABLE_EXTENSIONS = /* @__PURE__ */ new Set([
3258
2408
  ".txt",
@@ -3283,7 +2433,7 @@ var TEXT_EDITABLE_EXTENSIONS = /* @__PURE__ */ new Set([
3283
2433
  ".ps1"
3284
2434
  ]);
3285
2435
  function languageForPath(pathValue) {
3286
- const extension = extname2(pathValue).toLowerCase();
2436
+ const extension = extname(pathValue).toLowerCase();
3287
2437
  switch (extension) {
3288
2438
  case ".js":
3289
2439
  return "javascript";
@@ -3344,7 +2494,7 @@ function decodeBrowsePath(rawPath) {
3344
2494
  }
3345
2495
  }
3346
2496
  function isTextEditablePath(pathValue) {
3347
- return TEXT_EDITABLE_EXTENSIONS.has(extname2(pathValue).toLowerCase());
2497
+ return TEXT_EDITABLE_EXTENSIONS.has(extname(pathValue).toLowerCase());
3348
2498
  }
3349
2499
  function looksLikeTextBuffer(buffer) {
3350
2500
  if (buffer.length === 0) return true;
@@ -3390,7 +2540,7 @@ function escapeForInlineScriptString(value) {
3390
2540
  async function getDirectoryItems(localPath) {
3391
2541
  const entries = await readdir3(localPath, { withFileTypes: true });
3392
2542
  const withMeta = await Promise.all(entries.map(async (entry) => {
3393
- const entryPath = join4(localPath, entry.name);
2543
+ const entryPath = join3(localPath, entry.name);
3394
2544
  const entryStat = await stat3(entryPath);
3395
2545
  const editable = !entry.isDirectory() && await isTextEditableFile(entryPath);
3396
2546
  return {
@@ -3414,9 +2564,9 @@ async function createDirectoryListingHtml(localPath) {
3414
2564
  const rows = items.map((item) => {
3415
2565
  const suffix = item.isDirectory ? "/" : "";
3416
2566
  const editAction = item.editable ? ` <a class="icon-btn" aria-label="Edit ${escapeHtml(item.name)}" href="${escapeHtml(toEditHref(item.path))}" title="Edit">\u270F\uFE0F</a>` : "";
3417
- return `<li class="file-row"><a class="file-link" href="${escapeHtml(toBrowseHref(item.path))}">${escapeHtml(item.name)}${suffix}</a><span class="row-actions">${editAction}</span></li>`;
2567
+ return `<li class="file-row"><a class="file-link" href="${escapeHtml(toBrowseHref(item.path))}">${escapeHtml(item.name)}${suffix}</a>${editAction}</li>`;
3418
2568
  }).join("\n");
3419
- const parentLink = localPath !== parentPath ? `<a href="${escapeHtml(toBrowseHref(parentPath))}">..</a>` : "";
2569
+ const parentLink = localPath !== parentPath ? `<p><a href="${escapeHtml(toBrowseHref(parentPath))}">..</a></p>` : "";
3420
2570
  return `<!doctype html>
3421
2571
  <html lang="en">
3422
2572
  <head>
@@ -3430,27 +2580,8 @@ async function createDirectoryListingHtml(localPath) {
3430
2580
  ul { list-style: none; padding: 0; margin: 12px 0 0; display: flex; flex-direction: column; gap: 8px; }
3431
2581
  .file-row { display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: center; gap: 10px; }
3432
2582
  .file-link { display: block; padding: 10px 12px; border: 1px solid #28405f; border-radius: 10px; background: #0f1b33; overflow-wrap: anywhere; }
3433
- .header-actions { display: flex; align-items: center; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
3434
- .header-parent-link { color: #9ec8ff; font-size: 14px; padding: 8px 10px; border: 1px solid #2a4569; border-radius: 10px; background: #101f3a; }
3435
- .header-parent-link:hover { text-decoration: none; filter: brightness(1.08); }
3436
- .header-open-btn {
3437
- height: 42px;
3438
- padding: 0 14px;
3439
- border: 1px solid #4f8de0;
3440
- border-radius: 10px;
3441
- background: linear-gradient(135deg, #2e6ee6 0%, #3d8cff 100%);
3442
- color: #eef6ff;
3443
- font-weight: 700;
3444
- letter-spacing: 0.01em;
3445
- cursor: pointer;
3446
- box-shadow: 0 6px 18px rgba(33, 90, 199, 0.35);
3447
- }
3448
- .header-open-btn:hover { filter: brightness(1.08); }
3449
- .header-open-btn:disabled { opacity: 0.6; cursor: default; }
3450
- .row-actions { display: inline-flex; align-items: center; gap: 8px; min-width: 42px; justify-content: flex-end; }
3451
- .icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 42px; height: 42px; border: 1px solid #36557a; border-radius: 10px; background: #162643; color: #dbe6ff; text-decoration: none; cursor: pointer; }
2583
+ .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; }
3452
2584
  .icon-btn:hover { filter: brightness(1.08); text-decoration: none; }
3453
- .status { margin: 10px 0 0; color: #8cc2ff; min-height: 1.25em; }
3454
2585
  h1 { font-size: 18px; margin: 0; word-break: break-all; }
3455
2586
  @media (max-width: 640px) {
3456
2587
  body { margin: 12px; }
@@ -3462,46 +2593,8 @@ async function createDirectoryListingHtml(localPath) {
3462
2593
  </head>
3463
2594
  <body>
3464
2595
  <h1>Index of ${escapeHtml(localPath)}</h1>
3465
- <div class="header-actions">
3466
- ${parentLink ? `<a class="header-parent-link" href="${escapeHtml(toBrowseHref(parentPath))}">..</a>` : ""}
3467
- <button class="header-open-btn open-folder-btn" type="button" aria-label="Open current folder in Codex" title="Open folder in Codex" data-path="${escapeHtml(localPath)}">Open folder in Codex</button>
3468
- </div>
3469
- <p id="status" class="status"></p>
2596
+ ${parentLink}
3470
2597
  <ul>${rows}</ul>
3471
- <script>
3472
- const status = document.getElementById('status');
3473
- document.addEventListener('click', async (event) => {
3474
- const target = event.target;
3475
- if (!(target instanceof Element)) return;
3476
- const button = target.closest('.open-folder-btn');
3477
- if (!(button instanceof HTMLButtonElement)) return;
3478
-
3479
- const path = button.getAttribute('data-path') || '';
3480
- if (!path) return;
3481
- button.disabled = true;
3482
- status.textContent = 'Opening folder in Codex...';
3483
- try {
3484
- const response = await fetch('/codex-api/project-root', {
3485
- method: 'POST',
3486
- headers: { 'Content-Type': 'application/json' },
3487
- body: JSON.stringify({
3488
- path,
3489
- createIfMissing: false,
3490
- label: '',
3491
- }),
3492
- });
3493
- if (!response.ok) {
3494
- status.textContent = 'Failed to open folder.';
3495
- button.disabled = false;
3496
- return;
3497
- }
3498
- window.location.assign('/#/');
3499
- } catch {
3500
- status.textContent = 'Failed to open folder.';
3501
- button.disabled = false;
3502
- }
3503
- });
3504
- </script>
3505
2598
  </body>
3506
2599
  </html>`;
3507
2600
  }
@@ -3577,8 +2670,8 @@ async function createTextEditorHtml(localPath) {
3577
2670
  // src/server/httpServer.ts
3578
2671
  import { WebSocketServer } from "ws";
3579
2672
  var __dirname = dirname2(fileURLToPath(import.meta.url));
3580
- var distDir = join5(__dirname, "..", "dist");
3581
- var spaEntryFile = join5(distDir, "index.html");
2673
+ var distDir = join4(__dirname, "..", "dist");
2674
+ var spaEntryFile = join4(distDir, "index.html");
3582
2675
  var IMAGE_CONTENT_TYPES = {
3583
2676
  ".avif": "image/avif",
3584
2677
  ".bmp": "image/bmp",
@@ -3635,7 +2728,7 @@ function createServer(options = {}) {
3635
2728
  res.status(400).json({ error: "Expected absolute local file path." });
3636
2729
  return;
3637
2730
  }
3638
- const contentType = IMAGE_CONTENT_TYPES[extname3(localPath).toLowerCase()];
2731
+ const contentType = IMAGE_CONTENT_TYPES[extname2(localPath).toLowerCase()];
3639
2732
  if (!contentType) {
3640
2733
  res.status(415).json({ error: "Unsupported image type." });
3641
2734
  return;
@@ -3722,7 +2815,7 @@ function createServer(options = {}) {
3722
2815
  res.status(404).json({ error: "File not found." });
3723
2816
  }
3724
2817
  });
3725
- const hasFrontendAssets = existsSync4(spaEntryFile);
2818
+ const hasFrontendAssets = existsSync2(spaEntryFile);
3726
2819
  if (hasFrontendAssets) {
3727
2820
  app.use(express.static(distDir));
3728
2821
  }
@@ -3793,25 +2886,9 @@ function generatePassword() {
3793
2886
  // src/cli/index.ts
3794
2887
  var program = new Command().name("codexui").description("Web interface for Codex app-server");
3795
2888
  var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
3796
- var hasPromptedCloudflaredInstall = false;
3797
- function getCodexHomePath() {
3798
- return process.env.CODEX_HOME?.trim() || join6(homedir4(), ".codex");
3799
- }
3800
- function getCloudflaredPromptMarkerPath() {
3801
- return join6(getCodexHomePath(), ".cloudflared-install-prompted");
3802
- }
3803
- function hasPromptedCloudflaredInstallPersisted() {
3804
- return existsSync5(getCloudflaredPromptMarkerPath());
3805
- }
3806
- async function persistCloudflaredInstallPrompted() {
3807
- const codexHome = getCodexHomePath();
3808
- mkdirSync(codexHome, { recursive: true });
3809
- await writeFile4(getCloudflaredPromptMarkerPath(), `${Date.now()}
3810
- `, "utf8");
3811
- }
3812
2889
  async function readCliVersion() {
3813
2890
  try {
3814
- const packageJsonPath = join6(__dirname2, "..", "package.json");
2891
+ const packageJsonPath = join5(__dirname2, "..", "package.json");
3815
2892
  const raw = await readFile4(packageJsonPath, "utf8");
3816
2893
  const parsed = JSON.parse(raw);
3817
2894
  return typeof parsed.version === "string" ? parsed.version : "unknown";
@@ -3823,25 +2900,46 @@ function isTermuxRuntime() {
3823
2900
  return Boolean(process.env.TERMUX_VERSION || process.env.PREFIX?.includes("/com.termux/"));
3824
2901
  }
3825
2902
  function canRun(command, args = []) {
3826
- const result = canRunCommand(command, args);
3827
- return result;
2903
+ const result = spawnSync(command, args, { stdio: "ignore" });
2904
+ return result.status === 0;
3828
2905
  }
3829
2906
  function runOrFail(command, args, label) {
3830
- const result = spawnSyncCommand(command, args, { stdio: "inherit" });
2907
+ const result = spawnSync(command, args, { stdio: "inherit" });
3831
2908
  if (result.status !== 0) {
3832
2909
  throw new Error(`${label} failed with exit code ${String(result.status ?? -1)}`);
3833
2910
  }
3834
2911
  }
3835
2912
  function runWithStatus(command, args) {
3836
- const result = spawnSyncCommand(command, args, { stdio: "inherit" });
2913
+ const result = spawnSync(command, args, { stdio: "inherit" });
3837
2914
  return result.status ?? -1;
3838
2915
  }
2916
+ function getUserNpmPrefix() {
2917
+ return join5(homedir3(), ".npm-global");
2918
+ }
2919
+ function resolveCodexCommand() {
2920
+ if (canRun("codex", ["--version"])) {
2921
+ return "codex";
2922
+ }
2923
+ const userCandidate = join5(getUserNpmPrefix(), "bin", "codex");
2924
+ if (existsSync3(userCandidate) && canRun(userCandidate, ["--version"])) {
2925
+ return userCandidate;
2926
+ }
2927
+ const prefix = process.env.PREFIX?.trim();
2928
+ if (!prefix) {
2929
+ return null;
2930
+ }
2931
+ const candidate = join5(prefix, "bin", "codex");
2932
+ if (existsSync3(candidate) && canRun(candidate, ["--version"])) {
2933
+ return candidate;
2934
+ }
2935
+ return null;
2936
+ }
3839
2937
  function resolveCloudflaredCommand() {
3840
2938
  if (canRun("cloudflared", ["--version"])) {
3841
2939
  return "cloudflared";
3842
2940
  }
3843
- const localCandidate = join6(homedir4(), ".local", "bin", "cloudflared");
3844
- if (existsSync5(localCandidate) && canRun(localCandidate, ["--version"])) {
2941
+ const localCandidate = join5(homedir3(), ".local", "bin", "cloudflared");
2942
+ if (existsSync3(localCandidate) && canRun(localCandidate, ["--version"])) {
3845
2943
  return localCandidate;
3846
2944
  }
3847
2945
  return null;
@@ -3894,9 +2992,9 @@ async function ensureCloudflaredInstalledLinux() {
3894
2992
  if (!mappedArch) {
3895
2993
  throw new Error(`cloudflared auto-install is not supported for Linux architecture: ${process.arch}`);
3896
2994
  }
3897
- const userBinDir = join6(homedir4(), ".local", "bin");
2995
+ const userBinDir = join5(homedir3(), ".local", "bin");
3898
2996
  mkdirSync(userBinDir, { recursive: true });
3899
- const destination = join6(userBinDir, "cloudflared");
2997
+ const destination = join5(userBinDir, "cloudflared");
3900
2998
  const downloadUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${mappedArch}`;
3901
2999
  console.log("\ncloudflared not found. Installing to ~/.local/bin...\n");
3902
3000
  await downloadFile(downloadUrl, destination);
@@ -3910,19 +3008,11 @@ async function ensureCloudflaredInstalledLinux() {
3910
3008
  return installed;
3911
3009
  }
3912
3010
  async function shouldInstallCloudflaredInteractively() {
3913
- if (hasPromptedCloudflaredInstall || hasPromptedCloudflaredInstallPersisted()) {
3914
- return false;
3915
- }
3916
- hasPromptedCloudflaredInstall = true;
3917
- await persistCloudflaredInstallPrompted();
3918
- if (process.platform === "win32") {
3919
- return false;
3920
- }
3921
3011
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
3922
3012
  console.warn("\n[cloudflared] cloudflared is missing and terminal is non-interactive, skipping install.");
3923
3013
  return false;
3924
3014
  }
3925
- const prompt = createInterface2({ input: process.stdin, output: process.stdout });
3015
+ const prompt = createInterface({ input: process.stdin, output: process.stdout });
3926
3016
  try {
3927
3017
  const answer = await prompt.question("cloudflared is not installed. Install it now to ~/.local/bin? [y/N] ");
3928
3018
  const normalized = answer.trim().toLowerCase();
@@ -3936,9 +3026,6 @@ async function resolveCloudflaredForTunnel() {
3936
3026
  if (current) {
3937
3027
  return current;
3938
3028
  }
3939
- if (process.platform === "win32") {
3940
- return null;
3941
- }
3942
3029
  const installApproved = await shouldInstallCloudflaredInteractively();
3943
3030
  if (!installApproved) {
3944
3031
  return null;
@@ -3946,8 +3033,8 @@ async function resolveCloudflaredForTunnel() {
3946
3033
  return ensureCloudflaredInstalledLinux();
3947
3034
  }
3948
3035
  function hasCodexAuth() {
3949
- const codexHome = getCodexHomePath();
3950
- return existsSync5(join6(codexHome, "auth.json"));
3036
+ const codexHome = process.env.CODEX_HOME?.trim() || join5(homedir3(), ".codex");
3037
+ return existsSync3(join5(codexHome, "auth.json"));
3951
3038
  }
3952
3039
  function ensureCodexInstalled() {
3953
3040
  let codexCommand = resolveCodexCommand();
@@ -3965,7 +3052,7 @@ function ensureCodexInstalled() {
3965
3052
  Global npm install requires elevated permissions. Retrying with --prefix ${userPrefix}...
3966
3053
  `);
3967
3054
  runOrFail("npm", ["install", "-g", "--prefix", userPrefix, pkg], `${label} (user prefix)`);
3968
- process.env.PATH = `${join6(userPrefix, "bin")}:${process.env.PATH ?? ""}`;
3055
+ process.env.PATH = `${join5(userPrefix, "bin")}:${process.env.PATH ?? ""}`;
3969
3056
  };
3970
3057
  if (isTermuxRuntime()) {
3971
3058
  console.log("\nCodex CLI not found. Installing Termux-compatible Codex CLI from npm...\n");
@@ -4028,22 +3115,19 @@ function parseCloudflaredUrl(chunk) {
4028
3115
  }
4029
3116
  function getAccessibleUrls(port) {
4030
3117
  const urls = /* @__PURE__ */ new Set([`http://localhost:${String(port)}`]);
4031
- try {
4032
- const interfaces = networkInterfaces();
4033
- for (const entries of Object.values(interfaces)) {
4034
- if (!entries) {
3118
+ const interfaces = networkInterfaces();
3119
+ for (const entries of Object.values(interfaces)) {
3120
+ if (!entries) {
3121
+ continue;
3122
+ }
3123
+ for (const entry of entries) {
3124
+ if (entry.internal) {
4035
3125
  continue;
4036
3126
  }
4037
- for (const entry of entries) {
4038
- if (entry.internal) {
4039
- continue;
4040
- }
4041
- if (entry.family === "IPv4") {
4042
- urls.add(`http://${entry.address}:${String(port)}`);
4043
- }
3127
+ if (entry.family === "IPv4") {
3128
+ urls.add(`http://${entry.address}:${String(port)}`);
4044
3129
  }
4045
3130
  }
4046
- } catch {
4047
3131
  }
4048
3132
  return Array.from(urls);
4049
3133
  }
@@ -4106,8 +3190,8 @@ function listenWithFallback(server, startPort) {
4106
3190
  });
4107
3191
  }
4108
3192
  function getCodexGlobalStatePath2() {
4109
- const codexHome = getCodexHomePath();
4110
- return join6(codexHome, ".codex-global-state.json");
3193
+ const codexHome = process.env.CODEX_HOME?.trim() || join5(homedir3(), ".codex");
3194
+ return join5(codexHome, ".codex-global-state.json");
4111
3195
  }
4112
3196
  function normalizeUniqueStrings(value) {
4113
3197
  if (!Array.isArray(value)) return [];
@@ -4231,7 +3315,7 @@ async function startServer(options) {
4231
3315
  qrcode.generate(tunnelUrl, { small: true });
4232
3316
  console.log("");
4233
3317
  }
4234
- if (options.open) openBrowser(`http://localhost:${String(port)}`);
3318
+ openBrowser(`http://localhost:${String(port)}`);
4235
3319
  function shutdown() {
4236
3320
  console.log("\nShutting down...");
4237
3321
  if (tunnelChild && !tunnelChild.killed) {
@@ -4254,7 +3338,7 @@ async function runLogin() {
4254
3338
  console.log("\nStarting `codex login`...\n");
4255
3339
  runOrFail(codexCommand, ["login"], "Codex login");
4256
3340
  }
4257
- program.argument("[projectPath]", "project directory to open on launch").option("--open-project <path>", "open project directory on launch (Codex desktop parity)").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").option("--open", "open browser on startup", true).option("--no-open", "do not open browser on startup").action(async (projectPath, opts) => {
3341
+ program.argument("[projectPath]", "project directory to open on launch").option("--open-project <path>", "open project directory on launch (Codex desktop parity)").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 (projectPath, opts) => {
4258
3342
  const rawArgv = process.argv.slice(2);
4259
3343
  const openProjectFlagIndex = rawArgv.findIndex((arg) => arg === "--open-project" || arg.startsWith("--open-project="));
4260
3344
  let openProjectOnly = (opts.openProject ?? "").trim();