@nervmor/codexui 1.0.1 → 1.0.3

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
@@ -3,10 +3,10 @@
3
3
  // src/cli/index.ts
4
4
  import { createServer as createServer2 } from "http";
5
5
  import { chmodSync, createWriteStream, existsSync as existsSync3, mkdirSync } from "fs";
6
- import { readFile as readFile4 } from "fs/promises";
7
- import { homedir as homedir3, networkInterfaces } from "os";
8
- import { join as join5 } from "path";
9
- import { spawn as spawn3, spawnSync } from "child_process";
6
+ import { readFile as readFile5 } from "fs/promises";
7
+ import { homedir as homedir4, networkInterfaces } from "os";
8
+ import { join as join6 } from "path";
9
+ import { spawn as spawn4, spawnSync } from "child_process";
10
10
  import { createInterface } from "readline/promises";
11
11
  import { fileURLToPath as fileURLToPath2 } from "url";
12
12
  import { dirname as dirname3 } from "path";
@@ -16,60 +16,721 @@ 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 extname2, isAbsolute as isAbsolute2, join as join4 } from "path";
19
+ import { dirname as dirname2, extname as extname2, isAbsolute as isAbsolute2, join as join5 } from "path";
20
20
  import { existsSync as existsSync2 } from "fs";
21
- import { writeFile as writeFile3, stat as stat4 } from "fs/promises";
21
+ import { writeFile as writeFile4, stat as stat5 } from "fs/promises";
22
22
  import express from "express";
23
23
 
24
24
  // src/server/codexAppServerBridge.ts
25
- import { spawn as spawn2 } from "child_process";
25
+ import { spawn as spawn3 } from "child_process";
26
26
  import { randomBytes } from "crypto";
27
- import { mkdtemp as mkdtemp2, readFile as readFile2, mkdir as mkdir2, stat as stat2 } from "fs/promises";
27
+ import { mkdtemp as mkdtemp3, readFile as readFile3, mkdir as mkdir3, stat as stat3 } from "fs/promises";
28
28
  import { request as httpsRequest } from "https";
29
- import { homedir as homedir2 } from "os";
30
- import { tmpdir as tmpdir2 } from "os";
31
- import { basename, isAbsolute, join as join2, resolve } from "path";
32
- import { writeFile as writeFile2 } from "fs/promises";
29
+ import { homedir as homedir3 } from "os";
30
+ import { tmpdir as tmpdir3 } from "os";
31
+ import { basename, isAbsolute, join as join3, resolve } from "path";
32
+ import { writeFile as writeFile3 } from "fs/promises";
33
33
 
34
- // src/server/skillsRoutes.ts
34
+ // src/server/accountRoutes.ts
35
35
  import { spawn } from "child_process";
36
- import { mkdtemp, readFile, readdir, rm, mkdir, stat, lstat, readlink, symlink } from "fs/promises";
37
- import { existsSync } from "fs";
36
+ import { createHash } from "crypto";
37
+ import { mkdtemp, mkdir, readFile, rm, stat, writeFile } from "fs/promises";
38
38
  import { homedir, tmpdir } from "os";
39
39
  import { join } from "path";
40
- import { writeFile } from "fs/promises";
40
+ var APP_SERVER_ARGS = [
41
+ "app-server",
42
+ "-c",
43
+ 'approval_policy="never"',
44
+ "-c",
45
+ 'sandbox_mode="danger-full-access"'
46
+ ];
47
+ var ACCOUNT_QUOTA_REFRESH_TTL_MS = 5 * 60 * 1e3;
48
+ var backgroundRefreshPromise = null;
41
49
  function asRecord(value) {
42
50
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
43
51
  }
