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,172 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * run:一次性把提示词交给模型并打印结果。
5
+ *
6
+ * dgxay run -p "帮我整理这个目录" -C ./project -m work --permission auto
7
+ * dgxay run -p "生成接口文档" -o json
8
+ *
9
+ * 权限提示:带工具的工作模式如果遇到需要确认的操作,单次运行默认拒绝;
10
+ * 明确加 --yes 才会自动放行(只对本进程生效,不写入配置)。
11
+ */
12
+
13
+ const print = require('../cli/print');
14
+ const { parseArgs } = require('../cli/args');
15
+ const { askApproval } = require('../cli/approval');
16
+ const settings = require('../services/settings');
17
+ const { runAgent } = require('../core/agent');
18
+ const { StreamRenderer } = require('../cli/render');
19
+ const { resolveChatArg } = require('./_shared');
20
+
21
+ const options = [
22
+ { name: 'prompt', alias: 'p', type: 'string', valueName: 'text', desc: '提示词' },
23
+ { name: 'mode', alias: 'm', type: 'string', valueName: 'mode', desc: 'chat / work(默认 chat)' },
24
+ { name: 'permission', type: 'string', valueName: 'level', desc: 'none / normal / auto / full' },
25
+ { name: 'workspace', alias: 'C', type: 'string', valueName: 'dir', desc: '工作空间目录' },
26
+ { name: 'provider', type: 'string', valueName: 'name', desc: '供应商 id 或名称' },
27
+ { name: 'model', type: 'string', valueName: 'id', desc: '模型 id' },
28
+ { name: 'skill', type: 'string', valueName: 'name', desc: '启用的技能' },
29
+ { name: 'resume', alias: 'r', type: 'string', valueName: 'id|index', desc: '在指定会话上继续' },
30
+ { name: 'continue', alias: 'c', type: 'boolean', desc: '在最近的会话上继续' },
31
+ { name: 'output', alias: 'o', type: 'string', valueName: 'text|json', desc: '输出格式,默认 text' },
32
+ { name: 'yes', alias: 'y', type: 'boolean', desc: '自动同意授权请求(危险)' },
33
+ { name: 'quiet', alias: 'q', type: 'boolean', desc: '只输出回答正文' }
34
+ ];
35
+
36
+ async function run({ argv, globalOptions, runtime }) {
37
+ const { options: opts, positionals } = parseArgs(argv, options);
38
+ const prompt = String(opts.prompt || positionals.join(' ') || runtime.prompt || '').trim();
39
+ if (!prompt) {
40
+ throw Object.assign(new Error('缺少提示词:请使用 -p "..." 或直接跟问题'), { userError: true });
41
+ }
42
+
43
+ const config = settings.readConfig();
44
+ if (!config.providers?.length) {
45
+ throw Object.assign(new Error('尚未配置供应商,请先运行 dgxay config init'), { userError: true });
46
+ }
47
+
48
+ const mode = String(opts.mode || runtime.mode || 'chat') === 'work' ? 'work' : 'chat';
49
+ const permission = String(opts.permission || runtime.permission || settings.currentPermission(config));
50
+ const workspace = opts.workspace || runtime.workspace || config.work?.workspace || process.cwd();
51
+ const skill = String(opts.skill || runtime.skill || '');
52
+ const outputFormat = String(opts.output || runtime.output || 'text');
53
+ // JSON 输出时把正文让给最终的结构化结果,避免混入流式文本
54
+ const jsonMode = outputFormat === 'json';
55
+ const quiet = Boolean(opts.quiet) || jsonMode;
56
+ // 正文一旦输出就无法回退:提示词只在安静/JSON 模式之外显示
57
+ if (!quiet) print.write(prompt);
58
+
59
+ const chat = resolveChatArg(opts, runtime);
60
+ const history = chat ? require('../core/sessions').buildHistory(chat) : [];
61
+ // 关键:本次提问必须进入消息列表,否则模型只会看到历史(甚至只看得到 system)
62
+ const messages = [...history, { role: 'user', content: prompt }];
63
+
64
+ const renderer = new StreamRenderer({
65
+ output: process.stdout,
66
+ redraw: Boolean(process.stdout.isTTY) && !quiet
67
+ });
68
+ const startedAt = Date.now();
69
+ const result = await runAgent({
70
+ config,
71
+ messages,
72
+ providerId: opts.provider || runtime.provider,
73
+ modelId: opts.model || runtime.model,
74
+ mode,
75
+ permission,
76
+ workspace,
77
+ skill,
78
+ askUser: async (request) => {
79
+ if (opts.yes || runtime.yes) return true;
80
+ return askApproval(request);
81
+ },
82
+ onEvent: (event) => {
83
+ if (jsonMode) {
84
+ // JSON 模式只保留需要人工介入的过程提示(授权、审查),正文交给最终 JSON
85
+ if (!['review', 'review-start', 'waiting', 'file'].includes(event.type)) return;
86
+ if (event.type === 'tool') return;
87
+ }
88
+ if (opts.quiet && !['delta', 'done', 'error', 'aborted'].includes(event.type)) return;
89
+ renderer.handle(event);
90
+ }
91
+ });
92
+
93
+ if (jsonMode) {
94
+ process.stdout.write(
95
+ `${JSON.stringify(
96
+ {
97
+ ok: result.ok,
98
+ aborted: Boolean(result.aborted),
99
+ text: result.text,
100
+ reasoning: result.reasoning,
101
+ usage: result.usage,
102
+ toolLog: result.toolLog,
103
+ files: result.files,
104
+ error: result.error || null,
105
+ elapsedMs: Date.now() - startedAt
106
+ },
107
+ null,
108
+ 2
109
+ )}\n`
110
+ );
111
+ }
112
+
113
+ persistChat({ chat, prompt, mode, permission, result, history });
114
+
115
+ if (result.error) return 1;
116
+ if (result.aborted) return 130;
117
+ return 0;
118
+ }
119
+
120
+ /**
121
+ * 单次运行的会话落库(与桌面版一致:每次提问都会出现在历史里)。
122
+ * 失败不影响命令结果,只在 debug 模式下提示。
123
+ */
124
+ function persistChat({ chat, prompt, mode, permission, result, history }) {
125
+ const sessions = require('../core/sessions');
126
+ try {
127
+ const target =
128
+ chat ||
129
+ sessions.createChat({
130
+ mode,
131
+ skill: ''
132
+ });
133
+ target.mode = mode;
134
+ target.messages.push({
135
+ role: 'user',
136
+ content: prompt,
137
+ ts: Date.now(),
138
+ mode,
139
+ permission,
140
+ modeLabel: mode
141
+ });
142
+ target.messages.push({
143
+ role: 'assistant',
144
+ content: result.text || '',
145
+ ts: Date.now(),
146
+ error: result.error || '',
147
+ toolLog: result.toolLog,
148
+ files: result.files,
149
+ usage: result.usage || null
150
+ });
151
+ if (!target.title || target.title === '新对话') target.title = sessions.titleFrom(prompt);
152
+ sessions.saveChat(target);
153
+ return target;
154
+ } catch (err) {
155
+ if (process.env.DGXAY_DEBUG === '1') process.stderr.write(`[session] 保存失败:${err.message}\n`);
156
+ return null;
157
+ }
158
+ }
159
+
160
+ module.exports = {
161
+ name: 'run',
162
+ aliases: [],
163
+ summary: '单次执行:把提示词发给模型并输出结果',
164
+ usage: 'dgxay run -p "提示词" [-C 目录] [-m work] [--permission auto]',
165
+ options,
166
+ examples: [
167
+ 'dgxay run -p "解释这个仓库的目录结构" -C .',
168
+ 'dgxay run -p "给 README 补充安装说明" -m work -C ./project --permission normal',
169
+ 'dgxay run -p "写一个快排函数" -o json'
170
+ ],
171
+ run
172
+ };
@@ -0,0 +1,94 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * skill:技能列表(对应桌面版「/技能」)。
5
+ *
6
+ * dgxay skill list 列出 ~/.dcb/skills 下的技能
7
+ * dgxay skill show <名称> 查看技能正文
8
+ * dgxay skill path 显示技能目录
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const print = require('../cli/print');
14
+ const { parseArgs, actionFirst } = require('../cli/args');
15
+ const skills = require('../services/skills');
16
+ const cfg = require('../services/config');
17
+ const settings = require('../services/settings');
18
+
19
+ const options = [{ name: 'json', type: 'boolean', desc: '输出 JSON' }];
20
+
21
+ async function run({ argv }) {
22
+ const { action: rawAction, rest, options: opts } = actionFirst(argv, options);
23
+ const action = rawAction || 'list';
24
+
25
+ if (action === 'path') {
26
+ print.write(cfg.SKILLS_DIR);
27
+ return 0;
28
+ }
29
+
30
+ if (action === 'list' || action === 'ls') {
31
+ const list = skills.listSkills();
32
+ if (opts.json) {
33
+ process.stdout.write(`${JSON.stringify(list, null, 2)}\n`);
34
+ return 0;
35
+ }
36
+ print.blank();
37
+ print.heading(`技能(${settings.homeRelative(cfg.SKILLS_DIR)})`);
38
+ if (!list.length) {
39
+ print.hint(' 还没有技能。放一个 name.md 或 <目录>/SKILL.md 到技能目录即可。');
40
+ print.blank();
41
+ return 0;
42
+ }
43
+ const rows = list.map((skill, index) => [String(index + 1), skill.name, skill.description, path.basename(skill.path)]);
44
+ print.table(
45
+ [
46
+ { title: '#', max: 3 },
47
+ { title: '名称', max: 24 },
48
+ { title: '描述', max: 60 },
49
+ { title: '文件', max: 24 }
50
+ ],
51
+ rows
52
+ );
53
+ print.blank();
54
+ print.hint('在对话中使用 /skill <名称> 启用;单次运行可加 --skill <名称>');
55
+ print.blank();
56
+ return 0;
57
+ }
58
+
59
+ if (action === 'show' || action === 'cat') {
60
+ const name = rest[0];
61
+ if (!name) throw Object.assign(new Error('用法:dgxay skill show <名称>'), { userError: true });
62
+ const skill = skills.readSkill(name);
63
+ if (!skill) {
64
+ print.fail(`找不到技能:${name}`);
65
+ return 1;
66
+ }
67
+ print.blank();
68
+ print.heading(`${skill.name}`);
69
+ print.hint(skill.path);
70
+ print.blank();
71
+ print.write(skill.content);
72
+ print.blank();
73
+ return 0;
74
+ }
75
+
76
+ print.fail(`未知子命令:${action}`);
77
+ print.hint('可用:list / show / path');
78
+ return 1;
79
+ }
80
+
81
+ module.exports = {
82
+ name: 'skill',
83
+ aliases: ['skills'],
84
+ summary: '技能列表与查看',
85
+ usage: 'dgxay skill <list|show|path>',
86
+ options,
87
+ subcommands: [
88
+ ['list', '列出技能'],
89
+ ['show <名称>', '查看技能正文'],
90
+ ['path', '显示技能目录']
91
+ ],
92
+ examples: ['dgxay skill list', 'dgxay skill show review', 'dgxay skill path'],
93
+ run
94
+ };
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * workspace:工作空间管理。
5
+ *
6
+ * dgxay workspace 显示当前工作空间
7
+ * dgxay workspace set <目录> 设置工作空间(会记录到最近列表)
8
+ * dgxay workspace list 查看最近使用的工作空间
9
+ * dgxay workspace clear 清除工作空间
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const print = require('../cli/print');
15
+ const { parseArgs, actionFirst } = require('../cli/args');
16
+ const { COLORS } = require('../cli/ansi');
17
+ const settings = require('../services/settings');
18
+
19
+ const options = [{ name: 'both', alias: 'b', type: 'boolean', desc: '同时写入配置(默认只设当前进程)' }];
20
+
21
+ async function run({ argv }) {
22
+ const { action, rest, options: opts } = actionFirst(argv, options);
23
+ const config = settings.readConfig();
24
+
25
+ if (!action || action === 'show' || action === 'current') {
26
+ const workspace = config.work?.workspace || process.cwd();
27
+ print.blank();
28
+ print.keyValue([
29
+ { label: '当前工作空间', value: workspace },
30
+ { label: '进程目录', value: process.cwd() },
31
+ { label: '最近使用', value: (config.work?.recentWorkspaces || []).slice(0, 5).join('\n') || '(无)', multiline: true }
32
+ ]);
33
+ print.blank();
34
+ print.hint('dgxay workspace set <目录> 设置;对话中可用 /cd');
35
+ print.blank();
36
+ return 0;
37
+ }
38
+
39
+ if (action === 'list' || action === 'ls') {
40
+ const recents = config.work?.recentWorkspaces || [];
41
+ print.blank();
42
+ if (!recents.length) {
43
+ print.hint('还没有设置过工作空间');
44
+ print.blank();
45
+ return 0;
46
+ }
47
+ print.heading('最近使用的工作空间');
48
+ recents.forEach((item, index) => {
49
+ const exists = fs.existsSync(item) ? COLORS.green('✓') : COLORS.red('✗');
50
+ print.write(` ${status(index + 1)} ${exists} ${item}`);
51
+ });
52
+ print.blank();
53
+ return 0;
54
+ }
55
+
56
+ if (action === 'set' || action === 'use' || action === 'cd') {
57
+ const target = rest[0] || '';
58
+ if (!target) throw Object.assign(new Error('用法:dgxay workspace set <目录>'), { userError: true });
59
+ const resolved = path.resolve(target);
60
+ try {
61
+ if (!fs.statSync(resolved).isDirectory()) throw new Error('不是文件夹');
62
+ } catch {
63
+ print.fail(`目录不存在:${resolved}`);
64
+ return 1;
65
+ }
66
+ settings.setWorkspace(resolved);
67
+ print.ok(`工作空间:${resolved}`);
68
+ void opts;
69
+ return 0;
70
+ }
71
+
72
+ if (action === 'clear' || action === 'unset') {
73
+ settings.setWorkspace('');
74
+ print.ok('已清除工作空间');
75
+ return 0;
76
+ }
77
+
78
+ // 其他情况当作路径直接设置
79
+ return run({ argv: ['set', action, ...rest] });
80
+ }
81
+
82
+ function status(index) {
83
+ return COLORS.faint(String(index).padStart(2));
84
+ }
85
+
86
+ module.exports = {
87
+ name: 'workspace',
88
+ aliases: ['ws'],
89
+ summary: '工作空间管理',
90
+ usage: 'dgxay workspace [set|list|clear] [目录]',
91
+ options,
92
+ subcommands: [
93
+ ['show', '显示当前工作空间'],
94
+ ['set <目录>', '设置工作空间'],
95
+ ['list', '查看最近使用'],
96
+ ['clear', '清除']
97
+ ],
98
+ examples: ['dgxay workspace set ./project', 'dgxay workspace list'],
99
+ run
100
+ };