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/repl.js
CHANGED
|
@@ -16,8 +16,9 @@ const fs = require('fs');
|
|
|
16
16
|
const os = require('os');
|
|
17
17
|
const path = require('path');
|
|
18
18
|
|
|
19
|
-
const { COLORS, SIGN, termWidth, setColorMode, colorEnabled, displayWidth } = require('./ansi');
|
|
20
|
-
const {
|
|
19
|
+
const { COLORS, SIGN, termWidth, setColorMode, colorEnabled, displayWidth, clearScreen, useUnicode } = require('./ansi');
|
|
20
|
+
const { t } = require('./i18n');
|
|
21
|
+
const { LineEditor, createLineReader, detectCapabilities, wrapDisplay } = require('./editor');
|
|
21
22
|
const { parseArgs } = require('./args');
|
|
22
23
|
const print = require('./print');
|
|
23
24
|
const { askApproval } = require('./approval');
|
|
@@ -73,7 +74,7 @@ function createRuntime(options = {}) {
|
|
|
73
74
|
if (options.workspace) {
|
|
74
75
|
const resolved = path.resolve(options.workspace);
|
|
75
76
|
if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) st.cwd = resolved;
|
|
76
|
-
else throw Object.assign(new Error(
|
|
77
|
+
else throw Object.assign(new Error(`Workspace not found: ${options.workspace}`), { userError: true });
|
|
77
78
|
} else if (config.work?.workspace && fs.existsSync(config.work.workspace)) {
|
|
78
79
|
st.cwd = config.work.workspace;
|
|
79
80
|
} else {
|
|
@@ -82,7 +83,7 @@ function createRuntime(options = {}) {
|
|
|
82
83
|
|
|
83
84
|
if (options.resume) {
|
|
84
85
|
st.chat = sessions.getChat(String(options.resume));
|
|
85
|
-
if (!st.chat) throw Object.assign(new Error(
|
|
86
|
+
if (!st.chat) throw Object.assign(new Error(`Conversation not found: ${options.resume}`), { userError: true });
|
|
86
87
|
} else if (options.continueLast) {
|
|
87
88
|
st.chat = sessions.listChats()[0] || null;
|
|
88
89
|
}
|
|
@@ -94,21 +95,21 @@ function createRuntime(options = {}) {
|
|
|
94
95
|
/* ------------------------------ 展示 ------------------------------ */
|
|
95
96
|
|
|
96
97
|
function permissionBadge(st) {
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
return tone(
|
|
98
|
+
const tone =
|
|
99
|
+
st.runtime.permission === 'full' ? COLORS.red : st.runtime.permission === 'auto' ? COLORS.green : COLORS.faint;
|
|
100
|
+
return tone(t(`perm_${st.runtime.permission}`));
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
function modeBadge(st) {
|
|
103
|
-
return st.runtime.mode === 'work' ? COLORS.accent('
|
|
104
|
+
return st.runtime.mode === 'work' ? COLORS.accent(t('mode_work')) : COLORS.faint(t('mode_chat'));
|
|
104
105
|
}
|
|
105
106
|
|
|
107
|
+
/** 输入框上方的一行说明(模型 / 技能) */
|
|
106
108
|
function promptFor(st) {
|
|
107
109
|
const model = settings.activeModel(settings.readConfig());
|
|
108
|
-
const name = model?.name || '
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
return `${marker} ${COLORS.faint(name)}${skillChip} `
|
|
110
|
+
const name = model?.name || t('no_model');
|
|
111
|
+
const skill = st.runtime.skill ? ` · ⛭ ${st.runtime.skill}` : '';
|
|
112
|
+
return `${name}${skill}`;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
function showStatus(st, { withRing = true } = {}) {
|
|
@@ -119,12 +120,12 @@ function showStatus(st, { withRing = true } = {}) {
|
|
|
119
120
|
modeBadge(st),
|
|
120
121
|
permissionBadge(st),
|
|
121
122
|
state.workspaceLabel(st.cwd),
|
|
122
|
-
`${chat ? chat.messages.filter((m) => m.role !== 'terminal').length : 0}
|
|
123
|
+
`${chat ? chat.messages.filter((m) => m.role !== 'terminal').length : 0} ${t('messages')}`
|
|
123
124
|
];
|
|
124
125
|
const left = parts.filter(Boolean).join(COLORS.faint(' · '));
|
|
125
126
|
const right = withRing ? ringLine(info) : '';
|
|
126
127
|
const width = termWidth();
|
|
127
|
-
const gap = Math.max(2, width -
|
|
128
|
+
const gap = Math.max(2, width - stripLen(left) - stripLen(right) - 2);
|
|
128
129
|
process.stdout.write(`\n${left}${' '.repeat(gap)}${right}\n\n`);
|
|
129
130
|
}
|
|
130
131
|
|
|
@@ -138,20 +139,24 @@ function showWelcome(st) {
|
|
|
138
139
|
const model = settings.activeModel(config);
|
|
139
140
|
const chat = st.chat;
|
|
140
141
|
if (chat && chat.messages.length) {
|
|
141
|
-
print.hint(
|
|
142
|
+
print.hint(
|
|
143
|
+
`${t('restored')} "${chat.title}" (${chat.messages.filter((m) => m.role !== 'terminal').length} ${t('messages')})`
|
|
144
|
+
);
|
|
142
145
|
}
|
|
143
146
|
print.keyValue([
|
|
144
|
-
{
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
147
|
+
{
|
|
148
|
+
label: t('status_model'),
|
|
149
|
+
value: model ? `${model.providerName} / ${model.name}` : COLORS.yellow(t('no_model'))
|
|
150
|
+
},
|
|
151
|
+
{ label: t('status_workspace'), value: st.cwd },
|
|
152
|
+
{
|
|
153
|
+
label: t('status_permission'),
|
|
154
|
+
value: `${t(`perm_${st.runtime.permission}`)} · ${settings.PERMISSIONS[st.runtime.permission].desc}`
|
|
155
|
+
},
|
|
156
|
+
{ label: t('status_mode'), value: st.runtime.mode === 'work' ? t('mode_work') : t('mode_chat') }
|
|
148
157
|
]);
|
|
149
|
-
if (!model) {
|
|
150
|
-
print.blank();
|
|
151
|
-
print.warn('还没有可用模型,请输入 /config 或退出后运行 dgxay config init');
|
|
152
|
-
}
|
|
153
158
|
print.blank();
|
|
154
|
-
print.hint('
|
|
159
|
+
print.hint(t('hint_start'));
|
|
155
160
|
print.blank();
|
|
156
161
|
void chat;
|
|
157
162
|
}
|
|
@@ -174,7 +179,7 @@ function persist(st) {
|
|
|
174
179
|
if (!st.chat) return;
|
|
175
180
|
const chat = st.chat;
|
|
176
181
|
if (!chat.messages.length) return;
|
|
177
|
-
if (!chat.title || chat.title === '
|
|
182
|
+
if (!chat.title || chat.title === 'New conversation') {
|
|
178
183
|
const firstUser = chat.messages.find((m) => m.role === 'user' && m.content);
|
|
179
184
|
if (firstUser) chat.title = sessions.titleFrom(firstUser.content);
|
|
180
185
|
}
|
|
@@ -190,6 +195,33 @@ function historyForAgent(st) {
|
|
|
190
195
|
return sessions.trimHistory(sessions.buildHistory(st.chat), HISTORY_LIMIT);
|
|
191
196
|
}
|
|
192
197
|
|
|
198
|
+
/**
|
|
199
|
+
* 回显用户消息:与输入框同样的边框样式,让对话记录看起来是「框」。
|
|
200
|
+
*/
|
|
201
|
+
function printUserMessage(text) {
|
|
202
|
+
const width = Math.max(20, Math.min(termWidth() - 1, 100));
|
|
203
|
+
const inner = width - 2;
|
|
204
|
+
const unicode = useUnicode();
|
|
205
|
+
const box = unicode
|
|
206
|
+
? { tl: '┌', tr: '┐', bl: '└', br: '┘', h: '─', v: '│' }
|
|
207
|
+
: { tl: '+', tr: '+', bl: '+', br: '+', h: '-', v: '|' };
|
|
208
|
+
|
|
209
|
+
const lines = [];
|
|
210
|
+
for (const raw of String(text).split('\n')) {
|
|
211
|
+
for (const piece of wrapDisplay(raw, Math.max(4, inner - 2))) lines.push(piece);
|
|
212
|
+
}
|
|
213
|
+
if (!lines.length) lines.push('');
|
|
214
|
+
|
|
215
|
+
const label = ` ${t('you')} `;
|
|
216
|
+
const topFill = Math.max(0, width - 2 - displayWidth(label));
|
|
217
|
+
process.stdout.write(`\n${COLORS.accent(box.tl + label + box.h.repeat(topFill) + box.tr)}\n`);
|
|
218
|
+
for (const line of lines) {
|
|
219
|
+
const pad = Math.max(0, inner - displayWidth(line) - 1);
|
|
220
|
+
process.stdout.write(`${COLORS.accent(box.v)} ${line}${' '.repeat(pad)}${COLORS.accent(box.v)}\n`);
|
|
221
|
+
}
|
|
222
|
+
process.stdout.write(`${COLORS.accent(box.bl + box.h.repeat(width - 2) + box.br)}\n`);
|
|
223
|
+
}
|
|
224
|
+
|
|
193
225
|
/* ------------------------------ 对话 ------------------------------ */
|
|
194
226
|
|
|
195
227
|
async function sendMessage(st, editor, text, options = {}) {
|
|
@@ -202,7 +234,7 @@ async function sendMessage(st, editor, text, options = {}) {
|
|
|
202
234
|
activeModelId: st.runtime.modelId || config.activeModelId
|
|
203
235
|
});
|
|
204
236
|
if (!model) {
|
|
205
|
-
print.warn('
|
|
237
|
+
print.warn(t('no_model'));
|
|
206
238
|
return 1;
|
|
207
239
|
}
|
|
208
240
|
|
|
@@ -211,6 +243,12 @@ async function sendMessage(st, editor, text, options = {}) {
|
|
|
211
243
|
chat.skill = st.runtime.skill;
|
|
212
244
|
pushMessage(chat, { role: 'user', content: promptText, mode: st.runtime.mode, permission: st.runtime.permission });
|
|
213
245
|
persist(st);
|
|
246
|
+
// 记住上一条提示词,供输入框按 ↑ 回填
|
|
247
|
+
st.lastUserPrompt = promptText;
|
|
248
|
+
if (editor) editor.lastUserPrompt = promptText;
|
|
249
|
+
|
|
250
|
+
// 把用户消息按输入框的样式回显一遍,形成对话记录
|
|
251
|
+
printUserMessage(promptText);
|
|
214
252
|
|
|
215
253
|
const jsonMode = String(options.output || 'text') === 'json';
|
|
216
254
|
const renderer = new StreamRenderer({
|
|
@@ -226,7 +264,7 @@ async function sendMessage(st, editor, text, options = {}) {
|
|
|
226
264
|
if (!st.streaming || st.abortRequested) return false;
|
|
227
265
|
st.abortRequested = true;
|
|
228
266
|
controller.abort();
|
|
229
|
-
process.stdout.write(`${COLORS.yellow(`${SIGN.bullet}
|
|
267
|
+
process.stdout.write(`${COLORS.yellow(`${SIGN.bullet} ${t('interrupted')}`)}\n`);
|
|
230
268
|
return true;
|
|
231
269
|
});
|
|
232
270
|
|
|
@@ -275,7 +313,7 @@ async function sendMessage(st, editor, text, options = {}) {
|
|
|
275
313
|
elapsedMs: Date.now() - startedAt
|
|
276
314
|
};
|
|
277
315
|
pushMessage(chat, assistantMessage);
|
|
278
|
-
if (chat.title === '
|
|
316
|
+
if (chat.title === 'New conversation') chat.title = sessions.titleFrom(promptText);
|
|
279
317
|
persist(st);
|
|
280
318
|
st.lastResponse = result;
|
|
281
319
|
updateBusState(st);
|
|
@@ -289,17 +327,17 @@ async function sendMessage(st, editor, text, options = {}) {
|
|
|
289
327
|
async function runTerminal(st, editor, rawCommand) {
|
|
290
328
|
const command = String(rawCommand || '').trim();
|
|
291
329
|
if (!command) {
|
|
292
|
-
print.warn('
|
|
330
|
+
print.warn('Enter a command first');
|
|
293
331
|
return 1;
|
|
294
332
|
}
|
|
295
333
|
if (st.terminalBusy) {
|
|
296
|
-
print.warn('
|
|
334
|
+
print.warn('A command is already running');
|
|
297
335
|
return 1;
|
|
298
336
|
}
|
|
299
337
|
const config = settings.readConfig();
|
|
300
338
|
const permission = st.runtime.permission;
|
|
301
339
|
if (permission === 'none') {
|
|
302
|
-
print.warn('
|
|
340
|
+
print.warn('No permission to run shell commands');
|
|
303
341
|
return 1;
|
|
304
342
|
}
|
|
305
343
|
|
|
@@ -307,9 +345,9 @@ async function runTerminal(st, editor, rawCommand) {
|
|
|
307
345
|
editor.suspend();
|
|
308
346
|
const allowed = await askApproval(
|
|
309
347
|
{
|
|
310
|
-
title: '
|
|
348
|
+
title: t('approval_terminal'),
|
|
311
349
|
operation: 'run_terminal',
|
|
312
|
-
detail:
|
|
350
|
+
detail: `Command: ${command}`,
|
|
313
351
|
command,
|
|
314
352
|
workspace: st.cwd,
|
|
315
353
|
review: '',
|
|
@@ -352,14 +390,14 @@ async function runTerminal(st, editor, rawCommand) {
|
|
|
352
390
|
message.exitCode = Number.isInteger(result.code) ? result.code : null;
|
|
353
391
|
message.status = result.aborted ? 'done' : result.code === 0 ? 'done' : 'error';
|
|
354
392
|
if (result.error) message.error = result.error;
|
|
355
|
-
if (chat.title === '
|
|
393
|
+
if (chat.title === 'New conversation') chat.title = sessions.titleFrom(`Terminal · ${command}`);
|
|
356
394
|
chat.updatedAt = Date.now();
|
|
357
395
|
persist(st);
|
|
358
396
|
|
|
359
397
|
const code = message.exitCode;
|
|
360
|
-
if (result.aborted) print.hint('
|
|
361
|
-
else if (code === 0) process.stdout.write(COLORS.green(` ${SIGN.ok}
|
|
362
|
-
else process.stdout.write(COLORS.red(` ${SIGN.fail}
|
|
398
|
+
if (result.aborted) print.hint(t('stopped'));
|
|
399
|
+
else if (code === 0) process.stdout.write(COLORS.green(` ${SIGN.ok} done\n`));
|
|
400
|
+
else process.stdout.write(COLORS.red(` ${SIGN.fail} ${t('exit_code')} ${code ?? '?'}\n`));
|
|
363
401
|
editor.resume();
|
|
364
402
|
return code === 0 ? 0 : 1;
|
|
365
403
|
}
|
|
@@ -394,7 +432,7 @@ async function handleSlash(st, editor, input) {
|
|
|
394
432
|
const command = findCommand(head);
|
|
395
433
|
|
|
396
434
|
if (!command) {
|
|
397
|
-
print.warn(
|
|
435
|
+
print.warn(`Unknown command: ${head}. Type /help to list commands`);
|
|
398
436
|
return;
|
|
399
437
|
}
|
|
400
438
|
|
|
@@ -406,7 +444,7 @@ async function handleSlash(st, editor, input) {
|
|
|
406
444
|
persist(st);
|
|
407
445
|
st.chat = sessions.createChat({ mode: st.runtime.mode, skill: st.runtime.skill });
|
|
408
446
|
updateBusState(st);
|
|
409
|
-
print.ok('
|
|
447
|
+
print.ok(t('new_chat'));
|
|
410
448
|
return;
|
|
411
449
|
}
|
|
412
450
|
case '/list':
|
|
@@ -415,7 +453,7 @@ async function handleSlash(st, editor, input) {
|
|
|
415
453
|
case '/resume': {
|
|
416
454
|
const chats = sessions.listChats();
|
|
417
455
|
if (!chats.length) {
|
|
418
|
-
print.warn('
|
|
456
|
+
print.warn(t('no_chats'));
|
|
419
457
|
return;
|
|
420
458
|
}
|
|
421
459
|
let target;
|
|
@@ -426,13 +464,13 @@ async function handleSlash(st, editor, input) {
|
|
|
426
464
|
return;
|
|
427
465
|
}
|
|
428
466
|
if (!target) {
|
|
429
|
-
print.fail(
|
|
467
|
+
print.fail(`Conversation not found: ${rest}`);
|
|
430
468
|
return;
|
|
431
469
|
}
|
|
432
470
|
persist(st);
|
|
433
471
|
st.chat = target;
|
|
434
472
|
updateBusState(st);
|
|
435
|
-
print.ok(
|
|
473
|
+
print.ok(`Restored "${target.title}" (${target.messages.filter((m) => m.role !== 'terminal').length} messages)`);
|
|
436
474
|
printThread(st);
|
|
437
475
|
return;
|
|
438
476
|
}
|
|
@@ -440,27 +478,27 @@ async function handleSlash(st, editor, input) {
|
|
|
440
478
|
const chats = sessions.listChats();
|
|
441
479
|
const target = rest ? sessions.resolveChat(rest, chats) : st.chat;
|
|
442
480
|
if (!target) {
|
|
443
|
-
print.fail(
|
|
481
|
+
print.fail(`Conversation not found: ${rest}`);
|
|
444
482
|
return;
|
|
445
483
|
}
|
|
446
484
|
sessions.deleteChat(target.id);
|
|
447
485
|
if (st.chat?.id === target.id) st.chat = sessions.listChats()[0] || null;
|
|
448
486
|
updateBusState(st);
|
|
449
|
-
print.ok(
|
|
487
|
+
print.ok(`Deleted "${target.title}"`);
|
|
450
488
|
return;
|
|
451
489
|
}
|
|
452
490
|
case '/rename': {
|
|
453
491
|
if (!st.chat) {
|
|
454
|
-
print.warn('
|
|
492
|
+
print.warn('No active conversation');
|
|
455
493
|
return;
|
|
456
494
|
}
|
|
457
495
|
if (!rest) {
|
|
458
|
-
print.fail('
|
|
496
|
+
print.fail('Usage: /rename <title>');
|
|
459
497
|
return;
|
|
460
498
|
}
|
|
461
499
|
st.chat.title = rest;
|
|
462
500
|
persist(st);
|
|
463
|
-
print.ok(
|
|
501
|
+
print.ok(`Renamed to "${rest}"`);
|
|
464
502
|
return;
|
|
465
503
|
}
|
|
466
504
|
case '/compress':
|
|
@@ -512,13 +550,13 @@ async function handleSlash(st, editor, input) {
|
|
|
512
550
|
st.exitRequested = true;
|
|
513
551
|
return;
|
|
514
552
|
default:
|
|
515
|
-
print.warn(
|
|
553
|
+
print.warn(`Not implemented yet: ${command.name}`);
|
|
516
554
|
}
|
|
517
555
|
}
|
|
518
556
|
|
|
519
557
|
function showHelp() {
|
|
520
558
|
print.blank();
|
|
521
|
-
print.heading('
|
|
559
|
+
print.heading(t('menu_title'));
|
|
522
560
|
// 命令名列按显示宽度对齐(中文别名占两格,不能用 padEnd)
|
|
523
561
|
const nameWidth = Math.max(...SLASH_COMMANDS.map((cmd) => displayWidth(cmd.name))) + 2;
|
|
524
562
|
for (const cmd of SLASH_COMMANDS) {
|
|
@@ -527,20 +565,20 @@ function showHelp() {
|
|
|
527
565
|
if (cmd.usage !== cmd.name) print.write(` ${' '.repeat(nameWidth)}${COLORS.faint(cmd.usage)}`);
|
|
528
566
|
}
|
|
529
567
|
print.blank();
|
|
530
|
-
print.heading('
|
|
568
|
+
print.heading('Keys');
|
|
531
569
|
const tips = [
|
|
532
|
-
['!
|
|
533
|
-
['\\
|
|
534
|
-
['Tab', '
|
|
535
|
-
['Ctrl+C', '
|
|
536
|
-
['Ctrl+D', '
|
|
537
|
-
['↑ / ↓', '
|
|
570
|
+
['! cmd', 'Run a shell command; output joins the context (e.g. !git status)'],
|
|
571
|
+
['\\', 'Continue on the next line (multi-line input)'],
|
|
572
|
+
['Tab', 'Open / cycle the completion menu'],
|
|
573
|
+
['Ctrl+C', 'Interrupt generation · exit when idle · clear input'],
|
|
574
|
+
['Ctrl+D', 'Exit'],
|
|
575
|
+
['↑ / ↓', 'Recalls the previous prompt']
|
|
538
576
|
];
|
|
539
577
|
for (const [key, desc] of tips) {
|
|
540
578
|
print.write(` ${COLORS.cyan(key)} ${desc}`);
|
|
541
579
|
}
|
|
542
580
|
print.blank();
|
|
543
|
-
print.hint(
|
|
581
|
+
print.hint(`Everything is also available as a subcommand: ${BIN} provider list · ${BIN} mcp start`);
|
|
544
582
|
print.blank();
|
|
545
583
|
}
|
|
546
584
|
|
|
@@ -548,10 +586,10 @@ function listChats(st) {
|
|
|
548
586
|
const chats = sessions.listChats();
|
|
549
587
|
print.blank();
|
|
550
588
|
if (!chats.length) {
|
|
551
|
-
print.hint('
|
|
589
|
+
print.hint(t('no_chats'));
|
|
552
590
|
return;
|
|
553
591
|
}
|
|
554
|
-
print.heading(
|
|
592
|
+
print.heading(`Conversations (${chats.length})`);
|
|
555
593
|
const rows = chats.map((chat, index) => {
|
|
556
594
|
const messages = chat.messages.filter((m) => m.role !== 'terminal');
|
|
557
595
|
const last = messages[messages.length - 1];
|
|
@@ -562,10 +600,10 @@ function listChats(st) {
|
|
|
562
600
|
return [
|
|
563
601
|
chat.id === st.chat?.id ? COLORS.accent('*') : ' ',
|
|
564
602
|
String(index + 1),
|
|
565
|
-
chat.title || '
|
|
603
|
+
chat.title || 'New conversation',
|
|
566
604
|
String(messages.length),
|
|
567
605
|
stamp,
|
|
568
|
-
chat.mode === 'work' ? '
|
|
606
|
+
chat.mode === 'work' ? t('mode_work') : t('mode_chat'),
|
|
569
607
|
last?.content ? last.content.slice(0, 40) : ''
|
|
570
608
|
];
|
|
571
609
|
});
|
|
@@ -573,16 +611,16 @@ function listChats(st) {
|
|
|
573
611
|
[
|
|
574
612
|
{ title: '', max: 1 },
|
|
575
613
|
{ title: '#', max: 3 },
|
|
576
|
-
{ title: '
|
|
577
|
-
{ title: '
|
|
578
|
-
{ title: '
|
|
579
|
-
{ title: '
|
|
580
|
-
{ title: '
|
|
614
|
+
{ title: 'Title', max: 30 },
|
|
615
|
+
{ title: 'Msgs', max: 4 },
|
|
616
|
+
{ title: 'Updated', max: 12 },
|
|
617
|
+
{ title: 'Mode', max: 4 },
|
|
618
|
+
{ title: 'Last message', max: 40 }
|
|
581
619
|
],
|
|
582
620
|
rows
|
|
583
621
|
);
|
|
584
622
|
print.blank();
|
|
585
|
-
print.hint('/resume
|
|
623
|
+
print.hint('/resume <n> restore · /delete <n> remove');
|
|
586
624
|
}
|
|
587
625
|
|
|
588
626
|
function printThread(st) {
|
|
@@ -590,7 +628,7 @@ function printThread(st) {
|
|
|
590
628
|
if (!chat?.messages?.length) return;
|
|
591
629
|
for (const message of chat.messages) {
|
|
592
630
|
if (message.role === 'user') {
|
|
593
|
-
process.stdout.write(`\n${COLORS.faint('›
|
|
631
|
+
process.stdout.write(`\n${COLORS.faint('› You')}\n${message.content}\n`);
|
|
594
632
|
} else if (message.role === 'assistant') {
|
|
595
633
|
process.stdout.write(`\n${COLORS.accent('•')} ${COLORS.bold('DgxayCode')}\n`);
|
|
596
634
|
if (message.content) process.stdout.write(require('./ansi').renderMarkdown(message.content, { width: termWidth() }));
|
|
@@ -606,23 +644,23 @@ function printThread(st) {
|
|
|
606
644
|
|
|
607
645
|
async function compress(st, editor) {
|
|
608
646
|
if (st.streaming) {
|
|
609
|
-
print.warn('
|
|
647
|
+
print.warn(t('wait_current'));
|
|
610
648
|
return;
|
|
611
649
|
}
|
|
612
650
|
const chat = st.chat;
|
|
613
651
|
if (!chat?.messages?.length) {
|
|
614
|
-
print.warn('
|
|
652
|
+
print.warn(t('compress_nothing'));
|
|
615
653
|
return;
|
|
616
654
|
}
|
|
617
655
|
const config = settings.readConfig();
|
|
618
656
|
const model = settings.activeModel(config);
|
|
619
657
|
if (!model) {
|
|
620
|
-
print.warn('
|
|
658
|
+
print.warn(t('no_model'));
|
|
621
659
|
return;
|
|
622
660
|
}
|
|
623
661
|
const before = sessions.estimateChatTokens(chat);
|
|
624
662
|
editor.suspend();
|
|
625
|
-
process.stdout.write(`${COLORS.faint(`${SIGN.run}
|
|
663
|
+
process.stdout.write(`${COLORS.faint(`${SIGN.run} Compressing context…`)}`);
|
|
626
664
|
try {
|
|
627
665
|
const result = await compressMessages({
|
|
628
666
|
config,
|
|
@@ -630,19 +668,19 @@ async function compress(st, editor) {
|
|
|
630
668
|
modelId: st.runtime.modelId || config.activeModelId,
|
|
631
669
|
messages: sessions.buildHistory(chat)
|
|
632
670
|
});
|
|
633
|
-
if (!result.summary) throw new Error('
|
|
671
|
+
if (!result.summary) throw new Error('the model returned no summary');
|
|
634
672
|
chat.messages = [
|
|
635
|
-
{ role: 'assistant', content: `>
|
|
673
|
+
{ role: 'assistant', content: `> Context compressed; only key information kept.\n\n${result.summary}`, ts: Date.now(), compressed: true }
|
|
636
674
|
];
|
|
637
675
|
chat.updatedAt = Date.now();
|
|
638
676
|
persist(st);
|
|
639
677
|
const after = sessions.estimateChatTokens(chat);
|
|
640
678
|
process.stdout.write('\r\x1b[2K');
|
|
641
|
-
print.ok(
|
|
679
|
+
print.ok(`Context compressed: ${sessions.formatTokens(before)} → ${sessions.formatTokens(after)} tokens (estimated)`);
|
|
642
680
|
process.stdout.write(`\n${require('./ansi').renderMarkdown(result.summary, { width: termWidth() })}\n`);
|
|
643
681
|
} catch (err) {
|
|
644
682
|
process.stdout.write('\r\x1b[2K');
|
|
645
|
-
print.fail(err.message || '
|
|
683
|
+
print.fail(err.message || t('compress_failed'));
|
|
646
684
|
} finally {
|
|
647
685
|
editor.resume();
|
|
648
686
|
}
|
|
@@ -651,30 +689,30 @@ async function compress(st, editor) {
|
|
|
651
689
|
async function handleSkillCommand(st, rest) {
|
|
652
690
|
if (!rest) {
|
|
653
691
|
print.blank();
|
|
654
|
-
print.heading(
|
|
692
|
+
print.heading(`Skills (${settings.homeRelative(cfg.SKILLS_DIR)})`);
|
|
655
693
|
const list = skills.listSkills();
|
|
656
694
|
if (!list.length) {
|
|
657
|
-
print.hint('
|
|
695
|
+
print.hint(' No skills yet. Put a name.md or <dir>/SKILL.md into ~/.dcb/skills');
|
|
658
696
|
print.blank();
|
|
659
697
|
return;
|
|
660
698
|
}
|
|
661
699
|
const rows = list.map((skill, index) => [
|
|
662
700
|
String(index + 1),
|
|
663
701
|
skill.name,
|
|
664
|
-
st.runtime.skill === skill.name ? COLORS.green('
|
|
702
|
+
st.runtime.skill === skill.name ? COLORS.green('enabled') : '',
|
|
665
703
|
skill.description
|
|
666
704
|
]);
|
|
667
705
|
print.table(
|
|
668
706
|
[
|
|
669
707
|
{ title: '#', max: 3 },
|
|
670
|
-
{ title: '
|
|
671
|
-
{ title: '
|
|
672
|
-
{ title: '
|
|
708
|
+
{ title: 'Name', max: 22 },
|
|
709
|
+
{ title: 'Status', max: 8 },
|
|
710
|
+
{ title: 'Description', max: 60 }
|
|
673
711
|
],
|
|
674
712
|
rows
|
|
675
713
|
);
|
|
676
714
|
print.blank();
|
|
677
|
-
print.hint('/skill off
|
|
715
|
+
print.hint('/skill off to disable · /skill <name> [prompt] to enable for this conversation');
|
|
678
716
|
print.blank();
|
|
679
717
|
return;
|
|
680
718
|
}
|
|
@@ -682,7 +720,7 @@ async function handleSkillCommand(st, rest) {
|
|
|
682
720
|
st.runtime.skill = '';
|
|
683
721
|
if (st.chat) st.chat.skill = '';
|
|
684
722
|
updateBusState(st);
|
|
685
|
-
print.ok('
|
|
723
|
+
print.ok(t('skills_off'));
|
|
686
724
|
return;
|
|
687
725
|
}
|
|
688
726
|
const list = skills.listSkills();
|
|
@@ -690,13 +728,13 @@ async function handleSkillCommand(st, rest) {
|
|
|
690
728
|
.filter((item) => rest === item.name || rest.startsWith(`${item.name} `))
|
|
691
729
|
.sort((a, b) => b.name.length - a.name.length)[0];
|
|
692
730
|
if (!picked) {
|
|
693
|
-
print.warn(
|
|
731
|
+
print.warn(`Skill not found: ${rest}. Type /skill to list them`);
|
|
694
732
|
return;
|
|
695
733
|
}
|
|
696
734
|
st.runtime.skill = picked.name;
|
|
697
735
|
if (st.chat) st.chat.skill = picked.name;
|
|
698
736
|
updateBusState(st);
|
|
699
|
-
print.ok(
|
|
737
|
+
print.ok(`Skill enabled for this conversation: ${picked.name}`);
|
|
700
738
|
const prompt = rest.slice(picked.name.length).trim();
|
|
701
739
|
if (prompt) return prompt;
|
|
702
740
|
return undefined;
|
|
@@ -707,13 +745,13 @@ function handleModelCommand(st, rest) {
|
|
|
707
745
|
const provider = settings.findProvider(st.runtime.providerId) || settings.activeProvider(config);
|
|
708
746
|
const models = provider?.models || [];
|
|
709
747
|
if (!models.length) {
|
|
710
|
-
print.warn('
|
|
748
|
+
print.warn('This provider has no models. Run dgxay provider add');
|
|
711
749
|
return;
|
|
712
750
|
}
|
|
713
751
|
const current = models.find((m) => m.id === st.runtime.modelId) || models[0];
|
|
714
752
|
if (!rest) {
|
|
715
753
|
print.blank();
|
|
716
|
-
print.heading(
|
|
754
|
+
print.heading(`Models (${provider.name})`);
|
|
717
755
|
models.forEach((model, index) => {
|
|
718
756
|
const active = model.id === current?.id;
|
|
719
757
|
print.write(
|
|
@@ -725,7 +763,7 @@ function handleModelCommand(st, rest) {
|
|
|
725
763
|
);
|
|
726
764
|
});
|
|
727
765
|
print.blank();
|
|
728
|
-
print.hint('/model
|
|
766
|
+
print.hint('/model <n|id> to switch · saved to config');
|
|
729
767
|
print.blank();
|
|
730
768
|
return;
|
|
731
769
|
}
|
|
@@ -735,39 +773,39 @@ function handleModelCommand(st, rest) {
|
|
|
735
773
|
models.find((m) => m.id === rest) ||
|
|
736
774
|
models.find((m) => m.id.includes(rest));
|
|
737
775
|
if (!target) {
|
|
738
|
-
print.fail(
|
|
776
|
+
print.fail(`Model not found: ${rest}`);
|
|
739
777
|
return;
|
|
740
778
|
}
|
|
741
779
|
settings.setActiveModel(provider.id, target.id);
|
|
742
780
|
st.runtime.modelId = target.id;
|
|
743
781
|
st.runtime.providerId = provider.id;
|
|
744
782
|
updateBusState(st);
|
|
745
|
-
print.ok(
|
|
783
|
+
print.ok(`Switched to ${target.name || target.id}`);
|
|
746
784
|
}
|
|
747
785
|
|
|
748
786
|
function handleProviderCommand(st, rest) {
|
|
749
787
|
const config = settings.readConfig();
|
|
750
788
|
const providers = config.providers || [];
|
|
751
789
|
if (!providers.length) {
|
|
752
|
-
print.warn('
|
|
790
|
+
print.warn('No providers yet. Run dgxay config init');
|
|
753
791
|
return;
|
|
754
792
|
}
|
|
755
793
|
const current = settings.findProvider(st.runtime.providerId) || settings.activeProvider(config);
|
|
756
794
|
if (!rest) {
|
|
757
795
|
print.blank();
|
|
758
|
-
print.heading('
|
|
796
|
+
print.heading(t('provider_list_title'));
|
|
759
797
|
providers.forEach((provider, index) => {
|
|
760
798
|
const active = provider.id === current?.id;
|
|
761
799
|
print.write(
|
|
762
800
|
` ${active ? COLORS.accent('❯') : ' '} ${COLORS.faint(String(index + 1).padStart(2))}. ${
|
|
763
801
|
active ? COLORS.accent(provider.name) : provider.name
|
|
764
802
|
} ${COLORS.faint(settings.PROTOCOL_META[provider.protocol || 'openai'].short)} ${COLORS.faint(
|
|
765
|
-
`${(provider.models || []).length}
|
|
803
|
+
`${(provider.models || []).length} models`
|
|
766
804
|
)}`
|
|
767
805
|
);
|
|
768
806
|
});
|
|
769
807
|
print.blank();
|
|
770
|
-
print.hint('/provider
|
|
808
|
+
print.hint('/provider <n|name> to switch');
|
|
771
809
|
print.blank();
|
|
772
810
|
return;
|
|
773
811
|
}
|
|
@@ -776,62 +814,62 @@ function handleProviderCommand(st, rest) {
|
|
|
776
814
|
(Number.isInteger(index) && index >= 1 && index <= providers.length ? providers[index - 1] : null) ||
|
|
777
815
|
settings.findProvider(rest);
|
|
778
816
|
if (!target) {
|
|
779
|
-
print.fail(
|
|
817
|
+
print.fail(`Provider not found: ${rest}`);
|
|
780
818
|
return;
|
|
781
819
|
}
|
|
782
820
|
const model = target.models?.[0];
|
|
783
821
|
if (!model) {
|
|
784
|
-
print.fail(
|
|
822
|
+
print.fail(`Provider ${target.name} has no models`);
|
|
785
823
|
return;
|
|
786
824
|
}
|
|
787
825
|
settings.saveConfig({ activeProviderId: target.id, activeModelId: model.id });
|
|
788
826
|
st.runtime.providerId = target.id;
|
|
789
827
|
st.runtime.modelId = model.id;
|
|
790
828
|
updateBusState(st);
|
|
791
|
-
print.ok(
|
|
829
|
+
print.ok(`Switched to ${target.name} / ${model.name || model.id}`);
|
|
792
830
|
}
|
|
793
831
|
|
|
794
832
|
function handleModeCommand(st, rest) {
|
|
795
|
-
const next = rest ? (rest === 'work'
|
|
833
|
+
const next = rest ? (rest === 'work' ? 'work' : 'chat') : st.runtime.mode === 'work' ? 'chat' : 'work';
|
|
796
834
|
st.runtime.mode = next;
|
|
797
835
|
if (st.chat) st.chat.mode = next;
|
|
798
836
|
updateBusState(st);
|
|
799
|
-
print.ok(next === 'work' ? '
|
|
800
|
-
if (next === 'work' && !st.cwd) print.hint('
|
|
837
|
+
print.ok(next === 'work' ? `${t('mode_set')}: ${t('mode_work')}` : `${t('mode_set')}: ${t('mode_chat')}`);
|
|
838
|
+
if (next === 'work' && !st.cwd) print.hint('No workspace selected. Use /cd <dir>');
|
|
801
839
|
}
|
|
802
840
|
|
|
803
841
|
function handlePermissionCommand(st, rest) {
|
|
804
842
|
const config = settings.readConfig();
|
|
805
843
|
if (!rest) {
|
|
806
844
|
print.blank();
|
|
807
|
-
print.heading('
|
|
845
|
+
print.heading(t('perms_title'));
|
|
808
846
|
const available = settings.availablePermissions(config);
|
|
809
847
|
Object.entries(settings.PERMISSIONS).forEach(([key, meta]) => {
|
|
810
848
|
const usable = available.includes(key);
|
|
811
849
|
const active = st.runtime.permission === key;
|
|
812
850
|
print.write(
|
|
813
851
|
` ${active ? COLORS.accent('❯') : ' '} ${COLORS.faint(key.padEnd(8))} ${
|
|
814
|
-
usable ? meta.label : COLORS.faint(`${meta.label}
|
|
852
|
+
usable ? meta.label : COLORS.faint(`${meta.label} (unavailable)`)
|
|
815
853
|
} ${COLORS.faint(meta.desc)}`
|
|
816
854
|
);
|
|
817
855
|
});
|
|
818
|
-
if (!available.includes('auto')) print.hint('
|
|
856
|
+
if (!available.includes('auto')) print.hint('Auto review needs a valid review model');
|
|
819
857
|
print.blank();
|
|
820
858
|
return;
|
|
821
859
|
}
|
|
822
860
|
if (!settings.PERMISSIONS[rest]) {
|
|
823
|
-
print.fail('
|
|
861
|
+
print.fail('Level must be none / normal / auto / full');
|
|
824
862
|
return;
|
|
825
863
|
}
|
|
826
864
|
if (rest === 'auto' && !settings.hasReviewModel(config)) {
|
|
827
|
-
print.fail('
|
|
865
|
+
print.fail(t('perm_auto_unavailable'));
|
|
828
866
|
return;
|
|
829
867
|
}
|
|
830
868
|
st.runtime.permission = rest;
|
|
831
869
|
settings.setPermission(rest);
|
|
832
870
|
updateBusState(st);
|
|
833
|
-
print.ok(
|
|
834
|
-
if (rest === 'full') print.warn('
|
|
871
|
+
print.ok(`Permission set to: ${settings.PERMISSIONS[rest].label}`);
|
|
872
|
+
if (rest === 'full') print.warn(t('perm_full_warning'));
|
|
835
873
|
}
|
|
836
874
|
|
|
837
875
|
function handleCd(st, rest) {
|
|
@@ -844,17 +882,17 @@ function handleCd(st, rest) {
|
|
|
844
882
|
try {
|
|
845
883
|
stat = fs.statSync(target);
|
|
846
884
|
} catch {
|
|
847
|
-
print.fail(
|
|
885
|
+
print.fail(`Directory not found: ${target}`);
|
|
848
886
|
return;
|
|
849
887
|
}
|
|
850
888
|
if (!stat.isDirectory()) {
|
|
851
|
-
print.fail(
|
|
889
|
+
print.fail(`Not a directory: ${target}`);
|
|
852
890
|
return;
|
|
853
891
|
}
|
|
854
892
|
st.cwd = target;
|
|
855
893
|
settings.setWorkspace(target);
|
|
856
894
|
updateBusState(st);
|
|
857
|
-
print.ok(
|
|
895
|
+
print.ok(`Workspace: ${target}`);
|
|
858
896
|
}
|
|
859
897
|
|
|
860
898
|
function handleLs(st, rest) {
|
|
@@ -863,7 +901,7 @@ function handleLs(st, rest) {
|
|
|
863
901
|
try {
|
|
864
902
|
entries = fs.readdirSync(target, { withFileTypes: true });
|
|
865
903
|
} catch (err) {
|
|
866
|
-
print.fail(
|
|
904
|
+
print.fail(`Cannot read ${target}:${err.code || err.message}`);
|
|
867
905
|
return;
|
|
868
906
|
}
|
|
869
907
|
entries.sort((a, b) => Number(b.isDirectory()) - Number(a.isDirectory()) || a.name.localeCompare(b.name));
|
|
@@ -891,7 +929,7 @@ async function handleMcpCommand(st, rest) {
|
|
|
891
929
|
const list = mcp.listServers();
|
|
892
930
|
print.blank();
|
|
893
931
|
if (!list.length) {
|
|
894
|
-
print.hint(
|
|
932
|
+
print.hint(`No MCP servers. Use /mcp add or ${BIN} mcp add`);
|
|
895
933
|
print.blank();
|
|
896
934
|
return;
|
|
897
935
|
}
|
|
@@ -899,46 +937,46 @@ async function handleMcpCommand(st, rest) {
|
|
|
899
937
|
const rows = list.map((server, index) => [
|
|
900
938
|
String(index + 1),
|
|
901
939
|
server.name,
|
|
902
|
-
server.status === 'running' ? COLORS.green('
|
|
940
|
+
server.status === 'running' ? COLORS.green('running') : server.status === 'error' ? COLORS.red('error') : 'stopped',
|
|
903
941
|
server.pid ? String(server.pid) : '',
|
|
904
942
|
`${server.command} ${(server.args || []).join(' ')}`
|
|
905
943
|
]);
|
|
906
944
|
print.table(
|
|
907
945
|
[
|
|
908
946
|
{ title: '#', max: 3 },
|
|
909
|
-
{ title: '
|
|
910
|
-
{ title: '
|
|
947
|
+
{ title: 'Name', max: 18 },
|
|
948
|
+
{ title: 'Status', max: 8 },
|
|
911
949
|
{ title: 'PID', max: 8 },
|
|
912
|
-
{ title: '
|
|
950
|
+
{ title: 'Command', max: 50 }
|
|
913
951
|
],
|
|
914
952
|
rows
|
|
915
953
|
);
|
|
916
954
|
print.blank();
|
|
917
|
-
print.hint('/mcp start
|
|
955
|
+
print.hint('/mcp start <name> · /mcp stop <name> · /mcp logs <name> · /mcp add name command args');
|
|
918
956
|
print.blank();
|
|
919
957
|
return;
|
|
920
958
|
}
|
|
921
959
|
const server = mcp.findServer(arg);
|
|
922
960
|
if (action === 'start') {
|
|
923
|
-
if (!server) return print.fail(
|
|
961
|
+
if (!server) return print.fail(`MCP not found: ${arg}`);
|
|
924
962
|
try {
|
|
925
963
|
const result = mcp.startServer(server.id);
|
|
926
|
-
print.ok(
|
|
964
|
+
print.ok(`Started ${server.name}(pid ${result.pid})`);
|
|
927
965
|
} catch (err) {
|
|
928
966
|
print.fail(err.message);
|
|
929
967
|
}
|
|
930
968
|
return;
|
|
931
969
|
}
|
|
932
970
|
if (action === 'stop') {
|
|
933
|
-
if (!server) return print.fail(
|
|
971
|
+
if (!server) return print.fail(`MCP not found: ${arg}`);
|
|
934
972
|
mcp.stopServer(server.id);
|
|
935
|
-
print.ok(
|
|
973
|
+
print.ok(`Stopped ${server.name}`);
|
|
936
974
|
return;
|
|
937
975
|
}
|
|
938
976
|
if (action === 'logs') {
|
|
939
|
-
if (!server) return print.fail(
|
|
977
|
+
if (!server) return print.fail(`MCP not found: ${arg}`);
|
|
940
978
|
const logs = mcp.statusOf(server.id).logs || [];
|
|
941
|
-
if (!logs.length) return print.hint('
|
|
979
|
+
if (!logs.length) return print.hint('No logs yet');
|
|
942
980
|
for (const entry of logs) {
|
|
943
981
|
print.write(`${entry.stream === 'stderr' ? COLORS.red(entry.text) : entry.text}`);
|
|
944
982
|
}
|
|
@@ -946,33 +984,33 @@ async function handleMcpCommand(st, rest) {
|
|
|
946
984
|
}
|
|
947
985
|
if (action === 'add') {
|
|
948
986
|
const [name, command, ...args] = others;
|
|
949
|
-
if (!name || !command) return print.fail('
|
|
987
|
+
if (!name || !command) return print.fail('Usage: /mcp add <name> <command> [args...]');
|
|
950
988
|
const created = mcp.addServer({ name, command, args });
|
|
951
|
-
print.ok(
|
|
989
|
+
print.ok(`Added MCP "${created.name}" — start it with /mcp start ${created.name}`);
|
|
952
990
|
return;
|
|
953
991
|
}
|
|
954
992
|
if (action === 'rm' || action === 'remove') {
|
|
955
|
-
if (!server) return print.fail(
|
|
993
|
+
if (!server) return print.fail(`MCP not found: ${arg}`);
|
|
956
994
|
mcp.removeServer(server.id);
|
|
957
|
-
print.ok(
|
|
995
|
+
print.ok(`Deleted ${server.name}`);
|
|
958
996
|
return;
|
|
959
997
|
}
|
|
960
|
-
print.fail(
|
|
998
|
+
print.fail(`Unknown /mcp action: ${action}`);
|
|
961
999
|
}
|
|
962
1000
|
|
|
963
1001
|
function handleTheme(rest) {
|
|
964
1002
|
const mode = String(rest || '').toLowerCase();
|
|
965
1003
|
if (mode === 'on' || mode === 'color') {
|
|
966
1004
|
setColorMode('ansi256');
|
|
967
|
-
print.ok('
|
|
1005
|
+
print.ok('Color output on');
|
|
968
1006
|
return;
|
|
969
1007
|
}
|
|
970
1008
|
if (mode === 'off' || mode === 'plain') {
|
|
971
1009
|
setColorMode('none');
|
|
972
|
-
print.ok('
|
|
1010
|
+
print.ok('Color output off');
|
|
973
1011
|
return;
|
|
974
1012
|
}
|
|
975
|
-
print.write(
|
|
1013
|
+
print.write(`Color output: ${colorEnabled() ? 'on' : 'off'} (/theme on | off)`);
|
|
976
1014
|
}
|
|
977
1015
|
|
|
978
1016
|
/* ------------------------------ 输入分发 ------------------------------ */
|
|
@@ -1022,7 +1060,7 @@ async function singleShot(options) {
|
|
|
1022
1060
|
if (options.output) argv.push('--output', options.output);
|
|
1023
1061
|
if (options.yes) argv.push('--yes');
|
|
1024
1062
|
if (!prompt) {
|
|
1025
|
-
print.fail('
|
|
1063
|
+
print.fail('A prompt is required');
|
|
1026
1064
|
return 2;
|
|
1027
1065
|
}
|
|
1028
1066
|
return require('../commands/run').run({ argv, runtime: options });
|
|
@@ -1039,9 +1077,9 @@ async function startInteractive(options) {
|
|
|
1039
1077
|
const config = settings.readConfig();
|
|
1040
1078
|
if (!config.providers?.length) {
|
|
1041
1079
|
// 还没有任何供应商:交互终端里直接进入配置引导
|
|
1042
|
-
print.warn('
|
|
1080
|
+
print.warn('No provider configured yet');
|
|
1043
1081
|
if (!process.stdin.isTTY) {
|
|
1044
|
-
print.hint('
|
|
1082
|
+
print.hint('Run dgxay config init to set one up');
|
|
1045
1083
|
return 1;
|
|
1046
1084
|
}
|
|
1047
1085
|
const result = await require('../commands/config').run({
|
|
@@ -1052,12 +1090,29 @@ async function startInteractive(options) {
|
|
|
1052
1090
|
}
|
|
1053
1091
|
|
|
1054
1092
|
const st = createRuntime(options);
|
|
1055
|
-
const
|
|
1093
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
1094
|
+
|
|
1095
|
+
// 启动时先清屏,再渲染界面(管道模式不清屏,保持输出干净)
|
|
1096
|
+
if (interactive) {
|
|
1097
|
+
clearScreen(process.stdout);
|
|
1098
|
+
process.stdout.write('\x1b[3J');
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// 历史:优先把当前会话里用户说过的话填进去,↑ 可以直接回填
|
|
1102
|
+
const previousPrompts = (st.chat?.messages || [])
|
|
1103
|
+
.filter((message) => message.role === 'user' && typeof message.content === 'string' && message.content.trim())
|
|
1104
|
+
.map((message) => message.content.trim());
|
|
1105
|
+
const lastUserPrompt = previousPrompts[previousPrompts.length - 1] || '';
|
|
1106
|
+
|
|
1107
|
+
const editor = interactive
|
|
1056
1108
|
? new LineEditor({
|
|
1057
1109
|
prompt: promptFor(st),
|
|
1058
|
-
|
|
1110
|
+
placeholder: t('box_placeholder'),
|
|
1059
1111
|
completer,
|
|
1060
|
-
multiline: true
|
|
1112
|
+
multiline: true,
|
|
1113
|
+
capabilities: detectCapabilities(process.stdout),
|
|
1114
|
+
history: previousPrompts,
|
|
1115
|
+
lastUserPrompt
|
|
1061
1116
|
})
|
|
1062
1117
|
: createLineReader(process.stdin);
|
|
1063
1118
|
|
|
@@ -1113,7 +1168,7 @@ async function startInteractive(options) {
|
|
|
1113
1168
|
if (st.streaming && !st.abortRequested) {
|
|
1114
1169
|
st.abortRequested = true;
|
|
1115
1170
|
st.requestController?.abort();
|
|
1116
|
-
process.stdout.write(`${COLORS.yellow(`${SIGN.bullet}
|
|
1171
|
+
process.stdout.write(`${COLORS.yellow(`${SIGN.bullet} ${t('interrupted')}`)}\n`);
|
|
1117
1172
|
return true;
|
|
1118
1173
|
}
|
|
1119
1174
|
if (st.terminalBusy) return true;
|
|
@@ -1122,7 +1177,16 @@ async function startInteractive(options) {
|
|
|
1122
1177
|
|
|
1123
1178
|
try {
|
|
1124
1179
|
while (!st.exitRequested) {
|
|
1125
|
-
|
|
1180
|
+
// 输入框上方的一行说明:模型 / 技能
|
|
1181
|
+
if (interactive) editor.prompt = promptFor(st);
|
|
1182
|
+
// ↑ 回填:始终指向最近一条用户提示词
|
|
1183
|
+
if (interactive) {
|
|
1184
|
+
const prompts = (st.chat?.messages || []).filter(
|
|
1185
|
+
(message) => message.role === 'user' && typeof message.content === 'string' && message.content.trim()
|
|
1186
|
+
);
|
|
1187
|
+
editor.lastUserPrompt = prompts.length ? prompts[prompts.length - 1].content.trim() : '';
|
|
1188
|
+
editor.history = prompts.map((message) => message.content.trim());
|
|
1189
|
+
}
|
|
1126
1190
|
const answer = await editor.question();
|
|
1127
1191
|
if (answer.type === 'eof' || answer.type === 'cancel') break;
|
|
1128
1192
|
try {
|
|
@@ -1150,7 +1214,7 @@ async function startInteractive(options) {
|
|
|
1150
1214
|
if (typeof process.stdin.pause === 'function') process.stdin.pause();
|
|
1151
1215
|
}
|
|
1152
1216
|
|
|
1153
|
-
process.stdout.write(`${COLORS.faint('
|
|
1217
|
+
process.stdout.write(`${COLORS.faint(t('bye'))}\n`);
|
|
1154
1218
|
return 0;
|
|
1155
1219
|
}
|
|
1156
1220
|
|