yingclaw 1.7.12 → 1.8.2
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/cli.js +89 -28
- package/lib/desktop.js +27 -9
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -196,6 +196,7 @@ async function configureCustomProvider({ chalk, ora, existingConfig }) {
|
|
|
196
196
|
apiKey,
|
|
197
197
|
model,
|
|
198
198
|
fastModel,
|
|
199
|
+
availableModels: onlineResult?.models || (model ? [model, fastModel].filter(Boolean) : []),
|
|
199
200
|
};
|
|
200
201
|
}
|
|
201
202
|
|
|
@@ -283,7 +284,7 @@ async function runConfigFlow({ writeCodeEnv = false } = {}) {
|
|
|
283
284
|
if (!overwrite) return;
|
|
284
285
|
}
|
|
285
286
|
|
|
286
|
-
let providerKey, provider, apiKey, model, customConfig;
|
|
287
|
+
let providerKey, provider, apiKey, model, customConfig, availableModels;
|
|
287
288
|
let step = 'provider';
|
|
288
289
|
|
|
289
290
|
while (true) {
|
|
@@ -319,9 +320,11 @@ async function runConfigFlow({ writeCodeEnv = false } = {}) {
|
|
|
319
320
|
if (onlineModels && onlineModels.length > 0) {
|
|
320
321
|
fetchSpinner.succeed(chalk.green(`已获取 ${onlineModels.length} 个可用模型`));
|
|
321
322
|
modelChoices = onlineModels.map(id => ({ name: id, value: id }));
|
|
323
|
+
availableModels = onlineModels;
|
|
322
324
|
} else {
|
|
323
325
|
fetchSpinner.warn(chalk.yellow('无法获取在线列表,使用内置默认列表'));
|
|
324
326
|
modelChoices = provider.models;
|
|
327
|
+
availableModels = provider.models.map(m => m.value);
|
|
325
328
|
}
|
|
326
329
|
|
|
327
330
|
const m = await select({ loop: false,
|
|
@@ -342,7 +345,7 @@ async function runConfigFlow({ writeCodeEnv = false } = {}) {
|
|
|
342
345
|
let cfg;
|
|
343
346
|
try {
|
|
344
347
|
const fastModel = customConfig?.fastModel || resolveFastModel(provider, model);
|
|
345
|
-
cfg = customConfig || { provider: providerKey, model, fastModel, apiKey, baseUrl: provider.baseUrl };
|
|
348
|
+
cfg = customConfig || { provider: providerKey, model, fastModel, apiKey, baseUrl: provider.baseUrl, availableModels };
|
|
346
349
|
saveConfig(cfg);
|
|
347
350
|
if (writeCodeEnv) {
|
|
348
351
|
({ file } = writeEnvToZshrc(cfg.baseUrl, cfg.apiKey, cfg.model, cfg.fastModel));
|
|
@@ -589,12 +592,15 @@ program
|
|
|
589
592
|
const fetchSpinner = ora('正在获取可用模型...').start();
|
|
590
593
|
const onlineModels = await fetchModels(providerKey, apiKey);
|
|
591
594
|
let modelChoices;
|
|
595
|
+
let availableModels;
|
|
592
596
|
if (onlineModels && onlineModels.length > 0) {
|
|
593
597
|
fetchSpinner.succeed(chalk.green(`已获取 ${onlineModels.length} 个可用模型`));
|
|
594
598
|
modelChoices = onlineModels.map(id => ({ name: id, value: id }));
|
|
599
|
+
availableModels = onlineModels;
|
|
595
600
|
} else {
|
|
596
601
|
fetchSpinner.warn(chalk.yellow('无法获取在线列表,使用内置默认列表'));
|
|
597
602
|
modelChoices = provider.models;
|
|
603
|
+
availableModels = provider.models.map(m => m.value);
|
|
598
604
|
}
|
|
599
605
|
|
|
600
606
|
const model = await select({ loop: false,
|
|
@@ -608,7 +614,7 @@ program
|
|
|
608
614
|
|
|
609
615
|
const spinner = ora('切换中...').start();
|
|
610
616
|
const fastModel = resolveFastModel(provider, model);
|
|
611
|
-
const newConfig = { ...config, provider: providerKey, model, fastModel, baseUrl: provider.baseUrl, apiKey };
|
|
617
|
+
const newConfig = { ...config, provider: providerKey, model, fastModel, baseUrl: provider.baseUrl, apiKey, availableModels };
|
|
612
618
|
saveConfig(newConfig);
|
|
613
619
|
await new Promise(r => setTimeout(r, 300));
|
|
614
620
|
spinner.succeed(chalk.green(`API 连接已切换至 ${provider.name} · ${model}`));
|
|
@@ -676,8 +682,8 @@ program
|
|
|
676
682
|
if (shouldOpen) {
|
|
677
683
|
const openSpinner = ora('正在打开 Claude 桌面应用...').start();
|
|
678
684
|
try {
|
|
679
|
-
openClaudeDesktop();
|
|
680
|
-
openSpinner.succeed(chalk.green('Claude
|
|
685
|
+
await openClaudeDesktop();
|
|
686
|
+
openSpinner.succeed(chalk.green('Claude 桌面应用已重新打开(旧实例已退出,新配置生效)'));
|
|
681
687
|
} catch (e) {
|
|
682
688
|
openSpinner.fail(chalk.red(`打开失败: ${e.message}`));
|
|
683
689
|
console.log(chalk.dim(getDesktopOpenHint()));
|
|
@@ -802,62 +808,113 @@ async function renderStatusBar(apiStatus) {
|
|
|
802
808
|
return config ? ` ${cfgPart}` : ` ${claudeIcon} ${claudeText} ${cfgPart}`;
|
|
803
809
|
}
|
|
804
810
|
|
|
811
|
+
// 缓存上次校验的 config 哈希和结果,避免每次回菜单都重检
|
|
812
|
+
let lastCheckedHash = null;
|
|
813
|
+
let lastCheckResult; // undefined / true / false / null
|
|
814
|
+
|
|
815
|
+
function configHash(cfg) {
|
|
816
|
+
if (!cfg) return null;
|
|
817
|
+
return JSON.stringify({ p: cfg.provider, b: cfg.baseUrl, k: cfg.apiKey, m: cfg.model });
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
async function maybeCheckApi(config, forceRecheck) {
|
|
821
|
+
const hash = configHash(config);
|
|
822
|
+
if (!forceRecheck && hash === lastCheckedHash && lastCheckResult !== undefined) {
|
|
823
|
+
return lastCheckResult;
|
|
824
|
+
}
|
|
825
|
+
const result = await validateKey(config);
|
|
826
|
+
lastCheckedHash = hash;
|
|
827
|
+
lastCheckResult = result;
|
|
828
|
+
return result;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const ADVANCED_DISABLED_HINT = '需先配置 API 连接';
|
|
832
|
+
|
|
833
|
+
async function runAdvancedMenu(chalk, hasConfig) {
|
|
834
|
+
const action = await select({ loop: false,
|
|
835
|
+
message: chalk.cyan('高级选项'),
|
|
836
|
+
choices: [
|
|
837
|
+
{ name: '🔁 重新检测 API', value: 'recheck', disabled: !hasConfig && ADVANCED_DISABLED_HINT },
|
|
838
|
+
{ name: '↩️ 恢复 Claude Code 终端默认', value: 'code-reset' },
|
|
839
|
+
{ name: '↩️ 恢复 Claude 桌面默认', value: 'desktop-reset' },
|
|
840
|
+
{ name: '🗑 清除所有 yingclaw 配置', value: 'reset' },
|
|
841
|
+
{ name: chalk.dim('↩ 返回主菜单'), value: '__BACK__' },
|
|
842
|
+
],
|
|
843
|
+
});
|
|
844
|
+
return action;
|
|
845
|
+
}
|
|
846
|
+
|
|
805
847
|
async function runMenu() {
|
|
806
848
|
const chalk = (await import('chalk')).default;
|
|
807
849
|
const ora = (await import('ora')).default;
|
|
808
850
|
|
|
851
|
+
let forceRecheck = false;
|
|
852
|
+
|
|
809
853
|
while (true) {
|
|
810
854
|
console.clear();
|
|
811
855
|
console.log(await getBanner());
|
|
812
856
|
|
|
813
857
|
const config = loadConfig();
|
|
814
|
-
let apiStatus; // undefined = skipped, true/false/null = checked
|
|
815
858
|
const configProblem = config ? getConfigValidationMessage(config) : null;
|
|
859
|
+
let apiStatus;
|
|
860
|
+
|
|
816
861
|
if (config && !configProblem) {
|
|
817
|
-
const
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
862
|
+
const hash = configHash(config);
|
|
863
|
+
if (forceRecheck || hash !== lastCheckedHash || lastCheckResult === undefined) {
|
|
864
|
+
const spinner = ora('正在检测 API...').start();
|
|
865
|
+
apiStatus = await maybeCheckApi(config, true);
|
|
866
|
+
if (apiStatus === true) spinner.succeed('API 连接正常');
|
|
867
|
+
else if (apiStatus === false) spinner.fail('API Key 无效或已过期');
|
|
868
|
+
else spinner.warn('网络异常,无法连接 API');
|
|
869
|
+
} else {
|
|
870
|
+
apiStatus = lastCheckResult;
|
|
871
|
+
}
|
|
872
|
+
forceRecheck = false;
|
|
822
873
|
}
|
|
823
874
|
|
|
824
875
|
if (configProblem) {
|
|
825
876
|
console.log(chalk.red(` ● 配置无效:${configProblem}`));
|
|
826
|
-
console.log(chalk.dim('
|
|
877
|
+
console.log(chalk.dim(' 请先选择"配置 API 连接"重新配置'));
|
|
827
878
|
} else {
|
|
828
879
|
console.log(await renderStatusBar(apiStatus));
|
|
829
880
|
}
|
|
830
881
|
console.log();
|
|
831
882
|
|
|
883
|
+
const disabledHint = (!config || configProblem) && '需先配置 API 连接';
|
|
884
|
+
|
|
832
885
|
const action = await select({ loop: false,
|
|
833
886
|
message: chalk.cyan('选择操作'),
|
|
834
887
|
choices: [
|
|
835
|
-
{ name: '🤖 启动 Claude Code', value: 'launch', disabled:
|
|
888
|
+
{ name: '🤖 启动 Claude Code', value: 'launch', disabled: disabledHint },
|
|
836
889
|
{ name: '📦 安装 Claude Code', value: 'install' },
|
|
837
890
|
{ name: config ? '🔑 重新配置 API 连接' : '🔑 配置 API 连接', value: 'config' },
|
|
838
|
-
{ name: '
|
|
839
|
-
{ name: '
|
|
840
|
-
{ name: '🖥 接入 Claude 桌面应用', value: 'desktop', disabled:
|
|
841
|
-
{ name: '↩️ 恢复 Claude Code 终端默认', value: 'code-reset' },
|
|
842
|
-
{ name: '↩️ 恢复 Claude 桌面默认', value: 'desktop-reset' },
|
|
891
|
+
{ name: '🔄 切换厂商或模型', value: 'switch', disabled: disabledHint },
|
|
892
|
+
{ name: '💻 接入 Claude Code 终端', value: 'code', disabled: disabledHint },
|
|
893
|
+
{ name: '🖥 接入 Claude 桌面应用', value: 'desktop', disabled: disabledHint },
|
|
843
894
|
{ name: '📊 查看当前配置', value: 'status', disabled: !config && '需先配置 API 连接' },
|
|
844
|
-
{ name: '
|
|
845
|
-
{ name: '🗑 清除所有 yingclaw 配置', value: 'reset' },
|
|
895
|
+
{ name: '🛠 高级 ›', value: 'advanced' },
|
|
846
896
|
{ name: '退出', value: 'exit' },
|
|
847
897
|
],
|
|
848
898
|
});
|
|
849
899
|
|
|
850
900
|
if (action === 'exit') return;
|
|
851
901
|
|
|
852
|
-
|
|
853
|
-
|
|
902
|
+
let resolvedAction = action;
|
|
903
|
+
if (action === 'advanced') {
|
|
904
|
+
const adv = await runAdvancedMenu(chalk, !!config && !configProblem);
|
|
905
|
+
if (adv === '__BACK__') continue;
|
|
906
|
+
resolvedAction = adv;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
if (resolvedAction === 'recheck') {
|
|
910
|
+
lastCheckResult = undefined;
|
|
911
|
+
forceRecheck = true;
|
|
854
912
|
continue;
|
|
855
913
|
}
|
|
856
914
|
|
|
857
|
-
if (
|
|
915
|
+
if (resolvedAction === 'launch') {
|
|
858
916
|
const cfg = loadConfig();
|
|
859
917
|
if (!cfg || getConfigValidationMessage(cfg)) continue;
|
|
860
|
-
// 启动 claude 后等它退出,然后回菜单
|
|
861
918
|
await new Promise((resolve) => {
|
|
862
919
|
const child = spawn('claude', [], {
|
|
863
920
|
stdio: 'inherit',
|
|
@@ -875,7 +932,6 @@ async function runMenu() {
|
|
|
875
932
|
const cmdMap = {
|
|
876
933
|
install: 'install-claude',
|
|
877
934
|
config: 'config',
|
|
878
|
-
setup: 'setup',
|
|
879
935
|
code: 'code',
|
|
880
936
|
'code-reset': 'code-reset',
|
|
881
937
|
switch: 'switch',
|
|
@@ -885,14 +941,19 @@ async function runMenu() {
|
|
|
885
941
|
reset: 'reset',
|
|
886
942
|
};
|
|
887
943
|
|
|
888
|
-
//
|
|
944
|
+
// 执行子命令(用 spawn 隔离,避免 commander 对 program 的副作用)
|
|
889
945
|
await new Promise((resolve) => {
|
|
890
|
-
const child = spawn(process.execPath, [__filename, cmdMap[
|
|
946
|
+
const child = spawn(process.execPath, [__filename, cmdMap[resolvedAction]], { stdio: 'inherit' });
|
|
891
947
|
child.on('exit', resolve);
|
|
892
948
|
child.on('error', resolve);
|
|
893
949
|
});
|
|
894
950
|
|
|
895
|
-
//
|
|
951
|
+
// 改 config 的命令需要刷新缓存
|
|
952
|
+
if (['config', 'switch', 'reset', 'code-reset'].includes(resolvedAction)) {
|
|
953
|
+
lastCheckResult = undefined;
|
|
954
|
+
lastCheckedHash = null;
|
|
955
|
+
}
|
|
956
|
+
|
|
896
957
|
console.log();
|
|
897
958
|
const next = await select({ loop: false,
|
|
898
959
|
message: chalk.cyan('下一步'),
|
package/lib/desktop.js
CHANGED
|
@@ -40,12 +40,17 @@ function readJsonFile(file) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
function
|
|
44
|
-
|
|
43
|
+
function collectModels(config) {
|
|
44
|
+
// 优先用 config 中保存的全量模型列表(setup/switch 时联网拉取的)
|
|
45
|
+
// 退化:主模型 + 快速模型
|
|
46
|
+
const list = Array.isArray(config.availableModels) && config.availableModels.length > 0
|
|
47
|
+
? [config.model, config.fastModel, ...config.availableModels]
|
|
48
|
+
: [config.model, config.fastModel];
|
|
49
|
+
return [...new Set(list.filter(Boolean))];
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
function buildClaudeDesktopEnterpriseConfig(config, options = {}) {
|
|
48
|
-
const models =
|
|
53
|
+
const models = collectModels(config);
|
|
49
54
|
const baseUrl = normalizeAnthropicBaseUrl(config.baseUrl);
|
|
50
55
|
if (!baseUrl.startsWith('https://')) {
|
|
51
56
|
throw new Error('Claude 桌面应用要求 Gateway Base URL 使用 HTTPS');
|
|
@@ -121,24 +126,37 @@ function clearClaudeDesktopConfig(options = {}) {
|
|
|
121
126
|
|
|
122
127
|
function buildClaudeDesktopOpenCommands(platform = process.platform) {
|
|
123
128
|
if (platform !== 'darwin') return null;
|
|
129
|
+
// 先 graceful quit,再 force kill 兜底,最后 open
|
|
124
130
|
return [
|
|
131
|
+
{ command: 'osascript', args: ['-e', 'tell application "Claude" to quit'], optional: true, waitAfter: 600 },
|
|
132
|
+
{ command: 'pkill', args: ['-x', 'Claude'], optional: true, waitAfter: 400 },
|
|
125
133
|
{ command: 'open', args: ['-a', 'Claude'] },
|
|
126
134
|
];
|
|
127
135
|
}
|
|
128
136
|
|
|
129
|
-
function
|
|
137
|
+
async function sleep(ms) {
|
|
138
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function openClaudeDesktop(options = {}) {
|
|
130
142
|
const platform = options.platform || process.platform;
|
|
131
143
|
const commands = buildClaudeDesktopOpenCommands(platform);
|
|
132
144
|
if (!commands) return { result: 'unsupported' };
|
|
133
145
|
|
|
134
146
|
const runner = options.runner || spawnSync;
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
147
|
+
const isMocked = options.runner !== undefined;
|
|
148
|
+
|
|
149
|
+
for (const { command, args, optional, waitAfter } of commands) {
|
|
150
|
+
const result = runner(command, args, { stdio: 'ignore', windowsHide: true });
|
|
151
|
+
if (result.status !== 0 && !optional) {
|
|
152
|
+
throw new Error('Claude 桌面应用打开失败');
|
|
153
|
+
}
|
|
154
|
+
if (waitAfter && !isMocked) {
|
|
155
|
+
await sleep(waitAfter);
|
|
156
|
+
}
|
|
139
157
|
}
|
|
140
158
|
|
|
141
|
-
return { result: '
|
|
159
|
+
return { result: 'reopened' };
|
|
142
160
|
}
|
|
143
161
|
|
|
144
162
|
module.exports = {
|