dgxay-code-cli 0.0.0-stage → 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.
Files changed (45) hide show
  1. package/LICENSE +674 -0
  2. package/README-zh.md +200 -0
  3. package/README.md +182 -2
  4. package/bin/dgxay.js +42 -0
  5. package/package.json +47 -4
  6. package/src/cli/ansi.js +509 -0
  7. package/src/cli/approval.js +186 -0
  8. package/src/cli/args.js +162 -0
  9. package/src/cli/command-table.js +52 -0
  10. package/src/cli/editor.js +574 -0
  11. package/src/cli/index.js +228 -0
  12. package/src/cli/print.js +148 -0
  13. package/src/cli/render.js +324 -0
  14. package/src/cli/repl-state.js +85 -0
  15. package/src/cli/repl.js +1167 -0
  16. package/src/cli/slash.js +74 -0
  17. package/src/cli/ui.js +52 -0
  18. package/src/commands/_shared.js +20 -0
  19. package/src/commands/chat.js +212 -0
  20. package/src/commands/config.js +363 -0
  21. package/src/commands/context.js +43 -0
  22. package/src/commands/doctor.js +192 -0
  23. package/src/commands/export.js +102 -0
  24. package/src/commands/mcp.js +257 -0
  25. package/src/commands/model.js +223 -0
  26. package/src/commands/oauth.js +246 -0
  27. package/src/commands/permission.js +108 -0
  28. package/src/commands/provider.js +332 -0
  29. package/src/commands/run.js +172 -0
  30. package/src/commands/skill.js +94 -0
  31. package/src/commands/workspace.js +100 -0
  32. package/src/core/agent.js +419 -0
  33. package/src/core/llm.js +709 -0
  34. package/src/core/mcp.js +271 -0
  35. package/src/core/oauth.js +737 -0
  36. package/src/core/prompt.js +269 -0
  37. package/src/core/sessions.js +208 -0
  38. package/src/core/tools.js +391 -0
  39. package/src/services/config.js +182 -0
  40. package/src/services/input.js +91 -0
  41. package/src/services/session-bus.js +104 -0
  42. package/src/services/settings.js +340 -0
  43. package/src/services/skills.js +125 -0
  44. package/src/services/terminal.js +124 -0
  45. package/src/services/version.js +25 -0
