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.
@@ -28,19 +28,19 @@ const PRESETS = [
28
28
  { name: 'DeepSeek', protocol: 'openai', baseUrl: 'https://api.deepseek.com/v1', model: 'deepseek-chat' },
29
29
  { name: 'Claude', protocol: 'anthropic', baseUrl: 'https://api.anthropic.com', model: 'claude-sonnet-4-5' },
30
30
  { name: 'OpenAI Responses', protocol: 'responses', baseUrl: 'https://api.openai.com/v1', model: 'gpt-5' },
31
- { name: '通义千问', protocol: 'openai', baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', model: 'qwen-plus' },
31
+ { name: 'Qwen (DashScope)', protocol: 'openai', baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', model: 'qwen-plus' },
32
32
  { name: 'Moonshot', protocol: 'openai', baseUrl: 'https://api.moonshot.cn/v1', model: 'moonshot-v1-8k' },
33
- { name: 'Ollama 本地', protocol: 'openai', baseUrl: 'http://localhost:11434/v1', model: 'llama3.1' }
33
+ { name: 'Ollama (local)', protocol: 'openai', baseUrl: 'http://localhost:11434/v1', model: 'llama3.1' }
34
34
  ];
35
35
 
36
36
  const options = [
37
37
  { name: 'protocol', type: 'string', valueName: 'p', desc: 'openai / anthropic / responses' },
38
- { name: 'name', type: 'string', valueName: 'text', desc: '供应商名称' },
39
- { name: 'base-url', type: 'string', valueName: 'url', desc: '接口地址' },
40
- { name: 'api-key', type: 'string', valueName: 'key', desc: 'API Key(也可用 DGXAY_API_KEY 环境变量)' },
41
- { name: 'model', type: 'list', valueName: 'id', desc: '模型 ID,可重复指定或逗号分隔' },
42
- { name: 'oauth-provider', type: 'string', valueName: 'id', desc: '改用已连接的 OAuth 账户:openai/anthropic/xai/google' },
43
- { name: 'force', alias: 'f', type: 'boolean', desc: '已有配置时仍然覆盖' }
38
+ { name: 'name', type: 'string', valueName: 'text', desc: 'Provider name' },
39
+ { name: 'base-url', type: 'string', valueName: 'url', desc: 'Base URL' },
40
+ { name: 'api-key', type: 'string', valueName: 'key', desc: 'API Key (DGXAY_API_KEY env var also works)' },
41
+ { name: 'model', type: 'list', valueName: 'id', desc: 'Model ID; repeatable or comma-separated' },
42
+ { name: 'oauth-provider', type: 'string', valueName: 'id', desc: 'Use a connected OAuth account instead: openai/anthropic/xai/google' },
43
+ { name: 'force', alias: 'f', type: 'boolean', desc: 'Overwrite even when config already exists' }
44
44
  ];
45
45
 
46
46
  /* ------------------------------ init ------------------------------ */
@@ -49,13 +49,13 @@ async function promptProtocol(config) {
49
49
  const interactive = process.stdin.isTTY;
50
50
  if (!interactive) return 'openai';
51
51
  print.blank();
52
- print.heading('接口协议');
52
+ print.heading('API protocol');
53
53
  const list = ['openai', 'anthropic', 'responses'];
54
54
  list.forEach((key, index) => {
55
55
  const meta = settings.PROTOCOL_META[key];
56
56
  print.write(` ${COLORS.accent(String(index + 1))}. ${meta.full} ${COLORS.faint(meta.path)}`);
57
57
  });
58
- const answer = await ask(`选择 [1-3,默认 1]: `);
58
+ const answer = await ask(`Choose [1-3, default 1]: `);
59
59
  const index = Number(String(answer || '').trim());
60
60
  if (Number.isInteger(index) && index >= 1 && index <= 3) return list[index - 1];
61
61
  return 'openai';
@@ -65,13 +65,13 @@ async function promptPreset(protocol) {
65
65
  const interactive = process.stdin.isTTY;
66
66
  if (!interactive) return null;
67
67
  print.blank();
68
- print.heading('快速预设');
68
+ print.heading('Quick presets');
69
69
  const matches = PRESETS.filter((preset) => preset.protocol === protocol);
70
70
  matches.forEach((preset, index) => {
71
71
  print.write(` ${COLORS.accent(String(index + 1))}. ${preset.name} ${COLORS.faint(preset.baseUrl)}`);
72
72
  });
73
- print.write(` ${COLORS.faint('直接回车跳过,手动填写')}`);
74
- const answer = await ask('选择预设: ');
73
+ print.write(` ${COLORS.faint('Press Enter to skip and fill in manually')}`);
74
+ const answer = await ask('Choose a preset: ');
75
75
  const index = Number(String(answer || '').trim());
76
76
  if (Number.isInteger(index) && index >= 1 && index <= matches.length) return matches[index - 1];
77
77
  return null;
@@ -80,8 +80,8 @@ async function promptPreset(protocol) {
80
80
  async function runInit(opts, runtime) {
81
81
  const config = settings.readConfig();
82
82
  if (config.providers?.length && !opts.force) {
83
- print.warn(`已经配置了 ${config.providers.length} 个供应商。要追加新的供应商请使用 dgxay provider add`);
84
- print.hint('如需重新初始化首个供应商,加 --force');
83
+ print.warn(`${config.providers.length} provider(s) already configured. Use dgxay provider add to append a new provider`);
84
+ print.hint('To re-initialize the first provider, add --force');
85
85
  return 1;
86
86
  }
87
87
 
@@ -95,9 +95,9 @@ async function runInit(opts, runtime) {
95
95
 
96
96
  if (interactive) {
97
97
  print.write('');
98
- print.write(`${COLORS.bold('DgxayCode 首次配置')} ${COLORS.faint(`v${VERSION}`)}`);
99
- print.write(COLORS.faint('配置会写入本机数据目录,密钥仅保存在本地,不会上传。'));
100
- print.write(COLORS.faint(`数据目录:${cfg.DCB_DIR}`));
98
+ print.write(`${COLORS.bold('DgxayCode first-time setup')} ${COLORS.faint(`v${VERSION}`)}`);
99
+ print.write(COLORS.faint('Config is written to the local data directory; the API key stays on this machine and is never uploaded.'));
100
+ print.write(COLORS.faint(`Data directory: ${cfg.DCB_DIR}`));
101
101
 
102
102
  protocol = protocol || (await promptProtocol(config));
103
103
  const preset = await promptPreset(protocol);
@@ -105,46 +105,46 @@ async function runInit(opts, runtime) {
105
105
  name = name || preset.name;
106
106
  baseUrl = baseUrl || preset.baseUrl;
107
107
  models = models.length ? models : [preset.model];
108
- print.write(COLORS.faint(`已选择 ${preset.name},Base URL 预填 ${preset.baseUrl}`));
108
+ print.write(COLORS.faint(`Selected ${preset.name}, Base URL pre-filled with ${preset.baseUrl}`));
109
109
  }
110
110
 
111
111
  if (!name) {
112
- const answer = await ask(`供应商名称 [${preset?.name || '我的供应商'}]: `);
113
- name = String(answer || '').trim() || preset?.name || '我的供应商';
112
+ const answer = await ask(`Provider name [${preset?.name || 'My provider'}]: `);
113
+ name = String(answer || '').trim() || preset?.name || 'My provider';
114
114
  }
115
115
  if (!baseUrl) {
116
116
  const answer = await ask(`Base URL [${settings.PROTOCOL_META[protocol].basePlaceholder}]: `);
117
117
  baseUrl = String(answer || '').trim() || settings.PROTOCOL_META[protocol].basePlaceholder;
118
118
  }
119
119
 
120
- const useOauth = await confirm('使用已连接的 OAuth 账户(而不是 API Key)?', false);
120
+ const useOauth = await confirm('Use a connected OAuth account (instead of an API Key)?', false);
121
121
  if (useOauth) {
122
122
  const connected = oauth.listProviders().filter((item) => item.connection && !item.connection.expired);
123
123
  if (!connected.length) {
124
- print.warn('还没有可用账户,先继续填写 API Key。可稍后运行 dgxay oauth connect <平台>');
124
+ print.warn('No accounts available yet; continue with an API Key. You can run dgxay oauth connect <platform> later');
125
125
  } else {
126
126
  connected.forEach((item, index) =>
127
127
  print.write(` ${COLORS.accent(String(index + 1))}. ${item.name} ${COLORS.faint(item.connection.account)}`)
128
128
  );
129
- const answer = await ask('选择账户 [1]: ');
129
+ const answer = await ask('Choose an account [1]: ');
130
130
  const index = Number(String(answer || '').trim()) || 1;
131
131
  oauthProvider = connected[index - 1]?.id || '';
132
132
  }
133
133
  }
134
134
 
135
135
  if (!oauthProvider && !apiKey) {
136
- const secret = await askSecret('API Key(本地模型可留空): ');
136
+ const secret = await askSecret('API Key (may be empty for local models): ');
137
137
  apiKey = secret !== null ? secret : '';
138
138
  }
139
139
  if (!models.length) {
140
- const answer = await ask(`模型 ID [${settings.PROTOCOL_META[protocol].modelPlaceholder}]: `);
140
+ const answer = await ask(`Model ID [${settings.PROTOCOL_META[protocol].modelPlaceholder}]: `);
141
141
  models = String(answer || '')
142
142
  .split(/[,,\s]+/)
143
143
  .map((item) => item.trim())
144
144
  .filter(Boolean);
145
145
  }
146
146
  if (!models.length) {
147
- print.fail('模型 ID 不能为空');
147
+ print.fail('Model ID cannot be empty');
148
148
  return 1;
149
149
  }
150
150
  }
@@ -152,10 +152,10 @@ async function runInit(opts, runtime) {
152
152
  if (!protocol) protocol = 'openai';
153
153
  if (!baseUrl) baseUrl = settings.PROTOCOL_META[protocol].basePlaceholder;
154
154
  if (!models.length) {
155
- throw Object.assign(new Error('缺少 --model,例如 --model deepseek-chat'), { userError: true });
155
+ throw Object.assign(new Error('Missing --model, e.g. --model deepseek-chat'), { userError: true });
156
156
  }
157
157
  if (!apiKey && !oauthProvider) {
158
- print.warn('没有填写 API Key:本地模型(Ollama 等)可以忽略,云端接口会返回 401');
158
+ print.warn('No API Key provided: local models (Ollama, etc.) work without one, cloud endpoints return 401');
159
159
  }
160
160
 
161
161
  const result = settings.upsertProvider({
@@ -170,16 +170,16 @@ async function runInit(opts, runtime) {
170
170
  settings.saveConfig({ onboarded: true });
171
171
 
172
172
  print.blank();
173
- print.ok(`已保存供应商「${result.provider.name}」(${settings.PROTOCOL_META[result.provider.protocol].full})`);
173
+ print.ok(`Provider saved: '${result.provider.name}'(${settings.PROTOCOL_META[result.provider.protocol].full})`);
174
174
  print.keyValue([
175
175
  { label: 'Base URL', value: result.provider.baseUrl },
176
- { label: '模型', value: result.provider.models.map((m) => m.id).join(', ') },
177
- { label: '自动审查模型', value: result.provider.reviewModelId || '未配置' },
178
- { label: '认证方式', value: oauthProvider ? `OAuth · ${oauthProvider}` : apiKey ? 'API Key' : '无(本地模型)' },
179
- { label: '配置文件', value: settings.homeRelative(cfg.CONFIG_PATH) }
176
+ { label: 'Models', value: result.provider.models.map((m) => m.id).join(', ') },
177
+ { label: 'Review model', value: result.provider.reviewModelId || 'not configured' },
178
+ { label: 'Auth', value: oauthProvider ? `OAuth · ${oauthProvider}` : apiKey ? 'API Key' : 'none (local model)' },
179
+ { label: 'Config file', value: settings.homeRelative(cfg.CONFIG_PATH) }
180
180
  ]);
181
181
  print.blank();
182
- print.hint('下一步:dgxay doctor 检查连通性,然后直接运行 dgxay 开始对话');
182
+ print.hint('Next: run dgxay doctor to check connectivity, then run dgxay to start chatting');
183
183
  void interactive;
184
184
  void runtime;
185
185
  return 0;
@@ -191,32 +191,32 @@ function runShow() {
191
191
  const config = settings.readConfig();
192
192
  const provider = settings.activeProvider(config);
193
193
  const model = settings.activeModel(config);
194
- print.heading('当前配置');
194
+ print.heading('Current config');
195
195
  print.keyValue(
196
196
  [
197
- { label: '数据目录', value: settings.homeRelative(cfg.DCB_DIR) },
198
- { label: '配置文件', value: settings.homeRelative(cfg.CONFIG_PATH) },
199
- { label: '模式', value: config.ui?.mode === 'work' ? '工作模式' : '对话模式' },
197
+ { label: 'Data directory', value: settings.homeRelative(cfg.DCB_DIR) },
198
+ { label: 'Config file', value: settings.homeRelative(cfg.CONFIG_PATH) },
199
+ { label: 'Mode', value: config.ui?.mode === 'work' ? 'work mode' : 'chat mode' },
200
200
  {
201
- label: '权限',
201
+ label: 'Permission',
202
202
  value: `${settings.PERMISSIONS[settings.currentPermission(config)].label}${
203
- config.ui?.permission === 'auto' && !settings.hasReviewModel(config) ? '(审查模型缺失,已回退普通权限)' : ''
203
+ config.ui?.permission === 'auto' && !settings.hasReviewModel(config) ? ' (review model missing, fell back to normal permission)' : ''
204
204
  }`
205
205
  },
206
- { label: '工作空间', value: config.work?.workspace || '(未选择)' },
207
- { label: '当前供应商', value: provider ? `${provider.name}(${provider.id})` : '未配置' },
208
- { label: '当前模型', value: model ? `${model.name}${model.id !== model.name ? `(${model.id})` : ''}` : '未配置' },
209
- { label: '发送方式', value: config.chat?.stream === false ? '非流式' : '流式' },
210
- { label: '温度', value: String(config.chat?.temperature ?? 0.7) },
211
- { label: '最大输出', value: String(config.chat?.maxTokens ?? 4096) },
212
- { label: '自定义指令', value: config.chat?.systemPrompt ? '已设置' : '未设置' }
206
+ { label: 'Workspace', value: config.work?.workspace || '(not selected)' },
207
+ { label: 'Active provider', value: provider ? `${provider.name} (${provider.id})` : 'not configured' },
208
+ { label: 'Active model', value: model ? `${model.name}${model.id !== model.name ? ` (${model.id})` : ''}` : 'not configured' },
209
+ { label: 'Streaming', value: config.chat?.stream === false ? 'off' : 'on' },
210
+ { label: 'Temperature', value: String(config.chat?.temperature ?? 0.7) },
211
+ { label: 'Max output', value: String(config.chat?.maxTokens ?? 4096) },
212
+ { label: 'Custom instructions', value: config.chat?.systemPrompt ? 'set' : 'not set' }
213
213
  ],
214
214
  {}
215
215
  );
216
216
  print.blank();
217
- print.heading('供应商');
217
+ print.heading('Providers');
218
218
  if (!config.providers?.length) {
219
- print.hint(' 还没有供应商,运行 dgxay config init');
219
+ print.hint(' No providers yet, run dgxay config init');
220
220
  return 0;
221
221
  }
222
222
  const rows = config.providers.map((item, index) => [
@@ -224,24 +224,24 @@ function runShow() {
224
224
  item.name,
225
225
  settings.PROTOCOL_META[item.protocol || 'openai'].short,
226
226
  item.baseUrl,
227
- item.authMode === 'oauth' ? `OAuth(${item.oauthProvider || '?'})` : item.apiKey ? cfg.maskKey(item.apiKey) : '无',
227
+ item.authMode === 'oauth' ? `OAuth(${item.oauthProvider || '?'})` : item.apiKey ? cfg.maskKey(item.apiKey) : 'none',
228
228
  (item.models || []).length,
229
229
  item.reviewModelId || '-'
230
230
  ]);
231
231
  print.table(
232
232
  [
233
233
  { title: '#', max: 3 },
234
- { title: '名称', max: 18 },
235
- { title: '协议', max: 16 },
234
+ { title: 'Name', max: 18 },
235
+ { title: 'Protocol', max: 16 },
236
236
  { title: 'Base URL', max: 40 },
237
- { title: '认证', max: 16 },
238
- { title: '模型数', max: 5 },
239
- { title: '审查模型', max: 20 }
237
+ { title: 'Auth', max: 16 },
238
+ { title: 'Models', max: 5 },
239
+ { title: 'Review model', max: 20 }
240
240
  ],
241
241
  rows
242
242
  );
243
243
  print.blank();
244
- print.hint('添加/修改供应商:dgxay provider --help');
244
+ print.hint('Add/modify providers: dgxay provider --help');
245
245
  return 0;
246
246
  }
247
247
 
@@ -265,10 +265,10 @@ async function run({ argv, runtime }) {
265
265
  if (action === 'show' || action === 'list' || !action) return runShow();
266
266
 
267
267
  if (action === 'get') {
268
- if (!rest[0]) throw Object.assign(new Error('请指定配置键,例如 dgxay config get ui.permission'), { userError: true });
268
+ if (!rest[0]) throw Object.assign(new Error('Specify a config key, e.g. dgxay config get ui.permission'), { userError: true });
269
269
  const value = getPath(settings.readConfig(), rest[0]);
270
270
  if (value === undefined) {
271
- print.fail(`没有这个配置项:${rest[0]}`);
271
+ print.fail(`No such config key: ${rest[0]}`);
272
272
  return 1;
273
273
  }
274
274
  process.stdout.write(`${typeof value === 'string' ? value : JSON.stringify(value)}\n`);
@@ -279,22 +279,22 @@ async function run({ argv, runtime }) {
279
279
  const key = rest[0];
280
280
  const raw = rest.slice(1).join(' ');
281
281
  if (!key || !raw) {
282
- throw Object.assign(new Error('用法:dgxay config set <键> <值>,例如 dgxay config set chat.temperature 0.3'), {
282
+ throw Object.assign(new Error('Usage: dgxay config set <key> <value>, e.g. dgxay config set chat.temperature 0.3'), {
283
283
  userError: true
284
284
  });
285
285
  }
286
286
  const allowed = ['ui.permission', 'ui.mode', 'chat.temperature', 'chat.maxTokens', 'chat.stream', 'chat.systemPrompt'];
287
287
  if (!allowed.includes(key)) {
288
- throw Object.assign(new Error(`不支持修改 ${key}。可修改:${allowed.join(', ')}`), { userError: true });
288
+ throw Object.assign(new Error(`Cannot modify ${key}. Editable keys: ${allowed.join(', ')}`), { userError: true });
289
289
  }
290
290
  let value = raw;
291
291
  if (key === 'chat.temperature' || key === 'chat.maxTokens') value = Number(raw);
292
292
  if (!Number.isFinite(value) && typeof value === 'number') {
293
- throw Object.assign(new Error('需要数字'), { userError: true });
293
+ throw Object.assign(new Error('A number is required'), { userError: true });
294
294
  }
295
295
  if (key === 'chat.stream') value = !/^(false|0|no|off)$/i.test(raw);
296
296
  if (key === 'ui.permission' && !settings.PERMISSIONS[raw]) {
297
- throw Object.assign(new Error('权限只能是 none / normal / auto / full'), { userError: true });
297
+ throw Object.assign(new Error('Permission must be one of none / normal / auto / full'), { userError: true });
298
298
  }
299
299
  const [section, name] = key.split('.');
300
300
  settings.saveConfig({ [section]: { [name]: value } });
@@ -305,12 +305,12 @@ async function run({ argv, runtime }) {
305
305
  if (action === 'path') {
306
306
  const paths = settings.paths();
307
307
  print.keyValue([
308
- { label: '数据目录', value: paths.dataDir },
309
- { label: '配置文件', value: paths.config },
310
- { label: '对话记录', value: paths.chats },
308
+ { label: 'Data directory', value: paths.dataDir },
309
+ { label: 'Config file', value: paths.config },
310
+ { label: 'Conversations', value: paths.chats },
311
311
  { label: 'OAuth', value: paths.oauth },
312
312
  { label: 'MCP', value: paths.mcp },
313
- { label: '技能目录', value: paths.skills }
313
+ { label: 'Skills directory', value: paths.skills }
314
314
  ]);
315
315
  return 0;
316
316
  }
@@ -318,9 +318,9 @@ async function run({ argv, runtime }) {
318
318
  if (action === 'reset') {
319
319
  const yes =
320
320
  argv.includes('--yes') ||
321
- (await confirm('将重置全部配置(供应商、密钥、偏好),对话记录也会清空。继续?', false));
321
+ (await confirm('This resets all config (providers, keys, preferences) and clears conversation history. Continue?', false));
322
322
  if (!yes) {
323
- print.hint('已取消');
323
+ print.hint('Cancelled');
324
324
  return 1;
325
325
  }
326
326
  const backup = `${cfg.CONFIG_PATH}.bak-${Date.now()}`;
@@ -331,30 +331,30 @@ async function run({ argv, runtime }) {
331
331
  }
332
332
  cfg.resetConfig();
333
333
  cfg.saveChats([]);
334
- print.ok('配置已重置');
335
- print.hint('oauth.json、mcp.json 与 skills/ 不会被删除;如需彻底清理请手动删除数据目录');
336
- if (fs.existsSync(backup)) print.hint(`原配置已备份到 ${path.basename(backup)}`);
334
+ print.ok('Config reset');
335
+ print.hint('oauth.json, mcp.json and skills/ are not deleted; remove the data directory manually for a full clean-up');
336
+ if (fs.existsSync(backup)) print.hint(`Previous config backed up to ${path.basename(backup)}`);
337
337
  return 0;
338
338
  }
339
339
 
340
- print.fail(`未知的 config 子命令:${action}`);
341
- print.hint('可用:init / show / get / set / path / reset');
340
+ print.fail(`Unknown config subcommand: ${action}`);
341
+ print.hint('Available: init / show / get / set / path / reset');
342
342
  return 1;
343
343
  }
344
344
 
345
345
  module.exports = {
346
346
  name: 'config',
347
347
  aliases: ['cfg'],
348
- summary: '配置:初始化、查看、修改数据目录',
348
+ summary: 'Config: initialize, view, modify data directory',
349
349
  usage: 'dgxay config <init|show|get|set|path|reset>',
350
350
  options,
351
351
  subcommands: [
352
- ['init', '首次配置供应商与模型'],
353
- ['show', '查看当前配置(密钥打码)'],
354
- ['get <键>', '读取单项配置'],
355
- ['set <键> <值>', '修改单项配置'],
356
- ['path', '显示数据目录与文件路径'],
357
- ['reset', '重置配置(保留 OAuth / MCP / 技能)']
352
+ ['init', 'Initial config of provider and model'],
353
+ ['show', 'View current config (keys masked)'],
354
+ ['get <key>', 'Read a single config key'],
355
+ ['set <key> <value>', 'Set a single config key'],
356
+ ['path', 'Show data directory and file paths'],
357
+ ['reset', 'Reset config (keeps OAuth / MCP / skills)']
358
358
  ],
359
359
  examples: ['dgxay config init', 'dgxay config show', 'dgxay config set chat.temperature 0.3', 'dgxay config path'],
360
360
  run,
@@ -19,15 +19,15 @@ async function run({ argv }) {
19
19
  const model = settings.activeModel(config);
20
20
  const chat = ref ? sessions.resolveChat(ref) : sessions.listChats()[0] || null;
21
21
  if (ref && !chat) {
22
- print.fail(`找不到会话:${ref}`);
22
+ print.fail(`Conversation not found: ${ref}`);
23
23
  return 1;
24
24
  }
25
25
  if (!chat) {
26
- print.hint('还没有会话记录');
26
+ print.hint('No conversation history yet');
27
27
  return 0;
28
28
  }
29
29
  process.stdout.write(`\n${contextReport(contextInfo(chat, model))}\n`);
30
- print.hint(`会话:${chat.title}(${chat.id})`);
30
+ print.hint(`Conversation: ${chat.title} (${chat.id})`);
31
31
  print.blank();
32
32
  return 0;
33
33
  }
@@ -35,8 +35,8 @@ async function run({ argv }) {
35
35
  module.exports = {
36
36
  name: 'context',
37
37
  aliases: ['ctx'],
38
- summary: '查看上下文用量估算',
39
- usage: 'dgxay context [会话序号|ID]',
38
+ summary: 'Show the estimated context usage',
39
+ usage: 'dgxay context [n|id]',
40
40
  options,
41
41
  examples: ['dgxay context', 'dgxay context 1'],
42
42
  run
@@ -19,8 +19,8 @@ const { resolveProviderAuth } = require('../core/prompt');
19
19
  const { VERSION } = require('../services/version');
20
20
 
21
21
  const options = [
22
- { name: 'offline', type: 'boolean', desc: '跳过网络连通性测试' },
23
- { name: 'timeout', type: 'number', valueName: 'ms', desc: '连通性测试超时,默认 12000 毫秒' }
22
+ { name: 'offline', type: 'boolean', desc: 'Skip network connectivity test' },
23
+ { name: 'timeout', type: 'number', valueName: 'ms', desc: 'Connectivity test timeout, default 12000 ms' }
24
24
  ];
25
25
 
26
26
  function line(status, label, detail = '') {
@@ -47,7 +47,7 @@ async function checkConnectivity(provider, modelId, timeout) {
47
47
  apiKey: auth.apiKey,
48
48
  authType: auth.authType,
49
49
  model: modelId,
50
- messages: [{ role: 'user', content: '回复 OK 两个字母即可。' }],
50
+ messages: [{ role: 'user', content: 'Reply with just OK.' }],
51
51
  stream: false,
52
52
  temperature: 0,
53
53
  maxTokens: 16,
@@ -67,77 +67,77 @@ async function run({ argv }) {
67
67
  const timeout = Number(opts.timeout) > 0 ? Number(opts.timeout) : 12000;
68
68
 
69
69
  print.write('');
70
- print.heading(`DgxayCode CLI 自检 v${VERSION}`);
70
+ print.heading(`DgxayCode CLI self-check v${VERSION}`);
71
71
  print.write('');
72
72
 
73
73
  const major = Number(process.versions.node.split('.')[0]);
74
- line(major >= 20 ? 'ok' : major >= 18 ? 'warn' : 'fail', `Node.js ${process.versions.node}`, major >= 18 ? '' : '需要 18+');
75
- line('ok', `平台 ${process.platform} ${process.arch}`, `主目录 ${os.homedir()}`);
76
- if (typeof fetch !== 'function') line('fail', '全局 fetch 不可用', '需要 Node.js 18+');
74
+ line(major >= 20 ? 'ok' : major >= 18 ? 'warn' : 'fail', `Node.js ${process.versions.node}`, major >= 18 ? '' : 'requires 18+');
75
+ line('ok', `Platform ${process.platform} ${process.arch}`, `Home directory ${os.homedir()}`);
76
+ if (typeof fetch !== 'function') line('fail', 'Global fetch unavailable', 'requires Node.js 18+');
77
77
 
78
78
  // 数据目录
79
79
  const dir = cfg.DCB_DIR;
80
80
  try {
81
81
  fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
82
82
  fs.accessSync(dir, fs.constants.W_OK);
83
- line('ok', '数据目录可写', settings.homeRelative(dir));
83
+ line('ok', 'Data directory writable', settings.homeRelative(dir));
84
84
  } catch (err) {
85
- line('fail', '数据目录不可写', `${dir} · ${err.message}`);
85
+ line('fail', 'Data directory not writable', `${dir} · ${err.message}`);
86
86
  }
87
- if (fs.existsSync(cfg.CONFIG_PATH)) line('ok', '存在 config.json', settings.homeRelative(cfg.CONFIG_PATH));
88
- else line('warn', '尚无 config.json', '运行 dgxay config init');
87
+ if (fs.existsSync(cfg.CONFIG_PATH)) line('ok', 'config.json present', settings.homeRelative(cfg.CONFIG_PATH));
88
+ else line('warn', 'No config.json yet', 'run dgxay config init');
89
89
 
90
90
  const config = settings.readConfig();
91
91
  const mode = config.ui?.mode === 'work' ? 'work' : 'chat';
92
92
  const permission = settings.currentPermission(config);
93
- line('ok', `模式 / 权限`, `${mode === 'work' ? '工作模式' : '对话模式'} · ${settings.PERMISSIONS[permission].label}`);
93
+ line('ok', `Mode / Permission`, `${mode === 'work' ? 'work mode' : 'chat mode'} · ${settings.PERMISSIONS[permission].label}`);
94
94
  if (config.ui?.permission === 'auto' && !settings.hasReviewModel(config)) {
95
- line('warn', '自动审查已回退', '当前/默认供应商没有有效的审查模型');
95
+ line('warn', 'Auto review fell back', 'the current/default provider has no valid review model');
96
96
  }
97
97
 
98
98
  const workspace = config.work?.workspace || '';
99
99
  if (workspace) {
100
100
  try {
101
101
  const isDir = fs.statSync(workspace).isDirectory();
102
- line(isDir ? 'ok' : 'fail', '工作空间', workspace);
102
+ line(isDir ? 'ok' : 'fail', 'Workspace', workspace);
103
103
  } catch {
104
- line('warn', '工作空间已不存在', workspace);
104
+ line('warn', 'Workspace no longer exists', workspace);
105
105
  }
106
106
  } else {
107
- line('warn', '未选择工作空间', '工作模式下无法读写文件,用 /cd 或 dgxay workspace set 设置');
107
+ line('warn', 'No workspace selected', 'work mode cannot read or write files; set one with /cd or dgxay workspace set');
108
108
  }
109
109
 
110
110
  // 供应商
111
111
  print.write('');
112
- print.heading('供应商');
112
+ print.heading('Providers');
113
113
  const providers = config.providers || [];
114
114
  if (!providers.length) {
115
- line('fail', '没有供应商', '运行 dgxay config init');
115
+ line('fail', 'No providers', 'run dgxay config init');
116
116
  }
117
117
  for (const provider of providers) {
118
118
  const models = provider.models || [];
119
119
  const reviewOk = models.some((m) => m.id === provider.reviewModelId);
120
- line('ok', provider.name, `${provider.protocol || 'openai'} · ${provider.baseUrl} · ${models.length} 个模型`);
121
- if (!reviewOk) line('warn', ` ${provider.name} 审查模型无效`, provider.reviewModelId || '未配置');
120
+ line('ok', provider.name, `${provider.protocol || 'openai'} · ${provider.baseUrl} · ${models.length} models`);
121
+ if (!reviewOk) line('warn', ` ${provider.name} invalid review model`, provider.reviewModelId || 'not configured');
122
122
  if (provider.authMode === 'oauth') {
123
123
  const account = oauth.listProviders().find((item) => item.id === provider.oauthProvider);
124
124
  if (account?.connection && !account.connection.expired) {
125
- line('ok', ` OAuth 账户`, `${provider.oauthProvider} · ${account.connection.account}`);
125
+ line('ok', ` OAuth account`, `${provider.oauthProvider} · ${account.connection.account}`);
126
126
  } else {
127
- line('fail', ` OAuth 账户不可用`, `dgxay oauth connect ${provider.oauthProvider || '<平台>'}`);
127
+ line('fail', ` OAuth account unavailable`, `dgxay oauth connect ${provider.oauthProvider || '<provider>'}`);
128
128
  }
129
129
  }
130
130
  }
131
131
 
132
132
  if (!opts.offline && providers.length) {
133
133
  print.write('');
134
- print.heading('连通性测试');
134
+ print.heading('Connectivity test');
135
135
  const active = settings.activeProvider(config);
136
136
  const model = settings.activeModel(config);
137
137
  if (!active || !model) {
138
- line('warn', '跳过:没有当前模型');
138
+ line('warn', 'Skipped: no current model');
139
139
  } else {
140
- process.stdout.write(` ${COLORS.faint(`${SIGN.run} 正在请求 ${active.name} / ${model.id}…`)}`);
140
+ process.stdout.write(` ${COLORS.faint(`${SIGN.run} Requesting ${active.name} / ${model.id}…`)}`);
141
141
  const result = await checkConnectivity(active, model.id, timeout);
142
142
  process.stdout.write('\r\x1b[2K');
143
143
  if (result.ok) line('ok', `${active.name} / ${model.id}`, `${result.ms}ms${result.text ? ` · ${result.text}` : ''}`);
@@ -147,33 +147,33 @@ async function run({ argv }) {
147
147
 
148
148
  // OAuth / MCP / 技能
149
149
  print.write('');
150
- print.heading('账户与扩展');
150
+ print.heading('Accounts and extensions');
151
151
  const accounts = oauth.listProviders();
152
152
  const connected = accounts.filter((item) => item.connection && !item.connection.expired);
153
153
  if (connected.length) {
154
- for (const account of connected) line('ok', `${account.name} 账户`, account.connection.account);
154
+ for (const account of connected) line('ok', `${account.name} account`, account.connection.account);
155
155
  } else {
156
- line('-', '没有连接的 OAuth 账户', '可选:dgxay oauth connect openai');
156
+ line('-', 'No connected OAuth accounts', 'optional: dgxay oauth connect openai');
157
157
  }
158
158
  const servers = mcp.listServers();
159
159
  if (servers.length) {
160
160
  for (const server of servers) {
161
161
  line('ok', `MCP ${server.name}`, `${server.command} ${(server.args || []).join(' ')}`.trim());
162
162
  }
163
- print.hint(' 提示:当前版本只管理 MCP 进程,尚未把 MCP 工具暴露给模型');
163
+ print.hint(' Note: this version only manages MCP processes; MCP tools are not yet exposed to the model');
164
164
  } else {
165
- line('-', '没有 MCP server');
165
+ line('-', 'No MCP servers');
166
166
  }
167
167
  const skillList = skills.listSkills();
168
- line(skillList.length ? 'ok' : '-', `技能 ${skillList.length} 个`, settings.homeRelative(cfg.SKILLS_DIR));
168
+ line(skillList.length ? 'ok' : '-', `Skills: ${skillList.length}`, settings.homeRelative(cfg.SKILLS_DIR));
169
169
 
170
170
  const chatsPath = path.join(dir, 'chats.json');
171
171
  if (fs.existsSync(chatsPath)) {
172
172
  try {
173
173
  const chats = JSON.parse(fs.readFileSync(chatsPath, 'utf8'));
174
- line('ok', '对话记录', `${Array.isArray(chats) ? chats.length : 0} 段`);
174
+ line('ok', 'Conversation history', `${Array.isArray(chats) ? chats.length : 0} entries`);
175
175
  } catch {
176
- line('warn', '对话记录无法解析', chatsPath);
176
+ line('warn', 'Conversation history could not be parsed', chatsPath);
177
177
  }
178
178
  }
179
179
 
@@ -184,7 +184,7 @@ async function run({ argv }) {
184
184
  module.exports = {
185
185
  name: 'doctor',
186
186
  aliases: ['check'],
187
- summary: '自检:环境、配置、模型连通性',
187
+ summary: 'Self-check: environment, config, model connectivity',
188
188
  usage: 'dgxay doctor [--offline]',
189
189
  options,
190
190
  examples: ['dgxay doctor', 'dgxay doctor --offline'],