yymaxapi 1.0.116 → 1.0.118

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.
Files changed (2) hide show
  1. package/bin/yymaxapi.js +168 -32
  2. package/package.json +1 -1
package/bin/yymaxapi.js CHANGED
@@ -74,11 +74,15 @@ if (!process.__yymaxapiWarningFilterInstalled && typeof process.emitWarning ===
74
74
  const DEFAULT_ENDPOINTS = [
75
75
  {
76
76
  "name": "默认主节点",
77
- "url": "https://yunyi.cfd"
77
+ "url": "https://yunyi.yun"
78
78
  },
79
79
  {
80
- "name": "CF国外节点1",
81
- "url": "https://cdn2.yunyi.cfd"
80
+ "name": "CDN节点1",
81
+ "url": "https://cdn1.yunyi.yun"
82
+ },
83
+ {
84
+ "name": "CDN节点2",
85
+ "url": "https://cdn2.yunyi.yun"
82
86
  }
83
87
  ];
84
88
 
@@ -849,9 +853,29 @@ function buildPosixAuthCandidates(baseDirs) {
849
853
  return auths;
850
854
  }
851
855
 
852
- 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 = {}) {
853
876
  for (const candidate of candidates) {
854
- 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);
855
879
  if (check.ok && check.output.trim() === 'yes') {
856
880
  return candidate;
857
881
  }
@@ -1312,7 +1336,7 @@ function writeHermesConfig(baseUrl, apiKey, modelId = getDefaultClaudeModel().id
1312
1336
  const configPath = path.join(dataDir, 'config.yaml');
1313
1337
  const envPath = getHermesEnvPath();
1314
1338
  const existingConfigPath = getHermesConfigPath();
1315
- const wslMirror = getHermesWslMirrorInfo();
1339
+ const wslMirror = getHermesWslMirrorInfo(options);
1316
1340
  const modelType = options.type || options.provider || '';
1317
1341
  const resolvedType = resolveHermesModelType(modelId, modelType);
1318
1342
  const runtimeProvider = resolvedType === 'codex' ? 'custom' : 'anthropic';
@@ -1320,12 +1344,12 @@ function writeHermesConfig(baseUrl, apiKey, modelId = getDefaultClaudeModel().id
1320
1344
 
1321
1345
  let existingConfigRaw = readTextIfExists(existingConfigPath);
1322
1346
  if (!existingConfigRaw && wslMirror.sourceConfigPath) {
1323
- existingConfigRaw = readWslTextFile(wslMirror.sourceConfigPath);
1347
+ existingConfigRaw = readWslTextFile(wslMirror.sourceConfigPath, wslMirror.target);
1324
1348
  }
1325
1349
 
1326
1350
  let existingEnvRaw = readTextIfExists(envPath);
1327
1351
  if (!existingEnvRaw && wslMirror.envPath) {
1328
- existingEnvRaw = readWslTextFile(wslMirror.envPath);
1352
+ existingEnvRaw = readWslTextFile(wslMirror.envPath, wslMirror.target);
1329
1353
  }
1330
1354
 
1331
1355
  const normalizedBaseUrl = (
@@ -1353,10 +1377,10 @@ function writeHermesConfig(baseUrl, apiKey, modelId = getDefaultClaudeModel().id
1353
1377
  fs.writeFileSync(envPath, nextEnvRaw, 'utf8');
1354
1378
 
1355
1379
  if (wslMirror.configPath) {
1356
- try { syncFileToWsl(configPath, wslMirror.configPath); } catch { /* best-effort */ }
1380
+ try { syncFileToWsl(configPath, wslMirror.configPath, wslMirror.target); } catch { /* best-effort */ }
1357
1381
  }
1358
1382
  if (wslMirror.envPath) {
1359
- try { syncFileToWsl(envPath, wslMirror.envPath); } catch { /* best-effort */ }
1383
+ try { syncFileToWsl(envPath, wslMirror.envPath, wslMirror.target); } catch { /* best-effort */ }
1360
1384
  }
1361
1385
 
1362
1386
  return {
@@ -1365,6 +1389,8 @@ function writeHermesConfig(baseUrl, apiKey, modelId = getDefaultClaudeModel().id
1365
1389
  envPath,
1366
1390
  wslConfigPath: wslMirror.configPath,
1367
1391
  wslEnvPath: wslMirror.envPath,
1392
+ wslUser: wslMirror.target?.user || '',
1393
+ wslDistro: wslMirror.target?.distro || '',
1368
1394
  modelId,
1369
1395
  modelType: resolvedType,
1370
1396
  provider: runtimeProvider,
@@ -1629,12 +1655,18 @@ function isWslAvailable() {
1629
1655
  return _wslAvailCache;
1630
1656
  }
1631
1657
 
1632
- function getWslHome() {
1633
- if (_wslHomeCache !== undefined) return _wslHomeCache;
1658
+ function getWslHome(target = {}) {
1659
+ const hasTarget = !!String(target.user || target.wslUser || target.distro || target.wslDistro || '').trim();
1660
+ if (!hasTarget && _wslHomeCache !== undefined) return _wslHomeCache;
1634
1661
  try {
1635
- _wslHomeCache = execFileSync('wsl', ['bash', '-c', 'echo $HOME'], { encoding: 'utf8', timeout: 5000, stdio: 'pipe' }).trim() || null;
1636
- } catch { _wslHomeCache = null; }
1637
- return _wslHomeCache;
1662
+ const result = runWslCommand('echo $HOME', { timeout: 5000 }, target);
1663
+ const home = result.ok ? (result.output || '').trim() || null : null;
1664
+ if (!hasTarget) _wslHomeCache = home;
1665
+ return home;
1666
+ } catch {
1667
+ if (!hasTarget) _wslHomeCache = null;
1668
+ return null;
1669
+ }
1638
1670
  }
1639
1671
 
1640
1672
  // 获取 WSL 内 openclaw CLI 的完整路径(缓存)
@@ -3191,13 +3223,19 @@ function toWslMountPath(windowsPath) {
3191
3223
  return `/mnt/${match[1].toLowerCase()}/${match[2]}`;
3192
3224
  }
3193
3225
 
3194
- function syncFileToWsl(windowsPath, wslDestPath) {
3226
+ function syncFileToWsl(windowsPath, wslDestPath, target = {}) {
3195
3227
  if (process.platform !== 'win32' || !wslDestPath) return false;
3196
3228
  const wslSrc = toWslMountPath(windowsPath);
3197
3229
  if (!wslSrc) return false;
3198
3230
 
3199
3231
  const wslDir = path.posix.dirname(wslDestPath);
3200
- execFileSync('wsl', ['bash', '-lc', `mkdir -p ${shellQuote(wslDir)} && cp ${shellQuote(wslSrc)} ${shellQuote(wslDestPath)}`], {
3232
+ const args = [];
3233
+ const distro = String(target.distro || target.wslDistro || '').trim();
3234
+ const user = String(target.user || target.wslUser || '').trim();
3235
+ if (distro) args.push('-d', distro);
3236
+ if (user) args.push('-u', user);
3237
+ args.push('--', 'bash', '-lc', `mkdir -p ${shellQuote(wslDir)} && cp ${shellQuote(wslSrc)} ${shellQuote(wslDestPath)}`);
3238
+ execFileSync('wsl', args, {
3201
3239
  timeout: 10000,
3202
3240
  stdio: 'pipe'
3203
3241
  });
@@ -3738,35 +3776,124 @@ function getHermesEnvPath() {
3738
3776
  return path.join(getHermesDataDir(), '.env');
3739
3777
  }
3740
3778
 
3741
- function getHermesWslMirrorInfo() {
3779
+ function normalizeHermesWslOptions(options = {}) {
3780
+ return {
3781
+ user: String(options.wslUser || options['wsl-user'] || '').trim(),
3782
+ distro: String(options.wslDistro || options['wsl-distro'] || '').trim(),
3783
+ hermesHome: String(options.hermesHome || options['hermes-home'] || options.hermesDataDir || '').trim()
3784
+ };
3785
+ }
3786
+
3787
+ function hasWslFile(filePath, target = {}) {
3788
+ if (!filePath) return false;
3789
+ const quoted = `'${escapeSingleQuotedShell(filePath)}'`;
3790
+ const result = runWslCommand(`test -f ${quoted} && echo yes`, { timeout: 5000 }, target);
3791
+ return result.ok && (result.output || '').trim() === 'yes';
3792
+ }
3793
+
3794
+ function hasWslDir(dirPath, target = {}) {
3795
+ if (!dirPath) return false;
3796
+ const quoted = `'${escapeSingleQuotedShell(dirPath)}'`;
3797
+ const result = runWslCommand(`test -d ${quoted} && echo yes`, { timeout: 5000 }, target);
3798
+ return result.ok && (result.output || '').trim() === 'yes';
3799
+ }
3800
+
3801
+ function probeHermesWslCommand(target = {}) {
3802
+ const result = runWslCommand(
3803
+ 'command -v hermes 2>/dev/null || { test -x "$HOME/.local/bin/hermes" && echo "$HOME/.local/bin/hermes"; } || true',
3804
+ { timeout: 5000 },
3805
+ target
3806
+ );
3807
+ return result.ok ? (result.output || '').trim() : '';
3808
+ }
3809
+
3810
+ function buildHermesWslCandidate(target = {}, explicitHermesHome = '') {
3811
+ const user = String(target.user || '').trim();
3812
+ const distro = String(target.distro || '').trim();
3813
+ const home = explicitHermesHome
3814
+ ? ''
3815
+ : (getWslHome({ user, distro }) || (user === 'root' ? '/root' : ''));
3816
+ const dataDir = explicitHermesHome || (home ? path.posix.join(home, '.hermes') : '');
3817
+ if (!dataDir) return null;
3818
+ return {
3819
+ target: { user, distro },
3820
+ dataDir,
3821
+ configPath: path.posix.join(dataDir, 'config.yaml'),
3822
+ legacyConfigPath: path.posix.join(dataDir, 'config.yml'),
3823
+ envPath: path.posix.join(dataDir, '.env')
3824
+ };
3825
+ }
3826
+
3827
+ function scoreHermesWslCandidate(candidate) {
3828
+ if (!candidate) return { score: -1, commandPath: '', sourceConfigPath: null };
3829
+ const commandPath = probeHermesWslCommand(candidate.target);
3830
+ const sourceConfigPath = findExistingWslFile(
3831
+ [candidate.configPath, candidate.legacyConfigPath],
3832
+ candidate.target
3833
+ );
3834
+ let score = 0;
3835
+ if (commandPath) score += 4;
3836
+ if (sourceConfigPath) score += 3;
3837
+ if (hasWslFile(candidate.envPath, candidate.target)) score += 1;
3838
+ if (hasWslDir(candidate.dataDir, candidate.target)) score += 1;
3839
+ return { score, commandPath, sourceConfigPath };
3840
+ }
3841
+
3842
+ function getHermesWslMirrorInfo(options = {}) {
3742
3843
  if (process.platform !== 'win32' || !isWslAvailable()) {
3743
3844
  return {
3744
3845
  dataDir: null,
3745
3846
  sourceConfigPath: null,
3746
3847
  configPath: null,
3747
- envPath: null
3848
+ envPath: null,
3849
+ target: null
3748
3850
  };
3749
3851
  }
3750
3852
 
3751
- const wslHome = getWslHome() || '/root';
3752
- const dataDir = path.posix.join(wslHome, '.hermes');
3753
- const sourceConfigPath = findExistingWslFile([
3754
- path.posix.join(dataDir, 'config.yaml'),
3755
- path.posix.join(dataDir, 'config.yml')
3756
- ]);
3853
+ const normalized = normalizeHermesWslOptions(options);
3854
+ const candidates = [];
3855
+ const seen = new Set();
3856
+ const addCandidate = (candidate) => {
3857
+ if (!candidate) return;
3858
+ const key = `${candidate.target.distro}|${candidate.target.user}|${candidate.dataDir}`;
3859
+ if (seen.has(key)) return;
3860
+ seen.add(key);
3861
+ candidates.push(candidate);
3862
+ };
3863
+
3864
+ if (normalized.hermesHome || normalized.user || normalized.distro) {
3865
+ addCandidate(buildHermesWslCandidate({
3866
+ user: normalized.user,
3867
+ distro: normalized.distro
3868
+ }, normalized.hermesHome));
3869
+ }
3870
+
3871
+ addCandidate(buildHermesWslCandidate({ distro: normalized.distro }, ''));
3872
+ addCandidate(buildHermesWslCandidate({ user: 'root', distro: normalized.distro }, normalized.hermesHome && !normalized.user ? normalized.hermesHome : ''));
3873
+
3874
+ const scored = candidates
3875
+ .map(candidate => ({ candidate, ...scoreHermesWslCandidate(candidate) }))
3876
+ .sort((a, b) => b.score - a.score);
3877
+ const selected = scored.find(item => item.score > 0) || scored[0];
3878
+ const candidate = selected?.candidate || buildHermesWslCandidate({}, '') || buildHermesWslCandidate({ user: 'root' }, '/root/.hermes');
3879
+ const sourceConfigPath = selected?.sourceConfigPath || findExistingWslFile([
3880
+ candidate.configPath,
3881
+ candidate.legacyConfigPath
3882
+ ], candidate.target);
3757
3883
 
3758
3884
  return {
3759
- dataDir,
3885
+ dataDir: candidate.dataDir,
3760
3886
  sourceConfigPath,
3761
- configPath: path.posix.join(dataDir, 'config.yaml'),
3762
- envPath: path.posix.join(dataDir, '.env')
3887
+ configPath: candidate.configPath,
3888
+ envPath: candidate.envPath,
3889
+ target: candidate.target
3763
3890
  };
3764
3891
  }
3765
3892
 
3766
- function readWslTextFile(filePath) {
3893
+ function readWslTextFile(filePath, target = {}) {
3767
3894
  if (process.platform !== 'win32' || !filePath) return '';
3768
3895
  const quoted = shellQuote(filePath);
3769
- const result = safeExec(`wsl -- bash -lc "cat ${quoted} 2>/dev/null"`, { timeout: 10000 });
3896
+ const result = runWslCommand(`cat ${quoted} 2>/dev/null`, { timeout: 10000 }, target);
3770
3897
  return result.ok ? (result.output || result.stdout || '') : '';
3771
3898
  }
3772
3899
 
@@ -5719,7 +5846,12 @@ async function activateHermes(paths, args = {}) {
5719
5846
  const typeLabel = selectedType === 'codex' ? 'GPT' : 'Claude';
5720
5847
  const hermesBaseUrl = buildFullUrl(selectedEndpoint.url, selectedType === 'codex' ? 'codex' : 'claude');
5721
5848
  const writeSpinner = ora({ text: '正在写入 Hermes 配置...', spinner: 'dots' }).start();
5722
- const hermesPaths = writeHermesConfig(hermesBaseUrl, apiKey, selectedModel.id, { type: selectedType });
5849
+ const hermesPaths = writeHermesConfig(hermesBaseUrl, apiKey, selectedModel.id, {
5850
+ type: selectedType,
5851
+ hermesHome: args['hermes-home'] || args.hermesHome,
5852
+ wslUser: args['wsl-user'] || args.wslUser,
5853
+ wslDistro: args['wsl-distro'] || args.wslDistro
5854
+ });
5723
5855
  writeSpinner.succeed('Hermes 配置写入完成');
5724
5856
 
5725
5857
  console.log(chalk.green('\n✅ Hermes 配置完成!'));
@@ -5732,10 +5864,14 @@ async function activateHermes(paths, args = {}) {
5732
5864
  console.log(chalk.gray(` • ${hermesPaths.configPath}`));
5733
5865
  console.log(chalk.gray(` • ${hermesPaths.envPath}`));
5734
5866
  if (hermesPaths.wslConfigPath && hermesPaths.wslEnvPath) {
5735
- console.log(chalk.gray(' • 已额外同步到 WSL ~/.hermes'));
5867
+ const targetLabel = hermesPaths.wslUser
5868
+ ? `WSL ${hermesPaths.wslUser} 用户`
5869
+ : 'WSL 默认用户';
5870
+ console.log(chalk.gray(` • 已额外同步到 ${targetLabel}: ${hermesPaths.wslConfigPath}`));
5736
5871
  }
5737
5872
  console.log(chalk.yellow('\n 提示: Hermes doctor 当前会固定检查官方 Anthropic /v1/models,不会读取 model.base_url;第三方 Anthropic 中转可能被误报为 invalid API key'));
5738
5873
  console.log(chalk.yellow(' 建议: 以 yymaxapi 的 Hermes CLI 运行时测试,或 `hermes chat -Q -q "请只回复 OK"` 的结果为准'));
5874
+ console.log(chalk.yellow(' 如通过飞书/Telegram/Discord 调用,请重启网关: `hermes gateway restart` 或 `hermes gateway run`'));
5739
5875
 
5740
5876
  if (await confirmImmediateTest(args, '是否立即测试 Hermes CLI 连接?')) {
5741
5877
  await testAdditionalCliConnections(args, { only: ['hermes'] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.116",
3
+ "version": "1.0.118",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {