yymaxapi 1.0.117 → 1.0.119

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.md CHANGED
@@ -16,7 +16,7 @@ npx yymaxapi@latest
16
16
  npx yymaxapi@latest speed-test
17
17
  ```
18
18
  或在具体配置命令后追加 `--speed-test`,按测速结果手动选点。
19
- 如需直接切换到其他云翼域名,可在预设/一键激活命令后追加 `--endpoint-url https://yunyi.cfd`,也兼容 `--base-url https://你的域名/claude` 这种写法。
19
+ 如需直接切换到其他云翼域名,可在预设/一键激活命令后追加 `--endpoint-url https://yunyi.yun`,也兼容 `--base-url https://你的域名/claude` 这种写法。
20
20
 
21
21
  **方式二:一键配置 Claude**
22
22
  ```bash
package/bin/yymaxapi.js CHANGED
@@ -853,9 +853,29 @@ function buildPosixAuthCandidates(baseDirs) {
853
853
  return auths;
854
854
  }
855
855
 
856
- function findExistingWslFile(candidates = []) {
856
+ function formatWslArg(value) {
857
+ const str = String(value || '');
858
+ return /^[A-Za-z0-9_.:-]+$/.test(str) ? str : shellQuote(str);
859
+ }
860
+
861
+ function buildWslCommand(command, target = {}) {
862
+ const parts = ['wsl'];
863
+ const distro = String(target.distro || target.wslDistro || '').trim();
864
+ const user = String(target.user || target.wslUser || '').trim();
865
+ if (distro) parts.push('-d', formatWslArg(distro));
866
+ if (user) parts.push('-u', formatWslArg(user));
867
+ parts.push('--', 'bash', target.login ? '-lc' : '-c', shellQuote(command));
868
+ return parts.join(' ');
869
+ }
870
+
871
+ function runWslCommand(command, options = {}, target = {}) {
872
+ return safeExec(buildWslCommand(command, target), options);
873
+ }
874
+
875
+ function findExistingWslFile(candidates = [], target = {}) {
857
876
  for (const candidate of candidates) {
858
- const check = safeExec(`wsl -- bash -c "test -f '${candidate}' && echo yes"`, { timeout: 5000 });
877
+ const quotedCandidate = `'${escapeSingleQuotedShell(candidate)}'`;
878
+ const check = runWslCommand(`test -f ${quotedCandidate} && echo yes`, { timeout: 5000 }, target);
859
879
  if (check.ok && check.output.trim() === 'yes') {
860
880
  return candidate;
861
881
  }
@@ -1316,7 +1336,7 @@ function writeHermesConfig(baseUrl, apiKey, modelId = getDefaultClaudeModel().id
1316
1336
  const configPath = path.join(dataDir, 'config.yaml');
1317
1337
  const envPath = getHermesEnvPath();
1318
1338
  const existingConfigPath = getHermesConfigPath();
1319
- const wslMirror = getHermesWslMirrorInfo();
1339
+ const wslMirror = getHermesWslMirrorInfo(options);
1320
1340
  const modelType = options.type || options.provider || '';
1321
1341
  const resolvedType = resolveHermesModelType(modelId, modelType);
1322
1342
  const runtimeProvider = resolvedType === 'codex' ? 'custom' : 'anthropic';
@@ -1324,12 +1344,12 @@ function writeHermesConfig(baseUrl, apiKey, modelId = getDefaultClaudeModel().id
1324
1344
 
1325
1345
  let existingConfigRaw = readTextIfExists(existingConfigPath);
1326
1346
  if (!existingConfigRaw && wslMirror.sourceConfigPath) {
1327
- existingConfigRaw = readWslTextFile(wslMirror.sourceConfigPath);
1347
+ existingConfigRaw = readWslTextFile(wslMirror.sourceConfigPath, wslMirror.target);
1328
1348
  }
1329
1349
 
1330
1350
  let existingEnvRaw = readTextIfExists(envPath);
1331
1351
  if (!existingEnvRaw && wslMirror.envPath) {
1332
- existingEnvRaw = readWslTextFile(wslMirror.envPath);
1352
+ existingEnvRaw = readWslTextFile(wslMirror.envPath, wslMirror.target);
1333
1353
  }
1334
1354
 
1335
1355
  const normalizedBaseUrl = (
@@ -1357,10 +1377,10 @@ function writeHermesConfig(baseUrl, apiKey, modelId = getDefaultClaudeModel().id
1357
1377
  fs.writeFileSync(envPath, nextEnvRaw, 'utf8');
1358
1378
 
1359
1379
  if (wslMirror.configPath) {
1360
- try { syncFileToWsl(configPath, wslMirror.configPath); } catch { /* best-effort */ }
1380
+ try { syncFileToWsl(configPath, wslMirror.configPath, wslMirror.target); } catch { /* best-effort */ }
1361
1381
  }
1362
1382
  if (wslMirror.envPath) {
1363
- try { syncFileToWsl(envPath, wslMirror.envPath); } catch { /* best-effort */ }
1383
+ try { syncFileToWsl(envPath, wslMirror.envPath, wslMirror.target); } catch { /* best-effort */ }
1364
1384
  }
1365
1385
 
1366
1386
  return {
@@ -1369,6 +1389,8 @@ function writeHermesConfig(baseUrl, apiKey, modelId = getDefaultClaudeModel().id
1369
1389
  envPath,
1370
1390
  wslConfigPath: wslMirror.configPath,
1371
1391
  wslEnvPath: wslMirror.envPath,
1392
+ wslUser: wslMirror.target?.user || '',
1393
+ wslDistro: wslMirror.target?.distro || '',
1372
1394
  modelId,
1373
1395
  modelType: resolvedType,
1374
1396
  provider: runtimeProvider,
@@ -1624,21 +1646,33 @@ function invalidateGatewayEnvCache() {
1624
1646
  function isWslAvailable() {
1625
1647
  if (process.platform !== 'win32') return false;
1626
1648
  if (_wslAvailCache !== null) return _wslAvailCache;
1627
- try {
1628
- execFileSync('wsl', ['echo', 'ok'], { encoding: 'utf8', timeout: 5000, stdio: 'pipe' });
1629
- _wslAvailCache = true;
1630
- } catch {
1631
- _wslAvailCache = false;
1649
+ const probes = [
1650
+ 'wsl -- echo ok',
1651
+ 'wsl -u root -- echo ok'
1652
+ ];
1653
+ for (const cmd of probes) {
1654
+ const result = safeExec(cmd, { timeout: 5000 });
1655
+ if (result.ok && String(result.output || result.stdout || '').includes('ok')) {
1656
+ _wslAvailCache = true;
1657
+ return _wslAvailCache;
1658
+ }
1632
1659
  }
1660
+ _wslAvailCache = false;
1633
1661
  return _wslAvailCache;
1634
1662
  }
1635
1663
 
1636
- function getWslHome() {
1637
- if (_wslHomeCache !== undefined) return _wslHomeCache;
1664
+ function getWslHome(target = {}) {
1665
+ const hasTarget = !!String(target.user || target.wslUser || target.distro || target.wslDistro || '').trim();
1666
+ if (!hasTarget && _wslHomeCache !== undefined) return _wslHomeCache;
1638
1667
  try {
1639
- _wslHomeCache = execFileSync('wsl', ['bash', '-c', 'echo $HOME'], { encoding: 'utf8', timeout: 5000, stdio: 'pipe' }).trim() || null;
1640
- } catch { _wslHomeCache = null; }
1641
- return _wslHomeCache;
1668
+ const result = runWslCommand('echo $HOME', { timeout: 5000 }, target);
1669
+ const home = result.ok ? (result.output || '').trim() || null : null;
1670
+ if (!hasTarget) _wslHomeCache = home;
1671
+ return home;
1672
+ } catch {
1673
+ if (!hasTarget) _wslHomeCache = null;
1674
+ return null;
1675
+ }
1642
1676
  }
1643
1677
 
1644
1678
  // 获取 WSL 内 openclaw CLI 的完整路径(缓存)
@@ -3195,13 +3229,19 @@ function toWslMountPath(windowsPath) {
3195
3229
  return `/mnt/${match[1].toLowerCase()}/${match[2]}`;
3196
3230
  }
3197
3231
 
3198
- function syncFileToWsl(windowsPath, wslDestPath) {
3232
+ function syncFileToWsl(windowsPath, wslDestPath, target = {}) {
3199
3233
  if (process.platform !== 'win32' || !wslDestPath) return false;
3200
3234
  const wslSrc = toWslMountPath(windowsPath);
3201
3235
  if (!wslSrc) return false;
3202
3236
 
3203
3237
  const wslDir = path.posix.dirname(wslDestPath);
3204
- execFileSync('wsl', ['bash', '-lc', `mkdir -p ${shellQuote(wslDir)} && cp ${shellQuote(wslSrc)} ${shellQuote(wslDestPath)}`], {
3238
+ const args = [];
3239
+ const distro = String(target.distro || target.wslDistro || '').trim();
3240
+ const user = String(target.user || target.wslUser || '').trim();
3241
+ if (distro) args.push('-d', distro);
3242
+ if (user) args.push('-u', user);
3243
+ args.push('--', 'bash', '-lc', `mkdir -p ${shellQuote(wslDir)} && cp ${shellQuote(wslSrc)} ${shellQuote(wslDestPath)}`);
3244
+ execFileSync('wsl', args, {
3205
3245
  timeout: 10000,
3206
3246
  stdio: 'pipe'
3207
3247
  });
@@ -3742,35 +3782,124 @@ function getHermesEnvPath() {
3742
3782
  return path.join(getHermesDataDir(), '.env');
3743
3783
  }
3744
3784
 
3745
- function getHermesWslMirrorInfo() {
3785
+ function normalizeHermesWslOptions(options = {}) {
3786
+ return {
3787
+ user: String(options.wslUser || options['wsl-user'] || '').trim(),
3788
+ distro: String(options.wslDistro || options['wsl-distro'] || '').trim(),
3789
+ hermesHome: String(options.hermesHome || options['hermes-home'] || options.hermesDataDir || '').trim()
3790
+ };
3791
+ }
3792
+
3793
+ function hasWslFile(filePath, target = {}) {
3794
+ if (!filePath) return false;
3795
+ const quoted = `'${escapeSingleQuotedShell(filePath)}'`;
3796
+ const result = runWslCommand(`test -f ${quoted} && echo yes`, { timeout: 5000 }, target);
3797
+ return result.ok && (result.output || '').trim() === 'yes';
3798
+ }
3799
+
3800
+ function hasWslDir(dirPath, target = {}) {
3801
+ if (!dirPath) return false;
3802
+ const quoted = `'${escapeSingleQuotedShell(dirPath)}'`;
3803
+ const result = runWslCommand(`test -d ${quoted} && echo yes`, { timeout: 5000 }, target);
3804
+ return result.ok && (result.output || '').trim() === 'yes';
3805
+ }
3806
+
3807
+ function probeHermesWslCommand(target = {}) {
3808
+ const result = runWslCommand(
3809
+ 'command -v hermes 2>/dev/null || { test -x "$HOME/.local/bin/hermes" && echo "$HOME/.local/bin/hermes"; } || true',
3810
+ { timeout: 5000 },
3811
+ target
3812
+ );
3813
+ return result.ok ? (result.output || '').trim() : '';
3814
+ }
3815
+
3816
+ function buildHermesWslCandidate(target = {}, explicitHermesHome = '') {
3817
+ const user = String(target.user || '').trim();
3818
+ const distro = String(target.distro || '').trim();
3819
+ const home = explicitHermesHome
3820
+ ? ''
3821
+ : (getWslHome({ user, distro }) || (user === 'root' ? '/root' : ''));
3822
+ const dataDir = explicitHermesHome || (home ? path.posix.join(home, '.hermes') : '');
3823
+ if (!dataDir) return null;
3824
+ return {
3825
+ target: { user, distro },
3826
+ dataDir,
3827
+ configPath: path.posix.join(dataDir, 'config.yaml'),
3828
+ legacyConfigPath: path.posix.join(dataDir, 'config.yml'),
3829
+ envPath: path.posix.join(dataDir, '.env')
3830
+ };
3831
+ }
3832
+
3833
+ function scoreHermesWslCandidate(candidate) {
3834
+ if (!candidate) return { score: -1, commandPath: '', sourceConfigPath: null };
3835
+ const commandPath = probeHermesWslCommand(candidate.target);
3836
+ const sourceConfigPath = findExistingWslFile(
3837
+ [candidate.configPath, candidate.legacyConfigPath],
3838
+ candidate.target
3839
+ );
3840
+ let score = 0;
3841
+ if (commandPath) score += 4;
3842
+ if (sourceConfigPath) score += 3;
3843
+ if (hasWslFile(candidate.envPath, candidate.target)) score += 1;
3844
+ if (hasWslDir(candidate.dataDir, candidate.target)) score += 1;
3845
+ return { score, commandPath, sourceConfigPath };
3846
+ }
3847
+
3848
+ function getHermesWslMirrorInfo(options = {}) {
3746
3849
  if (process.platform !== 'win32' || !isWslAvailable()) {
3747
3850
  return {
3748
3851
  dataDir: null,
3749
3852
  sourceConfigPath: null,
3750
3853
  configPath: null,
3751
- envPath: null
3854
+ envPath: null,
3855
+ target: null
3752
3856
  };
3753
3857
  }
3754
3858
 
3755
- const wslHome = getWslHome() || '/root';
3756
- const dataDir = path.posix.join(wslHome, '.hermes');
3757
- const sourceConfigPath = findExistingWslFile([
3758
- path.posix.join(dataDir, 'config.yaml'),
3759
- path.posix.join(dataDir, 'config.yml')
3760
- ]);
3859
+ const normalized = normalizeHermesWslOptions(options);
3860
+ const candidates = [];
3861
+ const seen = new Set();
3862
+ const addCandidate = (candidate) => {
3863
+ if (!candidate) return;
3864
+ const key = `${candidate.target.distro}|${candidate.target.user}|${candidate.dataDir}`;
3865
+ if (seen.has(key)) return;
3866
+ seen.add(key);
3867
+ candidates.push(candidate);
3868
+ };
3869
+
3870
+ if (normalized.hermesHome || normalized.user || normalized.distro) {
3871
+ addCandidate(buildHermesWslCandidate({
3872
+ user: normalized.user,
3873
+ distro: normalized.distro
3874
+ }, normalized.hermesHome));
3875
+ }
3876
+
3877
+ addCandidate(buildHermesWslCandidate({ distro: normalized.distro }, ''));
3878
+ addCandidate(buildHermesWslCandidate({ user: 'root', distro: normalized.distro }, normalized.hermesHome && !normalized.user ? normalized.hermesHome : ''));
3879
+
3880
+ const scored = candidates
3881
+ .map(candidate => ({ candidate, ...scoreHermesWslCandidate(candidate) }))
3882
+ .sort((a, b) => b.score - a.score);
3883
+ const selected = scored.find(item => item.score > 0) || scored[0];
3884
+ const candidate = selected?.candidate || buildHermesWslCandidate({}, '') || buildHermesWslCandidate({ user: 'root' }, '/root/.hermes');
3885
+ const sourceConfigPath = selected?.sourceConfigPath || findExistingWslFile([
3886
+ candidate.configPath,
3887
+ candidate.legacyConfigPath
3888
+ ], candidate.target);
3761
3889
 
3762
3890
  return {
3763
- dataDir,
3891
+ dataDir: candidate.dataDir,
3764
3892
  sourceConfigPath,
3765
- configPath: path.posix.join(dataDir, 'config.yaml'),
3766
- envPath: path.posix.join(dataDir, '.env')
3893
+ configPath: candidate.configPath,
3894
+ envPath: candidate.envPath,
3895
+ target: candidate.target
3767
3896
  };
3768
3897
  }
3769
3898
 
3770
- function readWslTextFile(filePath) {
3899
+ function readWslTextFile(filePath, target = {}) {
3771
3900
  if (process.platform !== 'win32' || !filePath) return '';
3772
3901
  const quoted = shellQuote(filePath);
3773
- const result = safeExec(`wsl -- bash -lc "cat ${quoted} 2>/dev/null"`, { timeout: 10000 });
3902
+ const result = runWslCommand(`cat ${quoted} 2>/dev/null`, { timeout: 10000 }, target);
3774
3903
  return result.ok ? (result.output || result.stdout || '') : '';
3775
3904
  }
3776
3905
 
@@ -5723,7 +5852,12 @@ async function activateHermes(paths, args = {}) {
5723
5852
  const typeLabel = selectedType === 'codex' ? 'GPT' : 'Claude';
5724
5853
  const hermesBaseUrl = buildFullUrl(selectedEndpoint.url, selectedType === 'codex' ? 'codex' : 'claude');
5725
5854
  const writeSpinner = ora({ text: '正在写入 Hermes 配置...', spinner: 'dots' }).start();
5726
- const hermesPaths = writeHermesConfig(hermesBaseUrl, apiKey, selectedModel.id, { type: selectedType });
5855
+ const hermesPaths = writeHermesConfig(hermesBaseUrl, apiKey, selectedModel.id, {
5856
+ type: selectedType,
5857
+ hermesHome: args['hermes-home'] || args.hermesHome,
5858
+ wslUser: args['wsl-user'] || args.wslUser,
5859
+ wslDistro: args['wsl-distro'] || args.wslDistro
5860
+ });
5727
5861
  writeSpinner.succeed('Hermes 配置写入完成');
5728
5862
 
5729
5863
  console.log(chalk.green('\n✅ Hermes 配置完成!'));
@@ -5736,10 +5870,14 @@ async function activateHermes(paths, args = {}) {
5736
5870
  console.log(chalk.gray(` • ${hermesPaths.configPath}`));
5737
5871
  console.log(chalk.gray(` • ${hermesPaths.envPath}`));
5738
5872
  if (hermesPaths.wslConfigPath && hermesPaths.wslEnvPath) {
5739
- console.log(chalk.gray(' • 已额外同步到 WSL ~/.hermes'));
5873
+ const targetLabel = hermesPaths.wslUser
5874
+ ? `WSL ${hermesPaths.wslUser} 用户`
5875
+ : 'WSL 默认用户';
5876
+ console.log(chalk.gray(` • 已额外同步到 ${targetLabel}: ${hermesPaths.wslConfigPath}`));
5740
5877
  }
5741
5878
  console.log(chalk.yellow('\n 提示: Hermes doctor 当前会固定检查官方 Anthropic /v1/models,不会读取 model.base_url;第三方 Anthropic 中转可能被误报为 invalid API key'));
5742
5879
  console.log(chalk.yellow(' 建议: 以 yymaxapi 的 Hermes CLI 运行时测试,或 `hermes chat -Q -q "请只回复 OK"` 的结果为准'));
5880
+ console.log(chalk.yellow(' 如通过飞书/Telegram/Discord 调用,请重启网关: `hermes gateway restart` 或 `hermes gateway run`'));
5743
5881
 
5744
5882
  if (await confirmImmediateTest(args, '是否立即测试 Hermes CLI 连接?')) {
5745
5883
  await testAdditionalCliConnections(args, { only: ['hermes'] });
@@ -13,8 +13,8 @@
13
13
  ```json5