52
+ function readString(value) {
53
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
54
+ }
55
+ function readNumber(value) {
56
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
57
+ }
58
+ function readBoolean(value) {
59
+ return typeof value === "boolean" ? value : null;
60
+ }
61
+ function setJson(res, statusCode, payload) {
62
+ res.statusCode = statusCode;
63
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
64
+ res.end(JSON.stringify(payload));
65
+ }
44
66
  function getErrorMessage(payload, fallback) {
45
67
  if (payload instanceof Error && payload.message.trim().length > 0) {
46
68
  return payload.message;
47
69
  }
48
70
  const record = asRecord(payload);
71
+ const error = record?.error;
72
+ if (typeof error === "string" && error.trim().length > 0) {
73
+ return error.trim();
74
+ }
75
+ if (typeof record?.message === "string" && record.message.trim().length > 0) {
76
+ return record.message.trim();
77
+ }
78
+ return fallback;
79
+ }
80
+ function getCodexHomeDir() {
81
+ const codexHome = process.env.CODEX_HOME?.trim();
82
+ return codexHome && codexHome.length > 0 ? codexHome : join(homedir(), ".codex");
83
+ }
84
+ function getActiveAuthPath() {
85
+ return join(getCodexHomeDir(), "auth.json");
86
+ }
87
+ function getAccountsStatePath() {
88
+ return join(getCodexHomeDir(), "accounts.json");
89
+ }
90
+ function getAccountsSnapshotRoot() {
91
+ return join(getCodexHomeDir(), "accounts");
92
+ }
93
+ function toStorageId(accountId) {
94
+ return createHash("sha256").update(accountId).digest("hex");
95
+ }
96
+ function normalizeRateLimitWindow(value) {
97
+ const record = asRecord(value);
98
+ if (!record) return null;
99
+ const usedPercent = readNumber(record.usedPercent ?? record.used_percent);
100
+ if (usedPercent === null) return null;
101
+ return {
102
+ usedPercent,
103
+ windowMinutes: readNumber(record.windowDurationMins ?? record.window_minutes),
104
+ resetsAt: readNumber(record.resetsAt ?? record.resets_at)
105
+ };
106
+ }
107
+ function normalizeCreditsSnapshot(value) {
108
+ const record = asRecord(value);
109
+ if (!record) return null;
110
+ const hasCredits = readBoolean(record.hasCredits ?? record.has_credits);
111
+ const unlimited = readBoolean(record.unlimited);
112
+ if (hasCredits === null || unlimited === null) return null;
113
+ return {
114
+ hasCredits,
115
+ unlimited,
116
+ balance: readString(record.balance)
117
+ };
118
+ }
119
+ function normalizeRateLimitSnapshot(value) {
120
+ const record = asRecord(value);
121
+ if (!record) return null;
122
+ const primary = normalizeRateLimitWindow(record.primary);
123
+ const secondary = normalizeRateLimitWindow(record.secondary);
124
+ const credits = normalizeCreditsSnapshot(record.credits);
125
+ if (!primary && !secondary && !credits) return null;
126
+ return {
127
+ limitId: readString(record.limitId ?? record.limit_id),
128
+ limitName: readString(record.limitName ?? record.limit_name),
129
+ primary,
130
+ secondary,
131
+ credits,
132
+ planType: readString(record.planType ?? record.plan_type)
133
+ };
134
+ }
135
+ function pickCodexRateLimitSnapshot(payload) {
136
+ const record = asRecord(payload);
137
+ if (!record) return null;
138
+ const rateLimitsByLimitId = asRecord(record.rateLimitsByLimitId ?? record.rate_limits_by_limit_id);
139
+ const codexBucket = normalizeRateLimitSnapshot(rateLimitsByLimitId?.codex);
140
+ if (codexBucket) return codexBucket;
141
+ return normalizeRateLimitSnapshot(record.rateLimits ?? record.rate_limits);
142
+ }
143
+ function normalizeStoredAccountEntry(value) {
144
+ const record = asRecord(value);
145
+ const accountId = readString(record?.accountId);
146
+ const storageId = readString(record?.storageId);
147
+ const lastRefreshedAtIso = readString(record?.lastRefreshedAtIso);
148
+ const quotaStatusRaw = readString(record?.quotaStatus);
149
+ const quotaStatus = quotaStatusRaw === "loading" || quotaStatusRaw === "ready" || quotaStatusRaw === "error" ? quotaStatusRaw : "idle";
150
+ if (!accountId || !storageId || !lastRefreshedAtIso) return null;
151
+ return {
152
+ accountId,
153
+ storageId,
154
+ authMode: readString(record?.authMode),
155
+ email: readString(record?.email),
156
+ planType: readString(record?.planType),
157
+ lastRefreshedAtIso,
158
+ lastActivatedAtIso: readString(record?.lastActivatedAtIso),
159
+ quotaSnapshot: normalizeRateLimitSnapshot(record?.quotaSnapshot),
160
+ quotaUpdatedAtIso: readString(record?.quotaUpdatedAtIso),
161
+ quotaStatus,
162
+ quotaError: readString(record?.quotaError)
163
+ };
164
+ }
165
+ async function readStoredAccountsState() {
166
+ try {
167
+ const raw = await readFile(getAccountsStatePath(), "utf8");
168
+ const parsed = asRecord(JSON.parse(raw));
169
+ const activeAccountId = readString(parsed?.activeAccountId);
170
+ const rawAccounts = Array.isArray(parsed?.accounts) ? parsed.accounts : [];
171
+ const accounts = rawAccounts.map((entry) => normalizeStoredAccountEntry(entry)).filter((entry) => entry !== null);
172
+ return { activeAccountId, accounts };
173
+ } catch {
174
+ return { activeAccountId: null, accounts: [] };
175
+ }
176
+ }
177
+ async function writeStoredAccountsState(state) {
178
+ await writeFile(getAccountsStatePath(), JSON.stringify(state, null, 2), { encoding: "utf8", mode: 384 });
179
+ }
180
+ function withUpsertedAccount(state, nextEntry) {
181
+ const rest = state.accounts.filter((entry) => entry.accountId !== nextEntry.accountId);
182
+ return {
183
+ activeAccountId: state.activeAccountId,
184
+ accounts: [nextEntry, ...rest]
185
+ };
186
+ }
187
+ function sortAccounts(accounts, activeAccountId) {
188
+ return [...accounts].sort((left, right) => {
189
+ const leftActive = left.accountId === activeAccountId ? 1 : 0;
190
+ const rightActive = right.accountId === activeAccountId ? 1 : 0;
191
+ if (leftActive !== rightActive) return rightActive - leftActive;
192
+ return right.lastRefreshedAtIso.localeCompare(left.lastRefreshedAtIso);
193
+ });
194
+ }
195
+ function toPublicAccountEntry(entry, activeAccountId) {
196
+ return {
197
+ ...entry,
198
+ isActive: entry.accountId === activeAccountId
199
+ };
200
+ }
201
+ function decodeBase64UrlJson(input) {
202
+ try {
203
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
204
+ const padding = normalized.length % 4 === 0 ? "" : "=".repeat(4 - normalized.length % 4);
205
+ const raw = Buffer.from(`${normalized}${padding}`, "base64").toString("utf8");
206
+ const parsed = JSON.parse(raw);
207
+ return asRecord(parsed);
208
+ } catch {
209
+ return null;
210
+ }
211
+ }
212
+ function extractTokenMetadata(accessToken) {
213
+ if (!accessToken || typeof accessToken !== "string") {
214
+ return { email: null, planType: null };
215
+ }
216
+ const parts = accessToken.split(".");
217
+ if (parts.length < 2) {
218
+ return { email: null, planType: null };
219
+ }
220
+ const payload = decodeBase64UrlJson(parts[1] ?? "");
221
+ const profile = asRecord(payload?.["https://api.openai.com/profile"]);
222
+ const auth = asRecord(payload?.["https://api.openai.com/auth"]);
223
+ return {
224
+ email: typeof profile?.email === "string" && profile.email.trim().length > 0 ? profile.email.trim() : null,
225
+ planType: typeof auth?.chatgpt_plan_type === "string" && auth.chatgpt_plan_type.trim().length > 0 ? auth.chatgpt_plan_type.trim() : null
226
+ };
227
+ }
228
+ async function readAuthFileFromPath(path) {
229
+ const raw = await readFile(path, "utf8");
230
+ const parsed = JSON.parse(raw);
231
+ const accountId = parsed.tokens?.account_id?.trim() ?? "";
232
+ if (!accountId) {
233
+ throw new Error("missing_account_id");
234
+ }
235
+ return {
236
+ raw,
237
+ parsed,
238
+ accountId,
239
+ authMode: typeof parsed.auth_mode === "string" && parsed.auth_mode.trim().length > 0 ? parsed.auth_mode.trim() : null,
240
+ metadata: extractTokenMetadata(parsed.tokens?.access_token)
241
+ };
242
+ }
243
+ function getSnapshotPath(storageId) {
244
+ return join(getAccountsSnapshotRoot(), storageId, "auth.json");
245
+ }
246
+ async function writeSnapshot(storageId, raw) {
247
+ const dir = join(getAccountsSnapshotRoot(), storageId);
248
+ await mkdir(dir, { recursive: true, mode: 448 });
249
+ await writeFile(getSnapshotPath(storageId), raw, { encoding: "utf8", mode: 384 });
250
+ }
251
+ async function readRuntimeAccountMetadata(appServer) {
252
+ const payload = asRecord(await appServer.rpc("account/read", { refreshToken: false }));
253
+ const account = asRecord(payload?.account);
254
+ return {
255
+ email: typeof account?.email === "string" && account.email.trim().length > 0 ? account.email.trim() : null,
256
+ planType: typeof account?.planType === "string" && account.planType.trim().length > 0 ? account.planType.trim() : null
257
+ };
258
+ }
259
+ async function validateSwitchedAccount(appServer) {
260
+ const metadata = await readRuntimeAccountMetadata(appServer);
261
+ const quotaPayload = await appServer.rpc("account/rateLimits/read", null);
262
+ return {
263
+ metadata,
264
+ quotaSnapshot: pickCodexRateLimitSnapshot(quotaPayload)
265
+ };
266
+ }
267
+ async function restoreActiveAuth(raw) {
268
+ const path = getActiveAuthPath();
269
+ if (raw === null) {
270
+ await rm(path, { force: true });
271
+ return;
272
+ }
273
+ await writeFile(path, raw, { encoding: "utf8", mode: 384 });
274
+ }
275
+ async function fileExists(path) {
276
+ try {
277
+ await stat(path);
278
+ return true;
279
+ } catch {
280
+ return false;
281
+ }
282
+ }
283
+ async function withTemporaryCodexAppServer(authRaw, run) {
284
+ const tempCodexHome = await mkdtemp(join(tmpdir(), "codexui-account-"));
285
+ const authPath = join(tempCodexHome, "auth.json");
286
+ await writeFile(authPath, authRaw, { encoding: "utf8", mode: 384 });
287
+ const proc = spawn("codex", [...APP_SERVER_ARGS], {
288
+ env: { ...process.env, CODEX_HOME: tempCodexHome },
289
+ stdio: ["pipe", "pipe", "pipe"]
290
+ });
291
+ let disposed = false;
292
+ let initialized = false;
293
+ let initializePromise = null;
294
+ let readBuffer = "";
295
+ let nextId = 1;
296
+ const pending = /* @__PURE__ */ new Map();
297
+ const rejectAllPending = (error) => {
298
+ for (const request of pending.values()) {
299
+ request.reject(error);
300
+ }
301
+ pending.clear();
302
+ };
303
+ proc.stdout.setEncoding("utf8");
304
+ proc.stdout.on("data", (chunk) => {
305
+ readBuffer += chunk;
306
+ let lineEnd = readBuffer.indexOf("\n");
307
+ while (lineEnd !== -1) {
308
+ const line = readBuffer.slice(0, lineEnd).trim();
309
+ readBuffer = readBuffer.slice(lineEnd + 1);
310
+ if (line.length > 0) {
311
+ try {
312
+ const message = JSON.parse(line);
313
+ if (typeof message.id === "number" && pending.has(message.id)) {
314
+ const current = pending.get(message.id);
315
+ pending.delete(message.id);
316
+ if (!current) {
317
+ lineEnd = readBuffer.indexOf("\n");
318
+ continue;
319
+ }
320
+ if (message.error?.message) {
321
+ current.reject(new Error(message.error.message));
322
+ } else {
323
+ current.resolve(message.result);
324
+ }
325
+ }
326
+ } catch {
327
+ }
328
+ }
329
+ lineEnd = readBuffer.indexOf("\n");
330
+ }
331
+ });
332
+ proc.stderr.setEncoding("utf8");
333
+ proc.stderr.on("data", () => {
334
+ });
335
+ proc.on("error", (error) => {
336
+ rejectAllPending(error instanceof Error ? error : new Error("codex app-server failed to start"));
337
+ });
338
+ proc.on("exit", () => {
339
+ if (disposed) return;
340
+ rejectAllPending(new Error("codex app-server exited unexpectedly"));
341
+ });
342
+ const sendLine = (payload) => {
343
+ proc.stdin.write(`${JSON.stringify(payload)}
344
+ `);
345
+ };
346
+ const call = async (method, params) => {
347
+ const id = nextId++;
348
+ return await new Promise((resolve2, reject) => {
349
+ pending.set(id, { resolve: resolve2, reject });
350
+ sendLine({
351
+ jsonrpc: "2.0",
352
+ id,
353
+ method,
354
+ params
355
+ });
356
+ });
357
+ };
358
+ const ensureInitialized = async () => {
359
+ if (initialized) return;
360
+ if (initializePromise) {
361
+ await initializePromise;
362
+ return;
363
+ }
364
+ initializePromise = call("initialize", {
365
+ clientInfo: {
366
+ name: "codexui-account-refresh",
367
+ version: "0.1.0"
368
+ },
369
+ capabilities: {
370
+ experimentalApi: true
371
+ }
372
+ }).then(() => {
373
+ sendLine({
374
+ jsonrpc: "2.0",
375
+ method: "initialized"
376
+ });
377
+ initialized = true;
378
+ }).finally(() => {
379
+ initializePromise = null;
380
+ });
381
+ await initializePromise;
382
+ };
383
+ const dispose = async () => {
384
+ if (disposed) return;
385
+ disposed = true;
386
+ rejectAllPending(new Error("codex app-server stopped"));
387
+ try {
388
+ proc.stdin.end();
389
+ } catch {
390
+ }
391
+ try {
392
+ proc.kill("SIGTERM");
393
+ } catch {
394
+ }
395
+ await rm(tempCodexHome, { recursive: true, force: true });
396
+ };
397
+ try {
398
+ await ensureInitialized();
399
+ return await run(call);
400
+ } finally {
401
+ await dispose();
402
+ }
403
+ }
404
+ async function inspectStoredAccount(entry) {
405
+ const snapshotPath = getSnapshotPath(entry.storageId);
406
+ const authRaw = await readFile(snapshotPath, "utf8");
407
+ return await withTemporaryCodexAppServer(authRaw, async (rpc) => {
408
+ const accountPayload = asRecord(await rpc("account/read", { refreshToken: false }));
409
+ const account = asRecord(accountPayload?.account);
410
+ const quotaPayload = await rpc("account/rateLimits/read", null);
411
+ return {
412
+ metadata: {
413
+ email: typeof account?.email === "string" && account.email.trim().length > 0 ? account.email.trim() : entry.email,
414
+ planType: typeof account?.planType === "string" && account.planType.trim().length > 0 ? account.planType.trim() : entry.planType
415
+ },
416
+ quotaSnapshot: pickCodexRateLimitSnapshot(quotaPayload)
417
+ };
418
+ });
419
+ }
420
+ function shouldRefreshAccountQuota(entry) {
421
+ if (entry.quotaStatus === "loading") return false;
422
+ if (!entry.quotaUpdatedAtIso) return true;
423
+ const updatedAtMs = Date.parse(entry.quotaUpdatedAtIso);
424
+ if (!Number.isFinite(updatedAtMs)) return true;
425
+ return Date.now() - updatedAtMs >= ACCOUNT_QUOTA_REFRESH_TTL_MS;
426
+ }
427
+ async function replaceStoredAccount(nextEntry, activeAccountId) {
428
+ const state = await readStoredAccountsState();
429
+ const nextState = withUpsertedAccount({
430
+ activeAccountId,
431
+ accounts: state.accounts
432
+ }, nextEntry);
433
+ await writeStoredAccountsState({
434
+ activeAccountId,
435
+ accounts: nextState.accounts
436
+ });
437
+ }
438
+ async function refreshAccountsInBackground(accountIds, activeAccountId) {
439
+ for (const accountId of accountIds) {
440
+ const state = await readStoredAccountsState();
441
+ const entry = state.accounts.find((item) => item.accountId === accountId);
442
+ if (!entry) continue;
443
+ try {
444
+ const inspected = await inspectStoredAccount(entry);
445
+ await replaceStoredAccount({
446
+ ...entry,
447
+ email: inspected.metadata.email ?? entry.email,
448
+ planType: inspected.metadata.planType ?? entry.planType,
449
+ quotaSnapshot: inspected.quotaSnapshot ?? entry.quotaSnapshot,
450
+ quotaUpdatedAtIso: (/* @__PURE__ */ new Date()).toISOString(),
451
+ quotaStatus: "ready",
452
+ quotaError: null
453
+ }, activeAccountId);
454
+ } catch (error) {
455
+ await replaceStoredAccount({
456
+ ...entry,
457
+ quotaUpdatedAtIso: (/* @__PURE__ */ new Date()).toISOString(),
458
+ quotaStatus: "error",
459
+ quotaError: getErrorMessage(error, "Failed to refresh account quota")
460
+ }, activeAccountId);
461
+ }
462
+ }
463
+ }
464
+ async function scheduleAccountsBackgroundRefresh(options = {}) {
465
+ const state = await readStoredAccountsState();
466
+ if (state.accounts.length === 0) return state;
467
+ if (backgroundRefreshPromise) return state;
468
+ const allowedIds = options.accountIds ? new Set(options.accountIds) : null;
469
+ const candidates = state.accounts.filter((entry) => !allowedIds || allowedIds.has(entry.accountId)).filter((entry) => options.force === true || shouldRefreshAccountQuota(entry)).sort((left, right) => {
470
+ const prioritize = options.prioritizeAccountId ?? "";
471
+ const leftPriority = left.accountId === prioritize ? 1 : 0;
472
+ const rightPriority = right.accountId === prioritize ? 1 : 0;
473
+ if (leftPriority !== rightPriority) return rightPriority - leftPriority;
474
+ return 0;
475
+ });
476
+ if (candidates.length === 0) return state;
477
+ const candidateIds = new Set(candidates.map((entry) => entry.accountId));
478
+ const markedState = {
479
+ activeAccountId: state.activeAccountId,
480
+ accounts: state.accounts.map((entry) => candidateIds.has(entry.accountId) ? {
481
+ ...entry,
482
+ quotaStatus: "loading",
483
+ quotaError: null
484
+ } : entry)
485
+ };
486
+ await writeStoredAccountsState(markedState);
487
+ backgroundRefreshPromise = refreshAccountsInBackground(
488
+ candidates.map((entry) => entry.accountId),
489
+ markedState.activeAccountId
490
+ ).finally(() => {
491
+ backgroundRefreshPromise = null;
492
+ });
493
+ return markedState;
494
+ }
495
+ async function importAccountFromAuthPath(path) {
496
+ const imported = await readAuthFileFromPath(path);
497
+ const storageId = toStorageId(imported.accountId);
498
+ await writeSnapshot(storageId, imported.raw);
499
+ const state = await readStoredAccountsState();
500
+ const existing = state.accounts.find((entry) => entry.accountId === imported.accountId) ?? null;
501
+ const nextEntry = {
502
+ accountId: imported.accountId,
503
+ storageId,
504
+ authMode: imported.authMode,
505
+ email: imported.metadata.email ?? existing?.email ?? null,
506
+ planType: imported.metadata.planType ?? existing?.planType ?? null,
507
+ lastRefreshedAtIso: (/* @__PURE__ */ new Date()).toISOString(),
508
+ lastActivatedAtIso: existing?.lastActivatedAtIso ?? null,
509
+ quotaSnapshot: existing?.quotaSnapshot ?? null,
510
+ quotaUpdatedAtIso: existing?.quotaUpdatedAtIso ?? null,
511
+ quotaStatus: existing?.quotaStatus ?? "idle",
512
+ quotaError: existing?.quotaError ?? null
513
+ };
514
+ const nextState = withUpsertedAccount(state, nextEntry);
515
+ await writeStoredAccountsState(nextState);
516
+ return {
517
+ activeAccountId: nextState.activeAccountId,
518
+ importedAccountId: imported.accountId,
519
+ accounts: sortAccounts(nextState.accounts, nextState.activeAccountId).map((entry) => toPublicAccountEntry(entry, nextState.activeAccountId))
520
+ };
521
+ }
522
+ async function handleAccountRoutes(req, res, url, context) {
523
+ const { appServer } = context;
524
+ if (req.method === "GET" && url.pathname === "/codex-api/accounts") {
525
+ const state = await scheduleAccountsBackgroundRefresh();
526
+ setJson(res, 200, {
527
+ data: {
528
+ activeAccountId: state.activeAccountId,
529
+ accounts: sortAccounts(state.accounts, state.activeAccountId).map((entry) => toPublicAccountEntry(entry, state.activeAccountId))
530
+ }
531
+ });
532
+ return true;
533
+ }
534
+ if (req.method === "GET" && url.pathname === "/codex-api/accounts/active") {
535
+ const state = await readStoredAccountsState();
536
+ const active = state.activeAccountId ? state.accounts.find((entry) => entry.accountId === state.activeAccountId) ?? null : null;
537
+ setJson(res, 200, {
538
+ data: active ? toPublicAccountEntry(active, state.activeAccountId) : null
539
+ });
540
+ return true;
541
+ }
542
+ if (req.method === "POST" && url.pathname === "/codex-api/accounts/refresh") {
543
+ try {
544
+ const imported = await importAccountFromAuthPath(getActiveAuthPath());
545
+ try {
546
+ appServer.dispose();
547
+ const inspection = await validateSwitchedAccount(appServer);
548
+ const state = await readStoredAccountsState();
549
+ const importedAccountId = imported.importedAccountId;
550
+ const target = state.accounts.find((entry) => entry.accountId === importedAccountId) ?? null;
551
+ if (!target) {
552
+ throw new Error("account_not_found");
553
+ }
554
+ const nextEntry = {
555
+ ...target,
556
+ email: inspection.metadata.email ?? target.email,
557
+ planType: inspection.metadata.planType ?? target.planType,
558
+ lastActivatedAtIso: (/* @__PURE__ */ new Date()).toISOString(),
559
+ quotaSnapshot: inspection.quotaSnapshot ?? target.quotaSnapshot,
560
+ quotaUpdatedAtIso: (/* @__PURE__ */ new Date()).toISOString(),
561
+ quotaStatus: "ready",
562
+ quotaError: null
563
+ };
564
+ const nextState = withUpsertedAccount({
565
+ activeAccountId: importedAccountId,
566
+ accounts: state.accounts
567
+ }, nextEntry);
568
+ await writeStoredAccountsState({
569
+ activeAccountId: importedAccountId,
570
+ accounts: nextState.accounts
571
+ });
572
+ const backgroundState = await scheduleAccountsBackgroundRefresh({
573
+ force: true,
574
+ prioritizeAccountId: importedAccountId,
575
+ accountIds: nextState.accounts.filter((entry) => entry.accountId !== importedAccountId).map((entry) => entry.accountId)
576
+ });
577
+ setJson(res, 200, {
578
+ data: {
579
+ activeAccountId: importedAccountId,
580
+ importedAccountId,
581
+ accounts: sortAccounts(backgroundState.accounts, importedAccountId).map((entry) => toPublicAccountEntry(entry, importedAccountId))
582
+ }
583
+ });
584
+ } catch (error) {
585
+ setJson(res, 502, {
586
+ error: "account_refresh_failed",
587
+ message: getErrorMessage(error, "Failed to refresh account")
588
+ });
589
+ }
590
+ } catch (error) {
591
+ const message = getErrorMessage(error, "Failed to refresh account");
592
+ if (message === "missing_account_id") {
593
+ setJson(res, 400, { error: "missing_account_id", message: "Current auth.json is missing tokens.account_id." });
594
+ return true;
595
+ }
596
+ setJson(res, 400, { error: "invalid_auth_json", message: "Failed to parse the current auth.json file." });
597
+ }
598
+ return true;
599
+ }
600
+ if (req.method === "POST" && url.pathname === "/codex-api/accounts/switch") {
601
+ try {
602
+ if (appServer.listPendingServerRequests().length > 0) {
603
+ setJson(res, 409, {
604
+ error: "account_switch_blocked",
605
+ message: "Finish pending approval requests before switching accounts."
606
+ });
607
+ return true;
608
+ }
609
+ const rawBody = await new Promise((resolve2, reject) => {
610
+ let body = "";
611
+ req.setEncoding("utf8");
612
+ req.on("data", (chunk) => {
613
+ body += chunk;
614
+ });
615
+ req.on("end", () => resolve2(body));
616
+ req.on("error", reject);
617
+ });
618
+ const payload = asRecord(rawBody.length > 0 ? JSON.parse(rawBody) : {});
619
+ const accountId = typeof payload?.accountId === "string" ? payload.accountId.trim() : "";
620
+ if (!accountId) {
621
+ setJson(res, 400, { error: "account_not_found", message: "Missing accountId." });
622
+ return true;
623
+ }
624
+ const state = await readStoredAccountsState();
625
+ const target = state.accounts.find((entry) => entry.accountId === accountId) ?? null;
626
+ if (!target) {
627
+ setJson(res, 404, { error: "account_not_found", message: "The requested account was not found." });
628
+ return true;
629
+ }
630
+ const snapshotPath = getSnapshotPath(target.storageId);
631
+ if (!await fileExists(snapshotPath)) {
632
+ setJson(res, 404, { error: "account_not_found", message: "The requested account snapshot is missing." });
633
+ return true;
634
+ }
635
+ let previousRaw = null;
636
+ try {
637
+ previousRaw = await readFile(getActiveAuthPath(), "utf8");
638
+ } catch {
639
+ previousRaw = null;
640
+ }
641
+ const targetRaw = await readFile(snapshotPath, "utf8");
642
+ await writeFile(getActiveAuthPath(), targetRaw, { encoding: "utf8", mode: 384 });
643
+ try {
644
+ appServer.dispose();
645
+ const inspection = await validateSwitchedAccount(appServer);
646
+ const nextEntry = {
647
+ ...target,
648
+ email: inspection.metadata.email ?? target.email,
649
+ planType: inspection.metadata.planType ?? target.planType,
650
+ lastActivatedAtIso: (/* @__PURE__ */ new Date()).toISOString(),
651
+ quotaSnapshot: inspection.quotaSnapshot ?? target.quotaSnapshot,
652
+ quotaUpdatedAtIso: (/* @__PURE__ */ new Date()).toISOString(),
653
+ quotaStatus: "ready",
654
+ quotaError: null
655
+ };
656
+ const nextState = withUpsertedAccount({
657
+ activeAccountId: accountId,
658
+ accounts: state.accounts
659
+ }, nextEntry);
660
+ await writeStoredAccountsState({
661
+ activeAccountId: accountId,
662
+ accounts: nextState.accounts
663
+ });
664
+ void scheduleAccountsBackgroundRefresh({
665
+ force: true,
666
+ prioritizeAccountId: accountId,
667
+ accountIds: nextState.accounts.filter((entry) => entry.accountId !== accountId).map((entry) => entry.accountId)
668
+ });
669
+ setJson(res, 200, {
670
+ ok: true,
671
+ data: {
672
+ activeAccountId: accountId,
673
+ account: toPublicAccountEntry(nextEntry, accountId)
674
+ }
675
+ });
676
+ } catch (error) {
677
+ await restoreActiveAuth(previousRaw);
678
+ appServer.dispose();
679
+ setJson(res, 502, {
680
+ error: "account_switch_failed",
681
+ message: getErrorMessage(error, "Failed to switch account")
682
+ });
683
+ }
684
+ } catch (error) {
685
+ setJson(res, 400, {
686
+ error: "invalid_auth_json",
687
+ message: getErrorMessage(error, "Failed to switch account")
688
+ });
689
+ }
690
+ return true;
691
+ }
692
+ return false;
693
+ }
694
+
695
+ // src/server/skillsRoutes.ts
696
+ import { spawn as spawn2 } from "child_process";
697
+ import { mkdtemp as mkdtemp2, readFile as readFile2, readdir, rm as rm2, mkdir as mkdir2, stat as stat2, lstat, readlink, symlink } from "fs/promises";
698
+ import { existsSync } from "fs";
699
+ import { homedir as homedir2, tmpdir as tmpdir2 } from "os";
700
+ import { join as join2 } from "path";
701
+ import { writeFile as writeFile2 } from "fs/promises";
702
+ function asRecord2(value) {
703
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
704
+ }
705
+ function getErrorMessage2(payload, fallback) {
706
+ if (payload instanceof Error && payload.message.trim().length > 0) {
707
+ return payload.message;
708
+ }
709
+ const record = asRecord2(payload);
49
710
  if (!record) return fallback;
50
711
  const error = record.error;
51
712
  if (typeof error === "string" && error.length > 0) return error;
52
- const nestedError = asRecord(error);
713
+ const nestedError = asRecord2(error);
53
714
  if (nestedError && typeof nestedError.message === "string" && nestedError.message.length > 0) {
54
715
  return nestedError.message;
55
716
  }
56
717
  return fallback;
57
718
  }
