dgxay-code-cli 0.0.0-stage → 1.1.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/LICENSE +674 -0
- package/README-zh.md +242 -0
- package/README.md +223 -2
- package/bin/dgxay.js +42 -0
- package/package.json +47 -4
- package/src/cli/ansi.js +539 -0
- package/src/cli/approval.js +186 -0
- package/src/cli/args.js +162 -0
- package/src/cli/command-table.js +52 -0
- package/src/cli/editor.js +828 -0
- package/src/cli/i18n.js +276 -0
- package/src/cli/index.js +228 -0
- package/src/cli/print.js +148 -0
- package/src/cli/render.js +395 -0
- package/src/cli/repl-state.js +85 -0
- package/src/cli/repl.js +1231 -0
- package/src/cli/slash.js +75 -0
- package/src/cli/ui.js +52 -0
- package/src/commands/_shared.js +20 -0
- package/src/commands/chat.js +212 -0
- package/src/commands/config.js +363 -0
- package/src/commands/context.js +43 -0
- package/src/commands/doctor.js +192 -0
- package/src/commands/export.js +102 -0
- package/src/commands/mcp.js +257 -0
- package/src/commands/model.js +223 -0
- package/src/commands/oauth.js +246 -0
- package/src/commands/permission.js +108 -0
- package/src/commands/provider.js +332 -0
- package/src/commands/run.js +172 -0
- package/src/commands/skill.js +94 -0
- package/src/commands/workspace.js +100 -0
- package/src/core/agent.js +420 -0
- package/src/core/llm.js +709 -0
- package/src/core/mcp.js +271 -0
- package/src/core/oauth.js +737 -0
- package/src/core/prompt.js +269 -0
- package/src/core/sessions.js +208 -0
- package/src/core/tools.js +391 -0
- package/src/services/config.js +182 -0
- package/src/services/input.js +91 -0
- package/src/services/session-bus.js +104 -0
- package/src/services/settings.js +340 -0
- package/src/services/skills.js +125 -0
- package/src/services/terminal.js +124 -0
- 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: 'Prompt' },
|
|
23
|
+
{ name: 'mode', alias: 'm', type: 'string', valueName: 'mode', desc: 'chat / work (default chat)' },
|
|
24
|
+
{ name: 'permission', type: 'string', valueName: 'level', desc: 'none / normal / auto / full' },
|
|
25
|
+
{ name: 'workspace', alias: 'C', type: 'string', valueName: 'dir', desc: 'Workspace directory' },
|
|
26
|
+
{ name: 'provider', type: 'string', valueName: 'name', desc: 'Provider id or name' },
|
|
27
|
+
{ name: 'model', type: 'string', valueName: 'id', desc: 'Model id' },
|
|
28
|
+
{ name: 'skill', type: 'string', valueName: 'name', desc: 'Skill to enable' },
|
|
29
|
+
{ name: 'resume', alias: 'r', type: 'string', valueName: 'id|index', desc: 'Continue a conversation by index or id' },
|
|
30
|
+
{ name: 'continue', alias: 'c', type: 'boolean', desc: 'Continue the most recent conversation' },
|
|
31
|
+
{ name: 'output', alias: 'o', type: 'string', valueName: 'text|json', desc: 'Output format, default text' },
|
|
32
|
+
{ name: 'yes', alias: 'y', type: 'boolean', desc: 'Auto-approve permission requests (dangerous)' },
|
|
33
|
+
{ name: 'quiet', alias: 'q', type: 'boolean', desc: 'Only print the reply text' }
|
|
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('Missing prompt: use -p "..." or pass the question directly'), { userError: true });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const config = settings.readConfig();
|
|
44
|
+
if (!config.providers?.length) {
|
|
45
|
+
throw Object.assign(new Error('No provider configured. Run dgxay config init first'), { 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 === 'New conversation') 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] save failed: ${err.message}\n`);
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = {
|
|
161
|
+
name: 'run',
|
|
162
|
+
aliases: [],
|
|
163
|
+
summary: 'One-shot: send a prompt and print the reply',
|
|
164
|
+
usage: 'dgxay run -p "prompt" [-C dir] [-m work] [--permission auto]',
|
|
165
|
+
options,
|
|
166
|
+
examples: [
|
|
167
|
+
'dgxay run -p "explain the structure of this repo" -C .',
|
|
168
|
+
'dgxay run -p "add install notes to the README" -m work -C ./project',
|
|
169
|
+
'dgxay run -p "write a quicksort function" -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: 'Output 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(`Skills (${settings.homeRelative(cfg.SKILLS_DIR)})`);
|
|
38
|
+
if (!list.length) {
|
|
39
|
+
print.hint(' No skills yet. Drop a name.md or <dir>/SKILL.md into the skills directory.');
|
|
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: 'Name', max: 24 },
|
|
48
|
+
{ title: 'Description', max: 60 },
|
|
49
|
+
{ title: 'File', max: 24 }
|
|
50
|
+
],
|
|
51
|
+
rows
|
|
52
|
+
);
|
|
53
|
+
print.blank();
|
|
54
|
+
print.hint('Enable with /skill <name> in a conversation; for a single run add --skill <name>');
|
|
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('Usage: dgxay skill show <name>'), { userError: true });
|
|
62
|
+
const skill = skills.readSkill(name);
|
|
63
|
+
if (!skill) {
|
|
64
|
+
print.fail(`Skill not found: ${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(`Unknown command: ${action}`);
|
|
77
|
+
print.hint('Available: list / show / path');
|
|
78
|
+
return 1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = {
|
|
82
|
+
name: 'skill',
|
|
83
|
+
aliases: ['skills'],
|
|
84
|
+
summary: 'List and view skills',
|
|
85
|
+
usage: 'dgxay skill <list|show|path>',
|
|
86
|
+
options,
|
|
87
|
+
subcommands: [
|
|
88
|
+
['list', 'List skills'],
|
|
89
|
+
['show <name>', 'View skill content'],
|
|
90
|
+
['path', 'Show the skills directory']
|
|
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: 'Also persist to config (default only sets the current process)' }];
|
|
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: 'Current workspace', value: workspace },
|
|
30
|
+
{ label: 'Process directory', value: process.cwd() },
|
|
31
|
+
{ label: 'Recent', value: (config.work?.recentWorkspaces || []).slice(0, 5).join('\n') || '(none)', multiline: true }
|
|
32
|
+
]);
|
|
33
|
+
print.blank();
|
|
34
|
+
print.hint('Set it with dgxay workspace set <dir>; inside a conversation use /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('No workspace has been set yet');
|
|
44
|
+
print.blank();
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
print.heading('Recent workspaces');
|
|
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('Usage: dgxay workspace set <dir>'), { userError: true });
|
|
59
|
+
const resolved = path.resolve(target);
|
|
60
|
+
try {
|
|
61
|
+
if (!fs.statSync(resolved).isDirectory()) throw new Error('Not a directory');
|
|
62
|
+
} catch {
|
|
63
|
+
print.fail(`Directory not found: ${resolved}`);
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
settings.setWorkspace(resolved);
|
|
67
|
+
print.ok(`Workspace: ${resolved}`);
|
|
68
|
+
void opts;
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (action === 'clear' || action === 'unset') {
|
|
73
|
+
settings.setWorkspace('');
|
|
74
|
+
print.ok('Workspace cleared');
|
|
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: 'Workspace management',
|
|
90
|
+
usage: 'dgxay workspace [set|list|clear] [dir]',
|
|
91
|
+
options,
|
|
92
|
+
subcommands: [
|
|
93
|
+
['show', 'Show current workspace'],
|
|
94
|
+
['set <dir>', 'Set the workspace'],
|
|
95
|
+
['list', 'Show recent workspaces'],
|
|
96
|
+
['clear', 'Clear']
|
|
97
|
+
],
|
|
98
|
+
examples: ['dgxay workspace set ./project', 'dgxay workspace list'],
|
|
99
|
+
run
|
|
100
|
+
};
|