@kkmila/cpc 1.0.0
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/README.md +108 -0
- package/bin/cpc.mjs +16 -0
- package/package.json +51 -0
- package/registry.yaml +43 -0
- package/src/commands/provider.mjs +1576 -0
- package/src/lib/agents.mjs +36 -0
- package/src/lib/fs.mjs +75 -0
- package/src/lib/repo.mjs +13 -0
|
@@ -0,0 +1,1576 @@
|
|
|
1
|
+
import { select, checkbox, confirm, input, number } from "@inquirer/prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
4
|
+
import { resolve, join } from "node:path";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import yaml from "js-yaml";
|
|
7
|
+
import { loadRegistry, getAgents, resolveAgentPaths } from "../lib/agents.mjs";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get the config directory path (~/.config/cpc)
|
|
11
|
+
*/
|
|
12
|
+
function getConfigDir() {
|
|
13
|
+
return resolve(homedir(), ".config", "cpc");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Load providers configuration from ~/.config/cpc/providers.json
|
|
18
|
+
*/
|
|
19
|
+
function loadProviders() {
|
|
20
|
+
const configDir = getConfigDir();
|
|
21
|
+
const providersPath = resolve(configDir, "providers.json");
|
|
22
|
+
if (!existsSync(providersPath)) {
|
|
23
|
+
return { version: "2.0", providers: {} };
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const content = readFileSync(providersPath, "utf8");
|
|
27
|
+
const data = JSON.parse(content);
|
|
28
|
+
// 兼容旧版本
|
|
29
|
+
if (!data.version) {
|
|
30
|
+
data.version = "2.0";
|
|
31
|
+
}
|
|
32
|
+
return data;
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.error(chalk.red(`Error loading providers.json: ${err.message}`));
|
|
35
|
+
return { version: "2.0", providers: {} };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Save providers configuration to ~/.config/cpc/providers.json
|
|
41
|
+
*/
|
|
42
|
+
function saveProviders(data) {
|
|
43
|
+
const configDir = getConfigDir();
|
|
44
|
+
if (!existsSync(configDir)) {
|
|
45
|
+
mkdirSync(configDir, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
const providersPath = resolve(configDir, "providers.json");
|
|
48
|
+
writeFileSync(providersPath, JSON.stringify(data, null, 2));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Fetch models from OpenAI-compatible API
|
|
53
|
+
*/
|
|
54
|
+
async function fetchModelsFromAPI(baseUrl, apiKey, apiType) {
|
|
55
|
+
try {
|
|
56
|
+
let url = baseUrl;
|
|
57
|
+
|
|
58
|
+
// 根据API类型构造正确的URL
|
|
59
|
+
if (apiType === "openai-completions" || apiType === "responses") {
|
|
60
|
+
// 确保URL以/v1结尾
|
|
61
|
+
if (!url.endsWith("/v1")) {
|
|
62
|
+
url = url.replace(/\/+$/, "") + "/v1";
|
|
63
|
+
}
|
|
64
|
+
url += "/models";
|
|
65
|
+
} else if (apiType === "anthropic-messages") {
|
|
66
|
+
// Anthropic没有标准的模型列表端点
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
error: "Anthropic API does not support model listing",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const response = await fetch(url, {
|
|
74
|
+
method: "GET",
|
|
75
|
+
headers: {
|
|
76
|
+
Authorization: `Bearer ${apiKey}`,
|
|
77
|
+
"Content-Type": "application/json",
|
|
78
|
+
},
|
|
79
|
+
signal: AbortSignal.timeout(10000), // 10秒超时
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
error: `HTTP ${response.status}: ${response.statusText}`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const data = await response.json();
|
|
90
|
+
|
|
91
|
+
if (data.data && Array.isArray(data.data)) {
|
|
92
|
+
// 标准OpenAI格式
|
|
93
|
+
const models = data.data.map((m) => ({
|
|
94
|
+
id: m.id,
|
|
95
|
+
name: m.name || m.id,
|
|
96
|
+
contextWindow: m.context_window || null,
|
|
97
|
+
maxTokens: m.max_tokens || null,
|
|
98
|
+
reasoning: m.reasoning || false,
|
|
99
|
+
input: m.input || ["text"],
|
|
100
|
+
}));
|
|
101
|
+
return { success: true, models };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { success: false, error: "Invalid response format" };
|
|
105
|
+
} catch (err) {
|
|
106
|
+
if (err.name === "AbortError" || err.name === "TimeoutError") {
|
|
107
|
+
return { success: false, error: "Connection timeout" };
|
|
108
|
+
}
|
|
109
|
+
return { success: false, error: err.message };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 常见模型的默认配置
|
|
115
|
+
*/
|
|
116
|
+
const MODEL_DEFAULTS = {
|
|
117
|
+
// OpenAI
|
|
118
|
+
"gpt-4": { contextWindow: 8192, maxTokens: 4096, reasoning: false },
|
|
119
|
+
"gpt-4-turbo": { contextWindow: 128000, maxTokens: 4096, reasoning: false },
|
|
120
|
+
"gpt-4o": { contextWindow: 128000, maxTokens: 16384, reasoning: false },
|
|
121
|
+
"gpt-4o-mini": { contextWindow: 128000, maxTokens: 16384, reasoning: false },
|
|
122
|
+
"gpt-3.5-turbo": { contextWindow: 16385, maxTokens: 4096, reasoning: false },
|
|
123
|
+
o1: { contextWindow: 200000, maxTokens: 100000, reasoning: true },
|
|
124
|
+
"o1-mini": { contextWindow: 128000, maxTokens: 65536, reasoning: true },
|
|
125
|
+
"o1-preview": { contextWindow: 128000, maxTokens: 32768, reasoning: true },
|
|
126
|
+
"o3-mini": { contextWindow: 200000, maxTokens: 100000, reasoning: true },
|
|
127
|
+
|
|
128
|
+
// Anthropic
|
|
129
|
+
"claude-3-opus-20240229": {
|
|
130
|
+
contextWindow: 200000,
|
|
131
|
+
maxTokens: 4096,
|
|
132
|
+
reasoning: false,
|
|
133
|
+
},
|
|
134
|
+
"claude-3-sonnet-20240229": {
|
|
135
|
+
contextWindow: 200000,
|
|
136
|
+
maxTokens: 4096,
|
|
137
|
+
reasoning: false,
|
|
138
|
+
},
|
|
139
|
+
"claude-3-haiku-20240307": {
|
|
140
|
+
contextWindow: 200000,
|
|
141
|
+
maxTokens: 4096,
|
|
142
|
+
reasoning: false,
|
|
143
|
+
},
|
|
144
|
+
"claude-3-5-sonnet-20241022": {
|
|
145
|
+
contextWindow: 200000,
|
|
146
|
+
maxTokens: 8192,
|
|
147
|
+
reasoning: false,
|
|
148
|
+
},
|
|
149
|
+
"claude-3-5-haiku-20241022": {
|
|
150
|
+
contextWindow: 200000,
|
|
151
|
+
maxTokens: 8192,
|
|
152
|
+
reasoning: false,
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// DeepSeek
|
|
156
|
+
"deepseek-chat": { contextWindow: 32768, maxTokens: 4096, reasoning: false },
|
|
157
|
+
"deepseek-coder": { contextWindow: 32768, maxTokens: 4096, reasoning: false },
|
|
158
|
+
"deepseek-reasoner": {
|
|
159
|
+
contextWindow: 65536,
|
|
160
|
+
maxTokens: 8192,
|
|
161
|
+
reasoning: true,
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
// Qwen
|
|
165
|
+
"qwen-turbo": { contextWindow: 8192, maxTokens: 2048, reasoning: false },
|
|
166
|
+
"qwen-plus": { contextWindow: 32768, maxTokens: 8192, reasoning: false },
|
|
167
|
+
"qwen-max": { contextWindow: 32768, maxTokens: 8192, reasoning: false },
|
|
168
|
+
|
|
169
|
+
// 通用默认值
|
|
170
|
+
default: { contextWindow: 32768, maxTokens: 4096, reasoning: false },
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 获取模型的默认配置
|
|
175
|
+
*/
|
|
176
|
+
function getModelDefaults(modelId) {
|
|
177
|
+
// 精确匹配
|
|
178
|
+
if (MODEL_DEFAULTS[modelId]) {
|
|
179
|
+
return MODEL_DEFAULTS[modelId];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 模糊匹配
|
|
183
|
+
const lowerId = modelId.toLowerCase();
|
|
184
|
+
for (const [pattern, defaults] of Object.entries(MODEL_DEFAULTS)) {
|
|
185
|
+
if (lowerId.includes(pattern.toLowerCase())) {
|
|
186
|
+
return defaults;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 检测是否是推理模型
|
|
191
|
+
const isReasoning =
|
|
192
|
+
lowerId.includes("reason") ||
|
|
193
|
+
lowerId.includes("o1") ||
|
|
194
|
+
lowerId.includes("o3") ||
|
|
195
|
+
lowerId.includes("think");
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
...MODEL_DEFAULTS["default"],
|
|
199
|
+
reasoning: isReasoning,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 让用户配置模型属性
|
|
205
|
+
*/
|
|
206
|
+
async function configureModel(modelId, defaults = null) {
|
|
207
|
+
if (!defaults) {
|
|
208
|
+
defaults = getModelDefaults(modelId);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(chalk.dim(`\nConfiguring model: ${modelId}`));
|
|
212
|
+
console.log(chalk.dim(`(Press Enter to use defaults)`));
|
|
213
|
+
|
|
214
|
+
const contextWindow = await number({
|
|
215
|
+
message: "Context window size (tokens):",
|
|
216
|
+
default: defaults.contextWindow,
|
|
217
|
+
validate: (v) => v > 0 || "Must be positive",
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const maxTokens = await number({
|
|
221
|
+
message: "Max output tokens:",
|
|
222
|
+
default: defaults.maxTokens,
|
|
223
|
+
validate: (v) => v > 0 || "Must be positive",
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const isReasoning = await confirm({
|
|
227
|
+
message: "Is this a reasoning model?",
|
|
228
|
+
default: defaults.reasoning,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
id: modelId,
|
|
233
|
+
name: modelId,
|
|
234
|
+
contextWindow: contextWindow || defaults.contextWindow,
|
|
235
|
+
maxTokens: maxTokens || defaults.maxTokens,
|
|
236
|
+
reasoning: isReasoning,
|
|
237
|
+
input: ["text"],
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* 交互式添加模型
|
|
243
|
+
*/
|
|
244
|
+
async function addModelsInteractively(existingModels = []) {
|
|
245
|
+
const models = [...existingModels];
|
|
246
|
+
|
|
247
|
+
console.log(chalk.bold("\n添加模型配置"));
|
|
248
|
+
console.log(chalk.dim("输入模型ID,输入空行结束"));
|
|
249
|
+
|
|
250
|
+
while (true) {
|
|
251
|
+
const modelId = await input({
|
|
252
|
+
message: "模型ID (空行结束):",
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
if (!modelId.trim()) {
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 检查是否已存在
|
|
260
|
+
if (models.find((m) => m.id === modelId)) {
|
|
261
|
+
console.log(chalk.yellow(`模型 ${modelId} 已存在`));
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const configured = await configureModel(modelId);
|
|
266
|
+
models.push(configured);
|
|
267
|
+
console.log(chalk.green(`✓ 已添加: ${modelId}`));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return models;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get provider configuration for a specific agent
|
|
275
|
+
*/
|
|
276
|
+
function getAgentProviderConfig(agentId, provider) {
|
|
277
|
+
switch (agentId) {
|
|
278
|
+
case "claude-code":
|
|
279
|
+
return {
|
|
280
|
+
env: {
|
|
281
|
+
ANTHROPIC_AUTH_TOKEN: provider.apiKey,
|
|
282
|
+
ANTHROPIC_BASE_URL: provider.baseUrl,
|
|
283
|
+
ANTHROPIC_MODEL:
|
|
284
|
+
provider.models[0]?.id || "claude-3-5-sonnet-20241022",
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
case "pi":
|
|
289
|
+
return {
|
|
290
|
+
modelsJson: {
|
|
291
|
+
providers: {
|
|
292
|
+
[provider.name]: {
|
|
293
|
+
baseUrl: provider.baseUrl,
|
|
294
|
+
api: provider.api,
|
|
295
|
+
apiKey: provider.apiKey,
|
|
296
|
+
models: provider.models.map((m) => ({
|
|
297
|
+
id: m.id,
|
|
298
|
+
name: m.name || m.id,
|
|
299
|
+
reasoning: m.reasoning || false,
|
|
300
|
+
input: m.input || ["text"],
|
|
301
|
+
contextWindow: m.contextWindow || 32768,
|
|
302
|
+
maxTokens: m.maxTokens || 4096,
|
|
303
|
+
})),
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
case "hermes":
|
|
310
|
+
return {
|
|
311
|
+
configYaml: {
|
|
312
|
+
model: {
|
|
313
|
+
default: provider.models[0]?.id || "default",
|
|
314
|
+
provider: provider.name,
|
|
315
|
+
base_url: provider.baseUrl,
|
|
316
|
+
api_key: provider.apiKey,
|
|
317
|
+
api_mode:
|
|
318
|
+
provider.api === "anthropic-messages"
|
|
319
|
+
? "anthropic_messages"
|
|
320
|
+
: "openai",
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
case "codex":
|
|
326
|
+
return {
|
|
327
|
+
configToml: {
|
|
328
|
+
model_provider: provider.name,
|
|
329
|
+
model: provider.models[0]?.id || "default",
|
|
330
|
+
model_providers: {
|
|
331
|
+
[provider.name]: {
|
|
332
|
+
name: provider.name,
|
|
333
|
+
base_url: provider.baseUrl,
|
|
334
|
+
wire_api:
|
|
335
|
+
provider.api === "anthropic-messages"
|
|
336
|
+
? "chat_completions"
|
|
337
|
+
: "responses",
|
|
338
|
+
requires_openai_auth: false,
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
case "openclaw":
|
|
345
|
+
return {
|
|
346
|
+
openclawJson: {
|
|
347
|
+
models: {
|
|
348
|
+
providers: {
|
|
349
|
+
[provider.name]: {
|
|
350
|
+
baseUrl: provider.baseUrl,
|
|
351
|
+
apiKey: provider.apiKey,
|
|
352
|
+
api: provider.api,
|
|
353
|
+
models: provider.models.map((m) => ({
|
|
354
|
+
id: m.id,
|
|
355
|
+
name: m.name || m.id,
|
|
356
|
+
reasoning: m.reasoning || false,
|
|
357
|
+
input: m.input || ["text"],
|
|
358
|
+
contextWindow: m.contextWindow || 32768,
|
|
359
|
+
maxTokens: m.maxTokens || 4096,
|
|
360
|
+
})),
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
case "omp":
|
|
368
|
+
return {
|
|
369
|
+
modelsYml: {
|
|
370
|
+
providers: {
|
|
371
|
+
[`custom-${provider.name}`]: {
|
|
372
|
+
baseUrl: provider.baseUrl,
|
|
373
|
+
apiKey: provider.apiKey,
|
|
374
|
+
api: provider.api,
|
|
375
|
+
authHeader: true,
|
|
376
|
+
models: provider.models.map((m) => ({
|
|
377
|
+
id: m.id,
|
|
378
|
+
name: m.name || m.id,
|
|
379
|
+
reasoning: m.reasoning || false,
|
|
380
|
+
input: m.input || ["text"],
|
|
381
|
+
contextWindow: m.contextWindow || 32768,
|
|
382
|
+
maxTokens: m.maxTokens || 4096,
|
|
383
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
384
|
+
})),
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
default:
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Install provider configuration to agent
|
|
397
|
+
*/
|
|
398
|
+
function installProviderToAgent(agentId, provider, agentHome) {
|
|
399
|
+
const config = getAgentProviderConfig(agentId, provider);
|
|
400
|
+
if (!config) {
|
|
401
|
+
console.log(chalk.yellow(` ⚠ 未知 agent: ${agentId}`));
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
switch (agentId) {
|
|
407
|
+
case "claude-code": {
|
|
408
|
+
const settingsPath = join(agentHome, "settings.json");
|
|
409
|
+
let settings = {};
|
|
410
|
+
if (existsSync(settingsPath)) {
|
|
411
|
+
settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
412
|
+
}
|
|
413
|
+
settings.env = { ...settings.env, ...config.env };
|
|
414
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
415
|
+
console.log(chalk.green(` ✓ 更新 Claude Code settings.json`));
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
case "pi": {
|
|
420
|
+
const modelsPath = join(agentHome, "models.json");
|
|
421
|
+
let models = { providers: {} };
|
|
422
|
+
if (existsSync(modelsPath)) {
|
|
423
|
+
models = JSON.parse(readFileSync(modelsPath, "utf8"));
|
|
424
|
+
}
|
|
425
|
+
models.providers = {
|
|
426
|
+
...models.providers,
|
|
427
|
+
...config.modelsJson.providers,
|
|
428
|
+
};
|
|
429
|
+
writeFileSync(modelsPath, JSON.stringify(models, null, 2));
|
|
430
|
+
console.log(chalk.green(` ✓ 更新 Pi models.json`));
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
case "hermes": {
|
|
435
|
+
const configPath = join(agentHome, "config.yaml");
|
|
436
|
+
let configData = {};
|
|
437
|
+
if (existsSync(configPath)) {
|
|
438
|
+
configData = yaml.load(readFileSync(configPath, "utf8"));
|
|
439
|
+
}
|
|
440
|
+
configData.model = { ...configData.model, ...config.configYaml.model };
|
|
441
|
+
writeFileSync(configPath, yaml.dump(configData));
|
|
442
|
+
console.log(chalk.green(` ✓ 更新 Hermes config.yaml`));
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
case "codex": {
|
|
447
|
+
const configPath = join(agentHome, "config.toml");
|
|
448
|
+
let configContent = "";
|
|
449
|
+
if (existsSync(configPath)) {
|
|
450
|
+
configContent = readFileSync(configPath, "utf8");
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const providerSection = `\n[model_providers.${provider.name}]\nname = "${provider.name}"\nbase_url = "${provider.baseUrl}"\nwire_api = "${provider.api === "anthropic-messages" ? "chat_completions" : "responses"}"\nrequires_openai_auth = false\n`;
|
|
454
|
+
|
|
455
|
+
const modelLine = `model_provider = "${provider.name}"\n`;
|
|
456
|
+
const modelIdLine = `model = "${provider.models[0]?.id || "default"}"\n`;
|
|
457
|
+
|
|
458
|
+
if (configContent.includes("model_provider =")) {
|
|
459
|
+
configContent = configContent.replace(
|
|
460
|
+
/model_provider = .*\n/,
|
|
461
|
+
modelLine,
|
|
462
|
+
);
|
|
463
|
+
} else {
|
|
464
|
+
configContent = modelLine + configContent;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (configContent.includes("model =")) {
|
|
468
|
+
configContent = configContent.replace(/model = .*\n/, modelIdLine);
|
|
469
|
+
} else {
|
|
470
|
+
configContent = modelIdLine + configContent;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (!configContent.includes(`[model_providers.${provider.name}]`)) {
|
|
474
|
+
configContent += providerSection;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
writeFileSync(configPath, configContent);
|
|
478
|
+
console.log(chalk.green(` ✓ 更新 Codex config.toml`));
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
case "openclaw": {
|
|
483
|
+
const configPath = join(agentHome, "openclaw.json");
|
|
484
|
+
let configData = {};
|
|
485
|
+
if (existsSync(configPath)) {
|
|
486
|
+
configData = JSON.parse(readFileSync(configPath, "utf8"));
|
|
487
|
+
}
|
|
488
|
+
if (!configData.models) configData.models = {};
|
|
489
|
+
if (!configData.models.providers) configData.models.providers = {};
|
|
490
|
+
configData.models.providers = {
|
|
491
|
+
...configData.models.providers,
|
|
492
|
+
...config.openclawJson.models.providers,
|
|
493
|
+
};
|
|
494
|
+
writeFileSync(configPath, JSON.stringify(configData, null, 2));
|
|
495
|
+
console.log(chalk.green(` ✓ 更新 OpenClaw openclaw.json`));
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
case "omp": {
|
|
500
|
+
const modelsPath = join(agentHome, "models.yml");
|
|
501
|
+
let modelsData = { providers: {} };
|
|
502
|
+
if (existsSync(modelsPath)) {
|
|
503
|
+
modelsData = yaml.load(readFileSync(modelsPath, "utf8"));
|
|
504
|
+
}
|
|
505
|
+
modelsData.providers = {
|
|
506
|
+
...modelsData.providers,
|
|
507
|
+
...config.modelsYml.providers,
|
|
508
|
+
};
|
|
509
|
+
writeFileSync(modelsPath, yaml.dump(modelsData));
|
|
510
|
+
console.log(chalk.green(` ✓ 更新 OMP models.yml`));
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
default:
|
|
515
|
+
console.log(chalk.yellow(` ⚠ 未知 agent: ${agentId}`));
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
} catch (err) {
|
|
519
|
+
console.error(chalk.red(` ✗ 更新 ${agentId} 失败: ${err.message}`));
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
export function registerProvider(program, repoRoot) {
|
|
525
|
+
// ── cpc add ──────────────────────────────────────────────────
|
|
526
|
+
program
|
|
527
|
+
.command("add")
|
|
528
|
+
.description("Add a new custom model provider")
|
|
529
|
+
.option("--name <name>", "Provider name")
|
|
530
|
+
.option("--url <url>", "Base URL for the provider")
|
|
531
|
+
.option("--key <key>", "API key")
|
|
532
|
+
.option("--api <api>", "API type (openai-completions, anthropic-messages)")
|
|
533
|
+
.option("--fetch", "Auto-fetch models from API")
|
|
534
|
+
.option("--all", "Select all models when fetching")
|
|
535
|
+
.option("--force", "Overwrite existing provider without confirmation")
|
|
536
|
+
.action(async (opts) => {
|
|
537
|
+
const providersData = loadProviders();
|
|
538
|
+
|
|
539
|
+
// Step 1: Get provider name
|
|
540
|
+
let name = opts.name;
|
|
541
|
+
if (!name) {
|
|
542
|
+
name = await input({
|
|
543
|
+
message: "Enter provider name:",
|
|
544
|
+
validate: (value) =>
|
|
545
|
+
value.trim() !== "" || "Provider name is required",
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Check if provider already exists
|
|
550
|
+
if (providersData.providers[name]) {
|
|
551
|
+
if (!opts.force) {
|
|
552
|
+
const overwrite = await confirm({
|
|
553
|
+
message: `Provider "${name}" already exists. Overwrite?`,
|
|
554
|
+
default: false,
|
|
555
|
+
});
|
|
556
|
+
if (!overwrite) {
|
|
557
|
+
console.log(chalk.yellow("Cancelled."));
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Step 2: Get base URL
|
|
564
|
+
let baseUrl = opts.url;
|
|
565
|
+
if (!baseUrl) {
|
|
566
|
+
baseUrl = await input({
|
|
567
|
+
message: "Enter base URL (e.g., https://api.openai.com/v1):",
|
|
568
|
+
validate: (value) => {
|
|
569
|
+
try {
|
|
570
|
+
new URL(value);
|
|
571
|
+
return true;
|
|
572
|
+
} catch {
|
|
573
|
+
return "Please enter a valid URL";
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Step 3: Get API key
|
|
580
|
+
let apiKey = opts.key;
|
|
581
|
+
if (!apiKey) {
|
|
582
|
+
apiKey = await input({
|
|
583
|
+
message: "Enter API key:",
|
|
584
|
+
validate: (value) => value.trim() !== "" || "API key is required",
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Step 4: Get API type
|
|
589
|
+
let api = opts.api;
|
|
590
|
+
if (!api) {
|
|
591
|
+
api = await select({
|
|
592
|
+
message: "Select API type:",
|
|
593
|
+
choices: [
|
|
594
|
+
{
|
|
595
|
+
name: "OpenAI Completions (compatible with most providers)",
|
|
596
|
+
value: "openai-completions",
|
|
597
|
+
},
|
|
598
|
+
{ name: "Anthropic Messages", value: "anthropic-messages" },
|
|
599
|
+
{ name: "Responses (OpenAI native)", value: "responses" },
|
|
600
|
+
],
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Step 5: Fetch or add models
|
|
605
|
+
let models = [];
|
|
606
|
+
|
|
607
|
+
if (
|
|
608
|
+
opts.fetch ||
|
|
609
|
+
(!opts.name &&
|
|
610
|
+
(await confirm({
|
|
611
|
+
message: "Auto-fetch models from API?",
|
|
612
|
+
default: true,
|
|
613
|
+
})))
|
|
614
|
+
) {
|
|
615
|
+
console.log(chalk.dim("\nFetching models from API..."));
|
|
616
|
+
const result = await fetchModelsFromAPI(baseUrl, apiKey, api);
|
|
617
|
+
|
|
618
|
+
if (result.success && result.models.length > 0) {
|
|
619
|
+
console.log(chalk.green(`✓ Found ${result.models.length} models`));
|
|
620
|
+
|
|
621
|
+
let selected;
|
|
622
|
+
|
|
623
|
+
// 如果指定了--all或者非交互模式,选择所有模型
|
|
624
|
+
if (opts.all || opts.name) {
|
|
625
|
+
selected = result.models.map((m) => m.id);
|
|
626
|
+
console.log(
|
|
627
|
+
chalk.dim(`Auto-selected all ${selected.length} models`),
|
|
628
|
+
);
|
|
629
|
+
} else {
|
|
630
|
+
// 让用户选择要添加的模型
|
|
631
|
+
selected = await checkbox({
|
|
632
|
+
message: "Select models to add:",
|
|
633
|
+
choices: result.models.map((m) => ({
|
|
634
|
+
name: `${m.id.padEnd(40)} ctx:${m.contextWindow || "?"}`,
|
|
635
|
+
value: m.id,
|
|
636
|
+
checked: true,
|
|
637
|
+
})),
|
|
638
|
+
instructions: {
|
|
639
|
+
navigator: "↑↓ navigate",
|
|
640
|
+
select: "space select",
|
|
641
|
+
all: "a toggle all",
|
|
642
|
+
},
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// 配置选中的模型
|
|
647
|
+
for (const modelId of selected) {
|
|
648
|
+
const model = result.models.find((m) => m.id === modelId);
|
|
649
|
+
const defaults = getModelDefaults(modelId);
|
|
650
|
+
const configured = {
|
|
651
|
+
id: modelId,
|
|
652
|
+
name: model?.name || modelId,
|
|
653
|
+
contextWindow: model?.contextWindow || defaults.contextWindow,
|
|
654
|
+
maxTokens: model?.maxTokens || defaults.maxTokens,
|
|
655
|
+
reasoning: model?.reasoning || defaults.reasoning,
|
|
656
|
+
input: model?.input || ["text"],
|
|
657
|
+
};
|
|
658
|
+
models.push(configured);
|
|
659
|
+
}
|
|
660
|
+
} else {
|
|
661
|
+
console.log(
|
|
662
|
+
chalk.yellow(`⚠ Could not fetch models: ${result.error}`),
|
|
663
|
+
);
|
|
664
|
+
console.log(chalk.dim("You can add models manually."));
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// 如果没有通过API获取到模型,手动添加
|
|
669
|
+
if (models.length === 0) {
|
|
670
|
+
models = await addModelsInteractively();
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (models.length === 0) {
|
|
674
|
+
console.log(chalk.yellow("No models added. Cancelled."));
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Save provider
|
|
679
|
+
providersData.providers[name] = {
|
|
680
|
+
name,
|
|
681
|
+
baseUrl,
|
|
682
|
+
apiKey,
|
|
683
|
+
api,
|
|
684
|
+
models,
|
|
685
|
+
createdAt: new Date().toISOString(),
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
saveProviders(providersData);
|
|
689
|
+
console.log(
|
|
690
|
+
chalk.green(
|
|
691
|
+
`\n✓ Provider "${name}" added with ${models.length} model(s)!`,
|
|
692
|
+
),
|
|
693
|
+
);
|
|
694
|
+
console.log(chalk.dim(` Saved to: config/providers.json`));
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
// ── cpc list ─────────────────────────────────────────────────
|
|
698
|
+
program
|
|
699
|
+
.command("list")
|
|
700
|
+
.description("List all configured providers")
|
|
701
|
+
.option("--detail", "Show model details")
|
|
702
|
+
.action((opts) => {
|
|
703
|
+
const providersData = loadProviders();
|
|
704
|
+
const providers = Object.entries(providersData.providers);
|
|
705
|
+
|
|
706
|
+
console.log(
|
|
707
|
+
chalk.bold(
|
|
708
|
+
"\n╔══════════════════════════════════════════════════════════╗",
|
|
709
|
+
),
|
|
710
|
+
);
|
|
711
|
+
console.log(
|
|
712
|
+
chalk.bold(
|
|
713
|
+
"║ Custom Providers ║",
|
|
714
|
+
),
|
|
715
|
+
);
|
|
716
|
+
console.log(
|
|
717
|
+
chalk.bold(
|
|
718
|
+
"╚══════════════════════════════════════════════════════════╝",
|
|
719
|
+
),
|
|
720
|
+
);
|
|
721
|
+
console.log();
|
|
722
|
+
|
|
723
|
+
if (providers.length === 0) {
|
|
724
|
+
console.log(chalk.dim(" No providers configured."));
|
|
725
|
+
console.log(chalk.dim(" Use `cpc add` to add a new provider."));
|
|
726
|
+
console.log();
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
for (const [name, config] of providers) {
|
|
731
|
+
console.log(chalk.bold.blue(`[${name}]`));
|
|
732
|
+
console.log(` URL: ${chalk.dim(config.baseUrl)}`);
|
|
733
|
+
console.log(` API: ${chalk.dim(config.api)}`);
|
|
734
|
+
console.log(` Models: ${chalk.dim(config.models?.length || 0)}`);
|
|
735
|
+
|
|
736
|
+
if (opts.detail && config.models?.length > 0) {
|
|
737
|
+
console.log(chalk.dim(" ─────────────────────────────────────"));
|
|
738
|
+
for (const m of config.models) {
|
|
739
|
+
console.log(` ${chalk.green(m.id)}`);
|
|
740
|
+
console.log(` Context: ${m.contextWindow} tokens`);
|
|
741
|
+
console.log(` MaxOut: ${m.maxTokens} tokens`);
|
|
742
|
+
console.log(` Reason: ${m.reasoning ? "Yes" : "No"}`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
console.log(` Created: ${chalk.dim(config.createdAt)}`);
|
|
747
|
+
console.log();
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
// ── cpc models ───────────────────────────────────────────────
|
|
752
|
+
program
|
|
753
|
+
.command("models [providerName]")
|
|
754
|
+
.description("List models for a provider")
|
|
755
|
+
.action(async (providerName) => {
|
|
756
|
+
const providersData = loadProviders();
|
|
757
|
+
const providers = Object.entries(providersData.providers);
|
|
758
|
+
|
|
759
|
+
if (providers.length === 0) {
|
|
760
|
+
console.log(chalk.yellow("No providers configured."));
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (!providerName) {
|
|
765
|
+
providerName = await select({
|
|
766
|
+
message: "Select provider:",
|
|
767
|
+
choices: providers.map(([name, config]) => ({
|
|
768
|
+
name: `${name.padEnd(20)} (${config.models?.length || 0} models)`,
|
|
769
|
+
value: name,
|
|
770
|
+
})),
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const config = providersData.providers[providerName];
|
|
775
|
+
if (!config) {
|
|
776
|
+
console.error(chalk.red(`Provider "${providerName}" not found.`));
|
|
777
|
+
process.exit(1);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
console.log(chalk.bold(`\nModels for ${providerName}:\n`));
|
|
781
|
+
|
|
782
|
+
if (!config.models || config.models.length === 0) {
|
|
783
|
+
console.log(chalk.dim(" No models configured."));
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// 表格输出
|
|
788
|
+
console.log(
|
|
789
|
+
chalk.dim(
|
|
790
|
+
" Model ID Context MaxOut Reasoning",
|
|
791
|
+
),
|
|
792
|
+
);
|
|
793
|
+
console.log(
|
|
794
|
+
chalk.dim(
|
|
795
|
+
" ───────────────────────────────────────── ────────── ──────── ──────────",
|
|
796
|
+
),
|
|
797
|
+
);
|
|
798
|
+
|
|
799
|
+
for (const m of config.models) {
|
|
800
|
+
const id = m.id.padEnd(42);
|
|
801
|
+
const ctx = String(m.contextWindow || "?").padEnd(10);
|
|
802
|
+
const max = String(m.maxTokens || "?").padEnd(8);
|
|
803
|
+
const reason = m.reasoning ? "Yes" : "No";
|
|
804
|
+
console.log(` ${chalk.green(id)} ${ctx} ${max} ${reason}`);
|
|
805
|
+
}
|
|
806
|
+
console.log();
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
// ── cpc edit-model ───────────────────────────────────────────
|
|
810
|
+
program
|
|
811
|
+
.command("edit-model [providerName]")
|
|
812
|
+
.description("Edit model configuration for a provider")
|
|
813
|
+
.action(async (providerName) => {
|
|
814
|
+
const providersData = loadProviders();
|
|
815
|
+
const providers = Object.entries(providersData.providers);
|
|
816
|
+
|
|
817
|
+
if (providers.length === 0) {
|
|
818
|
+
console.log(chalk.yellow("No providers configured."));
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (!providerName) {
|
|
823
|
+
providerName = await select({
|
|
824
|
+
message: "Select provider:",
|
|
825
|
+
choices: providers.map(([name, config]) => ({
|
|
826
|
+
name: `${name.padEnd(20)} (${config.models?.length || 0} models)`,
|
|
827
|
+
value: name,
|
|
828
|
+
})),
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const config = providersData.providers[providerName];
|
|
833
|
+
if (!config) {
|
|
834
|
+
console.error(chalk.red(`Provider "${providerName}" not found.`));
|
|
835
|
+
process.exit(1);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// 选择操作
|
|
839
|
+
const action = await select({
|
|
840
|
+
message: "Select action:",
|
|
841
|
+
choices: [
|
|
842
|
+
{ name: "Add model", value: "add" },
|
|
843
|
+
{ name: "Edit model", value: "edit" },
|
|
844
|
+
{ name: "Remove model", value: "remove" },
|
|
845
|
+
{ name: "Cancel", value: "cancel" },
|
|
846
|
+
],
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
if (action === "cancel") {
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
if (action === "add") {
|
|
854
|
+
const newModels = await addModelsInteractively(config.models || []);
|
|
855
|
+
config.models = newModels;
|
|
856
|
+
saveProviders(providersData);
|
|
857
|
+
console.log(chalk.green(`✓ Models updated for "${providerName}"`));
|
|
858
|
+
} else if (action === "edit") {
|
|
859
|
+
if (!config.models || config.models.length === 0) {
|
|
860
|
+
console.log(chalk.yellow("No models to edit."));
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const modelId = await select({
|
|
865
|
+
message: "Select model to edit:",
|
|
866
|
+
choices: config.models.map((m) => ({
|
|
867
|
+
name: `${m.id.padEnd(40)} ctx:${m.contextWindow}`,
|
|
868
|
+
value: m.id,
|
|
869
|
+
})),
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
const model = config.models.find((m) => m.id === modelId);
|
|
873
|
+
const updated = await configureModel(modelId, model);
|
|
874
|
+
|
|
875
|
+
const index = config.models.findIndex((m) => m.id === modelId);
|
|
876
|
+
config.models[index] = updated;
|
|
877
|
+
saveProviders(providersData);
|
|
878
|
+
console.log(chalk.green(`✓ Model "${modelId}" updated`));
|
|
879
|
+
} else if (action === "remove") {
|
|
880
|
+
if (!config.models || config.models.length === 0) {
|
|
881
|
+
console.log(chalk.yellow("No models to remove."));
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const selected = await checkbox({
|
|
886
|
+
message: "Select models to remove:",
|
|
887
|
+
choices: config.models.map((m) => ({
|
|
888
|
+
name: m.id,
|
|
889
|
+
value: m.id,
|
|
890
|
+
})),
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
config.models = config.models.filter((m) => !selected.includes(m.id));
|
|
894
|
+
saveProviders(providersData);
|
|
895
|
+
console.log(chalk.green(`✓ Removed ${selected.length} model(s)`));
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
// ── cpc fetch-models ─────────────────────────────────────────
|
|
900
|
+
program
|
|
901
|
+
.command("fetch-models [providerName]")
|
|
902
|
+
.description("Fetch latest models from provider API")
|
|
903
|
+
.option("--merge", "Merge with existing models (default: replace)")
|
|
904
|
+
.action(async (providerName, opts) => {
|
|
905
|
+
const providersData = loadProviders();
|
|
906
|
+
const providers = Object.entries(providersData.providers);
|
|
907
|
+
|
|
908
|
+
if (providers.length === 0) {
|
|
909
|
+
console.log(chalk.yellow("No providers configured."));
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
if (!providerName) {
|
|
914
|
+
providerName = await select({
|
|
915
|
+
message: "Select provider:",
|
|
916
|
+
choices: providers.map(([name, config]) => ({
|
|
917
|
+
name: `${name.padEnd(20)} ${config.baseUrl}`,
|
|
918
|
+
value: name,
|
|
919
|
+
})),
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const config = providersData.providers[providerName];
|
|
924
|
+
if (!config) {
|
|
925
|
+
console.error(chalk.red(`Provider "${providerName}" not found.`));
|
|
926
|
+
process.exit(1);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
console.log(chalk.dim(`\nFetching models from ${config.baseUrl}...`));
|
|
930
|
+
const result = await fetchModelsFromAPI(
|
|
931
|
+
config.baseUrl,
|
|
932
|
+
config.apiKey,
|
|
933
|
+
config.api,
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
if (!result.success) {
|
|
937
|
+
console.log(chalk.red(`✗ Failed to fetch models: ${result.error}`));
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
console.log(chalk.green(`✓ Found ${result.models.length} models`));
|
|
942
|
+
|
|
943
|
+
// 让用户选择要添加的模型
|
|
944
|
+
const existingIds = (config.models || []).map((m) => m.id);
|
|
945
|
+
const newModels = result.models.filter(
|
|
946
|
+
(m) => !existingIds.includes(m.id),
|
|
947
|
+
);
|
|
948
|
+
|
|
949
|
+
if (newModels.length === 0) {
|
|
950
|
+
console.log(chalk.dim("All models already configured."));
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
console.log(chalk.dim(`\n${newModels.length} new models available:`));
|
|
955
|
+
|
|
956
|
+
const selected = await checkbox({
|
|
957
|
+
message: "Select models to add:",
|
|
958
|
+
choices: newModels.map((m) => ({
|
|
959
|
+
name: m.id,
|
|
960
|
+
value: m.id,
|
|
961
|
+
checked: true,
|
|
962
|
+
})),
|
|
963
|
+
instructions: {
|
|
964
|
+
navigator: "↑↓ navigate",
|
|
965
|
+
select: "space select",
|
|
966
|
+
all: "a toggle all",
|
|
967
|
+
},
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
if (selected.length === 0) {
|
|
971
|
+
console.log(chalk.yellow("No models selected."));
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// 配置选中的模型
|
|
976
|
+
for (const modelId of selected) {
|
|
977
|
+
const model = result.models.find((m) => m.id === modelId);
|
|
978
|
+
const defaults = getModelDefaults(modelId);
|
|
979
|
+
const configured = {
|
|
980
|
+
id: modelId,
|
|
981
|
+
name: model?.name || modelId,
|
|
982
|
+
contextWindow: model?.contextWindow || defaults.contextWindow,
|
|
983
|
+
maxTokens: model?.maxTokens || defaults.maxTokens,
|
|
984
|
+
reasoning: model?.reasoning || defaults.reasoning,
|
|
985
|
+
input: model?.input || ["text"],
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
if (opts.merge) {
|
|
989
|
+
config.models = config.models || [];
|
|
990
|
+
config.models.push(configured);
|
|
991
|
+
} else {
|
|
992
|
+
// 替换模式:保留现有的,添加新的
|
|
993
|
+
if (!config.models) config.models = [];
|
|
994
|
+
if (!config.models.find((m) => m.id === modelId)) {
|
|
995
|
+
config.models.push(configured);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
saveProviders(providersData);
|
|
1001
|
+
console.log(
|
|
1002
|
+
chalk.green(
|
|
1003
|
+
`\n✓ Added ${selected.length} model(s) to "${providerName}"`,
|
|
1004
|
+
),
|
|
1005
|
+
);
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
// ── cpc remove ───────────────────────────────────────────────
|
|
1009
|
+
program
|
|
1010
|
+
.command("remove [name]")
|
|
1011
|
+
.description("Remove a provider")
|
|
1012
|
+
.option("--force", "Skip confirmation prompt")
|
|
1013
|
+
.action(async (name, opts) => {
|
|
1014
|
+
const providersData = loadProviders();
|
|
1015
|
+
const providers = Object.keys(providersData.providers);
|
|
1016
|
+
|
|
1017
|
+
if (providers.length === 0) {
|
|
1018
|
+
console.log(chalk.yellow("No providers configured."));
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
if (!name) {
|
|
1023
|
+
name = await select({
|
|
1024
|
+
message: "Select provider to remove:",
|
|
1025
|
+
choices: providers.map((p) => ({ name: p, value: p })),
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
if (!providersData.providers[name]) {
|
|
1030
|
+
console.error(chalk.red(`Provider "${name}" not found.`));
|
|
1031
|
+
process.exit(1);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
if (!opts.force) {
|
|
1035
|
+
const modelCount = providersData.providers[name].models?.length || 0;
|
|
1036
|
+
const confirmed = await confirm({
|
|
1037
|
+
message: `Remove provider "${name}" with ${modelCount} model(s)?`,
|
|
1038
|
+
default: false,
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
if (!confirmed) {
|
|
1042
|
+
console.log(chalk.yellow("Cancelled."));
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
delete providersData.providers[name];
|
|
1048
|
+
saveProviders(providersData);
|
|
1049
|
+
console.log(chalk.green(`✓ Provider "${name}" removed.`));
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
// ── cpc install ──────────────────────────────────────────────
|
|
1053
|
+
program
|
|
1054
|
+
.command("install [providerName]")
|
|
1055
|
+
.description("Install a provider to agent(s)")
|
|
1056
|
+
.option("--agent <agent>", "Target agent")
|
|
1057
|
+
.option("--model <model>", "Install only specific model")
|
|
1058
|
+
.option("--all", "Install all models")
|
|
1059
|
+
.option("--first", "Install only first model")
|
|
1060
|
+
.action(async (providerName, opts) => {
|
|
1061
|
+
const registry = loadRegistry(repoRoot);
|
|
1062
|
+
const agents = getAgents(registry);
|
|
1063
|
+
const providersData = loadProviders();
|
|
1064
|
+
const providers = Object.entries(providersData.providers);
|
|
1065
|
+
|
|
1066
|
+
if (providers.length === 0) {
|
|
1067
|
+
console.log(chalk.yellow("No providers configured."));
|
|
1068
|
+
console.log(chalk.dim("Use `cpc add` to add a new provider first."));
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// Step 1: Select provider
|
|
1073
|
+
if (!providerName) {
|
|
1074
|
+
providerName = await select({
|
|
1075
|
+
message: "Select provider to install:",
|
|
1076
|
+
choices: providers.map(([name, config]) => ({
|
|
1077
|
+
name: `${name.padEnd(20)} (${config.models?.length || 0} models)`,
|
|
1078
|
+
value: name,
|
|
1079
|
+
})),
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
const provider = providersData.providers[providerName];
|
|
1084
|
+
if (!provider) {
|
|
1085
|
+
console.error(chalk.red(`Provider "${providerName}" not found.`));
|
|
1086
|
+
process.exit(1);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Step 2: Select model (optional)
|
|
1090
|
+
const installProvider = { ...provider };
|
|
1091
|
+
|
|
1092
|
+
if (opts.model) {
|
|
1093
|
+
const model = provider.models.find((m) => m.id === opts.model);
|
|
1094
|
+
if (!model) {
|
|
1095
|
+
console.error(
|
|
1096
|
+
chalk.red(
|
|
1097
|
+
`Model "${opts.model}" not found in provider "${providerName}".`,
|
|
1098
|
+
),
|
|
1099
|
+
);
|
|
1100
|
+
process.exit(1);
|
|
1101
|
+
}
|
|
1102
|
+
installProvider.models = [model];
|
|
1103
|
+
} else if (opts.all) {
|
|
1104
|
+
// 使用所有模型
|
|
1105
|
+
installProvider.models = provider.models;
|
|
1106
|
+
} else if (opts.first) {
|
|
1107
|
+
// 只使用第一个模型
|
|
1108
|
+
installProvider.models = [provider.models[0]];
|
|
1109
|
+
} else if (provider.models.length > 1) {
|
|
1110
|
+
const action = await select({
|
|
1111
|
+
message: "Select models to install:",
|
|
1112
|
+
choices: [
|
|
1113
|
+
{ name: `All ${provider.models.length} models`, value: "all" },
|
|
1114
|
+
{ name: "Select specific models", value: "select" },
|
|
1115
|
+
{ name: "Only first model", value: "first" },
|
|
1116
|
+
],
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
if (action === "select") {
|
|
1120
|
+
const selected = await checkbox({
|
|
1121
|
+
message: "Select models:",
|
|
1122
|
+
choices: provider.models.map((m) => ({
|
|
1123
|
+
name: `${m.id.padEnd(40)} ctx:${m.contextWindow}`,
|
|
1124
|
+
value: m.id,
|
|
1125
|
+
checked: true,
|
|
1126
|
+
})),
|
|
1127
|
+
});
|
|
1128
|
+
installProvider.models = provider.models.filter((m) =>
|
|
1129
|
+
selected.includes(m.id),
|
|
1130
|
+
);
|
|
1131
|
+
} else if (action === "first") {
|
|
1132
|
+
installProvider.models = [provider.models[0]];
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// Step 3: Select agent(s)
|
|
1137
|
+
let agentId;
|
|
1138
|
+
if (opts.agent) {
|
|
1139
|
+
agentId = opts.agent;
|
|
1140
|
+
} else {
|
|
1141
|
+
agentId = await select({
|
|
1142
|
+
message: "Select target agent:",
|
|
1143
|
+
choices: [
|
|
1144
|
+
...Object.entries(agents).map(([id, a]) => ({
|
|
1145
|
+
name: `${id.padEnd(14)} ${a.description || ""}`,
|
|
1146
|
+
value: id,
|
|
1147
|
+
})),
|
|
1148
|
+
{ name: "─────────", value: "__separator__", disabled: true },
|
|
1149
|
+
{ name: "all agents", value: "all" },
|
|
1150
|
+
],
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
const agentIds = agentId === "all" ? Object.keys(agents) : [agentId];
|
|
1155
|
+
|
|
1156
|
+
// Step 4: Install provider to agents
|
|
1157
|
+
console.log();
|
|
1158
|
+
let successCount = 0;
|
|
1159
|
+
|
|
1160
|
+
for (const aid of agentIds) {
|
|
1161
|
+
const agentDef = agents[aid];
|
|
1162
|
+
if (!agentDef) {
|
|
1163
|
+
console.log(chalk.red(`Unknown agent: ${aid}`));
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
const agent = resolveAgentPaths(agentDef);
|
|
1168
|
+
console.log(chalk.bold(`→ ${aid}`));
|
|
1169
|
+
console.log(chalk.dim(` ${installProvider.models.length} model(s)`));
|
|
1170
|
+
|
|
1171
|
+
const success = installProviderToAgent(
|
|
1172
|
+
aid,
|
|
1173
|
+
installProvider,
|
|
1174
|
+
agent.home,
|
|
1175
|
+
);
|
|
1176
|
+
if (success) successCount++;
|
|
1177
|
+
console.log();
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
if (successCount > 0) {
|
|
1181
|
+
console.log(
|
|
1182
|
+
chalk.green.bold(
|
|
1183
|
+
`✓ Provider installed to ${successCount} agent(s) with ${installProvider.models.length} model(s)!`,
|
|
1184
|
+
),
|
|
1185
|
+
);
|
|
1186
|
+
} else {
|
|
1187
|
+
console.log(chalk.yellow("No agents were updated."));
|
|
1188
|
+
}
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
// ── cpc test ─────────────────────────────────────────────────
|
|
1192
|
+
program
|
|
1193
|
+
.command("test [providerName]")
|
|
1194
|
+
.description("Test a provider connection")
|
|
1195
|
+
.action(async (providerName) => {
|
|
1196
|
+
const providersData = loadProviders();
|
|
1197
|
+
const providers = Object.entries(providersData.providers);
|
|
1198
|
+
|
|
1199
|
+
if (providers.length === 0) {
|
|
1200
|
+
console.log(chalk.yellow("No providers configured."));
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
if (!providerName) {
|
|
1205
|
+
providerName = await select({
|
|
1206
|
+
message: "Select provider to test:",
|
|
1207
|
+
choices: providers.map(([name, config]) => ({
|
|
1208
|
+
name: `${name.padEnd(20)} ${config.baseUrl}`,
|
|
1209
|
+
value: name,
|
|
1210
|
+
})),
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
const provider = providersData.providers[providerName];
|
|
1215
|
+
if (!provider) {
|
|
1216
|
+
console.error(chalk.red(`Provider "${providerName}" not found.`));
|
|
1217
|
+
process.exit(1);
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
console.log(chalk.bold(`\nTesting provider: ${providerName}`));
|
|
1221
|
+
console.log(chalk.dim(`URL: ${provider.baseUrl}`));
|
|
1222
|
+
console.log(chalk.dim(`API: ${provider.api}`));
|
|
1223
|
+
console.log(chalk.dim(`Models: ${provider.models?.length || 0}`));
|
|
1224
|
+
console.log();
|
|
1225
|
+
|
|
1226
|
+
// Test 1: Connection
|
|
1227
|
+
console.log(chalk.dim("1. Testing connection..."));
|
|
1228
|
+
try {
|
|
1229
|
+
const response = await fetch(provider.baseUrl, {
|
|
1230
|
+
method: "GET",
|
|
1231
|
+
headers: {
|
|
1232
|
+
Authorization: `Bearer ${provider.apiKey}`,
|
|
1233
|
+
"Content-Type": "application/json",
|
|
1234
|
+
},
|
|
1235
|
+
signal: AbortSignal.timeout(5000),
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
if (response.ok) {
|
|
1239
|
+
console.log(chalk.green(" ✓ Connection successful"));
|
|
1240
|
+
} else {
|
|
1241
|
+
console.log(chalk.yellow(` ⚠ Status: ${response.status}`));
|
|
1242
|
+
}
|
|
1243
|
+
} catch (err) {
|
|
1244
|
+
if (err.name === "AbortError" || err.name === "TimeoutError") {
|
|
1245
|
+
console.log(chalk.red(" ✗ Connection timeout"));
|
|
1246
|
+
} else {
|
|
1247
|
+
console.log(chalk.red(` ✗ Connection failed: ${err.message}`));
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
// Test 2: Model listing (if supported)
|
|
1252
|
+
if (provider.api !== "anthropic-messages") {
|
|
1253
|
+
console.log(chalk.dim("\n2. Testing model listing..."));
|
|
1254
|
+
const result = await fetchModelsFromAPI(
|
|
1255
|
+
provider.baseUrl,
|
|
1256
|
+
provider.apiKey,
|
|
1257
|
+
provider.api,
|
|
1258
|
+
);
|
|
1259
|
+
|
|
1260
|
+
if (result.success) {
|
|
1261
|
+
console.log(chalk.green(` ✓ Found ${result.models.length} models`));
|
|
1262
|
+
|
|
1263
|
+
// 检查配置的模型是否存在
|
|
1264
|
+
const configuredIds = (provider.models || []).map((m) => m.id);
|
|
1265
|
+
const availableIds = result.models.map((m) => m.id);
|
|
1266
|
+
const missing = configuredIds.filter(
|
|
1267
|
+
(id) => !availableIds.includes(id),
|
|
1268
|
+
);
|
|
1269
|
+
|
|
1270
|
+
if (missing.length > 0) {
|
|
1271
|
+
console.log(
|
|
1272
|
+
chalk.yellow(
|
|
1273
|
+
` ⚠ ${missing.length} configured model(s) not found in API`,
|
|
1274
|
+
),
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
} else {
|
|
1278
|
+
console.log(chalk.yellow(` ⚠ ${result.error}`));
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
console.log();
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
// ── cpc export ───────────────────────────────────────────────
|
|
1286
|
+
program
|
|
1287
|
+
.command("export [providerName]")
|
|
1288
|
+
.description("Export provider configuration to a file")
|
|
1289
|
+
.option("--output <file>", "Output file path")
|
|
1290
|
+
.action(async (providerName, opts) => {
|
|
1291
|
+
const providersData = loadProviders();
|
|
1292
|
+
const providers = Object.entries(providersData.providers);
|
|
1293
|
+
|
|
1294
|
+
if (providers.length === 0) {
|
|
1295
|
+
console.log(chalk.yellow("No providers configured."));
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
if (!providerName) {
|
|
1300
|
+
providerName = await select({
|
|
1301
|
+
message: "Select provider to export:",
|
|
1302
|
+
choices: providers.map(([name, config]) => ({
|
|
1303
|
+
name: `${name.padEnd(20)} (${config.models?.length || 0} models)`,
|
|
1304
|
+
value: name,
|
|
1305
|
+
})),
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
const provider = providersData.providers[providerName];
|
|
1310
|
+
if (!provider) {
|
|
1311
|
+
console.error(chalk.red(`Provider "${providerName}" not found.`));
|
|
1312
|
+
process.exit(1);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
const outputFile = opts.output || `${providerName}-provider.json`;
|
|
1316
|
+
|
|
1317
|
+
const exportData = {
|
|
1318
|
+
version: "2.0",
|
|
1319
|
+
exportedAt: new Date().toISOString(),
|
|
1320
|
+
provider: {
|
|
1321
|
+
...provider,
|
|
1322
|
+
apiKey: provider.apiKey ? "***" : undefined,
|
|
1323
|
+
},
|
|
1324
|
+
};
|
|
1325
|
+
|
|
1326
|
+
try {
|
|
1327
|
+
writeFileSync(outputFile, JSON.stringify(exportData, null, 2));
|
|
1328
|
+
console.log(chalk.green(`✓ Provider exported to: ${outputFile}`));
|
|
1329
|
+
console.log(chalk.dim(` Models: ${provider.models?.length || 0}`));
|
|
1330
|
+
console.log(chalk.dim(" Note: API key has been masked for security."));
|
|
1331
|
+
} catch (err) {
|
|
1332
|
+
console.error(chalk.red(`Failed to export: ${err.message}`));
|
|
1333
|
+
process.exit(1);
|
|
1334
|
+
}
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
// ── cpc import ───────────────────────────────────────────────
|
|
1338
|
+
program
|
|
1339
|
+
.command("import <file>")
|
|
1340
|
+
.description("Import provider configuration from a file")
|
|
1341
|
+
.option("--key <key>", "API key (required if exported key is masked)")
|
|
1342
|
+
.option("--force", "Overwrite existing provider without confirmation")
|
|
1343
|
+
.action(async (file, opts) => {
|
|
1344
|
+
const providersData = loadProviders();
|
|
1345
|
+
|
|
1346
|
+
if (!existsSync(file)) {
|
|
1347
|
+
console.error(chalk.red(`File not found: ${file}`));
|
|
1348
|
+
process.exit(1);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
try {
|
|
1352
|
+
const content = readFileSync(file, "utf8");
|
|
1353
|
+
const importData = JSON.parse(content);
|
|
1354
|
+
|
|
1355
|
+
if (!importData.provider || !importData.provider.name) {
|
|
1356
|
+
console.error(chalk.red("Invalid provider file format."));
|
|
1357
|
+
process.exit(1);
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
const provider = importData.provider;
|
|
1361
|
+
|
|
1362
|
+
if (providersData.providers[provider.name]) {
|
|
1363
|
+
if (!opts.force) {
|
|
1364
|
+
const overwrite = await confirm({
|
|
1365
|
+
message: `Provider "${provider.name}" already exists. Overwrite?`,
|
|
1366
|
+
default: false,
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
if (!overwrite) {
|
|
1370
|
+
console.log(chalk.yellow("Cancelled."));
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
if (provider.apiKey === "***") {
|
|
1377
|
+
if (opts.key) {
|
|
1378
|
+
provider.apiKey = opts.key;
|
|
1379
|
+
} else {
|
|
1380
|
+
provider.apiKey = await input({
|
|
1381
|
+
message: "Enter API key for this provider:",
|
|
1382
|
+
validate: (value) => value.trim() !== "" || "API key is required",
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
providersData.providers[provider.name] = {
|
|
1388
|
+
...provider,
|
|
1389
|
+
importedAt: new Date().toISOString(),
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1392
|
+
saveProviders(providersData);
|
|
1393
|
+
console.log(
|
|
1394
|
+
chalk.green(`✓ Provider "${provider.name}" imported successfully!`),
|
|
1395
|
+
);
|
|
1396
|
+
console.log(chalk.dim(` Models: ${provider.models?.length || 0}`));
|
|
1397
|
+
} catch (err) {
|
|
1398
|
+
console.error(chalk.red(`Failed to import: ${err.message}`));
|
|
1399
|
+
process.exit(1);
|
|
1400
|
+
}
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
// ── cpc list-installed ───────────────────────────────────────
|
|
1404
|
+
program
|
|
1405
|
+
.command("list-installed")
|
|
1406
|
+
.description("List providers installed in agents")
|
|
1407
|
+
.option("--agent <agent>", "Filter by agent")
|
|
1408
|
+
.action((opts) => {
|
|
1409
|
+
const registry = loadRegistry(repoRoot);
|
|
1410
|
+
const agents = getAgents(registry);
|
|
1411
|
+
|
|
1412
|
+
console.log(chalk.bold("\nInstalled Providers\n"));
|
|
1413
|
+
|
|
1414
|
+
const agentIds = opts.agent ? [opts.agent] : Object.keys(agents);
|
|
1415
|
+
|
|
1416
|
+
for (const aid of agentIds) {
|
|
1417
|
+
const agentDef = agents[aid];
|
|
1418
|
+
if (!agentDef) continue;
|
|
1419
|
+
|
|
1420
|
+
const agent = resolveAgentPaths(agentDef);
|
|
1421
|
+
console.log(chalk.bold(`${agent.name} (${aid})`));
|
|
1422
|
+
|
|
1423
|
+
try {
|
|
1424
|
+
switch (aid) {
|
|
1425
|
+
case "claude-code": {
|
|
1426
|
+
const settingsPath = join(agent.home, "settings.json");
|
|
1427
|
+
if (existsSync(settingsPath)) {
|
|
1428
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
1429
|
+
if (settings.env) {
|
|
1430
|
+
const baseUrl = settings.env.ANTHROPIC_BASE_URL;
|
|
1431
|
+
const model = settings.env.ANTHROPIC_MODEL;
|
|
1432
|
+
if (baseUrl) {
|
|
1433
|
+
console.log(` Base URL: ${chalk.dim(baseUrl)}`);
|
|
1434
|
+
console.log(` Model: ${chalk.dim(model || "not set")}`);
|
|
1435
|
+
} else {
|
|
1436
|
+
console.log(chalk.dim(" No custom provider configured"));
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
} else {
|
|
1440
|
+
console.log(chalk.dim(" Settings file not found"));
|
|
1441
|
+
}
|
|
1442
|
+
break;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
case "pi": {
|
|
1446
|
+
const modelsPath = join(agent.home, "models.json");
|
|
1447
|
+
if (existsSync(modelsPath)) {
|
|
1448
|
+
const models = JSON.parse(readFileSync(modelsPath, "utf8"));
|
|
1449
|
+
if (
|
|
1450
|
+
models.providers &&
|
|
1451
|
+
Object.keys(models.providers).length > 0
|
|
1452
|
+
) {
|
|
1453
|
+
for (const [name, config] of Object.entries(
|
|
1454
|
+
models.providers,
|
|
1455
|
+
)) {
|
|
1456
|
+
console.log(` ${chalk.blue(name)}`);
|
|
1457
|
+
console.log(` URL: ${chalk.dim(config.baseUrl)}`);
|
|
1458
|
+
console.log(` API: ${chalk.dim(config.api)}`);
|
|
1459
|
+
console.log(
|
|
1460
|
+
` Models: ${chalk.dim(config.models?.length || 0)}`,
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
} else {
|
|
1464
|
+
console.log(chalk.dim(" No providers configured"));
|
|
1465
|
+
}
|
|
1466
|
+
} else {
|
|
1467
|
+
console.log(chalk.dim(" Models file not found"));
|
|
1468
|
+
}
|
|
1469
|
+
break;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
case "hermes": {
|
|
1473
|
+
const configPath = join(agent.home, "config.yaml");
|
|
1474
|
+
if (existsSync(configPath)) {
|
|
1475
|
+
const configData = yaml.load(readFileSync(configPath, "utf8"));
|
|
1476
|
+
if (configData.model) {
|
|
1477
|
+
console.log(
|
|
1478
|
+
` Provider: ${chalk.dim(configData.model.provider || "not set")}`,
|
|
1479
|
+
);
|
|
1480
|
+
console.log(
|
|
1481
|
+
` Base URL: ${chalk.dim(configData.model.base_url || "not set")}`,
|
|
1482
|
+
);
|
|
1483
|
+
console.log(
|
|
1484
|
+
` Model: ${chalk.dim(configData.model.default || "not set")}`,
|
|
1485
|
+
);
|
|
1486
|
+
} else {
|
|
1487
|
+
console.log(chalk.dim(" No provider configured"));
|
|
1488
|
+
}
|
|
1489
|
+
} else {
|
|
1490
|
+
console.log(chalk.dim(" Config file not found"));
|
|
1491
|
+
}
|
|
1492
|
+
break;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
case "codex": {
|
|
1496
|
+
const configPath = join(agent.home, "config.toml");
|
|
1497
|
+
if (existsSync(configPath)) {
|
|
1498
|
+
const content = readFileSync(configPath, "utf8");
|
|
1499
|
+
const providerMatch = content.match(/model_provider = "(.*?)"/);
|
|
1500
|
+
const modelMatch = content.match(/model = "(.*?)"/);
|
|
1501
|
+
if (providerMatch) {
|
|
1502
|
+
console.log(` Provider: ${chalk.dim(providerMatch[1])}`);
|
|
1503
|
+
console.log(
|
|
1504
|
+
` Model: ${chalk.dim(modelMatch ? modelMatch[1] : "not set")}`,
|
|
1505
|
+
);
|
|
1506
|
+
} else {
|
|
1507
|
+
console.log(chalk.dim(" No provider configured"));
|
|
1508
|
+
}
|
|
1509
|
+
} else {
|
|
1510
|
+
console.log(chalk.dim(" Config file not found"));
|
|
1511
|
+
}
|
|
1512
|
+
break;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
case "openclaw": {
|
|
1516
|
+
const configPath = join(agent.home, "openclaw.json");
|
|
1517
|
+
if (existsSync(configPath)) {
|
|
1518
|
+
const configData = JSON.parse(readFileSync(configPath, "utf8"));
|
|
1519
|
+
if (configData.models && configData.models.providers) {
|
|
1520
|
+
for (const [name, config] of Object.entries(
|
|
1521
|
+
configData.models.providers,
|
|
1522
|
+
)) {
|
|
1523
|
+
console.log(` ${chalk.blue(name)}`);
|
|
1524
|
+
console.log(` URL: ${chalk.dim(config.baseUrl)}`);
|
|
1525
|
+
console.log(` API: ${chalk.dim(config.api)}`);
|
|
1526
|
+
console.log(
|
|
1527
|
+
` Models: ${chalk.dim(config.models?.length || 0)}`,
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
} else {
|
|
1531
|
+
console.log(chalk.dim(" No providers configured"));
|
|
1532
|
+
}
|
|
1533
|
+
} else {
|
|
1534
|
+
console.log(chalk.dim(" Config file not found"));
|
|
1535
|
+
}
|
|
1536
|
+
break;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
case "omp": {
|
|
1540
|
+
const modelsPath = join(agent.home, "models.yml");
|
|
1541
|
+
if (existsSync(modelsPath)) {
|
|
1542
|
+
const modelsData = yaml.load(readFileSync(modelsPath, "utf8"));
|
|
1543
|
+
if (
|
|
1544
|
+
modelsData.providers &&
|
|
1545
|
+
Object.keys(modelsData.providers).length > 0
|
|
1546
|
+
) {
|
|
1547
|
+
for (const [name, config] of Object.entries(
|
|
1548
|
+
modelsData.providers,
|
|
1549
|
+
)) {
|
|
1550
|
+
console.log(` ${chalk.blue(name)}`);
|
|
1551
|
+
console.log(` URL: ${chalk.dim(config.baseUrl)}`);
|
|
1552
|
+
console.log(` API: ${chalk.dim(config.api)}`);
|
|
1553
|
+
console.log(
|
|
1554
|
+
` Models: ${chalk.dim(config.models?.length || 0)}`,
|
|
1555
|
+
);
|
|
1556
|
+
}
|
|
1557
|
+
} else {
|
|
1558
|
+
console.log(chalk.dim(" No providers configured"));
|
|
1559
|
+
}
|
|
1560
|
+
} else {
|
|
1561
|
+
console.log(chalk.dim(" Models file not found"));
|
|
1562
|
+
}
|
|
1563
|
+
break;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
default:
|
|
1567
|
+
console.log(chalk.dim(" Unsupported agent"));
|
|
1568
|
+
}
|
|
1569
|
+
} catch (err) {
|
|
1570
|
+
console.log(chalk.red(` Error reading config: ${err.message}`));
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
console.log();
|
|
1574
|
+
}
|
|
1575
|
+
});
|
|
1576
|
+
}
|