yingclaw 2.0.6 → 2.0.8
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 +36 -8
- package/lib/config.js +9 -4
- package/lib/desktop.js +76 -51
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -21,7 +21,7 @@ const { execSync, spawn, spawnSync } = require('child_process');
|
|
|
21
21
|
const pkg = require('../package.json');
|
|
22
22
|
const { buildMenuStatusLines, buildStatusView } = require('../lib/panel');
|
|
23
23
|
const { buildClaudeInstallCommand } = require('../lib/install');
|
|
24
|
-
const { clearClaudeDesktopConfig, openClaudeDesktop, writeClaudeDesktopConfig } = require('../lib/desktop');
|
|
24
|
+
const { clearClaudeDesktopConfig, isDesktopConfigured, openClaudeDesktop, writeClaudeDesktopConfig } = require('../lib/desktop');
|
|
25
25
|
|
|
26
26
|
const program = new Command();
|
|
27
27
|
|
|
@@ -118,6 +118,32 @@ function getSavedConfigHint() {
|
|
|
118
118
|
return '⚠ API Key 以明文存储在 ~/.clawai.json';
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
async function offerDesktopSync(chalk, ora, config) {
|
|
122
|
+
if (!isDesktopConfigured()) return;
|
|
123
|
+
const syncDesktop = await confirm({ message: 'Claude 桌面应用已配置,是否同步新模型?', default: true });
|
|
124
|
+
if (!syncDesktop) return;
|
|
125
|
+
const spinner = ora('同步 Claude 桌面应用配置...').start();
|
|
126
|
+
try {
|
|
127
|
+
writeClaudeDesktopConfig(config);
|
|
128
|
+
spinner.succeed(chalk.green('Claude 桌面应用配置已同步'));
|
|
129
|
+
} catch (e) {
|
|
130
|
+
spinner.fail(chalk.red(`桌面配置同步失败: ${e.message}`));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (process.platform === 'darwin') {
|
|
134
|
+
const shouldOpen = await confirm({ message: '是否重启 Claude 桌面应用使新配置生效?', default: true });
|
|
135
|
+
if (shouldOpen) {
|
|
136
|
+
const openSpinner = ora('正在重启 Claude 桌面应用...').start();
|
|
137
|
+
try {
|
|
138
|
+
await openClaudeDesktop();
|
|
139
|
+
openSpinner.succeed(chalk.green('Claude 桌面应用已重启'));
|
|
140
|
+
} catch (e) {
|
|
141
|
+
openSpinner.fail(chalk.red(`打开失败: ${e.message}`));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
121
147
|
async function promptModelFromChoices({ chalk, choices, message, backLabel = '↩ 返回上一步', allowManual = true }) {
|
|
122
148
|
const selected = await select({ loop: false,
|
|
123
149
|
message: chalk.cyan(message),
|
|
@@ -513,7 +539,6 @@ program
|
|
|
513
539
|
|
|
514
540
|
const spinner = ora('正在恢复 Claude Code 终端默认配置...').start();
|
|
515
541
|
const cleared = clearClaudeCodeEnv();
|
|
516
|
-
await new Promise(r => setTimeout(r, 300));
|
|
517
542
|
|
|
518
543
|
if (cleared.length === 0) {
|
|
519
544
|
spinner.warn(chalk.yellow('没有找到 Claude Code 终端环境变量,无需恢复'));
|
|
@@ -568,9 +593,9 @@ program
|
|
|
568
593
|
|
|
569
594
|
const spinner = ora('切换中...').start();
|
|
570
595
|
saveConfig(customConfig);
|
|
571
|
-
await new Promise(r => setTimeout(r, 300));
|
|
572
596
|
spinner.succeed(chalk.green(`API 连接已切换至 ${customConfig.providerName} · ${customConfig.model}`));
|
|
573
597
|
console.log(chalk.dim('如需让外部 claude 命令使用新模型,请运行 claw code。'));
|
|
598
|
+
await offerDesktopSync(chalk, ora, customConfig);
|
|
574
599
|
return;
|
|
575
600
|
}
|
|
576
601
|
|
|
@@ -616,9 +641,9 @@ program
|
|
|
616
641
|
const fastModel = resolveFastModel(provider, model);
|
|
617
642
|
const newConfig = { ...config, provider: providerKey, model, fastModel, baseUrl: provider.baseUrl, apiKey, availableModels };
|
|
618
643
|
saveConfig(newConfig);
|
|
619
|
-
await new Promise(r => setTimeout(r, 300));
|
|
620
644
|
spinner.succeed(chalk.green(`API 连接已切换至 ${provider.name} · ${model}`));
|
|
621
645
|
console.log(chalk.dim('如需让外部 claude 命令使用新模型,请运行 claw code。'));
|
|
646
|
+
await offerDesktopSync(chalk, ora, newConfig);
|
|
622
647
|
});
|
|
623
648
|
|
|
624
649
|
program
|
|
@@ -720,13 +745,16 @@ program
|
|
|
720
745
|
|
|
721
746
|
const spinner = ora('正在恢复 Claude 桌面应用默认配置...').start();
|
|
722
747
|
const result = clearClaudeDesktopConfig();
|
|
723
|
-
await new Promise(r => setTimeout(r, 300));
|
|
724
748
|
|
|
725
749
|
if (result.result === 'updated') {
|
|
726
750
|
spinner.succeed(chalk.green('Claude 桌面应用已恢复默认'));
|
|
751
|
+
const cleared = [
|
|
752
|
+
result.dataDir ? require('path').join(result.dataDir, 'configLibrary/') : null,
|
|
753
|
+
result.file,
|
|
754
|
+
].filter(Boolean);
|
|
727
755
|
console.log(boxen(
|
|
728
756
|
chalk.bold('已清除 Claude 桌面应用第三方推理配置:\n\n') +
|
|
729
|
-
chalk.cyan(' • ' +
|
|
757
|
+
cleared.map(f => chalk.cyan(' • ' + f)).join('\n') +
|
|
730
758
|
'\n\n' + chalk.dim('如 Claude Desktop 已打开,请完全退出后重新打开。'),
|
|
731
759
|
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
732
760
|
));
|
|
@@ -758,9 +786,9 @@ program
|
|
|
758
786
|
const cleared = resetConfig();
|
|
759
787
|
const desktopCleared = clearClaudeDesktopConfig();
|
|
760
788
|
if (desktopCleared.result === 'updated') {
|
|
761
|
-
cleared.push(desktopCleared.
|
|
789
|
+
if (desktopCleared.dataDir) cleared.push(require('path').join(desktopCleared.dataDir, 'configLibrary/'));
|
|
790
|
+
if (desktopCleared.file) cleared.push(desktopCleared.file);
|
|
762
791
|
}
|
|
763
|
-
await new Promise(r => setTimeout(r, 300));
|
|
764
792
|
|
|
765
793
|
if (cleared.length === 0) {
|
|
766
794
|
spinner.warn(chalk.yellow('没有找到任何配置,无需清除'));
|
package/lib/config.js
CHANGED
|
@@ -22,10 +22,10 @@ const PROVIDERS = {
|
|
|
22
22
|
name: 'DeepSeek',
|
|
23
23
|
baseUrl: 'https://api.deepseek.com/anthropic',
|
|
24
24
|
modelsUrl: 'https://api.deepseek.com/v1/models',
|
|
25
|
-
fastModel: 'deepseek-v4-flash',
|
|
25
|
+
fastModel: 'deepseek-v4-flash[1m]',
|
|
26
26
|
models: [
|
|
27
27
|
{ name: 'DeepSeek V4 Pro(强力)', value: 'deepseek-v4-pro[1m]' },
|
|
28
|
-
{ name: 'DeepSeek V4 Flash(快速)', value: 'deepseek-v4-flash' },
|
|
28
|
+
{ name: 'DeepSeek V4 Flash(快速)', value: 'deepseek-v4-flash[1m]' },
|
|
29
29
|
],
|
|
30
30
|
},
|
|
31
31
|
kimi: {
|
|
@@ -41,6 +41,7 @@ const PROVIDERS = {
|
|
|
41
41
|
name: '阿里云百炼 (Qwen)',
|
|
42
42
|
baseUrl: 'https://dashscope.aliyuncs.com/apps/anthropic',
|
|
43
43
|
modelsUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1/models',
|
|
44
|
+
fastModel: 'qwen3.5-plus',
|
|
44
45
|
models: [
|
|
45
46
|
{ name: 'Qwen3 Max(强力)', value: 'qwen3-max' },
|
|
46
47
|
{ name: 'Qwen3 Plus(均衡)', value: 'qwen3-plus' },
|
|
@@ -86,8 +87,12 @@ const PROVIDERS = {
|
|
|
86
87
|
function normalizeModelIds(providerKey, ids) {
|
|
87
88
|
if (providerKey !== 'deepseek') return ids;
|
|
88
89
|
|
|
89
|
-
const mapped = ids.map((id) =>
|
|
90
|
-
|
|
90
|
+
const mapped = ids.map((id) => {
|
|
91
|
+
if (id === 'deepseek-v4-pro') return 'deepseek-v4-pro[1m]';
|
|
92
|
+
if (id === 'deepseek-v4-flash') return 'deepseek-v4-flash[1m]';
|
|
93
|
+
return id;
|
|
94
|
+
});
|
|
95
|
+
const preferred = ['deepseek-v4-pro[1m]', 'deepseek-v4-flash[1m]'];
|
|
91
96
|
return [
|
|
92
97
|
...preferred.filter((id) => mapped.includes(id)),
|
|
93
98
|
...mapped.filter((id) => !preferred.includes(id)),
|
package/lib/desktop.js
CHANGED
|
@@ -16,7 +16,7 @@ const DESKTOP_GATEWAY_KEYS = [
|
|
|
16
16
|
'deploymentOrganizationUuid',
|
|
17
17
|
];
|
|
18
18
|
|
|
19
|
-
// Claude
|
|
19
|
+
// Claude Desktop 主进程从 Claude-3p/ 目录读取 deploymentMode 和 enterpriseConfig
|
|
20
20
|
function getClaudeDesktopDataDir(options = {}) {
|
|
21
21
|
const platform = options.platform || process.platform;
|
|
22
22
|
const homeDir = options.homeDir || os.homedir();
|
|
@@ -51,10 +51,6 @@ function readJsonFile(file) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function normalizeLegacyDesktopModelId(model) {
|
|
55
|
-
return model.replace(/\[\w+\]$/, '');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
54
|
function toDesktopModelId(model) {
|
|
59
55
|
return model.startsWith('claude-') ? model : `claude-${model}`;
|
|
60
56
|
}
|
|
@@ -63,7 +59,7 @@ function collectModels(config) {
|
|
|
63
59
|
const list = Array.isArray(config.availableModels) && config.availableModels.length > 0
|
|
64
60
|
? [config.model, config.fastModel, ...config.availableModels]
|
|
65
61
|
: [config.model, config.fastModel];
|
|
66
|
-
return [...new Set(list.filter(Boolean).map(
|
|
62
|
+
return [...new Set(list.filter(Boolean).map(toDesktopModelId))];
|
|
67
63
|
}
|
|
68
64
|
|
|
69
65
|
// 按官方 schema:所有值必须是字符串(包括布尔、数组都序列化)
|
|
@@ -117,71 +113,99 @@ function clearClaudeDesktopConfigLibrary(options = {}) {
|
|
|
117
113
|
return { result: 'updated', dir };
|
|
118
114
|
}
|
|
119
115
|
|
|
120
|
-
//
|
|
116
|
+
// 写入 Claude-3p/configLibrary/ 下的 enterprise config 条目(主进程从此处读取)
|
|
121
117
|
function writeClaudeDesktopConfig(config, options = {}) {
|
|
122
|
-
const
|
|
123
|
-
if (!
|
|
118
|
+
const dataDir = options.dataDir || getClaudeDesktopDataDir(options);
|
|
119
|
+
if (!dataDir) {
|
|
124
120
|
return { result: 'unsupported', file: null };
|
|
125
121
|
}
|
|
126
122
|
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
123
|
+
const configLibraryDir = path.join(dataDir, 'configLibrary');
|
|
124
|
+
const existingMetaFile = path.join(configLibraryDir, '_meta.json');
|
|
125
|
+
const existingMeta = readJsonFile(existingMetaFile);
|
|
126
|
+
const uuid = existingMeta.appliedId || options.uuid || crypto.randomUUID();
|
|
127
|
+
|
|
128
|
+
const models = collectModels(config);
|
|
129
|
+
const baseUrl = normalizeAnthropicBaseUrl(config.baseUrl);
|
|
130
|
+
if (!baseUrl.startsWith('https://')) {
|
|
131
|
+
throw new Error('Claude 桌面应用要求 Gateway Base URL 使用 HTTPS');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const entry = {
|
|
135
|
+
inferenceProvider: 'gateway',
|
|
136
|
+
inferenceGatewayBaseUrl: baseUrl,
|
|
137
|
+
inferenceGatewayApiKey: config.apiKey,
|
|
138
|
+
inferenceGatewayAuthScheme: options.authScheme || 'bearer',
|
|
139
|
+
inferenceModels: models,
|
|
140
|
+
disableDeploymentModeChooser: true,
|
|
141
|
+
deploymentOrganizationUuid: uuid,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const meta = {
|
|
145
|
+
appliedId: uuid,
|
|
146
|
+
entries: [{ id: uuid, name: 'yingclaw' }],
|
|
147
|
+
isManaged: false,
|
|
148
|
+
platform: process.platform,
|
|
145
149
|
};
|
|
146
150
|
|
|
151
|
+
fs.mkdirSync(configLibraryDir, { recursive: true });
|
|
152
|
+
|
|
153
|
+
const entryFile = path.join(configLibraryDir, `${uuid}.json`);
|
|
154
|
+
const beforeEntry = fs.existsSync(entryFile) ? fs.readFileSync(entryFile, 'utf8') : '';
|
|
155
|
+
const entryBody = JSON.stringify(entry, null, 2) + '\n';
|
|
156
|
+
fs.writeFileSync(entryFile, entryBody);
|
|
157
|
+
fs.writeFileSync(existingMetaFile, JSON.stringify(meta, null, 2) + '\n');
|
|
158
|
+
|
|
159
|
+
const file = options.configFile || path.join(dataDir, 'claude_desktop_config.json');
|
|
160
|
+
const current = readJsonFile(file);
|
|
161
|
+
const next = { ...current, deploymentMode: '3p' };
|
|
147
162
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
clearClaudeDesktopConfigLibrary({ ...options, dataDir: path.dirname(file) });
|
|
152
|
-
return { result: before === body ? 'unchanged' : 'updated', file };
|
|
163
|
+
fs.writeFileSync(file, JSON.stringify(next, null, 2) + '\n');
|
|
164
|
+
|
|
165
|
+
return { result: beforeEntry === entryBody ? 'unchanged' : 'updated', file };
|
|
153
166
|
}
|
|
154
167
|
|
|
155
168
|
function clearClaudeDesktopConfig(options = {}) {
|
|
156
|
-
|
|
169
|
+
// Derive dataDir only when configFile is not explicitly overridden (to avoid touching real system dirs in tests)
|
|
170
|
+
const dataDir = options.dataDir || (options.configFile ? null : getClaudeDesktopDataDir(options));
|
|
171
|
+
const file = options.configFile || (dataDir ? path.join(dataDir, 'claude_desktop_config.json') : null);
|
|
172
|
+
|
|
173
|
+
// Clear configLibrary regardless of whether the main config file exists
|
|
174
|
+
const libResult = clearClaudeDesktopConfigLibrary({ ...options, dataDir });
|
|
175
|
+
|
|
157
176
|
if (!file || !fs.existsSync(file)) {
|
|
158
|
-
return { result: 'missing', file };
|
|
177
|
+
return { result: libResult.result === 'updated' ? 'updated' : 'missing', file, dataDir };
|
|
159
178
|
}
|
|
160
179
|
|
|
161
180
|
const current = readJsonFile(file);
|
|
162
|
-
|
|
163
|
-
return { result: 'missing', file };
|
|
164
|
-
}
|
|
181
|
+
const next = { ...current };
|
|
165
182
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
183
|
+
// Remove legacy enterpriseConfig gateway keys
|
|
184
|
+
if (next.enterpriseConfig && typeof next.enterpriseConfig === 'object') {
|
|
185
|
+
const enterpriseConfig = { ...next.enterpriseConfig };
|
|
186
|
+
for (const key of DESKTOP_GATEWAY_KEYS) {
|
|
187
|
+
delete enterpriseConfig[key];
|
|
188
|
+
}
|
|
189
|
+
if (Object.keys(enterpriseConfig).length > 0) {
|
|
190
|
+
next.enterpriseConfig = enterpriseConfig;
|
|
191
|
+
} else {
|
|
192
|
+
delete next.enterpriseConfig;
|
|
193
|
+
}
|
|
169
194
|
}
|
|
170
195
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
next.enterpriseConfig = enterpriseConfig;
|
|
174
|
-
} else {
|
|
175
|
-
delete next.enterpriseConfig;
|
|
176
|
-
}
|
|
177
|
-
if (Object.keys(next).length === 1 && next.deploymentMode === '3p') {
|
|
178
|
-
delete next.deploymentMode;
|
|
179
|
-
}
|
|
196
|
+
// Always remove deploymentMode when clearing 3P config
|
|
197
|
+
delete next.deploymentMode;
|
|
180
198
|
|
|
181
199
|
fs.writeFileSync(file, JSON.stringify(next, null, 2) + '\n');
|
|
182
|
-
clearClaudeDesktopConfigLibrary({ ...options, dataDir: path.dirname(file) });
|
|
183
200
|
|
|
184
|
-
return { result: 'updated', file };
|
|
201
|
+
return { result: 'updated', file, dataDir };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function isDesktopConfigured(options = {}) {
|
|
205
|
+
const dir = options.dataDir || getClaudeDesktopDataDir(options);
|
|
206
|
+
if (!dir) return false;
|
|
207
|
+
const meta = readJsonFile(path.join(dir, 'configLibrary', '_meta.json'));
|
|
208
|
+
return typeof meta.appliedId === 'string' && meta.appliedId.length > 0;
|
|
185
209
|
}
|
|
186
210
|
|
|
187
211
|
function buildClaudeDesktopOpenCommands(platform = process.platform) {
|
|
@@ -234,6 +258,7 @@ module.exports = {
|
|
|
234
258
|
clearClaudeDesktopConfig,
|
|
235
259
|
getClaudeDesktopConfigPath,
|
|
236
260
|
getClaudeDesktopDataDir,
|
|
261
|
+
isDesktopConfigured,
|
|
237
262
|
openClaudeDesktop,
|
|
238
263
|
writeClaudeDesktopConfig,
|
|
239
264
|
CLAUDE_DESKTOP_LABEL,
|