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/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 = '127.0.0.1';
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
- { app: 'claude', label: 'Claude Code', dir: CLAUDE_SKILLS_DIR },
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 isSkillDirectoryEntry(entryName) {
1394
- const targetPath = path.join(CODEX_SKILLS_DIR, entryName);
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 getCodexSkillEntryInfoByName(entryName) {
1564
- const targetPath = path.join(CODEX_SKILLS_DIR, entryName);
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(CODEX_SKILLS_DIR, targetPath);
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 && !isSkillDirectoryEntry(entryName)) {
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 listCodexSkills() {
1599
- if (!fs.existsSync(CODEX_SKILLS_DIR)) {
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
- root: CODEX_SKILLS_DIR,
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(CODEX_SKILLS_DIR, { withFileTypes: true });
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 getCodexSkillEntryInfoByName(name);
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
- root: CODEX_SKILLS_DIR,
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 scanUnmanagedCodexSkills() {
1672
- const existing = listCodexSkills();
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
- for (const source of SKILL_IMPORT_SOURCES) {
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
- root: CODEX_SKILLS_DIR,
1810
+ targetApp: target.app,
1811
+ targetLabel: target.label,
1812
+ root: target.dir,
1710
1813
  items,
1711
- sources: SKILL_IMPORT_SOURCES.map((source) => ({
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 importCodexSkills(params = {}) {
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(CODEX_SKILLS_DIR, normalizedName.name);
1778
- const targetRelative = path.relative(CODEX_SKILLS_DIR, targetPath);
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: 'Codex 中已存在同名 skill'
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
- root: CODEX_SKILLS_DIR
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 importCodexSkillsFromZipFile(zipPath, options = {}) {
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(CODEX_SKILLS_DIR, normalizedName.name);
1946
- const targetRelative = path.relative(CODEX_SKILLS_DIR, targetPath);
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: 'Codex 中已存在同名 skill'
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
- root: CODEX_SKILLS_DIR
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
- root: CODEX_SKILLS_DIR
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 importCodexSkillsFromZip(payload = {}) {
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 upload = writeUploadZip(payload.fileBase64, 'codex-skills-import', payload.fileName || 'codex-skills.zip');
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
- return importCodexSkillsFromZipFile(upload.zipPath, {
2038
- tempDir: upload.tempDir,
2039
- fallbackName: payload.fileName || ''
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 exportCodexSkills(params = {}) {
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(CODEX_SKILLS_DIR, normalizedName.name);
2066
- const sourceRelative = path.relative(CODEX_SKILLS_DIR, sourcePath);
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
- root: CODEX_SKILLS_DIR
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 = `codex-skills-${randomToken}.zip`;
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
- root: CODEX_SKILLS_DIR
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
- root: CODEX_SKILLS_DIR
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 deleteCodexSkills(params = {}) {
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(CODEX_SKILLS_DIR, normalized.name);
2179
- const relativePath = path.relative(CODEX_SKILLS_DIR, skillPath);
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
- root: CODEX_SKILLS_DIR
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' ? buildVirtualDefaultConfig() : {},
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
- const settings = readBuiltinProxySettings();
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
- const payload = isPlainObject(params) ? { ...params } : {};
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 defaultProvider = config.model_provider || providerNames[0] || '';
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
- async function handleImportCodexSkillsZipUpload(req, res) {
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 fileName = resolveUploadFileNameFromRequest(req, 'codex-skills.zip');
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 importCodexSkillsFromZipFile(upload.zipPath, {
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 handleImportCodexSkillsZipUpload(req, res);
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 = isAnyAddressHost(host) ? DEFAULT_WEB_HOST : host;
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
- const subcommand = (args[0] || 'status').toLowerCase();
10511
- const optionResult = parseProxyCliOptions(args.slice(1));
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]');