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/src/cli/slash.js CHANGED
@@ -1,35 +1,36 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * 交互模式里的斜杠命令(对应桌面版输入框的 / 菜单)。
4
+ * 交互模式里的斜杠命令。
5
5
  *
6
- * 与桌面版的关系:
7
- * 桌面版只有 /压缩 与 /技能;CLI 额外提供会话、模型、权限、MCP 等
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: '查看所有命令', usage: '/help' },
13
- { name: '/new', desc: '开始新对话', usage: '/new' },
14
- { name: '/list', desc: '列出历史对话', usage: '/list' },
15
- { name: '/resume', desc: '恢复历史对话', usage: '/resume <序号|ID>' },
16
- { name: '/delete', desc: '删除一段对话', usage: '/delete <序号|ID>' },
17
- { name: '/rename', desc: '重命名当前对话', usage: '/rename <标题>' },
18
- { name: '/compress', desc: '压缩上下文(有损,不可撤销)', usage: '/compress', aliases: ['/压缩'] },
19
- { name: '/skill', desc: '列出或启用技能', usage: '/skill [名称] [提示词]', aliases: ['/技能'] },
20
- { name: '/model', desc: '查看或切换模型', usage: '/model [序号|ID]' },
21
- { name: '/provider', desc: '查看或切换供应商', usage: '/provider [序号|名称]' },
22
- { name: '/mode', desc: '切换对话 / 工作模式', usage: '/mode [chat|work]' },
23
- { name: '/permission', desc: '切换权限档位', usage: '/permission [none|normal|auto|full]' },
24
- { name: '/cd', desc: '切换工作空间', usage: '/cd <目录>' },
25
- { name: '/ls', desc: '查看工作空间文件', usage: '/ls [目录]' },
26
- { name: '/pwd', desc: '显示当前工作空间', usage: '/pwd' },
27
- { name: '/term', desc: '执行终端命令(不进模型上下文时用 ! 前缀)', usage: '/term <命令>' },
28
- { name: '/context', desc: '查看上下文用量', usage: '/context' },
29
- { name: '/mcp', desc: '管理 MCP:list/start/stop/logs/add/rm', usage: '/mcp [子命令]' },
30
- { name: '/theme', desc: '开关颜色输出', usage: '/theme [on|off]' },
31
- { name: '/clear', desc: '清屏(不删除会话)', usage: '/clear' },
32
- { name: '/exit', desc: '退出', usage: '/exit', aliases: ['/quit'] }
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: '↑↓ 选择 · Tab 补全 · Enter 执行 · Esc 关闭',
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('上下文')} ${COLORS.bold(`${percent}%`)} ${COLORS.faint(
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
- ['对话消息', String(info.messageCount)],
37
- ['上下文上限', formatTokens(info.limit)],
38
- ['剩余可用', formatTokens(Math.max(0, info.limit - info.used))],
39
- ['当前模型', info.model?.name || '未选择'],
40
- ['供应商', info.model?.providerName || ''],
41
- ['技能', info.skill || '无']
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('按字符启发式估算,实际 token 以模型 usage 为准;接近上限时用 /compress 保留重要上下文。'));
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
 
@@ -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: '直接提问(等同 dgxay run)' },
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} 月 ${date.getDate()} 日`;
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(`历史会话(${chats.length},最多保留 ${sessions.MAX_CHATS} 段)`);
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: '标题', max: 30 },
65
- { title: '消息', max: 4 },
66
- { title: '更新时间', max: 10 },
67
- { title: '模式', max: 4 },
68
- { title: '技能', max: 12 },
69
- { title: '最后内容', max: 40 }
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 恢复第 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(`找不到会话:${ref}`);
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(`▍终端 $ ${message.command}`)}`);
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('用法:dgxay chat show <序号|ID>'), { userError: true });
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('用法:dgxay chat resume <序号|ID>'), { userError: true });
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(`找不到会话:${rest[0]}`);
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('用法:dgxay chat rm <序号|ID>'), { userError: true });
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(`找不到会话:${rest[0]}`);
149
+ print.fail(`Conversation not found: ${rest[0]}`);
150
150
  return 1;
151
151
  }
152
152
  sessions.deleteChat(chat.id);
153
- print.ok(`已删除「${chat.title}」`);
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('用法:dgxay chat rename <序号|ID> <新标题>'), { userError: true });
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(`找不到会话:${ref}`);
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(`已重命名为「${title}」`);
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('清空全部会话记录?(不可恢复)', false);
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 <序号|ID>', '查看会话内容'],
205
- ['resume <序号|ID>', '进入交互模式并恢复'],
206
- ['rm <序号|ID>', '删除会话'],
207
- ['rename <序号|ID> <标题>', '重命名会话'],
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