codexmate 0.0.18 → 0.0.19
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 +28 -15
- package/README.md +28 -23
- package/cli.js +498 -319
- package/package.json +1 -1
- package/web-ui/app.js +36 -260
- package/web-ui/index.html +274 -122
- package/web-ui/modules/config-mode.computed.mjs +1 -0
- package/web-ui/modules/skills.computed.mjs +26 -1
- package/web-ui/modules/skills.methods.mjs +154 -23
- package/web-ui/session-helpers.mjs +12 -0
- package/web-ui/styles.css +171 -2
- package/doc/CHANGELOG.md +0 -32
- package/doc/CHANGELOG.zh-CN.md +0 -34
package/cli.js
CHANGED
|
@@ -64,7 +64,8 @@ const {
|
|
|
64
64
|
} = require('./lib/workflow-engine');
|
|
65
65
|
|
|
66
66
|
const DEFAULT_WEB_PORT = 3737;
|
|
67
|
-
const DEFAULT_WEB_HOST = '
|
|
67
|
+
const DEFAULT_WEB_HOST = '0.0.0.0';
|
|
68
|
+
const DEFAULT_WEB_OPEN_HOST = '127.0.0.1';
|
|
68
69
|
|
|
69
70
|
// ============================================================================
|
|
70
71
|
// 配置
|
|
@@ -116,9 +117,13 @@ const AGENTS_FILE_NAME = 'AGENTS.md';
|
|
|
116
117
|
const CODEX_SKILLS_DIR = path.join(CONFIG_DIR, 'skills');
|
|
117
118
|
const CLAUDE_SKILLS_DIR = path.join(CLAUDE_DIR, 'skills');
|
|
118
119
|
const AGENTS_SKILLS_DIR = path.join(os.homedir(), '.agents', 'skills');
|
|
120
|
+
const SKILL_TARGETS = Object.freeze([
|
|
121
|
+
Object.freeze({ app: 'codex', label: 'Codex', dir: getCodexSkillsDir() }),
|
|
122
|
+
Object.freeze({ app: 'claude', label: 'Claude Code', dir: getClaudeSkillsDir() })
|
|
123
|
+
]);
|
|
119
124
|
const SKILL_IMPORT_SOURCES = Object.freeze([
|
|
120
|
-
|
|
121
|
-
{ app: 'agents', label: 'Agents', dir: AGENTS_SKILLS_DIR }
|
|
125
|
+
...SKILL_TARGETS,
|
|
126
|
+
Object.freeze({ app: 'agents', label: 'Agents', dir: AGENTS_SKILLS_DIR })
|
|
122
127
|
]);
|
|
123
128
|
const MODELS_CACHE_TTL_MS = 60 * 1000;
|
|
124
129
|
const MODELS_NEGATIVE_CACHE_TTL_MS = 5 * 1000;
|
|
@@ -167,6 +172,38 @@ const CLI_INSTALL_TARGETS = Object.freeze([
|
|
|
167
172
|
const HTTP_KEEP_ALIVE_AGENT = new http.Agent({ keepAlive: true });
|
|
168
173
|
const HTTPS_KEEP_ALIVE_AGENT = new https.Agent({ keepAlive: true });
|
|
169
174
|
|
|
175
|
+
function getCodexSkillsDir() {
|
|
176
|
+
const envCodexHome = typeof process.env.CODEX_HOME === 'string' ? process.env.CODEX_HOME.trim() : '';
|
|
177
|
+
if (envCodexHome) {
|
|
178
|
+
const target = path.join(envCodexHome, 'skills');
|
|
179
|
+
return resolveExistingDir([target], target);
|
|
180
|
+
}
|
|
181
|
+
const xdgConfig = typeof process.env.XDG_CONFIG_HOME === 'string' ? process.env.XDG_CONFIG_HOME.trim() : '';
|
|
182
|
+
if (xdgConfig) {
|
|
183
|
+
const target = path.join(xdgConfig, 'codex', 'skills');
|
|
184
|
+
return resolveExistingDir([target], target);
|
|
185
|
+
}
|
|
186
|
+
const homeConfigDir = path.join(os.homedir(), '.config', 'codex', 'skills');
|
|
187
|
+
return resolveExistingDir([homeConfigDir], CODEX_SKILLS_DIR);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function getClaudeSkillsDir() {
|
|
191
|
+
const envClaudeHome = typeof process.env.CLAUDE_HOME === 'string' && process.env.CLAUDE_HOME.trim()
|
|
192
|
+
? process.env.CLAUDE_HOME.trim()
|
|
193
|
+
: (typeof process.env.CLAUDE_CONFIG_DIR === 'string' ? process.env.CLAUDE_CONFIG_DIR.trim() : '');
|
|
194
|
+
if (envClaudeHome) {
|
|
195
|
+
const target = path.join(envClaudeHome, 'skills');
|
|
196
|
+
return resolveExistingDir([target], target);
|
|
197
|
+
}
|
|
198
|
+
const xdgConfig = typeof process.env.XDG_CONFIG_HOME === 'string' ? process.env.XDG_CONFIG_HOME.trim() : '';
|
|
199
|
+
if (xdgConfig) {
|
|
200
|
+
const target = path.join(xdgConfig, 'claude', 'skills');
|
|
201
|
+
return resolveExistingDir([target], target);
|
|
202
|
+
}
|
|
203
|
+
const homeConfigDir = path.join(os.homedir(), '.config', 'claude', 'skills');
|
|
204
|
+
return resolveExistingDir([homeConfigDir], CLAUDE_SKILLS_DIR);
|
|
205
|
+
}
|
|
206
|
+
|
|
170
207
|
function resolveWebPort() {
|
|
171
208
|
const raw = process.env.CODEXMATE_PORT;
|
|
172
209
|
if (!raw) return DEFAULT_WEB_PORT;
|
|
@@ -216,7 +253,7 @@ let g_builtinProxyRuntime = null;
|
|
|
216
253
|
const DEFAULT_LOCAL_PROVIDER_NAME = 'local';
|
|
217
254
|
|
|
218
255
|
function isBuiltinProxyProvider(providerName) {
|
|
219
|
-
return typeof providerName === 'string' && providerName.trim() === BUILTIN_PROXY_PROVIDER_NAME;
|
|
256
|
+
return typeof providerName === 'string' && providerName.trim().toLowerCase() === BUILTIN_PROXY_PROVIDER_NAME.toLowerCase();
|
|
220
257
|
}
|
|
221
258
|
|
|
222
259
|
function isReservedProviderNameForCreation(providerName) {
|
|
@@ -889,12 +926,18 @@ function normalizeAuthRegistry(raw) {
|
|
|
889
926
|
};
|
|
890
927
|
}
|
|
891
928
|
|
|
929
|
+
function ensureAuthProfileStoragePrepared() {
|
|
930
|
+
ensureDir(AUTH_PROFILES_DIR);
|
|
931
|
+
}
|
|
932
|
+
|
|
892
933
|
function readAuthRegistry() {
|
|
934
|
+
ensureAuthProfileStoragePrepared();
|
|
893
935
|
const parsed = readJsonFile(AUTH_REGISTRY_FILE, null);
|
|
894
936
|
return normalizeAuthRegistry(parsed);
|
|
895
937
|
}
|
|
896
938
|
|
|
897
939
|
function writeAuthRegistry(registry) {
|
|
940
|
+
ensureAuthProfileStoragePrepared();
|
|
898
941
|
writeJsonAtomic(AUTH_REGISTRY_FILE, normalizeAuthRegistry(registry));
|
|
899
942
|
}
|
|
900
943
|
|
|
@@ -953,6 +996,7 @@ function listAuthProfilesInfo() {
|
|
|
953
996
|
}
|
|
954
997
|
|
|
955
998
|
function upsertAuthProfile(payload, options = {}) {
|
|
999
|
+
ensureAuthProfileStoragePrepared();
|
|
956
1000
|
const safePayload = parseAuthProfileJson(JSON.stringify(payload || {}));
|
|
957
1001
|
const sourceFile = typeof options.sourceFile === 'string' ? options.sourceFile : '';
|
|
958
1002
|
const preferredName = normalizeAuthProfileName(options.name || '');
|
|
@@ -1042,6 +1086,7 @@ function importAuthProfileFromUpload(payload = {}) {
|
|
|
1042
1086
|
}
|
|
1043
1087
|
|
|
1044
1088
|
function switchAuthProfile(name, options = {}) {
|
|
1089
|
+
ensureAuthProfileStoragePrepared();
|
|
1045
1090
|
const profileName = normalizeAuthProfileName(name);
|
|
1046
1091
|
if (!profileName) {
|
|
1047
1092
|
throw new Error('认证名称不能为空');
|
|
@@ -1087,6 +1132,7 @@ function switchAuthProfile(name, options = {}) {
|
|
|
1087
1132
|
}
|
|
1088
1133
|
|
|
1089
1134
|
function deleteAuthProfile(name) {
|
|
1135
|
+
ensureAuthProfileStoragePrepared();
|
|
1090
1136
|
const profileName = normalizeAuthProfileName(name);
|
|
1091
1137
|
if (!profileName) {
|
|
1092
1138
|
return { error: '认证名称不能为空' };
|
|
@@ -1135,6 +1181,7 @@ function deleteAuthProfile(name) {
|
|
|
1135
1181
|
}
|
|
1136
1182
|
|
|
1137
1183
|
function resolveAuthTokenFromCurrentProfile() {
|
|
1184
|
+
ensureAuthProfileStoragePrepared();
|
|
1138
1185
|
const registry = readAuthRegistry();
|
|
1139
1186
|
if (!registry.current) return '';
|
|
1140
1187
|
const profile = registry.items.find((item) => item.name === registry.current);
|
|
@@ -1390,8 +1437,40 @@ function normalizeCodexSkillName(name) {
|
|
|
1390
1437
|
return { name: value };
|
|
1391
1438
|
}
|
|
1392
1439
|
|
|
1393
|
-
function
|
|
1394
|
-
const
|
|
1440
|
+
function normalizeSkillTargetApp(app) {
|
|
1441
|
+
const value = typeof app === 'string' ? app.trim().toLowerCase() : '';
|
|
1442
|
+
return SKILL_TARGETS.some((item) => item.app === value) ? value : '';
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
function getSkillTargetByApp(app) {
|
|
1446
|
+
const normalizedApp = normalizeSkillTargetApp(app);
|
|
1447
|
+
if (!normalizedApp) return null;
|
|
1448
|
+
return SKILL_TARGETS.find((item) => item.app === normalizedApp) || null;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
function resolveSkillTarget(params = {}, defaultApp = 'codex') {
|
|
1452
|
+
const hasExplicitTargetApp = !!(params && typeof params === 'object'
|
|
1453
|
+
&& Object.prototype.hasOwnProperty.call(params, 'targetApp'));
|
|
1454
|
+
const hasExplicitTarget = !!(params && typeof params === 'object'
|
|
1455
|
+
&& Object.prototype.hasOwnProperty.call(params, 'target'));
|
|
1456
|
+
const hasAnyExplicitTarget = hasExplicitTargetApp || hasExplicitTarget;
|
|
1457
|
+
const rawTargetApp = hasExplicitTargetApp ? params.targetApp : '';
|
|
1458
|
+
const rawTarget = hasExplicitTarget ? params.target : '';
|
|
1459
|
+
const raw = rawTargetApp || rawTarget || '';
|
|
1460
|
+
if (hasAnyExplicitTarget && raw === '') {
|
|
1461
|
+
return null;
|
|
1462
|
+
}
|
|
1463
|
+
if (hasAnyExplicitTarget && !getSkillTargetByApp(raw)) {
|
|
1464
|
+
return null;
|
|
1465
|
+
}
|
|
1466
|
+
return getSkillTargetByApp(raw)
|
|
1467
|
+
|| getSkillTargetByApp(defaultApp)
|
|
1468
|
+
|| SKILL_TARGETS[0]
|
|
1469
|
+
|| null;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
function isSkillDirectoryEntryAtRoot(rootDir, entryName) {
|
|
1473
|
+
const targetPath = path.join(rootDir, entryName);
|
|
1395
1474
|
try {
|
|
1396
1475
|
const stat = fs.statSync(targetPath);
|
|
1397
1476
|
return stat.isDirectory();
|
|
@@ -1560,13 +1639,13 @@ function readCodexSkillMetadata(skillPath) {
|
|
|
1560
1639
|
}
|
|
1561
1640
|
}
|
|
1562
1641
|
|
|
1563
|
-
function
|
|
1564
|
-
const targetPath = path.join(
|
|
1642
|
+
function getSkillEntryInfoByName(rootDir, entryName) {
|
|
1643
|
+
const targetPath = path.join(rootDir, entryName);
|
|
1565
1644
|
const normalized = normalizeCodexSkillName(entryName);
|
|
1566
1645
|
if (normalized.error) {
|
|
1567
1646
|
return null;
|
|
1568
1647
|
}
|
|
1569
|
-
const relativePath = path.relative(
|
|
1648
|
+
const relativePath = path.relative(rootDir, targetPath);
|
|
1570
1649
|
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
|
1571
1650
|
return null;
|
|
1572
1651
|
}
|
|
@@ -1577,7 +1656,7 @@ function getCodexSkillEntryInfoByName(entryName) {
|
|
|
1577
1656
|
if (!lstat.isDirectory() && !isSymbolicLink) {
|
|
1578
1657
|
return null;
|
|
1579
1658
|
}
|
|
1580
|
-
if (isSymbolicLink && !
|
|
1659
|
+
if (isSymbolicLink && !isSkillDirectoryEntryAtRoot(rootDir, entryName)) {
|
|
1581
1660
|
return null;
|
|
1582
1661
|
}
|
|
1583
1662
|
const metadata = readCodexSkillMetadata(targetPath);
|
|
@@ -1595,26 +1674,34 @@ function getCodexSkillEntryInfoByName(entryName) {
|
|
|
1595
1674
|
}
|
|
1596
1675
|
}
|
|
1597
1676
|
|
|
1598
|
-
function
|
|
1599
|
-
|
|
1677
|
+
function listSkills(params = {}) {
|
|
1678
|
+
const target = resolveSkillTarget(params);
|
|
1679
|
+
if (!target) {
|
|
1680
|
+
return { error: '目标宿主不支持' };
|
|
1681
|
+
}
|
|
1682
|
+
if (!fs.existsSync(target.dir)) {
|
|
1600
1683
|
return {
|
|
1601
|
-
|
|
1684
|
+
targetApp: target.app,
|
|
1685
|
+
targetLabel: target.label,
|
|
1686
|
+
root: target.dir,
|
|
1602
1687
|
exists: false,
|
|
1603
1688
|
items: []
|
|
1604
1689
|
};
|
|
1605
1690
|
}
|
|
1606
1691
|
try {
|
|
1607
|
-
const entries = fs.readdirSync(
|
|
1692
|
+
const entries = fs.readdirSync(target.dir, { withFileTypes: true });
|
|
1608
1693
|
const items = entries
|
|
1609
1694
|
.map((entry) => {
|
|
1610
1695
|
const name = entry && entry.name ? entry.name : '';
|
|
1611
1696
|
if (!name || name.startsWith('.')) return null;
|
|
1612
|
-
return
|
|
1697
|
+
return getSkillEntryInfoByName(target.dir, name);
|
|
1613
1698
|
})
|
|
1614
1699
|
.filter(Boolean)
|
|
1615
1700
|
.sort((a, b) => a.displayName.localeCompare(b.displayName, 'zh-Hans-CN'));
|
|
1616
1701
|
return {
|
|
1617
|
-
|
|
1702
|
+
targetApp: target.app,
|
|
1703
|
+
targetLabel: target.label,
|
|
1704
|
+
root: target.dir,
|
|
1618
1705
|
exists: true,
|
|
1619
1706
|
items
|
|
1620
1707
|
};
|
|
@@ -1623,6 +1710,10 @@ function listCodexSkills() {
|
|
|
1623
1710
|
}
|
|
1624
1711
|
}
|
|
1625
1712
|
|
|
1713
|
+
function listCodexSkills() {
|
|
1714
|
+
return listSkills({ targetApp: 'codex' });
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1626
1717
|
function listSkillEntriesByRoot(rootDir) {
|
|
1627
1718
|
if (!rootDir || !fs.existsSync(rootDir)) {
|
|
1628
1719
|
return [];
|
|
@@ -1668,8 +1759,13 @@ function listSkillEntriesByRoot(rootDir) {
|
|
|
1668
1759
|
}
|
|
1669
1760
|
}
|
|
1670
1761
|
|
|
1671
|
-
function
|
|
1672
|
-
const
|
|
1762
|
+
function scanUnmanagedSkills(params = {}) {
|
|
1763
|
+
const target = resolveSkillTarget(params);
|
|
1764
|
+
if (!target) {
|
|
1765
|
+
return { error: '目标宿主不支持' };
|
|
1766
|
+
}
|
|
1767
|
+
const targetRoot = resolveCopyTargetRoot(target.dir);
|
|
1768
|
+
const existing = listSkills({ targetApp: target.app });
|
|
1673
1769
|
if (existing.error) {
|
|
1674
1770
|
return { error: existing.error };
|
|
1675
1771
|
}
|
|
@@ -1678,9 +1774,14 @@ function scanUnmanagedCodexSkills() {
|
|
|
1678
1774
|
.filter(Boolean));
|
|
1679
1775
|
|
|
1680
1776
|
const items = [];
|
|
1681
|
-
|
|
1777
|
+
const sources = SKILL_IMPORT_SOURCES.filter((source) => source.app !== target.app);
|
|
1778
|
+
for (const source of sources) {
|
|
1682
1779
|
const sourceEntries = listSkillEntriesByRoot(source.dir);
|
|
1683
1780
|
for (const entry of sourceEntries) {
|
|
1781
|
+
const targetCandidate = path.join(targetRoot, entry.name);
|
|
1782
|
+
if (fs.existsSync(targetCandidate)) {
|
|
1783
|
+
continue;
|
|
1784
|
+
}
|
|
1684
1785
|
if (existingNames.has(entry.name)) {
|
|
1685
1786
|
continue;
|
|
1686
1787
|
}
|
|
@@ -1706,9 +1807,11 @@ function scanUnmanagedCodexSkills() {
|
|
|
1706
1807
|
});
|
|
1707
1808
|
|
|
1708
1809
|
return {
|
|
1709
|
-
|
|
1810
|
+
targetApp: target.app,
|
|
1811
|
+
targetLabel: target.label,
|
|
1812
|
+
root: target.dir,
|
|
1710
1813
|
items,
|
|
1711
|
-
sources:
|
|
1814
|
+
sources: sources.map((source) => ({
|
|
1712
1815
|
app: source.app,
|
|
1713
1816
|
label: source.label,
|
|
1714
1817
|
path: source.dir,
|
|
@@ -1717,14 +1820,21 @@ function scanUnmanagedCodexSkills() {
|
|
|
1717
1820
|
};
|
|
1718
1821
|
}
|
|
1719
1822
|
|
|
1720
|
-
function
|
|
1823
|
+
function scanUnmanagedCodexSkills() {
|
|
1824
|
+
return scanUnmanagedSkills({ targetApp: 'codex' });
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
function importSkills(params = {}) {
|
|
1828
|
+
const target = resolveSkillTarget(params);
|
|
1829
|
+
if (!target) {
|
|
1830
|
+
return { error: '目标宿主不支持' };
|
|
1831
|
+
}
|
|
1832
|
+
const targetRoot = resolveCopyTargetRoot(target.dir);
|
|
1721
1833
|
const rawItems = Array.isArray(params.items) ? params.items : [];
|
|
1722
1834
|
if (!rawItems.length) {
|
|
1723
1835
|
return { error: '请先选择要导入的 skill' };
|
|
1724
1836
|
}
|
|
1725
1837
|
|
|
1726
|
-
ensureDir(CODEX_SKILLS_DIR);
|
|
1727
|
-
|
|
1728
1838
|
const imported = [];
|
|
1729
1839
|
const failed = [];
|
|
1730
1840
|
const dedup = new Set();
|
|
@@ -1749,6 +1859,14 @@ function importCodexSkills(params = {}) {
|
|
|
1749
1859
|
});
|
|
1750
1860
|
continue;
|
|
1751
1861
|
}
|
|
1862
|
+
if (source.app === target.app) {
|
|
1863
|
+
failed.push({
|
|
1864
|
+
name: normalizedName.name,
|
|
1865
|
+
sourceApp: source.app,
|
|
1866
|
+
error: '来源与目标相同,无需导入'
|
|
1867
|
+
});
|
|
1868
|
+
continue;
|
|
1869
|
+
}
|
|
1752
1870
|
const dedupKey = `${source.app}:${normalizedName.name}`;
|
|
1753
1871
|
if (dedup.has(dedupKey)) {
|
|
1754
1872
|
continue;
|
|
@@ -1774,8 +1892,8 @@ function importCodexSkills(params = {}) {
|
|
|
1774
1892
|
continue;
|
|
1775
1893
|
}
|
|
1776
1894
|
|
|
1777
|
-
const targetPath = path.join(
|
|
1778
|
-
const targetRelative = path.relative(
|
|
1895
|
+
const targetPath = path.join(targetRoot, normalizedName.name);
|
|
1896
|
+
const targetRelative = path.relative(targetRoot, targetPath);
|
|
1779
1897
|
if (targetRelative.startsWith('..') || path.isAbsolute(targetRelative)) {
|
|
1780
1898
|
failed.push({
|
|
1781
1899
|
name: normalizedName.name,
|
|
@@ -1788,7 +1906,7 @@ function importCodexSkills(params = {}) {
|
|
|
1788
1906
|
failed.push({
|
|
1789
1907
|
name: normalizedName.name,
|
|
1790
1908
|
sourceApp: source.app,
|
|
1791
|
-
error:
|
|
1909
|
+
error: `${target.label} 中已存在同名 skill`
|
|
1792
1910
|
});
|
|
1793
1911
|
continue;
|
|
1794
1912
|
}
|
|
@@ -1814,6 +1932,15 @@ function importCodexSkills(params = {}) {
|
|
|
1814
1932
|
});
|
|
1815
1933
|
continue;
|
|
1816
1934
|
}
|
|
1935
|
+
if (isPathInside(targetRoot, sourceDirForCopy)) {
|
|
1936
|
+
failed.push({
|
|
1937
|
+
name: normalizedName.name,
|
|
1938
|
+
sourceApp: source.app,
|
|
1939
|
+
error: '目标路径不能位于来源 skill 目录内'
|
|
1940
|
+
});
|
|
1941
|
+
continue;
|
|
1942
|
+
}
|
|
1943
|
+
ensureDir(targetRoot);
|
|
1817
1944
|
const visitedRealPaths = new Set([sourceDirForCopy]);
|
|
1818
1945
|
copyDirRecursive(sourceDirForCopy, targetPath, {
|
|
1819
1946
|
dereferenceSymlinks: true,
|
|
@@ -1825,6 +1952,8 @@ function importCodexSkills(params = {}) {
|
|
|
1825
1952
|
name: normalizedName.name,
|
|
1826
1953
|
sourceApp: source.app,
|
|
1827
1954
|
sourceLabel: source.label,
|
|
1955
|
+
targetApp: target.app,
|
|
1956
|
+
targetLabel: target.label,
|
|
1828
1957
|
path: targetPath
|
|
1829
1958
|
});
|
|
1830
1959
|
} catch (e) {
|
|
@@ -1845,10 +1974,16 @@ function importCodexSkills(params = {}) {
|
|
|
1845
1974
|
success: failed.length === 0,
|
|
1846
1975
|
imported,
|
|
1847
1976
|
failed,
|
|
1848
|
-
|
|
1977
|
+
targetApp: target.app,
|
|
1978
|
+
targetLabel: target.label,
|
|
1979
|
+
root: targetRoot
|
|
1849
1980
|
};
|
|
1850
1981
|
}
|
|
1851
1982
|
|
|
1983
|
+
function importCodexSkills(params = {}) {
|
|
1984
|
+
return importSkills({ ...(params || {}), targetApp: 'codex' });
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1852
1987
|
function collectSkillDirectoriesFromRoot(rootDir, limit = MAX_SKILLS_ZIP_ENTRY_COUNT) {
|
|
1853
1988
|
const results = [];
|
|
1854
1989
|
let truncated = false;
|
|
@@ -1902,15 +2037,22 @@ function resolveSkillNameFromImportedDirectory(skillDir, extractionRoot, fallbac
|
|
|
1902
2037
|
return normalizeCodexSkillName(candidate);
|
|
1903
2038
|
}
|
|
1904
2039
|
|
|
1905
|
-
async function
|
|
2040
|
+
async function importSkillsFromZipFile(zipPath, options = {}) {
|
|
1906
2041
|
const fallbackName = typeof options.fallbackName === 'string' ? options.fallbackName : '';
|
|
1907
2042
|
const tempDir = typeof options.tempDir === 'string' ? options.tempDir : '';
|
|
1908
2043
|
const imported = [];
|
|
1909
2044
|
const failed = [];
|
|
1910
2045
|
const dedupNames = new Set();
|
|
1911
2046
|
const extractionRoot = path.join(tempDir || path.dirname(zipPath), 'extract');
|
|
2047
|
+
let target = null;
|
|
2048
|
+
let targetRoot = '';
|
|
1912
2049
|
|
|
1913
2050
|
try {
|
|
2051
|
+
target = resolveSkillTarget(options, 'codex');
|
|
2052
|
+
if (!target) {
|
|
2053
|
+
return { error: '目标宿主不支持' };
|
|
2054
|
+
}
|
|
2055
|
+
targetRoot = resolveCopyTargetRoot(target.dir);
|
|
1914
2056
|
await inspectZipArchiveLimits(zipPath, {
|
|
1915
2057
|
maxEntryCount: MAX_SKILLS_ZIP_ENTRY_COUNT,
|
|
1916
2058
|
maxUncompressedBytes: MAX_SKILLS_ZIP_UNCOMPRESSED_BYTES
|
|
@@ -1926,7 +2068,6 @@ async function importCodexSkillsFromZipFile(zipPath, options = {}) {
|
|
|
1926
2068
|
return { error: '压缩包中的技能目录数量超出导入上限' };
|
|
1927
2069
|
}
|
|
1928
2070
|
|
|
1929
|
-
ensureDir(CODEX_SKILLS_DIR);
|
|
1930
2071
|
for (const skillDir of discoveredDirs) {
|
|
1931
2072
|
const normalizedName = resolveSkillNameFromImportedDirectory(skillDir, extractionRoot, fallbackName);
|
|
1932
2073
|
if (normalizedName.error) {
|
|
@@ -1942,8 +2083,8 @@ async function importCodexSkillsFromZipFile(zipPath, options = {}) {
|
|
|
1942
2083
|
}
|
|
1943
2084
|
dedupNames.add(dedupKey);
|
|
1944
2085
|
|
|
1945
|
-
const targetPath = path.join(
|
|
1946
|
-
const targetRelative = path.relative(
|
|
2086
|
+
const targetPath = path.join(targetRoot, normalizedName.name);
|
|
2087
|
+
const targetRelative = path.relative(targetRoot, targetPath);
|
|
1947
2088
|
if (targetRelative.startsWith('..') || path.isAbsolute(targetRelative)) {
|
|
1948
2089
|
failed.push({
|
|
1949
2090
|
name: normalizedName.name,
|
|
@@ -1954,7 +2095,7 @@ async function importCodexSkillsFromZipFile(zipPath, options = {}) {
|
|
|
1954
2095
|
if (fs.existsSync(targetPath)) {
|
|
1955
2096
|
failed.push({
|
|
1956
2097
|
name: normalizedName.name,
|
|
1957
|
-
error:
|
|
2098
|
+
error: `${target.label} 中已存在同名 skill`
|
|
1958
2099
|
});
|
|
1959
2100
|
continue;
|
|
1960
2101
|
}
|
|
@@ -1970,6 +2111,14 @@ async function importCodexSkillsFromZipFile(zipPath, options = {}) {
|
|
|
1970
2111
|
});
|
|
1971
2112
|
continue;
|
|
1972
2113
|
}
|
|
2114
|
+
if (isPathInside(targetRoot, sourceRealPath)) {
|
|
2115
|
+
failed.push({
|
|
2116
|
+
name: normalizedName.name,
|
|
2117
|
+
error: '目标路径不能位于来源 skill 目录内'
|
|
2118
|
+
});
|
|
2119
|
+
continue;
|
|
2120
|
+
}
|
|
2121
|
+
ensureDir(targetRoot);
|
|
1973
2122
|
const visitedRealPaths = new Set([sourceRealPath]);
|
|
1974
2123
|
copyDirRecursive(sourceRealPath, targetPath, {
|
|
1975
2124
|
dereferenceSymlinks: true,
|
|
@@ -1979,6 +2128,8 @@ async function importCodexSkillsFromZipFile(zipPath, options = {}) {
|
|
|
1979
2128
|
copiedToTarget = true;
|
|
1980
2129
|
imported.push({
|
|
1981
2130
|
name: normalizedName.name,
|
|
2131
|
+
targetApp: target.app,
|
|
2132
|
+
targetLabel: target.label,
|
|
1982
2133
|
path: targetPath
|
|
1983
2134
|
});
|
|
1984
2135
|
} catch (e) {
|
|
@@ -1999,7 +2150,9 @@ async function importCodexSkillsFromZipFile(zipPath, options = {}) {
|
|
|
1999
2150
|
error: failed[0].error || '导入失败',
|
|
2000
2151
|
imported,
|
|
2001
2152
|
failed,
|
|
2002
|
-
|
|
2153
|
+
targetApp: target.app,
|
|
2154
|
+
targetLabel: target.label,
|
|
2155
|
+
root: targetRoot
|
|
2003
2156
|
};
|
|
2004
2157
|
}
|
|
2005
2158
|
|
|
@@ -2007,7 +2160,9 @@ async function importCodexSkillsFromZipFile(zipPath, options = {}) {
|
|
|
2007
2160
|
success: failed.length === 0,
|
|
2008
2161
|
imported,
|
|
2009
2162
|
failed,
|
|
2010
|
-
|
|
2163
|
+
targetApp: target.app,
|
|
2164
|
+
targetLabel: target.label,
|
|
2165
|
+
root: targetRoot
|
|
2011
2166
|
};
|
|
2012
2167
|
} catch (e) {
|
|
2013
2168
|
return {
|
|
@@ -2026,21 +2181,40 @@ async function importCodexSkillsFromZipFile(zipPath, options = {}) {
|
|
|
2026
2181
|
}
|
|
2027
2182
|
}
|
|
2028
2183
|
|
|
2029
|
-
async function
|
|
2184
|
+
async function importCodexSkillsFromZipFile(zipPath, options = {}) {
|
|
2185
|
+
return importSkillsFromZipFile(zipPath, { ...(options || {}), targetApp: 'codex' });
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
async function importSkillsFromZip(payload = {}) {
|
|
2030
2189
|
if (!payload || typeof payload.fileBase64 !== 'string' || !payload.fileBase64.trim()) {
|
|
2031
2190
|
return { error: '缺少技能压缩包内容' };
|
|
2032
2191
|
}
|
|
2033
|
-
const
|
|
2192
|
+
const fallbackTarget = resolveSkillTarget(payload, 'codex');
|
|
2193
|
+
const fallbackTargetApp = fallbackTarget ? fallbackTarget.app : 'codex';
|
|
2194
|
+
const fallbackName = payload.fileName || `${fallbackTargetApp}-skills.zip`;
|
|
2195
|
+
const upload = writeUploadZip(payload.fileBase64, 'codex-skills-import', fallbackName);
|
|
2034
2196
|
if (upload.error) {
|
|
2035
2197
|
return { error: upload.error };
|
|
2036
2198
|
}
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
}
|
|
2199
|
+
const importOptions = { tempDir: upload.tempDir, fallbackName };
|
|
2200
|
+
if (Object.prototype.hasOwnProperty.call(payload, 'targetApp')) {
|
|
2201
|
+
importOptions.targetApp = payload.targetApp;
|
|
2202
|
+
}
|
|
2203
|
+
if (Object.prototype.hasOwnProperty.call(payload, 'target')) {
|
|
2204
|
+
importOptions.target = payload.target;
|
|
2205
|
+
}
|
|
2206
|
+
return importSkillsFromZipFile(upload.zipPath, importOptions);
|
|
2041
2207
|
}
|
|
2042
2208
|
|
|
2043
|
-
async function
|
|
2209
|
+
async function importCodexSkillsFromZip(payload = {}) {
|
|
2210
|
+
return importSkillsFromZip({ ...(payload || {}), targetApp: 'codex' });
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
async function exportSkills(params = {}) {
|
|
2214
|
+
const target = resolveSkillTarget(params);
|
|
2215
|
+
if (!target) {
|
|
2216
|
+
return { error: '目标宿主不支持' };
|
|
2217
|
+
}
|
|
2044
2218
|
const rawNames = Array.isArray(params.names) ? params.names : [];
|
|
2045
2219
|
const uniqueNames = Array.from(new Set(rawNames
|
|
2046
2220
|
.map((item) => (typeof item === 'string' ? item.trim() : ''))
|
|
@@ -2062,8 +2236,8 @@ async function exportCodexSkills(params = {}) {
|
|
|
2062
2236
|
failed.push({ name: rawName, error: normalizedName.error });
|
|
2063
2237
|
continue;
|
|
2064
2238
|
}
|
|
2065
|
-
const sourcePath = path.join(
|
|
2066
|
-
const sourceRelative = path.relative(
|
|
2239
|
+
const sourcePath = path.join(target.dir, normalizedName.name);
|
|
2240
|
+
const sourceRelative = path.relative(target.dir, sourcePath);
|
|
2067
2241
|
if (sourceRelative.startsWith('..') || path.isAbsolute(sourceRelative)) {
|
|
2068
2242
|
failed.push({ name: normalizedName.name, error: '来源路径非法' });
|
|
2069
2243
|
continue;
|
|
@@ -2109,12 +2283,14 @@ async function exportCodexSkills(params = {}) {
|
|
|
2109
2283
|
error: failed[0] && failed[0].error ? failed[0].error : '无可导出的 skill',
|
|
2110
2284
|
exported,
|
|
2111
2285
|
failed,
|
|
2112
|
-
|
|
2286
|
+
targetApp: target.app,
|
|
2287
|
+
targetLabel: target.label,
|
|
2288
|
+
root: target.dir
|
|
2113
2289
|
};
|
|
2114
2290
|
}
|
|
2115
2291
|
|
|
2116
2292
|
const randomToken = crypto.randomBytes(12).toString('hex');
|
|
2117
|
-
const zipFileName =
|
|
2293
|
+
const zipFileName = `${target.app}-skills-${randomToken}.zip`;
|
|
2118
2294
|
const zipFilePath = path.join(os.tmpdir(), zipFileName);
|
|
2119
2295
|
if (fs.existsSync(zipFilePath)) {
|
|
2120
2296
|
try {
|
|
@@ -2133,14 +2309,18 @@ async function exportCodexSkills(params = {}) {
|
|
|
2133
2309
|
downloadPath: artifact.downloadPath,
|
|
2134
2310
|
exported,
|
|
2135
2311
|
failed,
|
|
2136
|
-
|
|
2312
|
+
targetApp: target.app,
|
|
2313
|
+
targetLabel: target.label,
|
|
2314
|
+
root: target.dir
|
|
2137
2315
|
};
|
|
2138
2316
|
} catch (e) {
|
|
2139
2317
|
return {
|
|
2140
2318
|
error: `导出失败:${e && e.message ? e.message : '未知错误'}`,
|
|
2141
2319
|
exported,
|
|
2142
2320
|
failed,
|
|
2143
|
-
|
|
2321
|
+
targetApp: target.app,
|
|
2322
|
+
targetLabel: target.label,
|
|
2323
|
+
root: target.dir
|
|
2144
2324
|
};
|
|
2145
2325
|
} finally {
|
|
2146
2326
|
try {
|
|
@@ -2149,6 +2329,10 @@ async function exportCodexSkills(params = {}) {
|
|
|
2149
2329
|
}
|
|
2150
2330
|
}
|
|
2151
2331
|
|
|
2332
|
+
async function exportCodexSkills(params = {}) {
|
|
2333
|
+
return exportSkills({ ...(params || {}), targetApp: 'codex' });
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2152
2336
|
function removeDirectoryRecursive(targetPath) {
|
|
2153
2337
|
if (typeof fs.rmSync === 'function') {
|
|
2154
2338
|
fs.rmSync(targetPath, { recursive: true, force: false });
|
|
@@ -2157,7 +2341,11 @@ function removeDirectoryRecursive(targetPath) {
|
|
|
2157
2341
|
fs.rmdirSync(targetPath, { recursive: true });
|
|
2158
2342
|
}
|
|
2159
2343
|
|
|
2160
|
-
function
|
|
2344
|
+
function deleteSkills(params = {}) {
|
|
2345
|
+
const target = resolveSkillTarget(params);
|
|
2346
|
+
if (!target) {
|
|
2347
|
+
return { error: '目标宿主不支持' };
|
|
2348
|
+
}
|
|
2161
2349
|
const rawList = Array.isArray(params.names) ? params.names : [];
|
|
2162
2350
|
const uniqueNames = Array.from(new Set(rawList
|
|
2163
2351
|
.map((item) => (typeof item === 'string' ? item.trim() : ''))
|
|
@@ -2175,8 +2363,8 @@ function deleteCodexSkills(params = {}) {
|
|
|
2175
2363
|
continue;
|
|
2176
2364
|
}
|
|
2177
2365
|
|
|
2178
|
-
const skillPath = path.join(
|
|
2179
|
-
const relativePath = path.relative(
|
|
2366
|
+
const skillPath = path.join(target.dir, normalized.name);
|
|
2367
|
+
const relativePath = path.relative(target.dir, skillPath);
|
|
2180
2368
|
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
|
2181
2369
|
failed.push({ name: normalized.name, error: '技能路径非法' });
|
|
2182
2370
|
continue;
|
|
@@ -2206,10 +2394,16 @@ function deleteCodexSkills(params = {}) {
|
|
|
2206
2394
|
success: failed.length === 0,
|
|
2207
2395
|
deleted,
|
|
2208
2396
|
failed,
|
|
2209
|
-
|
|
2397
|
+
targetApp: target.app,
|
|
2398
|
+
targetLabel: target.label,
|
|
2399
|
+
root: target.dir
|
|
2210
2400
|
};
|
|
2211
2401
|
}
|
|
2212
2402
|
|
|
2403
|
+
function deleteCodexSkills(params = {}) {
|
|
2404
|
+
return deleteSkills({ ...(params || {}), targetApp: 'codex' });
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2213
2407
|
function readAgentsFile(params = {}) {
|
|
2214
2408
|
const filePath = resolveAgentsFilePath(params);
|
|
2215
2409
|
const dirCheck = validateAgentsBaseDir(filePath);
|
|
@@ -2991,11 +3185,40 @@ function buildVirtualDefaultConfig() {
|
|
|
2991
3185
|
return toml.parse(EMPTY_CONFIG_FALLBACK_TEMPLATE);
|
|
2992
3186
|
}
|
|
2993
3187
|
|
|
3188
|
+
function sanitizeRemovedBuiltinProxyProvider(config) {
|
|
3189
|
+
const safeConfig = isPlainObject(config) ? config : {};
|
|
3190
|
+
const providers = isPlainObject(safeConfig.model_providers) ? safeConfig.model_providers : null;
|
|
3191
|
+
const currentProvider = typeof safeConfig.model_provider === 'string' ? safeConfig.model_provider.trim() : '';
|
|
3192
|
+
const hasRemovedBuiltin = !!(providers && providers[BUILTIN_PROXY_PROVIDER_NAME]);
|
|
3193
|
+
const currentIsRemovedBuiltin = currentProvider === BUILTIN_PROXY_PROVIDER_NAME;
|
|
3194
|
+
|
|
3195
|
+
if (!hasRemovedBuiltin && !currentIsRemovedBuiltin) {
|
|
3196
|
+
return safeConfig;
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
const nextProviders = providers ? { ...providers } : {};
|
|
3200
|
+
delete nextProviders[BUILTIN_PROXY_PROVIDER_NAME];
|
|
3201
|
+
const providerNames = Object.keys(nextProviders);
|
|
3202
|
+
const fallbackProvider = providerNames[0] || '';
|
|
3203
|
+
const currentModels = readCurrentModels();
|
|
3204
|
+
const fallbackModel = fallbackProvider
|
|
3205
|
+
? (currentModels[fallbackProvider] || (typeof safeConfig.model === 'string' ? safeConfig.model : ''))
|
|
3206
|
+
: '';
|
|
3207
|
+
|
|
3208
|
+
return {
|
|
3209
|
+
...safeConfig,
|
|
3210
|
+
model_providers: nextProviders,
|
|
3211
|
+
model_provider: currentIsRemovedBuiltin ? fallbackProvider : safeConfig.model_provider,
|
|
3212
|
+
model: currentIsRemovedBuiltin ? fallbackModel : safeConfig.model
|
|
3213
|
+
};
|
|
3214
|
+
}
|
|
3215
|
+
|
|
2994
3216
|
function readConfigOrVirtualDefault() {
|
|
2995
3217
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
2996
3218
|
try {
|
|
3219
|
+
removePersistedBuiltinProxyProviderFromConfig();
|
|
2997
3220
|
return {
|
|
2998
|
-
config: readConfig(),
|
|
3221
|
+
config: sanitizeRemovedBuiltinProxyProvider(readConfig()),
|
|
2999
3222
|
isVirtual: false,
|
|
3000
3223
|
reason: '',
|
|
3001
3224
|
detail: '',
|
|
@@ -3012,7 +3235,9 @@ function readConfigOrVirtualDefault() {
|
|
|
3012
3235
|
? e.configDetail.trim()
|
|
3013
3236
|
: (e && e.message ? e.message : publicReason);
|
|
3014
3237
|
return {
|
|
3015
|
-
config: errorType === 'missing'
|
|
3238
|
+
config: errorType === 'missing'
|
|
3239
|
+
? sanitizeRemovedBuiltinProxyProvider(buildVirtualDefaultConfig())
|
|
3240
|
+
: {},
|
|
3016
3241
|
isVirtual: true,
|
|
3017
3242
|
reason: publicReason,
|
|
3018
3243
|
detail,
|
|
@@ -3022,7 +3247,7 @@ function readConfigOrVirtualDefault() {
|
|
|
3022
3247
|
}
|
|
3023
3248
|
|
|
3024
3249
|
return {
|
|
3025
|
-
config: buildVirtualDefaultConfig(),
|
|
3250
|
+
config: sanitizeRemovedBuiltinProxyProvider(buildVirtualDefaultConfig()),
|
|
3026
3251
|
isVirtual: true,
|
|
3027
3252
|
reason: '未检测到 config.toml',
|
|
3028
3253
|
detail: `配置文件不存在: ${CONFIG_FILE}`,
|
|
@@ -3118,8 +3343,8 @@ function getConfigTemplate(params = {}) {
|
|
|
3118
3343
|
}
|
|
3119
3344
|
} catch (e) {}
|
|
3120
3345
|
}
|
|
3121
|
-
const selectedProvider = params.provider
|
|
3122
|
-
const selectedModel = params.model
|
|
3346
|
+
const selectedProvider = typeof params.provider === 'string' ? params.provider.trim() : '';
|
|
3347
|
+
const selectedModel = typeof params.model === 'string' ? params.model.trim() : '';
|
|
3123
3348
|
let template = normalizeTopLevelConfigWithTemplate(content, selectedProvider, selectedModel);
|
|
3124
3349
|
if (typeof params.serviceTier === 'string') {
|
|
3125
3350
|
template = applyServiceTierToTemplate(template, params.serviceTier);
|
|
@@ -3196,7 +3421,7 @@ function addProviderToConfig(params = {}) {
|
|
|
3196
3421
|
return { error: 'local provider 为系统保留名称,不可新增' };
|
|
3197
3422
|
}
|
|
3198
3423
|
if (isBuiltinProxyProvider(name) && !allowManaged) {
|
|
3199
|
-
return { error: '
|
|
3424
|
+
return { error: 'codexmate-proxy 为保留名称,不可手动添加' };
|
|
3200
3425
|
}
|
|
3201
3426
|
|
|
3202
3427
|
ensureConfigDir();
|
|
@@ -3274,7 +3499,7 @@ function updateProviderInConfig(params = {}) {
|
|
|
3274
3499
|
if (isDefaultLocalProvider(name)) {
|
|
3275
3500
|
return { error: 'local provider 为系统保留项,不可编辑' };
|
|
3276
3501
|
}
|
|
3277
|
-
return { error: '
|
|
3502
|
+
return { error: 'codexmate-proxy 为保留名称,不可编辑' };
|
|
3278
3503
|
}
|
|
3279
3504
|
|
|
3280
3505
|
try {
|
|
@@ -3292,7 +3517,7 @@ function deleteProviderFromConfig(params = {}) {
|
|
|
3292
3517
|
if (isDefaultLocalProvider(name)) {
|
|
3293
3518
|
return { error: 'local provider 为系统保留项,不可删除' };
|
|
3294
3519
|
}
|
|
3295
|
-
return { error: '
|
|
3520
|
+
return { error: 'codexmate-proxy 为保留名称,不可删除' };
|
|
3296
3521
|
}
|
|
3297
3522
|
if (!fs.existsSync(CONFIG_FILE)) {
|
|
3298
3523
|
return { error: 'config.toml 不存在' };
|
|
@@ -3322,7 +3547,7 @@ function performProviderDeletion(name, options = {}) {
|
|
|
3322
3547
|
if (isNonDeletableProvider(name)) {
|
|
3323
3548
|
const msg = isDefaultLocalProvider(name)
|
|
3324
3549
|
? 'local provider 为系统保留项,不可删除'
|
|
3325
|
-
: '
|
|
3550
|
+
: 'codexmate-proxy 为保留名称,不可删除';
|
|
3326
3551
|
if (!silent) console.error('错误:', msg);
|
|
3327
3552
|
return { error: msg };
|
|
3328
3553
|
}
|
|
@@ -3625,6 +3850,27 @@ function isPathInside(targetPath, rootPath) {
|
|
|
3625
3850
|
return resolvedTarget.startsWith(rootWithSlash);
|
|
3626
3851
|
}
|
|
3627
3852
|
|
|
3853
|
+
function resolveCopyTargetRoot(targetDir) {
|
|
3854
|
+
const suffixSegments = [];
|
|
3855
|
+
let current = path.resolve(targetDir || '');
|
|
3856
|
+
while (current && !fs.existsSync(current)) {
|
|
3857
|
+
const parent = path.dirname(current);
|
|
3858
|
+
if (!parent || parent === current) {
|
|
3859
|
+
break;
|
|
3860
|
+
}
|
|
3861
|
+
suffixSegments.unshift(path.basename(current));
|
|
3862
|
+
current = parent;
|
|
3863
|
+
}
|
|
3864
|
+
let resolvedRoot = normalizePathForCompare(current || targetDir);
|
|
3865
|
+
if (!resolvedRoot) {
|
|
3866
|
+
resolvedRoot = path.resolve(targetDir || '');
|
|
3867
|
+
}
|
|
3868
|
+
for (const segment of suffixSegments) {
|
|
3869
|
+
resolvedRoot = path.join(resolvedRoot, segment);
|
|
3870
|
+
}
|
|
3871
|
+
return resolvedRoot;
|
|
3872
|
+
}
|
|
3873
|
+
|
|
3628
3874
|
function collectJsonlFiles(rootDir, maxFiles = 5000) {
|
|
3629
3875
|
if (!fs.existsSync(rootDir)) {
|
|
3630
3876
|
return [];
|
|
@@ -5306,6 +5552,124 @@ function buildProxyListenUrl(settings) {
|
|
|
5306
5552
|
return `http://${host}:${settings.port}`;
|
|
5307
5553
|
}
|
|
5308
5554
|
|
|
5555
|
+
function buildBuiltinProxyProviderBaseUrl(settings) {
|
|
5556
|
+
return `${buildProxyListenUrl(settings).replace(/\/+$/, '')}/v1`;
|
|
5557
|
+
}
|
|
5558
|
+
|
|
5559
|
+
function buildBuiltinProxyProviderConfig(settings) {
|
|
5560
|
+
return {
|
|
5561
|
+
name: BUILTIN_PROXY_PROVIDER_NAME,
|
|
5562
|
+
base_url: buildBuiltinProxyProviderBaseUrl(settings),
|
|
5563
|
+
wire_api: 'responses',
|
|
5564
|
+
requires_openai_auth: false,
|
|
5565
|
+
preferred_auth_method: '',
|
|
5566
|
+
request_max_retries: 4,
|
|
5567
|
+
stream_max_retries: 10,
|
|
5568
|
+
stream_idle_timeout_ms: 300000
|
|
5569
|
+
};
|
|
5570
|
+
}
|
|
5571
|
+
|
|
5572
|
+
function injectBuiltinProxyProvider(config) {
|
|
5573
|
+
return isPlainObject(config) ? config : {};
|
|
5574
|
+
}
|
|
5575
|
+
|
|
5576
|
+
function removePersistedBuiltinProxyProviderFromConfig() {
|
|
5577
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
5578
|
+
return { success: true, removed: false };
|
|
5579
|
+
}
|
|
5580
|
+
|
|
5581
|
+
let config;
|
|
5582
|
+
try {
|
|
5583
|
+
config = readConfig();
|
|
5584
|
+
} catch (e) {
|
|
5585
|
+
return { error: e.message || '读取 config.toml 失败' };
|
|
5586
|
+
}
|
|
5587
|
+
|
|
5588
|
+
if (!config.model_providers || !config.model_providers[BUILTIN_PROXY_PROVIDER_NAME]) {
|
|
5589
|
+
return { success: true, removed: false };
|
|
5590
|
+
}
|
|
5591
|
+
|
|
5592
|
+
const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
5593
|
+
const lineEnding = content.includes('\r\n') ? '\r\n' : '\n';
|
|
5594
|
+
const hasBom = content.charCodeAt(0) === 0xFEFF;
|
|
5595
|
+
const providerConfig = config.model_providers[BUILTIN_PROXY_PROVIDER_NAME];
|
|
5596
|
+
const providerSegments = providerConfig && Array.isArray(providerConfig.__codexmate_legacy_segments)
|
|
5597
|
+
? providerConfig.__codexmate_legacy_segments
|
|
5598
|
+
: null;
|
|
5599
|
+
const providerSegmentVariants = (() => {
|
|
5600
|
+
const variants = [];
|
|
5601
|
+
const seen = new Set();
|
|
5602
|
+
const pushVariant = (segments) => {
|
|
5603
|
+
const normalized = normalizeLegacySegments(segments);
|
|
5604
|
+
const key = buildLegacySegmentsKey(normalized);
|
|
5605
|
+
if (!key || seen.has(key)) return;
|
|
5606
|
+
seen.add(key);
|
|
5607
|
+
variants.push(normalized);
|
|
5608
|
+
};
|
|
5609
|
+
if (providerConfig && Array.isArray(providerConfig.__codexmate_legacy_segments)) {
|
|
5610
|
+
pushVariant(providerConfig.__codexmate_legacy_segments);
|
|
5611
|
+
}
|
|
5612
|
+
if (providerConfig && Array.isArray(providerConfig.__codexmate_legacy_segment_variants)) {
|
|
5613
|
+
for (const segments of providerConfig.__codexmate_legacy_segment_variants) {
|
|
5614
|
+
pushVariant(segments);
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
5617
|
+
if (providerSegments) {
|
|
5618
|
+
pushVariant(providerSegments);
|
|
5619
|
+
}
|
|
5620
|
+
if (variants.length === 0) {
|
|
5621
|
+
pushVariant(String(BUILTIN_PROXY_PROVIDER_NAME || '').split('.').filter((item) => item));
|
|
5622
|
+
}
|
|
5623
|
+
return variants;
|
|
5624
|
+
})();
|
|
5625
|
+
|
|
5626
|
+
let updatedContent = null;
|
|
5627
|
+
const combinedRanges = [];
|
|
5628
|
+
for (const segments of providerSegmentVariants) {
|
|
5629
|
+
combinedRanges.push(...findProviderSectionRanges(content, BUILTIN_PROXY_PROVIDER_NAME, segments));
|
|
5630
|
+
combinedRanges.push(...findProviderDescendantSectionRanges(content, segments));
|
|
5631
|
+
}
|
|
5632
|
+
if (combinedRanges.length === 0) {
|
|
5633
|
+
combinedRanges.push(...findProviderSectionRanges(content, BUILTIN_PROXY_PROVIDER_NAME, providerSegments));
|
|
5634
|
+
}
|
|
5635
|
+
|
|
5636
|
+
if (combinedRanges.length > 0) {
|
|
5637
|
+
const sorted = combinedRanges.sort((a, b) => b.start - a.start || b.end - a.end);
|
|
5638
|
+
const seen = new Set();
|
|
5639
|
+
let removedContent = content;
|
|
5640
|
+
for (const range of sorted) {
|
|
5641
|
+
const rangeKey = `${range.start}:${range.end}`;
|
|
5642
|
+
if (seen.has(rangeKey)) continue;
|
|
5643
|
+
seen.add(rangeKey);
|
|
5644
|
+
removedContent = removedContent.slice(0, range.start) + removedContent.slice(range.end);
|
|
5645
|
+
}
|
|
5646
|
+
updatedContent = removedContent.replace(/\n{3,}/g, lineEnding + lineEnding);
|
|
5647
|
+
}
|
|
5648
|
+
|
|
5649
|
+
if (!updatedContent) {
|
|
5650
|
+
const rebuilt = JSON.parse(JSON.stringify(config));
|
|
5651
|
+
delete rebuilt.model_providers[BUILTIN_PROXY_PROVIDER_NAME];
|
|
5652
|
+
const hasMarker = content.includes(CODEXMATE_MANAGED_MARKER);
|
|
5653
|
+
let rebuiltToml = toml.stringify(rebuilt).trimEnd();
|
|
5654
|
+
rebuiltToml = rebuiltToml.replace(/\n/g, lineEnding);
|
|
5655
|
+
if (hasMarker && !rebuiltToml.includes(CODEXMATE_MANAGED_MARKER)) {
|
|
5656
|
+
rebuiltToml = `${CODEXMATE_MANAGED_MARKER}${lineEnding}${rebuiltToml}`;
|
|
5657
|
+
}
|
|
5658
|
+
updatedContent = rebuiltToml + lineEnding;
|
|
5659
|
+
if (hasBom && updatedContent.charCodeAt(0) !== 0xFEFF) {
|
|
5660
|
+
updatedContent = '\uFEFF' + updatedContent;
|
|
5661
|
+
}
|
|
5662
|
+
}
|
|
5663
|
+
|
|
5664
|
+
try {
|
|
5665
|
+
writeConfig(updatedContent.trimEnd() + lineEnding);
|
|
5666
|
+
} catch (e) {
|
|
5667
|
+
return { error: e.message || '写入 config.toml 失败' };
|
|
5668
|
+
}
|
|
5669
|
+
|
|
5670
|
+
return { success: true, removed: true };
|
|
5671
|
+
}
|
|
5672
|
+
|
|
5309
5673
|
function hasCodexConfigReadyForProxy() {
|
|
5310
5674
|
const result = readConfigOrVirtualDefault();
|
|
5311
5675
|
if (!result || result.isVirtual) {
|
|
@@ -5582,131 +5946,11 @@ function getBuiltinProxyStatus() {
|
|
|
5582
5946
|
}
|
|
5583
5947
|
|
|
5584
5948
|
function applyBuiltinProxyProvider(params = {}) {
|
|
5585
|
-
|
|
5586
|
-
const hostForUrl = formatHostForUrl(settings.host);
|
|
5587
|
-
const baseUrl = `http://${hostForUrl}:${settings.port}`;
|
|
5588
|
-
|
|
5589
|
-
const { config } = readConfigOrVirtualDefault();
|
|
5590
|
-
const providers = config && isPlainObject(config.model_providers) ? config.model_providers : {};
|
|
5591
|
-
const exists = !!providers[BUILTIN_PROXY_PROVIDER_NAME];
|
|
5592
|
-
const saveResult = exists
|
|
5593
|
-
? updateProviderInConfig({
|
|
5594
|
-
name: BUILTIN_PROXY_PROVIDER_NAME,
|
|
5595
|
-
url: baseUrl,
|
|
5596
|
-
key: '',
|
|
5597
|
-
allowManaged: true
|
|
5598
|
-
})
|
|
5599
|
-
: addProviderToConfig({
|
|
5600
|
-
name: BUILTIN_PROXY_PROVIDER_NAME,
|
|
5601
|
-
url: baseUrl,
|
|
5602
|
-
key: '',
|
|
5603
|
-
allowManaged: true
|
|
5604
|
-
});
|
|
5605
|
-
|
|
5606
|
-
if (saveResult && saveResult.error) {
|
|
5607
|
-
return saveResult;
|
|
5608
|
-
}
|
|
5609
|
-
|
|
5610
|
-
const switchToProxy = params.switchToProxy !== false;
|
|
5611
|
-
let targetModel = '';
|
|
5612
|
-
if (switchToProxy) {
|
|
5613
|
-
try {
|
|
5614
|
-
targetModel = cmdSwitch(BUILTIN_PROXY_PROVIDER_NAME, true) || '';
|
|
5615
|
-
} catch (e) {
|
|
5616
|
-
return { error: `写入代理 provider 成功,但切换失败: ${e.message}` };
|
|
5617
|
-
}
|
|
5618
|
-
}
|
|
5619
|
-
|
|
5620
|
-
return {
|
|
5621
|
-
success: true,
|
|
5622
|
-
provider: BUILTIN_PROXY_PROVIDER_NAME,
|
|
5623
|
-
baseUrl,
|
|
5624
|
-
switched: switchToProxy,
|
|
5625
|
-
model: targetModel
|
|
5626
|
-
};
|
|
5949
|
+
return { error: '该功能已移除' };
|
|
5627
5950
|
}
|
|
5628
5951
|
|
|
5629
5952
|
async function ensureBuiltinProxyForCodexDefault(params = {}) {
|
|
5630
|
-
|
|
5631
|
-
const switchToProxy = payload.switchToProxy !== false;
|
|
5632
|
-
delete payload.switchToProxy;
|
|
5633
|
-
payload.enabled = true;
|
|
5634
|
-
|
|
5635
|
-
const saveResult = saveBuiltinProxySettings(payload);
|
|
5636
|
-
if (saveResult.error) {
|
|
5637
|
-
return { error: saveResult.error };
|
|
5638
|
-
}
|
|
5639
|
-
let nextSettings = saveResult.settings;
|
|
5640
|
-
|
|
5641
|
-
let upstreamResult = resolveBuiltinProxyUpstream(nextSettings);
|
|
5642
|
-
if (upstreamResult.error) {
|
|
5643
|
-
return { error: upstreamResult.error };
|
|
5644
|
-
}
|
|
5645
|
-
|
|
5646
|
-
const runtime = g_builtinProxyRuntime;
|
|
5647
|
-
const shouldRestart = !!runtime && (
|
|
5648
|
-
runtime.settings.host !== nextSettings.host
|
|
5649
|
-
|| runtime.settings.port !== nextSettings.port
|
|
5650
|
-
|| runtime.settings.authSource !== nextSettings.authSource
|
|
5651
|
-
|| runtime.settings.timeoutMs !== nextSettings.timeoutMs
|
|
5652
|
-
|| runtime.upstream.providerName !== upstreamResult.providerName
|
|
5653
|
-
|| runtime.upstream.baseUrl !== upstreamResult.baseUrl
|
|
5654
|
-
|| runtime.upstream.authHeader !== upstreamResult.authHeader
|
|
5655
|
-
);
|
|
5656
|
-
|
|
5657
|
-
if (shouldRestart) {
|
|
5658
|
-
await stopBuiltinProxyRuntime();
|
|
5659
|
-
}
|
|
5660
|
-
|
|
5661
|
-
if (!g_builtinProxyRuntime) {
|
|
5662
|
-
let startRes = await startBuiltinProxyRuntime(nextSettings);
|
|
5663
|
-
if (!startRes.success && /EADDRINUSE/i.test(String(startRes.error || ''))) {
|
|
5664
|
-
const fallbackPort = await findAvailablePort(nextSettings.host, nextSettings.port + 1, 30);
|
|
5665
|
-
if (fallbackPort > 0) {
|
|
5666
|
-
const retrySave = saveBuiltinProxySettings({
|
|
5667
|
-
...nextSettings,
|
|
5668
|
-
port: fallbackPort,
|
|
5669
|
-
enabled: true
|
|
5670
|
-
});
|
|
5671
|
-
if (retrySave.success) {
|
|
5672
|
-
nextSettings = retrySave.settings;
|
|
5673
|
-
upstreamResult = resolveBuiltinProxyUpstream(nextSettings);
|
|
5674
|
-
if (upstreamResult.error) {
|
|
5675
|
-
return { error: upstreamResult.error };
|
|
5676
|
-
}
|
|
5677
|
-
startRes = await startBuiltinProxyRuntime(nextSettings);
|
|
5678
|
-
}
|
|
5679
|
-
}
|
|
5680
|
-
}
|
|
5681
|
-
if (!startRes.success) {
|
|
5682
|
-
return { error: startRes.error || '启动内建代理失败' };
|
|
5683
|
-
}
|
|
5684
|
-
}
|
|
5685
|
-
|
|
5686
|
-
let applyRes = {
|
|
5687
|
-
success: true,
|
|
5688
|
-
provider: BUILTIN_PROXY_PROVIDER_NAME,
|
|
5689
|
-
baseUrl: buildProxyListenUrl(nextSettings),
|
|
5690
|
-
switched: false,
|
|
5691
|
-
model: ''
|
|
5692
|
-
};
|
|
5693
|
-
if (switchToProxy) {
|
|
5694
|
-
applyRes = applyBuiltinProxyProvider({ switchToProxy: true });
|
|
5695
|
-
if (applyRes.error) {
|
|
5696
|
-
return applyRes;
|
|
5697
|
-
}
|
|
5698
|
-
}
|
|
5699
|
-
|
|
5700
|
-
const status = getBuiltinProxyStatus();
|
|
5701
|
-
return {
|
|
5702
|
-
success: true,
|
|
5703
|
-
provider: applyRes.provider,
|
|
5704
|
-
baseUrl: applyRes.baseUrl,
|
|
5705
|
-
switched: applyRes.switched,
|
|
5706
|
-
model: applyRes.model || '',
|
|
5707
|
-
settings: status.settings,
|
|
5708
|
-
runtime: status.runtime
|
|
5709
|
-
};
|
|
5953
|
+
return { error: '该功能已移除' };
|
|
5710
5954
|
}
|
|
5711
5955
|
|
|
5712
5956
|
function removeClaudeSessionIndexEntry(indexPath, sessionFilePath, sessionId) {
|
|
@@ -7348,7 +7592,8 @@ async function cmdSetup() {
|
|
|
7348
7592
|
const { config } = readConfigOrVirtualDefault();
|
|
7349
7593
|
const providers = config.model_providers || {};
|
|
7350
7594
|
const providerNames = Object.keys(providers);
|
|
7351
|
-
const
|
|
7595
|
+
const currentProvider = typeof config.model_provider === 'string' ? config.model_provider.trim() : '';
|
|
7596
|
+
const defaultProvider = currentProvider || providerNames[0] || '';
|
|
7352
7597
|
let availableModels = [];
|
|
7353
7598
|
let defaultModel = config.model || '';
|
|
7354
7599
|
let modelFetchUnlimited = false;
|
|
@@ -7634,7 +7879,7 @@ async function cmdModels() {
|
|
|
7634
7879
|
|
|
7635
7880
|
// 切换提供商
|
|
7636
7881
|
function cmdSwitch(providerName, silent = false) {
|
|
7637
|
-
const config = readConfig();
|
|
7882
|
+
const config = sanitizeRemovedBuiltinProxyProvider(readConfig());
|
|
7638
7883
|
const providers = config.model_providers || {};
|
|
7639
7884
|
|
|
7640
7885
|
if (!providers[providerName]) {
|
|
@@ -7737,6 +7982,10 @@ function cmdAdd(name, baseUrl, apiKey, silent = false) {
|
|
|
7737
7982
|
if (!silent) console.error('错误: local provider 为系统保留名称,不可新增');
|
|
7738
7983
|
throw new Error('local provider 为系统保留名称,不可新增');
|
|
7739
7984
|
}
|
|
7985
|
+
if (isBuiltinProxyProvider(providerName)) {
|
|
7986
|
+
if (!silent) console.error('错误: codexmate-proxy 为保留名称,不可手动添加');
|
|
7987
|
+
throw new Error('codexmate-proxy 为保留名称,不可手动添加');
|
|
7988
|
+
}
|
|
7740
7989
|
|
|
7741
7990
|
const config = readConfig();
|
|
7742
7991
|
if (config.model_providers && config.model_providers[providerName]) {
|
|
@@ -7801,7 +8050,7 @@ function cmdUpdate(name, baseUrl, apiKey, silent = false, options = {}) {
|
|
|
7801
8050
|
if (isNonEditableProvider(name) && !allowManaged) {
|
|
7802
8051
|
const msg = isDefaultLocalProvider(name)
|
|
7803
8052
|
? 'local provider 为系统保留项,不可编辑'
|
|
7804
|
-
: '
|
|
8053
|
+
: 'codexmate-proxy 为保留名称,不可编辑';
|
|
7805
8054
|
if (!silent) console.error(`错误: ${msg}`);
|
|
7806
8055
|
throw new Error(msg);
|
|
7807
8056
|
}
|
|
@@ -9633,22 +9882,55 @@ function resolveUploadFileNameFromRequest(req, fallbackName = 'codex-skills.zip'
|
|
|
9633
9882
|
return normalized || fallback;
|
|
9634
9883
|
}
|
|
9635
9884
|
|
|
9636
|
-
|
|
9885
|
+
function resolveSkillTargetAppFromRequest(req, fallbackApp = 'codex') {
|
|
9886
|
+
const fallbackTarget = resolveSkillTarget({}, fallbackApp);
|
|
9887
|
+
const fallback = fallbackTarget ? fallbackTarget.app : 'codex';
|
|
9888
|
+
try {
|
|
9889
|
+
const parsed = new URL(req.url || '/', 'http://localhost');
|
|
9890
|
+
const hasTargetApp = parsed.searchParams.has('targetApp');
|
|
9891
|
+
const hasTarget = parsed.searchParams.has('target');
|
|
9892
|
+
if (hasTargetApp || hasTarget) {
|
|
9893
|
+
const target = resolveSkillTarget({
|
|
9894
|
+
...(hasTargetApp ? { targetApp: parsed.searchParams.get('targetApp') } : {}),
|
|
9895
|
+
...(hasTarget ? { target: parsed.searchParams.get('target') } : {})
|
|
9896
|
+
}, fallback);
|
|
9897
|
+
return target ? target.app : null;
|
|
9898
|
+
}
|
|
9899
|
+
return fallback;
|
|
9900
|
+
} catch (_) {
|
|
9901
|
+
return fallback;
|
|
9902
|
+
}
|
|
9903
|
+
}
|
|
9904
|
+
|
|
9905
|
+
async function handleImportSkillsZipUpload(req, res, options = {}) {
|
|
9637
9906
|
if (req.method !== 'POST') {
|
|
9907
|
+
if (req && typeof req.resume === 'function') {
|
|
9908
|
+
req.resume();
|
|
9909
|
+
}
|
|
9638
9910
|
writeJsonResponse(res, 405, { error: 'Method Not Allowed' });
|
|
9639
9911
|
return;
|
|
9640
9912
|
}
|
|
9641
9913
|
try {
|
|
9642
|
-
const
|
|
9914
|
+
const forcedTargetApp = normalizeSkillTargetApp(options && options.targetApp ? options.targetApp : '');
|
|
9915
|
+
const targetApp = forcedTargetApp || resolveSkillTargetAppFromRequest(req, 'codex');
|
|
9916
|
+
if (!targetApp) {
|
|
9917
|
+
if (req && typeof req.resume === 'function') {
|
|
9918
|
+
req.resume();
|
|
9919
|
+
}
|
|
9920
|
+
writeJsonResponse(res, 400, { error: '目标宿主不支持' });
|
|
9921
|
+
return;
|
|
9922
|
+
}
|
|
9923
|
+
const fileName = resolveUploadFileNameFromRequest(req, `${targetApp}-skills.zip`);
|
|
9643
9924
|
const upload = await writeUploadZipStream(
|
|
9644
9925
|
req,
|
|
9645
9926
|
'codex-skills-import',
|
|
9646
9927
|
fileName,
|
|
9647
9928
|
MAX_SKILLS_ZIP_UPLOAD_SIZE
|
|
9648
9929
|
);
|
|
9649
|
-
const result = await
|
|
9930
|
+
const result = await importSkillsFromZipFile(upload.zipPath, {
|
|
9650
9931
|
tempDir: upload.tempDir,
|
|
9651
|
-
fallbackName: fileName
|
|
9932
|
+
fallbackName: fileName,
|
|
9933
|
+
targetApp
|
|
9652
9934
|
});
|
|
9653
9935
|
writeJsonResponse(res, 200, result || {});
|
|
9654
9936
|
} catch (e) {
|
|
@@ -9662,8 +9944,12 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
9662
9944
|
|
|
9663
9945
|
const server = http.createServer((req, res) => {
|
|
9664
9946
|
const requestPath = (req.url || '/').split('?')[0];
|
|
9947
|
+
if (requestPath === '/api/import-skills-zip') {
|
|
9948
|
+
void handleImportSkillsZipUpload(req, res);
|
|
9949
|
+
return;
|
|
9950
|
+
}
|
|
9665
9951
|
if (requestPath === '/api/import-codex-skills-zip') {
|
|
9666
|
-
void
|
|
9952
|
+
void handleImportSkillsZipUpload(req, res, { targetApp: 'codex' });
|
|
9667
9953
|
return;
|
|
9668
9954
|
}
|
|
9669
9955
|
if (requestPath === '/api') {
|
|
@@ -9795,6 +10081,21 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
9795
10081
|
case 'preview-agents-diff':
|
|
9796
10082
|
result = buildAgentsDiff(params || {});
|
|
9797
10083
|
break;
|
|
10084
|
+
case 'list-skills':
|
|
10085
|
+
result = listSkills(params || {});
|
|
10086
|
+
break;
|
|
10087
|
+
case 'delete-skills':
|
|
10088
|
+
result = deleteSkills(params || {});
|
|
10089
|
+
break;
|
|
10090
|
+
case 'scan-unmanaged-skills':
|
|
10091
|
+
result = scanUnmanagedSkills(params || {});
|
|
10092
|
+
break;
|
|
10093
|
+
case 'import-skills':
|
|
10094
|
+
result = importSkills(params || {});
|
|
10095
|
+
break;
|
|
10096
|
+
case 'export-skills':
|
|
10097
|
+
result = await exportSkills(params || {});
|
|
10098
|
+
break;
|
|
9798
10099
|
case 'list-codex-skills':
|
|
9799
10100
|
result = listCodexSkills();
|
|
9800
10101
|
break;
|
|
@@ -10154,7 +10455,9 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
10154
10455
|
process.exit(1);
|
|
10155
10456
|
});
|
|
10156
10457
|
|
|
10157
|
-
const openHost =
|
|
10458
|
+
const openHost = host === '::'
|
|
10459
|
+
? '::1'
|
|
10460
|
+
: (host === '0.0.0.0' ? DEFAULT_WEB_OPEN_HOST : host);
|
|
10158
10461
|
const openUrl = `http://${formatHostForUrl(openHost)}:${port}`;
|
|
10159
10462
|
server.listen(port, host, () => {
|
|
10160
10463
|
console.log('\n✓ Web UI 已启动:', openUrl);
|
|
@@ -10297,25 +10600,6 @@ function cmdStart(options = {}) {
|
|
|
10297
10600
|
openBrowser: !options.noBrowser
|
|
10298
10601
|
});
|
|
10299
10602
|
|
|
10300
|
-
const proxySettings = readBuiltinProxySettings();
|
|
10301
|
-
const shouldAutoStartProxy = proxySettings.enabled || hasCodexConfigReadyForProxy();
|
|
10302
|
-
if (shouldAutoStartProxy) {
|
|
10303
|
-
ensureBuiltinProxyForCodexDefault({
|
|
10304
|
-
...proxySettings,
|
|
10305
|
-
switchToProxy: false
|
|
10306
|
-
}).then((res) => {
|
|
10307
|
-
if (res && res.success && res.runtime && res.runtime.listenUrl) {
|
|
10308
|
-
const entryProvider = res.runtime.provider || DEFAULT_LOCAL_PROVIDER_NAME;
|
|
10309
|
-
const upstreamLabel = res.runtime.upstreamProvider ? `(上游: ${res.runtime.upstreamProvider})` : '';
|
|
10310
|
-
console.log(`~ 内建代理已启动(${entryProvider}): ${res.runtime.listenUrl}${upstreamLabel}`);
|
|
10311
|
-
} else if (res && res.error) {
|
|
10312
|
-
console.warn(`! 内建代理启动失败: ${res.error}`);
|
|
10313
|
-
}
|
|
10314
|
-
}).catch((err) => {
|
|
10315
|
-
console.warn(`! 内建代理启动失败: ${err && err.message ? err.message : err}`);
|
|
10316
|
-
});
|
|
10317
|
-
}
|
|
10318
|
-
|
|
10319
10603
|
const requestWebUiRestart = createSerializedWebUiRestartHandler(async (info) => {
|
|
10320
10604
|
const fileLabel = info && info.filename ? info.filename : (info && info.target ? path.basename(info.target) : 'unknown');
|
|
10321
10605
|
console.log(`\n~ 侦测到前端变更 (${fileLabel}),重启中...`);
|
|
@@ -10507,111 +10791,8 @@ function parseProxyCliOptions(args = []) {
|
|
|
10507
10791
|
}
|
|
10508
10792
|
|
|
10509
10793
|
async function cmdProxy(args = []) {
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
if (optionResult.error) {
|
|
10513
|
-
throw new Error(optionResult.error);
|
|
10514
|
-
}
|
|
10515
|
-
const options = optionResult.payload || {};
|
|
10516
|
-
|
|
10517
|
-
if (subcommand === 'status') {
|
|
10518
|
-
const status = getBuiltinProxyStatus();
|
|
10519
|
-
const settings = status.settings || DEFAULT_BUILTIN_PROXY_SETTINGS;
|
|
10520
|
-
console.log('\n内建代理状态:');
|
|
10521
|
-
console.log(' 运行中:', status.running ? '是' : '否');
|
|
10522
|
-
console.log(' 启用:', settings.enabled ? '是' : '否');
|
|
10523
|
-
console.log(' 监听:', buildProxyListenUrl(settings));
|
|
10524
|
-
console.log(' 上游 provider:', settings.provider || '(自动)');
|
|
10525
|
-
console.log(' 鉴权来源:', settings.authSource);
|
|
10526
|
-
if (status.runtime) {
|
|
10527
|
-
console.log(' 实际上游:', status.runtime.upstreamProvider);
|
|
10528
|
-
console.log(' 启动时间:', status.runtime.startedAt);
|
|
10529
|
-
}
|
|
10530
|
-
console.log();
|
|
10531
|
-
return;
|
|
10532
|
-
}
|
|
10533
|
-
|
|
10534
|
-
if (subcommand === 'set' || subcommand === 'config') {
|
|
10535
|
-
const result = saveBuiltinProxySettings(options);
|
|
10536
|
-
if (result.error) {
|
|
10537
|
-
throw new Error(result.error);
|
|
10538
|
-
}
|
|
10539
|
-
const settings = result.settings;
|
|
10540
|
-
console.log('✓ 内建代理配置已保存');
|
|
10541
|
-
console.log(' 监听:', buildProxyListenUrl(settings));
|
|
10542
|
-
console.log(' 上游 provider:', settings.provider || '(自动)');
|
|
10543
|
-
console.log(' 鉴权来源:', settings.authSource);
|
|
10544
|
-
console.log();
|
|
10545
|
-
return;
|
|
10546
|
-
}
|
|
10547
|
-
|
|
10548
|
-
if (subcommand === 'apply' || subcommand === 'apply-provider') {
|
|
10549
|
-
const result = applyBuiltinProxyProvider({
|
|
10550
|
-
switchToProxy: options.switchToProxy !== false
|
|
10551
|
-
});
|
|
10552
|
-
if (result.error) {
|
|
10553
|
-
throw new Error(result.error);
|
|
10554
|
-
}
|
|
10555
|
-
console.log(`✓ 已写入本地代理 provider: ${result.provider}`);
|
|
10556
|
-
console.log(` URL: ${result.baseUrl}`);
|
|
10557
|
-
if (result.switched) {
|
|
10558
|
-
console.log(` 已切换到 ${result.provider}${result.model ? ` / ${result.model}` : ''}`);
|
|
10559
|
-
}
|
|
10560
|
-
console.log();
|
|
10561
|
-
return;
|
|
10562
|
-
}
|
|
10563
|
-
|
|
10564
|
-
if (subcommand === 'enable' || subcommand === 'default-codex') {
|
|
10565
|
-
const result = await ensureBuiltinProxyForCodexDefault(options);
|
|
10566
|
-
if (result.error) {
|
|
10567
|
-
throw new Error(result.error);
|
|
10568
|
-
}
|
|
10569
|
-
const listenUrl = result.runtime && result.runtime.listenUrl
|
|
10570
|
-
? result.runtime.listenUrl
|
|
10571
|
-
: buildProxyListenUrl(result.settings || DEFAULT_BUILTIN_PROXY_SETTINGS);
|
|
10572
|
-
console.log('✓ 已启用 Codex 内建代理默认模式');
|
|
10573
|
-
console.log(` 监听: ${listenUrl}`);
|
|
10574
|
-
if (result.runtime && result.runtime.upstreamProvider) {
|
|
10575
|
-
console.log(` 上游 provider: ${result.runtime.upstreamProvider}`);
|
|
10576
|
-
}
|
|
10577
|
-
console.log(` 当前 provider: ${result.provider}${result.model ? ` / ${result.model}` : ''}`);
|
|
10578
|
-
console.log();
|
|
10579
|
-
return;
|
|
10580
|
-
}
|
|
10581
|
-
|
|
10582
|
-
if (subcommand === 'start') {
|
|
10583
|
-
const result = await startBuiltinProxyRuntime({
|
|
10584
|
-
...options,
|
|
10585
|
-
enabled: true
|
|
10586
|
-
});
|
|
10587
|
-
if (result.error) {
|
|
10588
|
-
throw new Error(result.error);
|
|
10589
|
-
}
|
|
10590
|
-
console.log(`✓ 内建代理已启动: ${result.listenUrl}`);
|
|
10591
|
-
console.log(` 上游 provider: ${result.upstreamProvider}`);
|
|
10592
|
-
console.log(' 按 Ctrl+C 停止代理\n');
|
|
10593
|
-
|
|
10594
|
-
await new Promise((resolve) => {
|
|
10595
|
-
let stopping = false;
|
|
10596
|
-
const gracefulStop = async () => {
|
|
10597
|
-
if (stopping) return;
|
|
10598
|
-
stopping = true;
|
|
10599
|
-
await stopBuiltinProxyRuntime();
|
|
10600
|
-
resolve();
|
|
10601
|
-
};
|
|
10602
|
-
process.once('SIGINT', gracefulStop);
|
|
10603
|
-
process.once('SIGTERM', gracefulStop);
|
|
10604
|
-
});
|
|
10605
|
-
return;
|
|
10606
|
-
}
|
|
10607
|
-
|
|
10608
|
-
if (subcommand === 'stop') {
|
|
10609
|
-
await stopBuiltinProxyRuntime();
|
|
10610
|
-
console.log('✓ 内建代理已停止\n');
|
|
10611
|
-
return;
|
|
10612
|
-
}
|
|
10613
|
-
|
|
10614
|
-
throw new Error(`未知 proxy 子命令: ${subcommand}`);
|
|
10794
|
+
void args;
|
|
10795
|
+
throw new Error('该功能已移除');
|
|
10615
10796
|
}
|
|
10616
10797
|
|
|
10617
10798
|
function parseWorkflowInputArg(rawInput) {
|
|
@@ -12685,11 +12866,9 @@ async function main() {
|
|
|
12685
12866
|
console.log(' codexmate claude <BaseURL> <API密钥> [模型] 写入 Claude Code 配置');
|
|
12686
12867
|
console.log(' codexmate add-model <模型> 添加模型');
|
|
12687
12868
|
console.log(' codexmate delete-model <模型> 删除模型');
|
|
12688
|
-
console.log(' codexmate auth <list|import|switch|delete|status> 认证文件管理');
|
|
12689
|
-
console.log(' codexmate proxy <status|set|apply|enable|start|stop> 内建代理');
|
|
12690
12869
|
console.log(' codexmate workflow <list|get|validate|run|runs> MCP 工作流中心');
|
|
12691
12870
|
console.log(' codexmate run [--host <HOST>] [--no-browser] 启动 Web 界面');
|
|
12692
|
-
console.log(' codexmate codex [参数...] [--follow-up <文本>|--queued-follow-up <文本> 可重复] 等同于 codex --yolo
|
|
12871
|
+
console.log(' codexmate codex [参数...] [--follow-up <文本>|--queued-follow-up <文本> 可重复] 等同于 codex --yolo');
|
|
12693
12872
|
console.log(' 注: follow-up 自动排队仅支持 linux/android/netbsd/openbsd/darwin/freebsd 且 stdin 必须是 TTY,其他平台会报错');
|
|
12694
12873
|
console.log(' codexmate qwen [参数...] 等同于 qwen --yolo');
|
|
12695
12874
|
console.log(' codexmate mcp [serve] [--transport stdio] [--allow-write|--read-only]');
|