dgxay-code-cli 1.0.0 → 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/README-zh.md +50 -8
- package/README.md +50 -9
- package/package.json +1 -1
- package/src/cli/ansi.js +38 -8
- package/src/cli/approval.js +11 -11
- package/src/cli/editor.js +456 -202
- package/src/cli/i18n.js +276 -0
- package/src/cli/index.js +34 -34
- package/src/cli/print.js +5 -5
- package/src/cli/render.js +147 -76
- package/src/cli/repl.js +205 -141
- package/src/cli/slash.js +29 -28
- package/src/cli/ui.js +8 -8
- package/src/commands/chat.js +45 -45
- package/src/commands/config.js +81 -81
- package/src/commands/context.js +5 -5
- package/src/commands/doctor.js +34 -34
- package/src/commands/export.js +13 -13
- package/src/commands/mcp.js +51 -51
- package/src/commands/model.js +38 -38
- package/src/commands/oauth.js +61 -61
- package/src/commands/permission.js +19 -19
- package/src/commands/provider.js +70 -70
- package/src/commands/run.js +20 -20
- package/src/commands/skill.js +15 -15
- package/src/commands/workspace.js +18 -18
- package/src/core/agent.js +19 -18
- package/src/core/llm.js +10 -10
- package/src/core/mcp.js +5 -5
- package/src/core/oauth.js +36 -36
- package/src/core/prompt.js +46 -46
- package/src/core/sessions.js +7 -7
- package/src/core/tools.js +24 -24
- package/src/services/input.js +1 -1
- package/src/services/settings.js +28 -28
- package/src/services/skills.js +1 -1
- package/src/services/terminal.js +3 -3
package/src/cli/slash.js
CHANGED
|
@@ -1,35 +1,36 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* 交互模式里的斜杠命令。
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* 「原本在界面按钮上」的操作,保证去掉 UI 后功能不丢。
|
|
6
|
+
* 桌面版只有 /压缩 与 /技能;CLI 额外提供会话、模型、权限、MCP 等
|
|
7
|
+
* 「原本在界面按钮上」的操作,保证去掉 UI 后功能不丢。
|
|
9
8
|
*/
|
|
10
9
|
|
|
10
|
+
const { t } = require('./i18n');
|
|
11
|
+
|
|
11
12
|
const SLASH_COMMANDS = [
|
|
12
|
-
{ name: '/help', desc: '
|
|
13
|
-
{ name: '/new', desc: '
|
|
14
|
-
{ name: '/list', desc: '
|
|
15
|
-
{ name: '/resume', desc: '
|
|
16
|
-
{ name: '/delete', desc: '
|
|
17
|
-
{ name: '/rename', desc: '
|
|
18
|
-
{ name: '/compress', desc: '
|
|
19
|
-
{ name: '/skill', desc: '
|
|
20
|
-
{ name: '/model', desc: '
|
|
21
|
-
{ name: '/provider', desc: '
|
|
22
|
-
{ name: '/mode', desc: '
|
|
23
|
-
{ name: '/permission', desc: '
|
|
24
|
-
{ name: '/cd', desc: '
|
|
25
|
-
{ name: '/ls', desc: '
|
|
26
|
-
{ name: '/pwd', desc: '
|
|
27
|
-
{ name: '/term', desc: '
|
|
28
|
-
{ name: '/context', desc: '
|
|
29
|
-
{ name: '/mcp', desc: '
|
|
30
|
-
{ name: '/theme', desc: '
|
|
31
|
-
{ name: '/clear', desc: '
|
|
32
|
-
{ name: '/exit', desc: '
|
|
13
|
+
{ name: '/help', desc: 'Show all commands', usage: '/help' },
|
|
14
|
+
{ name: '/new', desc: 'Start a new conversation', usage: '/new' },
|
|
15
|
+
{ name: '/list', desc: 'List saved conversations', usage: '/list' },
|
|
16
|
+
{ name: '/resume', desc: 'Restore a conversation', usage: '/resume <n|id>' },
|
|
17
|
+
{ name: '/delete', desc: 'Delete a conversation', usage: '/delete <n|id>' },
|
|
18
|
+
{ name: '/rename', desc: 'Rename current conversation', usage: '/rename <title>' },
|
|
19
|
+
{ name: '/compress', desc: 'Compress context (lossy)', usage: '/compress', aliases: ['/压缩'] },
|
|
20
|
+
{ name: '/skill', desc: 'List or enable skills', usage: '/skill [name] [prompt]', aliases: ['/技能'] },
|
|
21
|
+
{ name: '/model', desc: 'Show or switch model', usage: '/model [n|id]' },
|
|
22
|
+
{ name: '/provider', desc: 'Show or switch provider', usage: '/provider [n|name]' },
|
|
23
|
+
{ name: '/mode', desc: 'Switch chat / work mode', usage: '/mode [chat|work]' },
|
|
24
|
+
{ name: '/permission', desc: 'Switch permission level', usage: '/permission [level]' },
|
|
25
|
+
{ name: '/cd', desc: 'Change workspace', usage: '/cd <dir>' },
|
|
26
|
+
{ name: '/ls', desc: 'List workspace files', usage: '/ls [dir]' },
|
|
27
|
+
{ name: '/pwd', desc: 'Show current workspace', usage: '/pwd' },
|
|
28
|
+
{ name: '/term', desc: 'Run a shell command', usage: '/term <command>' },
|
|
29
|
+
{ name: '/context', desc: 'Show context usage', usage: '/context' },
|
|
30
|
+
{ name: '/mcp', desc: 'Manage MCP servers', usage: '/mcp [subcommand]' },
|
|
31
|
+
{ name: '/theme', desc: 'Toggle color output', usage: '/theme [on|off]' },
|
|
32
|
+
{ name: '/clear', desc: 'Clear the screen', usage: '/clear' },
|
|
33
|
+
{ name: '/exit', desc: 'Exit', usage: '/exit', aliases: ['/quit', '/q'] }
|
|
33
34
|
];
|
|
34
35
|
|
|
35
36
|
function findCommand(text) {
|
|
@@ -43,7 +44,7 @@ function findCommand(text) {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
|
-
*
|
|
47
|
+
* 行编辑器的补全器:输入以 / 开头且尚未输入参数时给出候选。
|
|
47
48
|
* 返回 { items: [{ label, hint, apply }], title, hint }
|
|
48
49
|
*/
|
|
49
50
|
function completer(value, cursor) {
|
|
@@ -65,8 +66,8 @@ function completer(value, cursor) {
|
|
|
65
66
|
}));
|
|
66
67
|
if (!items.length) return null;
|
|
67
68
|
return {
|
|
68
|
-
title: '
|
|
69
|
-
hint: '
|
|
69
|
+
title: t('menu_title'),
|
|
70
|
+
hint: t('menu_hint'),
|
|
70
71
|
items
|
|
71
72
|
};
|
|
72
73
|
}
|
package/src/cli/ui.js
CHANGED
|
@@ -25,7 +25,7 @@ function ringLine(info) {
|
|
|
25
25
|
function contextReport(info) {
|
|
26
26
|
const lines = [];
|
|
27
27
|
const percent = (info.ratio * 100).toFixed(info.ratio < 0.1 ? 1 : 0);
|
|
28
|
-
lines.push(`${COLORS.accent('
|
|
28
|
+
lines.push(`${COLORS.accent('Context')} ${COLORS.bold(`${percent}%`)} ${COLORS.faint(
|
|
29
29
|
`${formatTokens(info.used)} / ${formatTokens(info.limit)}`
|
|
30
30
|
)}`);
|
|
31
31
|
const width = Math.max(10, Math.min(40, termWidth() - 12));
|
|
@@ -33,19 +33,19 @@ function contextReport(info) {
|
|
|
33
33
|
lines.push(`${COLORS.accent('█'.repeat(filled))}${COLORS.faint('░'.repeat(Math.max(0, width - filled)))}`);
|
|
34
34
|
lines.push('');
|
|
35
35
|
const rows = [
|
|
36
|
-
['
|
|
37
|
-
['
|
|
38
|
-
['
|
|
39
|
-
['
|
|
40
|
-
['
|
|
41
|
-
['
|
|
36
|
+
['Messages', String(info.messageCount)],
|
|
37
|
+
['Context limit', formatTokens(info.limit)],
|
|
38
|
+
['Remaining', formatTokens(Math.max(0, info.limit - info.used))],
|
|
39
|
+
['Model', info.model?.name || 'none'],
|
|
40
|
+
['Provider', info.model?.providerName || ''],
|
|
41
|
+
['Skill', info.skill || 'none']
|
|
42
42
|
];
|
|
43
43
|
const labelWidth = Math.max(...rows.map(([label]) => displayWidth(label)));
|
|
44
44
|
for (const [label, value] of rows) {
|
|
45
45
|
lines.push(` ${COLORS.faint(truncate(label, 12).padEnd(labelWidth))} ${value}`);
|
|
46
46
|
}
|
|
47
47
|
lines.push('');
|
|
48
|
-
lines.push(COLORS.faint('
|
|
48
|
+
lines.push(COLORS.faint('Estimated from character counts; real usage comes from the model. Use /compress when near the limit.'));
|
|
49
49
|
return lines.join('\n');
|
|
50
50
|
}
|
|
51
51
|
|
package/src/commands/chat.js
CHANGED
|
@@ -20,40 +20,40 @@ const { COLORS, renderMarkdown, termWidth } = require('../cli/ansi');
|
|
|
20
20
|
const sessions = require('../core/sessions');
|
|
21
21
|
|
|
22
22
|
const options = [
|
|
23
|
-
{ name: 'print', alias: 'p', type: 'string', valueName: 'text', desc: '
|
|
23
|
+
{ name: 'print', alias: 'p', type: 'string', valueName: 'text', desc: 'Ask directly (same as dgxay run)' },
|
|
24
24
|
{ name: 'mode', alias: 'm', type: 'string', valueName: 'mode', desc: 'chat / work' },
|
|
25
25
|
{ name: 'permission', type: 'string', valueName: 'level', desc: 'none / normal / auto / full' },
|
|
26
|
-
{ name: 'workspace', alias: 'C', type: 'string', valueName: 'dir', desc: '
|
|
27
|
-
{ name: 'yes', alias: 'y', type: 'boolean', desc: '
|
|
28
|
-
{ name: 'yes-all', type: 'boolean', desc: '
|
|
26
|
+
{ name: 'workspace', alias: 'C', type: 'string', valueName: 'dir', desc: 'Workspace directory' },
|
|
27
|
+
{ name: 'yes', alias: 'y', type: 'boolean', desc: 'Auto-approve permission requests' },
|
|
28
|
+
{ name: 'yes-all', type: 'boolean', desc: 'Skip confirmation when clearing conversations' }
|
|
29
29
|
];
|
|
30
30
|
|
|
31
31
|
function relativeTime(ts) {
|
|
32
32
|
const diff = Date.now() - Number(ts || 0);
|
|
33
|
-
if (diff < 60000) return '
|
|
34
|
-
if (diff < 3600000) return `${Math.floor(diff / 60000)}
|
|
35
|
-
if (diff < 86400000) return `${Math.floor(diff / 3600000)}
|
|
33
|
+
if (diff < 60000) return 'just now';
|
|
34
|
+
if (diff < 3600000) return `${Math.floor(diff / 60000)} min ago`;
|
|
35
|
+
if (diff < 86400000) return `${Math.floor(diff / 3600000)} h ago`;
|
|
36
36
|
const date = new Date(ts);
|
|
37
|
-
return `${date.getMonth() + 1}
|
|
37
|
+
return `${date.getMonth() + 1}/${date.getDate()}`;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function listChats() {
|
|
41
41
|
const chats = sessions.listChats();
|
|
42
42
|
print.blank();
|
|
43
43
|
if (!chats.length) {
|
|
44
|
-
print.hint('
|
|
44
|
+
print.hint('No saved conversations yet');
|
|
45
45
|
return 0;
|
|
46
46
|
}
|
|
47
|
-
print.heading(
|
|
47
|
+
print.heading(`Conversation history (${chats.length}, at most ${sessions.MAX_CHATS} kept)`);
|
|
48
48
|
const rows = chats.map((chat, index) => {
|
|
49
49
|
const messages = chat.messages.filter((m) => m.role !== 'terminal');
|
|
50
50
|
const last = messages[messages.length - 1];
|
|
51
51
|
return [
|
|
52
52
|
String(index + 1),
|
|
53
|
-
chat.title || '
|
|
53
|
+
chat.title || 'New conversation',
|
|
54
54
|
String(messages.length),
|
|
55
55
|
relativeTime(chat.updatedAt),
|
|
56
|
-
chat.mode === 'work' ? '
|
|
56
|
+
chat.mode === 'work' ? 'work' : 'chat',
|
|
57
57
|
chat.skill || '',
|
|
58
58
|
last ? String(last.content || '').replace(/\s+/g, ' ').slice(0, 40) : ''
|
|
59
59
|
];
|
|
@@ -61,19 +61,19 @@ function listChats() {
|
|
|
61
61
|
print.table(
|
|
62
62
|
[
|
|
63
63
|
{ title: '#', max: 3 },
|
|
64
|
-
{ title: '
|
|
65
|
-
{ title: '
|
|
66
|
-
{ title: '
|
|
67
|
-
{ title: '
|
|
68
|
-
{ title: '
|
|
69
|
-
{ title: '
|
|
64
|
+
{ title: 'Title', max: 30 },
|
|
65
|
+
{ title: 'Messages', max: 4 },
|
|
66
|
+
{ title: 'Updated', max: 10 },
|
|
67
|
+
{ title: 'Mode', max: 4 },
|
|
68
|
+
{ title: 'Skill', max: 12 },
|
|
69
|
+
{ title: 'Last message', max: 40 }
|
|
70
70
|
],
|
|
71
71
|
rows
|
|
72
72
|
);
|
|
73
73
|
print.blank();
|
|
74
|
-
print.hint('dgxay chat resume 1
|
|
75
|
-
print.hint('dgxay -r 1
|
|
76
|
-
print.hint('dgxay chat show 1
|
|
74
|
+
print.hint('dgxay chat resume 1 resume conversation 1');
|
|
75
|
+
print.hint('dgxay -r 1 resume and enter interactive mode');
|
|
76
|
+
print.hint('dgxay chat show 1 view contents only');
|
|
77
77
|
print.blank();
|
|
78
78
|
return 0;
|
|
79
79
|
}
|
|
@@ -81,14 +81,14 @@ function listChats() {
|
|
|
81
81
|
function showChat(ref) {
|
|
82
82
|
const chat = sessions.resolveChat(ref);
|
|
83
83
|
if (!chat) {
|
|
84
|
-
print.fail(
|
|
84
|
+
print.fail(`Conversation not found: ${ref}`);
|
|
85
85
|
return 1;
|
|
86
86
|
}
|
|
87
87
|
print.blank();
|
|
88
88
|
print.write(`${COLORS.bold(chat.title)} ${COLORS.faint(`${chat.id} · ${relativeTime(chat.updatedAt)}`)}`);
|
|
89
89
|
for (const message of chat.messages) {
|
|
90
90
|
if (message.role === 'user') {
|
|
91
|
-
print.write(`\n${COLORS.accent('
|
|
91
|
+
print.write(`\n${COLORS.accent('▍You')}`);
|
|
92
92
|
print.write(message.content || '');
|
|
93
93
|
} else if (message.role === 'assistant') {
|
|
94
94
|
print.write(`\n${COLORS.accent('▍DgxayCode')}`);
|
|
@@ -96,11 +96,11 @@ function showChat(ref) {
|
|
|
96
96
|
if (message.error) print.write(COLORS.red(message.error));
|
|
97
97
|
if (message.tools?.length) {
|
|
98
98
|
for (const tool of message.tools) {
|
|
99
|
-
print.write(COLORS.faint(` · ${tool.name} ${tool.ok ? '
|
|
99
|
+
print.write(COLORS.faint(` · ${tool.name} ${tool.ok ? 'ok' : 'failed'}`));
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
} else if (message.role === 'terminal') {
|
|
103
|
-
print.write(`\n${COLORS.faint(
|
|
103
|
+
print.write(`\n${COLORS.faint(`▍Terminal $ ${message.command}`)}`);
|
|
104
104
|
const output = `${message.output || ''}${message.stderr ? `\n${message.stderr}` : ''}`;
|
|
105
105
|
print.write(output.slice(0, 4000));
|
|
106
106
|
print.write(COLORS.faint(` exit=${message.exitCode ?? '?'}`));
|
|
@@ -121,20 +121,20 @@ async function run({ argv, runtime }) {
|
|
|
121
121
|
|
|
122
122
|
case 'show':
|
|
123
123
|
case 'view': {
|
|
124
|
-
if (!rest[0]) throw Object.assign(new Error('
|
|
124
|
+
if (!rest[0]) throw Object.assign(new Error('Usage: dgxay chat show <index|id>'), { userError: true });
|
|
125
125
|
return showChat(rest[0]);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
case 'new': {
|
|
129
|
-
print.ok(opts.print ? '
|
|
129
|
+
print.ok(opts.print ? 'New conversation started (single question, not saved to history)' : 'A conversation is created automatically when interactive mode starts');
|
|
130
130
|
return 0;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
case 'resume': {
|
|
134
|
-
if (!rest[0]) throw Object.assign(new Error('
|
|
134
|
+
if (!rest[0]) throw Object.assign(new Error('Usage: dgxay chat resume <index|id>'), { userError: true });
|
|
135
135
|
const chat = sessions.resolveChat(rest[0]);
|
|
136
136
|
if (!chat) {
|
|
137
|
-
print.fail(
|
|
137
|
+
print.fail(`Conversation not found: ${rest[0]}`);
|
|
138
138
|
return 1;
|
|
139
139
|
}
|
|
140
140
|
const repl = require('../cli/repl');
|
|
@@ -143,44 +143,44 @@ async function run({ argv, runtime }) {
|
|
|
143
143
|
|
|
144
144
|
case 'rm':
|
|
145
145
|
case 'delete': {
|
|
146
|
-
if (!rest[0]) throw Object.assign(new Error('
|
|
146
|
+
if (!rest[0]) throw Object.assign(new Error('Usage: dgxay chat rm <index|id>'), { userError: true });
|
|
147
147
|
const chat = sessions.resolveChat(rest[0]);
|
|
148
148
|
if (!chat) {
|
|
149
|
-
print.fail(
|
|
149
|
+
print.fail(`Conversation not found: ${rest[0]}`);
|
|
150
150
|
return 1;
|
|
151
151
|
}
|
|
152
152
|
sessions.deleteChat(chat.id);
|
|
153
|
-
print.ok(
|
|
153
|
+
print.ok(`Deleted "${chat.title}"`);
|
|
154
154
|
return 0;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
case 'rename': {
|
|
158
158
|
const ref = rest[0];
|
|
159
159
|
const title = rest.slice(1).join(' ').trim();
|
|
160
|
-
if (!ref || !title) throw Object.assign(new Error('
|
|
160
|
+
if (!ref || !title) throw Object.assign(new Error('Usage: dgxay chat rename <index|id> <new title>'), { userError: true });
|
|
161
161
|
const chat = sessions.resolveChat(ref);
|
|
162
162
|
if (!chat) {
|
|
163
|
-
print.fail(
|
|
163
|
+
print.fail(`Conversation not found: ${ref}`);
|
|
164
164
|
return 1;
|
|
165
165
|
}
|
|
166
166
|
chat.title = title;
|
|
167
167
|
chat.updatedAt = Date.now();
|
|
168
168
|
sessions.saveChat(chat);
|
|
169
|
-
print.ok(
|
|
169
|
+
print.ok(`Renamed to "${title}"`);
|
|
170
170
|
return 0;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
case 'clear': {
|
|
174
174
|
if (!opts.yesAll && process.stdin.isTTY) {
|
|
175
175
|
const { confirm } = require('../services/input');
|
|
176
|
-
const ok = await confirm('
|
|
176
|
+
const ok = await confirm('Clear all conversation history? (cannot be undone)', false);
|
|
177
177
|
if (!ok) {
|
|
178
|
-
print.hint('
|
|
178
|
+
print.hint('Cancelled');
|
|
179
179
|
return 1;
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
sessions.clearChats();
|
|
183
|
-
print.ok('
|
|
183
|
+
print.ok('All conversations cleared');
|
|
184
184
|
return 0;
|
|
185
185
|
}
|
|
186
186
|
|
|
@@ -196,16 +196,16 @@ async function run({ argv, runtime }) {
|
|
|
196
196
|
module.exports = {
|
|
197
197
|
name: 'chat',
|
|
198
198
|
aliases: ['c'],
|
|
199
|
-
summary: '
|
|
199
|
+
summary: 'Manage conversations: list, show, resume, delete',
|
|
200
200
|
usage: 'dgxay chat <list|show|resume|rm|rename|clear>',
|
|
201
201
|
options,
|
|
202
202
|
subcommands: [
|
|
203
|
-
['list', '
|
|
204
|
-
['show
|
|
205
|
-
['resume
|
|
206
|
-
['rm
|
|
207
|
-
['rename
|
|
208
|
-
['clear', '
|
|
203
|
+
['list', 'List conversation history'],
|
|
204
|
+
['show <index|id>', 'View conversation contents'],
|
|
205
|
+
['resume <index|id>', 'Resume in interactive mode'],
|
|
206
|
+
['rm <index|id>', 'Delete a conversation'],
|
|
207
|
+
['rename <index|id> <title>', 'Rename a conversation'],
|
|
208
|
+
['clear', 'Clear all conversations']
|
|
209
209
|
],
|
|
210
210
|
examples: ['dgxay chat list', 'dgxay chat resume 1', 'dgxay chat rm 2', 'dgxay chat show 1'],
|
|
211
211
|
run
|