yymaxapi 1.0.117 → 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 +161 -29
  2. package/package.json +1 -1
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,
@@ -1633,12 +1655,18 @@ function isWslAvailable() {
1633
1655
  return _wslAvailCache;
1634
1656
  }
1635
1657
 
1636
- function getWslHome() {
1637
- 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;
1638
1661
  try {
1639
- _wslHomeCache = execFileSync('wsl', ['bash', '-c', 'echo $HOME'], { encoding: 'utf8', timeout: 5000, stdio: 'pipe' }).trim() || null;
1640
- } catch { _wslHomeCache = null; }
1641
- 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
+ }
1642
1670
  }
1643
1671
 
1644
1672
  // 获取 WSL 内 openclaw CLI 的完整路径(缓存)
@@ -3195,13 +3223,19 @@ function toWslMountPath(windowsPath) {
3195
3223
  return `/mnt/${match[1].toLowerCase()}/${match[2]}`;
3196
3224
  }
3197
3225
 
3198
- function syncFileToWsl(windowsPath, wslDestPath) {
3226
+ function syncFileToWsl(windowsPath, wslDestPath, target = {}) {
3199
3227
  if (process.platform !== 'win32' || !wslDestPath) return false;
3200
3228
  const wslSrc = toWslMountPath(windowsPath);
3201
3229
  if (!wslSrc) return false;
3202
3230
 
3203
3231
  const wslDir = path.posix.dirname(wslDestPath);
3204
- 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, {
3205
3239
  timeout: 10000,
3206
3240
  stdio: 'pipe'
3207
3241
  });
@@ -3742,35 +3776,124 @@ function getHermesEnvPath() {
3742
3776
  return path.join(getHermesDataDir(), '.env');
3743
3777
  }
3744
3778
 
3745
- 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 = {}) {
3746
3843
  if (process.platform !== 'win32' || !isWslAvailable()) {
3747
3844
  return {
3748
3845
  dataDir: null,
3749
3846
  sourceConfigPath: null,
3750
3847
  configPath: null,
3751
- envPath: null
3848
+ envPath: null,
3849
+ target: null
3752
3850
  };
3753
3851
  }
3754
3852
 
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
- ]);
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);
3761
3883
 
3762
3884
  return {
3763
- dataDir,
3885
+ dataDir: candidate.dataDir,
3764
3886
  sourceConfigPath,
3765
- configPath: path.posix.join(dataDir, 'config.yaml'),
3766
- envPath: path.posix.join(dataDir, '.env')
3887
+ configPath: candidate.configPath,
3888
+ envPath: candidate.envPath,
3889
+ target: candidate.target
3767
3890
  };
3768
3891
  }
3769
3892
 
3770
- function readWslTextFile(filePath) {
3893
+ function readWslTextFile(filePath, target = {}) {
3771
3894
  if (process.platform !== 'win32' || !filePath) return '';
3772
3895
  const quoted = shellQuote(filePath);
3773
- 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);
3774
3897
  return result.ok ? (result.output || result.stdout || '') : '';
3775
3898
  }
3776
3899
 
@@ -5723,7 +5846,12 @@ async function activateHermes(paths, args = {}) {
5723
5846
  const typeLabel = selectedType === 'codex' ? 'GPT' : 'Claude';
5724
5847
  const hermesBaseUrl = buildFullUrl(selectedEndpoint.url, selectedType === 'codex' ? 'codex' : 'claude');
5725
5848
  const writeSpinner = ora({ text: '正在写入 Hermes 配置...', spinner: 'dots' }).start();
5726
- 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
+ });
5727
5855
  writeSpinner.succeed('Hermes 配置写入完成');
5728
5856
 
5729
5857
  console.log(chalk.green('\n✅ Hermes 配置完成!'));
@@ -5736,10 +5864,14 @@ async function activateHermes(paths, args = {}) {
5736
5864
  console.log(chalk.gray(` • ${hermesPaths.configPath}`));
5737
5865
  console.log(chalk.gray(` • ${hermesPaths.envPath}`));
5738
5866
  if (hermesPaths.wslConfigPath && hermesPaths.wslEnvPath) {
5739
- 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}`));
5740
5871
  }
5741
5872
  console.log(chalk.yellow('\n 提示: Hermes doctor 当前会固定检查官方 Anthropic /v1/models,不会读取 model.base_url;第三方 Anthropic 中转可能被误报为 invalid API key'));
5742
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`'));
5743
5875
 
5744
5876
  if (await confirmImmediateTest(args, '是否立即测试 Hermes CLI 连接?')) {
5745
5877
  await testAdditionalCliConnections(args, { only: ['hermes'] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.117",
3
+ "version": "1.0.118",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {