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.
- package/bin/yymaxapi.js +168 -32
- 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.
|
|
77
|
+
"url": "https://yunyi.yun"
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
|
-
"name": "
|
|
81
|
-
"url": "https://
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
3752
|
-
const
|
|
3753
|
-
const
|
|
3754
|
-
|
|
3755
|
-
|
|
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:
|
|
3762
|
-
envPath:
|
|
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 =
|
|
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, {
|
|
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
|
-
|
|
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'] });
|