codexmate 0.0.14 → 0.0.15
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/README.en.md +146 -362
- package/README.md +146 -361
- package/cli.js +662 -36
- package/doc/CHANGELOG.md +14 -9
- package/doc/CHANGELOG.zh-CN.md +7 -0
- package/package.json +3 -3
- package/web-ui/app.js +24 -324
- package/web-ui/index.html +45 -22
- package/web-ui/modules/config-mode.computed.mjs +123 -0
- package/web-ui/modules/skills.computed.mjs +82 -0
- package/web-ui/modules/skills.methods.mjs +344 -0
package/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ const crypto = require('crypto');
|
|
|
6
6
|
const toml = require('@iarna/toml');
|
|
7
7
|
const JSON5 = require('json5');
|
|
8
8
|
const zipLib = require('zip-lib');
|
|
9
|
+
const yauzl = require('yauzl');
|
|
9
10
|
const { exec, execSync, spawn, spawnSync } = require('child_process');
|
|
10
11
|
const http = require('http');
|
|
11
12
|
const https = require('https');
|
|
@@ -108,12 +109,10 @@ const MAX_SESSION_PATH_LIST_SIZE = 2000;
|
|
|
108
109
|
const AGENTS_FILE_NAME = 'AGENTS.md';
|
|
109
110
|
const CODEX_SKILLS_DIR = path.join(CONFIG_DIR, 'skills');
|
|
110
111
|
const CLAUDE_SKILLS_DIR = path.join(CLAUDE_DIR, 'skills');
|
|
111
|
-
const
|
|
112
|
-
const OPENCODE_SKILLS_DIR = path.join(os.homedir(), '.opencode', 'skills');
|
|
112
|
+
const AGENTS_SKILLS_DIR = path.join(os.homedir(), '.agents', 'skills');
|
|
113
113
|
const SKILL_IMPORT_SOURCES = Object.freeze([
|
|
114
114
|
{ app: 'claude', label: 'Claude Code', dir: CLAUDE_SKILLS_DIR },
|
|
115
|
-
{ app: '
|
|
116
|
-
{ app: 'opencode', label: 'OpenCode', dir: OPENCODE_SKILLS_DIR }
|
|
115
|
+
{ app: 'agents', label: 'Agents', dir: AGENTS_SKILLS_DIR }
|
|
117
116
|
]);
|
|
118
117
|
const MODELS_CACHE_TTL_MS = 60 * 1000;
|
|
119
118
|
const MODELS_NEGATIVE_CACHE_TTL_MS = 5 * 1000;
|
|
@@ -121,6 +120,11 @@ const MODELS_CACHE_MAX_ENTRIES = 50;
|
|
|
121
120
|
const MODELS_RESPONSE_MAX_BYTES = 1024 * 1024;
|
|
122
121
|
const MAX_RECENT_CONFIGS = 3;
|
|
123
122
|
const MAX_UPLOAD_SIZE = 200 * 1024 * 1024;
|
|
123
|
+
const MAX_SKILLS_ZIP_UPLOAD_SIZE = 20 * 1024 * 1024;
|
|
124
|
+
const MAX_SKILLS_ZIP_ENTRY_COUNT = 2000;
|
|
125
|
+
const MAX_SKILLS_ZIP_UNCOMPRESSED_BYTES = 512 * 1024 * 1024;
|
|
126
|
+
const DOWNLOAD_ARTIFACT_TTL_MS = 10 * 60 * 1000;
|
|
127
|
+
const g_downloadArtifacts = new Map();
|
|
124
128
|
const BUILTIN_PROXY_PROVIDER_NAME = 'codexmate-proxy';
|
|
125
129
|
const DEFAULT_BUILTIN_PROXY_SETTINGS = Object.freeze({
|
|
126
130
|
enabled: false,
|
|
@@ -1804,6 +1808,7 @@ function importCodexSkills(params = {}) {
|
|
|
1804
1808
|
const visitedRealPaths = new Set([sourceDirForCopy]);
|
|
1805
1809
|
copyDirRecursive(sourceDirForCopy, targetPath, {
|
|
1806
1810
|
dereferenceSymlinks: true,
|
|
1811
|
+
allowedRootRealPath: sourceDirForCopy,
|
|
1807
1812
|
visitedRealPaths
|
|
1808
1813
|
});
|
|
1809
1814
|
copiedToTarget = true;
|
|
@@ -1835,6 +1840,306 @@ function importCodexSkills(params = {}) {
|
|
|
1835
1840
|
};
|
|
1836
1841
|
}
|
|
1837
1842
|
|
|
1843
|
+
function collectSkillDirectoriesFromRoot(rootDir, limit = MAX_SKILLS_ZIP_ENTRY_COUNT) {
|
|
1844
|
+
const results = [];
|
|
1845
|
+
let truncated = false;
|
|
1846
|
+
if (!rootDir || !fs.existsSync(rootDir)) {
|
|
1847
|
+
return { results, truncated };
|
|
1848
|
+
}
|
|
1849
|
+
const normalizedLimit = Number.isFinite(limit) && limit > 0
|
|
1850
|
+
? Math.floor(limit)
|
|
1851
|
+
: MAX_SKILLS_ZIP_ENTRY_COUNT;
|
|
1852
|
+
const stack = [rootDir];
|
|
1853
|
+
while (stack.length > 0) {
|
|
1854
|
+
if (results.length >= normalizedLimit) {
|
|
1855
|
+
truncated = true;
|
|
1856
|
+
break;
|
|
1857
|
+
}
|
|
1858
|
+
const currentDir = stack.pop();
|
|
1859
|
+
let entries = [];
|
|
1860
|
+
try {
|
|
1861
|
+
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
1862
|
+
} catch (e) {
|
|
1863
|
+
continue;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
const hasSkillFile = entries.some((entry) => entry && entry.isFile() && String(entry.name || '') === 'SKILL.md');
|
|
1867
|
+
if (hasSkillFile) {
|
|
1868
|
+
results.push(currentDir);
|
|
1869
|
+
continue;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
for (const entry of entries) {
|
|
1873
|
+
if (!entry || !entry.isDirectory()) continue;
|
|
1874
|
+
const entryName = typeof entry.name === 'string' ? entry.name.trim() : '';
|
|
1875
|
+
if (!entryName || entryName.startsWith('.')) {
|
|
1876
|
+
continue;
|
|
1877
|
+
}
|
|
1878
|
+
stack.push(path.join(currentDir, entryName));
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
return { results, truncated };
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
function resolveSkillNameFromImportedDirectory(skillDir, extractionRoot, fallbackName = '') {
|
|
1885
|
+
const directoryBaseName = path.basename(skillDir || '');
|
|
1886
|
+
const extractionBaseName = path.basename(extractionRoot || '');
|
|
1887
|
+
let candidate = directoryBaseName;
|
|
1888
|
+
if (!candidate || candidate === extractionBaseName || candidate.startsWith('.')) {
|
|
1889
|
+
const fallback = typeof fallbackName === 'string' ? fallbackName.trim() : '';
|
|
1890
|
+
const fallbackBase = fallback ? path.basename(fallback, path.extname(fallback)) : '';
|
|
1891
|
+
candidate = fallbackBase || candidate;
|
|
1892
|
+
}
|
|
1893
|
+
return normalizeCodexSkillName(candidate);
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
async function importCodexSkillsFromZipFile(zipPath, options = {}) {
|
|
1897
|
+
const fallbackName = typeof options.fallbackName === 'string' ? options.fallbackName : '';
|
|
1898
|
+
const tempDir = typeof options.tempDir === 'string' ? options.tempDir : '';
|
|
1899
|
+
const imported = [];
|
|
1900
|
+
const failed = [];
|
|
1901
|
+
const dedupNames = new Set();
|
|
1902
|
+
const extractionRoot = path.join(tempDir || path.dirname(zipPath), 'extract');
|
|
1903
|
+
|
|
1904
|
+
try {
|
|
1905
|
+
await inspectZipArchiveLimits(zipPath, {
|
|
1906
|
+
maxEntryCount: MAX_SKILLS_ZIP_ENTRY_COUNT,
|
|
1907
|
+
maxUncompressedBytes: MAX_SKILLS_ZIP_UNCOMPRESSED_BYTES
|
|
1908
|
+
});
|
|
1909
|
+
|
|
1910
|
+
await extractUploadZip(zipPath, extractionRoot);
|
|
1911
|
+
const discovery = collectSkillDirectoriesFromRoot(extractionRoot, MAX_SKILLS_ZIP_ENTRY_COUNT);
|
|
1912
|
+
const discoveredDirs = discovery.results;
|
|
1913
|
+
if (discoveredDirs.length === 0) {
|
|
1914
|
+
return { error: '压缩包中未发现包含 SKILL.md 的技能目录' };
|
|
1915
|
+
}
|
|
1916
|
+
if (discovery.truncated) {
|
|
1917
|
+
return { error: '压缩包中的技能目录数量超出导入上限' };
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
ensureDir(CODEX_SKILLS_DIR);
|
|
1921
|
+
for (const skillDir of discoveredDirs) {
|
|
1922
|
+
const normalizedName = resolveSkillNameFromImportedDirectory(skillDir, extractionRoot, fallbackName);
|
|
1923
|
+
if (normalizedName.error) {
|
|
1924
|
+
failed.push({
|
|
1925
|
+
name: path.basename(skillDir || ''),
|
|
1926
|
+
error: normalizedName.error
|
|
1927
|
+
});
|
|
1928
|
+
continue;
|
|
1929
|
+
}
|
|
1930
|
+
const dedupKey = normalizedName.name.toLowerCase();
|
|
1931
|
+
if (dedupNames.has(dedupKey)) {
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
dedupNames.add(dedupKey);
|
|
1935
|
+
|
|
1936
|
+
const targetPath = path.join(CODEX_SKILLS_DIR, normalizedName.name);
|
|
1937
|
+
const targetRelative = path.relative(CODEX_SKILLS_DIR, targetPath);
|
|
1938
|
+
if (targetRelative.startsWith('..') || path.isAbsolute(targetRelative)) {
|
|
1939
|
+
failed.push({
|
|
1940
|
+
name: normalizedName.name,
|
|
1941
|
+
error: '目标路径非法'
|
|
1942
|
+
});
|
|
1943
|
+
continue;
|
|
1944
|
+
}
|
|
1945
|
+
if (fs.existsSync(targetPath)) {
|
|
1946
|
+
failed.push({
|
|
1947
|
+
name: normalizedName.name,
|
|
1948
|
+
error: 'Codex 中已存在同名 skill'
|
|
1949
|
+
});
|
|
1950
|
+
continue;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
let copiedToTarget = false;
|
|
1954
|
+
try {
|
|
1955
|
+
const sourceRealPath = fs.realpathSync(skillDir);
|
|
1956
|
+
const sourceStat = fs.statSync(sourceRealPath);
|
|
1957
|
+
if (!sourceStat.isDirectory()) {
|
|
1958
|
+
failed.push({
|
|
1959
|
+
name: normalizedName.name,
|
|
1960
|
+
error: '来源 skill 无法读取'
|
|
1961
|
+
});
|
|
1962
|
+
continue;
|
|
1963
|
+
}
|
|
1964
|
+
const visitedRealPaths = new Set([sourceRealPath]);
|
|
1965
|
+
copyDirRecursive(sourceRealPath, targetPath, {
|
|
1966
|
+
dereferenceSymlinks: true,
|
|
1967
|
+
allowedRootRealPath: sourceRealPath,
|
|
1968
|
+
visitedRealPaths
|
|
1969
|
+
});
|
|
1970
|
+
copiedToTarget = true;
|
|
1971
|
+
imported.push({
|
|
1972
|
+
name: normalizedName.name,
|
|
1973
|
+
path: targetPath
|
|
1974
|
+
});
|
|
1975
|
+
} catch (e) {
|
|
1976
|
+
if (!copiedToTarget && fs.existsSync(targetPath)) {
|
|
1977
|
+
try {
|
|
1978
|
+
removeDirectoryRecursive(targetPath);
|
|
1979
|
+
} catch (_) {}
|
|
1980
|
+
}
|
|
1981
|
+
failed.push({
|
|
1982
|
+
name: normalizedName.name,
|
|
1983
|
+
error: e && e.message ? e.message : '导入失败'
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
if (imported.length === 0 && failed.length > 0) {
|
|
1989
|
+
return {
|
|
1990
|
+
error: failed[0].error || '导入失败',
|
|
1991
|
+
imported,
|
|
1992
|
+
failed,
|
|
1993
|
+
root: CODEX_SKILLS_DIR
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
return {
|
|
1998
|
+
success: failed.length === 0,
|
|
1999
|
+
imported,
|
|
2000
|
+
failed,
|
|
2001
|
+
root: CODEX_SKILLS_DIR
|
|
2002
|
+
};
|
|
2003
|
+
} catch (e) {
|
|
2004
|
+
return {
|
|
2005
|
+
error: `导入失败:${e && e.message ? e.message : '未知错误'}`
|
|
2006
|
+
};
|
|
2007
|
+
} finally {
|
|
2008
|
+
if (tempDir) {
|
|
2009
|
+
try {
|
|
2010
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
2011
|
+
} catch (_) {}
|
|
2012
|
+
} else if (fs.existsSync(extractionRoot)) {
|
|
2013
|
+
try {
|
|
2014
|
+
fs.rmSync(extractionRoot, { recursive: true, force: true });
|
|
2015
|
+
} catch (_) {}
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
async function importCodexSkillsFromZip(payload = {}) {
|
|
2021
|
+
if (!payload || typeof payload.fileBase64 !== 'string' || !payload.fileBase64.trim()) {
|
|
2022
|
+
return { error: '缺少技能压缩包内容' };
|
|
2023
|
+
}
|
|
2024
|
+
const upload = writeUploadZip(payload.fileBase64, 'codex-skills-import', payload.fileName || 'codex-skills.zip');
|
|
2025
|
+
if (upload.error) {
|
|
2026
|
+
return { error: upload.error };
|
|
2027
|
+
}
|
|
2028
|
+
return importCodexSkillsFromZipFile(upload.zipPath, {
|
|
2029
|
+
tempDir: upload.tempDir,
|
|
2030
|
+
fallbackName: payload.fileName || ''
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
async function exportCodexSkills(params = {}) {
|
|
2035
|
+
const rawNames = Array.isArray(params.names) ? params.names : [];
|
|
2036
|
+
const uniqueNames = Array.from(new Set(rawNames
|
|
2037
|
+
.map((item) => (typeof item === 'string' ? item.trim() : ''))
|
|
2038
|
+
.filter(Boolean)));
|
|
2039
|
+
if (uniqueNames.length === 0) {
|
|
2040
|
+
return { error: '请先选择要导出的 skill' };
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
const exported = [];
|
|
2044
|
+
const failed = [];
|
|
2045
|
+
const stagingTempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-skills-export-'));
|
|
2046
|
+
const stagingRoot = path.join(stagingTempDir, 'skills');
|
|
2047
|
+
ensureDir(stagingRoot);
|
|
2048
|
+
|
|
2049
|
+
try {
|
|
2050
|
+
for (const rawName of uniqueNames) {
|
|
2051
|
+
const normalizedName = normalizeCodexSkillName(rawName);
|
|
2052
|
+
if (normalizedName.error) {
|
|
2053
|
+
failed.push({ name: rawName, error: normalizedName.error });
|
|
2054
|
+
continue;
|
|
2055
|
+
}
|
|
2056
|
+
const sourcePath = path.join(CODEX_SKILLS_DIR, normalizedName.name);
|
|
2057
|
+
const sourceRelative = path.relative(CODEX_SKILLS_DIR, sourcePath);
|
|
2058
|
+
if (sourceRelative.startsWith('..') || path.isAbsolute(sourceRelative)) {
|
|
2059
|
+
failed.push({ name: normalizedName.name, error: '来源路径非法' });
|
|
2060
|
+
continue;
|
|
2061
|
+
}
|
|
2062
|
+
if (!fs.existsSync(sourcePath)) {
|
|
2063
|
+
failed.push({ name: normalizedName.name, error: 'skill 不存在' });
|
|
2064
|
+
continue;
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
try {
|
|
2068
|
+
const lstat = fs.lstatSync(sourcePath);
|
|
2069
|
+
if (!lstat.isDirectory() && !lstat.isSymbolicLink()) {
|
|
2070
|
+
failed.push({ name: normalizedName.name, error: '来源不是技能目录' });
|
|
2071
|
+
continue;
|
|
2072
|
+
}
|
|
2073
|
+
const sourceDirForCopy = lstat.isSymbolicLink() ? fs.realpathSync(sourcePath) : sourcePath;
|
|
2074
|
+
const sourceStat = fs.statSync(sourceDirForCopy);
|
|
2075
|
+
if (!sourceStat.isDirectory()) {
|
|
2076
|
+
failed.push({ name: normalizedName.name, error: '来源 skill 无法读取' });
|
|
2077
|
+
continue;
|
|
2078
|
+
}
|
|
2079
|
+
const targetPath = path.join(stagingRoot, normalizedName.name);
|
|
2080
|
+
const visitedRealPaths = new Set([sourceDirForCopy]);
|
|
2081
|
+
copyDirRecursive(sourceDirForCopy, targetPath, {
|
|
2082
|
+
dereferenceSymlinks: true,
|
|
2083
|
+
allowedRootRealPath: sourceDirForCopy,
|
|
2084
|
+
visitedRealPaths
|
|
2085
|
+
});
|
|
2086
|
+
exported.push({
|
|
2087
|
+
name: normalizedName.name,
|
|
2088
|
+
path: sourcePath
|
|
2089
|
+
});
|
|
2090
|
+
} catch (e) {
|
|
2091
|
+
failed.push({
|
|
2092
|
+
name: normalizedName.name,
|
|
2093
|
+
error: e && e.message ? e.message : '导出失败'
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
if (exported.length === 0) {
|
|
2099
|
+
return {
|
|
2100
|
+
error: failed[0] && failed[0].error ? failed[0].error : '无可导出的 skill',
|
|
2101
|
+
exported,
|
|
2102
|
+
failed,
|
|
2103
|
+
root: CODEX_SKILLS_DIR
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
const randomToken = crypto.randomBytes(12).toString('hex');
|
|
2108
|
+
const zipFileName = `codex-skills-${randomToken}.zip`;
|
|
2109
|
+
const zipFilePath = path.join(os.tmpdir(), zipFileName);
|
|
2110
|
+
if (fs.existsSync(zipFilePath)) {
|
|
2111
|
+
try {
|
|
2112
|
+
fs.unlinkSync(zipFilePath);
|
|
2113
|
+
} catch (_) {}
|
|
2114
|
+
}
|
|
2115
|
+
await zipLib.archiveFolder(stagingRoot, zipFilePath);
|
|
2116
|
+
const artifact = registerDownloadArtifact(zipFilePath, {
|
|
2117
|
+
fileName: zipFileName,
|
|
2118
|
+
deleteAfterDownload: true
|
|
2119
|
+
});
|
|
2120
|
+
|
|
2121
|
+
return {
|
|
2122
|
+
success: failed.length === 0,
|
|
2123
|
+
fileName: zipFileName,
|
|
2124
|
+
downloadPath: artifact.downloadPath,
|
|
2125
|
+
exported,
|
|
2126
|
+
failed,
|
|
2127
|
+
root: CODEX_SKILLS_DIR
|
|
2128
|
+
};
|
|
2129
|
+
} catch (e) {
|
|
2130
|
+
return {
|
|
2131
|
+
error: `导出失败:${e && e.message ? e.message : '未知错误'}`,
|
|
2132
|
+
exported,
|
|
2133
|
+
failed,
|
|
2134
|
+
root: CODEX_SKILLS_DIR
|
|
2135
|
+
};
|
|
2136
|
+
} finally {
|
|
2137
|
+
try {
|
|
2138
|
+
fs.rmSync(stagingTempDir, { recursive: true, force: true });
|
|
2139
|
+
} catch (_) {}
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
|
|
1838
2143
|
function removeDirectoryRecursive(targetPath) {
|
|
1839
2144
|
if (typeof fs.rmSync === 'function') {
|
|
1840
2145
|
fs.rmSync(targetPath, { recursive: true, force: false });
|
|
@@ -6629,6 +6934,70 @@ function readClaudeSettingsInfo() {
|
|
|
6629
6934
|
};
|
|
6630
6935
|
}
|
|
6631
6936
|
|
|
6937
|
+
function registerDownloadArtifact(filePath, options = {}) {
|
|
6938
|
+
const token = crypto.randomBytes(16).toString('hex');
|
|
6939
|
+
const fileName = typeof options.fileName === 'string' && options.fileName.trim()
|
|
6940
|
+
? options.fileName.trim()
|
|
6941
|
+
: path.basename(filePath || '');
|
|
6942
|
+
const ttlMs = Number.isFinite(options.ttlMs) && options.ttlMs > 0
|
|
6943
|
+
? Math.floor(options.ttlMs)
|
|
6944
|
+
: DOWNLOAD_ARTIFACT_TTL_MS;
|
|
6945
|
+
const expiresAt = Date.now() + ttlMs;
|
|
6946
|
+
const deleteAfterDownload = options.deleteAfterDownload !== false;
|
|
6947
|
+
|
|
6948
|
+
g_downloadArtifacts.set(token, {
|
|
6949
|
+
filePath,
|
|
6950
|
+
fileName,
|
|
6951
|
+
deleteAfterDownload,
|
|
6952
|
+
expiresAt
|
|
6953
|
+
});
|
|
6954
|
+
|
|
6955
|
+
setTimeout(() => {
|
|
6956
|
+
const artifact = g_downloadArtifacts.get(token);
|
|
6957
|
+
if (!artifact) return;
|
|
6958
|
+
if (Date.now() < artifact.expiresAt) return;
|
|
6959
|
+
g_downloadArtifacts.delete(token);
|
|
6960
|
+
if (artifact.deleteAfterDownload && artifact.filePath && fs.existsSync(artifact.filePath)) {
|
|
6961
|
+
try {
|
|
6962
|
+
fs.unlinkSync(artifact.filePath);
|
|
6963
|
+
} catch (_) {}
|
|
6964
|
+
}
|
|
6965
|
+
}, ttlMs + 2000);
|
|
6966
|
+
|
|
6967
|
+
return {
|
|
6968
|
+
token,
|
|
6969
|
+
fileName,
|
|
6970
|
+
downloadPath: `/download/${encodeURIComponent(token)}`
|
|
6971
|
+
};
|
|
6972
|
+
}
|
|
6973
|
+
|
|
6974
|
+
function resolveDownloadArtifact(tokenOrFileName, options = {}) {
|
|
6975
|
+
if (!tokenOrFileName) return null;
|
|
6976
|
+
const token = typeof tokenOrFileName === 'string' ? tokenOrFileName.trim() : '';
|
|
6977
|
+
if (!token) return null;
|
|
6978
|
+
|
|
6979
|
+
const artifact = g_downloadArtifacts.get(token);
|
|
6980
|
+
if (!artifact) {
|
|
6981
|
+
return null;
|
|
6982
|
+
}
|
|
6983
|
+
if (Date.now() > artifact.expiresAt) {
|
|
6984
|
+
g_downloadArtifacts.delete(token);
|
|
6985
|
+
if (artifact.deleteAfterDownload && artifact.filePath && fs.existsSync(artifact.filePath)) {
|
|
6986
|
+
try {
|
|
6987
|
+
fs.unlinkSync(artifact.filePath);
|
|
6988
|
+
} catch (_) {}
|
|
6989
|
+
}
|
|
6990
|
+
return null;
|
|
6991
|
+
}
|
|
6992
|
+
if (options && options.consume === true) {
|
|
6993
|
+
g_downloadArtifacts.delete(token);
|
|
6994
|
+
}
|
|
6995
|
+
return {
|
|
6996
|
+
token,
|
|
6997
|
+
...artifact
|
|
6998
|
+
};
|
|
6999
|
+
}
|
|
7000
|
+
|
|
6632
7001
|
// API: 打包 Claude 配置目录(系统 zip 可用则使用,否则回退 zip-lib)
|
|
6633
7002
|
async function prepareClaudeDirDownload() {
|
|
6634
7003
|
try {
|
|
@@ -6693,12 +7062,16 @@ async function prepareCodexDirDownload() {
|
|
|
6693
7062
|
|
|
6694
7063
|
function copyDirRecursive(srcDir, destDir, options = {}) {
|
|
6695
7064
|
const dereferenceSymlinks = !!(options && options.dereferenceSymlinks);
|
|
7065
|
+
const allowedRootRealPath = (options && typeof options.allowedRootRealPath === 'string')
|
|
7066
|
+
? options.allowedRootRealPath
|
|
7067
|
+
: '';
|
|
6696
7068
|
const visitedRealPaths = options && options.visitedRealPaths instanceof Set
|
|
6697
7069
|
? options.visitedRealPaths
|
|
6698
7070
|
: new Set();
|
|
6699
7071
|
const childOptions = {
|
|
6700
7072
|
...options,
|
|
6701
7073
|
dereferenceSymlinks,
|
|
7074
|
+
allowedRootRealPath,
|
|
6702
7075
|
visitedRealPaths
|
|
6703
7076
|
};
|
|
6704
7077
|
ensureDir(destDir);
|
|
@@ -6712,6 +7085,9 @@ function copyDirRecursive(srcDir, destDir, options = {}) {
|
|
|
6712
7085
|
continue;
|
|
6713
7086
|
}
|
|
6714
7087
|
const realPath = fs.realpathSync(srcPath);
|
|
7088
|
+
if (allowedRootRealPath && !isPathInside(realPath, allowedRootRealPath)) {
|
|
7089
|
+
throw new Error(`symlink escapes skill root: ${srcPath}`);
|
|
7090
|
+
}
|
|
6715
7091
|
if (visitedRealPaths.has(realPath)) {
|
|
6716
7092
|
continue;
|
|
6717
7093
|
}
|
|
@@ -6724,6 +7100,9 @@ function copyDirRecursive(srcDir, destDir, options = {}) {
|
|
|
6724
7100
|
} else if (entry.isSymbolicLink()) {
|
|
6725
7101
|
if (dereferenceSymlinks) {
|
|
6726
7102
|
const realPath = fs.realpathSync(srcPath);
|
|
7103
|
+
if (allowedRootRealPath && !isPathInside(realPath, allowedRootRealPath)) {
|
|
7104
|
+
throw new Error(`symlink escapes skill root: ${srcPath}`);
|
|
7105
|
+
}
|
|
6727
7106
|
const realStat = fs.statSync(realPath);
|
|
6728
7107
|
if (realStat.isDirectory()) {
|
|
6729
7108
|
if (visitedRealPaths.has(realPath)) {
|
|
@@ -6748,6 +7127,139 @@ function copyDirRecursive(srcDir, destDir, options = {}) {
|
|
|
6748
7127
|
}
|
|
6749
7128
|
}
|
|
6750
7129
|
|
|
7130
|
+
function inspectZipArchiveLimits(zipPath, options = {}) {
|
|
7131
|
+
const maxEntryCount = Number.isFinite(options.maxEntryCount) && options.maxEntryCount > 0
|
|
7132
|
+
? Math.floor(options.maxEntryCount)
|
|
7133
|
+
: MAX_SKILLS_ZIP_ENTRY_COUNT;
|
|
7134
|
+
const maxUncompressedBytes = Number.isFinite(options.maxUncompressedBytes) && options.maxUncompressedBytes > 0
|
|
7135
|
+
? Math.floor(options.maxUncompressedBytes)
|
|
7136
|
+
: MAX_SKILLS_ZIP_UNCOMPRESSED_BYTES;
|
|
7137
|
+
|
|
7138
|
+
return new Promise((resolve, reject) => {
|
|
7139
|
+
yauzl.open(zipPath, { lazyEntries: true, autoClose: true }, (openErr, zipFile) => {
|
|
7140
|
+
if (openErr) {
|
|
7141
|
+
reject(openErr);
|
|
7142
|
+
return;
|
|
7143
|
+
}
|
|
7144
|
+
if (!zipFile) {
|
|
7145
|
+
reject(new Error('无法读取 ZIP 文件'));
|
|
7146
|
+
return;
|
|
7147
|
+
}
|
|
7148
|
+
let entryCount = 0;
|
|
7149
|
+
let totalUncompressedBytes = 0;
|
|
7150
|
+
let settled = false;
|
|
7151
|
+
const finish = (err, data) => {
|
|
7152
|
+
if (settled) return;
|
|
7153
|
+
settled = true;
|
|
7154
|
+
try {
|
|
7155
|
+
zipFile.close();
|
|
7156
|
+
} catch (_) {}
|
|
7157
|
+
if (err) {
|
|
7158
|
+
reject(err);
|
|
7159
|
+
} else {
|
|
7160
|
+
resolve(data);
|
|
7161
|
+
}
|
|
7162
|
+
};
|
|
7163
|
+
|
|
7164
|
+
zipFile.on('entry', (entry) => {
|
|
7165
|
+
if (settled) return;
|
|
7166
|
+
entryCount += 1;
|
|
7167
|
+
const entrySize = Number.isFinite(entry.uncompressedSize) ? entry.uncompressedSize : 0;
|
|
7168
|
+
totalUncompressedBytes += entrySize;
|
|
7169
|
+
if (entryCount > maxEntryCount) {
|
|
7170
|
+
finish(new Error(`压缩包条目过多(>${maxEntryCount})`));
|
|
7171
|
+
return;
|
|
7172
|
+
}
|
|
7173
|
+
if (totalUncompressedBytes > maxUncompressedBytes) {
|
|
7174
|
+
finish(new Error(`压缩包解压总大小超限(>${Math.floor(maxUncompressedBytes / 1024 / 1024)}MB)`));
|
|
7175
|
+
return;
|
|
7176
|
+
}
|
|
7177
|
+
zipFile.readEntry();
|
|
7178
|
+
});
|
|
7179
|
+
|
|
7180
|
+
zipFile.on('end', () => {
|
|
7181
|
+
finish(null, { entryCount, totalUncompressedBytes });
|
|
7182
|
+
});
|
|
7183
|
+
|
|
7184
|
+
zipFile.on('error', (zipErr) => {
|
|
7185
|
+
finish(zipErr);
|
|
7186
|
+
});
|
|
7187
|
+
|
|
7188
|
+
zipFile.readEntry();
|
|
7189
|
+
});
|
|
7190
|
+
});
|
|
7191
|
+
}
|
|
7192
|
+
|
|
7193
|
+
function writeUploadZipStream(req, prefix, originalName = '', maxSize = MAX_SKILLS_ZIP_UPLOAD_SIZE) {
|
|
7194
|
+
return new Promise((resolve, reject) => {
|
|
7195
|
+
const lengthHeader = parseInt(req.headers['content-length'] || '0', 10);
|
|
7196
|
+
if (Number.isFinite(lengthHeader) && lengthHeader > maxSize) {
|
|
7197
|
+
reject(new Error(`备份文件过大(>${Math.floor(maxSize / 1024 / 1024)}MB)`));
|
|
7198
|
+
return;
|
|
7199
|
+
}
|
|
7200
|
+
|
|
7201
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `${prefix}-`));
|
|
7202
|
+
const rawName = originalName && typeof originalName === 'string' ? originalName : `${prefix}.zip`;
|
|
7203
|
+
const fileName = path.basename(rawName);
|
|
7204
|
+
const zipPath = path.join(tempDir, fileName.toLowerCase().endsWith('.zip') ? fileName : `${fileName}.zip`);
|
|
7205
|
+
const stream = fs.createWriteStream(zipPath);
|
|
7206
|
+
let bytesWritten = 0;
|
|
7207
|
+
let settled = false;
|
|
7208
|
+
let hasContent = false;
|
|
7209
|
+
|
|
7210
|
+
const fail = (err) => {
|
|
7211
|
+
if (settled) return;
|
|
7212
|
+
settled = true;
|
|
7213
|
+
try {
|
|
7214
|
+
stream.destroy();
|
|
7215
|
+
} catch (_) {}
|
|
7216
|
+
try {
|
|
7217
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
7218
|
+
} catch (_) {}
|
|
7219
|
+
reject(err);
|
|
7220
|
+
};
|
|
7221
|
+
|
|
7222
|
+
const done = () => {
|
|
7223
|
+
if (settled) return;
|
|
7224
|
+
settled = true;
|
|
7225
|
+
if (!hasContent || bytesWritten <= 0) {
|
|
7226
|
+
try {
|
|
7227
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
7228
|
+
} catch (_) {}
|
|
7229
|
+
reject(new Error('备份文件为空'));
|
|
7230
|
+
return;
|
|
7231
|
+
}
|
|
7232
|
+
resolve({ tempDir, zipPath });
|
|
7233
|
+
};
|
|
7234
|
+
|
|
7235
|
+
req.on('error', (err) => fail(err));
|
|
7236
|
+
req.on('aborted', () => fail(new Error('上传已中断')));
|
|
7237
|
+
req.on('close', () => {
|
|
7238
|
+
if (!settled && !req.complete) {
|
|
7239
|
+
fail(new Error('上传已中断'));
|
|
7240
|
+
}
|
|
7241
|
+
});
|
|
7242
|
+
stream.on('error', (err) => fail(err));
|
|
7243
|
+
req.on('data', (chunk) => {
|
|
7244
|
+
if (settled) return;
|
|
7245
|
+
hasContent = true;
|
|
7246
|
+
bytesWritten += chunk.length;
|
|
7247
|
+
if (bytesWritten > maxSize) {
|
|
7248
|
+
fail(new Error(`备份文件过大(>${Math.floor(maxSize / 1024 / 1024)}MB)`));
|
|
7249
|
+
try {
|
|
7250
|
+
req.destroy();
|
|
7251
|
+
} catch (_) {}
|
|
7252
|
+
return;
|
|
7253
|
+
}
|
|
7254
|
+
stream.write(chunk);
|
|
7255
|
+
});
|
|
7256
|
+
req.on('end', () => {
|
|
7257
|
+
if (settled) return;
|
|
7258
|
+
stream.end(() => done());
|
|
7259
|
+
});
|
|
7260
|
+
});
|
|
7261
|
+
}
|
|
7262
|
+
|
|
6751
7263
|
function writeUploadZip(base64, prefix, originalName = '') {
|
|
6752
7264
|
let buffer;
|
|
6753
7265
|
try {
|
|
@@ -7443,7 +7955,7 @@ async function cmdExportSession(args = []) {
|
|
|
7443
7955
|
}
|
|
7444
7956
|
|
|
7445
7957
|
function parseStartOptions(args = []) {
|
|
7446
|
-
const options = { host: '' };
|
|
7958
|
+
const options = { host: '', noBrowser: false };
|
|
7447
7959
|
if (!Array.isArray(args)) {
|
|
7448
7960
|
return options;
|
|
7449
7961
|
}
|
|
@@ -7451,6 +7963,10 @@ function parseStartOptions(args = []) {
|
|
|
7451
7963
|
for (let i = 0; i < args.length; i++) {
|
|
7452
7964
|
const arg = args[i];
|
|
7453
7965
|
if (!arg) continue;
|
|
7966
|
+
if (arg === '--no-browser') {
|
|
7967
|
+
options.noBrowser = true;
|
|
7968
|
+
continue;
|
|
7969
|
+
}
|
|
7454
7970
|
if (arg.startsWith('--host=')) {
|
|
7455
7971
|
options.host = arg.slice('--host='.length);
|
|
7456
7972
|
continue;
|
|
@@ -7523,11 +8039,125 @@ function watchPathsForRestart(targets, onChange) {
|
|
|
7523
8039
|
};
|
|
7524
8040
|
}
|
|
7525
8041
|
|
|
8042
|
+
function writeJsonResponse(res, statusCode, payload) {
|
|
8043
|
+
const body = JSON.stringify(payload, null, 2);
|
|
8044
|
+
res.writeHead(statusCode, {
|
|
8045
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
8046
|
+
'Content-Length': Buffer.byteLength(body, 'utf-8')
|
|
8047
|
+
});
|
|
8048
|
+
res.end(body, 'utf-8');
|
|
8049
|
+
}
|
|
8050
|
+
|
|
8051
|
+
function streamZipDownloadResponse(res, filePath, options = {}) {
|
|
8052
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
8053
|
+
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
8054
|
+
res.end('File Not Found');
|
|
8055
|
+
return;
|
|
8056
|
+
}
|
|
8057
|
+
const stat = fs.statSync(filePath);
|
|
8058
|
+
if (!stat.isFile()) {
|
|
8059
|
+
res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
8060
|
+
res.end('Not a File');
|
|
8061
|
+
return;
|
|
8062
|
+
}
|
|
8063
|
+
const downloadName = typeof options.fileName === 'string' && options.fileName.trim()
|
|
8064
|
+
? options.fileName.trim()
|
|
8065
|
+
: path.basename(filePath);
|
|
8066
|
+
const deleteAfterDownload = !!options.deleteAfterDownload;
|
|
8067
|
+
const onAfterComplete = typeof options.onAfterComplete === 'function'
|
|
8068
|
+
? options.onAfterComplete
|
|
8069
|
+
: null;
|
|
8070
|
+
res.writeHead(200, {
|
|
8071
|
+
'Content-Type': 'application/zip',
|
|
8072
|
+
'Content-Disposition': `attachment; filename="${path.basename(downloadName)}"`,
|
|
8073
|
+
'Content-Length': stat.size
|
|
8074
|
+
});
|
|
8075
|
+
|
|
8076
|
+
const stream = fs.createReadStream(filePath);
|
|
8077
|
+
let finished = false;
|
|
8078
|
+
const finalize = () => {
|
|
8079
|
+
if (finished) return;
|
|
8080
|
+
finished = true;
|
|
8081
|
+
if (deleteAfterDownload && fs.existsSync(filePath)) {
|
|
8082
|
+
try {
|
|
8083
|
+
fs.unlinkSync(filePath);
|
|
8084
|
+
} catch (_) {}
|
|
8085
|
+
}
|
|
8086
|
+
if (onAfterComplete) {
|
|
8087
|
+
try {
|
|
8088
|
+
onAfterComplete();
|
|
8089
|
+
} catch (_) {}
|
|
8090
|
+
}
|
|
8091
|
+
};
|
|
8092
|
+
stream.on('error', () => {
|
|
8093
|
+
if (!res.headersSent) {
|
|
8094
|
+
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
8095
|
+
res.end('Download Error');
|
|
8096
|
+
} else {
|
|
8097
|
+
try {
|
|
8098
|
+
res.destroy();
|
|
8099
|
+
} catch (_) {}
|
|
8100
|
+
}
|
|
8101
|
+
finalize();
|
|
8102
|
+
});
|
|
8103
|
+
res.on('finish', finalize);
|
|
8104
|
+
res.on('close', finalize);
|
|
8105
|
+
stream.pipe(res);
|
|
8106
|
+
}
|
|
8107
|
+
|
|
8108
|
+
function resolveUploadFileNameFromRequest(req, fallbackName = 'codex-skills.zip') {
|
|
8109
|
+
const rawHeader = req.headers['x-codexmate-file-name'];
|
|
8110
|
+
const source = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader;
|
|
8111
|
+
const fallback = typeof fallbackName === 'string' && fallbackName.trim()
|
|
8112
|
+
? fallbackName.trim()
|
|
8113
|
+
: 'codex-skills.zip';
|
|
8114
|
+
if (!source || typeof source !== 'string') {
|
|
8115
|
+
return fallback;
|
|
8116
|
+
}
|
|
8117
|
+
const decoded = (() => {
|
|
8118
|
+
try {
|
|
8119
|
+
return decodeURIComponent(source);
|
|
8120
|
+
} catch (_) {
|
|
8121
|
+
return source;
|
|
8122
|
+
}
|
|
8123
|
+
})();
|
|
8124
|
+
const normalized = path.basename(decoded.trim());
|
|
8125
|
+
return normalized || fallback;
|
|
8126
|
+
}
|
|
8127
|
+
|
|
8128
|
+
async function handleImportCodexSkillsZipUpload(req, res) {
|
|
8129
|
+
if (req.method !== 'POST') {
|
|
8130
|
+
writeJsonResponse(res, 405, { error: 'Method Not Allowed' });
|
|
8131
|
+
return;
|
|
8132
|
+
}
|
|
8133
|
+
try {
|
|
8134
|
+
const fileName = resolveUploadFileNameFromRequest(req, 'codex-skills.zip');
|
|
8135
|
+
const upload = await writeUploadZipStream(
|
|
8136
|
+
req,
|
|
8137
|
+
'codex-skills-import',
|
|
8138
|
+
fileName,
|
|
8139
|
+
MAX_SKILLS_ZIP_UPLOAD_SIZE
|
|
8140
|
+
);
|
|
8141
|
+
const result = await importCodexSkillsFromZipFile(upload.zipPath, {
|
|
8142
|
+
tempDir: upload.tempDir,
|
|
8143
|
+
fallbackName: fileName
|
|
8144
|
+
});
|
|
8145
|
+
writeJsonResponse(res, 200, result || {});
|
|
8146
|
+
} catch (e) {
|
|
8147
|
+
const message = e && e.message ? e.message : '上传失败';
|
|
8148
|
+
writeJsonResponse(res, 400, { error: message });
|
|
8149
|
+
}
|
|
8150
|
+
}
|
|
8151
|
+
|
|
7526
8152
|
function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser }) {
|
|
7527
8153
|
const connections = new Set();
|
|
7528
8154
|
|
|
7529
8155
|
const server = http.createServer((req, res) => {
|
|
7530
8156
|
const requestPath = (req.url || '/').split('?')[0];
|
|
8157
|
+
if (requestPath === '/api/import-codex-skills-zip') {
|
|
8158
|
+
void handleImportCodexSkillsZipUpload(req, res);
|
|
8159
|
+
return;
|
|
8160
|
+
}
|
|
7531
8161
|
if (requestPath === '/api') {
|
|
7532
8162
|
let body = '';
|
|
7533
8163
|
req.on('data', chunk => body += chunk);
|
|
@@ -7651,6 +8281,9 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
7651
8281
|
case 'import-codex-skills':
|
|
7652
8282
|
result = importCodexSkills(params || {});
|
|
7653
8283
|
break;
|
|
8284
|
+
case 'export-codex-skills':
|
|
8285
|
+
result = await exportCodexSkills(params || {});
|
|
8286
|
+
break;
|
|
7654
8287
|
case 'get-openclaw-config':
|
|
7655
8288
|
result = readOpenclawConfigFile();
|
|
7656
8289
|
break;
|
|
@@ -7909,32 +8542,35 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
7909
8542
|
fs.createReadStream(filePath).pipe(res);
|
|
7910
8543
|
} else if (requestPath.startsWith('/download/')) {
|
|
7911
8544
|
const fileName = requestPath.slice('/download/'.length);
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
res.
|
|
7918
|
-
res.end('Forbidden');
|
|
8545
|
+
let decodedFileName = '';
|
|
8546
|
+
try {
|
|
8547
|
+
decodedFileName = decodeURIComponent(fileName);
|
|
8548
|
+
} catch (_) {
|
|
8549
|
+
res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
8550
|
+
res.end('Bad Request');
|
|
7919
8551
|
return;
|
|
7920
8552
|
}
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
8553
|
+
|
|
8554
|
+
const artifact = resolveDownloadArtifact(decodedFileName, { consume: true });
|
|
8555
|
+
if (artifact) {
|
|
8556
|
+
streamZipDownloadResponse(res, artifact.filePath, {
|
|
8557
|
+
fileName: artifact.fileName,
|
|
8558
|
+
deleteAfterDownload: artifact.deleteAfterDownload !== false
|
|
8559
|
+
});
|
|
7924
8560
|
return;
|
|
7925
8561
|
}
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
8562
|
+
|
|
8563
|
+
const tempDir = os.tmpdir();
|
|
8564
|
+
const legacyFilePath = path.join(tempDir, decodedFileName);
|
|
8565
|
+
if (!isPathInside(legacyFilePath, tempDir)) {
|
|
8566
|
+
res.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
8567
|
+
res.end('Forbidden');
|
|
7930
8568
|
return;
|
|
7931
8569
|
}
|
|
7932
|
-
res
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
'Content-Length': stat.size
|
|
8570
|
+
streamZipDownloadResponse(res, legacyFilePath, {
|
|
8571
|
+
fileName: path.basename(legacyFilePath),
|
|
8572
|
+
deleteAfterDownload: false
|
|
7936
8573
|
});
|
|
7937
|
-
fs.createReadStream(filePath).pipe(res);
|
|
7938
8574
|
} else if (requestPath.startsWith('/res/')) {
|
|
7939
8575
|
const normalized = path.normalize(requestPath).replace(/^([\\.\\/])+/, '');
|
|
7940
8576
|
const filePath = path.join(__dirname, normalized);
|
|
@@ -8057,7 +8693,7 @@ function cmdStart(options = {}) {
|
|
|
8057
8693
|
webDir,
|
|
8058
8694
|
host,
|
|
8059
8695
|
port,
|
|
8060
|
-
openBrowser:
|
|
8696
|
+
openBrowser: !options.noBrowser
|
|
8061
8697
|
});
|
|
8062
8698
|
|
|
8063
8699
|
const proxySettings = readBuiltinProxySettings();
|
|
@@ -8669,10 +9305,6 @@ async function cmdQwen(args = []) {
|
|
|
8669
9305
|
return runProxyCommand('Qwen', ['qwen', 'qwen-code'], args, 'npm install -g @qwen-code/qwen-code');
|
|
8670
9306
|
}
|
|
8671
9307
|
|
|
8672
|
-
async function cmdGemini(args = []) {
|
|
8673
|
-
return runProxyCommand('Gemini', ['gemini', 'gemini-cli'], args, 'npm install -g @google/gemini-cli');
|
|
8674
|
-
}
|
|
8675
|
-
|
|
8676
9308
|
function parseMcpOptions(args = []) {
|
|
8677
9309
|
const options = {
|
|
8678
9310
|
subcommand: 'serve',
|
|
@@ -10132,10 +10764,9 @@ async function main() {
|
|
|
10132
10764
|
console.log(' codexmate auth <list|import|switch|delete|status> 认证文件管理');
|
|
10133
10765
|
console.log(' codexmate proxy <status|set|apply|enable|start|stop> 内建代理');
|
|
10134
10766
|
console.log(' codexmate workflow <list|get|validate|run|runs> MCP 工作流中心');
|
|
10135
|
-
console.log(' codexmate run [--host <HOST>] 启动 Web 界面');
|
|
10767
|
+
console.log(' codexmate run [--host <HOST>] [--no-browser] 启动 Web 界面');
|
|
10136
10768
|
console.log(' codexmate codex [参数...] 等同于 codex --yolo');
|
|
10137
10769
|
console.log(' codexmate qwen [参数...] 等同于 qwen --yolo');
|
|
10138
|
-
console.log(' codexmate gemini [参数...] 等同于 gemini --yolo');
|
|
10139
10770
|
console.log(' codexmate mcp [serve] [--transport stdio] [--allow-write|--read-only]');
|
|
10140
10771
|
console.log(' codexmate export-session --source <codex|claude> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
|
|
10141
10772
|
console.log(' codexmate zip <路径> [--max:级别] 压缩(系统 zip 优先,其次 zip-lib)');
|
|
@@ -10174,11 +10805,6 @@ async function main() {
|
|
|
10174
10805
|
process.exit(exitCode);
|
|
10175
10806
|
break;
|
|
10176
10807
|
}
|
|
10177
|
-
case 'gemini': {
|
|
10178
|
-
const exitCode = await cmdGemini(args.slice(1));
|
|
10179
|
-
process.exit(exitCode);
|
|
10180
|
-
break;
|
|
10181
|
-
}
|
|
10182
10808
|
case 'mcp': await cmdMcp(args.slice(1)); break;
|
|
10183
10809
|
case 'export-session': await cmdExportSession(args.slice(1)); break;
|
|
10184
10810
|
case 'zip': {
|