14
14
  {
15
15
  "endpoints": [
16
- { "name": "默认主节点", "url": "https://yunyi.cfd" },
17
- { "name": "CF国外节点1", "url": "https://cdn2.yunyi.cfd" }
16
+ { "name": "默认主节点", "url": "https://yunyi.yun" },
17
+ { "name": "CF国外节点1", "url": "https://cdn2.yunyi.yun" }
18
18
  ],
19
19
  "fallbackEndpoints": [
20
20
  { "name": "备用节点1", "url": "http://47.99.42.193" },
@@ -75,7 +75,7 @@ npx yymaxapi@latest
75
75
  ```json
76
76
  {
77
77
  "provider": "anthropic",
78
- "base_url": "https://yunyi.cfd/claude",
78
+ "base_url": "https://yunyi.yun/claude",
79
79
  "api": "anthropic-messages",
80
80
  "api_key": "<你的云翼 API Key>",
81
81
  "model": {
@@ -90,7 +90,7 @@ npx yymaxapi@latest
90
90
  ```json
91
91
  {
92
92
  "provider": "openai",
93
- "base_url": "https://yunyi.cfd/codex",
93
+ "base_url": "https://yunyi.yun/codex",
94
94
  "api": "openai-completions",
95
95
  "api_key": "<你的云翼 API Key>",
96
96
  "model": {
@@ -121,7 +121,7 @@ npx yymaxapi@latest
121
121
  ```json
122
122
  {
123
123
  "provider": "anthropic",
124
- "base_url": "https://yunyi.cfd/claude",
124
+ "base_url": "https://yunyi.yun/claude",
125
125
  "api": "anthropic-messages",
126
126
  "api_key": "<你的云翼 API Key>",
127
127
  "model": {
@@ -136,7 +136,7 @@ npx yymaxapi@latest
136
136
  ```json
137
137
  {
138
138
  "provider": "openai",
139
- "base_url": "https://yunyi.cfd/codex",
139
+ "base_url": "https://yunyi.yun/codex",
140
140
  "api": "openai-completions",
141
141
  "api_key": "<你的云翼 API Key>",
142
142
  "model": {
@@ -152,10 +152,10 @@ npx yymaxapi@latest
152
152
 
153
153
  ```json
154
154
  {
155
- "apiBaseUrl": "https://yunyi.cfd/claude",
155
+ "apiBaseUrl": "https://yunyi.yun/claude",
156
156
  "env": {
157
157
  "ANTHROPIC_API_KEY": "<你的云翼 API Key>",
158
- "ANTHROPIC_BASE_URL": "https://yunyi.cfd/claude"
158
+ "ANTHROPIC_BASE_URL": "https://yunyi.yun/claude"
159
159
  }
160
160
  }
161
161
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.117",
3
+ "version": "1.0.119",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {