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 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 spinner = ora('正在检测 API 是否通畅...').start();
818
- apiStatus = await validateKey(config);
819
- if (apiStatus === true) spinner.succeed('API 连接正常');
820
- else if (apiStatus === false) spinner.fail('API Key 无效或已过期');
821
- else spinner.warn('网络异常,无法连接 API');
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(' 请先选择“配置 API 连接”重新配置'));
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: (!config || configProblem) && '需先配置 API 连接' },
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: '💻 接入 Claude Code 终端', value: 'code', disabled: (!config || configProblem) && '需先配置 API 连接' },
839
- { name: '🔄 切换厂商或模型', value: 'switch', disabled: (!config || configProblem) && '需先配置 API 连接' },
840
- { name: '🖥 接入 Claude 桌面应用', value: 'desktop', disabled: (!config || configProblem) && '需先配置 API 连接' },
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: '🔁 重新检测 API', value: 'recheck', disabled: (!config || configProblem) && '需先配置 API 连接' },
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
- if (action === 'recheck') {
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 (action === 'launch') {
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[action]], { stdio: 'inherit' });
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 openClaudeDesktop(options = {}) {
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 [openCommand] = commands;
136
- const result = runner(openCommand.command, openCommand.args, { stdio: 'ignore', windowsHide: true });
137
- if (result.status !== 0) {
138
- throw new Error('Claude 桌面应用打开失败');
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: 'opened' };
156
+ return { result: 'reopened' };
142
157
  }
143
158
 
144
159
  module.exports = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yingclaw",
3
- "version": "1.7.12",
3
+ "version": "1.8.1",
4
4
  "description": "Claude Code × 国产大模型一键接入:DeepSeek、Kimi、Qwen、MiniMax、GLM、MiMo",
5
5
  "main": "index.js",
6
6
  "bin": {