codexui-android 0.1.98 → 0.1.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist-cli/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/cli/index.ts
4
4
  import { createServer as createServer2 } from "http";
5
- import { chmodSync as chmodSync2, createWriteStream, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
5
+ import { chmodSync as chmodSync2, createWriteStream, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
6
6
  import { readFile as readFile5, stat as stat7, writeFile as writeFile6 } from "fs/promises";
7
7
  import { homedir as homedir7, networkInterfaces } from "os";
8
8
  import { isAbsolute as isAbsolute4, join as join10, resolve as resolve3 } from "path";
@@ -124,37 +124,6 @@ function resolveRipgrepCommand() {
124
124
  }
125
125
  return null;
126
126
  }
127
- function resolvePythonCommand() {
128
- const candidates = process.platform === "win32" ? [
129
- { command: "python", args: [] },
130
- { command: "py", args: ["-3"] },
131
- { command: "python3", args: [] }
132
- ] : [
133
- { command: "python3", args: [] },
134
- { command: "python", args: [] }
135
- ];
136
- for (const candidate of candidates) {
137
- if (isRunnableCommand(candidate.command, [...candidate.args, "--version"])) {
138
- return candidate;
139
- }
140
- }
141
- return null;
142
- }
143
- function resolveSkillInstallerScriptPath(codexHome) {
144
- const normalizedCodexHome = codexHome?.trim();
145
- const candidates = uniqueStrings([
146
- normalizedCodexHome ? join(normalizedCodexHome, "skills", ".system", "skill-installer", "scripts", "install-skill-from-github.py") : null,
147
- process.env.CODEX_HOME?.trim() ? join(process.env.CODEX_HOME.trim(), "skills", ".system", "skill-installer", "scripts", "install-skill-from-github.py") : null,
148
- join(homedir(), ".codex", "skills", ".system", "skill-installer", "scripts", "install-skill-from-github.py"),
149
- join(homedir(), ".cursor", "skills", ".system", "skill-installer", "scripts", "install-skill-from-github.py")
150
- ]);
151
- for (const candidate of candidates) {
152
- if (existsSync(candidate)) {
153
- return candidate;
154
- }
155
- }
156
- return null;
157
- }
158
127
 
159
128
  // src/server/appServerRuntimeConfig.ts
160
129
  var SANDBOX_MODES = /* @__PURE__ */ new Set([
@@ -217,15 +186,15 @@ function parseApprovalPolicy(value) {
217
186
  // src/server/httpServer.ts
218
187
  import { fileURLToPath } from "url";
219
188
  import { dirname as dirname5, extname as extname3, isAbsolute as isAbsolute3, join as join9 } from "path";
220
- import { existsSync as existsSync5 } from "fs";
189
+ import { existsSync as existsSync6 } from "fs";
221
190
  import { writeFile as writeFile5, stat as stat6 } from "fs/promises";
222
191
  import express from "express";
223
192
 
224
193
  // src/server/codexAppServerBridge.ts
225
- import { spawn as spawn4 } from "child_process";
194
+ import { spawn as spawn4, spawnSync as spawnSync4 } from "child_process";
226
195
  import { createHash as createHash2, randomBytes } from "crypto";
227
- import { mkdtemp as mkdtemp3, readFile as readFile3, readdir as readdir2, rm as rm4, mkdir as mkdir4, stat as stat4 } from "fs/promises";
228
- import { createReadStream, readFileSync as readFileSync2 } from "fs";
196
+ import { mkdtemp as mkdtemp3, readFile as readFile3, readdir as readdir2, rm as rm4, mkdir as mkdir4, stat as stat4, lstat as lstat2 } from "fs/promises";
197
+ import { createReadStream, existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
229
198
  import { request as httpRequest2 } from "http";
230
199
  import { request as httpsRequest2 } from "https";
231
200
  import { homedir as homedir5 } from "os";
@@ -1778,6 +1747,50 @@ function getCodexHomeDir2() {
1778
1747
  const codexHome = process.env.CODEX_HOME?.trim();
1779
1748
  return codexHome && codexHome.length > 0 ? codexHome : join4(homedir3(), ".codex");
1780
1749
  }
1750
+ function splitAbsolutePath(pathValue) {
1751
+ return pathValue.split("/").filter(Boolean);
1752
+ }
1753
+ function buildAbsolutePath(parts) {
1754
+ return `/${parts.join("/")}`;
1755
+ }
1756
+ function normalizeSkillMarkdownPath(skillPath) {
1757
+ if (!skillPath) return "";
1758
+ return skillPath.endsWith("/SKILL.md") ? skillPath : `${skillPath}/SKILL.md`;
1759
+ }
1760
+ function deriveSkillPathInfo(skillPath, knownPaths = /* @__PURE__ */ new Set()) {
1761
+ const normalizedPath = normalizeSkillMarkdownPath(skillPath);
1762
+ const parts = splitAbsolutePath(normalizedPath);
1763
+ if (parts.length < 2) return null;
1764
+ const pluginSkillsIndex = parts.lastIndexOf("skills");
1765
+ if (pluginSkillsIndex >= 2) {
1766
+ const pluginName = parts[pluginSkillsIndex - 2] ?? "";
1767
+ if (pluginName) {
1768
+ const rootSkillPath = buildAbsolutePath([...parts.slice(0, pluginSkillsIndex + 1), pluginName, "SKILL.md"]);
1769
+ if (knownPaths.has(rootSkillPath)) {
1770
+ return {
1771
+ normalizedPath,
1772
+ rootSkillPath,
1773
+ rootSkillName: pluginName,
1774
+ installDir: buildAbsolutePath(parts.slice(0, pluginSkillsIndex + 1)),
1775
+ isNestedSkill: normalizedPath !== rootSkillPath
1776
+ };
1777
+ }
1778
+ }
1779
+ }
1780
+ const firstSkillsIndex = parts.indexOf("skills");
1781
+ if (firstSkillsIndex < 0 || firstSkillsIndex + 1 >= parts.length - 1) return null;
1782
+ const rootSkillName = parts[firstSkillsIndex + 1] ?? "";
1783
+ if (!rootSkillName) return null;
1784
+ const rootParts = parts.slice(0, firstSkillsIndex + 2);
1785
+ const installDirParts = parts.slice(0, firstSkillsIndex + 1);
1786
+ return {
1787
+ normalizedPath,
1788
+ rootSkillPath: buildAbsolutePath([...rootParts, "SKILL.md"]),
1789
+ rootSkillName,
1790
+ installDir: buildAbsolutePath(installDirParts),
1791
+ isNestedSkill: normalizedPath !== buildAbsolutePath([...rootParts, "SKILL.md"])
1792
+ };
1793
+ }
1781
1794
  function getSkillsInstallDir() {
1782
1795
  return join4(getCodexHomeDir2(), "skills");
1783
1796
  }
@@ -1889,109 +1902,94 @@ async function detectUserSkillsDir(appServer) {
1889
1902
  for (const entry of result.data ?? []) {
1890
1903
  for (const skill of entry.skills ?? []) {
1891
1904
  if (skill.scope !== "user" || !skill.path) continue;
1892
- const parts = skill.path.split("/").filter(Boolean);
1893
- if (parts.length < 2) continue;
1894
- return `/${parts.slice(0, -2).join("/")}`;
1905
+ const skillInfo = deriveSkillPathInfo(skill.path);
1906
+ if (!skillInfo) continue;
1907
+ return skillInfo.installDir;
1895
1908
  }
1896
1909
  }
1897
1910
  } catch {
1898
1911
  }
1899
1912
  return getSkillsInstallDir();
1900
1913
  }
1901
- async function ensureInstalledSkillIsValid(appServer, skillPath) {
1902
- const result = await appServer.rpc("skills/list", { forceReload: true });
1903
- const normalized = skillPath.endsWith("/SKILL.md") ? skillPath : `${skillPath}/SKILL.md`;
1904
- for (const entry of result.data ?? []) {
1905
- for (const error of entry.errors ?? []) {
1906
- if (error.path === normalized) {
1907
- throw new Error(error.message || "Installed skill is invalid");
1908
- }
1909
- }
1910
- }
1911
- }
1912
- var TREE_CACHE_TTL_MS = 5 * 60 * 1e3;
1913
- var skillsTreeCache = null;
1914
- var metaCache = /* @__PURE__ */ new Map();
1915
- async function getGhToken() {
1914
+ async function runGitFetchWithRefLockRetry(repoDir, args = ["fetch", "origin"]) {
1916
1915
  try {
1917
- const proc = spawn3("gh", ["auth", "token"], { stdio: ["ignore", "pipe", "ignore"] });
1918
- let out = "";
1919
- proc.stdout.on("data", (d) => {
1920
- out += d.toString();
1921
- });
1922
- return new Promise((resolve4) => {
1923
- proc.on("close", (code) => resolve4(code === 0 ? out.trim() : null));
1924
- proc.on("error", () => resolve4(null));
1925
- });
1926
- } catch {
1927
- return null;
1916
+ await runCommand2("git", args, { cwd: repoDir });
1917
+ } catch (error) {
1918
+ const message = getErrorMessage3(error, "");
1919
+ if (!message.includes("cannot lock ref 'refs/remotes/origin/")) throw error;
1920
+ const branchMatch = message.match(/refs\/remotes\/origin\/([^\s':]+)/);
1921
+ if (!branchMatch?.[1]) throw error;
1922
+ const refPath = join4(repoDir, ".git", "refs", "remotes", "origin", branchMatch[1]);
1923
+ try {
1924
+ await rm3(refPath, { force: true });
1925
+ } catch {
1926
+ }
1927
+ await runCommand2("git", args, { cwd: repoDir });
1928
1928
  }
1929
1929
  }
1930
- async function ghFetch(url) {
1931
- const token = await getGhToken();
1932
- const headers = {
1933
- Accept: "application/vnd.github+json",
1934
- "User-Agent": "codex-web-local"
1930
+ function buildLocalHubEntry(info) {
1931
+ return {
1932
+ name: info.name,
1933
+ owner: "local",
1934
+ description: "",
1935
+ displayName: "",
1936
+ publishedAt: 0,
1937
+ avatarUrl: "",
1938
+ url: "",
1939
+ installed: true,
1940
+ path: info.path,
1941
+ enabled: info.enabled
1935
1942
  };
1936
- if (token) headers.Authorization = `Bearer ${token}`;
1937
- return fetch(url, { headers });
1938
1943
  }
1939
- async function fetchSkillsTree() {
1940
- if (skillsTreeCache && Date.now() - skillsTreeCache.fetchedAt < TREE_CACHE_TTL_MS) {
1941
- return skillsTreeCache.entries;
1942
- }
1943
- const resp = await ghFetch(`https://api.github.com/repos/${HUB_SKILLS_OWNER}/${HUB_SKILLS_REPO}/git/trees/main?recursive=1`);
1944
- if (!resp.ok) throw new Error(`GitHub tree API returned ${resp.status}`);
1945
- const data = await resp.json();
1946
- const metaPattern = /^skills\/([^/]+)\/([^/]+)\/_meta\.json$/;
1947
- const seen = /* @__PURE__ */ new Set();
1948
- const entries = [];
1949
- for (const node of data.tree ?? []) {
1950
- const match = metaPattern.exec(node.path);
1951
- if (!match) continue;
1952
- const [, owner, skillName] = match;
1953
- const key = `${owner}/${skillName}`;
1954
- if (seen.has(key)) continue;
1955
- seen.add(key);
1956
- entries.push({
1957
- name: skillName,
1958
- owner,
1959
- url: `https://github.com/${HUB_SKILLS_OWNER}/${HUB_SKILLS_REPO}/tree/main/skills/${owner}/${skillName}`
1960
- });
1961
- }
1962
- skillsTreeCache = { entries, fetchedAt: Date.now() };
1963
- return entries;
1964
- }
1965
- async function fetchMetaBatch(entries) {
1966
- const toFetch = entries.filter((e) => !metaCache.has(`${e.owner}/${e.name}`));
1967
- if (toFetch.length === 0) return;
1968
- const batch = toFetch.slice(0, 50);
1969
- await Promise.allSettled(
1970
- batch.map(async (e) => {
1971
- const rawUrl = `https://raw.githubusercontent.com/${HUB_SKILLS_OWNER}/${HUB_SKILLS_REPO}/main/skills/${e.owner}/${e.name}/_meta.json`;
1972
- const resp = await fetch(rawUrl);
1973
- if (!resp.ok) return;
1974
- const meta = await resp.json();
1975
- metaCache.set(`${e.owner}/${e.name}`, {
1976
- displayName: typeof meta.displayName === "string" ? meta.displayName : "",
1977
- description: typeof meta.displayName === "string" ? meta.displayName : "",
1978
- publishedAt: meta.latest?.publishedAt ?? 0
1979
- });
1980
- })
1944
+ function groupRpcSkillRecords(skills) {
1945
+ const normalizedPathSet = new Set(
1946
+ skills.map((skill) => normalizeSkillMarkdownPath(typeof skill.path === "string" ? skill.path : "")).filter(Boolean)
1981
1947
  );
1982
- }
1983
- function buildHubEntry(e) {
1984
- const cached = metaCache.get(`${e.owner}/${e.name}`);
1985
- return {
1986
- name: e.name,
1987
- owner: e.owner,
1988
- description: cached?.description ?? "",
1989
- displayName: cached?.displayName ?? "",
1990
- publishedAt: cached?.publishedAt ?? 0,
1991
- avatarUrl: `https://github.com/${e.owner}.png?size=40`,
1992
- url: e.url,
1993
- installed: false
1994
- };
1948
+ const grouped = /* @__PURE__ */ new Map();
1949
+ for (const skill of skills) {
1950
+ const rawPath = typeof skill.path === "string" ? skill.path : "";
1951
+ const pathInfo = rawPath ? deriveSkillPathInfo(rawPath, normalizedPathSet) : null;
1952
+ const groupingKey = pathInfo && pathInfo.isNestedSkill && normalizedPathSet.has(pathInfo.rootSkillPath) ? pathInfo.rootSkillPath : pathInfo?.normalizedPath || rawPath || `${skill.scope ?? ""}:${skill.name ?? ""}`;
1953
+ const existing = grouped.get(groupingKey);
1954
+ const isRootEntry = pathInfo?.normalizedPath === groupingKey;
1955
+ const groupedName = pathInfo && groupingKey === pathInfo.rootSkillPath ? pathInfo.rootSkillName : skill.name;
1956
+ if (!existing) {
1957
+ grouped.set(groupingKey, {
1958
+ preferred: isRootEntry ? {
1959
+ ...skill,
1960
+ name: groupedName,
1961
+ path: groupingKey
1962
+ } : {
1963
+ ...skill,
1964
+ name: groupedName,
1965
+ path: groupingKey
1966
+ },
1967
+ hasRoot: isRootEntry,
1968
+ anyEnabled: skill.enabled !== false
1969
+ });
1970
+ continue;
1971
+ }
1972
+ existing.anyEnabled = existing.anyEnabled || skill.enabled !== false;
1973
+ if (!existing.hasRoot && isRootEntry) {
1974
+ existing.preferred = {
1975
+ ...skill,
1976
+ name: groupedName,
1977
+ path: groupingKey
1978
+ };
1979
+ existing.hasRoot = true;
1980
+ continue;
1981
+ }
1982
+ if (!existing.preferred.description && skill.description) {
1983
+ existing.preferred = { ...existing.preferred, description: skill.description };
1984
+ }
1985
+ if (!existing.preferred.shortDescription && skill.shortDescription) {
1986
+ existing.preferred = { ...existing.preferred, shortDescription: skill.shortDescription };
1987
+ }
1988
+ }
1989
+ return Array.from(grouped.values()).map(({ preferred, anyEnabled }) => ({
1990
+ ...preferred,
1991
+ enabled: preferred.enabled ?? anyEnabled
1992
+ }));
1995
1993
  }
1996
1994
  var GITHUB_DEVICE_CLIENT_ID = "Iv1.b507a08c87ecfe98";
1997
1995
  var DEFAULT_SKILLS_SYNC_REPO_NAME = "codexskills";
@@ -2001,8 +1999,6 @@ var SYNC_UPSTREAM_SKILLS_REPO = "skills";
2001
1999
  var PRIVATE_SYNC_BRANCH = "main";
2002
2000
  var PUBLIC_UPSTREAM_BRANCH_ANDROID = "android";
2003
2001
  var PUBLIC_UPSTREAM_BRANCH_DEFAULT = "main";
2004
- var HUB_SKILLS_OWNER = "openclaw";
2005
- var HUB_SKILLS_REPO = "skills";
2006
2002
  var startupSkillsSyncInitialized = false;
2007
2003
  var startupSyncStatus = {
2008
2004
  inProgress: false,
@@ -2280,7 +2276,7 @@ async function ensureSkillsWorkingTreeRepo(repoUrl, branch) {
2280
2276
  } catch {
2281
2277
  await runCommand2("git", ["remote", "set-url", "origin", repoUrl], { cwd: localDir });
2282
2278
  }
2283
- await runCommand2("git", ["fetch", "origin"], { cwd: localDir });
2279
+ await runGitFetchWithRefLockRetry(localDir);
2284
2280
  try {
2285
2281
  await runCommand2("git", ["merge", "--allow-unrelated-histories", "--no-edit", `origin/${branch}`], { cwd: localDir });
2286
2282
  } catch {
@@ -2288,7 +2284,7 @@ async function ensureSkillsWorkingTreeRepo(repoUrl, branch) {
2288
2284
  return localDir;
2289
2285
  }
2290
2286
  await runCommand2("git", ["remote", "set-url", "origin", repoUrl], { cwd: localDir });
2291
- await runCommand2("git", ["fetch", "origin"], { cwd: localDir });
2287
+ await runGitFetchWithRefLockRetry(localDir);
2292
2288
  const hasLocalChangesBeforeSync = await hasLocalUncommittedChanges(localDir);
2293
2289
  const localMtimesBeforeSync = hasLocalChangesBeforeSync ? await snapshotFileMtimes(localDir) : /* @__PURE__ */ new Map();
2294
2290
  await resolveMergeConflictsByNewerCommit(localDir, branch, localMtimesBeforeSync);
@@ -2308,7 +2304,7 @@ async function ensureSkillsWorkingTreeRepo(repoUrl, branch) {
2308
2304
  } catch {
2309
2305
  }
2310
2306
  let pulledMtimes = /* @__PURE__ */ new Map();
2311
- await runCommand2("git", ["fetch", "origin", branch], { cwd: localDir });
2307
+ await runGitFetchWithRefLockRetry(localDir, ["fetch", "origin", branch]);
2312
2308
  await runCommand2("git", ["reset", "--hard", `origin/${branch}`], { cwd: localDir });
2313
2309
  pulledMtimes = await snapshotFileMtimes(localDir);
2314
2310
  if (createdAutostash) {
@@ -2464,6 +2460,11 @@ async function syncInstalledSkillsFolderToRepo(token, repoOwner, repoName, _inst
2464
2460
  }
2465
2461
  await runCommand2("git", ["checkout", `origin/${branch2}`, "--", filePath], { cwd: repoDir2 });
2466
2462
  }
2463
+ try {
2464
+ await runCommand2("git", ["cat-file", "-e", `origin/${branch2}:shared_skills`], { cwd: repoDir2 });
2465
+ await runCommand2("git", ["checkout", `origin/${branch2}`, "--", "shared_skills"], { cwd: repoDir2 });
2466
+ } catch {
2467
+ }
2467
2468
  }
2468
2469
  function isNonFastForwardPushError(error) {
2469
2470
  const text = getErrorMessage3(error, "").toLowerCase();
@@ -2474,7 +2475,7 @@ async function syncInstalledSkillsFolderToRepo(token, repoOwner, repoName, _inst
2474
2475
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
2475
2476
  const hasLocalChangesBeforeReconcile = await hasLocalUncommittedChanges(repoDir2);
2476
2477
  const localMtimesBeforeReconcile = hasLocalChangesBeforeReconcile ? await snapshotFileMtimes(repoDir2) : /* @__PURE__ */ new Map();
2477
- await runCommand2("git", ["fetch", "origin"], { cwd: repoDir2 });
2478
+ await runGitFetchWithRefLockRetry(repoDir2);
2478
2479
  try {
2479
2480
  await runCommand2("git", ["rebase", `origin/${branch2}`], { cwd: repoDir2 });
2480
2481
  } catch {
@@ -2490,7 +2491,7 @@ async function syncInstalledSkillsFolderToRepo(token, repoOwner, repoName, _inst
2490
2491
  }
2491
2492
  }
2492
2493
  try {
2493
- await runCommand2("git", ["push", "origin", `HEAD:${branch2}`], { cwd: repoDir2 });
2494
+ await runCommand2("git", ["push", "--no-recurse-submodules", "origin", `HEAD:${branch2}`], { cwd: repoDir2 });
2494
2495
  const state = await readSkillsSyncState();
2495
2496
  const pushedHead = await runCommandWithOutput("git", ["rev-parse", "HEAD"], { cwd: repoDir2 });
2496
2497
  await writeSkillsSyncState({
@@ -2542,38 +2543,16 @@ async function bootstrapSkillsFromUpstreamIntoLocal() {
2542
2543
  async function collectLocalSyncedSkills(appServer) {
2543
2544
  const state = await readSkillsSyncState();
2544
2545
  const owners = { ...state.installedOwners ?? {} };
2545
- const tree = await fetchSkillsTree();
2546
- const uniqueOwnerByName = /* @__PURE__ */ new Map();
2547
- const ambiguousNames = /* @__PURE__ */ new Set();
2548
- for (const entry of tree) {
2549
- if (ambiguousNames.has(entry.name)) continue;
2550
- const existingOwner = uniqueOwnerByName.get(entry.name);
2551
- if (!existingOwner) {
2552
- uniqueOwnerByName.set(entry.name, entry.owner);
2553
- continue;
2554
- }
2555
- if (existingOwner !== entry.owner) {
2556
- uniqueOwnerByName.delete(entry.name);
2557
- ambiguousNames.add(entry.name);
2558
- }
2559
- }
2560
2546
  const skills = await appServer.rpc("skills/list", {});
2561
2547
  const seen = /* @__PURE__ */ new Set();
2562
2548
  const synced = [];
2563
2549
  let ownersChanged = false;
2564
2550
  for (const entry of skills.data ?? []) {
2565
- for (const skill of entry.skills ?? []) {
2551
+ for (const skill of groupRpcSkillRecords(entry.skills ?? [])) {
2566
2552
  const name = typeof skill.name === "string" ? skill.name : "";
2567
- if (!name || seen.has(name)) continue;
2553
+ if (!name || skill.scope !== "user" || seen.has(name)) continue;
2568
2554
  seen.add(name);
2569
- let owner = owners[name];
2570
- if (!owner) {
2571
- owner = uniqueOwnerByName.get(name) ?? "";
2572
- if (owner) {
2573
- owners[name] = owner;
2574
- ownersChanged = true;
2575
- }
2576
- }
2555
+ const owner = owners[name] ?? "";
2577
2556
  synced.push({ ...owner ? { owner } : {}, name, enabled: skill.enabled !== false });
2578
2557
  }
2579
2558
  }
@@ -2709,44 +2688,15 @@ async function finalizeGithubLoginAndSync(token, username, appServer) {
2709
2688
  }
2710
2689
  await autoPushSyncedSkills(appServer);
2711
2690
  }
2712
- async function searchSkillsHub(allEntries, query, limit, sort, installedMap) {
2713
- const q = query.toLowerCase().trim();
2714
- const filtered = q ? allEntries.filter((s) => {
2715
- if (s.name.toLowerCase().includes(q) || s.owner.toLowerCase().includes(q)) return true;
2716
- const cached = metaCache.get(`${s.owner}/${s.name}`);
2717
- return Boolean(cached?.displayName?.toLowerCase().includes(q));
2718
- }) : allEntries;
2719
- const page = filtered.slice(0, Math.min(limit * 2, 200));
2720
- await fetchMetaBatch(page);
2721
- let results = page.map(buildHubEntry);
2722
- if (sort === "date") {
2723
- results.sort((a, b) => b.publishedAt - a.publishedAt);
2724
- } else if (q) {
2725
- results.sort((a, b) => {
2726
- const aExact = a.name.toLowerCase() === q ? 1 : 0;
2727
- const bExact = b.name.toLowerCase() === q ? 1 : 0;
2728
- if (aExact !== bExact) return bExact - aExact;
2729
- return b.publishedAt - a.publishedAt;
2730
- });
2731
- }
2732
- return results.slice(0, limit).map((s) => {
2733
- const local = installedMap.get(s.name);
2734
- return local ? { ...s, installed: true, path: local.path, enabled: local.enabled } : s;
2735
- });
2736
- }
2737
2691
  async function handleSkillsRoutes(req, res, url, context) {
2738
2692
  const { appServer, readJsonBody: readJsonBody2 } = context;
2739
2693
  if (req.method === "GET" && url.pathname === "/codex-api/skills-hub") {
2740
2694
  try {
2741
- const q = url.searchParams.get("q") || "";
2742
- const limit = Math.min(Math.max(parseInt(url.searchParams.get("limit") || "50", 10) || 50, 1), 200);
2743
- const sort = url.searchParams.get("sort") || "date";
2744
- const allEntries = await fetchSkillsTree();
2745
2695
  const installedMap = await scanInstalledSkillsFromDisk();
2746
2696
  try {
2747
2697
  const result = await appServer.rpc("skills/list", {});
2748
2698
  for (const entry of result.data ?? []) {
2749
- for (const skill of entry.skills ?? []) {
2699
+ for (const skill of groupRpcSkillRecords(entry.skills ?? [])) {
2750
2700
  if (skill.name) {
2751
2701
  installedMap.set(skill.name, { name: skill.name, path: skill.path ?? "", enabled: skill.enabled !== false });
2752
2702
  }
@@ -2754,25 +2704,12 @@ async function handleSkillsRoutes(req, res, url, context) {
2754
2704
  }
2755
2705
  } catch {
2756
2706
  }
2757
- const installedHubEntries = allEntries.filter((e) => installedMap.has(e.name));
2758
- await fetchMetaBatch(installedHubEntries);
2759
2707
  const installed = [];
2760
2708
  for (const [, info] of installedMap) {
2761
- const hubEntry = allEntries.find((e) => e.name === info.name);
2762
- const base = hubEntry ? buildHubEntry(hubEntry) : {
2763
- name: info.name,
2764
- owner: "local",
2765
- description: "",
2766
- displayName: "",
2767
- publishedAt: 0,
2768
- avatarUrl: "",
2769
- url: "",
2770
- installed: false
2771
- };
2772
- installed.push({ ...base, installed: true, path: info.path, enabled: info.enabled });
2709
+ installed.push(buildLocalHubEntry(info));
2773
2710
  }
2774
- const results = await searchSkillsHub(allEntries, q, limit, sort, installedMap);
2775
- setJson3(res, 200, { data: results, installed, total: allEntries.length });
2711
+ installed.sort((a, b) => a.name.localeCompare(b.name));
2712
+ setJson3(res, 200, { installed });
2776
2713
  } catch (error) {
2777
2714
  setJson3(res, 502, { error: getErrorMessage3(error, "Failed to fetch skills hub") });
2778
2715
  }
@@ -2913,27 +2850,12 @@ async function handleSkillsRoutes(req, res, url, context) {
2913
2850
  return true;
2914
2851
  }
2915
2852
  const remote = await readRemoteSkillsManifest(state.githubToken, state.repoOwner, state.repoName);
2916
- const tree = await fetchSkillsTree();
2917
- const uniqueOwnerByName = /* @__PURE__ */ new Map();
2918
- const ambiguousNames = /* @__PURE__ */ new Set();
2919
- for (const entry of tree) {
2920
- if (ambiguousNames.has(entry.name)) continue;
2921
- const existingOwner = uniqueOwnerByName.get(entry.name);
2922
- if (!existingOwner) {
2923
- uniqueOwnerByName.set(entry.name, entry.owner);
2924
- continue;
2925
- }
2926
- if (existingOwner !== entry.owner) {
2927
- uniqueOwnerByName.delete(entry.name);
2928
- ambiguousNames.add(entry.name);
2929
- }
2930
- }
2931
2853
  const localDir = await detectUserSkillsDir(appServer);
2932
2854
  await pullInstalledSkillsFolderFromRepo(state.githubToken, state.repoOwner, state.repoName);
2933
2855
  const localSkills = await scanInstalledSkillsFromDisk();
2934
2856
  const missingAfterPull = [];
2935
2857
  for (const skill of remote) {
2936
- const owner = skill.owner || uniqueOwnerByName.get(skill.name) || "";
2858
+ const owner = skill.owner || "";
2937
2859
  if (!owner) continue;
2938
2860
  if (!localSkills.has(skill.name)) {
2939
2861
  missingAfterPull.push(`${owner}/${skill.name}`);
@@ -2953,7 +2875,7 @@ async function handleSkillsRoutes(req, res, url, context) {
2953
2875
  }
2954
2876
  const nextOwners = {};
2955
2877
  for (const item of remote) {
2956
- const owner = item.owner || uniqueOwnerByName.get(item.name) || "";
2878
+ const owner = item.owner || "";
2957
2879
  if (owner) nextOwners[item.name] = owner;
2958
2880
  }
2959
2881
  const pulledHead = await runCommandWithOutput("git", ["rev-parse", "HEAD"], { cwd: getSkillsInstallDir() }).catch(() => "");
@@ -2990,74 +2912,20 @@ async function handleSkillsRoutes(req, res, url, context) {
2990
2912
  const installedInfo = installedMap.get(name);
2991
2913
  const localSkillPath = installedInfo?.path || (skillPath ? skillPath.endsWith("/SKILL.md") ? skillPath : `${skillPath}/SKILL.md` : "");
2992
2914
  if (localSkillPath) {
2993
- const content2 = await readFile2(localSkillPath, "utf8");
2994
- const description2 = extractSkillDescriptionFromMarkdown(content2);
2995
- setJson3(res, 200, { content: content2, description: description2, source: "local" });
2915
+ const content = await readFile2(localSkillPath, "utf8");
2916
+ const description = extractSkillDescriptionFromMarkdown(content);
2917
+ setJson3(res, 200, { content, description, source: "local" });
2996
2918
  return true;
2997
2919
  }
2998
2920
  }
2999
- const rawUrl = `https://raw.githubusercontent.com/${HUB_SKILLS_OWNER}/${HUB_SKILLS_REPO}/main/skills/${owner}/${name}/SKILL.md`;
3000
- const resp = await fetch(rawUrl);
3001
- if (!resp.ok) throw new Error(`Failed to fetch SKILL.md: ${resp.status}`);
3002
- const content = await resp.text();
3003
- const description = extractSkillDescriptionFromMarkdown(content);
3004
- setJson3(res, 200, { content, description, source: "remote" });
2921
+ setJson3(res, 404, { error: "Only installed local skills are available in Skills Hub." });
3005
2922
  } catch (error) {
3006
2923
  setJson3(res, 502, { error: getErrorMessage3(error, "Failed to fetch SKILL.md") });
3007
2924
  }
3008
2925
  return true;
3009
2926
  }
3010
2927
  if (req.method === "POST" && url.pathname === "/codex-api/skills-hub/install") {
3011
- try {
3012
- const payload = asRecord3(await readJsonBody2(req));
3013
- const owner = typeof payload?.owner === "string" ? payload.owner : "";
3014
- const name = typeof payload?.name === "string" ? payload.name : "";
3015
- if (!owner || !name) {
3016
- setJson3(res, 400, { error: "Missing owner or name" });
3017
- return true;
3018
- }
3019
- const installerScript = resolveSkillInstallerScriptPath(getCodexHomeDir2());
3020
- if (!installerScript) {
3021
- throw new Error("Skill installer script not found");
3022
- }
3023
- const pythonCommand = resolvePythonCommand();
3024
- if (!pythonCommand) {
3025
- throw new Error("Python 3 is required to install skills");
3026
- }
3027
- const installDest = await withTimeout(
3028
- detectUserSkillsDir(appServer),
3029
- 1e4,
3030
- "detectUserSkillsDir"
3031
- ).catch(() => getSkillsInstallDir());
3032
- const skillDir = join4(installDest, name);
3033
- if (existsSync2(skillDir)) {
3034
- await rm3(skillDir, { recursive: true, force: true });
3035
- }
3036
- await runCommand2(pythonCommand.command, [
3037
- ...pythonCommand.args,
3038
- installerScript,
3039
- "--repo",
3040
- `${HUB_SKILLS_OWNER}/${HUB_SKILLS_REPO}`,
3041
- "--path",
3042
- `skills/${owner}/${name}`,
3043
- "--dest",
3044
- installDest,
3045
- "--method",
3046
- "git"
3047
- ], { timeoutMs: 9e4 });
3048
- try {
3049
- await withTimeout(ensureInstalledSkillIsValid(appServer, skillDir), 1e4, "ensureInstalledSkillIsValid");
3050
- } catch {
3051
- }
3052
- const syncState = await readSkillsSyncState();
3053
- const nextOwners = { ...syncState.installedOwners ?? {}, [name]: owner };
3054
- await writeSkillsSyncState({ ...syncState, installedOwners: nextOwners });
3055
- autoPushSyncedSkills(appServer).catch(() => {
3056
- });
3057
- setJson3(res, 200, { ok: true, path: skillDir });
3058
- } catch (error) {
3059
- setJson3(res, 502, { error: getErrorMessage3(error, "Failed to install skill") });
3060
- }
2928
+ setJson3(res, 410, { error: "Remote Skills Hub installation is disabled." });
3061
2929
  return true;
3062
2930
  }
3063
2931
  if (req.method === "POST" && url.pathname === "/codex-api/skills-hub/uninstall") {
@@ -4749,16 +4617,20 @@ function spawnSyncCommand(command, args = [], options = {}) {
4749
4617
  }
4750
4618
 
4751
4619
  // src/server/codexAppServerBridge.ts
4620
+ var COMPOSIO_CONNECTORS_PAGE_LIMIT_MAX = 1e3;
4752
4621
  var PROVIDER_MODELS_FETCH_TIMEOUT_MS = 5e3;
4753
4622
  var THREAD_RESPONSE_TURN_LIMIT = 10;
4754
4623
  var THREAD_METHODS_WITH_TURNS = /* @__PURE__ */ new Set(["thread/read", "thread/resume", "thread/fork", "thread/rollback"]);
4755
4624
  var THREAD_SEARCH_FULL_TEXT_THREAD_LIMIT = 100;
4625
+ var PROJECTLESS_THREAD_DIRECTORY_MAX_ATTEMPTS = 100;
4626
+ var PROJECTLESS_THREAD_SLUG_MAX_LENGTH = 80;
4756
4627
  var API_PERF_LOGGING_ENV_KEY = "CODEXUI_API_PERF_LOGGING";
4757
4628
  var API_PERF_MS_THRESHOLD_ENV_KEY = "CODEXUI_API_PERF_MS_THRESHOLD";
4758
4629
  var API_PERF_BODY_MB_THRESHOLD_ENV_KEY = "CODEXUI_API_PERF_BODY_MB_THRESHOLD";
4759
4630
  var DEFAULT_API_PERF_MS_THRESHOLD = 300;
4760
4631
  var DEFAULT_API_PERF_BODY_MB_THRESHOLD = 1;
4761
4632
  var MB_DIVISOR = 1024 * 1024;
4633
+ var COMPOSIO_USER_DATA_PATH = join6(homedir5(), ".composio", "user_data.json");
4762
4634
  function readEnvValueFromFile(filePath, key) {
4763
4635
  try {
4764
4636
  const content = readFileSync2(filePath, "utf8");
@@ -5137,6 +5009,45 @@ function logProviderModelDiscoveryWarning(message, details) {
5137
5009
  function isTimeoutError(payload) {
5138
5010
  return payload instanceof Error && (payload.name === "AbortError" || payload.name === "TimeoutError");
5139
5011
  }
5012
+ function formatProjectlessDateSegment(date = /* @__PURE__ */ new Date()) {
5013
+ const month = String(date.getMonth() + 1).padStart(2, "0");
5014
+ const day = String(date.getDate()).padStart(2, "0");
5015
+ return `${date.getFullYear()}-${month}-${day}`;
5016
+ }
5017
+ function buildProjectlessPromptSlug(prompt) {
5018
+ const slug = prompt?.toLowerCase().match(/[a-z0-9]+/g)?.slice(0, 6).join("-").slice(0, PROJECTLESS_THREAD_SLUG_MAX_LENGTH);
5019
+ return slug && slug.length > 0 ? slug : "new-chat";
5020
+ }
5021
+ async function ensureRealDirectory(path, label) {
5022
+ const info = await lstat2(path);
5023
+ if (info.isSymbolicLink() || !info.isDirectory()) {
5024
+ throw new Error(`${label} must be a real directory`);
5025
+ }
5026
+ }
5027
+ async function createProjectlessThreadDirectory(prompt) {
5028
+ const workspaceRoot = join6(homedir5(), "Documents", "Codex");
5029
+ await mkdir4(workspaceRoot, { recursive: true });
5030
+ await ensureRealDirectory(workspaceRoot, "Projectless workspace root");
5031
+ const dateDir = join6(workspaceRoot, formatProjectlessDateSegment());
5032
+ await mkdir4(dateDir, { recursive: true });
5033
+ await ensureRealDirectory(dateDir, "Projectless thread date directory");
5034
+ const slug = buildProjectlessPromptSlug(prompt);
5035
+ for (let index = 0; index < PROJECTLESS_THREAD_DIRECTORY_MAX_ATTEMPTS; index += 1) {
5036
+ const folderName = index === 0 ? slug : `${slug}-${index + 1}`;
5037
+ const cwd = join6(dateDir, folderName);
5038
+ try {
5039
+ await mkdir4(cwd, { recursive: false });
5040
+ return { cwd, outputDirectory: cwd, workspaceRoot };
5041
+ } catch {
5042
+ try {
5043
+ await stat4(cwd);
5044
+ } catch {
5045
+ throw new Error("Failed to create new chat folder");
5046
+ }
5047
+ }
5048
+ }
5049
+ throw new Error("Unable to create a unique new chat folder");
5050
+ }
5140
5051
  function normalizeHeaderValue(value) {
5141
5052
  if (typeof value === "string") {
5142
5053
  const trimmed = value.trim();
@@ -5336,6 +5247,413 @@ function extractThreadMessageText(threadReadPayload) {
5336
5247
  function readNonEmptyString(value) {
5337
5248
  return typeof value === "string" && value.trim().length > 0 ? value : "";
5338
5249
  }
5250
+ async function listTerminalQuickCommands(cwd) {
5251
+ const normalizedCwd = isAbsolute2(cwd) ? cwd : resolve2(cwd);
5252
+ const info = await stat4(normalizedCwd);
5253
+ if (!info.isDirectory()) {
5254
+ throw new Error("Terminal cwd is not a directory");
5255
+ }
5256
+ const commands = [];
5257
+ const seen = /* @__PURE__ */ new Set();
5258
+ const addCommand = (command) => {
5259
+ if (!command.value || seen.has(command.value)) return;
5260
+ seen.add(command.value);
5261
+ commands.push(command);
5262
+ };
5263
+ await addPackageJsonCommands(normalizedCwd, addCommand);
5264
+ await addMakefileCommands(normalizedCwd, addCommand);
5265
+ await addRootScriptCommands(normalizedCwd, addCommand);
5266
+ await addScriptsDirectoryCommands(normalizedCwd, addCommand);
5267
+ return commands;
5268
+ }
5269
+ async function addPackageJsonCommands(cwd, addCommand) {
5270
+ try {
5271
+ const raw = await readFile3(join6(cwd, "package.json"), "utf8");
5272
+ const parsed = JSON.parse(raw);
5273
+ const record = asRecord5(parsed);
5274
+ const scripts = asRecord5(record?.scripts);
5275
+ if (!scripts) return;
5276
+ const packageManager = resolvePackageManager(cwd);
5277
+ for (const scriptName of Object.keys(scripts)) {
5278
+ if (typeof scripts[scriptName] !== "string") continue;
5279
+ const value = formatPackageScriptCommand(packageManager, scriptName);
5280
+ addCommand({
5281
+ label: value,
5282
+ value,
5283
+ source: "package"
5284
+ });
5285
+ }
5286
+ } catch {
5287
+ }
5288
+ }
5289
+ async function addMakefileCommands(cwd, addCommand) {
5290
+ const makefilePath = existsSync4(join6(cwd, "Makefile")) ? join6(cwd, "Makefile") : existsSync4(join6(cwd, "makefile")) ? join6(cwd, "makefile") : "";
5291
+ if (!makefilePath) return;
5292
+ try {
5293
+ const raw = await readFile3(makefilePath, "utf8");
5294
+ for (const line of raw.split(/\r?\n/)) {
5295
+ const match = /^([A-Za-z0-9_.@%/+~-][A-Za-z0-9_.@%/+~-]*)\s*:(?![=])/.exec(line);
5296
+ if (!match) continue;
5297
+ const target = match[1];
5298
+ if (!target || target.startsWith(".")) continue;
5299
+ const value = `make ${quoteShellTokenIfNeeded(target)}`;
5300
+ addCommand({
5301
+ label: value,
5302
+ value,
5303
+ source: "make"
5304
+ });
5305
+ }
5306
+ } catch {
5307
+ }
5308
+ }
5309
+ async function addRootScriptCommands(cwd, addCommand) {
5310
+ await addScriptFileCommands(cwd, ".", addCommand);
5311
+ }
5312
+ async function addScriptsDirectoryCommands(cwd, addCommand) {
5313
+ await addScriptFileCommands(join6(cwd, "scripts"), "./scripts", addCommand);
5314
+ }
5315
+ async function addScriptFileCommands(directory, commandPrefix, addCommand) {
5316
+ try {
5317
+ const entries = await readdir2(directory, { withFileTypes: true });
5318
+ for (const entry of entries) {
5319
+ if (!entry.isFile()) continue;
5320
+ if (!entry.name.endsWith(".sh") && !entry.name.endsWith(".cmd")) continue;
5321
+ const value = `${commandPrefix}/${quoteShellTokenIfNeeded(entry.name)}`;
5322
+ addCommand({
5323
+ label: value,
5324
+ value,
5325
+ source: "script"
5326
+ });
5327
+ }
5328
+ } catch {
5329
+ }
5330
+ }
5331
+ function resolvePackageManager(cwd) {
5332
+ if (existsSync4(join6(cwd, "pnpm-lock.yaml"))) return "pnpm";
5333
+ if (existsSync4(join6(cwd, "yarn.lock"))) return "yarn";
5334
+ if (existsSync4(join6(cwd, "bun.lock")) || existsSync4(join6(cwd, "bun.lockb"))) return "bun";
5335
+ return "npm";
5336
+ }
5337
+ function formatPackageScriptCommand(packageManager, scriptName) {
5338
+ const quoted = quoteShellTokenIfNeeded(scriptName);
5339
+ if (packageManager === "npm") return `npm run ${quoted}`;
5340
+ if (packageManager === "pnpm") return `pnpm run ${quoted}`;
5341
+ if (packageManager === "bun") return `bun run ${quoted}`;
5342
+ return `yarn ${quoted}`;
5343
+ }
5344
+ function quoteShellTokenIfNeeded(value) {
5345
+ return /^[A-Za-z0-9_./:@-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
5346
+ }
5347
+ function readBoolean2(value) {
5348
+ return value === true;
5349
+ }
5350
+ function readNumber2(value) {
5351
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
5352
+ }
5353
+ function resolveComposioCommand() {
5354
+ const candidates = [
5355
+ process.env.CODEXUI_COMPOSIO_COMMAND?.trim() ?? "",
5356
+ join6(homedir5(), ".composio", "composio"),
5357
+ "composio"
5358
+ ];
5359
+ for (const candidate of candidates) {
5360
+ if (!candidate) continue;
5361
+ if ((candidate.includes("/") || candidate.includes("\\")) && !existsSync4(candidate)) continue;
5362
+ const invocation = getSpawnInvocation(candidate, ["--version"]);
5363
+ const probe = spawnSync4(invocation.command, invocation.args, {
5364
+ stdio: "ignore",
5365
+ windowsHide: true
5366
+ });
5367
+ if (!probe.error && probe.status === 0) {
5368
+ return candidate;
5369
+ }
5370
+ }
5371
+ return null;
5372
+ }
5373
+ function parseComposioJson(stdout, fallback) {
5374
+ const trimmed = stdout.trim();
5375
+ if (!trimmed) {
5376
+ throw new Error(fallback);
5377
+ }
5378
+ return JSON.parse(trimmed);
5379
+ }
5380
+ async function runComposioJson(args, fallback) {
5381
+ const command = resolveComposioCommand();
5382
+ if (!command) {
5383
+ throw new Error("Composio CLI is not installed");
5384
+ }
5385
+ const invocation = getSpawnInvocation(command, args);
5386
+ const child = spawn4(invocation.command, invocation.args, {
5387
+ env: process.env,
5388
+ stdio: ["ignore", "pipe", "pipe"],
5389
+ windowsHide: true
5390
+ });
5391
+ let stdout = "";
5392
+ let stderr = "";
5393
+ child.stdout.setEncoding("utf8");
5394
+ child.stderr.setEncoding("utf8");
5395
+ child.stdout.on("data", (chunk) => {
5396
+ stdout += chunk;
5397
+ });
5398
+ child.stderr.on("data", (chunk) => {
5399
+ stderr += chunk;
5400
+ });
5401
+ const exitCode = await new Promise((resolveExit, reject) => {
5402
+ child.once("error", reject);
5403
+ child.once("close", (code) => resolveExit(code ?? 0));
5404
+ });
5405
+ if (exitCode !== 0) {
5406
+ throw new Error(stderr.trim() || stdout.trim() || fallback);
5407
+ }
5408
+ try {
5409
+ return parseComposioJson(stdout, fallback);
5410
+ } catch (error) {
5411
+ const details = stderr.trim() || stdout.trim();
5412
+ throw new Error(details || getErrorMessage5(error, fallback));
5413
+ }
5414
+ }
5415
+ async function readComposioUserData() {
5416
+ try {
5417
+ const raw = await readFile3(COMPOSIO_USER_DATA_PATH, "utf8");
5418
+ const payload = asRecord5(JSON.parse(raw));
5419
+ if (!payload) return null;
5420
+ return {
5421
+ apiKey: readNonEmptyString(payload.api_key),
5422
+ baseUrl: readNonEmptyString(payload.base_url),
5423
+ webUrl: readNonEmptyString(payload.web_url),
5424
+ orgId: readNonEmptyString(payload.org_id),
5425
+ testUserId: readNonEmptyString(payload.test_user_id)
5426
+ };
5427
+ } catch {
5428
+ return null;
5429
+ }
5430
+ }
5431
+ function normalizeComposioConnection(value) {
5432
+ const record = asRecord5(value);
5433
+ if (!record) return null;
5434
+ const authConfig = asRecord5(record.auth_config);
5435
+ return {
5436
+ id: readNonEmptyString(record.id),
5437
+ wordId: readNonEmptyString(record.word_id),
5438
+ alias: readNonEmptyString(record.alias),
5439
+ status: readNonEmptyString(record.status),
5440
+ authScheme: readNonEmptyString(record.authScheme || authConfig?.auth_scheme),
5441
+ createdAt: readNonEmptyString(record.created_at),
5442
+ updatedAt: readNonEmptyString(record.updated_at),
5443
+ isComposioManaged: readBoolean2(authConfig?.is_composio_managed),
5444
+ isDisabled: readBoolean2(record.is_disabled)
5445
+ };
5446
+ }
5447
+ function normalizeComposioToolkit(value, connectionsBySlug) {
5448
+ const record = asRecord5(value);
5449
+ if (!record) return null;
5450
+ const slug = readNonEmptyString(record.slug);
5451
+ if (!slug) return null;
5452
+ const connectionRows = connectionsBySlug.get(slug) ?? [];
5453
+ return {
5454
+ slug,
5455
+ name: readNonEmptyString(record.name),
5456
+ description: readNonEmptyString(record.description),
5457
+ logoUrl: readNonEmptyString(record.logo || record.meta && asRecord5(record.meta)?.logo),
5458
+ latestVersion: readNonEmptyString(record.latest_version || record.latestVersion),
5459
+ toolsCount: readNumber2(record.tools_count),
5460
+ triggersCount: readNumber2(record.triggers_count),
5461
+ isNoAuth: readBoolean2(record.is_no_auth),
5462
+ enabled: record.enabled !== false,
5463
+ authModes: Array.isArray(record.auth_modes) ? record.auth_modes.map(readNonEmptyString).filter(Boolean) : [],
5464
+ activeCount: connectionRows.filter((row) => row.status === "ACTIVE" && !row.isDisabled).length,
5465
+ totalConnections: connectionRows.length,
5466
+ connectionStatuses: [...new Set(connectionRows.map((row) => row.status).filter(Boolean))]
5467
+ };
5468
+ }
5469
+ function normalizeComposioTool(value) {
5470
+ const record = asRecord5(value);
5471
+ if (!record) return null;
5472
+ const slug = readNonEmptyString(record.slug);
5473
+ if (!slug) return null;
5474
+ return {
5475
+ slug,
5476
+ name: readNonEmptyString(record.name),
5477
+ description: readNonEmptyString(record.description)
5478
+ };
5479
+ }
5480
+ async function readComposioConnectionsBySlug() {
5481
+ const payload = asRecord5(await runComposioJson(["connections", "list"], "Failed to list Composio connections"));
5482
+ const bySlug = /* @__PURE__ */ new Map();
5483
+ for (const [slug, rawRows] of Object.entries(payload ?? {})) {
5484
+ if (!Array.isArray(rawRows)) continue;
5485
+ const rows = rawRows.map(normalizeComposioConnection).filter((row) => row !== null);
5486
+ bySlug.set(slug, rows);
5487
+ }
5488
+ return bySlug;
5489
+ }
5490
+ async function readComposioStatus() {
5491
+ const cliVersion = (() => {
5492
+ const command = resolveComposioCommand();
5493
+ if (!command) return "";
5494
+ const invocation = getSpawnInvocation(command, ["--version"]);
5495
+ const probe = spawnSync4(invocation.command, invocation.args, {
5496
+ encoding: "utf8",
5497
+ windowsHide: true
5498
+ });
5499
+ return probe.status === 0 ? probe.stdout.trim() : "";
5500
+ })();
5501
+ const userData = await readComposioUserData();
5502
+ if (!resolveComposioCommand()) {
5503
+ return {
5504
+ available: false,
5505
+ authenticated: false,
5506
+ cliVersion,
5507
+ email: "",
5508
+ defaultOrgName: "",
5509
+ defaultOrgId: userData?.orgId ?? "",
5510
+ webUrl: userData?.webUrl ?? "",
5511
+ baseUrl: userData?.baseUrl ?? "",
5512
+ testUserId: userData?.testUserId ?? ""
5513
+ };
5514
+ }
5515
+ try {
5516
+ const payload = asRecord5(await runComposioJson(["whoami"], "Failed to read Composio account status"));
5517
+ return {
5518
+ available: true,
5519
+ authenticated: true,
5520
+ cliVersion,
5521
+ email: readNonEmptyString(payload?.email),
5522
+ defaultOrgName: readNonEmptyString(payload?.default_org_name),
5523
+ defaultOrgId: readNonEmptyString(payload?.default_org_id) || userData?.orgId || "",
5524
+ webUrl: userData?.webUrl || "https://dashboard.composio.dev/",
5525
+ baseUrl: userData?.baseUrl || "https://backend.composio.dev",
5526
+ testUserId: readNonEmptyString(payload?.test_user_id) || userData?.testUserId || ""
5527
+ };
5528
+ } catch {
5529
+ return {
5530
+ available: true,
5531
+ authenticated: false,
5532
+ cliVersion,
5533
+ email: "",
5534
+ defaultOrgName: "",
5535
+ defaultOrgId: userData?.orgId ?? "",
5536
+ webUrl: userData?.webUrl || "https://dashboard.composio.dev/",
5537
+ baseUrl: userData?.baseUrl || "https://backend.composio.dev",
5538
+ testUserId: userData?.testUserId ?? ""
5539
+ };
5540
+ }
5541
+ }
5542
+ async function listComposioConnectors(query, cursor = null, limit = 50) {
5543
+ const args = ["dev", "toolkits", "list", "--limit", String(COMPOSIO_CONNECTORS_PAGE_LIMIT_MAX)];
5544
+ const trimmedQuery = query.trim();
5545
+ if (trimmedQuery) {
5546
+ args.push("--query", trimmedQuery);
5547
+ }
5548
+ const [payload, connectionsBySlug] = await Promise.all([
5549
+ runComposioJson(args, "Failed to list Composio toolkits"),
5550
+ readComposioConnectionsBySlug()
5551
+ ]);
5552
+ const allRows = payload.map((item) => normalizeComposioToolkit(item, connectionsBySlug)).filter((row) => row !== null);
5553
+ const safeLimit = Number.isFinite(limit) ? Math.max(1, Math.min(COMPOSIO_CONNECTORS_PAGE_LIMIT_MAX, Math.floor(limit))) : 50;
5554
+ const safeCursor = parseComposioCursor(cursor, allRows.length);
5555
+ return {
5556
+ data: allRows.slice(safeCursor, safeCursor + safeLimit),
5557
+ nextCursor: safeCursor + safeLimit < allRows.length ? String(safeCursor + safeLimit) : null,
5558
+ total: allRows.length
5559
+ };
5560
+ }
5561
+ function parseComposioCursor(cursor, maxLength) {
5562
+ const trimmed = cursor?.trim() ?? "";
5563
+ const parsed = Number.parseInt(trimmed, 10);
5564
+ if (!Number.isFinite(parsed) || Number.isNaN(parsed) || parsed <= 0) return 0;
5565
+ if (parsed >= maxLength) return maxLength;
5566
+ return parsed;
5567
+ }
5568
+ function parseComposioLimit(rawLimit) {
5569
+ const parsed = Number.parseInt((rawLimit ?? "").trim(), 10);
5570
+ if (!Number.isFinite(parsed) || Number.isNaN(parsed) || parsed <= 0) return 50;
5571
+ return Math.max(1, Math.min(COMPOSIO_CONNECTORS_PAGE_LIMIT_MAX, parsed));
5572
+ }
5573
+ async function readComposioConnectorDetail(slug) {
5574
+ const normalizedSlug = slug.trim();
5575
+ if (!normalizedSlug) {
5576
+ throw new Error("Missing Composio connector slug");
5577
+ }
5578
+ const [infoPayload, toolsPayload, connectionsPayload, userData] = await Promise.all([
5579
+ runComposioJson(["dev", "toolkits", "info", normalizedSlug], `Failed to load Composio toolkit ${normalizedSlug}`),
5580
+ runComposioJson(["tools", "list", normalizedSlug, "--limit", "10"], `Failed to list tools for ${normalizedSlug}`),
5581
+ runComposioJson(["link", normalizedSlug, "--list"], `Failed to list connections for ${normalizedSlug}`),
5582
+ readComposioUserData()
5583
+ ]);
5584
+ const connections = Array.isArray(connectionsPayload.items) ? connectionsPayload.items.map(normalizeComposioConnection).filter((row) => row !== null) : [];
5585
+ const connector = normalizeComposioToolkit(infoPayload, /* @__PURE__ */ new Map([[normalizedSlug, connections]]));
5586
+ if (!connector) {
5587
+ throw new Error(`Unknown Composio connector: ${normalizedSlug}`);
5588
+ }
5589
+ return {
5590
+ connector,
5591
+ connections,
5592
+ tools: Array.isArray(toolsPayload) ? toolsPayload.map(normalizeComposioTool).filter((row) => row !== null) : [],
5593
+ dashboardUrl: userData?.webUrl || "https://dashboard.composio.dev/"
5594
+ };
5595
+ }
5596
+ async function startComposioLink(slug) {
5597
+ const normalizedSlug = slug.trim();
5598
+ if (!normalizedSlug) {
5599
+ throw new Error("Missing Composio connector slug");
5600
+ }
5601
+ const payload = asRecord5(await runComposioJson(["link", normalizedSlug, "--no-wait"], `Failed to start Composio link for ${normalizedSlug}`));
5602
+ return {
5603
+ status: readNonEmptyString(payload?.status),
5604
+ message: readNonEmptyString(payload?.message),
5605
+ connectedAccountId: readNonEmptyString(payload?.connected_account_id),
5606
+ redirectUrl: readNonEmptyString(payload?.redirect_url),
5607
+ toolkit: readNonEmptyString(payload?.toolkit),
5608
+ projectType: readNonEmptyString(payload?.project_type)
5609
+ };
5610
+ }
5611
+ async function startComposioLogin() {
5612
+ const command = resolveComposioCommand();
5613
+ if (!command) {
5614
+ throw new Error("Composio CLI is not installed");
5615
+ }
5616
+ const invocation = getSpawnInvocation(command, ["login", "-y"]);
5617
+ const proc = spawn4(invocation.command, invocation.args, {
5618
+ cwd: process.cwd(),
5619
+ env: process.env,
5620
+ detached: true,
5621
+ stdio: "ignore",
5622
+ windowsHide: true
5623
+ });
5624
+ proc.unref();
5625
+ return {
5626
+ status: "started",
5627
+ message: "Composio CLI login started",
5628
+ loginUrl: "",
5629
+ cliKey: "",
5630
+ expiresAt: ""
5631
+ };
5632
+ }
5633
+ async function installComposioCli() {
5634
+ const command = "bash";
5635
+ const installScriptUrl = "https://composio.dev/install";
5636
+ const args = ["-lc", `curl -fsSL ${installScriptUrl} | bash`];
5637
+ const invocation = getSpawnInvocation(command, args);
5638
+ const env = {
5639
+ ...process.env,
5640
+ COMPOSIO_INSTALL_DIR: process.env.COMPOSIO_INSTALL_DIR?.trim() || join6(homedir5(), ".composio")
5641
+ };
5642
+ const result = spawnSync4(invocation.command, invocation.args, {
5643
+ encoding: "utf8",
5644
+ env,
5645
+ windowsHide: true
5646
+ });
5647
+ const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
5648
+ if (result.error || result.status !== 0) {
5649
+ throw new Error(output || result.error?.message || "Failed to install Composio CLI");
5650
+ }
5651
+ return {
5652
+ ok: true,
5653
+ command: `curl -fsSL ${installScriptUrl} | bash`,
5654
+ output
5655
+ };
5656
+ }
5339
5657
  function countRecoveredContentLines(value) {
5340
5658
  if (!value) return 0;
5341
5659
  const normalized = value.replace(/\r\n/g, "\n");
@@ -5927,54 +6245,6 @@ function scoreFileCandidate(path, query) {
5927
6245
  if (lowerPath.includes(lowerQuery)) return 4;
5928
6246
  return 10;
5929
6247
  }
5930
- function decodeHtmlEntities(value) {
5931
- return value.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&#x2F;/gi, "/");
5932
- }
5933
- function stripHtml(value) {
5934
- return decodeHtmlEntities(value.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim());
5935
- }
5936
- function parseGithubTrendingHtml(html, limit) {
5937
- const rows = html.match(/<article[\s\S]*?<\/article>/g) ?? [];
5938
- const items = [];
5939
- let seq = Date.now();
5940
- for (const row of rows) {
5941
- const repoBlockMatch = row.match(/<h2[\s\S]*?<\/h2>/);
5942
- const hrefMatch = repoBlockMatch?.[0]?.match(/href="\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)"/);
5943
- if (!hrefMatch) continue;
5944
- const fullName = hrefMatch[1] ?? "";
5945
- if (!fullName || items.some((item) => item.fullName === fullName)) continue;
5946
- const descriptionMatch = row.match(/<p[^>]*class="[^"]*col-9[^"]*"[^>]*>([\s\S]*?)<\/p>/) ?? row.match(/<p[^>]*class="[^"]*color-fg-muted[^"]*"[^>]*>([\s\S]*?)<\/p>/) ?? row.match(/<p[^>]*>([\s\S]*?)<\/p>/);
5947
- const languageMatch = row.match(/programmingLanguage[^>]*>\s*([\s\S]*?)\s*<\/span>/);
5948
- const starsMatch = row.match(/href="\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/stargazers"[\s\S]*?>([\s\S]*?)<\/a>/);
5949
- const starsText = stripHtml(starsMatch?.[1] ?? "").replace(/,/g, "");
5950
- const stars = Number.parseInt(starsText, 10);
5951
- items.push({
5952
- id: seq,
5953
- fullName,
5954
- url: `https://github.com/${fullName}`,
5955
- description: stripHtml(descriptionMatch?.[1] ?? ""),
5956
- language: stripHtml(languageMatch?.[1] ?? ""),
5957
- stars: Number.isFinite(stars) ? stars : 0
5958
- });
5959
- seq += 1;
5960
- if (items.length >= limit) break;
5961
- }
5962
- return items;
5963
- }
5964
- async function fetchGithubTrending(since, limit) {
5965
- const endpoint = `https://github.com/trending?since=${since}`;
5966
- const response = await fetch(endpoint, {
5967
- headers: {
5968
- "User-Agent": "codex-web-local",
5969
- Accept: "text/html"
5970
- }
5971
- });
5972
- if (!response.ok) {
5973
- throw new Error(`GitHub trending fetch failed (${response.status})`);
5974
- }
5975
- const html = await response.text();
5976
- return parseGithubTrendingHtml(html, limit);
5977
- }
5978
6248
  async function listFilesWithRipgrep(cwd) {
5979
6249
  return await new Promise((resolve4, reject) => {
5980
6250
  const ripgrepCommand = resolveRipgrepCommand();
@@ -6011,6 +6281,79 @@ function getCodexHomeDir3() {
6011
6281
  const codexHome = process.env.CODEX_HOME?.trim();
6012
6282
  return codexHome && codexHome.length > 0 ? codexHome : join6(homedir5(), ".codex");
6013
6283
  }
6284
+ function getPromptsDir() {
6285
+ return join6(getCodexHomeDir3(), "prompts");
6286
+ }
6287
+ function promptNameToFileName(name) {
6288
+ const trimmed = name.trim();
6289
+ const withoutExtension = trimmed.replace(/\.md$/i, "");
6290
+ const sanitized = withoutExtension.replace(/[\/\\:*?"<>|]/g, " ").replace(/\s+/g, " ").trim();
6291
+ return `${sanitized || "prompt"}.md`;
6292
+ }
6293
+ function buildPromptDescription(content) {
6294
+ const firstNonEmptyLine = content.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "";
6295
+ return firstNonEmptyLine.slice(0, 120);
6296
+ }
6297
+ async function listComposerPrompts() {
6298
+ const promptsDir = getPromptsDir();
6299
+ try {
6300
+ const entries = await readdir2(promptsDir, { withFileTypes: true });
6301
+ const prompts = await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md")).map(async (entry) => {
6302
+ const promptPath = join6(promptsDir, entry.name);
6303
+ const content = await readFile3(promptPath, "utf8");
6304
+ return {
6305
+ name: entry.name.replace(/\.md$/i, ""),
6306
+ path: promptPath,
6307
+ content,
6308
+ description: buildPromptDescription(content)
6309
+ };
6310
+ }));
6311
+ return prompts.sort((a, b) => a.name.localeCompare(b.name));
6312
+ } catch (error) {
6313
+ if (error?.code === "ENOENT") return [];
6314
+ throw error;
6315
+ }
6316
+ }
6317
+ async function createComposerPromptFile(name, content) {
6318
+ const trimmedName = name.trim();
6319
+ if (!trimmedName) throw new Error("Prompt name is required");
6320
+ const trimmedContent = content.trim();
6321
+ if (!trimmedContent) throw new Error("Prompt content is required");
6322
+ const promptsDir = getPromptsDir();
6323
+ await mkdir4(promptsDir, { recursive: true });
6324
+ const baseFileName = promptNameToFileName(trimmedName);
6325
+ let targetPath = join6(promptsDir, baseFileName);
6326
+ let suffix = 2;
6327
+ while (existsSync4(targetPath)) {
6328
+ const nextFileName = `${baseFileName.replace(/\.md$/i, "")}-${suffix}.md`;
6329
+ targetPath = join6(promptsDir, nextFileName);
6330
+ suffix += 1;
6331
+ }
6332
+ await writeFile4(targetPath, `${trimmedContent}
6333
+ `, "utf8");
6334
+ return {
6335
+ name: basename4(targetPath).replace(/\.md$/i, ""),
6336
+ path: targetPath,
6337
+ content: `${trimmedContent}
6338
+ `,
6339
+ description: buildPromptDescription(trimmedContent)
6340
+ };
6341
+ }
6342
+ async function removeComposerPromptFile(promptPath) {
6343
+ const resolvedPath = resolve2(promptPath);
6344
+ const promptsDir = resolve2(getPromptsDir());
6345
+ const relative = resolvedPath.startsWith(`${promptsDir}/`) ? resolvedPath.slice(promptsDir.length + 1) : "";
6346
+ if (!relative || relative.includes("..") || !resolvedPath.toLowerCase().endsWith(".md")) {
6347
+ throw new Error("Invalid prompt path");
6348
+ }
6349
+ try {
6350
+ await rm4(resolvedPath, { force: false });
6351
+ return true;
6352
+ } catch (error) {
6353
+ if (error?.code === "ENOENT") return false;
6354
+ throw error;
6355
+ }
6356
+ }
6014
6357
  async function runCommand3(command, args, options = {}) {
6015
6358
  await new Promise((resolve4, reject) => {
6016
6359
  const proc = spawn4(command, args, {
@@ -6410,6 +6753,109 @@ async function writePinnedThreadIds(threadIds) {
6410
6753
  payload[PINNED_THREAD_IDS_KEY] = normalizePinnedThreadIds(threadIds);
6411
6754
  await writeFile4(statePath, JSON.stringify(payload), "utf8");
6412
6755
  }
6756
+ var FIRST_LAUNCH_PLUGINS_CARD_DISMISSED_KEY = "first-launch-plugins-card-dismissed";
6757
+ var THREAD_QUEUE_STATE_KEY = "thread-queue-state";
6758
+ function normalizeStoredQueuedMessage(value) {
6759
+ const record = asRecord5(value);
6760
+ if (!record) return null;
6761
+ const id = typeof record.id === "string" ? record.id.trim() : "";
6762
+ if (!id) return null;
6763
+ const normalizeNamedPathItems = (items) => {
6764
+ if (!Array.isArray(items)) return [];
6765
+ return items.flatMap((item) => {
6766
+ const itemRecord = asRecord5(item);
6767
+ if (!itemRecord) return [];
6768
+ const name = typeof itemRecord.name === "string" ? itemRecord.name.trim() : "";
6769
+ const path = typeof itemRecord.path === "string" ? itemRecord.path.trim() : "";
6770
+ return name && path ? [{ name, path }] : [];
6771
+ });
6772
+ };
6773
+ const normalizeFileAttachments = (items) => {
6774
+ if (!Array.isArray(items)) return [];
6775
+ return items.flatMap((item) => {
6776
+ const itemRecord = asRecord5(item);
6777
+ if (!itemRecord) return [];
6778
+ const label = typeof itemRecord.label === "string" ? itemRecord.label.trim() : "";
6779
+ const path = typeof itemRecord.path === "string" ? itemRecord.path.trim() : "";
6780
+ const fsPath = typeof itemRecord.fsPath === "string" ? itemRecord.fsPath.trim() : "";
6781
+ return label && path && fsPath ? [{ label, path, fsPath }] : [];
6782
+ });
6783
+ };
6784
+ return {
6785
+ id,
6786
+ text: typeof record.text === "string" ? record.text : "",
6787
+ imageUrls: normalizeStringArray(record.imageUrls),
6788
+ skills: normalizeNamedPathItems(record.skills),
6789
+ fileAttachments: normalizeFileAttachments(record.fileAttachments),
6790
+ collaborationMode: record.collaborationMode === "plan" ? "plan" : "default"
6791
+ };
6792
+ }
6793
+ function normalizeThreadQueueState(value) {
6794
+ const record = asRecord5(value);
6795
+ if (!record) return {};
6796
+ const state = {};
6797
+ for (const [threadId, rawMessages] of Object.entries(record)) {
6798
+ const normalizedThreadId = threadId.trim();
6799
+ if (!normalizedThreadId || !Array.isArray(rawMessages)) continue;
6800
+ const messages = rawMessages.flatMap((item) => {
6801
+ const message = normalizeStoredQueuedMessage(item);
6802
+ return message ? [message] : [];
6803
+ });
6804
+ if (messages.length > 0) {
6805
+ state[normalizedThreadId] = messages;
6806
+ }
6807
+ }
6808
+ return state;
6809
+ }
6810
+ async function readThreadQueueState() {
6811
+ const statePath = getCodexGlobalStatePath();
6812
+ try {
6813
+ const raw = await readFile3(statePath, "utf8");
6814
+ const payload = asRecord5(JSON.parse(raw)) ?? {};
6815
+ return normalizeThreadQueueState(payload[THREAD_QUEUE_STATE_KEY]);
6816
+ } catch {
6817
+ return {};
6818
+ }
6819
+ }
6820
+ async function writeThreadQueueState(nextState) {
6821
+ const statePath = getCodexGlobalStatePath();
6822
+ let payload = {};
6823
+ try {
6824
+ const raw = await readFile3(statePath, "utf8");
6825
+ payload = asRecord5(JSON.parse(raw)) ?? {};
6826
+ } catch {
6827
+ payload = {};
6828
+ }
6829
+ const normalized = normalizeThreadQueueState(nextState);
6830
+ if (Object.keys(normalized).length > 0) {
6831
+ payload[THREAD_QUEUE_STATE_KEY] = normalized;
6832
+ } else {
6833
+ delete payload[THREAD_QUEUE_STATE_KEY];
6834
+ }
6835
+ await writeFile4(statePath, JSON.stringify(payload), "utf8");
6836
+ }
6837
+ async function readFirstLaunchPluginsCardDismissed() {
6838
+ const statePath = getCodexGlobalStatePath();
6839
+ try {
6840
+ const raw = await readFile3(statePath, "utf8");
6841
+ const payload = asRecord5(JSON.parse(raw)) ?? {};
6842
+ return payload[FIRST_LAUNCH_PLUGINS_CARD_DISMISSED_KEY] === true;
6843
+ } catch {
6844
+ return false;
6845
+ }
6846
+ }
6847
+ async function writeFirstLaunchPluginsCardDismissed(dismissed) {
6848
+ const statePath = getCodexGlobalStatePath();
6849
+ let payload = {};
6850
+ try {
6851
+ const raw = await readFile3(statePath, "utf8");
6852
+ payload = asRecord5(JSON.parse(raw)) ?? {};
6853
+ } catch {
6854
+ payload = {};
6855
+ }
6856
+ payload[FIRST_LAUNCH_PLUGINS_CARD_DISMISSED_KEY] = dismissed === true;
6857
+ await writeFile4(statePath, JSON.stringify(payload), "utf8");
6858
+ }
6413
6859
  function getSessionIndexFileSignature(stats) {
6414
6860
  return `${String(stats.mtimeMs)}:${String(stats.size)}`;
6415
6861
  }
@@ -7647,6 +8093,19 @@ function createCodexBridgeMiddleware() {
7647
8093
  setJson4(res, 200, terminalManager.getAvailability());
7648
8094
  return;
7649
8095
  }
8096
+ if (req.method === "GET" && url.pathname === "/codex-api/thread-terminal/quick-commands") {
8097
+ const cwd = url.searchParams.get("cwd")?.trim() ?? "";
8098
+ if (!cwd) {
8099
+ setJson4(res, 400, { error: "Missing cwd" });
8100
+ return;
8101
+ }
8102
+ try {
8103
+ setJson4(res, 200, { commands: await listTerminalQuickCommands(cwd) });
8104
+ } catch (error) {
8105
+ setJson4(res, 500, { error: getErrorMessage5(error, "Failed to load terminal quick commands") });
8106
+ }
8107
+ return;
8108
+ }
7650
8109
  if (req.method === "POST" && url.pathname === "/codex-api/thread-terminal/attach") {
7651
8110
  const availability = terminalManager.getAvailability();
7652
8111
  if (!availability.available) {
@@ -7948,6 +8407,60 @@ function createCodexBridgeMiddleware() {
7948
8407
  res.end(upstream.body);
7949
8408
  return;
7950
8409
  }
8410
+ if (req.method === "GET" && url.pathname === "/codex-api/composio/status") {
8411
+ try {
8412
+ setJson4(res, 200, await readComposioStatus());
8413
+ } catch (error) {
8414
+ setJson4(res, 500, { error: getErrorMessage5(error, "Failed to read Composio status") });
8415
+ }
8416
+ return;
8417
+ }
8418
+ if (req.method === "GET" && url.pathname === "/codex-api/composio/connectors") {
8419
+ try {
8420
+ const query = url.searchParams.get("query") ?? "";
8421
+ const cursor = url.searchParams.get("cursor")?.trim() ?? null;
8422
+ const limit = parseComposioLimit(url.searchParams.get("limit"));
8423
+ setJson4(res, 200, await listComposioConnectors(query, cursor, limit));
8424
+ } catch (error) {
8425
+ setJson4(res, 500, { error: getErrorMessage5(error, "Failed to list Composio connectors") });
8426
+ }
8427
+ return;
8428
+ }
8429
+ if (req.method === "GET" && url.pathname === "/codex-api/composio/connector") {
8430
+ try {
8431
+ const slug = url.searchParams.get("slug") ?? "";
8432
+ setJson4(res, 200, await readComposioConnectorDetail(slug));
8433
+ } catch (error) {
8434
+ setJson4(res, 500, { error: getErrorMessage5(error, "Failed to load Composio connector") });
8435
+ }
8436
+ return;
8437
+ }
8438
+ if (req.method === "POST" && url.pathname === "/codex-api/composio/link") {
8439
+ try {
8440
+ const payload = asRecord5(await readJsonBody(req));
8441
+ const slug = readNonEmptyString(payload?.slug);
8442
+ setJson4(res, 200, await startComposioLink(slug));
8443
+ } catch (error) {
8444
+ setJson4(res, 500, { error: getErrorMessage5(error, "Failed to start Composio login") });
8445
+ }
8446
+ return;
8447
+ }
8448
+ if (req.method === "POST" && url.pathname === "/codex-api/composio/login") {
8449
+ try {
8450
+ setJson4(res, 200, await startComposioLogin());
8451
+ } catch (error) {
8452
+ setJson4(res, 500, { error: getErrorMessage5(error, "Failed to start Composio CLI login") });
8453
+ }
8454
+ return;
8455
+ }
8456
+ if (req.method === "POST" && url.pathname === "/codex-api/composio/install") {
8457
+ try {
8458
+ setJson4(res, 200, await installComposioCli());
8459
+ } catch (error) {
8460
+ setJson4(res, 500, { error: getErrorMessage5(error, "Failed to install Composio CLI") });
8461
+ }
8462
+ return;
8463
+ }
7951
8464
  if (req.method === "GET" && url.pathname === "/codex-api/connector-logo") {
7952
8465
  const src = url.searchParams.get("src")?.trim() ?? "";
7953
8466
  if (!src) {
@@ -8046,21 +8559,13 @@ function createCodexBridgeMiddleware() {
8046
8559
  setJson4(res, 200, { data: state });
8047
8560
  return;
8048
8561
  }
8049
- if (req.method === "GET" && url.pathname === "/codex-api/home-directory") {
8050
- setJson4(res, 200, { data: { path: homedir5() } });
8562
+ if (req.method === "GET" && url.pathname === "/codex-api/thread-queue-state") {
8563
+ const state = await readThreadQueueState();
8564
+ setJson4(res, 200, { data: state });
8051
8565
  return;
8052
8566
  }
8053
- if (req.method === "GET" && url.pathname === "/codex-api/github-trending") {
8054
- const sinceRaw = (url.searchParams.get("since") ?? "").trim().toLowerCase();
8055
- const since = sinceRaw === "weekly" ? "weekly" : sinceRaw === "monthly" ? "monthly" : "daily";
8056
- const limitRaw = Number.parseInt((url.searchParams.get("limit") ?? "6").trim(), 10);
8057
- const limit = Number.isFinite(limitRaw) ? Math.max(1, Math.min(10, limitRaw)) : 6;
8058
- try {
8059
- const data = await fetchGithubTrending(since, limit);
8060
- setJson4(res, 200, { data });
8061
- } catch (error) {
8062
- setJson4(res, 502, { error: getErrorMessage5(error, "Failed to fetch GitHub trending") });
8063
- }
8567
+ if (req.method === "GET" && url.pathname === "/codex-api/home-directory") {
8568
+ setJson4(res, 200, { data: { path: homedir5() } });
8064
8569
  return;
8065
8570
  }
8066
8571
  if (req.method === "POST" && url.pathname === "/codex-api/worktree/create") {
@@ -8322,6 +8827,17 @@ function createCodexBridgeMiddleware() {
8322
8827
  setJson4(res, 200, { ok: true });
8323
8828
  return;
8324
8829
  }
8830
+ if (req.method === "PUT" && url.pathname === "/codex-api/thread-queue-state") {
8831
+ const payload = await readJsonBody(req);
8832
+ const record = asRecord5(payload);
8833
+ if (!record) {
8834
+ setJson4(res, 400, { error: "Invalid body: expected object" });
8835
+ return;
8836
+ }
8837
+ await writeThreadQueueState(normalizeThreadQueueState(record));
8838
+ setJson4(res, 200, { ok: true });
8839
+ return;
8840
+ }
8325
8841
  if (req.method === "POST" && url.pathname === "/codex-api/project-root") {
8326
8842
  const payload = asRecord5(await readJsonBody(req));
8327
8843
  const rawPath = typeof payload?.path === "string" ? payload.path.trim() : "";
@@ -8383,6 +8899,17 @@ function createCodexBridgeMiddleware() {
8383
8899
  setJson4(res, 200, { data: { path: normalizedPath } });
8384
8900
  return;
8385
8901
  }
8902
+ if (req.method === "POST" && url.pathname === "/codex-api/projectless-thread-cwd") {
8903
+ const payload = asRecord5(await readJsonBody(req));
8904
+ const prompt = typeof payload?.prompt === "string" ? payload.prompt : null;
8905
+ try {
8906
+ const directory = await createProjectlessThreadDirectory(prompt);
8907
+ setJson4(res, 200, { data: directory });
8908
+ } catch (error) {
8909
+ setJson4(res, 500, { error: error instanceof Error ? error.message : "Failed to create new chat folder" });
8910
+ }
8911
+ return;
8912
+ }
8386
8913
  if (req.method === "GET" && url.pathname === "/codex-api/project-root-suggestion") {
8387
8914
  const basePath = url.searchParams.get("basePath")?.trim() ?? "";
8388
8915
  if (!basePath) {
@@ -8446,6 +8973,40 @@ function createCodexBridgeMiddleware() {
8446
8973
  }
8447
8974
  return;
8448
8975
  }
8976
+ if (req.method === "GET" && url.pathname === "/codex-api/prompts") {
8977
+ setJson4(res, 200, { data: await listComposerPrompts() });
8978
+ return;
8979
+ }
8980
+ if (req.method === "POST" && url.pathname === "/codex-api/prompts") {
8981
+ const payload = asRecord5(await readJsonBody(req));
8982
+ const name = typeof payload?.name === "string" ? payload.name.trim() : "";
8983
+ const content = typeof payload?.content === "string" ? payload.content : "";
8984
+ if (!name || !content.trim()) {
8985
+ setJson4(res, 400, { error: "Prompt name and content are required" });
8986
+ return;
8987
+ }
8988
+ try {
8989
+ const prompt = await createComposerPromptFile(name, content);
8990
+ setJson4(res, 200, { data: prompt });
8991
+ } catch (error) {
8992
+ setJson4(res, 500, { error: getErrorMessage5(error, "Failed to create prompt") });
8993
+ }
8994
+ return;
8995
+ }
8996
+ if (req.method === "DELETE" && url.pathname === "/codex-api/prompts") {
8997
+ const promptPath = url.searchParams.get("path")?.trim() ?? "";
8998
+ if (!promptPath) {
8999
+ setJson4(res, 400, { error: "Missing path" });
9000
+ return;
9001
+ }
9002
+ try {
9003
+ const removed = await removeComposerPromptFile(promptPath);
9004
+ setJson4(res, 200, { data: { removed } });
9005
+ } catch (error) {
9006
+ setJson4(res, 400, { error: getErrorMessage5(error, "Failed to remove prompt") });
9007
+ }
9008
+ return;
9009
+ }
8449
9010
  if (req.method === "GET" && url.pathname === "/codex-api/thread-titles") {
8450
9011
  const cache = await readMergedThreadTitleCache();
8451
9012
  setJson4(res, 200, { data: cache });
@@ -8456,6 +9017,11 @@ function createCodexBridgeMiddleware() {
8456
9017
  setJson4(res, 200, { data: { threadIds } });
8457
9018
  return;
8458
9019
  }
9020
+ if (req.method === "GET" && url.pathname === "/codex-api/preferences/first-launch-plugins-card") {
9021
+ const dismissed = await readFirstLaunchPluginsCardDismissed();
9022
+ setJson4(res, 200, { data: { dismissed } });
9023
+ return;
9024
+ }
8459
9025
  if (req.method === "GET" && url.pathname === "/codex-api/thread-automations") {
8460
9026
  const automationsByThreadId = await listThreadHeartbeatAutomations();
8461
9027
  setJson4(res, 200, { data: automationsByThreadId });
@@ -8506,6 +9072,13 @@ function createCodexBridgeMiddleware() {
8506
9072
  setJson4(res, 200, { ok: true });
8507
9073
  return;
8508
9074
  }
9075
+ if (req.method === "PUT" && url.pathname === "/codex-api/preferences/first-launch-plugins-card") {
9076
+ const payload = asRecord5(await readJsonBody(req));
9077
+ const dismissed = payload?.dismissed === true;
9078
+ await writeFirstLaunchPluginsCardDismissed(dismissed);
9079
+ setJson4(res, 200, { ok: true });
9080
+ return;
9081
+ }
8509
9082
  if (req.method === "PUT" && url.pathname === "/codex-api/thread-automation") {
8510
9083
  const payload = asRecord5(await readJsonBody(req));
8511
9084
  const threadId = typeof payload?.threadId === "string" ? payload.threadId.trim() : "";
@@ -8638,7 +9211,7 @@ data: ${JSON.stringify({ ok: true })}
8638
9211
 
8639
9212
  // src/server/authMiddleware.ts
8640
9213
  import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
8641
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "fs";
9214
+ import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "fs";
8642
9215
  import { homedir as homedir6 } from "os";
8643
9216
  import { dirname as dirname3, join as join7 } from "path";
8644
9217
  var TOKEN_COOKIE = "portal_session";
@@ -8701,7 +9274,7 @@ function getSessionStorePath() {
8701
9274
  }
8702
9275
  function readPersistedSessions() {
8703
9276
  const sessionStorePath = getSessionStorePath();
8704
- if (!existsSync4(sessionStorePath)) return /* @__PURE__ */ new Map();
9277
+ if (!existsSync5(sessionStorePath)) return /* @__PURE__ */ new Map();
8705
9278
  try {
8706
9279
  const raw = readFileSync3(sessionStorePath, "utf8");
8707
9280
  const parsed = JSON.parse(raw);
@@ -9437,7 +10010,7 @@ function createServer(options = {}) {
9437
10010
  res.status(404).json({ error: "File not found." });
9438
10011
  }
9439
10012
  });
9440
- const hasFrontendAssets = existsSync5(spaEntryFile);
10013
+ const hasFrontendAssets = existsSync6(spaEntryFile);
9441
10014
  if (hasFrontendAssets) {
9442
10015
  app.use(express.static(distDir));
9443
10016
  }
@@ -9516,7 +10089,7 @@ function getCloudflaredPromptMarkerPath() {
9516
10089
  return join10(getCodexHomePath(), ".cloudflared-install-prompted");
9517
10090
  }
9518
10091
  function hasPromptedCloudflaredInstallPersisted() {
9519
- return existsSync6(getCloudflaredPromptMarkerPath());
10092
+ return existsSync7(getCloudflaredPromptMarkerPath());
9520
10093
  }
9521
10094
  async function persistCloudflaredInstallPrompted() {
9522
10095
  const codexHome = getCodexHomePath();
@@ -9552,7 +10125,7 @@ function resolveCloudflaredCommand() {
9552
10125
  return "cloudflared";
9553
10126
  }
9554
10127
  const localCandidate = join10(homedir7(), ".local", "bin", "cloudflared");
9555
- if (existsSync6(localCandidate) && canRunCommand(localCandidate, ["--version"])) {
10128
+ if (existsSync7(localCandidate) && canRunCommand(localCandidate, ["--version"])) {
9556
10129
  return localCandidate;
9557
10130
  }
9558
10131
  return null;
@@ -9658,7 +10231,7 @@ async function resolveCloudflaredForTunnel() {
9658
10231
  }
9659
10232
  function hasCodexAuth() {
9660
10233
  const codexHome = getCodexHomePath();
9661
- return existsSync6(join10(codexHome, "auth.json"));
10234
+ return existsSync7(join10(codexHome, "auth.json"));
9662
10235
  }
9663
10236
  function ensureCodexInstalled() {
9664
10237
  let codexCommand = resolveCodexCommand();