codexui-android 0.1.97 → 0.1.99
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-BsQI3jt6.js +2 -0
- package/dist/assets/DirectoryHub-DEzhdxiz.css +1 -0
- package/dist/assets/{ReviewPane-DuPX5OZA.css → ReviewPane-AzgWKh85.css} +1 -1
- package/dist/assets/ReviewPane-BkNGxMQY.js +1 -0
- package/dist/assets/ThreadConversation-08orN8b4.js +39 -0
- package/dist/assets/ThreadConversation-CJqnXrJL.css +1 -0
- package/dist/assets/{ThreadTerminalPanel-CGTJQ1BI.css → ThreadTerminalPanel-Bik-IBVJ.css} +1 -1
- package/dist/assets/{ThreadTerminalPanel-DjP4TSjB.js → ThreadTerminalPanel-Dw7SInGu.js} +12 -12
- package/dist/assets/index-BpQcV8Wg.css +1 -0
- package/dist/assets/index-Ce2BNria.js +63 -0
- package/dist/assets/{index.esm-Bi-9KxvS.js → index.esm--s_sgTDk.js} +2 -2
- package/dist/assets/{index.esm-DECIu6Fp.js → index.esm-D_uN0D-X.js} +1 -1
- package/dist/assets/{index.esm-DPq88-QA.js → index.esm-DvadSfWE.js} +3 -18
- package/dist/index.html +2 -2
- package/dist-cli/index.js +1173 -391
- package/dist-cli/index.js.map +1 -1
- package/package.json +2 -1
- package/scripts/dev.cjs +11 -7
- package/dist/assets/ReviewPane-CUrlR8zS.js +0 -1
- package/dist/assets/SkillsHub-BxltnLjE.js +0 -2
- package/dist/assets/SkillsHub-CTnWejwn.css +0 -1
- package/dist/assets/ThreadConversation-3WaRKicR.css +0 -1
- package/dist/assets/ThreadConversation-BNvqi1ws.js +0 -39
- package/dist/assets/index-Bqk3qCGk.js +0 -64
- package/dist/assets/index-CfRa3ma1.css +0 -1
package/dist-cli/index.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
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
|
-
import { homedir as
|
|
8
|
-
import { isAbsolute as isAbsolute4, join as
|
|
7
|
+
import { homedir as homedir7, networkInterfaces } from "os";
|
|
8
|
+
import { isAbsolute as isAbsolute4, join as join10, resolve as resolve3 } from "path";
|
|
9
9
|
import { spawn as spawn5 } from "child_process";
|
|
10
10
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
11
11
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
12
|
-
import { dirname as
|
|
12
|
+
import { dirname as dirname6 } from "path";
|
|
13
13
|
import { get as httpsGet } from "https";
|
|
14
14
|
import { Command } from "commander";
|
|
15
15
|
import qrcode from "qrcode-terminal";
|
|
@@ -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([
|
|
@@ -216,16 +185,16 @@ function parseApprovalPolicy(value) {
|
|
|
216
185
|
|
|
217
186
|
// src/server/httpServer.ts
|
|
218
187
|
import { fileURLToPath } from "url";
|
|
219
|
-
import { dirname as
|
|
220
|
-
import { existsSync as
|
|
188
|
+
import { dirname as dirname5, extname as extname3, isAbsolute as isAbsolute3, join as join9 } from "path";
|
|
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");
|
|
@@ -4830,13 +4702,42 @@ function asRecord5(value) {
|
|
|
4830
4702
|
function isInlineDataUrl(value) {
|
|
4831
4703
|
return /^data:/iu.test(value.trim());
|
|
4832
4704
|
}
|
|
4705
|
+
function inferImageMimeTypeFromBytes(bytes) {
|
|
4706
|
+
if (bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71 && bytes[4] === 13 && bytes[5] === 10 && bytes[6] === 26 && bytes[7] === 10) {
|
|
4707
|
+
return "image/png";
|
|
4708
|
+
}
|
|
4709
|
+
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
|
|
4710
|
+
return "image/jpeg";
|
|
4711
|
+
}
|
|
4712
|
+
if (bytes.length >= 12 && bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80) {
|
|
4713
|
+
return "image/webp";
|
|
4714
|
+
}
|
|
4715
|
+
if (bytes.length >= 6 && bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 56 && (bytes[4] === 55 || bytes[4] === 57) && bytes[5] === 97) {
|
|
4716
|
+
return "image/gif";
|
|
4717
|
+
}
|
|
4718
|
+
return null;
|
|
4719
|
+
}
|
|
4720
|
+
function inferImageMimeTypeFromBase64(value) {
|
|
4721
|
+
const compact = value.trim().replace(/\s+/gu, "");
|
|
4722
|
+
if (compact.length < 32 || !/^[A-Za-z0-9+/]+={0,2}$/u.test(compact)) return null;
|
|
4723
|
+
try {
|
|
4724
|
+
return inferImageMimeTypeFromBytes(Buffer.from(compact.slice(0, 64), "base64"));
|
|
4725
|
+
} catch {
|
|
4726
|
+
return null;
|
|
4727
|
+
}
|
|
4728
|
+
}
|
|
4833
4729
|
function normalizeBase64ImageDataUrl(value, mimeType) {
|
|
4834
4730
|
const trimmed = value.trim();
|
|
4835
4731
|
if (!trimmed) return null;
|
|
4836
|
-
if (isInlineDataUrl(trimmed))
|
|
4732
|
+
if (isInlineDataUrl(trimmed)) {
|
|
4733
|
+
return /^data:image\//iu.test(trimmed) ? trimmed : null;
|
|
4734
|
+
}
|
|
4837
4735
|
const compact = trimmed.replace(/\s+/gu, "");
|
|
4838
|
-
|
|
4839
|
-
return
|
|
4736
|
+
const inferredMimeType = inferImageMimeTypeFromBase64(compact);
|
|
4737
|
+
if (!inferredMimeType) return null;
|
|
4738
|
+
const normalizedMimeType = mimeType.trim().toLowerCase();
|
|
4739
|
+
const finalMimeType = normalizedMimeType.startsWith("image/") && normalizedMimeType !== "image/*" ? normalizedMimeType : inferredMimeType;
|
|
4740
|
+
return `data:${finalMimeType};base64,${compact}`;
|
|
4840
4741
|
}
|
|
4841
4742
|
function extensionFromMimeType(mimeType) {
|
|
4842
4743
|
const normalized = mimeType.trim().toLowerCase();
|
|
@@ -4888,6 +4789,30 @@ async function persistInlineDataUrlToLocalFile(dataUrl, baseName) {
|
|
|
4888
4789
|
function toLocalImageProxyUrl(path) {
|
|
4889
4790
|
return `/codex-local-image?path=${encodeURIComponent(path)}`;
|
|
4890
4791
|
}
|
|
4792
|
+
var INLINE_IMAGE_FIELD_NAMES = /* @__PURE__ */ new Set([
|
|
4793
|
+
"b64_json",
|
|
4794
|
+
"image",
|
|
4795
|
+
"image_url",
|
|
4796
|
+
"images",
|
|
4797
|
+
"result",
|
|
4798
|
+
"url"
|
|
4799
|
+
]);
|
|
4800
|
+
function isPotentialInlineImageField(fieldName) {
|
|
4801
|
+
return typeof fieldName === "string" && INLINE_IMAGE_FIELD_NAMES.has(fieldName);
|
|
4802
|
+
}
|
|
4803
|
+
async function sanitizeInlineImageString(value, context) {
|
|
4804
|
+
if (!isPotentialInlineImageField(context.fieldName)) {
|
|
4805
|
+
return { value, changed: false };
|
|
4806
|
+
}
|
|
4807
|
+
const dataUrl = normalizeBase64ImageDataUrl(value, "image/*");
|
|
4808
|
+
if (!dataUrl) return { value, changed: false };
|
|
4809
|
+
const localUrl = await persistInlineDataUrlToLocalFile(
|
|
4810
|
+
dataUrl,
|
|
4811
|
+
`inline-image-${context.turnId}-${context.itemId}-${context.fieldName}-${String(context.blockIndex)}`
|
|
4812
|
+
);
|
|
4813
|
+
if (!localUrl) return { value, changed: false };
|
|
4814
|
+
return { value: toLocalImageProxyUrl(localUrl), changed: true };
|
|
4815
|
+
}
|
|
4891
4816
|
async function sanitizeInlineUserContentBlock(block, context) {
|
|
4892
4817
|
const record = asRecord5(block);
|
|
4893
4818
|
if (!record) return block;
|
|
@@ -4896,10 +4821,16 @@ async function sanitizeInlineUserContentBlock(block, context) {
|
|
|
4896
4821
|
if (imageUrl && isInlineDataUrl(imageUrl)) {
|
|
4897
4822
|
const localUrl = await persistInlineDataUrlToLocalFile(imageUrl, `inline-image-${context.turnId}-${context.itemId}-${String(context.blockIndex)}`);
|
|
4898
4823
|
if (localUrl) {
|
|
4824
|
+
const nextRecord = { ...record };
|
|
4825
|
+
if (typeof record.url === "string") {
|
|
4826
|
+
nextRecord.url = toLocalImageProxyUrl(localUrl);
|
|
4827
|
+
}
|
|
4828
|
+
if (typeof record.image_url === "string") {
|
|
4829
|
+
nextRecord.image_url = toLocalImageProxyUrl(localUrl);
|
|
4830
|
+
}
|
|
4899
4831
|
return {
|
|
4900
|
-
...
|
|
4901
|
-
type: "image"
|
|
4902
|
-
url: toLocalImageProxyUrl(localUrl)
|
|
4832
|
+
...nextRecord,
|
|
4833
|
+
type: "image"
|
|
4903
4834
|
};
|
|
4904
4835
|
}
|
|
4905
4836
|
const target = toAttachmentLinkTarget(record, `inline-image/${context.turnId}/${context.itemId}/${String(context.blockIndex)}`);
|
|
@@ -4947,6 +4878,9 @@ async function sanitizeInlinePayloadDeep(value, context) {
|
|
|
4947
4878
|
if (maybeBlock !== value) {
|
|
4948
4879
|
return { value: maybeBlock, changed: true };
|
|
4949
4880
|
}
|
|
4881
|
+
if (typeof value === "string") {
|
|
4882
|
+
return sanitizeInlineImageString(value, context);
|
|
4883
|
+
}
|
|
4950
4884
|
if (Array.isArray(value)) {
|
|
4951
4885
|
let changed2 = false;
|
|
4952
4886
|
const nextArray = [];
|
|
@@ -4954,7 +4888,8 @@ async function sanitizeInlinePayloadDeep(value, context) {
|
|
|
4954
4888
|
const nested = await sanitizeInlinePayloadDeep(value[index], {
|
|
4955
4889
|
turnId: context.turnId,
|
|
4956
4890
|
itemId: context.itemId,
|
|
4957
|
-
blockIndex: index
|
|
4891
|
+
blockIndex: index,
|
|
4892
|
+
fieldName: context.fieldName
|
|
4958
4893
|
});
|
|
4959
4894
|
if (nested.changed) changed2 = true;
|
|
4960
4895
|
nextArray.push(nested.value);
|
|
@@ -4969,7 +4904,8 @@ async function sanitizeInlinePayloadDeep(value, context) {
|
|
|
4969
4904
|
const nested = await sanitizeInlinePayloadDeep(nestedValue, {
|
|
4970
4905
|
turnId: context.turnId,
|
|
4971
4906
|
itemId: context.itemId,
|
|
4972
|
-
blockIndex: context.blockIndex
|
|
4907
|
+
blockIndex: context.blockIndex,
|
|
4908
|
+
fieldName: key
|
|
4973
4909
|
});
|
|
4974
4910
|
if (nested.changed) changed = true;
|
|
4975
4911
|
nextRecord[key] = nested.value;
|
|
@@ -5073,6 +5009,45 @@ function logProviderModelDiscoveryWarning(message, details) {
|
|
|
5073
5009
|
function isTimeoutError(payload) {
|
|
5074
5010
|
return payload instanceof Error && (payload.name === "AbortError" || payload.name === "TimeoutError");
|
|
5075
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
|
+
}
|
|
5076
5051
|
function normalizeHeaderValue(value) {
|
|
5077
5052
|
if (typeof value === "string") {
|
|
5078
5053
|
const trimmed = value.trim();
|
|
@@ -5272,6 +5247,414 @@ function extractThreadMessageText(threadReadPayload) {
|
|
|
5272
5247
|
function readNonEmptyString(value) {
|
|
5273
5248
|
return typeof value === "string" && value.trim().length > 0 ? value : "";
|
|
5274
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 homeBin = join6(homedir5(), ".npm-global", "bin");
|
|
5635
|
+
const command = "npm";
|
|
5636
|
+
const args = ["install", "-g", "@composio/cli@latest"];
|
|
5637
|
+
const invocation = getSpawnInvocation(command, args);
|
|
5638
|
+
const env = {
|
|
5639
|
+
...process.env,
|
|
5640
|
+
npm_config_prefix: process.env.npm_config_prefix?.trim() || join6(homedir5(), ".npm-global"),
|
|
5641
|
+
PATH: process.env.PATH ? `${homeBin}:${process.env.PATH}` : homeBin
|
|
5642
|
+
};
|
|
5643
|
+
const result = spawnSync4(invocation.command, invocation.args, {
|
|
5644
|
+
encoding: "utf8",
|
|
5645
|
+
env,
|
|
5646
|
+
windowsHide: true
|
|
5647
|
+
});
|
|
5648
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
5649
|
+
if (result.error || result.status !== 0) {
|
|
5650
|
+
throw new Error(output || result.error?.message || "Failed to install Composio CLI");
|
|
5651
|
+
}
|
|
5652
|
+
return {
|
|
5653
|
+
ok: true,
|
|
5654
|
+
command: `${command} ${args.join(" ")}`,
|
|
5655
|
+
output
|
|
5656
|
+
};
|
|
5657
|
+
}
|
|
5275
5658
|
function countRecoveredContentLines(value) {
|
|
5276
5659
|
if (!value) return 0;
|
|
5277
5660
|
const normalized = value.replace(/\r\n/g, "\n");
|
|
@@ -5863,54 +6246,6 @@ function scoreFileCandidate(path, query) {
|
|
|
5863
6246
|
if (lowerPath.includes(lowerQuery)) return 4;
|
|
5864
6247
|
return 10;
|
|
5865
6248
|
}
|
|
5866
|
-
function decodeHtmlEntities(value) {
|
|
5867
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(///gi, "/");
|
|
5868
|
-
}
|
|
5869
|
-
function stripHtml(value) {
|
|
5870
|
-
return decodeHtmlEntities(value.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim());
|
|
5871
|
-
}
|
|
5872
|
-
function parseGithubTrendingHtml(html, limit) {
|
|
5873
|
-
const rows = html.match(/<article[\s\S]*?<\/article>/g) ?? [];
|
|
5874
|
-
const items = [];
|
|
5875
|
-
let seq = Date.now();
|
|
5876
|
-
for (const row of rows) {
|
|
5877
|
-
const repoBlockMatch = row.match(/<h2[\s\S]*?<\/h2>/);
|
|
5878
|
-
const hrefMatch = repoBlockMatch?.[0]?.match(/href="\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)"/);
|
|
5879
|
-
if (!hrefMatch) continue;
|
|
5880
|
-
const fullName = hrefMatch[1] ?? "";
|
|
5881
|
-
if (!fullName || items.some((item) => item.fullName === fullName)) continue;
|
|
5882
|
-
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>/);
|
|
5883
|
-
const languageMatch = row.match(/programmingLanguage[^>]*>\s*([\s\S]*?)\s*<\/span>/);
|
|
5884
|
-
const starsMatch = row.match(/href="\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/stargazers"[\s\S]*?>([\s\S]*?)<\/a>/);
|
|
5885
|
-
const starsText = stripHtml(starsMatch?.[1] ?? "").replace(/,/g, "");
|
|
5886
|
-
const stars = Number.parseInt(starsText, 10);
|
|
5887
|
-
items.push({
|
|
5888
|
-
id: seq,
|
|
5889
|
-
fullName,
|
|
5890
|
-
url: `https://github.com/${fullName}`,
|
|
5891
|
-
description: stripHtml(descriptionMatch?.[1] ?? ""),
|
|
5892
|
-
language: stripHtml(languageMatch?.[1] ?? ""),
|
|
5893
|
-
stars: Number.isFinite(stars) ? stars : 0
|
|
5894
|
-
});
|
|
5895
|
-
seq += 1;
|
|
5896
|
-
if (items.length >= limit) break;
|
|
5897
|
-
}
|
|
5898
|
-
return items;
|
|
5899
|
-
}
|
|
5900
|
-
async function fetchGithubTrending(since, limit) {
|
|
5901
|
-
const endpoint = `https://github.com/trending?since=${since}`;
|
|
5902
|
-
const response = await fetch(endpoint, {
|
|
5903
|
-
headers: {
|
|
5904
|
-
"User-Agent": "codex-web-local",
|
|
5905
|
-
Accept: "text/html"
|
|
5906
|
-
}
|
|
5907
|
-
});
|
|
5908
|
-
if (!response.ok) {
|
|
5909
|
-
throw new Error(`GitHub trending fetch failed (${response.status})`);
|
|
5910
|
-
}
|
|
5911
|
-
const html = await response.text();
|
|
5912
|
-
return parseGithubTrendingHtml(html, limit);
|
|
5913
|
-
}
|
|
5914
6249
|
async function listFilesWithRipgrep(cwd) {
|
|
5915
6250
|
return await new Promise((resolve4, reject) => {
|
|
5916
6251
|
const ripgrepCommand = resolveRipgrepCommand();
|
|
@@ -5947,6 +6282,79 @@ function getCodexHomeDir3() {
|
|
|
5947
6282
|
const codexHome = process.env.CODEX_HOME?.trim();
|
|
5948
6283
|
return codexHome && codexHome.length > 0 ? codexHome : join6(homedir5(), ".codex");
|
|
5949
6284
|
}
|
|
6285
|
+
function getPromptsDir() {
|
|
6286
|
+
return join6(getCodexHomeDir3(), "prompts");
|
|
6287
|
+
}
|
|
6288
|
+
function promptNameToFileName(name) {
|
|
6289
|
+
const trimmed = name.trim();
|
|
6290
|
+
const withoutExtension = trimmed.replace(/\.md$/i, "");
|
|
6291
|
+
const sanitized = withoutExtension.replace(/[\/\\:*?"<>|]/g, " ").replace(/\s+/g, " ").trim();
|
|
6292
|
+
return `${sanitized || "prompt"}.md`;
|
|
6293
|
+
}
|
|
6294
|
+
function buildPromptDescription(content) {
|
|
6295
|
+
const firstNonEmptyLine = content.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "";
|
|
6296
|
+
return firstNonEmptyLine.slice(0, 120);
|
|
6297
|
+
}
|
|
6298
|
+
async function listComposerPrompts() {
|
|
6299
|
+
const promptsDir = getPromptsDir();
|
|
6300
|
+
try {
|
|
6301
|
+
const entries = await readdir2(promptsDir, { withFileTypes: true });
|
|
6302
|
+
const prompts = await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md")).map(async (entry) => {
|
|
6303
|
+
const promptPath = join6(promptsDir, entry.name);
|
|
6304
|
+
const content = await readFile3(promptPath, "utf8");
|
|
6305
|
+
return {
|
|
6306
|
+
name: entry.name.replace(/\.md$/i, ""),
|
|
6307
|
+
path: promptPath,
|
|
6308
|
+
content,
|
|
6309
|
+
description: buildPromptDescription(content)
|
|
6310
|
+
};
|
|
6311
|
+
}));
|
|
6312
|
+
return prompts.sort((a, b) => a.name.localeCompare(b.name));
|
|
6313
|
+
} catch (error) {
|
|
6314
|
+
if (error?.code === "ENOENT") return [];
|
|
6315
|
+
throw error;
|
|
6316
|
+
}
|
|
6317
|
+
}
|
|
6318
|
+
async function createComposerPromptFile(name, content) {
|
|
6319
|
+
const trimmedName = name.trim();
|
|
6320
|
+
if (!trimmedName) throw new Error("Prompt name is required");
|
|
6321
|
+
const trimmedContent = content.trim();
|
|
6322
|
+
if (!trimmedContent) throw new Error("Prompt content is required");
|
|
6323
|
+
const promptsDir = getPromptsDir();
|
|
6324
|
+
await mkdir4(promptsDir, { recursive: true });
|
|
6325
|
+
const baseFileName = promptNameToFileName(trimmedName);
|
|
6326
|
+
let targetPath = join6(promptsDir, baseFileName);
|
|
6327
|
+
let suffix = 2;
|
|
6328
|
+
while (existsSync4(targetPath)) {
|
|
6329
|
+
const nextFileName = `${baseFileName.replace(/\.md$/i, "")}-${suffix}.md`;
|
|
6330
|
+
targetPath = join6(promptsDir, nextFileName);
|
|
6331
|
+
suffix += 1;
|
|
6332
|
+
}
|
|
6333
|
+
await writeFile4(targetPath, `${trimmedContent}
|
|
6334
|
+
`, "utf8");
|
|
6335
|
+
return {
|
|
6336
|
+
name: basename4(targetPath).replace(/\.md$/i, ""),
|
|
6337
|
+
path: targetPath,
|
|
6338
|
+
content: `${trimmedContent}
|
|
6339
|
+
`,
|
|
6340
|
+
description: buildPromptDescription(trimmedContent)
|
|
6341
|
+
};
|
|
6342
|
+
}
|
|
6343
|
+
async function removeComposerPromptFile(promptPath) {
|
|
6344
|
+
const resolvedPath = resolve2(promptPath);
|
|
6345
|
+
const promptsDir = resolve2(getPromptsDir());
|
|
6346
|
+
const relative = resolvedPath.startsWith(`${promptsDir}/`) ? resolvedPath.slice(promptsDir.length + 1) : "";
|
|
6347
|
+
if (!relative || relative.includes("..") || !resolvedPath.toLowerCase().endsWith(".md")) {
|
|
6348
|
+
throw new Error("Invalid prompt path");
|
|
6349
|
+
}
|
|
6350
|
+
try {
|
|
6351
|
+
await rm4(resolvedPath, { force: false });
|
|
6352
|
+
return true;
|
|
6353
|
+
} catch (error) {
|
|
6354
|
+
if (error?.code === "ENOENT") return false;
|
|
6355
|
+
throw error;
|
|
6356
|
+
}
|
|
6357
|
+
}
|
|
5950
6358
|
async function runCommand3(command, args, options = {}) {
|
|
5951
6359
|
await new Promise((resolve4, reject) => {
|
|
5952
6360
|
const proc = spawn4(command, args, {
|
|
@@ -6346,6 +6754,109 @@ async function writePinnedThreadIds(threadIds) {
|
|
|
6346
6754
|
payload[PINNED_THREAD_IDS_KEY] = normalizePinnedThreadIds(threadIds);
|
|
6347
6755
|
await writeFile4(statePath, JSON.stringify(payload), "utf8");
|
|
6348
6756
|
}
|
|
6757
|
+
var FIRST_LAUNCH_PLUGINS_CARD_DISMISSED_KEY = "first-launch-plugins-card-dismissed";
|
|
6758
|
+
var THREAD_QUEUE_STATE_KEY = "thread-queue-state";
|
|
6759
|
+
function normalizeStoredQueuedMessage(value) {
|
|
6760
|
+
const record = asRecord5(value);
|
|
6761
|
+
if (!record) return null;
|
|
6762
|
+
const id = typeof record.id === "string" ? record.id.trim() : "";
|
|
6763
|
+
if (!id) return null;
|
|
6764
|
+
const normalizeNamedPathItems = (items) => {
|
|
6765
|
+
if (!Array.isArray(items)) return [];
|
|
6766
|
+
return items.flatMap((item) => {
|
|
6767
|
+
const itemRecord = asRecord5(item);
|
|
6768
|
+
if (!itemRecord) return [];
|
|
6769
|
+
const name = typeof itemRecord.name === "string" ? itemRecord.name.trim() : "";
|
|
6770
|
+
const path = typeof itemRecord.path === "string" ? itemRecord.path.trim() : "";
|
|
6771
|
+
return name && path ? [{ name, path }] : [];
|
|
6772
|
+
});
|
|
6773
|
+
};
|
|
6774
|
+
const normalizeFileAttachments = (items) => {
|
|
6775
|
+
if (!Array.isArray(items)) return [];
|
|
6776
|
+
return items.flatMap((item) => {
|
|
6777
|
+
const itemRecord = asRecord5(item);
|
|
6778
|
+
if (!itemRecord) return [];
|
|
6779
|
+
const label = typeof itemRecord.label === "string" ? itemRecord.label.trim() : "";
|
|
6780
|
+
const path = typeof itemRecord.path === "string" ? itemRecord.path.trim() : "";
|
|
6781
|
+
const fsPath = typeof itemRecord.fsPath === "string" ? itemRecord.fsPath.trim() : "";
|
|
6782
|
+
return label && path && fsPath ? [{ label, path, fsPath }] : [];
|
|
6783
|
+
});
|
|
6784
|
+
};
|
|
6785
|
+
return {
|
|
6786
|
+
id,
|
|
6787
|
+
text: typeof record.text === "string" ? record.text : "",
|
|
6788
|
+
imageUrls: normalizeStringArray(record.imageUrls),
|
|
6789
|
+
skills: normalizeNamedPathItems(record.skills),
|
|
6790
|
+
fileAttachments: normalizeFileAttachments(record.fileAttachments),
|
|
6791
|
+
collaborationMode: record.collaborationMode === "plan" ? "plan" : "default"
|
|
6792
|
+
};
|
|
6793
|
+
}
|
|
6794
|
+
function normalizeThreadQueueState(value) {
|
|
6795
|
+
const record = asRecord5(value);
|
|
6796
|
+
if (!record) return {};
|
|
6797
|
+
const state = {};
|
|
6798
|
+
for (const [threadId, rawMessages] of Object.entries(record)) {
|
|
6799
|
+
const normalizedThreadId = threadId.trim();
|
|
6800
|
+
if (!normalizedThreadId || !Array.isArray(rawMessages)) continue;
|
|
6801
|
+
const messages = rawMessages.flatMap((item) => {
|
|
6802
|
+
const message = normalizeStoredQueuedMessage(item);
|
|
6803
|
+
return message ? [message] : [];
|
|
6804
|
+
});
|
|
6805
|
+
if (messages.length > 0) {
|
|
6806
|
+
state[normalizedThreadId] = messages;
|
|
6807
|
+
}
|
|
6808
|
+
}
|
|
6809
|
+
return state;
|
|
6810
|
+
}
|
|
6811
|
+
async function readThreadQueueState() {
|
|
6812
|
+
const statePath = getCodexGlobalStatePath();
|
|
6813
|
+
try {
|
|
6814
|
+
const raw = await readFile3(statePath, "utf8");
|
|
6815
|
+
const payload = asRecord5(JSON.parse(raw)) ?? {};
|
|
6816
|
+
return normalizeThreadQueueState(payload[THREAD_QUEUE_STATE_KEY]);
|
|
6817
|
+
} catch {
|
|
6818
|
+
return {};
|
|
6819
|
+
}
|
|
6820
|
+
}
|
|
6821
|
+
async function writeThreadQueueState(nextState) {
|
|
6822
|
+
const statePath = getCodexGlobalStatePath();
|
|
6823
|
+
let payload = {};
|
|
6824
|
+
try {
|
|
6825
|
+
const raw = await readFile3(statePath, "utf8");
|
|
6826
|
+
payload = asRecord5(JSON.parse(raw)) ?? {};
|
|
6827
|
+
} catch {
|
|
6828
|
+
payload = {};
|
|
6829
|
+
}
|
|
6830
|
+
const normalized = normalizeThreadQueueState(nextState);
|
|
6831
|
+
if (Object.keys(normalized).length > 0) {
|
|
6832
|
+
payload[THREAD_QUEUE_STATE_KEY] = normalized;
|
|
6833
|
+
} else {
|
|
6834
|
+
delete payload[THREAD_QUEUE_STATE_KEY];
|
|
6835
|
+
}
|
|
6836
|
+
await writeFile4(statePath, JSON.stringify(payload), "utf8");
|
|
6837
|
+
}
|
|
6838
|
+
async function readFirstLaunchPluginsCardDismissed() {
|
|
6839
|
+
const statePath = getCodexGlobalStatePath();
|
|
6840
|
+
try {
|
|
6841
|
+
const raw = await readFile3(statePath, "utf8");
|
|
6842
|
+
const payload = asRecord5(JSON.parse(raw)) ?? {};
|
|
6843
|
+
return payload[FIRST_LAUNCH_PLUGINS_CARD_DISMISSED_KEY] === true;
|
|
6844
|
+
} catch {
|
|
6845
|
+
return false;
|
|
6846
|
+
}
|
|
6847
|
+
}
|
|
6848
|
+
async function writeFirstLaunchPluginsCardDismissed(dismissed) {
|
|
6849
|
+
const statePath = getCodexGlobalStatePath();
|
|
6850
|
+
let payload = {};
|
|
6851
|
+
try {
|
|
6852
|
+
const raw = await readFile3(statePath, "utf8");
|
|
6853
|
+
payload = asRecord5(JSON.parse(raw)) ?? {};
|
|
6854
|
+
} catch {
|
|
6855
|
+
payload = {};
|
|
6856
|
+
}
|
|
6857
|
+
payload[FIRST_LAUNCH_PLUGINS_CARD_DISMISSED_KEY] = dismissed === true;
|
|
6858
|
+
await writeFile4(statePath, JSON.stringify(payload), "utf8");
|
|
6859
|
+
}
|
|
6349
6860
|
function getSessionIndexFileSignature(stats) {
|
|
6350
6861
|
return `${String(stats.mtimeMs)}:${String(stats.size)}`;
|
|
6351
6862
|
}
|
|
@@ -6651,6 +7162,46 @@ async function proxyTranscribe(body, contentType, authToken, accountId) {
|
|
|
6651
7162
|
}
|
|
6652
7163
|
return result;
|
|
6653
7164
|
}
|
|
7165
|
+
function parseConnectorLogoUrl(rawUrl) {
|
|
7166
|
+
const trimmed = rawUrl.trim();
|
|
7167
|
+
if (!trimmed.startsWith("connectors://")) return null;
|
|
7168
|
+
const rest = trimmed.slice("connectors://".length);
|
|
7169
|
+
const connectorId = (rest.split(/[/?#]/u)[0] ?? "").trim();
|
|
7170
|
+
if (!connectorId) return null;
|
|
7171
|
+
const query = rest.includes("?") ? rest.slice(rest.indexOf("?") + 1).split("#")[0] ?? "" : "";
|
|
7172
|
+
const theme = new URLSearchParams(query).get("theme")?.toLowerCase() === "dark" ? "dark" : "light";
|
|
7173
|
+
return { connectorId, theme };
|
|
7174
|
+
}
|
|
7175
|
+
async function fetchConnectorLogo(rawUrl) {
|
|
7176
|
+
const parsed = parseConnectorLogoUrl(rawUrl);
|
|
7177
|
+
if (!parsed) throw new Error("Unsupported connector logo URL");
|
|
7178
|
+
const auth = await readCodexAuth();
|
|
7179
|
+
if (!auth) throw new Error("No auth token available for connector logo");
|
|
7180
|
+
const endpoint = `https://chatgpt.com/backend-api/aip/connectors/${encodeURIComponent(parsed.connectorId)}/logo?theme=${parsed.theme}`;
|
|
7181
|
+
const response = await fetch(endpoint, {
|
|
7182
|
+
headers: {
|
|
7183
|
+
Authorization: `Bearer ${auth.accessToken}`,
|
|
7184
|
+
originator: "Codex Desktop",
|
|
7185
|
+
"User-Agent": `Codex Desktop/0.1.0 (${process.platform}; ${process.arch})`,
|
|
7186
|
+
...auth.accountId ? { "ChatGPT-Account-Id": auth.accountId } : {}
|
|
7187
|
+
},
|
|
7188
|
+
signal: AbortSignal.timeout(1e4)
|
|
7189
|
+
});
|
|
7190
|
+
if (!response.ok) throw new Error(`Connector logo fetch failed (${response.status})`);
|
|
7191
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
7192
|
+
if (contentType.includes("application/json")) {
|
|
7193
|
+
const payload = asRecord5(await response.json());
|
|
7194
|
+
const body = asRecord5(payload?.body);
|
|
7195
|
+
const base64 = readNonEmptyString(body?.base64);
|
|
7196
|
+
const nestedContentType = readNonEmptyString(body?.contentType) ?? readNonEmptyString(body?.content_type);
|
|
7197
|
+
if (!base64 || !nestedContentType) throw new Error("Connector logo response was missing image data");
|
|
7198
|
+
return { contentType: nestedContentType, body: Buffer.from(base64, "base64") };
|
|
7199
|
+
}
|
|
7200
|
+
return {
|
|
7201
|
+
contentType: contentType || "image/png",
|
|
7202
|
+
body: Buffer.from(await response.arrayBuffer())
|
|
7203
|
+
};
|
|
7204
|
+
}
|
|
6654
7205
|
var STREAM_EVENT_BUFFER_LIMIT = 400;
|
|
6655
7206
|
var MERGEABLE_ITEM_TYPES = /* @__PURE__ */ new Set([
|
|
6656
7207
|
"commandExecution",
|
|
@@ -7543,6 +8094,19 @@ function createCodexBridgeMiddleware() {
|
|
|
7543
8094
|
setJson4(res, 200, terminalManager.getAvailability());
|
|
7544
8095
|
return;
|
|
7545
8096
|
}
|
|
8097
|
+
if (req.method === "GET" && url.pathname === "/codex-api/thread-terminal/quick-commands") {
|
|
8098
|
+
const cwd = url.searchParams.get("cwd")?.trim() ?? "";
|
|
8099
|
+
if (!cwd) {
|
|
8100
|
+
setJson4(res, 400, { error: "Missing cwd" });
|
|
8101
|
+
return;
|
|
8102
|
+
}
|
|
8103
|
+
try {
|
|
8104
|
+
setJson4(res, 200, { commands: await listTerminalQuickCommands(cwd) });
|
|
8105
|
+
} catch (error) {
|
|
8106
|
+
setJson4(res, 500, { error: getErrorMessage5(error, "Failed to load terminal quick commands") });
|
|
8107
|
+
}
|
|
8108
|
+
return;
|
|
8109
|
+
}
|
|
7546
8110
|
if (req.method === "POST" && url.pathname === "/codex-api/thread-terminal/attach") {
|
|
7547
8111
|
const availability = terminalManager.getAvailability();
|
|
7548
8112
|
if (!availability.available) {
|
|
@@ -7844,6 +8408,77 @@ function createCodexBridgeMiddleware() {
|
|
|
7844
8408
|
res.end(upstream.body);
|
|
7845
8409
|
return;
|
|
7846
8410
|
}
|
|
8411
|
+
if (req.method === "GET" && url.pathname === "/codex-api/composio/status") {
|
|
8412
|
+
try {
|
|
8413
|
+
setJson4(res, 200, await readComposioStatus());
|
|
8414
|
+
} catch (error) {
|
|
8415
|
+
setJson4(res, 500, { error: getErrorMessage5(error, "Failed to read Composio status") });
|
|
8416
|
+
}
|
|
8417
|
+
return;
|
|
8418
|
+
}
|
|
8419
|
+
if (req.method === "GET" && url.pathname === "/codex-api/composio/connectors") {
|
|
8420
|
+
try {
|
|
8421
|
+
const query = url.searchParams.get("query") ?? "";
|
|
8422
|
+
const cursor = url.searchParams.get("cursor")?.trim() ?? null;
|
|
8423
|
+
const limit = parseComposioLimit(url.searchParams.get("limit"));
|
|
8424
|
+
setJson4(res, 200, await listComposioConnectors(query, cursor, limit));
|
|
8425
|
+
} catch (error) {
|
|
8426
|
+
setJson4(res, 500, { error: getErrorMessage5(error, "Failed to list Composio connectors") });
|
|
8427
|
+
}
|
|
8428
|
+
return;
|
|
8429
|
+
}
|
|
8430
|
+
if (req.method === "GET" && url.pathname === "/codex-api/composio/connector") {
|
|
8431
|
+
try {
|
|
8432
|
+
const slug = url.searchParams.get("slug") ?? "";
|
|
8433
|
+
setJson4(res, 200, await readComposioConnectorDetail(slug));
|
|
8434
|
+
} catch (error) {
|
|
8435
|
+
setJson4(res, 500, { error: getErrorMessage5(error, "Failed to load Composio connector") });
|
|
8436
|
+
}
|
|
8437
|
+
return;
|
|
8438
|
+
}
|
|
8439
|
+
if (req.method === "POST" && url.pathname === "/codex-api/composio/link") {
|
|
8440
|
+
try {
|
|
8441
|
+
const payload = asRecord5(await readJsonBody(req));
|
|
8442
|
+
const slug = readNonEmptyString(payload?.slug);
|
|
8443
|
+
setJson4(res, 200, await startComposioLink(slug));
|
|
8444
|
+
} catch (error) {
|
|
8445
|
+
setJson4(res, 500, { error: getErrorMessage5(error, "Failed to start Composio login") });
|
|
8446
|
+
}
|
|
8447
|
+
return;
|
|
8448
|
+
}
|
|
8449
|
+
if (req.method === "POST" && url.pathname === "/codex-api/composio/login") {
|
|
8450
|
+
try {
|
|
8451
|
+
setJson4(res, 200, await startComposioLogin());
|
|
8452
|
+
} catch (error) {
|
|
8453
|
+
setJson4(res, 500, { error: getErrorMessage5(error, "Failed to start Composio CLI login") });
|
|
8454
|
+
}
|
|
8455
|
+
return;
|
|
8456
|
+
}
|
|
8457
|
+
if (req.method === "POST" && url.pathname === "/codex-api/composio/install") {
|
|
8458
|
+
try {
|
|
8459
|
+
setJson4(res, 200, await installComposioCli());
|
|
8460
|
+
} catch (error) {
|
|
8461
|
+
setJson4(res, 500, { error: getErrorMessage5(error, "Failed to install Composio CLI") });
|
|
8462
|
+
}
|
|
8463
|
+
return;
|
|
8464
|
+
}
|
|
8465
|
+
if (req.method === "GET" && url.pathname === "/codex-api/connector-logo") {
|
|
8466
|
+
const src = url.searchParams.get("src")?.trim() ?? "";
|
|
8467
|
+
if (!src) {
|
|
8468
|
+
setJson4(res, 400, { error: "Missing src" });
|
|
8469
|
+
return;
|
|
8470
|
+
}
|
|
8471
|
+
try {
|
|
8472
|
+
const logo = await fetchConnectorLogo(src);
|
|
8473
|
+
res.statusCode = 200;
|
|
8474
|
+
res.setHeader("Content-Type", logo.contentType);
|
|
8475
|
+
res.setHeader("Cache-Control", "private, max-age=3600");
|
|
8476
|
+
res.end(logo.body);
|
|
8477
|
+
} catch (error) {
|
|
8478
|
+
setJson4(res, 502, { error: getErrorMessage5(error, "Failed to fetch connector logo") });
|
|
8479
|
+
}
|
|
8480
|
+
return;
|
|
8481
|
+
}
|
|
7847
8482
|
if (req.method === "POST" && url.pathname === "/codex-api/server-requests/respond") {
|
|
7848
8483
|
const payload = await readJsonBody(req);
|
|
7849
8484
|
await appServer.respondToServerRequest(payload);
|
|
@@ -7925,21 +8560,13 @@ function createCodexBridgeMiddleware() {
|
|
|
7925
8560
|
setJson4(res, 200, { data: state });
|
|
7926
8561
|
return;
|
|
7927
8562
|
}
|
|
7928
|
-
if (req.method === "GET" && url.pathname === "/codex-api/
|
|
7929
|
-
|
|
8563
|
+
if (req.method === "GET" && url.pathname === "/codex-api/thread-queue-state") {
|
|
8564
|
+
const state = await readThreadQueueState();
|
|
8565
|
+
setJson4(res, 200, { data: state });
|
|
7930
8566
|
return;
|
|
7931
8567
|
}
|
|
7932
|
-
if (req.method === "GET" && url.pathname === "/codex-api/
|
|
7933
|
-
|
|
7934
|
-
const since = sinceRaw === "weekly" ? "weekly" : sinceRaw === "monthly" ? "monthly" : "daily";
|
|
7935
|
-
const limitRaw = Number.parseInt((url.searchParams.get("limit") ?? "6").trim(), 10);
|
|
7936
|
-
const limit = Number.isFinite(limitRaw) ? Math.max(1, Math.min(10, limitRaw)) : 6;
|
|
7937
|
-
try {
|
|
7938
|
-
const data = await fetchGithubTrending(since, limit);
|
|
7939
|
-
setJson4(res, 200, { data });
|
|
7940
|
-
} catch (error) {
|
|
7941
|
-
setJson4(res, 502, { error: getErrorMessage5(error, "Failed to fetch GitHub trending") });
|
|
7942
|
-
}
|
|
8568
|
+
if (req.method === "GET" && url.pathname === "/codex-api/home-directory") {
|
|
8569
|
+
setJson4(res, 200, { data: { path: homedir5() } });
|
|
7943
8570
|
return;
|
|
7944
8571
|
}
|
|
7945
8572
|
if (req.method === "POST" && url.pathname === "/codex-api/worktree/create") {
|
|
@@ -8201,6 +8828,17 @@ function createCodexBridgeMiddleware() {
|
|
|
8201
8828
|
setJson4(res, 200, { ok: true });
|
|
8202
8829
|
return;
|
|
8203
8830
|
}
|
|
8831
|
+
if (req.method === "PUT" && url.pathname === "/codex-api/thread-queue-state") {
|
|
8832
|
+
const payload = await readJsonBody(req);
|
|
8833
|
+
const record = asRecord5(payload);
|
|
8834
|
+
if (!record) {
|
|
8835
|
+
setJson4(res, 400, { error: "Invalid body: expected object" });
|
|
8836
|
+
return;
|
|
8837
|
+
}
|
|
8838
|
+
await writeThreadQueueState(normalizeThreadQueueState(record));
|
|
8839
|
+
setJson4(res, 200, { ok: true });
|
|
8840
|
+
return;
|
|
8841
|
+
}
|
|
8204
8842
|
if (req.method === "POST" && url.pathname === "/codex-api/project-root") {
|
|
8205
8843
|
const payload = asRecord5(await readJsonBody(req));
|
|
8206
8844
|
const rawPath = typeof payload?.path === "string" ? payload.path.trim() : "";
|
|
@@ -8262,6 +8900,17 @@ function createCodexBridgeMiddleware() {
|
|
|
8262
8900
|
setJson4(res, 200, { data: { path: normalizedPath } });
|
|
8263
8901
|
return;
|
|
8264
8902
|
}
|
|
8903
|
+
if (req.method === "POST" && url.pathname === "/codex-api/projectless-thread-cwd") {
|
|
8904
|
+
const payload = asRecord5(await readJsonBody(req));
|
|
8905
|
+
const prompt = typeof payload?.prompt === "string" ? payload.prompt : null;
|
|
8906
|
+
try {
|
|
8907
|
+
const directory = await createProjectlessThreadDirectory(prompt);
|
|
8908
|
+
setJson4(res, 200, { data: directory });
|
|
8909
|
+
} catch (error) {
|
|
8910
|
+
setJson4(res, 500, { error: error instanceof Error ? error.message : "Failed to create new chat folder" });
|
|
8911
|
+
}
|
|
8912
|
+
return;
|
|
8913
|
+
}
|
|
8265
8914
|
if (req.method === "GET" && url.pathname === "/codex-api/project-root-suggestion") {
|
|
8266
8915
|
const basePath = url.searchParams.get("basePath")?.trim() ?? "";
|
|
8267
8916
|
if (!basePath) {
|
|
@@ -8325,6 +8974,40 @@ function createCodexBridgeMiddleware() {
|
|
|
8325
8974
|
}
|
|
8326
8975
|
return;
|
|
8327
8976
|
}
|
|
8977
|
+
if (req.method === "GET" && url.pathname === "/codex-api/prompts") {
|
|
8978
|
+
setJson4(res, 200, { data: await listComposerPrompts() });
|
|
8979
|
+
return;
|
|
8980
|
+
}
|
|
8981
|
+
if (req.method === "POST" && url.pathname === "/codex-api/prompts") {
|
|
8982
|
+
const payload = asRecord5(await readJsonBody(req));
|
|
8983
|
+
const name = typeof payload?.name === "string" ? payload.name.trim() : "";
|
|
8984
|
+
const content = typeof payload?.content === "string" ? payload.content : "";
|
|
8985
|
+
if (!name || !content.trim()) {
|
|
8986
|
+
setJson4(res, 400, { error: "Prompt name and content are required" });
|
|
8987
|
+
return;
|
|
8988
|
+
}
|
|
8989
|
+
try {
|
|
8990
|
+
const prompt = await createComposerPromptFile(name, content);
|
|
8991
|
+
setJson4(res, 200, { data: prompt });
|
|
8992
|
+
} catch (error) {
|
|
8993
|
+
setJson4(res, 500, { error: getErrorMessage5(error, "Failed to create prompt") });
|
|
8994
|
+
}
|
|
8995
|
+
return;
|
|
8996
|
+
}
|
|
8997
|
+
if (req.method === "DELETE" && url.pathname === "/codex-api/prompts") {
|
|
8998
|
+
const promptPath = url.searchParams.get("path")?.trim() ?? "";
|
|
8999
|
+
if (!promptPath) {
|
|
9000
|
+
setJson4(res, 400, { error: "Missing path" });
|
|
9001
|
+
return;
|
|
9002
|
+
}
|
|
9003
|
+
try {
|
|
9004
|
+
const removed = await removeComposerPromptFile(promptPath);
|
|
9005
|
+
setJson4(res, 200, { data: { removed } });
|
|
9006
|
+
} catch (error) {
|
|
9007
|
+
setJson4(res, 400, { error: getErrorMessage5(error, "Failed to remove prompt") });
|
|
9008
|
+
}
|
|
9009
|
+
return;
|
|
9010
|
+
}
|
|
8328
9011
|
if (req.method === "GET" && url.pathname === "/codex-api/thread-titles") {
|
|
8329
9012
|
const cache = await readMergedThreadTitleCache();
|
|
8330
9013
|
setJson4(res, 200, { data: cache });
|
|
@@ -8335,6 +9018,11 @@ function createCodexBridgeMiddleware() {
|
|
|
8335
9018
|
setJson4(res, 200, { data: { threadIds } });
|
|
8336
9019
|
return;
|
|
8337
9020
|
}
|
|
9021
|
+
if (req.method === "GET" && url.pathname === "/codex-api/preferences/first-launch-plugins-card") {
|
|
9022
|
+
const dismissed = await readFirstLaunchPluginsCardDismissed();
|
|
9023
|
+
setJson4(res, 200, { data: { dismissed } });
|
|
9024
|
+
return;
|
|
9025
|
+
}
|
|
8338
9026
|
if (req.method === "GET" && url.pathname === "/codex-api/thread-automations") {
|
|
8339
9027
|
const automationsByThreadId = await listThreadHeartbeatAutomations();
|
|
8340
9028
|
setJson4(res, 200, { data: automationsByThreadId });
|
|
@@ -8385,6 +9073,13 @@ function createCodexBridgeMiddleware() {
|
|
|
8385
9073
|
setJson4(res, 200, { ok: true });
|
|
8386
9074
|
return;
|
|
8387
9075
|
}
|
|
9076
|
+
if (req.method === "PUT" && url.pathname === "/codex-api/preferences/first-launch-plugins-card") {
|
|
9077
|
+
const payload = asRecord5(await readJsonBody(req));
|
|
9078
|
+
const dismissed = payload?.dismissed === true;
|
|
9079
|
+
await writeFirstLaunchPluginsCardDismissed(dismissed);
|
|
9080
|
+
setJson4(res, 200, { ok: true });
|
|
9081
|
+
return;
|
|
9082
|
+
}
|
|
8388
9083
|
if (req.method === "PUT" && url.pathname === "/codex-api/thread-automation") {
|
|
8389
9084
|
const payload = asRecord5(await readJsonBody(req));
|
|
8390
9085
|
const threadId = typeof payload?.threadId === "string" ? payload.threadId.trim() : "";
|
|
@@ -8517,7 +9212,13 @@ data: ${JSON.stringify({ ok: true })}
|
|
|
8517
9212
|
|
|
8518
9213
|
// src/server/authMiddleware.ts
|
|
8519
9214
|
import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
9215
|
+
import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
9216
|
+
import { homedir as homedir6 } from "os";
|
|
9217
|
+
import { dirname as dirname3, join as join7 } from "path";
|
|
8520
9218
|
var TOKEN_COOKIE = "portal_session";
|
|
9219
|
+
var SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
9220
|
+
var SESSION_STORE_FILE = "webui-auth-sessions.json";
|
|
9221
|
+
var MAX_PERSISTED_TOKENS = 128;
|
|
8521
9222
|
function constantTimeCompare(a, b) {
|
|
8522
9223
|
const bufA = Buffer.from(a);
|
|
8523
9224
|
const bufB = Buffer.from(b);
|
|
@@ -8565,6 +9266,69 @@ function isTrustedTailscaleIPv6(remote) {
|
|
|
8565
9266
|
function isTrustedTailscaleRemote(remote) {
|
|
8566
9267
|
return isTrustedTailscaleIPv4(remote) || isTrustedTailscaleIPv6(remote);
|
|
8567
9268
|
}
|
|
9269
|
+
function getCodexHomeDir4() {
|
|
9270
|
+
const codexHome = process.env.CODEX_HOME?.trim();
|
|
9271
|
+
return codexHome && codexHome.length > 0 ? codexHome : join7(homedir6(), ".codex");
|
|
9272
|
+
}
|
|
9273
|
+
function getSessionStorePath() {
|
|
9274
|
+
return join7(getCodexHomeDir4(), SESSION_STORE_FILE);
|
|
9275
|
+
}
|
|
9276
|
+
function readPersistedSessions() {
|
|
9277
|
+
const sessionStorePath = getSessionStorePath();
|
|
9278
|
+
if (!existsSync5(sessionStorePath)) return /* @__PURE__ */ new Map();
|
|
9279
|
+
try {
|
|
9280
|
+
const raw = readFileSync3(sessionStorePath, "utf8");
|
|
9281
|
+
const parsed = JSON.parse(raw);
|
|
9282
|
+
const now = Date.now();
|
|
9283
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
9284
|
+
for (const entry of parsed.tokens ?? []) {
|
|
9285
|
+
const token = typeof entry?.value === "string" ? entry.value : "";
|
|
9286
|
+
const expiresAt = typeof entry?.expiresAt === "number" ? entry.expiresAt : 0;
|
|
9287
|
+
if (!token || !Number.isFinite(expiresAt) || expiresAt <= now) continue;
|
|
9288
|
+
sessions.set(token, expiresAt);
|
|
9289
|
+
}
|
|
9290
|
+
return sessions;
|
|
9291
|
+
} catch {
|
|
9292
|
+
return /* @__PURE__ */ new Map();
|
|
9293
|
+
}
|
|
9294
|
+
}
|
|
9295
|
+
function persistSessions(validTokens) {
|
|
9296
|
+
const sessionStorePath = getSessionStorePath();
|
|
9297
|
+
mkdirSync(dirname3(sessionStorePath), { recursive: true });
|
|
9298
|
+
const tokens = Array.from(validTokens.entries()).sort((left, right) => right[1] - left[1]).slice(0, MAX_PERSISTED_TOKENS).map(([value, expiresAt]) => ({ value, expiresAt }));
|
|
9299
|
+
const tmpPath = `${sessionStorePath}.tmp`;
|
|
9300
|
+
writeFileSync2(tmpPath, `${JSON.stringify({ tokens }, null, 2)}
|
|
9301
|
+
`, { encoding: "utf8", mode: 384 });
|
|
9302
|
+
renameSync(tmpPath, sessionStorePath);
|
|
9303
|
+
}
|
|
9304
|
+
function tryPersistSessions(validTokens) {
|
|
9305
|
+
try {
|
|
9306
|
+
persistSessions(validTokens);
|
|
9307
|
+
} catch (error) {
|
|
9308
|
+
console.warn("[auth] failed to persist login sessions:", error);
|
|
9309
|
+
}
|
|
9310
|
+
}
|
|
9311
|
+
function pruneExpiredSessions(validTokens) {
|
|
9312
|
+
const now = Date.now();
|
|
9313
|
+
let changed = false;
|
|
9314
|
+
for (const [token, expiresAt] of validTokens.entries()) {
|
|
9315
|
+
if (expiresAt > now) continue;
|
|
9316
|
+
validTokens.delete(token);
|
|
9317
|
+
changed = true;
|
|
9318
|
+
}
|
|
9319
|
+
return changed;
|
|
9320
|
+
}
|
|
9321
|
+
function buildSessionCookie(token, expiresAt) {
|
|
9322
|
+
const maxAgeSeconds = Math.max(0, Math.floor((expiresAt - Date.now()) / 1e3));
|
|
9323
|
+
return [
|
|
9324
|
+
`${TOKEN_COOKIE}=${token}`,
|
|
9325
|
+
"Path=/",
|
|
9326
|
+
"HttpOnly",
|
|
9327
|
+
"SameSite=Lax",
|
|
9328
|
+
`Max-Age=${String(maxAgeSeconds)}`,
|
|
9329
|
+
`Expires=${new Date(expiresAt).toUTCString()}`
|
|
9330
|
+
].join("; ");
|
|
9331
|
+
}
|
|
8568
9332
|
function isAuthorizedByRequestLike(remoteAddress, hostHeader, cookieHeader, validTokens) {
|
|
8569
9333
|
const remote = remoteAddress ?? "";
|
|
8570
9334
|
if (isLocalhostRemote(remote) && isLocalhostHost(hostHeader ?? "")) {
|
|
@@ -8575,7 +9339,9 @@ function isAuthorizedByRequestLike(remoteAddress, hostHeader, cookieHeader, vali
|
|
|
8575
9339
|
}
|
|
8576
9340
|
const cookies = parseCookies(cookieHeader);
|
|
8577
9341
|
const token = cookies[TOKEN_COOKIE];
|
|
8578
|
-
|
|
9342
|
+
if (!token) return false;
|
|
9343
|
+
const expiresAt = validTokens.get(token);
|
|
9344
|
+
return typeof expiresAt === "number" && expiresAt > Date.now();
|
|
8579
9345
|
}
|
|
8580
9346
|
var LOGIN_PAGE_HTML = `<!DOCTYPE html>
|
|
8581
9347
|
<html lang="en">
|
|
@@ -8619,8 +9385,14 @@ form.addEventListener('submit',async e=>{
|
|
|
8619
9385
|
</body>
|
|
8620
9386
|
</html>`;
|
|
8621
9387
|
function createAuthSession(password) {
|
|
8622
|
-
const validTokens =
|
|
9388
|
+
const validTokens = readPersistedSessions();
|
|
9389
|
+
if (pruneExpiredSessions(validTokens)) {
|
|
9390
|
+
tryPersistSessions(validTokens);
|
|
9391
|
+
}
|
|
8623
9392
|
const middleware = (req, res, next) => {
|
|
9393
|
+
if (pruneExpiredSessions(validTokens)) {
|
|
9394
|
+
tryPersistSessions(validTokens);
|
|
9395
|
+
}
|
|
8624
9396
|
if (isAuthorizedByRequestLike(req.socket.remoteAddress, req.headers.host, req.headers.cookie, validTokens)) {
|
|
8625
9397
|
next();
|
|
8626
9398
|
return;
|
|
@@ -8632,19 +9404,27 @@ function createAuthSession(password) {
|
|
|
8632
9404
|
body += chunk;
|
|
8633
9405
|
});
|
|
8634
9406
|
req.on("end", () => {
|
|
9407
|
+
let parsed;
|
|
9408
|
+
try {
|
|
9409
|
+
parsed = JSON.parse(body);
|
|
9410
|
+
} catch {
|
|
9411
|
+
res.status(400).json({ error: "Invalid request body" });
|
|
9412
|
+
return;
|
|
9413
|
+
}
|
|
9414
|
+
const provided = typeof parsed.password === "string" ? parsed.password : "";
|
|
9415
|
+
if (!constantTimeCompare(provided, password)) {
|
|
9416
|
+
res.status(401).json({ error: "Invalid password" });
|
|
9417
|
+
return;
|
|
9418
|
+
}
|
|
8635
9419
|
try {
|
|
8636
|
-
const parsed = JSON.parse(body);
|
|
8637
|
-
const provided = typeof parsed.password === "string" ? parsed.password : "";
|
|
8638
|
-
if (!constantTimeCompare(provided, password)) {
|
|
8639
|
-
res.status(401).json({ error: "Invalid password" });
|
|
8640
|
-
return;
|
|
8641
|
-
}
|
|
8642
9420
|
const token = randomBytes2(32).toString("hex");
|
|
8643
|
-
|
|
8644
|
-
|
|
9421
|
+
const expiresAt = Date.now() + SESSION_TTL_MS;
|
|
9422
|
+
validTokens.set(token, expiresAt);
|
|
9423
|
+
tryPersistSessions(validTokens);
|
|
9424
|
+
res.setHeader("Set-Cookie", buildSessionCookie(token, expiresAt));
|
|
8645
9425
|
res.json({ ok: true });
|
|
8646
9426
|
} catch {
|
|
8647
|
-
res.status(
|
|
9427
|
+
res.status(500).json({ error: "Failed to create login session" });
|
|
8648
9428
|
}
|
|
8649
9429
|
});
|
|
8650
9430
|
return;
|
|
@@ -8653,8 +9433,10 @@ function createAuthSession(password) {
|
|
|
8653
9433
|
const provided = req.path.slice("/password=".length);
|
|
8654
9434
|
if (constantTimeCompare(provided, password)) {
|
|
8655
9435
|
const token = randomBytes2(32).toString("hex");
|
|
8656
|
-
|
|
8657
|
-
|
|
9436
|
+
const expiresAt = Date.now() + SESSION_TTL_MS;
|
|
9437
|
+
validTokens.set(token, expiresAt);
|
|
9438
|
+
tryPersistSessions(validTokens);
|
|
9439
|
+
res.setHeader("Set-Cookie", buildSessionCookie(token, expiresAt));
|
|
8658
9440
|
res.redirect(302, "/");
|
|
8659
9441
|
return;
|
|
8660
9442
|
}
|
|
@@ -8669,7 +9451,7 @@ function createAuthSession(password) {
|
|
|
8669
9451
|
}
|
|
8670
9452
|
|
|
8671
9453
|
// src/server/localBrowseUi.ts
|
|
8672
|
-
import { dirname as
|
|
9454
|
+
import { dirname as dirname4, extname as extname2, join as join8 } from "path";
|
|
8673
9455
|
import { open, readFile as readFile4, readdir as readdir3, stat as stat5 } from "fs/promises";
|
|
8674
9456
|
var TEXT_EDITABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
8675
9457
|
".txt",
|
|
@@ -8817,7 +9599,7 @@ function escapeForInlineScriptString(value) {
|
|
|
8817
9599
|
async function getDirectoryItems(localPath) {
|
|
8818
9600
|
const entries = await readdir3(localPath, { withFileTypes: true });
|
|
8819
9601
|
const withMeta = await Promise.all(entries.map(async (entry) => {
|
|
8820
|
-
const entryPath =
|
|
9602
|
+
const entryPath = join8(localPath, entry.name);
|
|
8821
9603
|
const entryStat = await stat5(entryPath);
|
|
8822
9604
|
const editable = !entry.isDirectory() && await isTextEditableFile(entryPath);
|
|
8823
9605
|
return {
|
|
@@ -8838,7 +9620,7 @@ async function getDirectoryItems(localPath) {
|
|
|
8838
9620
|
function projectCreationTargetPath(parentPath, newProjectName) {
|
|
8839
9621
|
const normalizedName = normalizeNewProjectName(newProjectName);
|
|
8840
9622
|
if (!normalizedName) return "";
|
|
8841
|
-
return
|
|
9623
|
+
return join8(parentPath, normalizedName);
|
|
8842
9624
|
}
|
|
8843
9625
|
function projectCreationButtonLabel(newProjectName) {
|
|
8844
9626
|
const normalizedName = normalizeNewProjectName(newProjectName);
|
|
@@ -8867,7 +9649,7 @@ async function getLocalDirectoryListing(localPath, options = {}) {
|
|
|
8867
9649
|
const entries = await readdir3(localPath, { withFileTypes: true });
|
|
8868
9650
|
const directories = entries.filter((entry) => entry.isDirectory()).map((entry) => ({
|
|
8869
9651
|
name: entry.name,
|
|
8870
|
-
path:
|
|
9652
|
+
path: join8(localPath, entry.name)
|
|
8871
9653
|
})).filter((entry) => options.showHidden === true || !isHiddenName(entry.name)).sort((a, b) => {
|
|
8872
9654
|
const aHidden = isHiddenName(a.name);
|
|
8873
9655
|
const bHidden = isHiddenName(b.name);
|
|
@@ -8876,14 +9658,14 @@ async function getLocalDirectoryListing(localPath, options = {}) {
|
|
|
8876
9658
|
});
|
|
8877
9659
|
return {
|
|
8878
9660
|
path: localPath,
|
|
8879
|
-
parentPath:
|
|
9661
|
+
parentPath: dirname4(localPath),
|
|
8880
9662
|
entries: directories
|
|
8881
9663
|
};
|
|
8882
9664
|
}
|
|
8883
9665
|
async function createDirectoryListingHtml(localPath, options) {
|
|
8884
9666
|
const newProjectName = normalizeNewProjectName(options?.newProjectName ?? "");
|
|
8885
9667
|
const items = await getDirectoryItems(localPath);
|
|
8886
|
-
const parentPath =
|
|
9668
|
+
const parentPath = dirname4(localPath);
|
|
8887
9669
|
const rows = items.map((item) => {
|
|
8888
9670
|
const suffix = item.isDirectory ? "/" : "";
|
|
8889
9671
|
const editAction = item.editable ? ` <a class="icon-btn" aria-label="Edit ${escapeHtml2(item.name)}" href="${escapeHtml2(toEditHref(item.path, newProjectName))}" title="Edit">\u270F\uFE0F</a>` : "";
|
|
@@ -8989,7 +9771,7 @@ async function createDirectoryListingHtml(localPath, options) {
|
|
|
8989
9771
|
}
|
|
8990
9772
|
async function createTextEditorHtml(localPath) {
|
|
8991
9773
|
const content = await readFile4(localPath, "utf8");
|
|
8992
|
-
const parentPath =
|
|
9774
|
+
const parentPath = dirname4(localPath);
|
|
8993
9775
|
const language = languageForPath(localPath);
|
|
8994
9776
|
const safeContentLiteral = escapeForInlineScriptString(content);
|
|
8995
9777
|
return `<!doctype html>
|
|
@@ -9058,9 +9840,9 @@ async function createTextEditorHtml(localPath) {
|
|
|
9058
9840
|
|
|
9059
9841
|
// src/server/httpServer.ts
|
|
9060
9842
|
import { WebSocketServer } from "ws";
|
|
9061
|
-
var __dirname =
|
|
9062
|
-
var distDir =
|
|
9063
|
-
var spaEntryFile =
|
|
9843
|
+
var __dirname = dirname5(fileURLToPath(import.meta.url));
|
|
9844
|
+
var distDir = join9(__dirname, "..", "dist");
|
|
9845
|
+
var spaEntryFile = join9(distDir, "index.html");
|
|
9064
9846
|
var IMAGE_CONTENT_TYPES = {
|
|
9065
9847
|
".avif": "image/avif",
|
|
9066
9848
|
".bmp": "image/bmp",
|
|
@@ -9229,7 +10011,7 @@ function createServer(options = {}) {
|
|
|
9229
10011
|
res.status(404).json({ error: "File not found." });
|
|
9230
10012
|
}
|
|
9231
10013
|
});
|
|
9232
|
-
const hasFrontendAssets =
|
|
10014
|
+
const hasFrontendAssets = existsSync6(spaEntryFile);
|
|
9233
10015
|
if (hasFrontendAssets) {
|
|
9234
10016
|
app.use(express.static(distDir));
|
|
9235
10017
|
}
|
|
@@ -9299,26 +10081,26 @@ function generatePassword() {
|
|
|
9299
10081
|
|
|
9300
10082
|
// src/cli/index.ts
|
|
9301
10083
|
var program = new Command().name("codexui").description("Web interface for Codex app-server");
|
|
9302
|
-
var __dirname2 =
|
|
10084
|
+
var __dirname2 = dirname6(fileURLToPath2(import.meta.url));
|
|
9303
10085
|
var hasPromptedCloudflaredInstall = false;
|
|
9304
10086
|
function getCodexHomePath() {
|
|
9305
|
-
return process.env.CODEX_HOME?.trim() ||
|
|
10087
|
+
return process.env.CODEX_HOME?.trim() || join10(homedir7(), ".codex");
|
|
9306
10088
|
}
|
|
9307
10089
|
function getCloudflaredPromptMarkerPath() {
|
|
9308
|
-
return
|
|
10090
|
+
return join10(getCodexHomePath(), ".cloudflared-install-prompted");
|
|
9309
10091
|
}
|
|
9310
10092
|
function hasPromptedCloudflaredInstallPersisted() {
|
|
9311
|
-
return
|
|
10093
|
+
return existsSync7(getCloudflaredPromptMarkerPath());
|
|
9312
10094
|
}
|
|
9313
10095
|
async function persistCloudflaredInstallPrompted() {
|
|
9314
10096
|
const codexHome = getCodexHomePath();
|
|
9315
|
-
|
|
10097
|
+
mkdirSync2(codexHome, { recursive: true });
|
|
9316
10098
|
await writeFile6(getCloudflaredPromptMarkerPath(), `${Date.now()}
|
|
9317
10099
|
`, "utf8");
|
|
9318
10100
|
}
|
|
9319
10101
|
async function readCliVersion() {
|
|
9320
10102
|
try {
|
|
9321
|
-
const packageJsonPath =
|
|
10103
|
+
const packageJsonPath = join10(__dirname2, "..", "package.json");
|
|
9322
10104
|
const raw = await readFile5(packageJsonPath, "utf8");
|
|
9323
10105
|
const parsed = JSON.parse(raw);
|
|
9324
10106
|
return typeof parsed.version === "string" ? parsed.version : "unknown";
|
|
@@ -9343,8 +10125,8 @@ function resolveCloudflaredCommand() {
|
|
|
9343
10125
|
if (canRunCommand("cloudflared", ["--version"])) {
|
|
9344
10126
|
return "cloudflared";
|
|
9345
10127
|
}
|
|
9346
|
-
const localCandidate =
|
|
9347
|
-
if (
|
|
10128
|
+
const localCandidate = join10(homedir7(), ".local", "bin", "cloudflared");
|
|
10129
|
+
if (existsSync7(localCandidate) && canRunCommand(localCandidate, ["--version"])) {
|
|
9348
10130
|
return localCandidate;
|
|
9349
10131
|
}
|
|
9350
10132
|
return null;
|
|
@@ -9397,9 +10179,9 @@ async function ensureCloudflaredInstalledLinux() {
|
|
|
9397
10179
|
if (!mappedArch) {
|
|
9398
10180
|
throw new Error(`cloudflared auto-install is not supported for Linux architecture: ${process.arch}`);
|
|
9399
10181
|
}
|
|
9400
|
-
const userBinDir =
|
|
9401
|
-
|
|
9402
|
-
const destination =
|
|
10182
|
+
const userBinDir = join10(homedir7(), ".local", "bin");
|
|
10183
|
+
mkdirSync2(userBinDir, { recursive: true });
|
|
10184
|
+
const destination = join10(userBinDir, "cloudflared");
|
|
9403
10185
|
const downloadUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${mappedArch}`;
|
|
9404
10186
|
console.log("\ncloudflared not found. Installing to ~/.local/bin...\n");
|
|
9405
10187
|
await downloadFile(downloadUrl, destination);
|
|
@@ -9450,7 +10232,7 @@ async function resolveCloudflaredForTunnel() {
|
|
|
9450
10232
|
}
|
|
9451
10233
|
function hasCodexAuth() {
|
|
9452
10234
|
const codexHome = getCodexHomePath();
|
|
9453
|
-
return
|
|
10235
|
+
return existsSync7(join10(codexHome, "auth.json"));
|
|
9454
10236
|
}
|
|
9455
10237
|
function ensureCodexInstalled() {
|
|
9456
10238
|
let codexCommand = resolveCodexCommand();
|
|
@@ -9640,7 +10422,7 @@ function listenWithFallback(server, startPort) {
|
|
|
9640
10422
|
}
|
|
9641
10423
|
function getCodexGlobalStatePath2() {
|
|
9642
10424
|
const codexHome = getCodexHomePath();
|
|
9643
|
-
return
|
|
10425
|
+
return join10(codexHome, ".codex-global-state.json");
|
|
9644
10426
|
}
|
|
9645
10427
|
function normalizeUniqueStrings(value) {
|
|
9646
10428
|
if (!Array.isArray(value)) return [];
|