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
package/src/cli/i18n.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 界面文案:默认英文,可用 DGXAY_LANG=zh 切回中文。
|
|
5
|
+
*
|
|
6
|
+
* 只负责「界面」文字;AI 回复语言由系统提示决定(见 core/prompt.js)。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const EN = {
|
|
10
|
+
// 启动 / 横幅
|
|
11
|
+
tagline: 'multi-protocol AI coding assistant',
|
|
12
|
+
hint_start: 'Type / for commands · ! for shell · ↑ history · Ctrl+C to interrupt · Ctrl+D to exit',
|
|
13
|
+
|
|
14
|
+
// 状态行
|
|
15
|
+
status_model: 'model',
|
|
16
|
+
status_workspace: 'workspace',
|
|
17
|
+
status_permission: 'permission',
|
|
18
|
+
status_mode: 'mode',
|
|
19
|
+
mode_chat: 'chat',
|
|
20
|
+
mode_work: 'work',
|
|
21
|
+
|
|
22
|
+
// 权限
|
|
23
|
+
perm_none: 'no permission',
|
|
24
|
+
perm_normal: 'normal',
|
|
25
|
+
perm_auto: 'auto review',
|
|
26
|
+
perm_full: 'full access',
|
|
27
|
+
|
|
28
|
+
// 对话
|
|
29
|
+
you: 'You',
|
|
30
|
+
assistant: 'DgxayCode',
|
|
31
|
+
thinking: 'Thinking',
|
|
32
|
+
thought_for: 'Thought for',
|
|
33
|
+
thinking_now: 'Thinking…',
|
|
34
|
+
tokens: 'tokens',
|
|
35
|
+
in_out: 'in /',
|
|
36
|
+
out: 'out',
|
|
37
|
+
no_content: '(the model returned nothing)',
|
|
38
|
+
generation_error: 'Error',
|
|
39
|
+
stopped: 'Stopped',
|
|
40
|
+
interrupted: 'Interrupting…',
|
|
41
|
+
waiting_confirm: 'waiting for your confirmation',
|
|
42
|
+
reviewing: 'auto-reviewing',
|
|
43
|
+
review_allow: 'auto-review allowed',
|
|
44
|
+
review_suspect: 'auto-review flagged this, asking you',
|
|
45
|
+
empty_input: 'Nothing to send',
|
|
46
|
+
|
|
47
|
+
// 授权
|
|
48
|
+
approval_title: 'Permission required',
|
|
49
|
+
approval_tool: 'AI wants to run a tool',
|
|
50
|
+
approval_terminal: 'AI wants to run a shell command',
|
|
51
|
+
approval_op: 'operation',
|
|
52
|
+
approval_ask: 'Allow this?',
|
|
53
|
+
approval_yes: '[y] yes',
|
|
54
|
+
approval_no: '[n] no',
|
|
55
|
+
approval_allowed: 'allowed',
|
|
56
|
+
approval_denied: 'denied',
|
|
57
|
+
approval_timeout: 'timed out',
|
|
58
|
+
approval_noninteractive: 'not an interactive terminal, denied automatically',
|
|
59
|
+
|
|
60
|
+
// 输入框
|
|
61
|
+
box_placeholder: 'Ask anything…',
|
|
62
|
+
box_hint: 'Enter to send · \\ to continue · Tab to complete · ↑ history',
|
|
63
|
+
|
|
64
|
+
// 斜杠命令菜单
|
|
65
|
+
menu_title: 'Commands',
|
|
66
|
+
menu_hint: '↑↓ select · Tab complete · Enter run · Esc close',
|
|
67
|
+
|
|
68
|
+
// 文件 / 工具
|
|
69
|
+
tool_list: 'List',
|
|
70
|
+
tool_read: 'Read',
|
|
71
|
+
tool_write: 'Write',
|
|
72
|
+
tool_mkdir: 'Create dir',
|
|
73
|
+
tool_delete: 'Delete',
|
|
74
|
+
tool_command: 'Run',
|
|
75
|
+
tool_fetch: 'Fetch',
|
|
76
|
+
tool_mcp: 'Install MCP',
|
|
77
|
+
file_created: 'created',
|
|
78
|
+
file_updated: 'updated',
|
|
79
|
+
file_deleted: 'deleted',
|
|
80
|
+
chars: 'chars',
|
|
81
|
+
lines: 'lines',
|
|
82
|
+
items: 'items',
|
|
83
|
+
exit_code: 'exit code',
|
|
84
|
+
no_output: 'no output',
|
|
85
|
+
|
|
86
|
+
// 斜杠命令输出
|
|
87
|
+
cmd_unknown: 'Unknown command',
|
|
88
|
+
cmd_help_hint: 'Type /help to list all commands',
|
|
89
|
+
new_chat: 'Started a new conversation',
|
|
90
|
+
restored: 'Restored',
|
|
91
|
+
messages: 'messages',
|
|
92
|
+
deleted: 'Deleted',
|
|
93
|
+
renamed: 'Renamed to',
|
|
94
|
+
no_chats: 'No saved conversations yet',
|
|
95
|
+
compressed: 'Context compressed',
|
|
96
|
+
compress_failed: 'Compression failed',
|
|
97
|
+
compress_running: 'Compressing context…',
|
|
98
|
+
compress_nothing: 'Nothing to compress in this conversation',
|
|
99
|
+
wait_current: 'Please wait for the current reply to finish',
|
|
100
|
+
no_model: 'No model configured. Run dgxay config init',
|
|
101
|
+
model_switched: 'Switched to',
|
|
102
|
+
provider_switched: 'Switched to',
|
|
103
|
+
skill_enabled: 'Skill enabled for this conversation',
|
|
104
|
+
skill_none: 'No skills installed. Put a name.md or <dir>/SKILL.md into ~/.dcb/skills',
|
|
105
|
+
skill_not_found: 'Skill not found',
|
|
106
|
+
skills_off: 'Skill disabled',
|
|
107
|
+
perms_title: 'Permissions',
|
|
108
|
+
perm_set: 'Permission set to',
|
|
109
|
+
perm_auto_unavailable: 'Auto review unavailable: no review model configured',
|
|
110
|
+
perm_full_warning: 'Full access lets the AI run any command and write any file without asking',
|
|
111
|
+
mode_set: 'Mode set to',
|
|
112
|
+
workspace_set: 'Workspace',
|
|
113
|
+
dir_not_found: 'Directory not found',
|
|
114
|
+
not_a_dir: 'Not a directory',
|
|
115
|
+
no_workspace: 'no workspace selected',
|
|
116
|
+
context_title: 'Context',
|
|
117
|
+
used: 'used',
|
|
118
|
+
remaining: 'remaining',
|
|
119
|
+
context_note: 'Estimated from character count; real usage is reported by the model.',
|
|
120
|
+
mcp_title: 'MCP servers',
|
|
121
|
+
mcp_none: 'No MCP servers. Add one with dgxay mcp add',
|
|
122
|
+
mcp_unknown: 'Unknown /mcp action',
|
|
123
|
+
bye: 'Bye. Run dgxay -c to continue the last conversation.',
|
|
124
|
+
cancelled: 'Cancelled',
|
|
125
|
+
session_not_found: 'Conversation not found',
|
|
126
|
+
delete_confirm: 'Delete this conversation?',
|
|
127
|
+
terminal_running: 'A command is already running',
|
|
128
|
+
command_empty: 'Enter a command first',
|
|
129
|
+
perm_denied_terminal: 'No permission to run shell commands',
|
|
130
|
+
model_list_title: 'Models',
|
|
131
|
+
provider_list_title: 'Providers',
|
|
132
|
+
current: 'current',
|
|
133
|
+
|
|
134
|
+
// 键盘提示
|
|
135
|
+
key_hint_and: 'and',
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
// 错误
|
|
139
|
+
err_title: 'Error',
|
|
140
|
+
err_network: 'Network error',
|
|
141
|
+
err_unauthorized: 'Unauthorized: check your API key or reconnect the account'
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const ZH = {
|
|
145
|
+
tagline: '多协议 AI 编码助手',
|
|
146
|
+
hint_start: '输入 / 查看命令 · ! 执行终端 · ↑ 历史 · Ctrl+C 中断 · Ctrl+D 退出',
|
|
147
|
+
status_model: '模型',
|
|
148
|
+
status_workspace: '工作空间',
|
|
149
|
+
status_permission: '权限',
|
|
150
|
+
status_mode: '模式',
|
|
151
|
+
mode_chat: '对话',
|
|
152
|
+
mode_work: '工作',
|
|
153
|
+
perm_none: '无权限',
|
|
154
|
+
perm_normal: '普通权限',
|
|
155
|
+
perm_auto: '自动审查',
|
|
156
|
+
perm_full: '完全权限',
|
|
157
|
+
you: '你',
|
|
158
|
+
assistant: 'DgxayCode',
|
|
159
|
+
thinking: '思考',
|
|
160
|
+
thought_for: '已思考',
|
|
161
|
+
thinking_now: '思考中…',
|
|
162
|
+
tokens: 'tokens',
|
|
163
|
+
in_out: 'in /',
|
|
164
|
+
out: 'out',
|
|
165
|
+
no_content: '(模型没有返回内容)',
|
|
166
|
+
generation_error: '出错了',
|
|
167
|
+
stopped: '已停止',
|
|
168
|
+
interrupted: '正在中断…',
|
|
169
|
+
waiting_confirm: '等待确认',
|
|
170
|
+
reviewing: '正在自动审查',
|
|
171
|
+
review_allow: '自动审查放行',
|
|
172
|
+
review_suspect: '自动审查判定可疑,转交确认',
|
|
173
|
+
empty_input: '没有可发送的内容',
|
|
174
|
+
approval_title: '需要授权',
|
|
175
|
+
approval_tool: 'AI 请求执行操作',
|
|
176
|
+
approval_terminal: 'AI 请求执行终端命令',
|
|
177
|
+
approval_op: '操作',
|
|
178
|
+
approval_ask: '允许执行吗?',
|
|
179
|
+
approval_yes: '[y] 允许',
|
|
180
|
+
approval_no: '[n] 拒绝',
|
|
181
|
+
approval_allowed: '允许',
|
|
182
|
+
approval_denied: '已拒绝',
|
|
183
|
+
approval_timeout: '等待超时,已拒绝',
|
|
184
|
+
approval_noninteractive: '当前不是交互终端,已自动拒绝',
|
|
185
|
+
box_placeholder: '问点什么…',
|
|
186
|
+
box_hint: 'Enter 发送 · \\ 续行 · Tab 补全 · ↑ 历史',
|
|
187
|
+
menu_title: '斜杠命令',
|
|
188
|
+
menu_hint: '↑↓ 选择 · Tab 补全 · Enter 执行 · Esc 关闭',
|
|
189
|
+
tool_list: '列出目录',
|
|
190
|
+
tool_read: '读取文件',
|
|
191
|
+
tool_write: '写入文件',
|
|
192
|
+
tool_mkdir: '创建目录',
|
|
193
|
+
tool_delete: '删除',
|
|
194
|
+
tool_command: '执行命令',
|
|
195
|
+
tool_fetch: '访问网络',
|
|
196
|
+
tool_mcp: '安装 MCP',
|
|
197
|
+
file_created: '创建',
|
|
198
|
+
file_updated: '更新',
|
|
199
|
+
file_deleted: '删除',
|
|
200
|
+
chars: '字符',
|
|
201
|
+
lines: '行',
|
|
202
|
+
items: '项',
|
|
203
|
+
exit_code: '退出码',
|
|
204
|
+
no_output: '无输出',
|
|
205
|
+
cmd_unknown: '未知命令',
|
|
206
|
+
cmd_help_hint: '输入 /help 查看全部命令',
|
|
207
|
+
new_chat: '已开始新对话',
|
|
208
|
+
restored: '已恢复',
|
|
209
|
+
messages: '条消息',
|
|
210
|
+
deleted: '已删除',
|
|
211
|
+
renamed: '已重命名为',
|
|
212
|
+
no_chats: '还没有历史对话',
|
|
213
|
+
compressed: '上下文已压缩',
|
|
214
|
+
compress_failed: '压缩失败',
|
|
215
|
+
compress_running: '正在压缩上下文…',
|
|
216
|
+
compress_nothing: '当前对话还没有可压缩的内容',
|
|
217
|
+
wait_current: '请等待当前回复结束',
|
|
218
|
+
no_model: '还没有可用模型,运行 dgxay config init',
|
|
219
|
+
model_switched: '已切换到',
|
|
220
|
+
provider_switched: '已切换到',
|
|
221
|
+
skill_enabled: '已在本次对话启用技能',
|
|
222
|
+
skill_none: '还没有安装技能。把 name.md 或 <目录>/SKILL.md 放到 ~/.dcb/skills',
|
|
223
|
+
skill_not_found: '未找到该技能',
|
|
224
|
+
skills_off: '已关闭技能',
|
|
225
|
+
perms_title: '权限',
|
|
226
|
+
perm_set: '权限已切换为',
|
|
227
|
+
perm_auto_unavailable: '自动审查不可用:尚未配置审查模型',
|
|
228
|
+
perm_full_warning: '完全权限下 AI 可以执行任意命令、读写任意文件,且不会询问',
|
|
229
|
+
mode_set: '模式已切换为',
|
|
230
|
+
workspace_set: '工作空间',
|
|
231
|
+
dir_not_found: '目录不存在',
|
|
232
|
+
not_a_dir: '不是文件夹',
|
|
233
|
+
no_workspace: '未选择工作空间',
|
|
234
|
+
context_title: '上下文',
|
|
235
|
+
used: '已用',
|
|
236
|
+
remaining: '剩余',
|
|
237
|
+
context_note: '按字符启发式估算,实际 token 以模型 usage 为准。',
|
|
238
|
+
mcp_title: 'MCP Server',
|
|
239
|
+
mcp_none: '还没有 MCP Server,用 dgxay mcp add 添加',
|
|
240
|
+
mcp_unknown: '未知的 /mcp 子命令',
|
|
241
|
+
bye: '再见。用 dgxay -c 继续上一次对话。',
|
|
242
|
+
cancelled: '已取消',
|
|
243
|
+
session_not_found: '找不到会话',
|
|
244
|
+
delete_confirm: '删除这段对话?',
|
|
245
|
+
terminal_running: '已有命令正在执行',
|
|
246
|
+
command_empty: '请输入要执行的终端命令',
|
|
247
|
+
perm_denied_terminal: '无权限模式禁止执行终端命令',
|
|
248
|
+
model_list_title: '模型',
|
|
249
|
+
provider_list_title: '供应商',
|
|
250
|
+
current: '当前',
|
|
251
|
+
key_hint_and: '与',
|
|
252
|
+
err_title: '出错了',
|
|
253
|
+
err_network: '网络错误',
|
|
254
|
+
err_unauthorized: '未授权:检查 API Key 或重新连接账户'
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const LANG = String(process.env.DGXAY_LANG || process.env.DGXAY_LANGUAGE || 'en').toLowerCase();
|
|
258
|
+
const TABLE = LANG.startsWith('zh') || LANG === 'cn' ? ZH : EN;
|
|
259
|
+
|
|
260
|
+
const LANGUAGES = { en: EN, zh: ZH };
|
|
261
|
+
|
|
262
|
+
/** 取文案;缺失时回退英文,再回退 key 本身(避免界面出现 undefined) */
|
|
263
|
+
function t(key, ...args) {
|
|
264
|
+
const table = TABLE[key] !== undefined ? TABLE : EN;
|
|
265
|
+
const raw = table[key] !== undefined ? table[key] : key;
|
|
266
|
+
return String(raw).replace(/\{(\d+)\}/g, (m, index) => {
|
|
267
|
+
const value = args[Number(index)];
|
|
268
|
+
return value === undefined ? m : String(value);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function lang() {
|
|
273
|
+
return TABLE === ZH ? 'zh' : 'en';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
module.exports = { t, lang, LANGUAGES, EN, ZH };
|
package/src/cli/index.js
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI 调度入口:全局选项 → 子命令分发 → 无参数时Start the interactive shell。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { COLORS } = require('./ansi');
|
|
8
|
+
const { parseArgs } = require('./args');
|
|
9
|
+
const { COMMANDS, findCommand } = require('./command-table');
|
|
10
|
+
const print = require('./print');
|
|
11
|
+
const { VERSION, BIN } = require('../services/version');
|
|
12
|
+
|
|
13
|
+
const GLOBAL_OPTIONS = [
|
|
14
|
+
{ name: 'help', alias: 'h', type: 'boolean', desc: 'Show help' },
|
|
15
|
+
{ name: 'version', alias: 'v', type: 'boolean', desc: 'Show version' },
|
|
16
|
+
{ name: 'home', type: 'string', valueName: 'dir', desc: 'Override the data directory (default ~/.dcb)' },
|
|
17
|
+
{ name: 'debug', type: 'boolean', desc: 'Print debug information and stack traces' },
|
|
18
|
+
{ name: 'no-color', type: 'boolean', desc: 'Disable colors (same as NO_COLOR=1)' },
|
|
19
|
+
{ name: 'print', alias: 'p', type: 'boolean', desc: 'One-shot: send a prompt, print the reply, exit' },
|
|
20
|
+
{ name: 'prompt', type: 'string', valueName: 'text', desc: 'Prompt for the one-shot run' },
|
|
21
|
+
{ name: 'output', alias: 'o', type: 'string', valueName: 'format', desc: 'Output format: text / json' },
|
|
22
|
+
{ name: 'mode', alias: 'm', type: 'string', valueName: 'mode', desc: 'Mode: chat / work' },
|
|
23
|
+
{ name: 'permission', type: 'string', valueName: 'level', desc: 'Permission: none / normal / auto / full' },
|
|
24
|
+
{ name: 'workspace', alias: 'C', type: 'string', valueName: 'dir', desc: 'Workspace directory' },
|
|
25
|
+
{ name: 'model', type: 'string', valueName: 'id', desc: 'Model id' },
|
|
26
|
+
{ name: 'provider', type: 'string', valueName: 'name', desc: 'Provider id or name' },
|
|
27
|
+
{ name: 'skill', type: 'string', valueName: 'name', desc: 'Skill to enable for this run' },
|
|
28
|
+
{ name: 'resume', alias: 'r', type: 'string', valueName: 'id|index', desc: 'Resume a conversation by index or id' },
|
|
29
|
+
{ name: 'continue', alias: 'c', type: 'boolean', desc: 'Resume the most recent conversation' },
|
|
30
|
+
{ name: 'yes', alias: 'y', type: 'boolean', desc: 'Auto-approve all permission requests (one-shot only)' }
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const LOGO = [
|
|
34
|
+
' ██████╗ ██████╗ ██╗ ██╗ █████╗ ██╗ ██╗',
|
|
35
|
+
' ██╔══██╗██╔════╝ ╚██╗██╔╝██╔══██╗╚██╗ ██╔╝',
|
|
36
|
+
' ██║ ██║██║ ███╗ ╚███╔╝ ███████║ ╚████╔╝ ',
|
|
37
|
+
' ██║ ██║██║ ██║ ██╔██╗ ██╔══██║ ╚██╔╝ ',
|
|
38
|
+
' ██████╔╝╚██████╔╝██╔╝ ██╗██║ ██║ ██║ ',
|
|
39
|
+
' ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ '
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
function showBanner() {
|
|
43
|
+
for (const line of LOGO) process.stdout.write(`${COLORS.accent(line)}\n`);
|
|
44
|
+
process.stdout.write(
|
|
45
|
+
`${COLORS.faint(` v${VERSION} · multi-protocol AI coding assistant · /help for commands · Ctrl+C to exit`)}\n\n`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function showHelp() {
|
|
50
|
+
process.stdout.write(`\n${COLORS.bold(`${BIN} — DgxayCode CLI`)} ${COLORS.faint(`v${VERSION}`)}\n\n`);
|
|
51
|
+
process.stdout.write(`${COLORS.faint('Usage')}\n ${BIN} [options] [command] [args...]\n\n`);
|
|
52
|
+
process.stdout.write(`${COLORS.faint('Quick start')}\n`);
|
|
53
|
+
const rows = [
|
|
54
|
+
[`${BIN}`, 'Start the interactive shell'],
|
|
55
|
+
[`${BIN} "write a python script"`, 'Ask once and exit'],
|
|
56
|
+
[`${BIN} run -p "..." -C ./project`, 'One-shot with a workspace'],
|
|
57
|
+
[`${BIN} config init`, 'First-time setup (provider + API key)'],
|
|
58
|
+
[`${BIN} doctor`, 'Self-check: config, paths, connectivity'],
|
|
59
|
+
[`${BIN} <command> --help`, 'Show detailed usage for a command']
|
|
60
|
+
];
|
|
61
|
+
const width = Math.max(...rows.map(([cmd]) => cmd.length));
|
|
62
|
+
for (const [cmd, desc] of rows) {
|
|
63
|
+
process.stdout.write(` ${COLORS.cyan(cmd.padEnd(width))} ${COLORS.faint(desc)}\n`);
|
|
64
|
+
}
|
|
65
|
+
process.stdout.write(`\n${COLORS.faint('Commands')}\n`);
|
|
66
|
+
const nameWidth = Math.max(...COMMANDS.map((cmd) => cmd.name.length));
|
|
67
|
+
for (const cmd of COMMANDS) {
|
|
68
|
+
const aliases = cmd.aliases?.length ? COLORS.faint(` (${cmd.aliases.join(', ')})`) : '';
|
|
69
|
+
process.stdout.write(` ${COLORS.accent(cmd.name.padEnd(nameWidth))} ${cmd.summary || ''}${aliases}\n`);
|
|
70
|
+
}
|
|
71
|
+
process.stdout.write(`\n${COLORS.faint('Global options')}\n`);
|
|
72
|
+
const flags = GLOBAL_OPTIONS.map((opt) => {
|
|
73
|
+
const alias = opt.alias ? `-${opt.alias}, ` : ' ';
|
|
74
|
+
const value = opt.type !== 'boolean' ? ` <${opt.valueName || opt.type}>` : '';
|
|
75
|
+
return [`${alias}--${opt.name}${value}`, opt.desc];
|
|
76
|
+
});
|
|
77
|
+
const flagWidth = Math.max(...flags.map(([flag]) => flag.length));
|
|
78
|
+
for (const [flag, desc] of flags) {
|
|
79
|
+
process.stdout.write(` ${COLORS.cyan(flag.padEnd(flagWidth))} ${COLORS.faint(desc)}\n`);
|
|
80
|
+
}
|
|
81
|
+
process.stdout.write(
|
|
82
|
+
`\n${COLORS.faint('Notes')} Configuration and conversations live in ${COLORS.faint('~/.dcb')} (override with DGXAY_HOME). Run ${COLORS.faint(
|
|
83
|
+
`${BIN} config path`
|
|
84
|
+
)} to see the paths.\n\n`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** 把全局选项转成运行参数 */
|
|
89
|
+
function runtimeOptions(options) {
|
|
90
|
+
return {
|
|
91
|
+
mode: options.mode,
|
|
92
|
+
permission: options.permission,
|
|
93
|
+
workspace: options.workspace,
|
|
94
|
+
model: options.model,
|
|
95
|
+
provider: options.provider,
|
|
96
|
+
skill: options.skill,
|
|
97
|
+
resume: options.resume,
|
|
98
|
+
continueLast: options.continue,
|
|
99
|
+
yes: options.yes,
|
|
100
|
+
output: options.output || 'text',
|
|
101
|
+
print: Boolean(options.print || options.prompt)
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** --home 必须在读取配置前生效:去掉该参数后重新进入 main */
|
|
106
|
+
function extractHome(argv) {
|
|
107
|
+
const args = argv.slice();
|
|
108
|
+
let home = null;
|
|
109
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
110
|
+
const token = args[i];
|
|
111
|
+
if (token === '--home') {
|
|
112
|
+
if (args[i + 1]) {
|
|
113
|
+
home = args[i + 1];
|
|
114
|
+
args.splice(i, 2);
|
|
115
|
+
return { args, home };
|
|
116
|
+
}
|
|
117
|
+
args.splice(i, 1);
|
|
118
|
+
return { args, home };
|
|
119
|
+
}
|
|
120
|
+
if (token.startsWith('--home=')) {
|
|
121
|
+
home = token.slice('--home='.length);
|
|
122
|
+
args.splice(i, 1);
|
|
123
|
+
return { args, home };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { args, home };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** 清掉本项目模块的缓存,让 config.js 用新的 DGXAY_HOME 重新计算数据目录 */
|
|
130
|
+
function resetModuleCache() {
|
|
131
|
+
process.env.DGXAY_HOME = process.env.DGXAY_HOME || '';
|
|
132
|
+
if (!process.env.DGXAY_HOME) delete process.env.DGXAY_HOME;
|
|
133
|
+
const sep = require('path').sep;
|
|
134
|
+
const root = require('path').join(__dirname, '..');
|
|
135
|
+
for (const key of Object.keys(require.cache)) {
|
|
136
|
+
if (key.startsWith(root) && !key.includes(`${sep}bin${sep}`)) delete require.cache[key];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function main(argv) {
|
|
141
|
+
let args = Array.isArray(argv) ? argv.slice() : [];
|
|
142
|
+
|
|
143
|
+
const extracted = extractHome(args);
|
|
144
|
+
if (extracted.home !== null) {
|
|
145
|
+
process.env.DGXAY_HOME = extracted.home;
|
|
146
|
+
args = extracted.args;
|
|
147
|
+
resetModuleCache();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let parsed;
|
|
151
|
+
try {
|
|
152
|
+
parsed = parseArgs(args, GLOBAL_OPTIONS.map((opt) => ({ ...opt })), { allowUnknown: true });
|
|
153
|
+
} catch (err) {
|
|
154
|
+
print.fail(err.message);
|
|
155
|
+
return 2;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (parsed.options.noColor) process.env.NO_COLOR = '1';
|
|
159
|
+
if (parsed.options.debug) process.env.DGXAY_DEBUG = '1';
|
|
160
|
+
|
|
161
|
+
if (parsed.options.version) {
|
|
162
|
+
process.stdout.write(`${VERSION}\n`);
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const commandName = parsed.positionals[0];
|
|
167
|
+
const command = commandName ? findCommand(commandName) : null;
|
|
168
|
+
|
|
169
|
+
// 只有命令 token 本身放在位置参数里;它后面的全部原始参数原样交给子命令,
|
|
170
|
+
// 否则子命令自己的选项(如 provider add --model)会被全局解析器吞掉。
|
|
171
|
+
const commandIndex = commandName ? args.lastIndexOf(commandName) : -1;
|
|
172
|
+
const commandArgv = command ? args.slice(commandIndex + 1) : [];
|
|
173
|
+
|
|
174
|
+
if (parsed.options.help && !command) {
|
|
175
|
+
showHelp();
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const runtime = runtimeOptions(parsed.options);
|
|
180
|
+
|
|
181
|
+
if (command) {
|
|
182
|
+
if (parsed.options.help) {
|
|
183
|
+
print.usage(command);
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
const result = await command.run({
|
|
188
|
+
argv: commandArgv,
|
|
189
|
+
globalOptions: parsed.options,
|
|
190
|
+
runtime,
|
|
191
|
+
io: { command }
|
|
192
|
+
});
|
|
193
|
+
return typeof result === 'number' ? result : 0;
|
|
194
|
+
} catch (err) {
|
|
195
|
+
if (err?.userError) {
|
|
196
|
+
print.fail(err.message);
|
|
197
|
+
print.hint(`Run ${BIN} ${command.name} --help for usage`);
|
|
198
|
+
return 2;
|
|
199
|
+
}
|
|
200
|
+
print.fail(err?.message || String(err));
|
|
201
|
+
if (process.env.DGXAY_DEBUG === '1' && err?.stack) process.stderr.write(`${err.stack}\n`);
|
|
202
|
+
return 1;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 不是子命令:整串参数当作单次运行的提示词;否则Start the interactive shell
|
|
207
|
+
const prompt = parsed.positionals.join(' ').trim();
|
|
208
|
+
const repl = require('./repl');
|
|
209
|
+
try {
|
|
210
|
+
return await repl.start({
|
|
211
|
+
...runtime,
|
|
212
|
+
initialPrompt: prompt,
|
|
213
|
+
// 只有显式给了提示词或 --print 才是单次运行;
|
|
214
|
+
// 用 -c / -r 指定会话但没有提示词时,仍是交互模式(恢复会话后继续输入)
|
|
215
|
+
single: Boolean(prompt) || Boolean(runtime.print)
|
|
216
|
+
});
|
|
217
|
+
} catch (err) {
|
|
218
|
+
if (err?.userError) {
|
|
219
|
+
print.fail(err.message);
|
|
220
|
+
return 2;
|
|
221
|
+
}
|
|
222
|
+
print.fail(err?.message || String(err));
|
|
223
|
+
if (process.env.DGXAY_DEBUG === '1' && err?.stack) process.stderr.write(`${err.stack}\n`);
|
|
224
|
+
return 1;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
module.exports = { main, GLOBAL_OPTIONS, showHelp, showBanner, LOGO };
|
package/src/cli/print.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 输出辅助:统一成功 / 提示 / 错误 / 表格 / 键值列表的样式。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { COLORS, SIGN, displayWidth, truncate, termWidth, renderMarkdown } = require('./ansi');
|
|
8
|
+
|
|
9
|
+
function write(text = '') {
|
|
10
|
+
process.stdout.write(`${text}\n`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function print(text = '') {
|
|
14
|
+
write(text);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function blank() {
|
|
18
|
+
write('');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function ok(message) {
|
|
22
|
+
write(`${COLORS.green(SIGN.ok)} ${message}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function info(message) {
|
|
26
|
+
write(`${COLORS.accent(SIGN.bullet)} ${message}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function warn(message) {
|
|
30
|
+
write(`${COLORS.yellow('!')} ${message}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function fail(message) {
|
|
34
|
+
process.stderr.write(`${COLORS.red(SIGN.fail)} ${message}\n`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function hint(message) {
|
|
38
|
+
write(COLORS.faint(message));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function heading(text) {
|
|
42
|
+
write(`${COLORS.bold(text)}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function markdown(text, width = termWidth()) {
|
|
46
|
+
process.stdout.write(renderMarkdown(text, { width }));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 按显示宽度补空格(中文占两格,padEnd 会算错) */
|
|
50
|
+
function padWidth(text, width) {
|
|
51
|
+
const visible = displayWidth(text);
|
|
52
|
+
return visible >= width ? String(text) : `${text}${' '.repeat(width - visible)}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 键值列表:条目 { label, value, tone } */
|
|
56
|
+
function keyValue(rows, options = {}) {
|
|
57
|
+
const indent = options.indent || ' ';
|
|
58
|
+
const width = Math.max(...rows.map((row) => displayWidth(String(row.label))));
|
|
59
|
+
for (const row of rows) {
|
|
60
|
+
const label = `${indent}${COLORS.faint(padWidth(String(row.label), width))}`;
|
|
61
|
+
const value = row.tone ? COLORS[row.tone](String(row.value ?? '')) : String(row.value ?? '');
|
|
62
|
+
if (row.multiline) {
|
|
63
|
+
write(label);
|
|
64
|
+
for (const line of String(row.value ?? '').split('\n')) write(`${indent}${' '.repeat(width + 2)}${line}`);
|
|
65
|
+
} else {
|
|
66
|
+
write(`${label} ${value}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** 表格:columns 决定列宽;rows 为二维数组 */
|
|
72
|
+
function table(columns, rows) {
|
|
73
|
+
const widths = columns.map((col, index) => {
|
|
74
|
+
const head = displayWidth(col.title);
|
|
75
|
+
const cells = rows.map((row) => displayWidth(String(row[index] ?? '')));
|
|
76
|
+
const max = Math.min(col.max || 60, Math.max(head, ...cells));
|
|
77
|
+
return Math.max(head, Math.min(max, Math.max(head, ...cells)));
|
|
78
|
+
});
|
|
79
|
+
const total = widths.reduce((sum, w) => sum + w, 0) + (columns.length - 1) * 2;
|
|
80
|
+
const limit = termWidth() - 1;
|
|
81
|
+
if (total > limit) {
|
|
82
|
+
const overflow = total - limit;
|
|
83
|
+
const widest = widths.indexOf(Math.max(...widths));
|
|
84
|
+
widths[widest] = Math.max(6, widths[widest] - overflow);
|
|
85
|
+
}
|
|
86
|
+
const line = (cells, painter) =>
|
|
87
|
+
cells
|
|
88
|
+
.map((cell, index) => {
|
|
89
|
+
const text = truncate(String(cell ?? '').replace(/\n/g, ' '), widths[index]);
|
|
90
|
+
const pad = ' '.repeat(Math.max(0, widths[index] - displayWidth(text)));
|
|
91
|
+
return painter ? painter(`${text}${pad}`) : `${text}${pad}`;
|
|
92
|
+
})
|
|
93
|
+
.join(' ')
|
|
94
|
+
.replace(/\s+$/, '');
|
|
95
|
+
write(line(columns.map((col) => col.title), COLORS.faint));
|
|
96
|
+
write(COLORS.faint(widths.map((w) => '─'.repeat(w)).join(' ')));
|
|
97
|
+
for (const row of rows) write(line(row));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** 简单的使用说明 */
|
|
101
|
+
function usage(command) {
|
|
102
|
+
if (!command) return;
|
|
103
|
+
write(`${COLORS.bold(`dgxay ${command.name}`)}${command.summary ? COLORS.faint(` — ${command.summary}`) : ''}`);
|
|
104
|
+
if (command.usage) {
|
|
105
|
+
blank();
|
|
106
|
+
write(COLORS.faint('Usage'));
|
|
107
|
+
write(` ${command.usage}`);
|
|
108
|
+
}
|
|
109
|
+
if (command.examples?.length) {
|
|
110
|
+
blank();
|
|
111
|
+
write(COLORS.faint('Examples'));
|
|
112
|
+
for (const example of command.examples) write(` ${COLORS.cyan(example)}`);
|
|
113
|
+
}
|
|
114
|
+
if (command.options?.length) {
|
|
115
|
+
blank();
|
|
116
|
+
write(COLORS.faint('Options'));
|
|
117
|
+
const rows = command.options.map((opt) => {
|
|
118
|
+
const alias = opt.alias ? [].concat(opt.alias).map((a) => (a.length === 1 ? `-${a}` : a)).join(', ') : '';
|
|
119
|
+
const name = `--${opt.name}${opt.type && opt.type !== 'boolean' ? ` <${opt.valueName || opt.type}>` : ''}`;
|
|
120
|
+
return [alias ? `${alias}, ${name}` : name, opt.desc || ''];
|
|
121
|
+
});
|
|
122
|
+
rows.push(['-h, --help', 'Show help']);
|
|
123
|
+
const width = Math.max(...rows.map(([flag]) => displayWidth(flag)));
|
|
124
|
+
for (const [flag, desc] of rows) write(` ${COLORS.cyan(padWidth(flag, width))} ${COLORS.faint(desc)}`);
|
|
125
|
+
}
|
|
126
|
+
if (command.subcommands?.length) {
|
|
127
|
+
blank();
|
|
128
|
+
write(COLORS.faint('Commands'));
|
|
129
|
+
const width = Math.max(...command.subcommands.map(([, name]) => displayWidth(name)));
|
|
130
|
+
for (const [name, desc] of command.subcommands) write(` ${COLORS.accent(padWidth(name, width))} ${desc}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = {
|
|
135
|
+
write,
|
|
136
|
+
print,
|
|
137
|
+
blank,
|
|
138
|
+
ok,
|
|
139
|
+
info,
|
|
140
|
+
warn,
|
|
141
|
+
fail,
|
|
142
|
+
hint,
|
|
143
|
+
heading,
|
|
144
|
+
keyValue,
|
|
145
|
+
table,
|
|
146
|
+
usage,
|
|
147
|
+
markdown
|
|
148
|
+
};
|