yingclaw 1.7.12 → 1.8.1
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 +80 -25
- package/lib/desktop.js +21 -6
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -676,8 +676,8 @@ program
|
|
|
676
676
|
if (shouldOpen) {
|
|
677
677
|
const openSpinner = ora('正在打开 Claude 桌面应用...').start();
|
|
678
678
|
try {
|
|
679
|
-
openClaudeDesktop();
|
|
680
|
-
openSpinner.succeed(chalk.green('Claude
|
|
679
|
+
await openClaudeDesktop();
|
|
680
|
+
openSpinner.succeed(chalk.green('Claude 桌面应用已重新打开(旧实例已退出,新配置生效)'));
|
|
681
681
|
} catch (e) {
|
|
682
682
|
openSpinner.fail(chalk.red(`打开失败: ${e.message}`));
|
|
683
683
|
console.log(chalk.dim(getDesktopOpenHint()));
|
|
@@ -802,62 +802,113 @@ async function renderStatusBar(apiStatus) {
|
|
|
802
802
|
return config ? ` ${cfgPart}` : ` ${claudeIcon} ${claudeText} ${cfgPart}`;
|
|
803
803
|
}
|
|
804
804
|
|
|
805
|
+
// 缓存上次校验的 config 哈希和结果,避免每次回菜单都重检
|
|
806
|
+
let lastCheckedHash = null;
|
|
807
|
+
let lastCheckResult; // undefined / true / false / null
|
|
808
|
+
|
|
809
|
+
function configHash(cfg) {
|
|
810
|
+
if (!cfg) return null;
|
|
811
|
+
return JSON.stringify({ p: cfg.provider, b: cfg.baseUrl, k: cfg.apiKey, m: cfg.model });
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
async function maybeCheckApi(config, forceRecheck) {
|
|
815
|
+
const hash = configHash(config);
|
|
816
|
+
if (!forceRecheck && hash === lastCheckedHash && lastCheckResult !== undefined) {
|
|
817
|
+
return lastCheckResult;
|
|
818
|
+
}
|
|
819
|
+
const result = await validateKey(config);
|
|
820
|
+
lastCheckedHash = hash;
|
|
821
|
+
lastCheckResult = result;
|
|
822
|
+
return result;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const ADVANCED_DISABLED_HINT = '需先配置 API 连接';
|
|
826
|
+
|
|
827
|
+
async function runAdvancedMenu(chalk, hasConfig) {
|
|
828
|
+
const action = await select({ loop: false,
|
|
829
|
+
message: chalk.cyan('高级选项'),
|
|
830
|
+
choices: [
|
|
831
|
+
{ name: '🔁 重新检测 API', value: 'recheck', disabled: !hasConfig && ADVANCED_DISABLED_HINT },
|
|
832
|
+
{ name: '↩️ 恢复 Claude Code 终端默认', value: 'code-reset' },
|
|
833
|
+
{ name: '↩️ 恢复 Claude 桌面默认', value: 'desktop-reset' },
|
|
834
|
+
{ name: '🗑 清除所有 yingclaw 配置', value: 'reset' },
|
|
835
|
+
{ name: chalk.dim('↩ 返回主菜单'), value: '__BACK__' },
|
|
836
|
+
],
|
|
837
|
+
});
|
|
838
|
+
return action;
|
|
839
|
+
}
|
|
840
|
+
|
|
805
841
|
async function runMenu() {
|
|
806
842
|
const chalk = (await import('chalk')).default;
|
|
807
843
|
const ora = (await import('ora')).default;
|
|
808
844
|
|
|
845
|
+
let forceRecheck = false;
|
|
846
|
+
|
|
809
847
|
while (true) {
|
|
810
848
|
console.clear();
|
|
811
849
|
console.log(await getBanner());
|
|
812
850
|
|
|
813
851
|
const config = loadConfig();
|
|
814
|
-
let apiStatus; // undefined = skipped, true/false/null = checked
|
|
815
852
|
const configProblem = config ? getConfigValidationMessage(config) : null;
|
|
853
|
+
let apiStatus;
|
|
854
|
+
|
|
816
855
|
if (config && !configProblem) {
|
|
817
|
-
const
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
856
|
+
const hash = configHash(config);
|
|
857
|
+
if (forceRecheck || hash !== lastCheckedHash || lastCheckResult === undefined) {
|
|
858
|
+
const spinner = ora('正在检测 API...').start();
|
|
859
|
+
apiStatus = await maybeCheckApi(config, true);
|
|
860
|
+
if (apiStatus === true) spinner.succeed('API 连接正常');
|
|
861
|
+
else if (apiStatus === false) spinner.fail('API Key 无效或已过期');
|
|
862
|
+
else spinner.warn('网络异常,无法连接 API');
|
|
863
|
+
} else {
|
|
864
|
+
apiStatus = lastCheckResult;
|
|
865
|
+
}
|
|
866
|
+
forceRecheck = false;
|
|
822
867
|
}
|
|
823
868
|
|
|
824
869
|
if (configProblem) {
|
|
825
870
|
console.log(chalk.red(` ● 配置无效:${configProblem}`));
|
|
826
|
-
console.log(chalk.dim('
|
|
871
|
+
console.log(chalk.dim(' 请先选择"配置 API 连接"重新配置'));
|
|
827
872
|
} else {
|
|
828
873
|
console.log(await renderStatusBar(apiStatus));
|
|
829
874
|
}
|
|
830
875
|
console.log();
|
|
831
876
|
|
|
877
|
+
const disabledHint = (!config || configProblem) && '需先配置 API 连接';
|
|
878
|
+
|
|
832
879
|
const action = await select({ loop: false,
|
|
833
880
|
message: chalk.cyan('选择操作'),
|
|
834
881
|
choices: [
|
|
835
|
-
{ name: '🤖 启动 Claude Code', value: 'launch', disabled:
|
|
882
|
+
{ name: '🤖 启动 Claude Code', value: 'launch', disabled: disabledHint },
|
|
836
883
|
{ name: '📦 安装 Claude Code', value: 'install' },
|
|
837
884
|
{ 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' },
|
|
885
|
+
{ name: '🔄 切换厂商或模型', value: 'switch', disabled: disabledHint },
|
|
886
|
+
{ name: '💻 接入 Claude Code 终端', value: 'code', disabled: disabledHint },
|
|
887
|
+
{ name: '🖥 接入 Claude 桌面应用', value: 'desktop', disabled: disabledHint },
|
|
843
888
|
{ name: '📊 查看当前配置', value: 'status', disabled: !config && '需先配置 API 连接' },
|
|
844
|
-
{ name: '
|
|
845
|
-
{ name: '🗑 清除所有 yingclaw 配置', value: 'reset' },
|
|
889
|
+
{ name: '🛠 高级 ›', value: 'advanced' },
|
|
846
890
|
{ name: '退出', value: 'exit' },
|
|
847
891
|
],
|
|
848
892
|
});
|
|
849
893
|
|
|
850
894
|
if (action === 'exit') return;
|
|
851
895
|
|
|
852
|
-
|
|
853
|
-
|
|
896
|
+
let resolvedAction = action;
|
|
897
|
+
if (action === 'advanced') {
|
|
898
|
+
const adv = await runAdvancedMenu(chalk, !!config && !configProblem);
|
|
899
|
+
if (adv === '__BACK__') continue;
|
|
900
|
+
resolvedAction = adv;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
if (resolvedAction === 'recheck') {
|
|
904
|
+
lastCheckResult = undefined;
|
|
905
|
+
forceRecheck = true;
|
|
854
906
|
continue;
|
|
855
907
|
}
|
|
856
908
|
|
|
857
|
-
if (
|
|
909
|
+
if (resolvedAction === 'launch') {
|
|
858
910
|
const cfg = loadConfig();
|
|
859
911
|
if (!cfg || getConfigValidationMessage(cfg)) continue;
|
|
860
|
-
// 启动 claude 后等它退出,然后回菜单
|
|
861
912
|
await new Promise((resolve) => {
|
|
862
913
|
const child = spawn('claude', [], {
|
|
863
914
|
stdio: 'inherit',
|
|
@@ -875,7 +926,6 @@ async function runMenu() {
|
|
|
875
926
|
const cmdMap = {
|
|
876
927
|
install: 'install-claude',
|
|
877
928
|
config: 'config',
|
|
878
|
-
setup: 'setup',
|
|
879
929
|
code: 'code',
|
|
880
930
|
'code-reset': 'code-reset',
|
|
881
931
|
switch: 'switch',
|
|
@@ -885,14 +935,19 @@ async function runMenu() {
|
|
|
885
935
|
reset: 'reset',
|
|
886
936
|
};
|
|
887
937
|
|
|
888
|
-
//
|
|
938
|
+
// 执行子命令(用 spawn 隔离,避免 commander 对 program 的副作用)
|
|
889
939
|
await new Promise((resolve) => {
|
|
890
|
-
const child = spawn(process.execPath, [__filename, cmdMap[
|
|
940
|
+
const child = spawn(process.execPath, [__filename, cmdMap[resolvedAction]], { stdio: 'inherit' });
|
|
891
941
|
child.on('exit', resolve);
|
|
892
942
|
child.on('error', resolve);
|
|
893
943
|
});
|
|
894
944
|
|
|
895
|
-
//
|
|
945
|
+
// 改 config 的命令需要刷新缓存
|
|
946
|
+
if (['config', 'switch', 'reset', 'code-reset'].includes(resolvedAction)) {
|
|
947
|
+
lastCheckResult = undefined;
|
|
948
|
+
lastCheckedHash = null;
|
|
949
|
+
}
|
|
950
|
+
|
|
896
951
|
console.log();
|
|
897
952
|
const next = await select({ loop: false,
|
|
898
953
|
message: chalk.cyan('下一步'),
|
package/lib/desktop.js
CHANGED
|
@@ -121,24 +121,39 @@ function clearClaudeDesktopConfig(options = {}) {
|
|
|
121
121
|
|
|
122
122
|
function buildClaudeDesktopOpenCommands(platform = process.platform) {
|
|
123
123
|
if (platform !== 'darwin') return null;
|
|
124
|
+
// 先 quit 再 open:避免已运行的实例缓存了旧配置
|
|
124
125
|
return [
|
|
126
|
+
{ command: 'osascript', args: ['-e', 'tell application "Claude" to quit'], optional: true },
|
|
125
127
|
{ command: 'open', args: ['-a', 'Claude'] },
|
|
126
128
|
];
|
|
127
129
|
}
|
|
128
130
|
|
|
129
|
-
function
|
|
131
|
+
async function sleep(ms) {
|
|
132
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function openClaudeDesktop(options = {}) {
|
|
130
136
|
const platform = options.platform || process.platform;
|
|
131
137
|
const commands = buildClaudeDesktopOpenCommands(platform);
|
|
132
138
|
if (!commands) return { result: 'unsupported' };
|
|
133
139
|
|
|
134
140
|
const runner = options.runner || spawnSync;
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
const waitMs = options.waitMs ?? 700;
|
|
142
|
+
|
|
143
|
+
for (let i = 0; i < commands.length; i++) {
|
|
144
|
+
const { command, args, optional } = commands[i];
|
|
145
|
+
const result = runner(command, args, { stdio: 'ignore', windowsHide: true });
|
|
146
|
+
// optional 命令失败(比如 Claude 没在运行)不报错
|
|
147
|
+
if (result.status !== 0 && !optional) {
|
|
148
|
+
throw new Error('Claude 桌面应用打开失败');
|
|
149
|
+
}
|
|
150
|
+
// quit 后等一下再 open,给系统清理资源的时间
|
|
151
|
+
if (i === 0 && commands.length > 1 && options.runner === undefined) {
|
|
152
|
+
await sleep(waitMs);
|
|
153
|
+
}
|
|
139
154
|
}
|
|
140
155
|
|
|
141
|
-
return { result: '
|
|
156
|
+
return { result: 'reopened' };
|
|
142
157
|
}
|
|
143
158
|
|
|
144
159
|
module.exports = {
|