58
- function setJson(res, statusCode, payload) {
719
+ function setJson2(res, statusCode, payload) {
59
720
  res.statusCode = statusCode;
60
721
  res.setHeader("Content-Type", "application/json; charset=utf-8");
61
722
  res.end(JSON.stringify(payload));
62
723
  }
63
- function getCodexHomeDir() {
724
+ function getCodexHomeDir2() {
64
725
  const codexHome = process.env.CODEX_HOME?.trim();
65
- return codexHome && codexHome.length > 0 ? codexHome : join(homedir(), ".codex");
726
+ return codexHome && codexHome.length > 0 ? codexHome : join2(homedir2(), ".codex");
66
727
  }
67
728
  function getSkillsInstallDir() {
68
- return join(getCodexHomeDir(), "skills");
729
+ return join2(getCodexHomeDir2(), "skills");
69
730
  }
70
731
  async function runCommand(command, args, options = {}) {
71
732
  await new Promise((resolve2, reject) => {
72
- const proc = spawn(command, args, {
733
+ const proc = spawn2(command, args, {
73
734
  cwd: options.cwd,
74
735
  env: process.env,
75
736
  stdio: ["ignore", "pipe", "pipe"]
@@ -96,7 +757,7 @@ async function runCommand(command, args, options = {}) {
96
757
  }
97
758
  async function runCommandWithOutput(command, args, options = {}) {
98
759
  return await new Promise((resolve2, reject) => {
99
- const proc = spawn(command, args, {
760
+ const proc = spawn2(command, args, {
100
761
  cwd: options.cwd,
101
762
  env: process.env,
102
763
  stdio: ["ignore", "pipe", "pipe"]
@@ -152,7 +813,7 @@ var skillsTreeCache = null;
152
813
  var metaCache = /* @__PURE__ */ new Map();
153
814
  async function getGhToken() {
154
815
  try {
155
- const proc = spawn("gh", ["auth", "token"], { stdio: ["ignore", "pipe", "ignore"] });
816
+ const proc = spawn2("gh", ["auth", "token"], { stdio: ["ignore", "pipe", "ignore"] });
156
817
  let out = "";
157
818
  proc.stdout.on("data", (d) => {
158
819
  out += d.toString();
@@ -255,9 +916,9 @@ async function scanInstalledSkillsFromDisk() {
255
916
  const entries = await readdir(skillsDir, { withFileTypes: true });
256
917
  for (const entry of entries) {
257
918
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
258
- const skillMd = join(skillsDir, entry.name, "SKILL.md");
919
+ const skillMd = join2(skillsDir, entry.name, "SKILL.md");
259
920
  try {
260
- await stat(skillMd);
921
+ await stat2(skillMd);
261
922
  map.set(entry.name, { name: entry.name, path: skillMd, enabled: true });
262
923
  } catch {
263
924
  }
@@ -267,11 +928,11 @@ async function scanInstalledSkillsFromDisk() {
267
928
  return map;
268
929
  }
269
930
  function getSkillsSyncStatePath() {
270
- return join(getCodexHomeDir(), "skills-sync.json");
931
+ return join2(getCodexHomeDir2(), "skills-sync.json");
271
932
  }
272
933
  async function readSkillsSyncState() {
273
934
  try {
274
- const raw = await readFile(getSkillsSyncStatePath(), "utf8");
935
+ const raw = await readFile2(getSkillsSyncStatePath(), "utf8");
275
936
  const parsed = JSON.parse(raw);
276
937
  return parsed && typeof parsed === "object" ? parsed : {};
277
938
  } catch {
@@ -279,7 +940,7 @@ async function readSkillsSyncState() {
279
940
  }
280
941
  }
281
942
  async function writeSkillsSyncState(state) {
282
- await writeFile(getSkillsSyncStatePath(), JSON.stringify(state), "utf8");
943
+ await writeFile2(getSkillsSyncStatePath(), JSON.stringify(state), "utf8");
283
944
  }
284
945
  async function getGithubJson(url, token, method = "GET", body) {
285
946
  const resp = await fetch(url, {
@@ -402,7 +1063,7 @@ async function ensurePrivateForkFromUpstream(token, username, repoName) {
402
1063
  }
403
1064
  if (!ready) throw new Error("Private mirror repo was created but is not available yet");
404
1065
  if (!created) return;
405
- const tmp = await mkdtemp(join(tmpdir(), "codex-skills-seed-"));
1066
+ const tmp = await mkdtemp2(join2(tmpdir2(), "codex-skills-seed-"));
406
1067
  try {
407
1068
  const upstreamUrl = `https://github.com/${SYNC_UPSTREAM_SKILLS_OWNER}/${SYNC_UPSTREAM_SKILLS_REPO}.git`;
408
1069
  const branch = getPreferredSyncBranch();
@@ -419,7 +1080,7 @@ async function ensurePrivateForkFromUpstream(token, username, repoName) {
419
1080
  }
420
1081
  await runCommand("git", ["push", "-u", "origin", `HEAD:${branch}`], { cwd: tmp });
421
1082
  } finally {
422
- await rm(tmp, { recursive: true, force: true });
1083
+ await rm2(tmp, { recursive: true, force: true });
423
1084
  }
424
1085
  }
425
1086
  async function readRemoteSkillsManifest(token, repoOwner, repoName) {
@@ -440,7 +1101,7 @@ async function readRemoteSkillsManifest(token, repoOwner, repoName) {
440
1101
  if (!Array.isArray(parsed)) return [];
441
1102
  const skills = [];
442
1103
  for (const row of parsed) {
443
- const item = asRecord(row);
1104
+ const item = asRecord2(row);
444
1105
  const owner = typeof item?.owner === "string" ? item.owner : "";
445
1106
  const name = typeof item?.name === "string" ? item.name : "";
446
1107
  if (!name) continue;
@@ -475,11 +1136,11 @@ function toGitHubTokenRemote(repoOwner, repoName, token) {
475
1136
  }
476
1137
  async function ensureSkillsWorkingTreeRepo(repoUrl, branch) {
477
1138
  const localDir = getSkillsInstallDir();
478
- await mkdir(localDir, { recursive: true });
479
- const gitDir = join(localDir, ".git");
1139
+ await mkdir2(localDir, { recursive: true });
1140
+ const gitDir = join2(localDir, ".git");
480
1141
  let hasGitDir = false;
481
1142
  try {
482
- hasGitDir = (await stat(gitDir)).isDirectory();
1143
+ hasGitDir = (await stat2(gitDir)).isDirectory();
483
1144
  } catch {
484
1145
  hasGitDir = false;
485
1146
  }
@@ -591,7 +1252,7 @@ async function walkFileMtimes(rootDir, currentDir, out) {
591
1252
  for (const entry of entries) {
592
1253
  const entryName = String(entry.name);
593
1254
  if (entryName === ".git") continue;
594
- const absolutePath = join(currentDir, entryName);
1255
+ const absolutePath = join2(currentDir, entryName);
595
1256
  const relativePath = absolutePath.slice(rootDir.length + 1);
596
1257
  if (entry.isDirectory()) {
597
1258
  await walkFileMtimes(rootDir, absolutePath, out);
@@ -599,7 +1260,7 @@ async function walkFileMtimes(rootDir, currentDir, out) {
599
1260
  }
600
1261
  if (!entry.isFile()) continue;
601
1262
  try {
602
- const info = await stat(absolutePath);
1263
+ const info = await stat2(absolutePath);
603
1264
  out.set(relativePath, info.mtimeMs);
604
1265
  } catch {
605
1266
  }
@@ -684,41 +1345,41 @@ async function autoPushSyncedSkills(appServer) {
684
1345
  await syncInstalledSkillsFolderToRepo(state.githubToken, state.repoOwner, state.repoName, installedMap);
685
1346
  }
686
1347
  async function ensureCodexAgentsSymlinkToSkillsAgents() {
687
- const codexHomeDir = getCodexHomeDir();
688
- const skillsAgentsPath = join(codexHomeDir, "skills", "AGENTS.md");
689
- const codexAgentsPath = join(codexHomeDir, "AGENTS.md");
690
- await mkdir(join(codexHomeDir, "skills"), { recursive: true });
1348
+ const codexHomeDir = getCodexHomeDir2();
1349
+ const skillsAgentsPath = join2(codexHomeDir, "skills", "AGENTS.md");
1350
+ const codexAgentsPath = join2(codexHomeDir, "AGENTS.md");
1351
+ await mkdir2(join2(codexHomeDir, "skills"), { recursive: true });
691
1352
  let copiedFromCodex = false;
692
1353
  try {
693
1354
  const codexAgentsStat = await lstat(codexAgentsPath);
694
1355
  if (codexAgentsStat.isFile() || codexAgentsStat.isSymbolicLink()) {
695
- const content = await readFile(codexAgentsPath, "utf8");
696
- await writeFile(skillsAgentsPath, content, "utf8");
1356
+ const content = await readFile2(codexAgentsPath, "utf8");
1357
+ await writeFile2(skillsAgentsPath, content, "utf8");
697
1358
  copiedFromCodex = true;
698
1359
  } else {
699
- await rm(codexAgentsPath, { force: true, recursive: true });
1360
+ await rm2(codexAgentsPath, { force: true, recursive: true });
700
1361
  }
701
1362
  } catch {
702
1363
  }
703
1364
  if (!copiedFromCodex) {
704
1365
  try {
705
- const skillsAgentsStat = await stat(skillsAgentsPath);
1366
+ const skillsAgentsStat = await stat2(skillsAgentsPath);
706
1367
  if (!skillsAgentsStat.isFile()) {
707
- await rm(skillsAgentsPath, { force: true, recursive: true });
708
- await writeFile(skillsAgentsPath, "", "utf8");
1368
+ await rm2(skillsAgentsPath, { force: true, recursive: true });
1369
+ await writeFile2(skillsAgentsPath, "", "utf8");
709
1370
  }
710
1371
  } catch {
711
- await writeFile(skillsAgentsPath, "", "utf8");
1372
+ await writeFile2(skillsAgentsPath, "", "utf8");
712
1373
  }
713
1374
  }
714
- const relativeTarget = join("skills", "AGENTS.md");
1375
+ const relativeTarget = join2("skills", "AGENTS.md");
715
1376
  try {
716
1377
  const current = await lstat(codexAgentsPath);
717
1378
  if (current.isSymbolicLink()) {
718
1379
  const existingTarget = await readlink(codexAgentsPath);
719
1380
  if (existingTarget === relativeTarget) return;
720
1381
  }
721
- await rm(codexAgentsPath, { force: true, recursive: true });
1382
+ await rm2(codexAgentsPath, { force: true, recursive: true });
722
1383
  } catch {
723
1384
  }
724
1385
  await symlink(relativeTarget, codexAgentsPath);
@@ -768,7 +1429,7 @@ async function initializeSkillsSyncOnStartup(appServer) {
768
1429
  startupSyncStatus.lastSuccessAtIso = (/* @__PURE__ */ new Date()).toISOString();
769
1430
  startupSyncStatus.lastAction = "startup-sync-complete";
770
1431
  } catch (error) {
771
- startupSyncStatus.lastError = getErrorMessage(error, "startup-sync-failed");
1432
+ startupSyncStatus.lastError = getErrorMessage2(error, "startup-sync-failed");
772
1433
  startupSyncStatus.lastAction = "startup-sync-failed";
773
1434
  } finally {
774
1435
  startupSyncStatus.inProgress = false;
@@ -849,15 +1510,15 @@ async function handleSkillsRoutes(req, res, url, context) {
849
1510
  installed.push({ ...base, installed: true, path: info.path, enabled: info.enabled });
850
1511
  }
851
1512
  const results = await searchSkillsHub(allEntries, q, limit, sort, installedMap);
852
- setJson(res, 200, { data: results, installed, total: allEntries.length });
1513
+ setJson2(res, 200, { data: results, installed, total: allEntries.length });
853
1514
  } catch (error) {
854
- setJson(res, 502, { error: getErrorMessage(error, "Failed to fetch skills hub") });
1515
+ setJson2(res, 502, { error: getErrorMessage2(error, "Failed to fetch skills hub") });
855
1516
  }
856
1517
  return true;
857
1518
  }
858
1519
  if (req.method === "GET" && url.pathname === "/codex-api/skills-sync/status") {
859
1520
  const state = await readSkillsSyncState();
860
- setJson(res, 200, {
1521
+ setJson2(res, 200, {
861
1522
  data: {
862
1523
  loggedIn: Boolean(state.githubToken),
863
1524
  githubUsername: state.githubUsername ?? "",
@@ -880,25 +1541,25 @@ async function handleSkillsRoutes(req, res, url, context) {
880
1541
  if (req.method === "POST" && url.pathname === "/codex-api/skills-sync/github/start-login") {
881
1542
  try {
882
1543
  const started = await startGithubDeviceLogin();
883
- setJson(res, 200, { data: started });
1544
+ setJson2(res, 200, { data: started });
884
1545
  } catch (error) {
885
- setJson(res, 502, { error: getErrorMessage(error, "Failed to start GitHub login") });
1546
+ setJson2(res, 502, { error: getErrorMessage2(error, "Failed to start GitHub login") });
886
1547
  }
887
1548
  return true;
888
1549
  }
889
1550
  if (req.method === "POST" && url.pathname === "/codex-api/skills-sync/github/token-login") {
890
1551
  try {
891
- const payload = asRecord(await readJsonBody2(req));
1552
+ const payload = asRecord2(await readJsonBody2(req));
892
1553
  const token = typeof payload?.token === "string" ? payload.token.trim() : "";
893
1554
  if (!token) {
894
- setJson(res, 400, { error: "Missing GitHub token" });
1555
+ setJson2(res, 400, { error: "Missing GitHub token" });
895
1556
  return true;
896
1557
  }
897
1558
  const username = await resolveGithubUsername(token);
898
1559
  await finalizeGithubLoginAndSync(token, username, appServer);
899
- setJson(res, 200, { ok: true, data: { githubUsername: username } });
1560
+ setJson2(res, 200, { ok: true, data: { githubUsername: username } });
900
1561
  } catch (error) {
901
- setJson(res, 502, { error: getErrorMessage(error, "Failed to login with GitHub token") });
1562
+ setJson2(res, 502, { error: getErrorMessage2(error, "Failed to login with GitHub token") });
902
1563
  }
903
1564
  return true;
904
1565
  }
@@ -912,31 +1573,31 @@ async function handleSkillsRoutes(req, res, url, context) {
912
1573
  repoOwner: void 0,
913
1574
  repoName: void 0
914
1575
  });
915
- setJson(res, 200, { ok: true });
1576
+ setJson2(res, 200, { ok: true });
916
1577
  } catch (error) {
917
- setJson(res, 500, { error: getErrorMessage(error, "Failed to logout GitHub") });
1578
+ setJson2(res, 500, { error: getErrorMessage2(error, "Failed to logout GitHub") });
918
1579
  }
919
1580
  return true;
920
1581
  }
921
1582
  if (req.method === "POST" && url.pathname === "/codex-api/skills-sync/github/complete-login") {
922
1583
  try {
923
- const payload = asRecord(await readJsonBody2(req));
1584
+ const payload = asRecord2(await readJsonBody2(req));
924
1585
  const deviceCode = typeof payload?.deviceCode === "string" ? payload.deviceCode : "";
925
1586
  if (!deviceCode) {
926
- setJson(res, 400, { error: "Missing deviceCode" });
1587
+ setJson2(res, 400, { error: "Missing deviceCode" });
927
1588
  return true;
928
1589
  }
929
1590
  const result = await completeGithubDeviceLogin(deviceCode);
930
1591
  if (!result.token) {
931
- setJson(res, 200, { ok: false, pending: result.error === "authorization_pending", error: result.error || "login_failed" });
1592
+ setJson2(res, 200, { ok: false, pending: result.error === "authorization_pending", error: result.error || "login_failed" });
932
1593
  return true;
933
1594
  }
934
1595
  const token = result.token;
935
1596
  const username = await resolveGithubUsername(token);
936
1597
  await finalizeGithubLoginAndSync(token, username, appServer);
937
- setJson(res, 200, { ok: true, data: { githubUsername: username } });
1598
+ setJson2(res, 200, { ok: true, data: { githubUsername: username } });
938
1599
  } catch (error) {
939
- setJson(res, 502, { error: getErrorMessage(error, "Failed to complete GitHub login") });
1600
+ setJson2(res, 502, { error: getErrorMessage2(error, "Failed to complete GitHub login") });
940
1601
  }
941
1602
  return true;
942
1603
  }
@@ -944,20 +1605,20 @@ async function handleSkillsRoutes(req, res, url, context) {
944
1605
  try {
945
1606
  const state = await readSkillsSyncState();
946
1607
  if (!state.githubToken || !state.repoOwner || !state.repoName) {
947
- setJson(res, 400, { error: "Skills sync is not configured yet" });
1608
+ setJson2(res, 400, { error: "Skills sync is not configured yet" });
948
1609
  return true;
949
1610
  }
950
1611
  if (isUpstreamSkillsRepo(state.repoOwner, state.repoName)) {
951
- setJson(res, 400, { error: "Refusing to push to upstream repository" });
1612
+ setJson2(res, 400, { error: "Refusing to push to upstream repository" });
952
1613
  return true;
953
1614
  }
954
1615
  const local = await collectLocalSyncedSkills(appServer);
955
1616
  const installedMap = await scanInstalledSkillsFromDisk();
956
1617
  await writeRemoteSkillsManifest(state.githubToken, state.repoOwner, state.repoName, local);
957
1618
  await syncInstalledSkillsFolderToRepo(state.githubToken, state.repoOwner, state.repoName, installedMap);
958
- setJson(res, 200, { ok: true, data: { synced: local.length } });
1619
+ setJson2(res, 200, { ok: true, data: { synced: local.length } });
959
1620
  } catch (error) {
960
- setJson(res, 502, { error: getErrorMessage(error, "Failed to push synced skills") });
1621
+ setJson2(res, 502, { error: getErrorMessage2(error, "Failed to push synced skills") });
961
1622
  }
962
1623
  return true;
963
1624
  }
@@ -970,7 +1631,7 @@ async function handleSkillsRoutes(req, res, url, context) {
970
1631
  await appServer.rpc("skills/list", { forceReload: true });
971
1632
  } catch {
972
1633
  }
973
- setJson(res, 200, { ok: true, data: { synced: 0, source: "upstream" } });
1634
+ setJson2(res, 200, { ok: true, data: { synced: 0, source: "upstream" } });
974
1635
  return true;
975
1636
  }
976
1637
  const remote = await readRemoteSkillsManifest(state.githubToken, state.repoOwner, state.repoName);
@@ -1009,13 +1670,13 @@ async function handleSkillsRoutes(req, res, url, context) {
1009
1670
  "git"
1010
1671
  ]);
1011
1672
  }
1012
- const skillPath = join(localDir, skill.name);
1673
+ const skillPath = join2(localDir, skill.name);
1013
1674
  await appServer.rpc("skills/config/write", { path: skillPath, enabled: skill.enabled });
1014
1675
  }
1015
1676
  const remoteNames = new Set(remote.map((row) => row.name));
1016
1677
  for (const [name, localInfo] of localSkills.entries()) {
1017
1678
  if (!remoteNames.has(name)) {
1018
- await rm(localInfo.path.replace(/\/SKILL\.md$/, ""), { recursive: true, force: true });
1679
+ await rm2(localInfo.path.replace(/\/SKILL\.md$/, ""), { recursive: true, force: true });
1019
1680
  }
1020
1681
  }
1021
1682
  const nextOwners = {};
@@ -1028,9 +1689,9 @@ async function handleSkillsRoutes(req, res, url, context) {
1028
1689
  await appServer.rpc("skills/list", { forceReload: true });
1029
1690
  } catch {
1030
1691
  }
1031
- setJson(res, 200, { ok: true, data: { synced: remote.length } });
1692
+ setJson2(res, 200, { ok: true, data: { synced: remote.length } });
1032
1693
  } catch (error) {
1033
- setJson(res, 502, { error: getErrorMessage(error, "Failed to pull synced skills") });
1694
+ setJson2(res, 502, { error: getErrorMessage2(error, "Failed to pull synced skills") });
1034
1695
  }
1035
1696
  return true;
1036
1697
  }
@@ -1039,26 +1700,26 @@ async function handleSkillsRoutes(req, res, url, context) {
1039
1700
  const owner = url.searchParams.get("owner") || "";
1040
1701
  const name = url.searchParams.get("name") || "";
1041
1702
  if (!owner || !name) {
1042
- setJson(res, 400, { error: "Missing owner or name" });
1703
+ setJson2(res, 400, { error: "Missing owner or name" });
1043
1704
  return true;
1044
1705
  }
1045
1706
  const rawUrl = `https://raw.githubusercontent.com/${HUB_SKILLS_OWNER}/${HUB_SKILLS_REPO}/main/skills/${owner}/${name}/SKILL.md`;
1046
1707
  const resp = await fetch(rawUrl);
1047
1708
  if (!resp.ok) throw new Error(`Failed to fetch SKILL.md: ${resp.status}`);
1048
1709
  const content = await resp.text();
1049
- setJson(res, 200, { content });
1710
+ setJson2(res, 200, { content });
1050
1711
  } catch (error) {
1051
- setJson(res, 502, { error: getErrorMessage(error, "Failed to fetch SKILL.md") });
1712
+ setJson2(res, 502, { error: getErrorMessage2(error, "Failed to fetch SKILL.md") });
1052
1713
  }
1053
1714
  return true;
1054
1715
  }
1055
1716
  if (req.method === "POST" && url.pathname === "/codex-api/skills-hub/install") {
1056
1717
  try {
1057
- const payload = asRecord(await readJsonBody2(req));
1718
+ const payload = asRecord2(await readJsonBody2(req));
1058
1719
  const owner = typeof payload?.owner === "string" ? payload.owner : "";
1059
1720
  const name = typeof payload?.name === "string" ? payload.name : "";
1060
1721
  if (!owner || !name) {
1061
- setJson(res, 400, { error: "Missing owner or name" });
1722
+ setJson2(res, 400, { error: "Missing owner or name" });
1062
1723
  return true;
1063
1724
  }
1064
1725
  const installerScript = "/Users/igor/.cursor/skills/.system/skill-installer/scripts/install-skill-from-github.py";
@@ -1074,29 +1735,29 @@ async function handleSkillsRoutes(req, res, url, context) {
1074
1735
  "--method",
1075
1736
  "git"
1076
1737
  ]);
1077
- const skillDir = join(installDest, name);
1738
+ const skillDir = join2(installDest, name);
1078
1739
  await ensureInstalledSkillIsValid(appServer, skillDir);
1079
1740
  const syncState = await readSkillsSyncState();
1080
1741
  const nextOwners = { ...syncState.installedOwners ?? {}, [name]: owner };
1081
1742
  await writeSkillsSyncState({ ...syncState, installedOwners: nextOwners });
1082
1743
  await autoPushSyncedSkills(appServer);
1083
- setJson(res, 200, { ok: true, path: skillDir });
1744
+ setJson2(res, 200, { ok: true, path: skillDir });
1084
1745
  } catch (error) {
1085
- setJson(res, 502, { error: getErrorMessage(error, "Failed to install skill") });
1746
+ setJson2(res, 502, { error: getErrorMessage2(error, "Failed to install skill") });
1086
1747
  }
1087
1748
  return true;
1088
1749
  }
1089
1750
  if (req.method === "POST" && url.pathname === "/codex-api/skills-hub/uninstall") {
1090
1751
  try {
1091
- const payload = asRecord(await readJsonBody2(req));
1752
+ const payload = asRecord2(await readJsonBody2(req));
1092
1753
  const name = typeof payload?.name === "string" ? payload.name : "";
1093
1754
  const path = typeof payload?.path === "string" ? payload.path : "";
1094
- const target = path || (name ? join(getSkillsInstallDir(), name) : "");
1755
+ const target = path || (name ? join2(getSkillsInstallDir(), name) : "");
1095
1756
  if (!target) {
1096
- setJson(res, 400, { error: "Missing name or path" });
1757
+ setJson2(res, 400, { error: "Missing name or path" });
1097
1758
  return true;
1098
1759
  }
1099
- await rm(target, { recursive: true, force: true });
1760
+ await rm2(target, { recursive: true, force: true });
1100
1761
  if (name) {
1101
1762
  const syncState = await readSkillsSyncState();
1102
1763
  const nextOwners = { ...syncState.installedOwners ?? {} };
@@ -1108,9 +1769,9 @@ async function handleSkillsRoutes(req, res, url, context) {
1108
1769
  await appServer.rpc("skills/list", { forceReload: true });
1109
1770
  } catch {
1110
1771
  }
1111
- setJson(res, 200, { ok: true, deletedPath: target });
1772
+ setJson2(res, 200, { ok: true, deletedPath: target });
1112
1773
  } catch (error) {
1113
- setJson(res, 502, { error: getErrorMessage(error, "Failed to uninstall skill") });
1774
+ setJson2(res, 502, { error: getErrorMessage2(error, "Failed to uninstall skill") });
1114
1775
  }
1115
1776
  return true;
1116
1777
  }
@@ -1118,38 +1779,38 @@ async function handleSkillsRoutes(req, res, url, context) {
1118
1779
  }
1119
1780
 
1120
1781
  // src/server/codexAppServerBridge.ts
1121
- function asRecord2(value) {
1782
+ function asRecord3(value) {
1122
1783
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
1123
1784
  }
1124
- function getErrorMessage2(payload, fallback) {
1785
+ function getErrorMessage3(payload, fallback) {
1125
1786
  if (payload instanceof Error && payload.message.trim().length > 0) {
1126
1787
  return payload.message;
1127
1788
  }
1128
- const record = asRecord2(payload);
1789
+ const record = asRecord3(payload);
1129
1790
  if (!record) return fallback;
1130
1791
  const error = record.error;
1131
1792
  if (typeof error === "string" && error.length > 0) return error;
1132
- const nestedError = asRecord2(error);
1793
+ const nestedError = asRecord3(error);
1133
1794
  if (nestedError && typeof nestedError.message === "string" && nestedError.message.length > 0) {
1134
1795
  return nestedError.message;
1135
1796
  }
1136
1797
  return fallback;
1137
1798
  }
1138
- function setJson2(res, statusCode, payload) {
1799
+ function setJson3(res, statusCode, payload) {
1139
1800
  res.statusCode = statusCode;
1140
1801
  res.setHeader("Content-Type", "application/json; charset=utf-8");
1141
1802
  res.end(JSON.stringify(payload));
1142
1803
  }
1143
1804
  function extractThreadMessageText(threadReadPayload) {
1144
- const payload = asRecord2(threadReadPayload);
1145
- const thread = asRecord2(payload?.thread);
1805
+ const payload = asRecord3(threadReadPayload);
1806
+ const thread = asRecord3(payload?.thread);
1146
1807
  const turns = Array.isArray(thread?.turns) ? thread.turns : [];
1147
1808
  const parts = [];
1148
1809
  for (const turn of turns) {
1149
- const turnRecord = asRecord2(turn);
1810
+ const turnRecord = asRecord3(turn);
1150
1811
  const items = Array.isArray(turnRecord?.items) ? turnRecord.items : [];
1151
1812
  for (const item of items) {
1152
- const itemRecord = asRecord2(item);
1813
+ const itemRecord = asRecord3(item);
1153
1814
  const type = typeof itemRecord?.type === "string" ? itemRecord.type : "";
1154
1815
  if (type === "agentMessage" && typeof itemRecord?.text === "string" && itemRecord.text.trim().length > 0) {
1155
1816
  parts.push(itemRecord.text.trim());
@@ -1158,7 +1819,7 @@ function extractThreadMessageText(threadReadPayload) {
1158
1819
  if (type === "userMessage") {
1159
1820
  const content = Array.isArray(itemRecord?.content) ? itemRecord.content : [];
1160
1821
  for (const block of content) {
1161
- const blockRecord = asRecord2(block);
1822
+ const blockRecord = asRecord3(block);
1162
1823
  if (blockRecord?.type === "text" && typeof blockRecord.text === "string" && blockRecord.text.trim().length > 0) {
1163
1824
  parts.push(blockRecord.text.trim());
1164
1825
  }
@@ -1194,7 +1855,7 @@ function scoreFileCandidate(path, query) {
1194
1855
  }
1195
1856
  async function listFilesWithRipgrep(cwd) {
1196
1857
  return await new Promise((resolve2, reject) => {
1197
- const proc = spawn2("rg", ["--files", "--hidden", "-g", "!.git", "-g", "!node_modules"], {
1858
+ const proc = spawn3("rg", ["--files", "--hidden", "-g", "!.git", "-g", "!node_modules"], {
1198
1859
  cwd,
1199
1860
  env: process.env,
1200
1861
  stdio: ["ignore", "pipe", "pipe"]
@@ -1219,13 +1880,13 @@ async function listFilesWithRipgrep(cwd) {
1219
1880
  });
1220
1881
  });
1221
1882
  }
1222
- function getCodexHomeDir2() {
1883
+ function getCodexHomeDir3() {
1223
1884
  const codexHome = process.env.CODEX_HOME?.trim();
1224
- return codexHome && codexHome.length > 0 ? codexHome : join2(homedir2(), ".codex");
1885
+ return codexHome && codexHome.length > 0 ? codexHome : join3(homedir3(), ".codex");
1225
1886
  }
1226
1887
  async function runCommand2(command, args, options = {}) {
1227
1888
  await new Promise((resolve2, reject) => {
1228
- const proc = spawn2(command, args, {
1889
+ const proc = spawn3(command, args, {
1229
1890
  cwd: options.cwd,
1230
1891
  env: process.env,
1231
1892
  stdio: ["ignore", "pipe", "pipe"]
@@ -1251,19 +1912,19 @@ async function runCommand2(command, args, options = {}) {
1251
1912
  });
1252
1913
  }
1253
1914
  function isMissingHeadError(error) {
1254
- const message = getErrorMessage2(error, "").toLowerCase();
1915
+ const message = getErrorMessage3(error, "").toLowerCase();
1255
1916
  return message.includes("not a valid object name: 'head'") || message.includes("not a valid object name: head") || message.includes("invalid reference: head");
1256
1917
  }
1257
1918
  function isNotGitRepositoryError(error) {
1258
- const message = getErrorMessage2(error, "").toLowerCase();
1919
+ const message = getErrorMessage3(error, "").toLowerCase();
1259
1920
  return message.includes("not a git repository") || message.includes("fatal: not a git repository");
1260
1921
  }
1261
1922
  async function ensureRepoHasInitialCommit(repoRoot) {
1262
- const agentsPath = join2(repoRoot, "AGENTS.md");
1923
+ const agentsPath = join3(repoRoot, "AGENTS.md");
1263
1924
  try {
1264
- await stat2(agentsPath);
1925
+ await stat3(agentsPath);
1265
1926
  } catch {
1266
- await writeFile2(agentsPath, "", "utf8");
1927
+ await writeFile3(agentsPath, "", "utf8");
1267
1928
  }
1268
1929
  await runCommand2("git", ["add", "AGENTS.md"], { cwd: repoRoot });
1269
1930
  await runCommand2(
@@ -1274,7 +1935,7 @@ async function ensureRepoHasInitialCommit(repoRoot) {
1274
1935
  }
1275
1936
  async function runCommandCapture(command, args, options = {}) {
1276
1937
  return await new Promise((resolve2, reject) => {
1277
- const proc = spawn2(command, args, {
1938
+ const proc = spawn3(command, args, {
1278
1939
  cwd: options.cwd,
1279
1940
  env: process.env,
1280
1941
  stdio: ["ignore", "pipe", "pipe"]
@@ -1320,11 +1981,11 @@ function normalizeStringRecord(value) {
1320
1981
  return next;
1321
1982
  }
1322
1983
  function getCodexAuthPath() {
1323
- return join2(getCodexHomeDir2(), "auth.json");
1984
+ return join3(getCodexHomeDir3(), "auth.json");
1324
1985
  }
1325
1986
  async function readCodexAuth() {
1326
1987
  try {
1327
- const raw = await readFile2(getCodexAuthPath(), "utf8");
1988
+ const raw = await readFile3(getCodexAuthPath(), "utf8");
1328
1989
  const auth = JSON.parse(raw);
1329
1990
  const token = auth.tokens?.access_token;
1330
1991
  if (!token) return null;
@@ -1334,13 +1995,13 @@ async function readCodexAuth() {
1334
1995
  }
1335
1996
  }
1336
1997
  function getCodexGlobalStatePath() {
1337
- return join2(getCodexHomeDir2(), ".codex-global-state.json");
1998
+ return join3(getCodexHomeDir3(), ".codex-global-state.json");
1338
1999
  }
1339
2000
  var MAX_THREAD_TITLES = 500;
1340
2001
  function normalizeThreadTitleCache(value) {
1341
- const record = asRecord2(value);
2002
+ const record = asRecord3(value);
1342
2003
  if (!record) return { titles: {}, order: [] };
1343
- const rawTitles = asRecord2(record.titles);
2004
+ const rawTitles = asRecord3(record.titles);
1344
2005
  const titles = {};
1345
2006
  if (rawTitles) {
1346
2007
  for (const [k, v] of Object.entries(rawTitles)) {
@@ -1366,8 +2027,8 @@ function removeFromThreadTitleCache(cache, id) {
1366
2027
  async function readThreadTitleCache() {
1367
2028
  const statePath = getCodexGlobalStatePath();
1368
2029
  try {
1369
- const raw = await readFile2(statePath, "utf8");
1370
- const payload = asRecord2(JSON.parse(raw)) ?? {};
2030
+ const raw = await readFile3(statePath, "utf8");
2031
+ const payload = asRecord3(JSON.parse(raw)) ?? {};
1371
2032
  return normalizeThreadTitleCache(payload["thread-titles"]);
1372
2033
  } catch {
1373
2034
  return { titles: {}, order: [] };
@@ -1377,21 +2038,21 @@ async function writeThreadTitleCache(cache) {
1377
2038
  const statePath = getCodexGlobalStatePath();
1378
2039
  let payload = {};
1379
2040
  try {
1380
- const raw = await readFile2(statePath, "utf8");
1381
- payload = asRecord2(JSON.parse(raw)) ?? {};
2041
+ const raw = await readFile3(statePath, "utf8");
2042
+ payload = asRecord3(JSON.parse(raw)) ?? {};
1382
2043
  } catch {
1383
2044
  payload = {};
1384
2045
  }
1385
2046
  payload["thread-titles"] = cache;
1386
- await writeFile2(statePath, JSON.stringify(payload), "utf8");
2047
+ await writeFile3(statePath, JSON.stringify(payload), "utf8");
1387
2048
  }
1388
2049
  async function readWorkspaceRootsState() {
1389
2050
  const statePath = getCodexGlobalStatePath();
1390
2051
  let payload = {};
1391
2052
  try {
1392
- const raw = await readFile2(statePath, "utf8");
2053
+ const raw = await readFile3(statePath, "utf8");
1393
2054
  const parsed = JSON.parse(raw);
1394
- payload = asRecord2(parsed) ?? {};
2055
+ payload = asRecord3(parsed) ?? {};
1395
2056
  } catch {
1396
2057
  payload = {};
1397
2058
  }
@@ -1405,15 +2066,15 @@ async function writeWorkspaceRootsState(nextState) {
1405
2066
  const statePath = getCodexGlobalStatePath();
1406
2067
  let payload = {};
1407
2068
  try {
1408
- const raw = await readFile2(statePath, "utf8");
1409
- payload = asRecord2(JSON.parse(raw)) ?? {};
2069
+ const raw = await readFile3(statePath, "utf8");
2070
+ payload = asRecord3(JSON.parse(raw)) ?? {};
1410
2071
  } catch {
1411
2072
  payload = {};
1412
2073
  }
1413
2074
  payload["electron-saved-workspace-roots"] = normalizeStringArray(nextState.order);
1414
2075
  payload["electron-workspace-root-labels"] = normalizeStringRecord(nextState.labels);
1415
2076
  payload["active-workspace-roots"] = normalizeStringArray(nextState.active);
1416
- await writeFile2(statePath, JSON.stringify(payload), "utf8");
2077
+ await writeFile3(statePath, JSON.stringify(payload), "utf8");
1417
2078
  }
1418
2079
  async function readJsonBody(req) {
1419
2080
  const raw = await readRawBody(req);
@@ -1451,7 +2112,7 @@ function handleFileUpload(req, res) {
1451
2112
  const contentType = req.headers["content-type"] ?? "";
1452
2113
  const boundaryMatch = contentType.match(/boundary=(.+)/i);
1453
2114
  if (!boundaryMatch) {
1454
- setJson2(res, 400, { error: "Missing multipart boundary" });
2115
+ setJson3(res, 400, { error: "Missing multipart boundary" });
1455
2116
  return;
1456
2117
  }
1457
2118
  const boundary = boundaryMatch[1];
@@ -1481,21 +2142,21 @@ function handleFileUpload(req, res) {
1481
2142
  break;
1482
2143
  }
1483
2144
  if (!fileData) {
1484
- setJson2(res, 400, { error: "No file in request" });
2145
+ setJson3(res, 400, { error: "No file in request" });
1485
2146
  return;
1486
2147
  }
1487
- const uploadDir = join2(tmpdir2(), "codex-web-uploads");
1488
- await mkdir2(uploadDir, { recursive: true });
1489
- const destDir = await mkdtemp2(join2(uploadDir, "f-"));
1490
- const destPath = join2(destDir, fileName);
1491
- await writeFile2(destPath, fileData);
1492
- setJson2(res, 200, { path: destPath });
2148
+ const uploadDir = join3(tmpdir3(), "codex-web-uploads");
2149
+ await mkdir3(uploadDir, { recursive: true });
2150
+ const destDir = await mkdtemp3(join3(uploadDir, "f-"));
2151
+ const destPath = join3(destDir, fileName);
2152
+ await writeFile3(destPath, fileData);
2153
+ setJson3(res, 200, { path: destPath });
1493
2154
  } catch (err) {
1494
- setJson2(res, 500, { error: getErrorMessage2(err, "Upload failed") });
2155
+ setJson3(res, 500, { error: getErrorMessage3(err, "Upload failed") });
1495
2156
  }
1496
2157
  });
1497
2158
  req.on("error", (err) => {
1498
- setJson2(res, 500, { error: getErrorMessage2(err, "Upload stream error") });
2159
+ setJson3(res, 500, { error: getErrorMessage3(err, "Upload stream error") });
1499
2160
  });
1500
2161
  }
1501
2162
  async function proxyTranscribe(body, contentType, authToken, accountId) {
@@ -1547,7 +2208,7 @@ var AppServerProcess = class {
1547
2208
  start() {
1548
2209
  if (this.process) return;
1549
2210
  this.stopping = false;
1550
- const proc = spawn2("codex", this.appServerArgs, { stdio: ["pipe", "pipe", "pipe"] });
2211
+ const proc = spawn3("codex", this.appServerArgs, { stdio: ["pipe", "pipe", "pipe"] });
1551
2212
  this.process = proc;
1552
2213
  proc.stdout.setEncoding("utf8");
1553
2214
  proc.stdout.on("data", (chunk) => {
@@ -1566,6 +2227,9 @@ var AppServerProcess = class {
1566
2227
  proc.stderr.on("data", () => {
1567
2228
  });
1568
2229
  proc.on("exit", () => {
2230
+ if (this.process !== proc) {
2231
+ return;
2232
+ }
1569
2233
  const failure = new Error(this.stopping ? "codex app-server stopped" : "codex app-server exited unexpectedly");
1570
2234
  for (const request of this.pending.values()) {
1571
2235
  request.reject(failure);
@@ -1641,7 +2305,7 @@ var AppServerProcess = class {
1641
2305
  }
1642
2306
  this.pendingServerRequests.delete(requestId);
1643
2307
  this.sendServerRequestReply(requestId, reply);
1644
- const requestParams = asRecord2(pendingRequest.params);
2308
+ const requestParams = asRecord3(pendingRequest.params);
1645
2309
  const threadId = typeof requestParams?.threadId === "string" && requestParams.threadId.length > 0 ? requestParams.threadId : "";
1646
2310
  this.emitNotification({
1647
2311
  method: "server/request/resolved",
@@ -1717,7 +2381,7 @@ var AppServerProcess = class {
1717
2381
  }
1718
2382
  async respondToServerRequest(payload) {
1719
2383
  await this.ensureInitialized();
1720
- const body = asRecord2(payload);
2384
+ const body = asRecord3(payload);
1721
2385
  if (!body) {
1722
2386
  throw new Error("Invalid response payload: expected object");
1723
2387
  }
@@ -1725,7 +2389,7 @@ var AppServerProcess = class {
1725
2389
  if (typeof id !== "number" || !Number.isInteger(id)) {
1726
2390
  throw new Error('Invalid response payload: "id" must be an integer');
1727
2391
  }
1728
- const rawError = asRecord2(body.error);
2392
+ const rawError = asRecord3(body.error);
1729
2393
  if (rawError) {
1730
2394
  const message = typeof rawError.message === "string" && rawError.message.trim().length > 0 ? rawError.message.trim() : "Server request rejected by client";
1731
2395
  const code = typeof rawError.code === "number" && Number.isFinite(rawError.code) ? Math.trunc(rawError.code) : -32e3;
@@ -1780,7 +2444,7 @@ var MethodCatalog = class {
1780
2444
  }
1781
2445
  async runGenerateSchemaCommand(outDir) {
1782
2446
  await new Promise((resolve2, reject) => {
1783
- const process2 = spawn2("codex", ["app-server", "generate-json-schema", "--out", outDir], {
2447
+ const process2 = spawn3("codex", ["app-server", "generate-json-schema", "--out", outDir], {
1784
2448
  stdio: ["ignore", "ignore", "pipe"]
1785
2449
  });
1786
2450
  let stderr = "";
@@ -1799,13 +2463,13 @@ var MethodCatalog = class {
1799
2463
  });
1800
2464
  }
1801
2465
  extractMethodsFromClientRequest(payload) {
1802
- const root = asRecord2(payload);
2466
+ const root = asRecord3(payload);
1803
2467
  const oneOf = Array.isArray(root?.oneOf) ? root.oneOf : [];
1804
2468
  const methods = /* @__PURE__ */ new Set();
1805
2469
  for (const entry of oneOf) {
1806
- const row = asRecord2(entry);
1807
- const properties = asRecord2(row?.properties);
1808
- const methodDef = asRecord2(properties?.method);
2470
+ const row = asRecord3(entry);
2471
+ const properties = asRecord3(row?.properties);
2472
+ const methodDef = asRecord3(properties?.method);
1809
2473
  const methodEnum = Array.isArray(methodDef?.enum) ? methodDef.enum : [];
1810
2474
  for (const item of methodEnum) {
1811
2475
  if (typeof item === "string" && item.length > 0) {
@@ -1816,13 +2480,13 @@ var MethodCatalog = class {
1816
2480
  return Array.from(methods).sort((a, b) => a.localeCompare(b));
1817
2481
  }
1818
2482
  extractMethodsFromServerNotification(payload) {
1819
- const root = asRecord2(payload);
2483
+ const root = asRecord3(payload);
1820
2484
  const oneOf = Array.isArray(root?.oneOf) ? root.oneOf : [];
1821
2485
  const methods = /* @__PURE__ */ new Set();
1822
2486
  for (const entry of oneOf) {
1823
- const row = asRecord2(entry);
1824
- const properties = asRecord2(row?.properties);
1825
- const methodDef = asRecord2(properties?.method);
2487
+ const row = asRecord3(entry);
2488
+ const properties = asRecord3(row?.properties);
2489
+ const methodDef = asRecord3(properties?.method);
1826
2490
  const methodEnum = Array.isArray(methodDef?.enum) ? methodDef.enum : [];
1827
2491
  for (const item of methodEnum) {
1828
2492
  if (typeof item === "string" && item.length > 0) {
@@ -1836,10 +2500,10 @@ var MethodCatalog = class {
1836
2500
  if (this.methodCache) {
1837
2501
  return this.methodCache;
1838
2502
  }
1839
- const outDir = await mkdtemp2(join2(tmpdir2(), "codex-web-local-schema-"));
2503
+ const outDir = await mkdtemp3(join3(tmpdir3(), "codex-web-local-schema-"));
1840
2504
  await this.runGenerateSchemaCommand(outDir);
1841
- const clientRequestPath = join2(outDir, "ClientRequest.json");
1842
- const raw = await readFile2(clientRequestPath, "utf8");
2505
+ const clientRequestPath = join3(outDir, "ClientRequest.json");
2506
+ const raw = await readFile3(clientRequestPath, "utf8");
1843
2507
  const parsed = JSON.parse(raw);
1844
2508
  const methods = this.extractMethodsFromClientRequest(parsed);
1845
2509
  this.methodCache = methods;
@@ -1849,10 +2513,10 @@ var MethodCatalog = class {
1849
2513
  if (this.notificationCache) {
1850
2514
  return this.notificationCache;
1851
2515
  }
1852
- const outDir = await mkdtemp2(join2(tmpdir2(), "codex-web-local-schema-"));
2516
+ const outDir = await mkdtemp3(join3(tmpdir3(), "codex-web-local-schema-"));
1853
2517
  await this.runGenerateSchemaCommand(outDir);
1854
- const serverNotificationPath = join2(outDir, "ServerNotification.json");
1855
- const raw = await readFile2(serverNotificationPath, "utf8");
2518
+ const serverNotificationPath = join3(outDir, "ServerNotification.json");
2519
+ const raw = await readFile3(serverNotificationPath, "utf8");
1856
2520
  const parsed = JSON.parse(raw);
1857
2521
  const methods = this.extractMethodsFromServerNotification(parsed);
1858
2522
  this.notificationCache = methods;
@@ -1882,7 +2546,7 @@ async function loadAllThreadsForSearch(appServer) {
1882
2546
  const threads = [];
1883
2547
  let cursor = null;
1884
2548
  do {
1885
- const response = asRecord2(await appServer.rpc("thread/list", {
2549
+ const response = asRecord3(await appServer.rpc("thread/list", {
1886
2550
  archived: false,
1887
2551
  limit: 100,
1888
2552
  sortKey: "updated_at",
@@ -1890,7 +2554,7 @@ async function loadAllThreadsForSearch(appServer) {
1890
2554
  }));
1891
2555
  const data = Array.isArray(response?.data) ? response.data : [];
1892
2556
  for (const row of data) {
1893
- const record = asRecord2(row);
2557
+ const record = asRecord3(row);
1894
2558
  const id = typeof record?.id === "string" ? record.id : "";
1895
2559
  if (!id) continue;
1896
2560
  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";
@@ -1962,6 +2626,9 @@ function createCodexBridgeMiddleware() {
1962
2626
  return;
1963
2627
  }
1964
2628
  const url = new URL(req.url, "http://localhost");
2629
+ if (await handleAccountRoutes(req, res, url, { appServer })) {
2630
+ return;
2631
+ }
1965
2632
  if (await handleSkillsRoutes(req, res, url, { appServer, readJsonBody })) {
1966
2633
  return;
1967
2634
  }
@@ -1971,19 +2638,19 @@ function createCodexBridgeMiddleware() {
1971
2638
  }
1972
2639
  if (req.method === "POST" && url.pathname === "/codex-api/rpc") {
1973
2640
  const payload = await readJsonBody(req);
1974
- const body = asRecord2(payload);
2641
+ const body = asRecord3(payload);
1975
2642
  if (!body || typeof body.method !== "string" || body.method.length === 0) {
1976
- setJson2(res, 400, { error: "Invalid body: expected { method, params? }" });
2643
+ setJson3(res, 400, { error: "Invalid body: expected { method, params? }" });
1977
2644
  return;
1978
2645
  }
1979
2646
  const result = await appServer.rpc(body.method, body.params ?? null);
1980
- setJson2(res, 200, { result });
2647
+ setJson3(res, 200, { result });
1981
2648
  return;
1982
2649
  }
1983
2650
  if (req.method === "POST" && url.pathname === "/codex-api/transcribe") {
1984
2651
  const auth = await readCodexAuth();
1985
2652
  if (!auth) {
1986
- setJson2(res, 401, { error: "No auth token available for transcription" });
2653
+ setJson3(res, 401, { error: "No auth token available for transcription" });
1987
2654
  return;
1988
2655
  }
1989
2656
  const rawBody = await readRawBody(req);
@@ -1997,48 +2664,48 @@ function createCodexBridgeMiddleware() {
1997
2664
  if (req.method === "POST" && url.pathname === "/codex-api/server-requests/respond") {
1998
2665
  const payload = await readJsonBody(req);
1999
2666
  await appServer.respondToServerRequest(payload);
2000
- setJson2(res, 200, { ok: true });
2667
+ setJson3(res, 200, { ok: true });
2001
2668
  return;
2002
2669
  }
2003
2670
  if (req.method === "GET" && url.pathname === "/codex-api/server-requests/pending") {
2004
- setJson2(res, 200, { data: appServer.listPendingServerRequests() });
2671
+ setJson3(res, 200, { data: appServer.listPendingServerRequests() });
2005
2672
  return;
2006
2673
  }
2007
2674
  if (req.method === "GET" && url.pathname === "/codex-api/meta/methods") {
2008
2675
  const methods = await methodCatalog.listMethods();
2009
- setJson2(res, 200, { data: methods });
2676
+ setJson3(res, 200, { data: methods });
2010
2677
  return;
2011
2678
  }
2012
2679
  if (req.method === "GET" && url.pathname === "/codex-api/meta/notifications") {
2013
2680
  const methods = await methodCatalog.listNotificationMethods();
2014
- setJson2(res, 200, { data: methods });
2681
+ setJson3(res, 200, { data: methods });
2015
2682
  return;
2016
2683
  }
2017
2684
  if (req.method === "GET" && url.pathname === "/codex-api/workspace-roots-state") {
2018
2685
  const state = await readWorkspaceRootsState();
2019
- setJson2(res, 200, { data: state });
2686
+ setJson3(res, 200, { data: state });
2020
2687
  return;
2021
2688
  }
2022
2689
  if (req.method === "GET" && url.pathname === "/codex-api/home-directory") {
2023
- setJson2(res, 200, { data: { path: homedir2() } });
2690
+ setJson3(res, 200, { data: { path: homedir3() } });
2024
2691
  return;
2025
2692
  }
2026
2693
  if (req.method === "POST" && url.pathname === "/codex-api/worktree/create") {
2027
- const payload = asRecord2(await readJsonBody(req));
2694
+ const payload = asRecord3(await readJsonBody(req));
2028
2695
  const rawSourceCwd = typeof payload?.sourceCwd === "string" ? payload.sourceCwd.trim() : "";
2029
2696
  if (!rawSourceCwd) {
2030
- setJson2(res, 400, { error: "Missing sourceCwd" });
2697
+ setJson3(res, 400, { error: "Missing sourceCwd" });
2031
2698
  return;
2032
2699
  }
2033
2700
  const sourceCwd = isAbsolute(rawSourceCwd) ? rawSourceCwd : resolve(rawSourceCwd);
2034
2701
  try {
2035
- const sourceInfo = await stat2(sourceCwd);
2702
+ const sourceInfo = await stat3(sourceCwd);
2036
2703
  if (!sourceInfo.isDirectory()) {
2037
- setJson2(res, 400, { error: "sourceCwd is not a directory" });
2704
+ setJson3(res, 400, { error: "sourceCwd is not a directory" });
2038
2705
  return;
2039
2706
  }
2040
2707
  } catch {
2041
- setJson2(res, 404, { error: "sourceCwd does not exist" });
2708
+ setJson3(res, 404, { error: "sourceCwd does not exist" });
2042
2709
  return;
2043
2710
  }
2044
2711
  try {
@@ -2051,21 +2718,21 @@ function createCodexBridgeMiddleware() {
2051
2718
  gitRoot = await runCommandCapture("git", ["rev-parse", "--show-toplevel"], { cwd: sourceCwd });
2052
2719
  }
2053
2720
  const repoName = basename(gitRoot) || "repo";
2054
- const worktreesRoot = join2(getCodexHomeDir2(), "worktrees");
2055
- await mkdir2(worktreesRoot, { recursive: true });
2721
+ const worktreesRoot = join3(getCodexHomeDir3(), "worktrees");
2722
+ await mkdir3(worktreesRoot, { recursive: true });
2056
2723
  let worktreeId = "";
2057
2724
  let worktreeParent = "";
2058
2725
  let worktreeCwd = "";
2059
2726
  for (let attempt = 0; attempt < 12; attempt += 1) {
2060
2727
  const candidate = randomBytes(2).toString("hex");
2061
- const parent = join2(worktreesRoot, candidate);
2728
+ const parent = join3(worktreesRoot, candidate);
2062
2729
  try {
2063
- await stat2(parent);
2730
+ await stat3(parent);
2064
2731
  continue;
2065
2732
  } catch {
2066
2733
  worktreeId = candidate;
2067
2734
  worktreeParent = parent;
2068
- worktreeCwd = join2(parent, repoName);
2735
+ worktreeCwd = join3(parent, repoName);
2069
2736
  break;
2070
2737
  }
2071
2738
  }
@@ -2073,7 +2740,7 @@ function createCodexBridgeMiddleware() {
2073
2740
  throw new Error("Failed to allocate a unique worktree id");
2074
2741
  }
2075
2742
  const branch = `codex/${worktreeId}`;
2076
- await mkdir2(worktreeParent, { recursive: true });
2743
+ await mkdir3(worktreeParent, { recursive: true });
2077
2744
  try {
2078
2745
  await runCommand2("git", ["worktree", "add", "-b", branch, worktreeCwd, "HEAD"], { cwd: gitRoot });
2079
2746
  } catch (error) {
@@ -2081,7 +2748,7 @@ function createCodexBridgeMiddleware() {
2081
2748
  await ensureRepoHasInitialCommit(gitRoot);
2082
2749
  await runCommand2("git", ["worktree", "add", "-b", branch, worktreeCwd, "HEAD"], { cwd: gitRoot });
2083
2750
  }
2084
- setJson2(res, 200, {
2751
+ setJson3(res, 200, {
2085
2752
  data: {
2086
2753
  cwd: worktreeCwd,
2087
2754
  branch,
@@ -2089,15 +2756,15 @@ function createCodexBridgeMiddleware() {
2089
2756
  }
2090
2757
  });
2091
2758
  } catch (error) {
2092
- setJson2(res, 500, { error: getErrorMessage2(error, "Failed to create worktree") });
2759
+ setJson3(res, 500, { error: getErrorMessage3(error, "Failed to create worktree") });
2093
2760
  }
2094
2761
  return;
2095
2762
  }
2096
2763
  if (req.method === "PUT" && url.pathname === "/codex-api/workspace-roots-state") {
2097
2764
  const payload = await readJsonBody(req);
2098
- const record = asRecord2(payload);
2765
+ const record = asRecord3(payload);
2099
2766
  if (!record) {
2100
- setJson2(res, 400, { error: "Invalid body: expected object" });
2767
+ setJson3(res, 400, { error: "Invalid body: expected object" });
2101
2768
  return;
2102
2769
  }
2103
2770
  const nextState = {
@@ -2106,33 +2773,33 @@ function createCodexBridgeMiddleware() {
2106
2773
  active: normalizeStringArray(record.active)
2107
2774
  };
2108
2775
  await writeWorkspaceRootsState(nextState);
2109
- setJson2(res, 200, { ok: true });
2776
+ setJson3(res, 200, { ok: true });
2110
2777
  return;
2111
2778
  }
2112
2779
  if (req.method === "POST" && url.pathname === "/codex-api/project-root") {
2113
- const payload = asRecord2(await readJsonBody(req));
2780
+ const payload = asRecord3(await readJsonBody(req));
2114
2781
  const rawPath = typeof payload?.path === "string" ? payload.path.trim() : "";
2115
2782
  const createIfMissing = payload?.createIfMissing === true;
2116
2783
  const label = typeof payload?.label === "string" ? payload.label : "";
2117
2784
  if (!rawPath) {
2118
- setJson2(res, 400, { error: "Missing path" });
2785
+ setJson3(res, 400, { error: "Missing path" });
2119
2786
  return;
2120
2787
  }
2121
2788
  const normalizedPath = isAbsolute(rawPath) ? rawPath : resolve(rawPath);
2122
2789
  let pathExists = true;
2123
2790
  try {
2124
- const info = await stat2(normalizedPath);
2791
+ const info = await stat3(normalizedPath);
2125
2792
  if (!info.isDirectory()) {
2126
- setJson2(res, 400, { error: "Path exists but is not a directory" });
2793
+ setJson3(res, 400, { error: "Path exists but is not a directory" });
2127
2794
  return;
2128
2795
  }
2129
2796
  } catch {
2130
2797
  pathExists = false;
2131
2798
  }
2132
2799
  if (!pathExists && createIfMissing) {
2133
- await mkdir2(normalizedPath, { recursive: true });
2800
+ await mkdir3(normalizedPath, { recursive: true });
2134
2801
  } else if (!pathExists) {
2135
- setJson2(res, 404, { error: "Directory does not exist" });
2802
+ setJson3(res, 404, { error: "Directory does not exist" });
2136
2803
  return;
2137
2804
  }
2138
2805
  const existingState = await readWorkspaceRootsState();
@@ -2147,103 +2814,103 @@ function createCodexBridgeMiddleware() {
2147
2814
  labels: nextLabels,
2148
2815
  active: nextActive
2149
2816
  });
2150
- setJson2(res, 200, { data: { path: normalizedPath } });
2817
+ setJson3(res, 200, { data: { path: normalizedPath } });
2151
2818
  return;
2152
2819
  }
2153
2820
  if (req.method === "GET" && url.pathname === "/codex-api/project-root-suggestion") {
2154
2821
  const basePath = url.searchParams.get("basePath")?.trim() ?? "";
2155
2822
  if (!basePath) {
2156
- setJson2(res, 400, { error: "Missing basePath" });
2823
+ setJson3(res, 400, { error: "Missing basePath" });
2157
2824
  return;
2158
2825
  }
2159
2826
  const normalizedBasePath = isAbsolute(basePath) ? basePath : resolve(basePath);
2160
2827
  try {
2161
- const baseInfo = await stat2(normalizedBasePath);
2828
+ const baseInfo = await stat3(normalizedBasePath);
2162
2829
  if (!baseInfo.isDirectory()) {
2163
- setJson2(res, 400, { error: "basePath is not a directory" });
2830
+ setJson3(res, 400, { error: "basePath is not a directory" });
2164
2831
  return;
2165
2832
  }
2166
2833
  } catch {
2167
- setJson2(res, 404, { error: "basePath does not exist" });
2834
+ setJson3(res, 404, { error: "basePath does not exist" });
2168
2835
  return;
2169
2836
  }
2170
2837
  let index = 1;
2171
2838
  while (index < 1e5) {
2172
2839
  const candidateName = `New Project (${String(index)})`;
2173
- const candidatePath = join2(normalizedBasePath, candidateName);
2840
+ const candidatePath = join3(normalizedBasePath, candidateName);
2174
2841
  try {
2175
- await stat2(candidatePath);
2842
+ await stat3(candidatePath);
2176
2843
  index += 1;
2177
2844
  continue;
2178
2845
  } catch {
2179
- setJson2(res, 200, { data: { name: candidateName, path: candidatePath } });
2846
+ setJson3(res, 200, { data: { name: candidateName, path: candidatePath } });
2180
2847
  return;
2181
2848
  }
2182
2849
  }
2183
- setJson2(res, 500, { error: "Failed to compute project name suggestion" });
2850
+ setJson3(res, 500, { error: "Failed to compute project name suggestion" });
2184
2851
  return;
2185
2852
  }
2186
2853
  if (req.method === "POST" && url.pathname === "/codex-api/composer-file-search") {
2187
- const payload = asRecord2(await readJsonBody(req));
2854
+ const payload = asRecord3(await readJsonBody(req));
2188
2855
  const rawCwd = typeof payload?.cwd === "string" ? payload.cwd.trim() : "";
2189
2856
  const query = typeof payload?.query === "string" ? payload.query.trim() : "";
2190
2857
  const limitRaw = typeof payload?.limit === "number" ? payload.limit : 20;
2191
2858
  const limit = Math.max(1, Math.min(100, Math.floor(limitRaw)));
2192
2859
  if (!rawCwd) {
2193
- setJson2(res, 400, { error: "Missing cwd" });
2860
+ setJson3(res, 400, { error: "Missing cwd" });
2194
2861
  return;
2195
2862
  }
2196
2863
  const cwd = isAbsolute(rawCwd) ? rawCwd : resolve(rawCwd);
2197
2864
  try {
2198
- const info = await stat2(cwd);
2865
+ const info = await stat3(cwd);
2199
2866
  if (!info.isDirectory()) {
2200
- setJson2(res, 400, { error: "cwd is not a directory" });
2867
+ setJson3(res, 400, { error: "cwd is not a directory" });
2201
2868
  return;
2202
2869
  }
2203
2870
  } catch {
2204
- setJson2(res, 404, { error: "cwd does not exist" });
2871
+ setJson3(res, 404, { error: "cwd does not exist" });
2205
2872
  return;
2206
2873
  }
2207
2874
  try {
2208
2875
  const files = await listFilesWithRipgrep(cwd);
2209
2876
  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 }));
2210
- setJson2(res, 200, { data: scored });
2877
+ setJson3(res, 200, { data: scored });
2211
2878
  } catch (error) {
2212
- setJson2(res, 500, { error: getErrorMessage2(error, "Failed to search files") });
2879
+ setJson3(res, 500, { error: getErrorMessage3(error, "Failed to search files") });
2213
2880
  }
2214
2881
  return;
2215
2882
  }
2216
2883
  if (req.method === "GET" && url.pathname === "/codex-api/thread-titles") {
2217
2884
  const cache = await readThreadTitleCache();
2218
- setJson2(res, 200, { data: cache });
2885
+ setJson3(res, 200, { data: cache });
2219
2886
  return;
2220
2887
  }
2221
2888
  if (req.method === "POST" && url.pathname === "/codex-api/thread-search") {
2222
- const payload = asRecord2(await readJsonBody(req));
2889
+ const payload = asRecord3(await readJsonBody(req));
2223
2890
  const query = typeof payload?.query === "string" ? payload.query.trim() : "";
2224
2891
  const limitRaw = typeof payload?.limit === "number" ? payload.limit : 200;
2225
2892
  const limit = Math.max(1, Math.min(1e3, Math.floor(limitRaw)));
2226
2893
  if (!query) {
2227
- setJson2(res, 200, { data: { threadIds: [], indexedThreadCount: 0 } });
2894
+ setJson3(res, 200, { data: { threadIds: [], indexedThreadCount: 0 } });
2228
2895
  return;
2229
2896
  }
2230
2897
  const index = await getThreadSearchIndex();
2231
2898
  const matchedIds = Array.from(index.docsById.entries()).filter(([, doc]) => isExactPhraseMatch(query, doc)).slice(0, limit).map(([id]) => id);
2232
- setJson2(res, 200, { data: { threadIds: matchedIds, indexedThreadCount: index.docsById.size } });
2899
+ setJson3(res, 200, { data: { threadIds: matchedIds, indexedThreadCount: index.docsById.size } });
2233
2900
  return;
2234
2901
  }
2235
2902
  if (req.method === "PUT" && url.pathname === "/codex-api/thread-titles") {
2236
- const payload = asRecord2(await readJsonBody(req));
2903
+ const payload = asRecord3(await readJsonBody(req));
2237
2904
  const id = typeof payload?.id === "string" ? payload.id : "";
2238
2905
  const title = typeof payload?.title === "string" ? payload.title : "";
2239
2906
  if (!id) {
2240
- setJson2(res, 400, { error: "Missing id" });
2907
+ setJson3(res, 400, { error: "Missing id" });
2241
2908
  return;
2242
2909
  }
2243
2910
  const cache = await readThreadTitleCache();
2244
2911
  const next2 = title ? updateThreadTitleCache(cache, id, title) : removeFromThreadTitleCache(cache, id);
2245
2912
  await writeThreadTitleCache(next2);
2246
- setJson2(res, 200, { ok: true });
2913
+ setJson3(res, 200, { ok: true });
2247
2914
  return;
2248
2915
  }
2249
2916
  if (req.method === "GET" && url.pathname === "/codex-api/events") {
@@ -2278,8 +2945,8 @@ data: ${JSON.stringify({ ok: true })}
2278
2945
  }
2279
2946
  next();
2280
2947
  } catch (error) {
2281
- const message = getErrorMessage2(error, "Unknown bridge error");
2282
- setJson2(res, 502, { error: message });
2948
+ const message = getErrorMessage3(error, "Unknown bridge error");
2949
+ setJson3(res, 502, { error: message });
2283
2950
  }
2284
2951
  };
2285
2952
  middleware.dispose = () => {
@@ -2416,8 +3083,8 @@ function createAuthSession(password) {
2416
3083
  }
2417
3084
 
2418
3085
  // src/server/localBrowseUi.ts
2419
- import { dirname, extname, join as join3 } from "path";
2420
- import { open, readFile as readFile3, readdir as readdir3, stat as stat3 } from "fs/promises";
3086
+ import { dirname, extname, join as join4 } from "path";
3087
+ import { open, readFile as readFile4, readdir as readdir3, stat as stat4 } from "fs/promises";
2421
3088
  var TEXT_EDITABLE_EXTENSIONS = /* @__PURE__ */ new Set([
2422
3089
  ".txt",
2423
3090
  ".md",
@@ -2532,7 +3199,7 @@ async function probeFileIsText(localPath) {
2532
3199
  async function isTextEditableFile(localPath) {
2533
3200
  if (isTextEditablePath(localPath)) return true;
2534
3201
  try {
2535
- const fileStat = await stat3(localPath);
3202
+ const fileStat = await stat4(localPath);
2536
3203
  if (!fileStat.isFile()) return false;
2537
3204
  return await probeFileIsText(localPath);
2538
3205
  } catch {
@@ -2554,8 +3221,8 @@ function escapeForInlineScriptString(value) {
2554
3221
  async function getDirectoryItems(localPath) {
2555
3222
  const entries = await readdir3(localPath, { withFileTypes: true });
2556
3223
  const withMeta = await Promise.all(entries.map(async (entry) => {
2557
- const entryPath = join3(localPath, entry.name);
2558
- const entryStat = await stat3(entryPath);
3224
+ const entryPath = join4(localPath, entry.name);
3225
+ const entryStat = await stat4(entryPath);
2559
3226
  const editable = !entry.isDirectory() && await isTextEditableFile(entryPath);
2560
3227
  return {
2561
3228
  name: entry.name,
@@ -2613,7 +3280,7 @@ async function createDirectoryListingHtml(localPath) {
2613
3280
  </html>`;
2614
3281
  }
2615
3282
  async function createTextEditorHtml(localPath) {
2616
- const content = await readFile3(localPath, "utf8");
3283
+ const content = await readFile4(localPath, "utf8");
2617
3284
  const parentPath = dirname(localPath);
2618
3285
  const language = languageForPath(localPath);
2619
3286
  const safeContentLiteral = escapeForInlineScriptString(content);
@@ -2684,8 +3351,8 @@ async function createTextEditorHtml(localPath) {
2684
3351
  // src/server/httpServer.ts
2685
3352
  import { WebSocketServer } from "ws";
2686
3353
  var __dirname = dirname2(fileURLToPath(import.meta.url));
2687
- var distDir = join4(__dirname, "..", "dist");
2688
- var spaEntryFile = join4(distDir, "index.html");
3354
+ var distDir = join5(__dirname, "..", "dist");
3355
+ var spaEntryFile = join5(distDir, "index.html");
2689
3356
  var IMAGE_CONTENT_TYPES = {
2690
3357
  ".avif": "image/avif",
2691
3358
  ".bmp": "image/bmp",
@@ -2762,7 +3429,7 @@ function createServer(options = {}) {
2762
3429
  return;
2763
3430
  }
2764
3431
  try {
2765
- const fileStat = await stat4(localPath);
3432
+ const fileStat = await stat5(localPath);
2766
3433
  res.setHeader("Cache-Control", "private, no-store");
2767
3434
  if (fileStat.isDirectory()) {
2768
3435
  const html = await createDirectoryListingHtml(localPath);
@@ -2785,7 +3452,7 @@ function createServer(options = {}) {
2785
3452
  return;
2786
3453
  }
2787
3454
  try {
2788
- const fileStat = await stat4(localPath);
3455
+ const fileStat = await stat5(localPath);
2789
3456
  if (!fileStat.isFile()) {
2790
3457
  res.status(400).json({ error: "Expected file path." });
2791
3458
  return;
@@ -2809,7 +3476,7 @@ function createServer(options = {}) {
2809
3476
  }
2810
3477
  const body = typeof req.body === "string" ? req.body : "";
2811
3478
  try {
2812
- await writeFile3(localPath, body, "utf8");
3479
+ await writeFile4(localPath, body, "utf8");
2813
3480
  res.status(200).json({ ok: true });
2814
3481
  } catch {
2815
3482
  res.status(404).json({ error: "File not found." });
@@ -2889,8 +3556,8 @@ var program = new Command().name("codexui").description("Web interface for Codex
2889
3556
  var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
2890
3557
  async function readCliVersion() {
2891
3558
  try {
2892
- const packageJsonPath = join5(__dirname2, "..", "package.json");
2893
- const raw = await readFile4(packageJsonPath, "utf8");
3559
+ const packageJsonPath = join6(__dirname2, "..", "package.json");
3560
+ const raw = await readFile5(packageJsonPath, "utf8");
2894
3561
  const parsed = JSON.parse(raw);
2895
3562
  return typeof parsed.version === "string" ? parsed.version : "unknown";
2896
3563
  } catch {
@@ -2915,13 +3582,13 @@ function runWithStatus(command, args) {
2915
3582
  return result.status ?? -1;
2916
3583
  }
2917
3584
  function getUserNpmPrefix() {
2918
- return join5(homedir3(), ".npm-global");
3585
+ return join6(homedir4(), ".npm-global");
2919
3586
  }
2920
3587
  function resolveCodexCommand() {
2921
3588
  if (canRun("codex", ["--version"])) {
2922
3589
  return "codex";
2923
3590
  }
2924
- const userCandidate = join5(getUserNpmPrefix(), "bin", "codex");
3591
+ const userCandidate = join6(getUserNpmPrefix(), "bin", "codex");
2925
3592
  if (existsSync3(userCandidate) && canRun(userCandidate, ["--version"])) {
2926
3593
  return userCandidate;
2927
3594
  }
@@ -2929,7 +3596,7 @@ function resolveCodexCommand() {
2929
3596
  if (!prefix) {
2930
3597
  return null;
2931
3598
  }
2932
- const candidate = join5(prefix, "bin", "codex");
3599
+ const candidate = join6(prefix, "bin", "codex");
2933
3600
  if (existsSync3(candidate) && canRun(candidate, ["--version"])) {
2934
3601
  return candidate;
2935
3602
  }
@@ -2939,7 +3606,7 @@ function resolveCloudflaredCommand() {
2939
3606
  if (canRun("cloudflared", ["--version"])) {
2940
3607
  return "cloudflared";
2941
3608
  }
2942
- const localCandidate = join5(homedir3(), ".local", "bin", "cloudflared");
3609
+ const localCandidate = join6(homedir4(), ".local", "bin", "cloudflared");
2943
3610
  if (existsSync3(localCandidate) && canRun(localCandidate, ["--version"])) {
2944
3611
  return localCandidate;
2945
3612
  }
@@ -2993,9 +3660,9 @@ async function ensureCloudflaredInstalledLinux() {
2993
3660
  if (!mappedArch) {
2994
3661
  throw new Error(`cloudflared auto-install is not supported for Linux architecture: ${process.arch}`);
2995
3662
  }
2996
- const userBinDir = join5(homedir3(), ".local", "bin");
3663
+ const userBinDir = join6(homedir4(), ".local", "bin");
2997
3664
  mkdirSync(userBinDir, { recursive: true });
2998
- const destination = join5(userBinDir, "cloudflared");
3665
+ const destination = join6(userBinDir, "cloudflared");
2999
3666
  const downloadUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${mappedArch}`;
3000
3667
  console.log("\ncloudflared not found. Installing to ~/.local/bin...\n");
3001
3668
  await downloadFile(downloadUrl, destination);
@@ -3034,8 +3701,8 @@ async function resolveCloudflaredForTunnel() {
3034
3701
  return ensureCloudflaredInstalledLinux();
3035
3702
  }
3036
3703
  function hasCodexAuth() {
3037
- const codexHome = process.env.CODEX_HOME?.trim() || join5(homedir3(), ".codex");
3038
- return existsSync3(join5(codexHome, "auth.json"));
3704
+ const codexHome = process.env.CODEX_HOME?.trim() || join6(homedir4(), ".codex");
3705
+ return existsSync3(join6(codexHome, "auth.json"));
3039
3706
  }
3040
3707
  function ensureCodexInstalled() {
3041
3708
  let codexCommand = resolveCodexCommand();
@@ -3053,7 +3720,7 @@ function ensureCodexInstalled() {
3053
3720
  Global npm install requires elevated permissions. Retrying with --prefix ${userPrefix}...
3054
3721
  `);
3055
3722
  runOrFail("npm", ["install", "-g", "--prefix", userPrefix, pkg], `${label} (user prefix)`);
3056
- process.env.PATH = `${join5(userPrefix, "bin")}:${process.env.PATH ?? ""}`;
3723
+ process.env.PATH = `${join6(userPrefix, "bin")}:${process.env.PATH ?? ""}`;
3057
3724
  };
3058
3725
  if (isTermuxRuntime()) {
3059
3726
  console.log("\nCodex CLI not found. Installing Termux-compatible Codex CLI from npm...\n");
@@ -3102,7 +3769,7 @@ function printTermuxKeepAlive(lines) {
3102
3769
  }
3103
3770
  function openBrowser(url) {
3104
3771
  const command = process.platform === "darwin" ? { cmd: "open", args: [url] } : process.platform === "win32" ? { cmd: "cmd", args: ["/c", "start", "", url] } : { cmd: "xdg-open", args: [url] };
3105
- const child = spawn3(command.cmd, command.args, { detached: true, stdio: "ignore" });
3772
+ const child = spawn4(command.cmd, command.args, { detached: true, stdio: "ignore" });
3106
3773
  child.on("error", () => {
3107
3774
  });
3108
3775
  child.unref();
@@ -3134,7 +3801,7 @@ function getAccessibleUrls(port) {
3134
3801
  }
3135
3802
  async function startCloudflaredTunnel(command, localPort) {
3136
3803
  return new Promise((resolve2, reject) => {
3137
- const child = spawn3(command, ["tunnel", "--url", `http://localhost:${String(localPort)}`], {
3804
+ const child = spawn4(command, ["tunnel", "--url", `http://localhost:${String(localPort)}`], {
3138
3805
  stdio: ["ignore", "pipe", "pipe"]
3139
3806
  });
3140
3807
  const timeout = setTimeout(() => {