@@ -0,0 +1,102 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * export:导出会话(桌面版没有这个能力,CLI 补上)。
5
+ *
6
+ * dgxay export 把最近一个会话输出到 stdout(Markdown)
7
+ * dgxay export -o chat.md 写入文件
8
+ * dgxay export --format json 导出 JSON
9
+ * dgxay export 2 指定会话序号
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const print = require('../cli/print');
15
+ const { actionFirst } = require('../cli/args');
16
+ const sessions = require('../core/sessions');
17
+
18
+ const options = [
19
+ { name: 'format', type: 'string', valueName: 'md|json', desc: '输出格式:markdown(默认)/ json' },
20
+ { name: 'output', alias: 'o', type: 'string', valueName: 'file', desc: '写入文件而不是打印' }
21
+ ];
22
+
23
+ function toMarkdown(chat) {
24
+ const lines = [];
25
+ const date = new Date(chat.createdAt || Date.now());
26
+ lines.push(`# ${chat.title || '新对话'}`);
27
+ lines.push('');
28
+ lines.push(`> ${date.toLocaleString()} · ${chat.mode === 'work' ? '工作模式' : '对话模式'}${chat.skill ? ` · 技能 ${chat.skill}` : ''}`);
29
+ lines.push('');
30
+ for (const message of chat.messages) {
31
+ if (message.role === 'user') {
32
+ lines.push('## 用户');
33
+ lines.push('');
34
+ lines.push(message.content || '');
35
+ lines.push('');
36
+ } else if (message.role === 'assistant') {
37
+ lines.push('## DgxayCode');
38
+ lines.push('');
39
+ lines.push(message.content || '');
40
+ lines.push('');
41
+ if (message.toolLog?.length) {
42
+ lines.push('<details><summary>工具调用</summary>');
43
+ lines.push('');
44
+ for (const tool of message.toolLog) {
45
+ lines.push(`- \`${tool.name}\` ${tool.ok ? '成功' : '失败'}${tool.review?.reason ? ` · ${tool.review.reason}` : ''}`);
46
+ }
47
+ lines.push('');
48
+ lines.push('</details>');
49
+ lines.push('');
50
+ }
51
+ } else if (message.role === 'terminal') {
52
+ lines.push('## 终端');
53
+ lines.push('');
54
+ lines.push('```');
55
+ lines.push(`$ ${message.command}`);
56
+ lines.push(`${message.output || ''}${message.stderr || ''}`.trimEnd());
57
+ lines.push('```');
58
+ lines.push('');
59
+ }
60
+ }
61
+ return lines.join('\n');
62
+ }
63
+
64
+ async function run({ argv }) {
65
+ const { action, rest, options: opts } = actionFirst(argv, options);
66
+ const ref = action || rest[0] || '';
67
+ const chats = sessions.listChats();
68
+ if (!chats.length) {
69
+ print.warn('还没有可以导出的会话');
70
+ return 1;
71
+ }
72
+ const chat = ref ? sessions.resolveChat(ref, chats) : chats[0];
73
+ if (!chat) {
74
+ print.fail(`找不到会话:${ref}`);
75
+ return 1;
76
+ }
77
+ const format = String(opts.format || 'md').toLowerCase();
78
+ const content =
79
+ format === 'json'
80
+ ? `${JSON.stringify(chat, null, 2)}\n`
81
+ : toMarkdown(chat);
82
+
83
+ if (opts.output) {
84
+ const target = path.resolve(opts.output);
85
+ fs.mkdirSync(path.dirname(target), { recursive: true });
86
+ fs.writeFileSync(target, content, 'utf8');
87
+ print.ok(`已导出「${chat.title}」到 ${target}`);
88
+ return 0;
89
+ }
90
+ process.stdout.write(content.endsWith('\n') ? content : `${content}\n`);
91
+ return 0;
92
+ }
93
+
94
+ module.exports = {
95
+ name: 'export',
96
+ aliases: ['dump'],
97
+ summary: '导出会话为 Markdown / JSON',
98
+ usage: 'dgxay export [序号|ID] [--format md|json] [-o 文件]',
99
+ options,
100
+ examples: ['dgxay export', 'dgxay export 2 -o chat.md', 'dgxay export -o chats.json --format json'],
101
+ run
102
+ };
@@ -0,0 +1,257 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * mcp:MCP Server 管理(对应桌面版「设置 → MCP」)。
5
+ *
6
+ * dgxay mcp list
7
+ * dgxay mcp add --name filesystem --command npx --args "-y @modelcontextprotocol/server-filesystem ."
8
+ * dgxay mcp start <名称|ID>
9
+ * dgxay mcp stop <名称|ID>
10
+ * dgxay mcp logs <名称|ID>
11
+ * dgxay mcp rm <名称|ID>
12
+ * dgxay mcp env <名称|ID> --env "KEY=value" 追加环境变量
13
+ *
14
+ * 注意:与桌面版一致,当前版本只管理进程,不把 MCP 工具暴露给模型。
15
+ */
16
+
17
+ const print = require('../cli/print');
18
+ const { parseArgs, actionFirst } = require('../cli/args');
19
+ const { COLORS } = require('../cli/ansi');
20
+ const mcp = require('../core/mcp');
21
+ const cfg = require('../services/config');
22
+ const { ask } = require('../services/input');
23
+
24
+ const options = [
25
+ { name: 'name', type: 'string', valueName: 'text', desc: '显示名称' },
26
+ { name: 'command', type: 'string', valueName: 'cmd', desc: '启动命令(如 npx / uvx)' },
27
+ { name: 'args', type: 'string', valueName: 'text', desc: '命令参数' },
28
+ { name: 'env', type: 'list', valueName: 'K=V', desc: '环境变量,可重复' },
29
+ { name: 'cwd', type: 'string', valueName: 'dir', desc: '工作目录' },
30
+ { name: 'follow', alias: 'f', type: 'boolean', desc: 'logs:持续输出' },
31
+ { name: 'yes', alias: 'y', type: 'boolean', desc: '跳过确认' }
32
+ ];
33
+
34
+ function statusText(status) {
35
+ if (status === 'running') return COLORS.green('运行中');
36
+ if (status === 'error') return COLORS.red('异常');
37
+ if (status === 'stopping') return COLORS.yellow('停止中');
38
+ return COLORS.faint('已停止');
39
+ }
40
+
41
+ function list() {
42
+ const servers = mcp.listServers();
43
+ print.blank();
44
+ if (!servers.length) {
45
+ print.hint(`还没有 MCP Server(配置保存在 ${cfg.MCP_PATH})`);
46
+ print.hint('添加:dgxay mcp add --name filesystem --command npx --args "-y @modelcontextprotocol/server-filesystem ."');
47
+ print.blank();
48
+ return 0;
49
+ }
50
+ print.heading(`MCP Server(${servers.length})`);
51
+ const rows = servers.map((server, index) => [
52
+ String(index + 1),
53
+ server.name,
54
+ statusText(server.status),
55
+ server.pid ? String(server.pid) : '',
56
+ `${server.command} ${(server.args || []).join(' ')}`.trim(),
57
+ server.cwd || ''
58
+ ]);
59
+ print.table(
60
+ [
61
+ { title: '#', max: 3 },
62
+ { title: '名称', max: 20 },
63
+ { title: '状态', max: 8 },
64
+ { title: 'PID', max: 7 },
65
+ { title: '命令', max: 50 },
66
+ { title: '工作目录', max: 30 }
67
+ ],
68
+ rows
69
+ );
70
+ print.blank();
71
+ print.hint('MCP 不会随应用启动而自动运行;每次启动后需要手动 start');
72
+ print.hint('当前版本只管理进程,尚未把 MCP 工具接入模型');
73
+ print.blank();
74
+ return 0;
75
+ }
76
+
77
+ function resolve(ref) {
78
+ const server = mcp.findServer(ref);
79
+ if (!server) {
80
+ print.fail(`找不到 MCP:${ref}`);
81
+ return null;
82
+ }
83
+ return server;
84
+ }
85
+
86
+ async function add(opts) {
87
+ let name = opts.name;
88
+ let command = opts.command;
89
+ if (!name && process.stdin.isTTY) name = String((await ask('名称: ')) || '').trim();
90
+ if (!command && process.stdin.isTTY) command = String((await ask('启动命令(如 npx): ')) || '').trim();
91
+ if (!name || !command) {
92
+ throw Object.assign(new Error('用法:dgxay mcp add --name <名称> --command <命令> [--args "..."]'), {
93
+ userError: true
94
+ });
95
+ }
96
+ const server = mcp.addServer({
97
+ name,
98
+ command,
99
+ args: opts.args || '',
100
+ env: (opts.env || []).join('\n'),
101
+ cwd: opts.cwd || ''
102
+ });
103
+ print.ok(`已保存 MCP「${server.name}」(${server.id})`);
104
+ print.hint(`启动:dgxay mcp start ${server.name}`);
105
+ return 0;
106
+ }
107
+
108
+ function start(ref) {
109
+ const server = resolve(ref);
110
+ if (!server) return 1;
111
+ try {
112
+ const result = mcp.startServer(server.id);
113
+ if (result.status === 'running' && result.pid) print.ok(`已启动 ${server.name}(pid ${result.pid})`);
114
+ else print.ok(`${server.name} 已在运行`);
115
+ print.hint(`查看日志:dgxay mcp logs ${server.name}`);
116
+ return 0;
117
+ } catch (err) {
118
+ print.fail(err.message);
119
+ return 1;
120
+ }
121
+ }
122
+
123
+ function stop(ref) {
124
+ const server = resolve(ref);
125
+ if (!server) return 1;
126
+ const ok = mcp.stopServer(server.id);
127
+ if (ok) print.ok(`已停止 ${server.name}`);
128
+ else print.warn(`${server.name} 当前没有运行`);
129
+ return 0;
130
+ }
131
+
132
+ async function logs(ref, opts) {
133
+ const server = resolve(ref);
134
+ if (!server) return 1;
135
+ const shown = new Set();
136
+ const printLogs = () => {
137
+ const entries = mcp.statusOf(server.id).logs || [];
138
+ for (const entry of entries) {
139
+ const key = `${entry.at}:${entry.stream}:${entry.text}`;
140
+ if (shown.has(key)) continue;
141
+ shown.add(key);
142
+ print.write(entry.stream === 'stderr' ? COLORS.red(entry.text) : entry.text);
143
+ }
144
+ };
145
+ printLogs();
146
+ if (!opts.follow) {
147
+ if (!shown.size) print.hint('暂无日志');
148
+ return 0;
149
+ }
150
+ print.hint('持续输出中,Ctrl+C 退出');
151
+ mcp.setNotifier((event) => {
152
+ if (event.type === 'log' && event.id === server.id) {
153
+ print.write(event.stream === 'stderr' ? COLORS.red(event.text) : event.text);
154
+ }
155
+ });
156
+ await new Promise((resolve) => {
157
+ process.once('SIGINT', () => resolve());
158
+ });
159
+ return 0;
160
+ }
161
+
162
+ async function remove(ref, opts) {
163
+ const server = resolve(ref);
164
+ if (!server) return 1;
165
+ if (!opts.yes && process.stdin.isTTY) {
166
+ const answer = await ask(`删除 MCP「${server.name}」并停止进程?[y/N] `);
167
+ if (!/^y/i.test(String(answer || '').trim())) {
168
+ print.hint('已取消');
169
+ return 1;
170
+ }
171
+ }
172
+ mcp.removeServer(server.id);
173
+ print.ok(`已删除 ${server.name}`);
174
+ return 0;
175
+ }
176
+
177
+ function updateEnv(ref, opts) {
178
+ const server = resolve(ref);
179
+ if (!server) return 1;
180
+ const env = { ...(server.env || {}) };
181
+ for (const pair of opts.env || []) {
182
+ const index = String(pair).indexOf('=');
183
+ if (index > 0) env[String(pair).slice(0, index).trim()] = String(pair).slice(index + 1).trim();
184
+ }
185
+ mcp.addServer({
186
+ name: server.name,
187
+ command: server.command,
188
+ args: server.args,
189
+ env,
190
+ cwd: server.cwd
191
+ });
192
+ print.ok(`已更新 ${server.name} 的环境变量(${Object.keys(env).length} 项)`);
193
+ print.warn('重启后生效:dgxay mcp stop ' + server.name + ' && dgxay mcp start ' + server.name);
194
+ return 0;
195
+ }
196
+
197
+ async function run({ argv }) {
198
+ const { action: rawAction, rest: restArgs, options: opts } = actionFirst(argv, options);
199
+ const action = rawAction || 'list';
200
+ const ref = restArgs.join(' ');
201
+
202
+ // 兼容:dgxay mcp <名称> 视为 start
203
+ if (!['list', 'ls', 'add', 'start', 'run', 'stop', 'logs', 'log', 'rm', 'remove', 'delete', 'env'].includes(action)) {
204
+ return start(restArgs.join(' '));
205
+ }
206
+
207
+ switch (action) {
208
+ case 'list':
209
+ case 'ls':
210
+ return list();
211
+ case 'add':
212
+ return add(opts);
213
+ case 'start':
214
+ case 'run':
215
+ if (!ref) throw Object.assign(new Error('用法:dgxay mcp start <名称|ID>'), { userError: true });
216
+ return start(ref);
217
+ case 'stop':
218
+ if (!ref) throw Object.assign(new Error('用法:dgxay mcp stop <名称|ID>'), { userError: true });
219
+ return stop(ref);
220
+ case 'logs':
221
+ case 'log':
222
+ if (!ref) throw Object.assign(new Error('用法:dgxay mcp logs <名称|ID> [-f]'), { userError: true });
223
+ return logs(ref, opts);
224
+ case 'rm':
225
+ case 'remove':
226
+ case 'delete':
227
+ if (!ref) throw Object.assign(new Error('用法:dgxay mcp rm <名称|ID>'), { userError: true });
228
+ return remove(ref, opts);
229
+ case 'env':
230
+ if (!ref) throw Object.assign(new Error('用法:dgxay mcp env <名称|ID> --env "KEY=value"'), { userError: true });
231
+ return updateEnv(ref, opts);
232
+ default:
233
+ return 1;
234
+ }
235
+ }
236
+
237
+ module.exports = {
238
+ name: 'mcp',
239
+ summary: 'MCP Server 管理',
240
+ usage: 'dgxay mcp <list|add|start|stop|logs|rm|env>',
241
+ options,
242
+ subcommands: [
243
+ ['list', '列出 MCP Server'],
244
+ ['add', '新增配置'],
245
+ ['start <名称>', '启动进程'],
246
+ ['stop <名称>', '停止进程'],
247
+ ['logs <名称> [-f]', '查看日志'],
248
+ ['rm <名称>', '删除'],
249
+ ['env <名称> --env K=V', '更新环境变量']
250
+ ],
251
+ examples: [
252
+ 'dgxay mcp add --name filesystem --command npx --args "-y @modelcontextprotocol/server-filesystem ."',
253
+ 'dgxay mcp start filesystem',
254
+ 'dgxay mcp logs filesystem -f'
255
+ ],
256
+ run
257
+ };
@@ -0,0 +1,223 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * model:模型管理。
5
+ *
6
+ * dgxay model list 列出当前供应商的模型与上下文窗口
7
+ * dgxay model use <id|序号> 设为当前模型
8
+ * dgxay model add <id> [--name X] [--context-window N] [--provider P]
9
+ * dgxay model rm <id> [--provider P]
10
+ * dgxay model fetch [--provider P] [--base-url ...] [--api-key ...]
11
+ */
12
+
13
+ const print = require('../cli/print');
14
+ const { parseArgs, actionFirst } = require('../cli/args');
15
+ const { COLORS } = require('../cli/ansi');
16
+ const settings = require('../services/settings');
17
+ const sessions = require('../core/sessions');
18
+ const { askSecret } = require('../services/input');
19
+
20
+ const options = [
21
+ { name: 'provider', alias: 'p', type: 'string', valueName: 'name', desc: '指定供应商(默认当前供应商)' },
22
+ { name: 'name', type: 'string', valueName: 'text', desc: '显示名称' },
23
+ { name: 'context-window', type: 'number', valueName: 'n', desc: '上下文窗口' },
24
+ { name: 'base-url', type: 'string', valueName: 'url', desc: '拉取列表时使用该地址' },
25
+ { name: 'api-key', type: 'string', valueName: 'key', desc: '拉取列表时使用该密钥' },
26
+ { name: 'add', type: 'list', valueName: 'id', desc: '拉取后把指定模型加入供应商' }
27
+ ];
28
+
29
+ function list(providerRef) {
30
+ const config = settings.readConfig();
31
+ const provider = providerRef ? settings.findProvider(providerRef) : settings.activeProvider(config);
32
+ if (!provider) {
33
+ print.warn('还没有供应商,运行 dgxay config init');
34
+ return 1;
35
+ }
36
+ const models = provider.models || [];
37
+ print.blank();
38
+ print.heading(`${provider.name} 的模型(${models.length})`);
39
+ if (!models.length) {
40
+ print.hint(' 还没有模型,运行 dgxay model add <id>');
41
+ print.blank();
42
+ return 0;
43
+ }
44
+ const active = provider.id === config.activeProviderId ? config.activeModelId : '';
45
+ const rows = models.map((model, index) => [
46
+ String(index + 1),
47
+ model.id === active ? COLORS.accent('❯') : ' ',
48
+ model.name || model.id,
49
+ model.id,
50
+ model.contextWindow ? sessions.formatTokens(model.contextWindow) : COLORS.faint('按协议默认'),
51
+ provider.reviewModelId === model.id ? '审查' : ''
52
+ ]);
53
+ print.table(
54
+ [
55
+ { title: '#', max: 3 },
56
+ { title: '', max: 1 },
57
+ { title: '名称', max: 26 },
58
+ { title: 'ID', max: 32 },
59
+ { title: '上下文', max: 10 },
60
+ { title: '', max: 4 }
61
+ ],
62
+ rows
63
+ );
64
+ print.blank();
65
+ return 0;
66
+ }
67
+
68
+ async function fetchList(opts) {
69
+ const config = settings.readConfig();
70
+ const provider = opts.provider ? settings.findProvider(opts.provider) : settings.activeProvider(config);
71
+ let apiKey = opts.apiKey;
72
+ if (apiKey === undefined && provider && !(provider.authMode === 'oauth') && !provider.apiKey) {
73
+ const secret = await askSecret('API Key: ');
74
+ apiKey = secret || '';
75
+ }
76
+ process.stdout.write(`${COLORS.faint('正在拉取模型列表…')}`);
77
+ try {
78
+ let models;
79
+ if (opts.baseUrl) {
80
+ models = await settings.fetchModels({
81
+ baseUrl: opts.baseUrl,
82
+ apiKey: apiKey ?? '',
83
+ protocol: opts.protocol || provider?.protocol || 'openai'
84
+ });
85
+ } else {
86
+ if (!provider) throw new Error('没有可用的供应商');
87
+ models = await settings.fetchModels({ providerRef: provider.id, apiKey });
88
+ }
89
+ process.stdout.write('\r\x1b[2K');
90
+ print.ok(`拉到 ${models.length} 个模型`);
91
+ const existing = new Set((provider?.models || []).map((m) => m.id));
92
+ for (const id of models) {
93
+ print.write(` ${existing.has(id) ? COLORS.green('✓') : ' '} ${id}`);
94
+ }
95
+ print.blank();
96
+ print.hint('用 dgxay model add <id> 加入供应商;拉取失败时也可以直接手动填写模型 ID');
97
+ return 0;
98
+ } catch (err) {
99
+ process.stdout.write('\r\x1b[2K');
100
+ print.fail(`拉取失败:${err.message}`);
101
+ print.hint('部分网关不支持 /models 接口,可以直接用 dgxay model add <id> 手动添加');
102
+ return 1;
103
+ }
104
+ }
105
+
106
+ function addModel(id, opts) {
107
+ const config = settings.readConfig();
108
+ const provider = opts.provider ? settings.findProvider(opts.provider) : settings.activeProvider(config);
109
+ if (!provider) {
110
+ print.warn('还没有供应商,运行 dgxay config init');
111
+ return 1;
112
+ }
113
+ const models = (provider.models || []).slice();
114
+ if (models.some((m) => m.id === id)) {
115
+ print.warn(`模型 ${id} 已存在`);
116
+ return 1;
117
+ }
118
+ models.push({
119
+ id,
120
+ name: opts.name || id,
121
+ contextWindow: Number(opts.contextWindow) || 0
122
+ });
123
+ const next = (config.providers || []).map((p) => (p.id === provider.id ? { ...p, models } : p));
124
+ const patch = { providers: next };
125
+ if (provider.id === config.activeProviderId && !config.activeModelId) patch.activeModelId = id;
126
+ settings.saveConfig(patch);
127
+ print.ok(`已添加模型 ${id}`);
128
+ return 0;
129
+ }
130
+
131
+ function removeModel(id, opts) {
132
+ const config = settings.readConfig();
133
+ const provider = opts.provider ? settings.findProvider(opts.provider) : settings.activeProvider(config);
134
+ if (!provider) {
135
+ print.warn('还没有供应商');
136
+ return 1;
137
+ }
138
+ const models = (provider.models || []).filter((m) => m.id !== id);
139
+ if (models.length === (provider.models || []).length) {
140
+ print.fail(`供应商 ${provider.name} 下没有模型 ${id}`);
141
+ return 1;
142
+ }
143
+ if (!models.length) {
144
+ print.fail('不能删除最后一个模型,请先添加其他模型');
145
+ return 1;
146
+ }
147
+ const reviewModelId = models.some((m) => m.id === provider.reviewModelId)
148
+ ? provider.reviewModelId
149
+ : models[0].id;
150
+ const next = (config.providers || []).map((p) =>
151
+ p.id === provider.id ? { ...p, models, reviewModelId } : p
152
+ );
153
+ const patch = { providers: next };
154
+ if (config.activeModelId === id) patch.activeModelId = models[0].id;
155
+ settings.saveConfig(patch);
156
+ print.ok(`已删除模型 ${id}`);
157
+ return 0;
158
+ }
159
+
160
+ function useModel(ref, opts) {
161
+ const config = settings.readConfig();
162
+ const provider = opts.provider ? settings.findProvider(opts.provider) : settings.activeProvider(config);
163
+ if (!provider) {
164
+ print.warn('还没有供应商');
165
+ return 1;
166
+ }
167
+ const models = provider.models || [];
168
+ const index = Number(ref);
169
+ const target =
170
+ (Number.isInteger(index) && index >= 1 && index <= models.length ? models[index - 1] : null) ||
171
+ models.find((m) => m.id === ref) ||
172
+ models.find((m) => m.id.includes(ref));
173
+ if (!target) {
174
+ print.fail(`找不到模型:${ref}`);
175
+ return 1;
176
+ }
177
+ settings.setActiveModel(provider.id, target.id);
178
+ print.ok(`当前模型:${target.name || target.id}(${provider.name})`);
179
+ return 0;
180
+ }
181
+
182
+ async function run({ argv }) {
183
+ const { action: rawAction, rest, options: opts } = actionFirst(argv, options);
184
+ const action = rawAction || 'list';
185
+
186
+ // model add deepseek-chat gpt-4o 支持多个
187
+ if (action === 'add') {
188
+ if (!rest.length) throw Object.assign(new Error('用法:dgxay model add <id> [<id>…]'), { userError: true });
189
+ let code = 0;
190
+ for (const id of rest) code = Math.max(code, addModel(id, opts));
191
+ return code;
192
+ }
193
+ if (action === 'rm' || action === 'remove' || action === 'delete') {
194
+ if (!rest[0]) throw Object.assign(new Error('用法:dgxay model rm <id>'), { userError: true });
195
+ return removeModel(rest[0], opts);
196
+ }
197
+ if (action === 'use' || action === 'set') {
198
+ if (!rest[0]) throw Object.assign(new Error('用法:dgxay model use <id|序号>'), { userError: true });
199
+ return useModel(rest[0], opts);
200
+ }
201
+ if (action === 'fetch' || action === 'ls-remote') return fetchList(opts);
202
+ if (action === 'list' || action === 'ls') return list(opts.provider);
203
+
204
+ // 不是已知子命令:当作模型 ID 使用
205
+ return useModel(action, opts);
206
+ }
207
+
208
+ module.exports = {
209
+ name: 'model',
210
+ aliases: ['models'],
211
+ summary: '模型管理:列表、切换、增删、拉取',
212
+ usage: 'dgxay model <list|use|add|rm|fetch>',
213
+ options,
214
+ subcommands: [
215
+ ['list', '列出模型'],
216
+ ['use <id|序号>', '切换当前模型'],
217
+ ['add <id>', '添加模型'],
218
+ ['rm <id>', '删除模型'],
219
+ ['fetch', '从供应商接口拉取模型列表']
220
+ ],
221
+ examples: ['dgxay model list', 'dgxay model use 2', 'dgxay model add gpt-4o --name GPT-4o', 'dgxay model fetch'],
222
+ run
223
+ };