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.
- package/bin/yymaxapi.js +161 -29
- 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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
3756
|
-
const
|
|
3757
|
-
const
|
|
3758
|
-
|
|
3759
|
-
|
|
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:
|
|
3766
|
-
envPath:
|
|
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 =
|
|
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, {
|
|
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
|
-
|
|
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'] });
|