codexapp 0.1.34 → 0.1.36

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 readFile3 } from "fs/promises";
7
- import { homedir as homedir2, networkInterfaces } from "os";
8
- import { join as join4 } from "path";
9
- import { spawn as spawn2, spawnSync } from "child_process";
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";
10
10
  import { createInterface } from "readline/promises";
11
11
  import { fileURLToPath as fileURLToPath2 } from "url";
12
12
  import { dirname as dirname3 } from "path";
@@ -16,20 +16,27 @@ 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 join3 } from "path";
19
+ import { dirname as dirname2, extname as extname2, isAbsolute as isAbsolute2, join as join4 } from "path";
20
20
  import { existsSync as existsSync2 } from "fs";
21
- import { writeFile as writeFile2, stat as stat3 } from "fs/promises";
21
+ import { writeFile as writeFile3, stat as stat4 } from "fs/promises";
22
22
  import express from "express";
23
23
 
24
24
  // src/server/codexAppServerBridge.ts
25
- import { spawn } from "child_process";
25
+ import { spawn as spawn2 } 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";
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";
33
+
34
+ // src/server/skillsRoutes.ts
35
+ import { spawn } from "child_process";
27
36
  import { mkdtemp, readFile, readdir, rm, mkdir, stat, lstat, readlink, symlink } from "fs/promises";
28
37
  import { existsSync } from "fs";
29
- import { request as httpsRequest } from "https";
30
- import { homedir } from "os";
31
- import { tmpdir } from "os";
32
- import { basename, isAbsolute, join, resolve } from "path";
38
+ import { homedir, tmpdir } from "os";
39
+ import { join } from "path";
33
40
  import { writeFile } from "fs/promises";
34
41
  function asRecord(value) {
35
42
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -53,85 +60,6 @@ function setJson(res, statusCode, payload) {
53
60
  res.setHeader("Content-Type", "application/json; charset=utf-8");
54
61
  res.end(JSON.stringify(payload));
55
62
  }
56
- function extractThreadMessageText(threadReadPayload) {
57
- const payload = asRecord(threadReadPayload);
58
- const thread = asRecord(payload?.thread);
59
- const turns = Array.isArray(thread?.turns) ? thread.turns : [];
60
- const parts = [];
61
- for (const turn of turns) {
62
- const turnRecord = asRecord(turn);
63
- const items = Array.isArray(turnRecord?.items) ? turnRecord.items : [];
64
- for (const item of items) {
65
- const itemRecord = asRecord(item);
66
- const type = typeof itemRecord?.type === "string" ? itemRecord.type : "";
67
- if (type === "agentMessage" && typeof itemRecord?.text === "string" && itemRecord.text.trim().length > 0) {
68
- parts.push(itemRecord.text.trim());
69
- continue;
70
- }
71
- if (type === "userMessage") {
72
- const content = Array.isArray(itemRecord?.content) ? itemRecord.content : [];
73
- for (const block of content) {
74
- const blockRecord = asRecord(block);
75
- if (blockRecord?.type === "text" && typeof blockRecord.text === "string" && blockRecord.text.trim().length > 0) {
76
- parts.push(blockRecord.text.trim());
77
- }
78
- }
79
- continue;
80
- }
81
- if (type === "commandExecution") {
82
- const command = typeof itemRecord?.command === "string" ? itemRecord.command.trim() : "";
83
- const output = typeof itemRecord?.aggregatedOutput === "string" ? itemRecord.aggregatedOutput.trim() : "";
84
- if (command) parts.push(command);
85
- if (output) parts.push(output);
86
- }
87
- }
88
- }
89
- return parts.join("\n").trim();
90
- }
91
- function isExactPhraseMatch(query, doc) {
92
- const q = query.trim().toLowerCase();
93
- if (!q) return false;
94
- return doc.title.toLowerCase().includes(q) || doc.preview.toLowerCase().includes(q) || doc.messageText.toLowerCase().includes(q);
95
- }
96
- function scoreFileCandidate(path, query) {
97
- if (!query) return 0;
98
- const lowerPath = path.toLowerCase();
99
- const lowerQuery = query.toLowerCase();
100
- const baseName = lowerPath.slice(lowerPath.lastIndexOf("/") + 1);
101
- if (baseName === lowerQuery) return 0;
102
- if (baseName.startsWith(lowerQuery)) return 1;
103
- if (baseName.includes(lowerQuery)) return 2;
104
- if (lowerPath.includes(`/${lowerQuery}`)) return 3;
105
- if (lowerPath.includes(lowerQuery)) return 4;
106
- return 10;
107
- }
108
- async function listFilesWithRipgrep(cwd) {
109
- return await new Promise((resolve2, reject) => {
110
- const proc = spawn("rg", ["--files", "--hidden", "-g", "!.git", "-g", "!node_modules"], {
111
- cwd,
112
- env: process.env,
113
- stdio: ["ignore", "pipe", "pipe"]
114
- });
115
- let stdout = "";
116
- let stderr = "";
117
- proc.stdout.on("data", (chunk) => {
118
- stdout += chunk.toString();
119
- });
120
- proc.stderr.on("data", (chunk) => {
121
- stderr += chunk.toString();
122
- });
123
- proc.on("error", reject);
124
- proc.on("close", (code) => {
125
- if (code === 0) {
126
- const rows = stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
127
- resolve2(rows);
128
- return;
129
- }
130
- const details = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
131
- reject(new Error(details || "rg --files failed"));
132
- });
133
- });
134
- }
135
63
  function getCodexHomeDir() {
136
64
  const codexHome = process.env.CODEX_HOME?.trim();
137
65
  return codexHome && codexHome.length > 0 ? codexHome : join(homedir(), ".codex");
@@ -166,55 +94,6 @@ async function runCommand(command, args, options = {}) {
166
94
  });
167
95
  });
168
96
  }
169
- function isMissingHeadError(error) {
170
- const message = getErrorMessage(error, "").toLowerCase();
171
- return message.includes("not a valid object name: 'head'") || message.includes("not a valid object name: head") || message.includes("invalid reference: head");
172
- }
173
- function isNotGitRepositoryError(error) {
174
- const message = getErrorMessage(error, "").toLowerCase();
175
- return message.includes("not a git repository") || message.includes("fatal: not a git repository");
176
- }
177
- async function ensureRepoHasInitialCommit(repoRoot) {
178
- const agentsPath = join(repoRoot, "AGENTS.md");
179
- try {
180
- await stat(agentsPath);
181
- } catch {
182
- await writeFile(agentsPath, "", "utf8");
183
- }
184
- await runCommand("git", ["add", "AGENTS.md"], { cwd: repoRoot });
185
- await runCommand(
186
- "git",
187
- ["-c", "user.name=Codex", "-c", "user.email=codex@local", "commit", "-m", "Initialize repository for worktree support"],
188
- { cwd: repoRoot }
189
- );
190
- }
191
- async function runCommandCapture(command, args, options = {}) {
192
- return await new Promise((resolve2, reject) => {
193
- const proc = spawn(command, args, {
194
- cwd: options.cwd,
195
- env: process.env,
196
- stdio: ["ignore", "pipe", "pipe"]
197
- });
198
- let stdout = "";
199
- let stderr = "";
200
- proc.stdout.on("data", (chunk) => {
201
- stdout += chunk.toString();
202
- });
203
- proc.stderr.on("data", (chunk) => {
204
- stderr += chunk.toString();
205
- });
206
- proc.on("error", reject);
207
- proc.on("close", (code) => {
208
- if (code === 0) {
209
- resolve2(stdout.trim());
210
- return;
211
- }
212
- const details = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
213
- const suffix = details.length > 0 ? `: ${details}` : "";
214
- reject(new Error(`Command failed (${command} ${args.join(" ")})${suffix}`));
215
- });
216
- });
217
- }
218
97
  async function runCommandWithOutput(command, args, options = {}) {
219
98
  return await new Promise((resolve2, reject) => {
220
99
  const proc = spawn(command, args, {
@@ -325,7 +204,7 @@ async function fetchMetaBatch(entries) {
325
204
  const toFetch = entries.filter((e) => !metaCache.has(`${e.owner}/${e.name}`));
326
205
  if (toFetch.length === 0) return;
327
206
  const batch = toFetch.slice(0, 50);
328
- const results = await Promise.allSettled(
207
+ await Promise.allSettled(
329
208
  batch.map(async (e) => {
330
209
  const rawUrl = `https://raw.githubusercontent.com/${HUB_SKILLS_OWNER}/${HUB_SKILLS_REPO}/main/skills/${e.owner}/${e.name}/_meta.json`;
331
210
  const resp = await fetch(rawUrl);
@@ -338,7 +217,6 @@ async function fetchMetaBatch(entries) {
338
217
  });
339
218
  })
340
219
  );
341
- void results;
342
220
  }
343
221
  function buildHubEntry(e) {
344
222
  const cached = metaCache.get(`${e.owner}/${e.name}`);
@@ -467,8 +345,7 @@ function isAndroidLikeRuntime() {
467
345
  const prefix = process.env.PREFIX?.toLowerCase() ?? "";
468
346
  if (prefix.includes("/com.termux/")) return true;
469
347
  const proot = process.env.PROOT_TMP_DIR?.toLowerCase() ?? "";
470
- if (proot.length > 0) return true;
471
- return false;
348
+ return proot.length > 0;
472
349
  }
473
350
  function getPreferredSyncBranch() {
474
351
  return isAndroidLikeRuntime() ? "android" : "main";
@@ -732,23 +609,21 @@ async function syncInstalledSkillsFolderToRepo(token, repoOwner, repoName, _inst
732
609
  const remoteUrl = toGitHubTokenRemote(repoOwner, repoName, token);
733
610
  const branch = getPreferredSyncBranch();
734
611
  const repoDir = await ensureSkillsWorkingTreeRepo(remoteUrl, branch);
735
- const addPaths = ["."];
736
612
  void _installedMap;
737
613
  await runCommand("git", ["config", "user.email", "skills-sync@local"], { cwd: repoDir });
738
614
  await runCommand("git", ["config", "user.name", "Skills Sync"], { cwd: repoDir });
739
- await runCommand("git", ["add", ...addPaths], { cwd: repoDir });
615
+ await runCommand("git", ["add", "."], { cwd: repoDir });
740
616
  const status = (await runCommandWithOutput("git", ["status", "--porcelain"], { cwd: repoDir })).trim();
741
617
  if (!status) return;
742
618
  await runCommand("git", ["commit", "-m", "Sync installed skills folder and manifest"], { cwd: repoDir });
743
619
  await runCommand("git", ["push", "origin", `HEAD:${branch}`], { cwd: repoDir });
744
620
  }
745
- async function pullInstalledSkillsFolderFromRepo(token, repoOwner, repoName, _localSkillsDir) {
621
+ async function pullInstalledSkillsFolderFromRepo(token, repoOwner, repoName) {
746
622
  const remoteUrl = toGitHubTokenRemote(repoOwner, repoName, token);
747
623
  const branch = getPreferredSyncBranch();
748
624
  await ensureSkillsWorkingTreeRepo(remoteUrl, branch);
749
625
  }
750
- async function bootstrapSkillsFromUpstreamIntoLocal(_localSkillsDir) {
751
- void _localSkillsDir;
626
+ async function bootstrapSkillsFromUpstreamIntoLocal() {
752
627
  const repoUrl = `https://github.com/${SYNC_UPSTREAM_SKILLS_OWNER}/${SYNC_UPSTREAM_SKILLS_REPO}.git`;
753
628
  const branch = getPreferredSyncBranch();
754
629
  await ensureSkillsWorkingTreeRepo(repoUrl, branch);
@@ -857,7 +732,6 @@ async function initializeSkillsSyncOnStartup(appServer) {
857
732
  startupSyncStatus.branch = getPreferredSyncBranch();
858
733
  try {
859
734
  const state = await readSkillsSyncState();
860
- const localSkillsDir = getSkillsInstallDir();
861
735
  if (!state.githubToken) {
862
736
  await ensureCodexAgentsSymlinkToSkillsAgents();
863
737
  if (!isAndroidLikeRuntime()) {
@@ -868,7 +742,7 @@ async function initializeSkillsSyncOnStartup(appServer) {
868
742
  }
869
743
  startupSyncStatus.mode = "unauthenticated-bootstrap";
870
744
  startupSyncStatus.lastAction = "pull-upstream";
871
- await bootstrapSkillsFromUpstreamIntoLocal(localSkillsDir);
745
+ await bootstrapSkillsFromUpstreamIntoLocal();
872
746
  try {
873
747
  await appServer.rpc("skills/list", { forceReload: true });
874
748
  } catch {
@@ -882,10 +756,9 @@ async function initializeSkillsSyncOnStartup(appServer) {
882
756
  const username = state.githubUsername || await resolveGithubUsername(state.githubToken);
883
757
  const repoName = DEFAULT_SKILLS_SYNC_REPO_NAME;
884
758
  await ensurePrivateForkFromUpstream(state.githubToken, username, repoName);
885
- const nextState = { ...state, githubUsername: username, repoOwner: username, repoName };
886
- await writeSkillsSyncState(nextState);
759
+ await writeSkillsSyncState({ ...state, githubUsername: username, repoOwner: username, repoName });
887
760
  startupSyncStatus.lastAction = "pull-private-fork";
888
- await pullInstalledSkillsFolderFromRepo(state.githubToken, username, repoName, localSkillsDir);
761
+ await pullInstalledSkillsFolderFromRepo(state.githubToken, username, repoName);
889
762
  try {
890
763
  await appServer.rpc("skills/list", { forceReload: true });
891
764
  } catch {
@@ -905,15 +778,8 @@ async function finalizeGithubLoginAndSync(token, username, appServer) {
905
778
  const repoName = DEFAULT_SKILLS_SYNC_REPO_NAME;
906
779
  await ensurePrivateForkFromUpstream(token, username, repoName);
907
780
  const current = await readSkillsSyncState();
908
- await writeSkillsSyncState({
909
- ...current,
910
- githubToken: token,
911
- githubUsername: username,
912
- repoOwner: username,
913
- repoName
914
- });
915
- const localDir = getSkillsInstallDir();
916
- await pullInstalledSkillsFolderFromRepo(token, username, repoName, localDir);
781
+ await writeSkillsSyncState({ ...current, githubToken: token, githubUsername: username, repoOwner: username, repoName });
782
+ await pullInstalledSkillsFolderFromRepo(token, username, repoName);
917
783
  try {
918
784
  await appServer.rpc("skills/list", { forceReload: true });
919
785
  } catch {
@@ -922,11 +788,10 @@ async function finalizeGithubLoginAndSync(token, username, appServer) {
922
788
  }
923
789
  async function searchSkillsHub(allEntries, query, limit, sort, installedMap) {
924
790
  const q = query.toLowerCase().trim();
925
- let filtered = q ? allEntries.filter((s) => {
791
+ const filtered = q ? allEntries.filter((s) => {
926
792
  if (s.name.toLowerCase().includes(q) || s.owner.toLowerCase().includes(q)) return true;
927
793
  const cached = metaCache.get(`${s.owner}/${s.name}`);
928
- if (cached?.displayName?.toLowerCase().includes(q)) return true;
929
- return false;
794
+ return Boolean(cached?.displayName?.toLowerCase().includes(q));
930
795
  }) : allEntries;
931
796
  const page = filtered.slice(0, Math.min(limit * 2, 200));
932
797
  await fetchMetaBatch(page);
@@ -946,6 +811,494 @@ async function searchSkillsHub(allEntries, query, limit, sort, installedMap) {
946
811
  return local ? { ...s, installed: true, path: local.path, enabled: local.enabled } : s;
947
812
  });
948
813
  }
814
+ async function handleSkillsRoutes(req, res, url, context) {
815
+ const { appServer, readJsonBody: readJsonBody2 } = context;
816
+ if (req.method === "GET" && url.pathname === "/codex-api/skills-hub") {
817
+ try {
818
+ const q = url.searchParams.get("q") || "";
819
+ const limit = Math.min(Math.max(parseInt(url.searchParams.get("limit") || "50", 10) || 50, 1), 200);
820
+ const sort = url.searchParams.get("sort") || "date";
821
+ const allEntries = await fetchSkillsTree();
822
+ const installedMap = await scanInstalledSkillsFromDisk();
823
+ try {
824
+ const result = await appServer.rpc("skills/list", {});
825
+ for (const entry of result.data ?? []) {
826
+ for (const skill of entry.skills ?? []) {
827
+ if (skill.name) {
828
+ installedMap.set(skill.name, { name: skill.name, path: skill.path ?? "", enabled: skill.enabled !== false });
829
+ }
830
+ }
831
+ }
832
+ } catch {
833
+ }
834
+ const installedHubEntries = allEntries.filter((e) => installedMap.has(e.name));
835
+ await fetchMetaBatch(installedHubEntries);
836
+ const installed = [];
837
+ for (const [, info] of installedMap) {
838
+ const hubEntry = allEntries.find((e) => e.name === info.name);
839
+ const base = hubEntry ? buildHubEntry(hubEntry) : {
840
+ name: info.name,
841
+ owner: "local",
842
+ description: "",
843
+ displayName: "",
844
+ publishedAt: 0,
845
+ avatarUrl: "",
846
+ url: "",
847
+ installed: false
848
+ };
849
+ installed.push({ ...base, installed: true, path: info.path, enabled: info.enabled });
850
+ }
851
+ const results = await searchSkillsHub(allEntries, q, limit, sort, installedMap);
852
+ setJson(res, 200, { data: results, installed, total: allEntries.length });
853
+ } catch (error) {
854
+ setJson(res, 502, { error: getErrorMessage(error, "Failed to fetch skills hub") });
855
+ }
856
+ return true;
857
+ }
858
+ if (req.method === "GET" && url.pathname === "/codex-api/skills-sync/status") {
859
+ const state = await readSkillsSyncState();
860
+ setJson(res, 200, {
861
+ data: {
862
+ loggedIn: Boolean(state.githubToken),
863
+ githubUsername: state.githubUsername ?? "",
864
+ repoOwner: state.repoOwner ?? "",
865
+ repoName: state.repoName ?? "",
866
+ configured: Boolean(state.githubToken && state.repoOwner && state.repoName),
867
+ startup: {
868
+ inProgress: startupSyncStatus.inProgress,
869
+ mode: startupSyncStatus.mode,
870
+ branch: startupSyncStatus.branch,
871
+ lastAction: startupSyncStatus.lastAction,
872
+ lastRunAtIso: startupSyncStatus.lastRunAtIso,
873
+ lastSuccessAtIso: startupSyncStatus.lastSuccessAtIso,
874
+ lastError: startupSyncStatus.lastError
875
+ }
876
+ }
877
+ });
878
+ return true;
879
+ }
880
+ if (req.method === "POST" && url.pathname === "/codex-api/skills-sync/github/start-login") {
881
+ try {
882
+ const started = await startGithubDeviceLogin();
883
+ setJson(res, 200, { data: started });
884
+ } catch (error) {
885
+ setJson(res, 502, { error: getErrorMessage(error, "Failed to start GitHub login") });
886
+ }
887
+ return true;
888
+ }
889
+ if (req.method === "POST" && url.pathname === "/codex-api/skills-sync/github/token-login") {
890
+ try {
891
+ const payload = asRecord(await readJsonBody2(req));
892
+ const token = typeof payload?.token === "string" ? payload.token.trim() : "";
893
+ if (!token) {
894
+ setJson(res, 400, { error: "Missing GitHub token" });
895
+ return true;
896
+ }
897
+ const username = await resolveGithubUsername(token);
898
+ await finalizeGithubLoginAndSync(token, username, appServer);
899
+ setJson(res, 200, { ok: true, data: { githubUsername: username } });
900
+ } catch (error) {
901
+ setJson(res, 502, { error: getErrorMessage(error, "Failed to login with GitHub token") });
902
+ }
903
+ return true;
904
+ }
905
+ if (req.method === "POST" && url.pathname === "/codex-api/skills-sync/github/logout") {
906
+ try {
907
+ const state = await readSkillsSyncState();
908
+ await writeSkillsSyncState({
909
+ ...state,
910
+ githubToken: void 0,
911
+ githubUsername: void 0,
912
+ repoOwner: void 0,
913
+ repoName: void 0
914
+ });
915
+ setJson(res, 200, { ok: true });
916
+ } catch (error) {
917
+ setJson(res, 500, { error: getErrorMessage(error, "Failed to logout GitHub") });
918
+ }
919
+ return true;
920
+ }
921
+ if (req.method === "POST" && url.pathname === "/codex-api/skills-sync/github/complete-login") {
922
+ try {
923
+ const payload = asRecord(await readJsonBody2(req));
924
+ const deviceCode = typeof payload?.deviceCode === "string" ? payload.deviceCode : "";
925
+ if (!deviceCode) {
926
+ setJson(res, 400, { error: "Missing deviceCode" });
927
+ return true;
928
+ }
929
+ const result = await completeGithubDeviceLogin(deviceCode);
930
+ if (!result.token) {
931
+ setJson(res, 200, { ok: false, pending: result.error === "authorization_pending", error: result.error || "login_failed" });
932
+ return true;
933
+ }
934
+ const token = result.token;
935
+ const username = await resolveGithubUsername(token);
936
+ await finalizeGithubLoginAndSync(token, username, appServer);
937
+ setJson(res, 200, { ok: true, data: { githubUsername: username } });
938
+ } catch (error) {
939
+ setJson(res, 502, { error: getErrorMessage(error, "Failed to complete GitHub login") });
940
+ }
941
+ return true;
942
+ }
943
+ if (req.method === "POST" && url.pathname === "/codex-api/skills-sync/push") {
944
+ try {
945
+ const state = await readSkillsSyncState();
946
+ if (!state.githubToken || !state.repoOwner || !state.repoName) {
947
+ setJson(res, 400, { error: "Skills sync is not configured yet" });
948
+ return true;
949
+ }
950
+ if (isUpstreamSkillsRepo(state.repoOwner, state.repoName)) {
951
+ setJson(res, 400, { error: "Refusing to push to upstream repository" });
952
+ return true;
953
+ }
954
+ const local = await collectLocalSyncedSkills(appServer);
955
+ const installedMap = await scanInstalledSkillsFromDisk();
956
+ await writeRemoteSkillsManifest(state.githubToken, state.repoOwner, state.repoName, local);
957
+ await syncInstalledSkillsFolderToRepo(state.githubToken, state.repoOwner, state.repoName, installedMap);
958
+ setJson(res, 200, { ok: true, data: { synced: local.length } });
959
+ } catch (error) {
960
+ setJson(res, 502, { error: getErrorMessage(error, "Failed to push synced skills") });
961
+ }
962
+ return true;
963
+ }
964
+ if (req.method === "POST" && url.pathname === "/codex-api/skills-sync/pull") {
965
+ try {
966
+ const state = await readSkillsSyncState();
967
+ if (!state.githubToken || !state.repoOwner || !state.repoName) {
968
+ await bootstrapSkillsFromUpstreamIntoLocal();
969
+ try {
970
+ await appServer.rpc("skills/list", { forceReload: true });
971
+ } catch {
972
+ }
973
+ setJson(res, 200, { ok: true, data: { synced: 0, source: "upstream" } });
974
+ return true;
975
+ }
976
+ const remote = await readRemoteSkillsManifest(state.githubToken, state.repoOwner, state.repoName);
977
+ const tree = await fetchSkillsTree();
978
+ const uniqueOwnerByName = /* @__PURE__ */ new Map();
979
+ const ambiguousNames = /* @__PURE__ */ new Set();
980
+ for (const entry of tree) {
981
+ if (ambiguousNames.has(entry.name)) continue;
982
+ const existingOwner = uniqueOwnerByName.get(entry.name);
983
+ if (!existingOwner) {
984
+ uniqueOwnerByName.set(entry.name, entry.owner);
985
+ continue;
986
+ }
987
+ if (existingOwner !== entry.owner) {
988
+ uniqueOwnerByName.delete(entry.name);
989
+ ambiguousNames.add(entry.name);
990
+ }
991
+ }
992
+ const localDir = await detectUserSkillsDir(appServer);
993
+ await pullInstalledSkillsFolderFromRepo(state.githubToken, state.repoOwner, state.repoName);
994
+ const installerScript = "/Users/igor/.cursor/skills/.system/skill-installer/scripts/install-skill-from-github.py";
995
+ const localSkills = await scanInstalledSkillsFromDisk();
996
+ for (const skill of remote) {
997
+ const owner = skill.owner || uniqueOwnerByName.get(skill.name) || "";
998
+ if (!owner) continue;
999
+ if (!localSkills.has(skill.name)) {
1000
+ await runCommand("python3", [
1001
+ installerScript,
1002
+ "--repo",
1003
+ `${HUB_SKILLS_OWNER}/${HUB_SKILLS_REPO}`,
1004
+ "--path",
1005
+ `skills/${owner}/${skill.name}`,
1006
+ "--dest",
1007
+ localDir,
1008
+ "--method",
1009
+ "git"
1010
+ ]);
1011
+ }
1012
+ const skillPath = join(localDir, skill.name);
1013
+ await appServer.rpc("skills/config/write", { path: skillPath, enabled: skill.enabled });
1014
+ }
1015
+ const remoteNames = new Set(remote.map((row) => row.name));
1016
+ for (const [name, localInfo] of localSkills.entries()) {
1017
+ if (!remoteNames.has(name)) {
1018
+ await rm(localInfo.path.replace(/\/SKILL\.md$/, ""), { recursive: true, force: true });
1019
+ }
1020
+ }
1021
+ const nextOwners = {};
1022
+ for (const item of remote) {
1023
+ const owner = item.owner || uniqueOwnerByName.get(item.name) || "";
1024
+ if (owner) nextOwners[item.name] = owner;
1025
+ }
1026
+ await writeSkillsSyncState({ ...state, installedOwners: nextOwners });
1027
+ try {
1028
+ await appServer.rpc("skills/list", { forceReload: true });
1029
+ } catch {
1030
+ }
1031
+ setJson(res, 200, { ok: true, data: { synced: remote.length } });
1032
+ } catch (error) {
1033
+ setJson(res, 502, { error: getErrorMessage(error, "Failed to pull synced skills") });
1034
+ }
1035
+ return true;
1036
+ }
1037
+ if (req.method === "GET" && url.pathname === "/codex-api/skills-hub/readme") {
1038
+ try {
1039
+ const owner = url.searchParams.get("owner") || "";
1040
+ const name = url.searchParams.get("name") || "";
1041
+ if (!owner || !name) {
1042
+ setJson(res, 400, { error: "Missing owner or name" });
1043
+ return true;
1044
+ }
1045
+ const rawUrl = `https://raw.githubusercontent.com/${HUB_SKILLS_OWNER}/${HUB_SKILLS_REPO}/main/skills/${owner}/${name}/SKILL.md`;
1046
+ const resp = await fetch(rawUrl);
1047
+ if (!resp.ok) throw new Error(`Failed to fetch SKILL.md: ${resp.status}`);
1048
+ const content = await resp.text();
1049
+ setJson(res, 200, { content });
1050
+ } catch (error) {
1051
+ setJson(res, 502, { error: getErrorMessage(error, "Failed to fetch SKILL.md") });
1052
+ }
1053
+ return true;
1054
+ }
1055
+ if (req.method === "POST" && url.pathname === "/codex-api/skills-hub/install") {
1056
+ try {
1057
+ const payload = asRecord(await readJsonBody2(req));
1058
+ const owner = typeof payload?.owner === "string" ? payload.owner : "";
1059
+ const name = typeof payload?.name === "string" ? payload.name : "";
1060
+ if (!owner || !name) {
1061
+ setJson(res, 400, { error: "Missing owner or name" });
1062
+ return true;
1063
+ }
1064
+ const installerScript = "/Users/igor/.cursor/skills/.system/skill-installer/scripts/install-skill-from-github.py";
1065
+ const installDest = await detectUserSkillsDir(appServer);
1066
+ await runCommand("python3", [
1067
+ installerScript,
1068
+ "--repo",
1069
+ `${HUB_SKILLS_OWNER}/${HUB_SKILLS_REPO}`,
1070
+ "--path",
1071
+ `skills/${owner}/${name}`,
1072
+ "--dest",
1073
+ installDest,
1074
+ "--method",
1075
+ "git"
1076
+ ]);
1077
+ const skillDir = join(installDest, name);
1078
+ await ensureInstalledSkillIsValid(appServer, skillDir);
1079
+ const syncState = await readSkillsSyncState();
1080
+ const nextOwners = { ...syncState.installedOwners ?? {}, [name]: owner };
1081
+ await writeSkillsSyncState({ ...syncState, installedOwners: nextOwners });
1082
+ await autoPushSyncedSkills(appServer);
1083
+ setJson(res, 200, { ok: true, path: skillDir });
1084
+ } catch (error) {
1085
+ setJson(res, 502, { error: getErrorMessage(error, "Failed to install skill") });
1086
+ }
1087
+ return true;
1088
+ }
1089
+ if (req.method === "POST" && url.pathname === "/codex-api/skills-hub/uninstall") {
1090
+ try {
1091
+ const payload = asRecord(await readJsonBody2(req));
1092
+ const name = typeof payload?.name === "string" ? payload.name : "";
1093
+ const path = typeof payload?.path === "string" ? payload.path : "";
1094
+ const target = path || (name ? join(getSkillsInstallDir(), name) : "");
1095
+ if (!target) {
1096
+ setJson(res, 400, { error: "Missing name or path" });
1097
+ return true;
1098
+ }
1099
+ await rm(target, { recursive: true, force: true });
1100
+ if (name) {
1101
+ const syncState = await readSkillsSyncState();
1102
+ const nextOwners = { ...syncState.installedOwners ?? {} };
1103
+ delete nextOwners[name];
1104
+ await writeSkillsSyncState({ ...syncState, installedOwners: nextOwners });
1105
+ }
1106
+ await autoPushSyncedSkills(appServer);
1107
+ try {
1108
+ await appServer.rpc("skills/list", { forceReload: true });
1109
+ } catch {
1110
+ }
1111
+ setJson(res, 200, { ok: true, deletedPath: target });
1112
+ } catch (error) {
1113
+ setJson(res, 502, { error: getErrorMessage(error, "Failed to uninstall skill") });
1114
+ }
1115
+ return true;
1116
+ }
1117
+ return false;
1118
+ }
1119
+
1120
+ // src/server/codexAppServerBridge.ts
1121
+ function asRecord2(value) {
1122
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
1123
+ }
1124
+ function getErrorMessage2(payload, fallback) {
1125
+ if (payload instanceof Error && payload.message.trim().length > 0) {
1126
+ return payload.message;
1127
+ }
1128
+ const record = asRecord2(payload);
1129
+ if (!record) return fallback;
1130
+ const error = record.error;
1131
+ if (typeof error === "string" && error.length > 0) return error;
1132
+ const nestedError = asRecord2(error);
1133
+ if (nestedError && typeof nestedError.message === "string" && nestedError.message.length > 0) {
1134
+ return nestedError.message;
1135
+ }
1136
+ return fallback;
1137
+ }
1138
+ function setJson2(res, statusCode, payload) {
1139
+ res.statusCode = statusCode;
1140
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
1141
+ res.end(JSON.stringify(payload));
1142
+ }
1143
+ function extractThreadMessageText(threadReadPayload) {
1144
+ const payload = asRecord2(threadReadPayload);
1145
+ const thread = asRecord2(payload?.thread);
1146
+ const turns = Array.isArray(thread?.turns) ? thread.turns : [];
1147
+ const parts = [];
1148
+ for (const turn of turns) {
1149
+ const turnRecord = asRecord2(turn);
1150
+ const items = Array.isArray(turnRecord?.items) ? turnRecord.items : [];
1151
+ for (const item of items) {
1152
+ const itemRecord = asRecord2(item);
1153
+ const type = typeof itemRecord?.type === "string" ? itemRecord.type : "";
1154
+ if (type === "agentMessage" && typeof itemRecord?.text === "string" && itemRecord.text.trim().length > 0) {
1155
+ parts.push(itemRecord.text.trim());
1156
+ continue;
1157
+ }
1158
+ if (type === "userMessage") {
1159
+ const content = Array.isArray(itemRecord?.content) ? itemRecord.content : [];
1160
+ for (const block of content) {
1161
+ const blockRecord = asRecord2(block);
1162
+ if (blockRecord?.type === "text" && typeof blockRecord.text === "string" && blockRecord.text.trim().length > 0) {
1163
+ parts.push(blockRecord.text.trim());
1164
+ }
1165
+ }
1166
+ continue;
1167
+ }
1168
+ if (type === "commandExecution") {
1169
+ const command = typeof itemRecord?.command === "string" ? itemRecord.command.trim() : "";
1170
+ const output = typeof itemRecord?.aggregatedOutput === "string" ? itemRecord.aggregatedOutput.trim() : "";
1171
+ if (command) parts.push(command);
1172
+ if (output) parts.push(output);
1173
+ }
1174
+ }
1175
+ }
1176
+ return parts.join("\n").trim();
1177
+ }
1178
+ function isExactPhraseMatch(query, doc) {
1179
+ const q = query.trim().toLowerCase();
1180
+ if (!q) return false;
1181
+ return doc.title.toLowerCase().includes(q) || doc.preview.toLowerCase().includes(q) || doc.messageText.toLowerCase().includes(q);
1182
+ }
1183
+ function scoreFileCandidate(path, query) {
1184
+ if (!query) return 0;
1185
+ const lowerPath = path.toLowerCase();
1186
+ const lowerQuery = query.toLowerCase();
1187
+ const baseName = lowerPath.slice(lowerPath.lastIndexOf("/") + 1);
1188
+ if (baseName === lowerQuery) return 0;
1189
+ if (baseName.startsWith(lowerQuery)) return 1;
1190
+ if (baseName.includes(lowerQuery)) return 2;
1191
+ if (lowerPath.includes(`/${lowerQuery}`)) return 3;
1192
+ if (lowerPath.includes(lowerQuery)) return 4;
1193
+ return 10;
1194
+ }
1195
+ async function listFilesWithRipgrep(cwd) {
1196
+ return await new Promise((resolve2, reject) => {
1197
+ const proc = spawn2("rg", ["--files", "--hidden", "-g", "!.git", "-g", "!node_modules"], {
1198
+ cwd,
1199
+ env: process.env,
1200
+ stdio: ["ignore", "pipe", "pipe"]
1201
+ });
1202
+ let stdout = "";
1203
+ let stderr = "";
1204
+ proc.stdout.on("data", (chunk) => {
1205
+ stdout += chunk.toString();
1206
+ });
1207
+ proc.stderr.on("data", (chunk) => {
1208
+ stderr += chunk.toString();
1209
+ });
1210
+ proc.on("error", reject);
1211
+ proc.on("close", (code) => {
1212
+ if (code === 0) {
1213
+ const rows = stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1214
+ resolve2(rows);
1215
+ return;
1216
+ }
1217
+ const details = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
1218
+ reject(new Error(details || "rg --files failed"));
1219
+ });
1220
+ });
1221
+ }
1222
+ function getCodexHomeDir2() {
1223
+ const codexHome = process.env.CODEX_HOME?.trim();
1224
+ return codexHome && codexHome.length > 0 ? codexHome : join2(homedir2(), ".codex");
1225
+ }
1226
+ async function runCommand2(command, args, options = {}) {
1227
+ await new Promise((resolve2, reject) => {
1228
+ const proc = spawn2(command, args, {
1229
+ cwd: options.cwd,
1230
+ env: process.env,
1231
+ stdio: ["ignore", "pipe", "pipe"]
1232
+ });
1233
+ let stdout = "";
1234
+ let stderr = "";
1235
+ proc.stdout.on("data", (chunk) => {
1236
+ stdout += chunk.toString();
1237
+ });
1238
+ proc.stderr.on("data", (chunk) => {
1239
+ stderr += chunk.toString();
1240
+ });
1241
+ proc.on("error", reject);
1242
+ proc.on("close", (code) => {
1243
+ if (code === 0) {
1244
+ resolve2();
1245
+ return;
1246
+ }
1247
+ const details = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
1248
+ const suffix = details.length > 0 ? `: ${details}` : "";
1249
+ reject(new Error(`Command failed (${command} ${args.join(" ")})${suffix}`));
1250
+ });
1251
+ });
1252
+ }
1253
+ function isMissingHeadError(error) {
1254
+ const message = getErrorMessage2(error, "").toLowerCase();
1255
+ return message.includes("not a valid object name: 'head'") || message.includes("not a valid object name: head") || message.includes("invalid reference: head");
1256
+ }
1257
+ function isNotGitRepositoryError(error) {
1258
+ const message = getErrorMessage2(error, "").toLowerCase();
1259
+ return message.includes("not a git repository") || message.includes("fatal: not a git repository");
1260
+ }
1261
+ async function ensureRepoHasInitialCommit(repoRoot) {
1262
+ const agentsPath = join2(repoRoot, "AGENTS.md");
1263
+ try {
1264
+ await stat2(agentsPath);
1265
+ } catch {
1266
+ await writeFile2(agentsPath, "", "utf8");
1267
+ }
1268
+ await runCommand2("git", ["add", "AGENTS.md"], { cwd: repoRoot });
1269
+ await runCommand2(
1270
+ "git",
1271
+ ["-c", "user.name=Codex", "-c", "user.email=codex@local", "commit", "-m", "Initialize repository for worktree support"],
1272
+ { cwd: repoRoot }
1273
+ );
1274
+ }
1275
+ async function runCommandCapture(command, args, options = {}) {
1276
+ return await new Promise((resolve2, reject) => {
1277
+ const proc = spawn2(command, args, {
1278
+ cwd: options.cwd,
1279
+ env: process.env,
1280
+ stdio: ["ignore", "pipe", "pipe"]
1281
+ });
1282
+ let stdout = "";
1283
+ let stderr = "";
1284
+ proc.stdout.on("data", (chunk) => {
1285
+ stdout += chunk.toString();
1286
+ });
1287
+ proc.stderr.on("data", (chunk) => {
1288
+ stderr += chunk.toString();
1289
+ });
1290
+ proc.on("error", reject);
1291
+ proc.on("close", (code) => {
1292
+ if (code === 0) {
1293
+ resolve2(stdout.trim());
1294
+ return;
1295
+ }
1296
+ const details = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
1297
+ const suffix = details.length > 0 ? `: ${details}` : "";
1298
+ reject(new Error(`Command failed (${command} ${args.join(" ")})${suffix}`));
1299
+ });
1300
+ });
1301
+ }
949
1302
  function normalizeStringArray(value) {
950
1303
  if (!Array.isArray(value)) return [];
951
1304
  const normalized = [];
@@ -967,11 +1320,11 @@ function normalizeStringRecord(value) {
967
1320
  return next;
968
1321
  }
969
1322
  function getCodexAuthPath() {
970
- return join(getCodexHomeDir(), "auth.json");
1323
+ return join2(getCodexHomeDir2(), "auth.json");
971
1324
  }
972
1325
  async function readCodexAuth() {
973
1326
  try {
974
- const raw = await readFile(getCodexAuthPath(), "utf8");
1327
+ const raw = await readFile2(getCodexAuthPath(), "utf8");
975
1328
  const auth = JSON.parse(raw);
976
1329
  const token = auth.tokens?.access_token;
977
1330
  if (!token) return null;
@@ -981,13 +1334,13 @@ async function readCodexAuth() {
981
1334
  }
982
1335
  }
983
1336
  function getCodexGlobalStatePath() {
984
- return join(getCodexHomeDir(), ".codex-global-state.json");
1337
+ return join2(getCodexHomeDir2(), ".codex-global-state.json");
985
1338
  }
986
1339
  var MAX_THREAD_TITLES = 500;
987
1340
  function normalizeThreadTitleCache(value) {
988
- const record = asRecord(value);
1341
+ const record = asRecord2(value);
989
1342
  if (!record) return { titles: {}, order: [] };
990
- const rawTitles = asRecord(record.titles);
1343
+ const rawTitles = asRecord2(record.titles);
991
1344
  const titles = {};
992
1345
  if (rawTitles) {
993
1346
  for (const [k, v] of Object.entries(rawTitles)) {
@@ -1013,8 +1366,8 @@ function removeFromThreadTitleCache(cache, id) {
1013
1366
  async function readThreadTitleCache() {
1014
1367
  const statePath = getCodexGlobalStatePath();
1015
1368
  try {
1016
- const raw = await readFile(statePath, "utf8");
1017
- const payload = asRecord(JSON.parse(raw)) ?? {};
1369
+ const raw = await readFile2(statePath, "utf8");
1370
+ const payload = asRecord2(JSON.parse(raw)) ?? {};
1018
1371
  return normalizeThreadTitleCache(payload["thread-titles"]);
1019
1372
  } catch {
1020
1373
  return { titles: {}, order: [] };
@@ -1024,21 +1377,21 @@ async function writeThreadTitleCache(cache) {
1024
1377
  const statePath = getCodexGlobalStatePath();
1025
1378
  let payload = {};
1026
1379
  try {
1027
- const raw = await readFile(statePath, "utf8");
1028
- payload = asRecord(JSON.parse(raw)) ?? {};
1380
+ const raw = await readFile2(statePath, "utf8");
1381
+ payload = asRecord2(JSON.parse(raw)) ?? {};
1029
1382
  } catch {
1030
1383
  payload = {};
1031
1384
  }
1032
1385
  payload["thread-titles"] = cache;
1033
- await writeFile(statePath, JSON.stringify(payload), "utf8");
1386
+ await writeFile2(statePath, JSON.stringify(payload), "utf8");
1034
1387
  }
1035
1388
  async function readWorkspaceRootsState() {
1036
1389
  const statePath = getCodexGlobalStatePath();
1037
1390
  let payload = {};
1038
1391
  try {
1039
- const raw = await readFile(statePath, "utf8");
1392
+ const raw = await readFile2(statePath, "utf8");
1040
1393
  const parsed = JSON.parse(raw);
1041
- payload = asRecord(parsed) ?? {};
1394
+ payload = asRecord2(parsed) ?? {};
1042
1395
  } catch {
1043
1396
  payload = {};
1044
1397
  }
@@ -1052,15 +1405,15 @@ async function writeWorkspaceRootsState(nextState) {
1052
1405
  const statePath = getCodexGlobalStatePath();
1053
1406
  let payload = {};
1054
1407
  try {
1055
- const raw = await readFile(statePath, "utf8");
1056
- payload = asRecord(JSON.parse(raw)) ?? {};
1408
+ const raw = await readFile2(statePath, "utf8");
1409
+ payload = asRecord2(JSON.parse(raw)) ?? {};
1057
1410
  } catch {
1058
1411
  payload = {};
1059
1412
  }
1060
1413
  payload["electron-saved-workspace-roots"] = normalizeStringArray(nextState.order);
1061
1414
  payload["electron-workspace-root-labels"] = normalizeStringRecord(nextState.labels);
1062
1415
  payload["active-workspace-roots"] = normalizeStringArray(nextState.active);
1063
- await writeFile(statePath, JSON.stringify(payload), "utf8");
1416
+ await writeFile2(statePath, JSON.stringify(payload), "utf8");
1064
1417
  }
1065
1418
  async function readJsonBody(req) {
1066
1419
  const raw = await readRawBody(req);
@@ -1098,7 +1451,7 @@ function handleFileUpload(req, res) {
1098
1451
  const contentType = req.headers["content-type"] ?? "";
1099
1452
  const boundaryMatch = contentType.match(/boundary=(.+)/i);
1100
1453
  if (!boundaryMatch) {
1101
- setJson(res, 400, { error: "Missing multipart boundary" });
1454
+ setJson2(res, 400, { error: "Missing multipart boundary" });
1102
1455
  return;
1103
1456
  }
1104
1457
  const boundary = boundaryMatch[1];
@@ -1128,21 +1481,21 @@ function handleFileUpload(req, res) {
1128
1481
  break;
1129
1482
  }
1130
1483
  if (!fileData) {
1131
- setJson(res, 400, { error: "No file in request" });
1484
+ setJson2(res, 400, { error: "No file in request" });
1132
1485
  return;
1133
1486
  }
1134
- const uploadDir = join(tmpdir(), "codex-web-uploads");
1135
- await mkdir(uploadDir, { recursive: true });
1136
- const destDir = await mkdtemp(join(uploadDir, "f-"));
1137
- const destPath = join(destDir, fileName);
1138
- await writeFile(destPath, fileData);
1139
- setJson(res, 200, { path: destPath });
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 });
1140
1493
  } catch (err) {
1141
- setJson(res, 500, { error: getErrorMessage(err, "Upload failed") });
1494
+ setJson2(res, 500, { error: getErrorMessage2(err, "Upload failed") });
1142
1495
  }
1143
1496
  });
1144
1497
  req.on("error", (err) => {
1145
- setJson(res, 500, { error: getErrorMessage(err, "Upload stream error") });
1498
+ setJson2(res, 500, { error: getErrorMessage2(err, "Upload stream error") });
1146
1499
  });
1147
1500
  }
1148
1501
  async function proxyTranscribe(body, contentType, authToken, accountId) {
@@ -1194,7 +1547,7 @@ var AppServerProcess = class {
1194
1547
  start() {
1195
1548
  if (this.process) return;
1196
1549
  this.stopping = false;
1197
- const proc = spawn("codex", this.appServerArgs, { stdio: ["pipe", "pipe", "pipe"] });
1550
+ const proc = spawn2("codex", this.appServerArgs, { stdio: ["pipe", "pipe", "pipe"] });
1198
1551
  this.process = proc;
1199
1552
  proc.stdout.setEncoding("utf8");
1200
1553
  proc.stdout.on("data", (chunk) => {
@@ -1288,7 +1641,7 @@ var AppServerProcess = class {
1288
1641
  }
1289
1642
  this.pendingServerRequests.delete(requestId);
1290
1643
  this.sendServerRequestReply(requestId, reply);
1291
- const requestParams = asRecord(pendingRequest.params);
1644
+ const requestParams = asRecord2(pendingRequest.params);
1292
1645
  const threadId = typeof requestParams?.threadId === "string" && requestParams.threadId.length > 0 ? requestParams.threadId : "";
1293
1646
  this.emitNotification({
1294
1647
  method: "server/request/resolved",
@@ -1357,7 +1710,7 @@ var AppServerProcess = class {
1357
1710
  }
1358
1711
  async respondToServerRequest(payload) {
1359
1712
  await this.ensureInitialized();
1360
- const body = asRecord(payload);
1713
+ const body = asRecord2(payload);
1361
1714
  if (!body) {
1362
1715
  throw new Error("Invalid response payload: expected object");
1363
1716
  }
@@ -1365,7 +1718,7 @@ var AppServerProcess = class {
1365
1718
  if (typeof id !== "number" || !Number.isInteger(id)) {
1366
1719
  throw new Error('Invalid response payload: "id" must be an integer');
1367
1720
  }
1368
- const rawError = asRecord(body.error);
1721
+ const rawError = asRecord2(body.error);
1369
1722
  if (rawError) {
1370
1723
  const message = typeof rawError.message === "string" && rawError.message.trim().length > 0 ? rawError.message.trim() : "Server request rejected by client";
1371
1724
  const code = typeof rawError.code === "number" && Number.isFinite(rawError.code) ? Math.trunc(rawError.code) : -32e3;
@@ -1420,7 +1773,7 @@ var MethodCatalog = class {
1420
1773
  }
1421
1774
  async runGenerateSchemaCommand(outDir) {
1422
1775
  await new Promise((resolve2, reject) => {
1423
- const process2 = spawn("codex", ["app-server", "generate-json-schema", "--out", outDir], {
1776
+ const process2 = spawn2("codex", ["app-server", "generate-json-schema", "--out", outDir], {
1424
1777
  stdio: ["ignore", "ignore", "pipe"]
1425
1778
  });
1426
1779
  let stderr = "";
@@ -1439,13 +1792,13 @@ var MethodCatalog = class {
1439
1792
  });
1440
1793
  }
1441
1794
  extractMethodsFromClientRequest(payload) {
1442
- const root = asRecord(payload);
1795
+ const root = asRecord2(payload);
1443
1796
  const oneOf = Array.isArray(root?.oneOf) ? root.oneOf : [];
1444
1797
  const methods = /* @__PURE__ */ new Set();
1445
1798
  for (const entry of oneOf) {
1446
- const row = asRecord(entry);
1447
- const properties = asRecord(row?.properties);
1448
- const methodDef = asRecord(properties?.method);
1799
+ const row = asRecord2(entry);
1800
+ const properties = asRecord2(row?.properties);
1801
+ const methodDef = asRecord2(properties?.method);
1449
1802
  const methodEnum = Array.isArray(methodDef?.enum) ? methodDef.enum : [];
1450
1803
  for (const item of methodEnum) {
1451
1804
  if (typeof item === "string" && item.length > 0) {
@@ -1456,13 +1809,13 @@ var MethodCatalog = class {
1456
1809
  return Array.from(methods).sort((a, b) => a.localeCompare(b));
1457
1810
  }
1458
1811
  extractMethodsFromServerNotification(payload) {
1459
- const root = asRecord(payload);
1812
+ const root = asRecord2(payload);
1460
1813
  const oneOf = Array.isArray(root?.oneOf) ? root.oneOf : [];
1461
1814
  const methods = /* @__PURE__ */ new Set();
1462
1815
  for (const entry of oneOf) {
1463
- const row = asRecord(entry);
1464
- const properties = asRecord(row?.properties);
1465
- const methodDef = asRecord(properties?.method);
1816
+ const row = asRecord2(entry);
1817
+ const properties = asRecord2(row?.properties);
1818
+ const methodDef = asRecord2(properties?.method);
1466
1819
  const methodEnum = Array.isArray(methodDef?.enum) ? methodDef.enum : [];
1467
1820
  for (const item of methodEnum) {
1468
1821
  if (typeof item === "string" && item.length > 0) {
@@ -1476,10 +1829,10 @@ var MethodCatalog = class {
1476
1829
  if (this.methodCache) {
1477
1830
  return this.methodCache;
1478
1831
  }
1479
- const outDir = await mkdtemp(join(tmpdir(), "codex-web-local-schema-"));
1832
+ const outDir = await mkdtemp2(join2(tmpdir2(), "codex-web-local-schema-"));
1480
1833
  await this.runGenerateSchemaCommand(outDir);
1481
- const clientRequestPath = join(outDir, "ClientRequest.json");
1482
- const raw = await readFile(clientRequestPath, "utf8");
1834
+ const clientRequestPath = join2(outDir, "ClientRequest.json");
1835
+ const raw = await readFile2(clientRequestPath, "utf8");
1483
1836
  const parsed = JSON.parse(raw);
1484
1837
  const methods = this.extractMethodsFromClientRequest(parsed);
1485
1838
  this.methodCache = methods;
@@ -1489,10 +1842,10 @@ var MethodCatalog = class {
1489
1842
  if (this.notificationCache) {
1490
1843
  return this.notificationCache;
1491
1844
  }
1492
- const outDir = await mkdtemp(join(tmpdir(), "codex-web-local-schema-"));
1845
+ const outDir = await mkdtemp2(join2(tmpdir2(), "codex-web-local-schema-"));
1493
1846
  await this.runGenerateSchemaCommand(outDir);
1494
- const serverNotificationPath = join(outDir, "ServerNotification.json");
1495
- const raw = await readFile(serverNotificationPath, "utf8");
1847
+ const serverNotificationPath = join2(outDir, "ServerNotification.json");
1848
+ const raw = await readFile2(serverNotificationPath, "utf8");
1496
1849
  const parsed = JSON.parse(raw);
1497
1850
  const methods = this.extractMethodsFromServerNotification(parsed);
1498
1851
  this.notificationCache = methods;
@@ -1515,7 +1868,7 @@ async function loadAllThreadsForSearch(appServer) {
1515
1868
  const threads = [];
1516
1869
  let cursor = null;
1517
1870
  do {
1518
- const response = asRecord(await appServer.rpc("thread/list", {
1871
+ const response = asRecord2(await appServer.rpc("thread/list", {
1519
1872
  archived: false,
1520
1873
  limit: 100,
1521
1874
  sortKey: "updated_at",
@@ -1523,7 +1876,7 @@ async function loadAllThreadsForSearch(appServer) {
1523
1876
  }));
1524
1877
  const data = Array.isArray(response?.data) ? response.data : [];
1525
1878
  for (const row of data) {
1526
- const record = asRecord(row);
1879
+ const record = asRecord2(row);
1527
1880
  const id = typeof record?.id === "string" ? record.id : "";
1528
1881
  if (!id) continue;
1529
1882
  const title = typeof record?.name === "string" && record.name.trim().length > 0 ? record.name.trim() : typeof record?.preview === "string" && record.preview.trim().length > 0 ? record.preview.trim() : "Untitled thread";
@@ -1595,25 +1948,28 @@ function createCodexBridgeMiddleware() {
1595
1948
  return;
1596
1949
  }
1597
1950
  const url = new URL(req.url, "http://localhost");
1951
+ if (await handleSkillsRoutes(req, res, url, { appServer, readJsonBody })) {
1952
+ return;
1953
+ }
1598
1954
  if (req.method === "POST" && url.pathname === "/codex-api/upload-file") {
1599
1955
  handleFileUpload(req, res);
1600
1956
  return;
1601
1957
  }
1602
1958
  if (req.method === "POST" && url.pathname === "/codex-api/rpc") {
1603
1959
  const payload = await readJsonBody(req);
1604
- const body = asRecord(payload);
1960
+ const body = asRecord2(payload);
1605
1961
  if (!body || typeof body.method !== "string" || body.method.length === 0) {
1606
- setJson(res, 400, { error: "Invalid body: expected { method, params? }" });
1962
+ setJson2(res, 400, { error: "Invalid body: expected { method, params? }" });
1607
1963
  return;
1608
1964
  }
1609
1965
  const result = await appServer.rpc(body.method, body.params ?? null);
1610
- setJson(res, 200, { result });
1966
+ setJson2(res, 200, { result });
1611
1967
  return;
1612
1968
  }
1613
1969
  if (req.method === "POST" && url.pathname === "/codex-api/transcribe") {
1614
1970
  const auth = await readCodexAuth();
1615
1971
  if (!auth) {
1616
- setJson(res, 401, { error: "No auth token available for transcription" });
1972
+ setJson2(res, 401, { error: "No auth token available for transcription" });
1617
1973
  return;
1618
1974
  }
1619
1975
  const rawBody = await readRawBody(req);
@@ -1627,48 +1983,48 @@ function createCodexBridgeMiddleware() {
1627
1983
  if (req.method === "POST" && url.pathname === "/codex-api/server-requests/respond") {
1628
1984
  const payload = await readJsonBody(req);
1629
1985
  await appServer.respondToServerRequest(payload);
1630
- setJson(res, 200, { ok: true });
1986
+ setJson2(res, 200, { ok: true });
1631
1987
  return;
1632
1988
  }
1633
1989
  if (req.method === "GET" && url.pathname === "/codex-api/server-requests/pending") {
1634
- setJson(res, 200, { data: appServer.listPendingServerRequests() });
1990
+ setJson2(res, 200, { data: appServer.listPendingServerRequests() });
1635
1991
  return;
1636
1992
  }
1637
1993
  if (req.method === "GET" && url.pathname === "/codex-api/meta/methods") {
1638
1994
  const methods = await methodCatalog.listMethods();
1639
- setJson(res, 200, { data: methods });
1995
+ setJson2(res, 200, { data: methods });
1640
1996
  return;
1641
1997
  }
1642
1998
  if (req.method === "GET" && url.pathname === "/codex-api/meta/notifications") {
1643
1999
  const methods = await methodCatalog.listNotificationMethods();
1644
- setJson(res, 200, { data: methods });
2000
+ setJson2(res, 200, { data: methods });
1645
2001
  return;
1646
2002
  }
1647
2003
  if (req.method === "GET" && url.pathname === "/codex-api/workspace-roots-state") {
1648
2004
  const state = await readWorkspaceRootsState();
1649
- setJson(res, 200, { data: state });
2005
+ setJson2(res, 200, { data: state });
1650
2006
  return;
1651
2007
  }
1652
2008
  if (req.method === "GET" && url.pathname === "/codex-api/home-directory") {
1653
- setJson(res, 200, { data: { path: homedir() } });
2009
+ setJson2(res, 200, { data: { path: homedir2() } });
1654
2010
  return;
1655
2011
  }
1656
2012
  if (req.method === "POST" && url.pathname === "/codex-api/worktree/create") {
1657
- const payload = asRecord(await readJsonBody(req));
2013
+ const payload = asRecord2(await readJsonBody(req));
1658
2014
  const rawSourceCwd = typeof payload?.sourceCwd === "string" ? payload.sourceCwd.trim() : "";
1659
2015
  if (!rawSourceCwd) {
1660
- setJson(res, 400, { error: "Missing sourceCwd" });
2016
+ setJson2(res, 400, { error: "Missing sourceCwd" });
1661
2017
  return;
1662
2018
  }
1663
2019
  const sourceCwd = isAbsolute(rawSourceCwd) ? rawSourceCwd : resolve(rawSourceCwd);
1664
2020
  try {
1665
- const sourceInfo = await stat(sourceCwd);
2021
+ const sourceInfo = await stat2(sourceCwd);
1666
2022
  if (!sourceInfo.isDirectory()) {
1667
- setJson(res, 400, { error: "sourceCwd is not a directory" });
2023
+ setJson2(res, 400, { error: "sourceCwd is not a directory" });
1668
2024
  return;
1669
2025
  }
1670
2026
  } catch {
1671
- setJson(res, 404, { error: "sourceCwd does not exist" });
2027
+ setJson2(res, 404, { error: "sourceCwd does not exist" });
1672
2028
  return;
1673
2029
  }
1674
2030
  try {
@@ -1677,25 +2033,25 @@ function createCodexBridgeMiddleware() {
1677
2033
  gitRoot = await runCommandCapture("git", ["rev-parse", "--show-toplevel"], { cwd: sourceCwd });
1678
2034
  } catch (error) {
1679
2035
  if (!isNotGitRepositoryError(error)) throw error;
1680
- await runCommand("git", ["init"], { cwd: sourceCwd });
2036
+ await runCommand2("git", ["init"], { cwd: sourceCwd });
1681
2037
  gitRoot = await runCommandCapture("git", ["rev-parse", "--show-toplevel"], { cwd: sourceCwd });
1682
2038
  }
1683
2039
  const repoName = basename(gitRoot) || "repo";
1684
- const worktreesRoot = join(getCodexHomeDir(), "worktrees");
1685
- await mkdir(worktreesRoot, { recursive: true });
2040
+ const worktreesRoot = join2(getCodexHomeDir2(), "worktrees");
2041
+ await mkdir2(worktreesRoot, { recursive: true });
1686
2042
  let worktreeId = "";
1687
2043
  let worktreeParent = "";
1688
2044
  let worktreeCwd = "";
1689
2045
  for (let attempt = 0; attempt < 12; attempt += 1) {
1690
2046
  const candidate = randomBytes(2).toString("hex");
1691
- const parent = join(worktreesRoot, candidate);
2047
+ const parent = join2(worktreesRoot, candidate);
1692
2048
  try {
1693
- await stat(parent);
2049
+ await stat2(parent);
1694
2050
  continue;
1695
2051
  } catch {
1696
2052
  worktreeId = candidate;
1697
2053
  worktreeParent = parent;
1698
- worktreeCwd = join(parent, repoName);
2054
+ worktreeCwd = join2(parent, repoName);
1699
2055
  break;
1700
2056
  }
1701
2057
  }
@@ -1703,15 +2059,15 @@ function createCodexBridgeMiddleware() {
1703
2059
  throw new Error("Failed to allocate a unique worktree id");
1704
2060
  }
1705
2061
  const branch = `codex/${worktreeId}`;
1706
- await mkdir(worktreeParent, { recursive: true });
2062
+ await mkdir2(worktreeParent, { recursive: true });
1707
2063
  try {
1708
- await runCommand("git", ["worktree", "add", "-b", branch, worktreeCwd, "HEAD"], { cwd: gitRoot });
2064
+ await runCommand2("git", ["worktree", "add", "-b", branch, worktreeCwd, "HEAD"], { cwd: gitRoot });
1709
2065
  } catch (error) {
1710
2066
  if (!isMissingHeadError(error)) throw error;
1711
2067
  await ensureRepoHasInitialCommit(gitRoot);
1712
- await runCommand("git", ["worktree", "add", "-b", branch, worktreeCwd, "HEAD"], { cwd: gitRoot });
2068
+ await runCommand2("git", ["worktree", "add", "-b", branch, worktreeCwd, "HEAD"], { cwd: gitRoot });
1713
2069
  }
1714
- setJson(res, 200, {
2070
+ setJson2(res, 200, {
1715
2071
  data: {
1716
2072
  cwd: worktreeCwd,
1717
2073
  branch,
@@ -1719,15 +2075,15 @@ function createCodexBridgeMiddleware() {
1719
2075
  }
1720
2076
  });
1721
2077
  } catch (error) {
1722
- setJson(res, 500, { error: getErrorMessage(error, "Failed to create worktree") });
2078
+ setJson2(res, 500, { error: getErrorMessage2(error, "Failed to create worktree") });
1723
2079
  }
1724
2080
  return;
1725
2081
  }
1726
2082
  if (req.method === "PUT" && url.pathname === "/codex-api/workspace-roots-state") {
1727
2083
  const payload = await readJsonBody(req);
1728
- const record = asRecord(payload);
2084
+ const record = asRecord2(payload);
1729
2085
  if (!record) {
1730
- setJson(res, 400, { error: "Invalid body: expected object" });
2086
+ setJson2(res, 400, { error: "Invalid body: expected object" });
1731
2087
  return;
1732
2088
  }
1733
2089
  const nextState = {
@@ -1736,33 +2092,33 @@ function createCodexBridgeMiddleware() {
1736
2092
  active: normalizeStringArray(record.active)
1737
2093
  };
1738
2094
  await writeWorkspaceRootsState(nextState);
1739
- setJson(res, 200, { ok: true });
2095
+ setJson2(res, 200, { ok: true });
1740
2096
  return;
1741
2097
  }
1742
2098
  if (req.method === "POST" && url.pathname === "/codex-api/project-root") {
1743
- const payload = asRecord(await readJsonBody(req));
2099
+ const payload = asRecord2(await readJsonBody(req));
1744
2100
  const rawPath = typeof payload?.path === "string" ? payload.path.trim() : "";
1745
2101
  const createIfMissing = payload?.createIfMissing === true;
1746
2102
  const label = typeof payload?.label === "string" ? payload.label : "";
1747
2103
  if (!rawPath) {
1748
- setJson(res, 400, { error: "Missing path" });
2104
+ setJson2(res, 400, { error: "Missing path" });
1749
2105
  return;
1750
2106
  }
1751
2107
  const normalizedPath = isAbsolute(rawPath) ? rawPath : resolve(rawPath);
1752
2108
  let pathExists = true;
1753
2109
  try {
1754
- const info = await stat(normalizedPath);
2110
+ const info = await stat2(normalizedPath);
1755
2111
  if (!info.isDirectory()) {
1756
- setJson(res, 400, { error: "Path exists but is not a directory" });
2112
+ setJson2(res, 400, { error: "Path exists but is not a directory" });
1757
2113
  return;
1758
2114
  }
1759
2115
  } catch {
1760
2116
  pathExists = false;
1761
2117
  }
1762
2118
  if (!pathExists && createIfMissing) {
1763
- await mkdir(normalizedPath, { recursive: true });
2119
+ await mkdir2(normalizedPath, { recursive: true });
1764
2120
  } else if (!pathExists) {
1765
- setJson(res, 404, { error: "Directory does not exist" });
2121
+ setJson2(res, 404, { error: "Directory does not exist" });
1766
2122
  return;
1767
2123
  }
1768
2124
  const existingState = await readWorkspaceRootsState();
@@ -1777,408 +2133,103 @@ function createCodexBridgeMiddleware() {
1777
2133
  labels: nextLabels,
1778
2134
  active: nextActive
1779
2135
  });
1780
- setJson(res, 200, { data: { path: normalizedPath } });
2136
+ setJson2(res, 200, { data: { path: normalizedPath } });
1781
2137
  return;
1782
2138
  }
1783
2139
  if (req.method === "GET" && url.pathname === "/codex-api/project-root-suggestion") {
1784
2140
  const basePath = url.searchParams.get("basePath")?.trim() ?? "";
1785
2141
  if (!basePath) {
1786
- setJson(res, 400, { error: "Missing basePath" });
2142
+ setJson2(res, 400, { error: "Missing basePath" });
1787
2143
  return;
1788
2144
  }
1789
2145
  const normalizedBasePath = isAbsolute(basePath) ? basePath : resolve(basePath);
1790
2146
  try {
1791
- const baseInfo = await stat(normalizedBasePath);
2147
+ const baseInfo = await stat2(normalizedBasePath);
1792
2148
  if (!baseInfo.isDirectory()) {
1793
- setJson(res, 400, { error: "basePath is not a directory" });
2149
+ setJson2(res, 400, { error: "basePath is not a directory" });
1794
2150
  return;
1795
2151
  }
1796
2152
  } catch {
1797
- setJson(res, 404, { error: "basePath does not exist" });
2153
+ setJson2(res, 404, { error: "basePath does not exist" });
1798
2154
  return;
1799
2155
  }
1800
2156
  let index = 1;
1801
2157
  while (index < 1e5) {
1802
2158
  const candidateName = `New Project (${String(index)})`;
1803
- const candidatePath = join(normalizedBasePath, candidateName);
2159
+ const candidatePath = join2(normalizedBasePath, candidateName);
1804
2160
  try {
1805
- await stat(candidatePath);
2161
+ await stat2(candidatePath);
1806
2162
  index += 1;
1807
2163
  continue;
1808
2164
  } catch {
1809
- setJson(res, 200, { data: { name: candidateName, path: candidatePath } });
2165
+ setJson2(res, 200, { data: { name: candidateName, path: candidatePath } });
1810
2166
  return;
1811
2167
  }
1812
2168
  }
1813
- setJson(res, 500, { error: "Failed to compute project name suggestion" });
2169
+ setJson2(res, 500, { error: "Failed to compute project name suggestion" });
1814
2170
  return;
1815
2171
  }
1816
2172
  if (req.method === "POST" && url.pathname === "/codex-api/composer-file-search") {
1817
- const payload = asRecord(await readJsonBody(req));
2173
+ const payload = asRecord2(await readJsonBody(req));
1818
2174
  const rawCwd = typeof payload?.cwd === "string" ? payload.cwd.trim() : "";
1819
2175
  const query = typeof payload?.query === "string" ? payload.query.trim() : "";
1820
2176
  const limitRaw = typeof payload?.limit === "number" ? payload.limit : 20;
1821
2177
  const limit = Math.max(1, Math.min(100, Math.floor(limitRaw)));
1822
2178
  if (!rawCwd) {
1823
- setJson(res, 400, { error: "Missing cwd" });
2179
+ setJson2(res, 400, { error: "Missing cwd" });
1824
2180
  return;
1825
2181
  }
1826
2182
  const cwd = isAbsolute(rawCwd) ? rawCwd : resolve(rawCwd);
1827
2183
  try {
1828
- const info = await stat(cwd);
2184
+ const info = await stat2(cwd);
1829
2185
  if (!info.isDirectory()) {
1830
- setJson(res, 400, { error: "cwd is not a directory" });
2186
+ setJson2(res, 400, { error: "cwd is not a directory" });
1831
2187
  return;
1832
2188
  }
1833
2189
  } catch {
1834
- setJson(res, 404, { error: "cwd does not exist" });
2190
+ setJson2(res, 404, { error: "cwd does not exist" });
1835
2191
  return;
1836
2192
  }
1837
2193
  try {
1838
2194
  const files = await listFilesWithRipgrep(cwd);
1839
2195
  const scored = files.map((path) => ({ path, score: scoreFileCandidate(path, query) })).filter((row) => query.length === 0 || row.score < 10).sort((a, b) => a.score - b.score || a.path.localeCompare(b.path)).slice(0, limit).map((row) => ({ path: row.path }));
1840
- setJson(res, 200, { data: scored });
2196
+ setJson2(res, 200, { data: scored });
1841
2197
  } catch (error) {
1842
- setJson(res, 500, { error: getErrorMessage(error, "Failed to search files") });
2198
+ setJson2(res, 500, { error: getErrorMessage2(error, "Failed to search files") });
1843
2199
  }
1844
2200
  return;
1845
2201
  }
1846
2202
  if (req.method === "GET" && url.pathname === "/codex-api/thread-titles") {
1847
2203
  const cache = await readThreadTitleCache();
1848
- setJson(res, 200, { data: cache });
2204
+ setJson2(res, 200, { data: cache });
1849
2205
  return;
1850
2206
  }
1851
2207
  if (req.method === "POST" && url.pathname === "/codex-api/thread-search") {
1852
- const payload = asRecord(await readJsonBody(req));
2208
+ const payload = asRecord2(await readJsonBody(req));
1853
2209
  const query = typeof payload?.query === "string" ? payload.query.trim() : "";
1854
2210
  const limitRaw = typeof payload?.limit === "number" ? payload.limit : 200;
1855
2211
  const limit = Math.max(1, Math.min(1e3, Math.floor(limitRaw)));
1856
2212
  if (!query) {
1857
- setJson(res, 200, { data: { threadIds: [], indexedThreadCount: 0 } });
2213
+ setJson2(res, 200, { data: { threadIds: [], indexedThreadCount: 0 } });
1858
2214
  return;
1859
2215
  }
1860
2216
  const index = await getThreadSearchIndex();
1861
2217
  const matchedIds = Array.from(index.docsById.entries()).filter(([, doc]) => isExactPhraseMatch(query, doc)).slice(0, limit).map(([id]) => id);
1862
- setJson(res, 200, { data: { threadIds: matchedIds, indexedThreadCount: index.docsById.size } });
2218
+ setJson2(res, 200, { data: { threadIds: matchedIds, indexedThreadCount: index.docsById.size } });
1863
2219
  return;
1864
2220
  }
1865
2221
  if (req.method === "PUT" && url.pathname === "/codex-api/thread-titles") {
1866
- const payload = asRecord(await readJsonBody(req));
2222
+ const payload = asRecord2(await readJsonBody(req));
1867
2223
  const id = typeof payload?.id === "string" ? payload.id : "";
1868
2224
  const title = typeof payload?.title === "string" ? payload.title : "";
1869
2225
  if (!id) {
1870
- setJson(res, 400, { error: "Missing id" });
2226
+ setJson2(res, 400, { error: "Missing id" });
1871
2227
  return;
1872
2228
  }
1873
2229
  const cache = await readThreadTitleCache();
1874
2230
  const next2 = title ? updateThreadTitleCache(cache, id, title) : removeFromThreadTitleCache(cache, id);
1875
2231
  await writeThreadTitleCache(next2);
1876
- setJson(res, 200, { ok: true });
1877
- return;
1878
- }
1879
- if (req.method === "GET" && url.pathname === "/codex-api/skills-hub") {
1880
- try {
1881
- const q = url.searchParams.get("q") || "";
1882
- const limit = Math.min(Math.max(parseInt(url.searchParams.get("limit") || "50", 10) || 50, 1), 200);
1883
- const sort = url.searchParams.get("sort") || "date";
1884
- const allEntries = await fetchSkillsTree();
1885
- const installedMap = await scanInstalledSkillsFromDisk();
1886
- try {
1887
- const result = await appServer.rpc("skills/list", {});
1888
- for (const entry of result.data ?? []) {
1889
- for (const skill of entry.skills ?? []) {
1890
- if (skill.name) {
1891
- installedMap.set(skill.name, { name: skill.name, path: skill.path ?? "", enabled: skill.enabled !== false });
1892
- }
1893
- }
1894
- }
1895
- } catch {
1896
- }
1897
- const installedHubEntries = allEntries.filter((e) => installedMap.has(e.name));
1898
- await fetchMetaBatch(installedHubEntries);
1899
- const installed = [];
1900
- for (const [, info] of installedMap) {
1901
- const hubEntry = allEntries.find((e) => e.name === info.name);
1902
- const base = hubEntry ? buildHubEntry(hubEntry) : {
1903
- name: info.name,
1904
- owner: "local",
1905
- description: "",
1906
- displayName: "",
1907
- publishedAt: 0,
1908
- avatarUrl: "",
1909
- url: "",
1910
- installed: false
1911
- };
1912
- installed.push({ ...base, installed: true, path: info.path, enabled: info.enabled });
1913
- }
1914
- const results = await searchSkillsHub(allEntries, q, limit, sort, installedMap);
1915
- setJson(res, 200, { data: results, installed, total: allEntries.length });
1916
- } catch (error) {
1917
- setJson(res, 502, { error: getErrorMessage(error, "Failed to fetch skills hub") });
1918
- }
1919
- return;
1920
- }
1921
- if (req.method === "GET" && url.pathname === "/codex-api/skills-sync/status") {
1922
- const state = await readSkillsSyncState();
1923
- setJson(res, 200, {
1924
- data: {
1925
- loggedIn: Boolean(state.githubToken),
1926
- githubUsername: state.githubUsername ?? "",
1927
- repoOwner: state.repoOwner ?? "",
1928
- repoName: state.repoName ?? "",
1929
- configured: Boolean(state.githubToken && state.repoOwner && state.repoName),
1930
- startup: {
1931
- inProgress: startupSyncStatus.inProgress,
1932
- mode: startupSyncStatus.mode,
1933
- branch: startupSyncStatus.branch,
1934
- lastAction: startupSyncStatus.lastAction,
1935
- lastRunAtIso: startupSyncStatus.lastRunAtIso,
1936
- lastSuccessAtIso: startupSyncStatus.lastSuccessAtIso,
1937
- lastError: startupSyncStatus.lastError
1938
- }
1939
- }
1940
- });
1941
- return;
1942
- }
1943
- if (req.method === "POST" && url.pathname === "/codex-api/skills-sync/github/start-login") {
1944
- try {
1945
- const started = await startGithubDeviceLogin();
1946
- setJson(res, 200, { data: started });
1947
- } catch (error) {
1948
- setJson(res, 502, { error: getErrorMessage(error, "Failed to start GitHub login") });
1949
- }
1950
- return;
1951
- }
1952
- if (req.method === "POST" && url.pathname === "/codex-api/skills-sync/github/token-login") {
1953
- try {
1954
- const payload = asRecord(await readJsonBody(req));
1955
- const token = typeof payload?.token === "string" ? payload.token.trim() : "";
1956
- if (!token) {
1957
- setJson(res, 400, { error: "Missing GitHub token" });
1958
- return;
1959
- }
1960
- const username = await resolveGithubUsername(token);
1961
- await finalizeGithubLoginAndSync(token, username, appServer);
1962
- setJson(res, 200, { ok: true, data: { githubUsername: username } });
1963
- } catch (error) {
1964
- setJson(res, 502, { error: getErrorMessage(error, "Failed to login with GitHub token") });
1965
- }
1966
- return;
1967
- }
1968
- if (req.method === "POST" && url.pathname === "/codex-api/skills-sync/github/logout") {
1969
- try {
1970
- const state = await readSkillsSyncState();
1971
- await writeSkillsSyncState({
1972
- ...state,
1973
- githubToken: void 0,
1974
- githubUsername: void 0,
1975
- repoOwner: void 0,
1976
- repoName: void 0
1977
- });
1978
- setJson(res, 200, { ok: true });
1979
- } catch (error) {
1980
- setJson(res, 500, { error: getErrorMessage(error, "Failed to logout GitHub") });
1981
- }
1982
- return;
1983
- }
1984
- if (req.method === "POST" && url.pathname === "/codex-api/skills-sync/github/complete-login") {
1985
- try {
1986
- const payload = asRecord(await readJsonBody(req));
1987
- const deviceCode = typeof payload?.deviceCode === "string" ? payload.deviceCode : "";
1988
- if (!deviceCode) {
1989
- setJson(res, 400, { error: "Missing deviceCode" });
1990
- return;
1991
- }
1992
- const result = await completeGithubDeviceLogin(deviceCode);
1993
- if (!result.token) {
1994
- setJson(res, 200, { ok: false, pending: result.error === "authorization_pending", error: result.error || "login_failed" });
1995
- return;
1996
- }
1997
- const token = result.token;
1998
- const username = await resolveGithubUsername(token);
1999
- await finalizeGithubLoginAndSync(token, username, appServer);
2000
- setJson(res, 200, { ok: true, data: { githubUsername: username } });
2001
- } catch (error) {
2002
- setJson(res, 502, { error: getErrorMessage(error, "Failed to complete GitHub login") });
2003
- }
2004
- return;
2005
- }
2006
- if (req.method === "POST" && url.pathname === "/codex-api/skills-sync/push") {
2007
- try {
2008
- const state = await readSkillsSyncState();
2009
- if (!state.githubToken || !state.repoOwner || !state.repoName) {
2010
- setJson(res, 400, { error: "Skills sync is not configured yet" });
2011
- return;
2012
- }
2013
- if (isUpstreamSkillsRepo(state.repoOwner, state.repoName)) {
2014
- setJson(res, 400, { error: "Refusing to push to upstream repository" });
2015
- return;
2016
- }
2017
- const local = await collectLocalSyncedSkills(appServer);
2018
- const installedMap = await scanInstalledSkillsFromDisk();
2019
- await writeRemoteSkillsManifest(state.githubToken, state.repoOwner, state.repoName, local);
2020
- await syncInstalledSkillsFolderToRepo(state.githubToken, state.repoOwner, state.repoName, installedMap);
2021
- setJson(res, 200, { ok: true, data: { synced: local.length } });
2022
- } catch (error) {
2023
- setJson(res, 502, { error: getErrorMessage(error, "Failed to push synced skills") });
2024
- }
2025
- return;
2026
- }
2027
- if (req.method === "POST" && url.pathname === "/codex-api/skills-sync/pull") {
2028
- try {
2029
- const state = await readSkillsSyncState();
2030
- if (!state.githubToken || !state.repoOwner || !state.repoName) {
2031
- const localDir2 = await detectUserSkillsDir(appServer);
2032
- await bootstrapSkillsFromUpstreamIntoLocal(localDir2);
2033
- try {
2034
- await appServer.rpc("skills/list", { forceReload: true });
2035
- } catch {
2036
- }
2037
- setJson(res, 200, { ok: true, data: { synced: 0, source: "upstream" } });
2038
- return;
2039
- }
2040
- const remote = await readRemoteSkillsManifest(state.githubToken, state.repoOwner, state.repoName);
2041
- const tree = await fetchSkillsTree();
2042
- const uniqueOwnerByName = /* @__PURE__ */ new Map();
2043
- const ambiguousNames = /* @__PURE__ */ new Set();
2044
- for (const entry of tree) {
2045
- if (ambiguousNames.has(entry.name)) continue;
2046
- const existingOwner = uniqueOwnerByName.get(entry.name);
2047
- if (!existingOwner) {
2048
- uniqueOwnerByName.set(entry.name, entry.owner);
2049
- continue;
2050
- }
2051
- if (existingOwner !== entry.owner) {
2052
- uniqueOwnerByName.delete(entry.name);
2053
- ambiguousNames.add(entry.name);
2054
- }
2055
- }
2056
- const localDir = await detectUserSkillsDir(appServer);
2057
- await pullInstalledSkillsFolderFromRepo(state.githubToken, state.repoOwner, state.repoName, localDir);
2058
- const installerScript = "/Users/igor/.cursor/skills/.system/skill-installer/scripts/install-skill-from-github.py";
2059
- const localSkills = await scanInstalledSkillsFromDisk();
2060
- for (const skill of remote) {
2061
- const owner = skill.owner || uniqueOwnerByName.get(skill.name) || "";
2062
- if (!owner) {
2063
- continue;
2064
- }
2065
- if (!localSkills.has(skill.name)) {
2066
- await runCommand("python3", [
2067
- installerScript,
2068
- "--repo",
2069
- `${HUB_SKILLS_OWNER}/${HUB_SKILLS_REPO}`,
2070
- "--path",
2071
- `skills/${owner}/${skill.name}`,
2072
- "--dest",
2073
- localDir,
2074
- "--method",
2075
- "git"
2076
- ]);
2077
- }
2078
- const skillPath = join(localDir, skill.name);
2079
- await appServer.rpc("skills/config/write", { path: skillPath, enabled: skill.enabled });
2080
- }
2081
- const remoteNames = new Set(remote.map((row) => row.name));
2082
- for (const [name, localInfo] of localSkills.entries()) {
2083
- if (!remoteNames.has(name)) {
2084
- await rm(localInfo.path.replace(/\/SKILL\.md$/, ""), { recursive: true, force: true });
2085
- }
2086
- }
2087
- const nextOwners = {};
2088
- for (const item of remote) {
2089
- const owner = item.owner || uniqueOwnerByName.get(item.name) || "";
2090
- if (owner) nextOwners[item.name] = owner;
2091
- }
2092
- await writeSkillsSyncState({ ...state, installedOwners: nextOwners });
2093
- try {
2094
- await appServer.rpc("skills/list", { forceReload: true });
2095
- } catch {
2096
- }
2097
- setJson(res, 200, { ok: true, data: { synced: remote.length } });
2098
- } catch (error) {
2099
- setJson(res, 502, { error: getErrorMessage(error, "Failed to pull synced skills") });
2100
- }
2101
- return;
2102
- }
2103
- if (req.method === "GET" && url.pathname === "/codex-api/skills-hub/readme") {
2104
- try {
2105
- const owner = url.searchParams.get("owner") || "";
2106
- const name = url.searchParams.get("name") || "";
2107
- if (!owner || !name) {
2108
- setJson(res, 400, { error: "Missing owner or name" });
2109
- return;
2110
- }
2111
- const rawUrl = `https://raw.githubusercontent.com/${HUB_SKILLS_OWNER}/${HUB_SKILLS_REPO}/main/skills/${owner}/${name}/SKILL.md`;
2112
- const resp = await fetch(rawUrl);
2113
- if (!resp.ok) throw new Error(`Failed to fetch SKILL.md: ${resp.status}`);
2114
- const content = await resp.text();
2115
- setJson(res, 200, { content });
2116
- } catch (error) {
2117
- setJson(res, 502, { error: getErrorMessage(error, "Failed to fetch SKILL.md") });
2118
- }
2119
- return;
2120
- }
2121
- if (req.method === "POST" && url.pathname === "/codex-api/skills-hub/install") {
2122
- try {
2123
- const payload = asRecord(await readJsonBody(req));
2124
- const owner = typeof payload?.owner === "string" ? payload.owner : "";
2125
- const name = typeof payload?.name === "string" ? payload.name : "";
2126
- if (!owner || !name) {
2127
- setJson(res, 400, { error: "Missing owner or name" });
2128
- return;
2129
- }
2130
- const installerScript = "/Users/igor/.cursor/skills/.system/skill-installer/scripts/install-skill-from-github.py";
2131
- const installDest = await detectUserSkillsDir(appServer);
2132
- const skillPathInRepo = `skills/${owner}/${name}`;
2133
- await runCommand("python3", [
2134
- installerScript,
2135
- "--repo",
2136
- `${HUB_SKILLS_OWNER}/${HUB_SKILLS_REPO}`,
2137
- "--path",
2138
- skillPathInRepo,
2139
- "--dest",
2140
- installDest,
2141
- "--method",
2142
- "git"
2143
- ]);
2144
- const skillDir = join(installDest, name);
2145
- await ensureInstalledSkillIsValid(appServer, skillDir);
2146
- const syncState = await readSkillsSyncState();
2147
- const nextOwners = { ...syncState.installedOwners ?? {}, [name]: owner };
2148
- await writeSkillsSyncState({ ...syncState, installedOwners: nextOwners });
2149
- await autoPushSyncedSkills(appServer);
2150
- setJson(res, 200, { ok: true, path: skillDir });
2151
- } catch (error) {
2152
- setJson(res, 502, { error: getErrorMessage(error, "Failed to install skill") });
2153
- }
2154
- return;
2155
- }
2156
- if (req.method === "POST" && url.pathname === "/codex-api/skills-hub/uninstall") {
2157
- try {
2158
- const payload = asRecord(await readJsonBody(req));
2159
- const name = typeof payload?.name === "string" ? payload.name : "";
2160
- const path = typeof payload?.path === "string" ? payload.path : "";
2161
- const target = path || (name ? join(getSkillsInstallDir(), name) : "");
2162
- if (!target) {
2163
- setJson(res, 400, { error: "Missing name or path" });
2164
- return;
2165
- }
2166
- await rm(target, { recursive: true, force: true });
2167
- if (name) {
2168
- const syncState = await readSkillsSyncState();
2169
- const nextOwners = { ...syncState.installedOwners ?? {} };
2170
- delete nextOwners[name];
2171
- await writeSkillsSyncState({ ...syncState, installedOwners: nextOwners });
2172
- }
2173
- await autoPushSyncedSkills(appServer);
2174
- try {
2175
- await appServer.rpc("skills/list", { forceReload: true });
2176
- } catch {
2177
- }
2178
- setJson(res, 200, { ok: true, deletedPath: target });
2179
- } catch (error) {
2180
- setJson(res, 502, { error: getErrorMessage(error, "Failed to uninstall skill") });
2181
- }
2232
+ setJson2(res, 200, { ok: true });
2182
2233
  return;
2183
2234
  }
2184
2235
  if (req.method === "GET" && url.pathname === "/codex-api/events") {
@@ -2213,8 +2264,8 @@ data: ${JSON.stringify({ ok: true })}
2213
2264
  }
2214
2265
  next();
2215
2266
  } catch (error) {
2216
- const message = getErrorMessage(error, "Unknown bridge error");
2217
- setJson(res, 502, { error: message });
2267
+ const message = getErrorMessage2(error, "Unknown bridge error");
2268
+ setJson2(res, 502, { error: message });
2218
2269
  }
2219
2270
  };
2220
2271
  middleware.dispose = () => {
@@ -2351,8 +2402,8 @@ function createAuthSession(password) {
2351
2402
  }
2352
2403
 
2353
2404
  // src/server/localBrowseUi.ts
2354
- import { dirname, extname, join as join2 } from "path";
2355
- import { open, readFile as readFile2, readdir as readdir2, stat as stat2 } from "fs/promises";
2405
+ import { dirname, extname, join as join3 } from "path";
2406
+ import { open, readFile as readFile3, readdir as readdir3, stat as stat3 } from "fs/promises";
2356
2407
  var TEXT_EDITABLE_EXTENSIONS = /* @__PURE__ */ new Set([
2357
2408
  ".txt",
2358
2409
  ".md",
@@ -2467,7 +2518,7 @@ async function probeFileIsText(localPath) {
2467
2518
  async function isTextEditableFile(localPath) {
2468
2519
  if (isTextEditablePath(localPath)) return true;
2469
2520
  try {
2470
- const fileStat = await stat2(localPath);
2521
+ const fileStat = await stat3(localPath);
2471
2522
  if (!fileStat.isFile()) return false;
2472
2523
  return await probeFileIsText(localPath);
2473
2524
  } catch {
@@ -2487,10 +2538,10 @@ function escapeForInlineScriptString(value) {
2487
2538
  return JSON.stringify(value).replace(/<\//gu, "<\\/").replace(/<!--/gu, "<\\!--").replace(/\u2028/gu, "\\u2028").replace(/\u2029/gu, "\\u2029");
2488
2539
  }
2489
2540
  async function getDirectoryItems(localPath) {
2490
- const entries = await readdir2(localPath, { withFileTypes: true });
2541
+ const entries = await readdir3(localPath, { withFileTypes: true });
2491
2542
  const withMeta = await Promise.all(entries.map(async (entry) => {
2492
- const entryPath = join2(localPath, entry.name);
2493
- const entryStat = await stat2(entryPath);
2543
+ const entryPath = join3(localPath, entry.name);
2544
+ const entryStat = await stat3(entryPath);
2494
2545
  const editable = !entry.isDirectory() && await isTextEditableFile(entryPath);
2495
2546
  return {
2496
2547
  name: entry.name,
@@ -2548,7 +2599,7 @@ async function createDirectoryListingHtml(localPath) {
2548
2599
  </html>`;
2549
2600
  }
2550
2601
  async function createTextEditorHtml(localPath) {
2551
- const content = await readFile2(localPath, "utf8");
2602
+ const content = await readFile3(localPath, "utf8");
2552
2603
  const parentPath = dirname(localPath);
2553
2604
  const language = languageForPath(localPath);
2554
2605
  const safeContentLiteral = escapeForInlineScriptString(content);
@@ -2619,8 +2670,8 @@ async function createTextEditorHtml(localPath) {
2619
2670
  // src/server/httpServer.ts
2620
2671
  import { WebSocketServer } from "ws";
2621
2672
  var __dirname = dirname2(fileURLToPath(import.meta.url));
2622
- var distDir = join3(__dirname, "..", "dist");
2623
- var spaEntryFile = join3(distDir, "index.html");
2673
+ var distDir = join4(__dirname, "..", "dist");
2674
+ var spaEntryFile = join4(distDir, "index.html");
2624
2675
  var IMAGE_CONTENT_TYPES = {
2625
2676
  ".avif": "image/avif",
2626
2677
  ".bmp": "image/bmp",
@@ -2697,7 +2748,7 @@ function createServer(options = {}) {
2697
2748
  return;
2698
2749
  }
2699
2750
  try {
2700
- const fileStat = await stat3(localPath);
2751
+ const fileStat = await stat4(localPath);
2701
2752
  res.setHeader("Cache-Control", "private, no-store");
2702
2753
  if (fileStat.isDirectory()) {
2703
2754
  const html = await createDirectoryListingHtml(localPath);
@@ -2720,7 +2771,7 @@ function createServer(options = {}) {
2720
2771
  return;
2721
2772
  }
2722
2773
  try {
2723
- const fileStat = await stat3(localPath);
2774
+ const fileStat = await stat4(localPath);
2724
2775
  if (!fileStat.isFile()) {
2725
2776
  res.status(400).json({ error: "Expected file path." });
2726
2777
  return;
@@ -2744,7 +2795,7 @@ function createServer(options = {}) {
2744
2795
  }
2745
2796
  const body = typeof req.body === "string" ? req.body : "";
2746
2797
  try {
2747
- await writeFile2(localPath, body, "utf8");
2798
+ await writeFile3(localPath, body, "utf8");
2748
2799
  res.status(200).json({ ok: true });
2749
2800
  } catch {
2750
2801
  res.status(404).json({ error: "File not found." });
@@ -2824,8 +2875,8 @@ var program = new Command().name("codexui").description("Web interface for Codex
2824
2875
  var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
2825
2876
  async function readCliVersion() {
2826
2877
  try {
2827
- const packageJsonPath = join4(__dirname2, "..", "package.json");
2828
- const raw = await readFile3(packageJsonPath, "utf8");
2878
+ const packageJsonPath = join5(__dirname2, "..", "package.json");
2879
+ const raw = await readFile4(packageJsonPath, "utf8");
2829
2880
  const parsed = JSON.parse(raw);
2830
2881
  return typeof parsed.version === "string" ? parsed.version : "unknown";
2831
2882
  } catch {
@@ -2850,13 +2901,13 @@ function runWithStatus(command, args) {
2850
2901
  return result.status ?? -1;
2851
2902
  }
2852
2903
  function getUserNpmPrefix() {
2853
- return join4(homedir2(), ".npm-global");
2904
+ return join5(homedir3(), ".npm-global");
2854
2905
  }
2855
2906
  function resolveCodexCommand() {
2856
2907
  if (canRun("codex", ["--version"])) {
2857
2908
  return "codex";
2858
2909
  }
2859
- const userCandidate = join4(getUserNpmPrefix(), "bin", "codex");
2910
+ const userCandidate = join5(getUserNpmPrefix(), "bin", "codex");
2860
2911
  if (existsSync3(userCandidate) && canRun(userCandidate, ["--version"])) {
2861
2912
  return userCandidate;
2862
2913
  }
@@ -2864,7 +2915,7 @@ function resolveCodexCommand() {
2864
2915
  if (!prefix) {
2865
2916
  return null;
2866
2917
  }
2867
- const candidate = join4(prefix, "bin", "codex");
2918
+ const candidate = join5(prefix, "bin", "codex");
2868
2919
  if (existsSync3(candidate) && canRun(candidate, ["--version"])) {
2869
2920
  return candidate;
2870
2921
  }
@@ -2874,7 +2925,7 @@ function resolveCloudflaredCommand() {
2874
2925
  if (canRun("cloudflared", ["--version"])) {
2875
2926
  return "cloudflared";
2876
2927
  }
2877
- const localCandidate = join4(homedir2(), ".local", "bin", "cloudflared");
2928
+ const localCandidate = join5(homedir3(), ".local", "bin", "cloudflared");
2878
2929
  if (existsSync3(localCandidate) && canRun(localCandidate, ["--version"])) {
2879
2930
  return localCandidate;
2880
2931
  }
@@ -2928,9 +2979,9 @@ async function ensureCloudflaredInstalledLinux() {
2928
2979
  if (!mappedArch) {
2929
2980
  throw new Error(`cloudflared auto-install is not supported for Linux architecture: ${process.arch}`);
2930
2981
  }
2931
- const userBinDir = join4(homedir2(), ".local", "bin");
2982
+ const userBinDir = join5(homedir3(), ".local", "bin");
2932
2983
  mkdirSync(userBinDir, { recursive: true });
2933
- const destination = join4(userBinDir, "cloudflared");
2984
+ const destination = join5(userBinDir, "cloudflared");
2934
2985
  const downloadUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${mappedArch}`;
2935
2986
  console.log("\ncloudflared not found. Installing to ~/.local/bin...\n");
2936
2987
  await downloadFile(downloadUrl, destination);
@@ -2969,8 +3020,8 @@ async function resolveCloudflaredForTunnel() {
2969
3020
  return ensureCloudflaredInstalledLinux();
2970
3021
  }
2971
3022
  function hasCodexAuth() {
2972
- const codexHome = process.env.CODEX_HOME?.trim() || join4(homedir2(), ".codex");
2973
- return existsSync3(join4(codexHome, "auth.json"));
3023
+ const codexHome = process.env.CODEX_HOME?.trim() || join5(homedir3(), ".codex");
3024
+ return existsSync3(join5(codexHome, "auth.json"));
2974
3025
  }
2975
3026
  function ensureCodexInstalled() {
2976
3027
  let codexCommand = resolveCodexCommand();
@@ -2988,7 +3039,7 @@ function ensureCodexInstalled() {
2988
3039
  Global npm install requires elevated permissions. Retrying with --prefix ${userPrefix}...
2989
3040
  `);
2990
3041
  runOrFail("npm", ["install", "-g", "--prefix", userPrefix, pkg], `${label} (user prefix)`);
2991
- process.env.PATH = `${join4(userPrefix, "bin")}:${process.env.PATH ?? ""}`;
3042
+ process.env.PATH = `${join5(userPrefix, "bin")}:${process.env.PATH ?? ""}`;
2992
3043
  };
2993
3044
  if (isTermuxRuntime()) {
2994
3045
  console.log("\nCodex CLI not found. Installing Termux-compatible Codex CLI from npm...\n");
@@ -3037,7 +3088,7 @@ function printTermuxKeepAlive(lines) {
3037
3088
  }
3038
3089
  function openBrowser(url) {
3039
3090
  const command = process.platform === "darwin" ? { cmd: "open", args: [url] } : process.platform === "win32" ? { cmd: "cmd", args: ["/c", "start", "", url] } : { cmd: "xdg-open", args: [url] };
3040
- const child = spawn2(command.cmd, command.args, { detached: true, stdio: "ignore" });
3091
+ const child = spawn3(command.cmd, command.args, { detached: true, stdio: "ignore" });
3041
3092
  child.on("error", () => {
3042
3093
  });
3043
3094
  child.unref();
@@ -3069,7 +3120,7 @@ function getAccessibleUrls(port) {
3069
3120
  }
3070
3121
  async function startCloudflaredTunnel(command, localPort) {
3071
3122
  return new Promise((resolve2, reject) => {
3072
- const child = spawn2(command, ["tunnel", "--url", `http://localhost:${String(localPort)}`], {
3123
+ const child = spawn3(command, ["tunnel", "--url", `http://localhost:${String(localPort)}`], {
3073
3124
  stdio: ["ignore", "pipe", "pipe"]
3074
3125
  });
3075
3126
  const timeout = setTimeout(() => {