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/assets/DirectoryHub-47WzZs4I.js +2 -0
- package/dist/assets/DirectoryHub-DkF7hkxl.css +1 -0
- package/dist/assets/{ReviewPane-BwO2MHvI.js → ReviewPane-W_UvYmui.js} +1 -1
- package/dist/assets/ThreadConversation-CSH0aUWx.js +39 -0
- package/dist/assets/ThreadConversation-DdzQ_R3z.css +1 -0
- package/dist/assets/{ThreadTerminalPanel-pVx2Zv0U.css → ThreadTerminalPanel-BfzQNi0v.css} +1 -1
- package/dist/assets/ThreadTerminalPanel-QkJZ7U3Y.js +38 -0
- package/dist/assets/index-B6Gx9sFJ.css +1 -0
- package/dist/assets/index-Cmr8liLe.js +63 -0
- package/dist/assets/{index.esm-DECIu6Fp.js → index.esm-BMAgl1KF.js} +1 -1
- package/dist/assets/{index.esm-DPq88-QA.js → index.esm-O-ekzJz9.js} +1 -16
- package/dist/assets/{index.esm-Bi-9KxvS.js → index.esm-TywpZG2x.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-cli/index.js +921 -348
- package/dist-cli/index.js.map +1 -1
- package/package.json +2 -1
- package/scripts/dev.cjs +11 -7
- package/dist/assets/DirectoryHub-CAcNjRYF.css +0 -1
- package/dist/assets/DirectoryHub-D6h4lJqw.js +0 -2
- package/dist/assets/ThreadConversation-CsHUh1AP.js +0 -39
- package/dist/assets/ThreadConversation-J3tJ8-9X.css +0 -1
- package/dist/assets/ThreadTerminalPanel-BTHfTRnz.js +0 -38
- package/dist/assets/index-23FodFfK.js +0 -64
- package/dist/assets/index-Wz7G59hk.css +0 -1
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
|
|
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
|
|
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
|
|
1893
|
-
if (
|
|
1894
|
-
return
|
|
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
|
|
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
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
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
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
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
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
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
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2775
|
-
setJson3(res, 200, {
|
|
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 ||
|
|
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 ||
|
|
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
|
|
2994
|
-
const
|
|
2995
|
-
setJson3(res, 200, { content
|
|
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
|
-
|
|
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
|
-
|
|
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(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(///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/
|
|
8050
|
-
|
|
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/
|
|
8054
|
-
|
|
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
|
|
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 (!
|
|
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 =
|
|
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
|
|
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 (
|
|
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
|
|
10234
|
+
return existsSync7(join10(codexHome, "auth.json"));
|
|
9662
10235
|
}
|
|
9663
10236
|
function ensureCodexInstalled() {
|
|
9664
10237
|
let codexCommand = resolveCodexCommand();
|