@serjm/deepseek-code 0.3.1 → 0.4.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.
Files changed (68) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +12 -0
  3. package/README.ru.md +12 -0
  4. package/dist/api/index.d.ts +4 -4
  5. package/dist/api/index.d.ts.map +1 -1
  6. package/dist/api/index.js +17 -5
  7. package/dist/api/index.js.map +1 -1
  8. package/dist/cli/headless.d.ts.map +1 -1
  9. package/dist/cli/headless.js +2 -0
  10. package/dist/cli/headless.js.map +1 -1
  11. package/dist/commands/index.d.ts +7 -0
  12. package/dist/commands/index.d.ts.map +1 -1
  13. package/dist/commands/index.js +188 -79
  14. package/dist/commands/index.js.map +1 -1
  15. package/dist/config/defaults.d.ts +2 -1
  16. package/dist/config/defaults.d.ts.map +1 -1
  17. package/dist/config/defaults.js +4 -2
  18. package/dist/config/defaults.js.map +1 -1
  19. package/dist/core/agent-loop.d.ts +27 -1
  20. package/dist/core/agent-loop.d.ts.map +1 -1
  21. package/dist/core/agent-loop.js +165 -10
  22. package/dist/core/agent-loop.js.map +1 -1
  23. package/dist/core/i18n.d.ts +2 -0
  24. package/dist/core/i18n.d.ts.map +1 -1
  25. package/dist/core/i18n.js +15 -9
  26. package/dist/core/i18n.js.map +1 -1
  27. package/dist/core/metrics.d.ts +53 -0
  28. package/dist/core/metrics.d.ts.map +1 -1
  29. package/dist/core/metrics.js +205 -6
  30. package/dist/core/metrics.js.map +1 -1
  31. package/dist/tools/chrome.js +53 -53
  32. package/dist/tools/edit.d.ts.map +1 -1
  33. package/dist/tools/edit.js +30 -8
  34. package/dist/tools/edit.js.map +1 -1
  35. package/dist/tools/types.d.ts +28 -0
  36. package/dist/tools/types.d.ts.map +1 -1
  37. package/dist/tools/types.js +10 -0
  38. package/dist/tools/types.js.map +1 -1
  39. package/dist/tools/write.d.ts.map +1 -1
  40. package/dist/tools/write.js +13 -1
  41. package/dist/tools/write.js.map +1 -1
  42. package/dist/ui/activity-cards.js +9 -9
  43. package/dist/ui/activity-cards.js.map +1 -1
  44. package/dist/ui/app.d.ts.map +1 -1
  45. package/dist/ui/app.js +59 -14
  46. package/dist/ui/app.js.map +1 -1
  47. package/dist/ui/chat-view.js +2 -2
  48. package/dist/ui/chat-view.js.map +1 -1
  49. package/dist/ui/input-bar.d.ts.map +1 -1
  50. package/dist/ui/input-bar.js +268 -70
  51. package/dist/ui/input-bar.js.map +1 -1
  52. package/dist/ui/markdown-view.d.ts +9 -0
  53. package/dist/ui/markdown-view.d.ts.map +1 -1
  54. package/dist/ui/markdown-view.js +70 -34
  55. package/dist/ui/markdown-view.js.map +1 -1
  56. package/dist/ui/reasoning-view.js +1 -1
  57. package/dist/ui/reasoning-view.js.map +1 -1
  58. package/dist/ui/results-panel.js +1 -1
  59. package/dist/ui/results-panel.js.map +1 -1
  60. package/dist/ui/setup-wizard.js +5 -5
  61. package/dist/ui/setup-wizard.js.map +1 -1
  62. package/dist/ui/status-bar.js +1 -1
  63. package/dist/ui/status-bar.js.map +1 -1
  64. package/dist/ui/tool-activity-card.js +9 -9
  65. package/dist/ui/tool-activity-card.js.map +1 -1
  66. package/dist/ui/tool-call-view.js +7 -7
  67. package/dist/ui/tool-call-view.js.map +1 -1
  68. package/package.json +2 -1
@@ -18,6 +18,7 @@ import { saveConfig } from '../config/loader.js';
18
18
  import { getDefaultTools, getToolsForMode } from '../tools/registry.js';
19
19
  import { browserTest, getLastBrowserTestResult, browserRealTest } from '../tools/chrome.js';
20
20
  import { chromeManager } from '../tools/chrome-manager.js';
21
+ import { AUDIT_BUDGET_PRESET } from '../tools/types.js';
21
22
  // ─── Helpers ─────────────────────────────────────────────────────────────────
22
23
  function generateFollowups(lastContent) {
23
24
  const suggestions = [];
@@ -43,28 +44,85 @@ function generateFollowups(lastContent) {
43
44
  }
44
45
  return suggestions;
45
46
  }
47
+ // ─── Russian descriptions for commands ──────────────────────────────────────
48
+ const RU_DESCRIPTIONS = {
49
+ '/help': 'Показать эту справку',
50
+ '/setup': 'Настройки: язык, API-ключ, тема, режим',
51
+ '/remember': 'Сохранить в память: /remember <текст>',
52
+ '/forget': 'Удалить из памяти по поиску',
53
+ '/memory': 'Показать все сохранённые записи',
54
+ '/compress': 'Сжать историю чата',
55
+ '/checkpoint': 'Создать git-чекпоинт',
56
+ '/restore': 'Список или восстановление чекпоинта: /restore [id]',
57
+ '/mcp': 'MCP-серверы: /mcp list | connect',
58
+ '/skills': 'Список или описание навыка',
59
+ '/agents': 'Список активных под-агентов',
60
+ '/review': 'Ревью кода: /review all|diff|auto',
61
+ '/sandbox': 'Запустить команду в sandbox',
62
+ '/git': 'Git: /git commit|branch|diff|status',
63
+ '/loop': 'Планировщик: /loop <интервал> <задача>',
64
+ '/stats': 'Статистика сессии с токенами',
65
+ '/theme': 'Сменить тему или открыть выбор',
66
+ '/model': 'Сменить модель или открыть выбор: /model [id]',
67
+ '/lang': 'Сменить язык: /lang en|ru|zh',
68
+ '/extensions': 'Список установленных расширений',
69
+ '/followup': 'Сгенерировать предложения продолжения',
70
+ '/logs': 'Показать последние логи',
71
+ '/plan': 'Обзор возможностей',
72
+ '/tools': 'Показать доступные инструменты и статус',
73
+ '/capabilities': 'Полная матрица возможностей',
74
+ '/browser-test': 'Запустить тест Chrome',
75
+ '/browser-real-test': 'Smoke-тест реальных сайтов',
76
+ '/last-browser-test': 'Показать последний отчёт browser-test',
77
+ '/chrome': 'Режим Chrome: --headed|--headless|-s',
78
+ '/budget': 'Бюджет: /budget status|off|audit|small',
79
+ };
80
+ function getDescription(name) {
81
+ if (i18n.getLocale() === 'ru') {
82
+ return RU_DESCRIPTIONS[name] ?? COMMANDS.find(c => c.name === name)?.description ?? '';
83
+ }
84
+ return COMMANDS.find(c => c.name === name)?.description ?? '';
85
+ }
46
86
  // ─── Command handlers ────────────────────────────────────────────────────────
47
87
  async function cmdHelp(ctx) {
48
- const lines = ['**Available commands:**', ''];
88
+ const locale = i18n.getLocale();
89
+ const helpLine = (left, right) => `${left}\n ${right}`;
90
+ const lines = [`${i18n.t('helpCommands')}:`];
49
91
  for (const cmd of COMMANDS) {
50
92
  if (cmd.name === '/language')
51
93
  continue;
52
- lines.push(` ${cmd.name.padEnd(22)} ${cmd.description}`);
53
- }
54
- lines.push('', ' /clear Clear chat history (with confirmation)');
55
- lines.push('', '**Keyboard shortcuts:**', '');
56
- lines.push(' Ctrl+L Clear chat (opens confirmation dialog)');
57
- lines.push(' Ctrl+C Cancel running agent / double-tap to exit');
58
- lines.push(' Alt+V Paste image from clipboard (vision models only)');
59
- if (platform() === 'win32') {
60
- lines.push(' **Windows note:** If Alt+V does not work, ensure your terminal sends');
61
- lines.push(' proper Alt/Meta sequences. Windows Terminal 1.14 works correctly.');
62
- lines.push(' In older terminals try: Settings Compatibility → "Use Alt as Meta key".');
63
- }
64
- lines.push(' Tab Cycle approval mode: plan → default → auto-edit → turbo');
65
- lines.push(' PageUp / PageDown Scroll chat history');
66
- lines.push(' End Jump to latest message');
67
- lines.push(' Shift+Enter Insert newline in input');
94
+ lines.push('', helpLine(cmd.name, getDescription(cmd.name)));
95
+ }
96
+ lines.push('', helpLine('/clear', locale === 'ru' ? 'Очистить историю чата (с подтверждением)' : 'Clear chat history (with confirmation)'));
97
+ lines.push('', `${i18n.t('helpKeyboard')}:`);
98
+ if (locale === 'ru') {
99
+ lines.push('', helpLine('Ctrl+L', 'Очистить чат (открывает диалог подтверждения)'));
100
+ lines.push('', helpLine('Ctrl+C', 'Отменить выполнение / двойное нажатие для выхода'));
101
+ lines.push('', helpLine('Alt+V', 'Вставить изображение из буфера (только vision модели)'));
102
+ if (platform() === 'win32') {
103
+ lines.push('', ' Для Windows: Если Alt+V не работает, убедитесь, что терминал передаёт');
104
+ lines.push(' правильные Alt/Meta-последовательности. Windows Terminal 1.14 работает корректно.');
105
+ lines.push(' В старых терминалах попробуйте: Настройки → Совместимость → "Использовать Alt как Meta".');
106
+ }
107
+ lines.push('', helpLine('Tab', 'Цикл режимов: plan → default → auto-edit → turbo'));
108
+ lines.push('', helpLine('PageUp / PageDown', 'Прокрутка истории чата'));
109
+ lines.push('', helpLine('End', 'Перейти к последнему сообщению'));
110
+ lines.push('', helpLine('Shift+Enter / Alt+Enter', 'Новая строка в поле ввода'));
111
+ }
112
+ else {
113
+ lines.push('', helpLine('Ctrl+L', 'Clear chat (opens confirmation dialog)'));
114
+ lines.push('', helpLine('Ctrl+C', 'Cancel running agent / double-tap to exit'));
115
+ lines.push('', helpLine('Alt+V', 'Paste image from clipboard (vision models only)'));
116
+ if (platform() === 'win32') {
117
+ lines.push('', ' Windows note: If Alt+V does not work, ensure your terminal sends');
118
+ lines.push(' proper Alt/Meta sequences. Windows Terminal ≥ 1.14 works correctly.');
119
+ lines.push(' In older terminals try: Settings → Compatibility → "Use Alt as Meta key".');
120
+ }
121
+ lines.push('', helpLine('Tab', 'Cycle approval mode: plan → default → auto-edit → turbo'));
122
+ lines.push('', helpLine('PageUp / PageDown', 'Scroll chat history'));
123
+ lines.push('', helpLine('End', 'Jump to latest message'));
124
+ lines.push('', helpLine('Shift+Enter / Alt+Enter', 'Insert newline in input'));
125
+ }
68
126
  ctx.setMessages(prev => [...prev, { role: 'assistant', content: lines.join('\n') }]);
69
127
  return true;
70
128
  }
@@ -90,7 +148,7 @@ async function cmdRemember(ctx, input) {
90
148
  });
91
149
  ctx.setMessages(prev => [...prev, {
92
150
  role: 'assistant',
93
- content: `✓ Saved to memory: "${text.slice(0, 100)}${text.length > 100 ? '...' : ''}"`,
151
+ content: `[ok] Saved to memory: "${text.slice(0, 100)}${text.length > 100 ? '...' : ''}"`,
94
152
  }]);
95
153
  return true;
96
154
  }
@@ -116,7 +174,7 @@ async function cmdForget(ctx, input) {
116
174
  }
117
175
  ctx.setMessages(prev => [...prev, {
118
176
  role: 'assistant',
119
- content: `✓ Deleted ${results.length} memory/memories matching: "${query}"`,
177
+ content: `[ok] Deleted ${results.length} memory/memories matching: "${query}"`,
120
178
  }]);
121
179
  return true;
122
180
  }
@@ -132,7 +190,7 @@ async function cmdMemory(ctx) {
132
190
  const memoryList = memories.map((m, i) => `${i + 1}. **${m.name}** — ${m.description}`).join('\n');
133
191
  ctx.setMessages(prev => [...prev, {
134
192
  role: 'assistant',
135
- content: `📝 **Memories** (${memories.length}):\n\n${memoryList}`,
193
+ content: `**Memories** (${memories.length}):\n\n${memoryList}`,
136
194
  }]);
137
195
  return true;
138
196
  }
@@ -157,7 +215,7 @@ async function cmdCompress(ctx) {
157
215
  const systemMsg = ctx.messages.find(m => m.role === 'system');
158
216
  ctx.setMessages([
159
217
  ...(systemMsg ? [systemMsg] : []),
160
- { role: 'assistant', content: `📦 **Context Compressed**\n\nOriginal: ${msgCount} messages (~${(totalLen / 1024).toFixed(1)}KB)\n\n**Summary:**\n${summary}` },
218
+ { role: 'assistant', content: `**Context Compressed**\n\nOriginal: ${msgCount} messages (~${(totalLen / 1024).toFixed(1)}KB)\n\n**Summary:**\n${summary}` },
161
219
  ]);
162
220
  }
163
221
  catch {
@@ -188,7 +246,7 @@ async function cmdCheckpoint(ctx, input) {
188
246
  }
189
247
  ctx.setMessages(prev => [...prev, {
190
248
  role: 'assistant',
191
- content: `✓ Checkpoint created: **${cp.id}**\nFiles: ${cp.files.length > 0 ? cp.files.join(', ') : '(no changes)'}`,
249
+ content: `[ok] Checkpoint created: **${cp.id}**\nFiles: ${cp.files.length > 0 ? cp.files.join(', ') : '(no changes)'}`,
192
250
  }]);
193
251
  return true;
194
252
  }
@@ -206,7 +264,7 @@ async function cmdRestore(ctx, input) {
206
264
  const list = checkpoints.slice(0, 10).map((cp, i) => `${i + 1}. **${cp.id}** — ${cp.message} (${new Date(cp.timestamp).toLocaleString()})`).join('\n');
207
265
  ctx.setMessages(prev => [...prev, {
208
266
  role: 'assistant',
209
- content: `📋 **Checkpoints:**\n${list}\n\nUse \`/restore <id>\` to restore.`,
267
+ content: `**Checkpoints:**\n${list}\n\nUse \`/restore <id>\` to restore.`,
210
268
  }]);
211
269
  return true;
212
270
  }
@@ -216,14 +274,14 @@ async function cmdRestore(ctx, input) {
216
274
  ctx.setMessages(prev => [...prev, {
217
275
  role: 'assistant',
218
276
  content: restoredMessages
219
- ? `✓ Restored checkpoint: ${arg}`
220
- : `✗ Could not restore checkpoint: ${arg}`,
277
+ ? `[ok] Restored checkpoint: ${arg}`
278
+ : `[err] Could not restore checkpoint: ${arg}`,
221
279
  }]);
222
280
  }
223
281
  catch (err) {
224
282
  ctx.setMessages(prev => [...prev, {
225
283
  role: 'assistant',
226
- content: `❌ Restore failed: ${err.message}`,
284
+ content: `[err] Restore failed: ${err.message}`,
227
285
  }]);
228
286
  }
229
287
  ctx.setStatusText('Ready');
@@ -261,13 +319,13 @@ async function cmdMcp(ctx, input) {
261
319
  await server.connect();
262
320
  ctx.setMessages(prev => [...prev, {
263
321
  role: 'assistant',
264
- content: `✓ Connected to MCP server: ${name} (${server.tools.length} tools)`,
322
+ content: `[ok] Connected to MCP server: ${name} (${server.tools.length} tools)`,
265
323
  }]);
266
324
  }
267
325
  catch (err) {
268
326
  ctx.setMessages(prev => [...prev, {
269
327
  role: 'assistant',
270
- content: `❌ MCP connection failed: ${err.message}`,
328
+ content: `[err] MCP connection failed: ${err.message}`,
271
329
  }]);
272
330
  }
273
331
  }
@@ -292,7 +350,7 @@ async function cmdSkills(ctx, input) {
292
350
  }
293
351
  ctx.setMessages(prev => [...prev, {
294
352
  role: 'assistant',
295
- content: `📖 **Skill: ${skill.name}**\n\n${skill.description || ''}`,
353
+ content: `**Skill: ${skill.name}**\n\n${skill.description || ''}`,
296
354
  }]);
297
355
  }
298
356
  else {
@@ -302,7 +360,7 @@ async function cmdSkills(ctx, input) {
302
360
  : skills.map(s => `- **${s.name}**: ${s.description}`).join('\n');
303
361
  ctx.setMessages(prev => [...prev, {
304
362
  role: 'assistant',
305
- content: `📖 **Available Skills**\n\n${list}`,
363
+ content: `**Available Skills**\n\n${list}`,
306
364
  }]);
307
365
  }
308
366
  return true;
@@ -337,13 +395,13 @@ async function cmdReview(ctx, input) {
337
395
  options.autoFix = true;
338
396
  }
339
397
  ctx.setStatusText('Reviewing code...');
340
- ctx.setMessages(prev => [...prev, { role: 'assistant', content: '🔍 Running code review...' }]);
398
+ ctx.setMessages(prev => [...prev, { role: 'assistant', content: 'Running code review...' }]);
341
399
  try {
342
400
  const result = await reviewCode(ctx.config, options);
343
401
  const report = formatReviewReport(result);
344
402
  ctx.setMessages(prev => [...prev, {
345
403
  role: 'assistant',
346
- content: `**Code Review Results**\n\nScore: **${result.score}/100**\nIssues: ${result.issues.length}\nDuration: ${(result.durationMs / 1000).toFixed(1)}s\n\n${report || ' No issues found.'}\n\n${result.summary}`,
404
+ content: `**Code Review Results**\n\nScore: **${result.score}/100**\nIssues: ${result.issues.length}\nDuration: ${(result.durationMs / 1000).toFixed(1)}s\n\n${report || '[ok] No issues found.'}\n\n${result.summary}`,
347
405
  }]);
348
406
  }
349
407
  catch (err) {
@@ -390,8 +448,8 @@ async function cmdSandbox(ctx, input) {
390
448
  ctx.setMessages(prev => [...prev, {
391
449
  role: 'assistant',
392
450
  content: result.exitCode === 0
393
- ? `✅ **Sandbox Result** (${(result.durationMs / 1000).toFixed(1)}s, exit: ${result.exitCode})\n\n${result.stdout.slice(0, 2000)}${result.stderr ? `\n\n**Stderr:**\n${result.stderr.slice(0, 1000)}` : ''}`
394
- : `❌ **Sandbox Error** (exit: ${result.exitCode})\n\n${result.stderr.slice(0, 2000)}`,
451
+ ? `[ok] **Sandbox Result** (${(result.durationMs / 1000).toFixed(1)}s, exit: ${result.exitCode})\n\n${result.stdout.slice(0, 2000)}${result.stderr ? `\n\n**Stderr:**\n${result.stderr.slice(0, 1000)}` : ''}`
452
+ : `[err] **Sandbox Error** (exit: ${result.exitCode})\n\n${result.stderr.slice(0, 2000)}`,
395
453
  }]);
396
454
  }
397
455
  catch (err) {
@@ -417,14 +475,14 @@ async function cmdGit(ctx, input) {
417
475
  ctx.setMessages(prev => [...prev, {
418
476
  role: 'assistant',
419
477
  content: result.success
420
- ? `✓ Committed: \`${msg}\` (${result.hash?.slice(0, 7)})`
421
- : `✗ Commit failed: ${result.error}`,
478
+ ? `[ok] Committed: \`${msg}\` (${result.hash?.slice(0, 7)})`
479
+ : `[err] Commit failed: ${result.error}`,
422
480
  }]);
423
481
  }
424
482
  catch (err) {
425
483
  ctx.setMessages(prev => [...prev, {
426
484
  role: 'assistant',
427
- content: `❌ Commit failed: ${err.message}`,
485
+ content: `[err] Commit failed: ${err.message}`,
428
486
  }]);
429
487
  }
430
488
  ctx.setStatusText('Ready');
@@ -444,14 +502,14 @@ async function cmdGit(ctx, input) {
444
502
  ctx.setMessages(prev => [...prev, {
445
503
  role: 'assistant',
446
504
  content: result.success
447
- ? `🌿 Switched to branch: ${name}`
448
- : `✗ ${result.error}`,
505
+ ? `Switched to branch: ${name}`
506
+ : `[err] ${result.error}`,
449
507
  }]);
450
508
  }
451
509
  catch (err) {
452
510
  ctx.setMessages(prev => [...prev, {
453
511
  role: 'assistant',
454
- content: `❌ Branch failed: ${err.message}`,
512
+ content: `[err] Branch failed: ${err.message}`,
455
513
  }]);
456
514
  }
457
515
  return true;
@@ -467,7 +525,7 @@ async function cmdGit(ctx, input) {
467
525
  catch (err) {
468
526
  ctx.setMessages(prev => [...prev, {
469
527
  role: 'assistant',
470
- content: `❌ Diff failed: ${err.message}`,
528
+ content: `[err] Diff failed: ${err.message}`,
471
529
  }]);
472
530
  }
473
531
  return true;
@@ -484,7 +542,7 @@ async function cmdGit(ctx, input) {
484
542
  catch (err) {
485
543
  ctx.setMessages(prev => [...prev, {
486
544
  role: 'assistant',
487
- content: `❌ Status failed: ${err.message}`,
545
+ content: `[err] Status failed: ${err.message}`,
488
546
  }]);
489
547
  }
490
548
  return true;
@@ -519,7 +577,7 @@ async function cmdLoop(ctx, input) {
519
577
  scheduler.clearAll();
520
578
  ctx.setMessages(prev => [...prev, {
521
579
  role: 'assistant',
522
- content: ' All tasks cleared.',
580
+ content: '[ok] All tasks cleared.',
523
581
  }]);
524
582
  }
525
583
  else if (sub) {
@@ -537,7 +595,7 @@ async function cmdLoop(ctx, input) {
537
595
  const task = scheduler.addTask(prompt, intervalMs);
538
596
  ctx.setMessages(prev => [...prev, {
539
597
  role: 'assistant',
540
- content: `✓ Task scheduled: "${prompt}" every ${intervalStr} (ID: ${task.id})`,
598
+ content: `[ok] Task scheduled: "${prompt}" every ${intervalStr} (ID: ${task.id})`,
541
599
  }]);
542
600
  }
543
601
  else {
@@ -559,7 +617,7 @@ async function cmdStats(ctx) {
559
617
  const cost = metrics ? metrics.estimatedCostUSD(ctx.config.model) : 0;
560
618
  let content = `**Session Statistics:**\n- Messages: ${ctx.messages.length}\n- MCP Tools: ${mcpTools}\n- Skills: ${skills}\n- Subagents: ${agents}\n- Scheduled Tasks: ${tasks}\n- Extensions: ${exts}\n- Theme: ${themeManager.theme.name}\n- Language: ${i18n.getLocale()}\n- Approval Mode: ${ctx.approvalMode}`;
561
619
  if (usage && usage.total > 0) {
562
- content += `\n\n**Token Usage:**\n- Input: ${usage.input.toLocaleString()} tokens\n- Output: ${usage.output.toLocaleString()} tokens\n- Total: ${usage.total.toLocaleString()} tokens\n- Est. Cost: $${cost.toFixed(4)}`;
620
+ content += `\n\n**Token Usage:**\n- Input: ${usage.input.toLocaleString()} tokens\n- Cache hit input: ${usage.cacheHitInput.toLocaleString()} tokens\n- Cache miss input: ${usage.cacheMissInput.toLocaleString()} tokens\n- Output: ${usage.output.toLocaleString()} tokens\n- Reasoning output: ${usage.reasoningOutput.toLocaleString()} tokens\n- Total: ${usage.total.toLocaleString()} tokens\n- Est. Cost: $${cost.toFixed(4)}`;
563
621
  }
564
622
  ctx.setMessages(prev => [...prev, { role: 'assistant', content }]);
565
623
  return true;
@@ -582,11 +640,11 @@ async function cmdTheme(ctx, input) {
582
640
  }
583
641
  const success = themeManager.setTheme(themeName);
584
642
  if (success) {
585
- ctx.addServiceNotice?.(`🎨 Тема изменена: ${themeName}`);
643
+ ctx.addServiceNotice?.(`[theme] Тема изменена: ${themeName}`);
586
644
  ctx.setStatusText(`Тема: ${themeName}`);
587
645
  }
588
646
  else {
589
- ctx.addServiceNotice?.(`❌ Тема "${themeName}" не найдена`);
647
+ ctx.addServiceNotice?.(`[err] Тема "${themeName}" не найдена`);
590
648
  }
591
649
  return true;
592
650
  }
@@ -597,7 +655,7 @@ async function cmdModel(ctx, input) {
597
655
  ctx.onModelPicker();
598
656
  }
599
657
  else {
600
- const list = DEEPSEEK_MODELS.map(m => `- **${m.label}** (\`${m.id}\`): ${m.description}${m.id === ctx.config.model ? ' current' : ''}`).join('\n');
658
+ const list = DEEPSEEK_MODELS.map(m => `- **${m.label}** (\`${m.id}\`): ${m.description}${m.id === ctx.config.model ? ' [current]' : ''}`).join('\n');
601
659
  ctx.setMessages(prev => [...prev, {
602
660
  role: 'assistant',
603
661
  content: `**Available Models:**\n${list}\n\nCurrent: **${ctx.config.model}**\nUse \`/model <id>\` to switch.`,
@@ -611,21 +669,27 @@ async function cmdModel(ctx, input) {
611
669
  const { saveConfig } = await import('../config/loader.js');
612
670
  await saveConfig({ ...ctx.config, model: targetId });
613
671
  const label = found?.label ?? targetId;
614
- ctx.addServiceNotice?.(`🤖 Модель: ${label} (${targetId})`);
672
+ ctx.addServiceNotice?.(`[model] Модель: ${label} (${targetId})`);
615
673
  return true;
616
674
  }
617
675
  async function cmdLang(ctx, input) {
618
676
  const code = input.split(/\s+/).pop()?.toLowerCase();
619
677
  if (!code || !['en', 'ru', 'zh'].includes(code)) {
620
- ctx.setMessages(prev => [...prev, {
621
- role: 'assistant',
622
- content: 'Usage: `/lang en|ru|zh`',
623
- }]);
678
+ // Open interactive picker if available, fallback to usage text
679
+ if (ctx.onLangPicker) {
680
+ ctx.onLangPicker();
681
+ }
682
+ else {
683
+ ctx.setMessages(prev => [...prev, {
684
+ role: 'assistant',
685
+ content: 'Usage: `/lang en|ru|zh`',
686
+ }]);
687
+ }
624
688
  return true;
625
689
  }
626
690
  i18n.setLocale(code);
627
691
  await saveConfig({ ...ctx.config, language: code });
628
- ctx.addServiceNotice?.(`🌐 Язык изменён: ${code}`);
692
+ ctx.addServiceNotice?.(`[lang] Язык изменён: ${code}`);
629
693
  return true;
630
694
  }
631
695
  async function cmdExtensions(ctx) {
@@ -651,7 +715,7 @@ async function cmdFollowup(ctx) {
651
715
  const suggestions = generateFollowups(lastContent);
652
716
  ctx.setMessages(prev => [...prev, {
653
717
  role: 'assistant',
654
- content: `💡 **Follow-up suggestions:**\n${suggestions.map((s, i) => `${i + 1}. ${s}`).join('\n')}`,
718
+ content: `**Follow-up suggestions:**\n${suggestions.map((s, i) => `${i + 1}. ${s}`).join('\n')}`,
655
719
  }]);
656
720
  return true;
657
721
  }
@@ -673,7 +737,7 @@ async function cmdLogs(ctx) {
673
737
  const fileList = files.map((f, i) => `${i + 1}. ${f}`).join('\n');
674
738
  ctx.setMessages(prev => [...prev, {
675
739
  role: 'assistant',
676
- content: `📋 **Recent Logs** (${files.length} files):\n\n${fileList}\n\n**Tail of ${files[0]}:**\n\`\`\`\n${latestLog}\n\`\`\``,
740
+ content: `**Recent Logs** (${files.length} files):\n\n${fileList}\n\n**Tail of ${files[0]}:**\n\`\`\`\n${latestLog}\n\`\`\``,
677
741
  }]);
678
742
  }
679
743
  catch {
@@ -697,7 +761,7 @@ async function cmdTools(ctx) {
697
761
  const modeToolNames = new Set(modeTools.map(t => t.tool.name));
698
762
  const toolLines = allTools.map(def => {
699
763
  const t = def.tool;
700
- const inMode = modeToolNames.has(t.name) ? '' : '';
764
+ const inMode = modeToolNames.has(t.name) ? '[on]' : '[off]';
701
765
  let approvalLabel;
702
766
  if (def.approval === 'never') {
703
767
  approvalLabel = 'read-only';
@@ -716,7 +780,7 @@ async function cmdTools(ctx) {
716
780
  '',
717
781
  `**Текущий режим:** \`${ctx.approvalMode}\``,
718
782
  ...(ctx.approvalMode === 'plan'
719
- ? ['> ⚠️ В PLAN mode доступны только read-only инструменты. Для записи используйте `/setup` и смените режим на default/auto-edit/turbo.']
783
+ ? ['> [warn] В PLAN mode доступны только read-only инструменты. Для записи используйте `/setup` и смените режим на default/auto-edit/turbo.']
720
784
  : []),
721
785
  ].filter(Boolean).join('\n');
722
786
  ctx.setMessages(prev => [...prev, {
@@ -739,7 +803,7 @@ async function cmdBrowserTest(ctx, input) {
739
803
  else {
740
804
  headless = false; // default: headed (видимое окно)
741
805
  }
742
- ctx.setStatusText('🧪 Запуск browser test...');
806
+ ctx.setStatusText('Запуск browser test...');
743
807
  // Сохраняем текущий режим Chrome, чтобы восстановить после теста
744
808
  const prevState = chromeManager.getState();
745
809
  const prevHeadless = prevState.headless;
@@ -753,7 +817,7 @@ async function cmdBrowserTest(ctx, input) {
753
817
  catch (err) {
754
818
  ctx.setMessages(prev => [...prev, {
755
819
  role: 'assistant',
756
- content: `## Browser Test Error\n\n\`\`\`\n${String(err)}\n\`\`\``,
820
+ content: `## Browser Test Error\n\n\`\`\`\n${String(err)}\n\`\`\``,
757
821
  }]);
758
822
  }
759
823
  finally {
@@ -771,21 +835,21 @@ async function cmdBrowserRealTest(ctx, input) {
771
835
  const headless = parts.includes('--headless');
772
836
  const siteArgs = parts.filter(p => !p.startsWith('--'));
773
837
  const sites = siteArgs.length > 0 ? siteArgs : undefined;
774
- ctx.setStatusText('🌐 Real site smoke-test...');
838
+ ctx.setStatusText('Real site smoke-test...');
775
839
  const prevState = chromeManager.getState();
776
840
  try {
777
841
  const report = await browserRealTest({ sites, headless });
778
842
  if (saveReport) {
779
843
  const { writeFile } = await import('node:fs/promises');
780
844
  await writeFile('BROWSER_REAL_TEST_REPORT.md', report, 'utf8');
781
- ctx.addServiceNotice?.('📄 Report saved: BROWSER_REAL_TEST_REPORT.md');
845
+ ctx.addServiceNotice?.('Report saved: BROWSER_REAL_TEST_REPORT.md');
782
846
  }
783
847
  ctx.setMessages(prev => [...prev, { role: 'assistant', content: report }]);
784
848
  }
785
849
  catch (err) {
786
850
  ctx.setMessages(prev => [...prev, {
787
851
  role: 'assistant',
788
- content: `## browser-real-test error\n\`\`\`\n${String(err)}\n\`\`\``,
852
+ content: `## browser-real-test error\n\`\`\`\n${String(err)}\n\`\`\``,
789
853
  }]);
790
854
  }
791
855
  finally {
@@ -805,7 +869,7 @@ async function cmdChrome(ctx, input) {
805
869
  const modeStr = state.connected
806
870
  ? (state.headless ? 'headless (фоновый)' : 'headed (видимое окно)')
807
871
  : 'не запущен';
808
- ctx.addServiceNotice?.(`🌐 Chrome: ${modeStr}${state.connected ? ` | PID: ${state.managedProcessPid ?? '—'} | Порт: ${state.debugPort}` : ''}`);
872
+ ctx.addServiceNotice?.(`Chrome: ${modeStr}${state.connected ? ` | PID: ${state.managedProcessPid ?? '—'} | Порт: ${state.debugPort}` : ''}`);
809
873
  return true;
810
874
  }
811
875
  // Determine desired mode
@@ -817,7 +881,7 @@ async function cmdChrome(ctx, input) {
817
881
  desiredHeadless = false;
818
882
  }
819
883
  else {
820
- ctx.addServiceNotice?.(' /chrome: используйте --headed, --headless или без флага для статуса');
884
+ ctx.addServiceNotice?.('[err] /chrome: используйте --headed, --headless или без флага для статуса');
821
885
  return true;
822
886
  }
823
887
  try {
@@ -827,10 +891,10 @@ async function cmdChrome(ctx, input) {
827
891
  // Save to config
828
892
  ctx.config.chromeHeadless = state.headless;
829
893
  saveConfig({ chromeHeadless: state.headless }).catch(() => { });
830
- ctx.addServiceNotice?.(`🌐 Chrome: ${modeStr} | PID: ${state.managedProcessPid ?? '—'} | Порт: ${state.debugPort}`);
894
+ ctx.addServiceNotice?.(`Chrome: ${modeStr} | PID: ${state.managedProcessPid ?? '—'} | Порт: ${state.debugPort}`);
831
895
  }
832
896
  catch (err) {
833
- ctx.addServiceNotice?.(`❌ Chrome: ${String(err)}`);
897
+ ctx.addServiceNotice?.(`[err] Chrome: ${String(err)}`);
834
898
  }
835
899
  return true;
836
900
  }
@@ -839,12 +903,12 @@ async function cmdLastBrowserTest(ctx) {
839
903
  if (!result) {
840
904
  ctx.setMessages(prev => [...prev, {
841
905
  role: 'assistant',
842
- content: '## 📋 Последний browser test\n\nНет сохранённого отчёта последнего теста. Запустите `/browser-test` сначала.',
906
+ content: '## Последний browser test\n\nНет сохранённого отчёта последнего теста. Запустите `/browser-test` сначала.',
843
907
  }]);
844
908
  return true;
845
909
  }
846
910
  const lines = [
847
- '## 📋 Последний browser test',
911
+ '## Последний browser test',
848
912
  '',
849
913
  `> **Timestamp:** ${result.timestamp}`,
850
914
  '> **Источник:** сохранённый structured result (не LLM-реконструкция)',
@@ -853,7 +917,7 @@ async function cmdLastBrowserTest(ctx) {
853
917
  '|-----|--------|-------------|',
854
918
  ];
855
919
  for (const step of result.steps) {
856
- const icon = step.status === 'passed' ? '' : step.status === 'failed' ? '' : '⏭️';
920
+ const icon = step.status === 'passed' ? '[ok]' : step.status === 'failed' ? '[err]' : '[skip]';
857
921
  const dur = step.durationMs > 0 ? `${step.durationMs}ms` : '—';
858
922
  lines.push(`| ${icon} ${step.name} | ${step.status} | ${dur} |`);
859
923
  }
@@ -878,32 +942,76 @@ async function cmdCapabilities(ctx) {
878
942
  `**Режим:** \`${ctx.approvalMode}\``,
879
943
  '',
880
944
  '### Чтение и поиск',
881
- ...modeReadTools.map(t => ` - \`${t.tool.name}\` — ${t.tool.description}`),
945
+ ...modeReadTools.map(t => ` - [on] \`${t.tool.name}\` — ${t.tool.description}`),
882
946
  ...readTools
883
947
  .filter(t => !modeReadTools.some(mt => mt.tool.name === t.tool.name))
884
- .map(t => ` - \`${t.tool.name}\` — заблокирован в PLAN mode`),
948
+ .map(t => ` - [off] \`${t.tool.name}\` — заблокирован в PLAN mode`),
885
949
  '',
886
950
  '### Запись и исполнение',
887
- ...modeWriteTools.map(t => ` - \`${t.tool.name}\` — ${t.tool.description} (${t.approval === 'auto' ? 'авто-подтверждение' : 'требует подтверждения'})`),
951
+ ...modeWriteTools.map(t => ` - [on] \`${t.tool.name}\` — ${t.tool.description} (${t.approval === 'auto' ? 'авто-подтверждение' : 'требует подтверждения'})`),
888
952
  ...writeTools
889
953
  .filter(t => !modeWriteTools.some(mt => mt.tool.name === t.tool.name))
890
- .map(t => ` - \`${t.tool.name}\` — заблокирован в PLAN mode`),
954
+ .map(t => ` - [off] \`${t.tool.name}\` — заблокирован в PLAN mode`),
891
955
  '',
892
956
  ];
893
957
  if (ctx.approvalMode === 'plan') {
894
- lines.push('> ⚠️ **Вы в PLAN mode.**', '> У меня есть инструменты write_file и edit, но в этом режиме они отключены.', '> Я могу предложить изменения, но не могу применить их напрямую.', '> Используйте `/setup` и выберите другой режим (default, auto-edit, turbo) для включения записи.', '');
958
+ lines.push('> [warn] **Вы в PLAN mode.**', '> У меня есть инструменты write_file и edit, но в этом режиме они отключены.', '> Я могу предложить изменения, но не могу применить их напрямую.', '> Используйте `/setup` и выберите другой режим (default, auto-edit, turbo) для включения записи.', '');
895
959
  }
896
960
  lines.push('### Дополнительно');
897
- lines.push(' - 🌐 **MCP серверы** — подключаемые внешние инструменты');
898
- lines.push(' - 🧩 **Расширения** — плагины, добавляющие функциональность');
899
- lines.push(' - 🧠 **Навыки (Skills)** — предустановленные сценарии работы');
900
- lines.push(' - 🤖 **Под-агенты** — дочерние агенты для параллельных задач');
961
+ lines.push(' - **MCP серверы** — подключаемые внешние инструменты');
962
+ lines.push(' - **Расширения** — плагины, добавляющие функциональность');
963
+ lines.push(' - **Навыки (Skills)** — предустановленные сценарии работы');
964
+ lines.push(' - **Под-агенты** — дочерние агенты для параллельных задач');
901
965
  ctx.setMessages(prev => [...prev, {
902
966
  role: 'assistant',
903
967
  content: lines.join('\n'),
904
968
  }]);
905
969
  return true;
906
970
  }
971
+ // ─── Budget command handler ──────────────────────────────────────────────────
972
+ async function cmdBudget(ctx, input) {
973
+ const sub = input.slice('/budget'.length).trim();
974
+ const notice = ctx.addServiceNotice ?? ((text) => {
975
+ ctx.setMessages(prev => [...prev, { role: 'assistant', content: text }]);
976
+ });
977
+ if (sub === 'status') {
978
+ const budget = ctx.getBudget?.();
979
+ if (!budget) {
980
+ notice('Budget: off');
981
+ }
982
+ else {
983
+ const prefix = 'Budget: active';
984
+ const lines = [
985
+ prefix,
986
+ `maxToolCalls: ${budget.maxToolCalls}`,
987
+ `maxApiCalls: ${budget.maxApiCalls}`,
988
+ `maxReadFiles: ${budget.maxReadFiles}`,
989
+ `maxShellCommands: ${budget.maxShellCommands}`,
990
+ ];
991
+ notice(lines.join('\n'));
992
+ }
993
+ return true;
994
+ }
995
+ if (sub === 'off') {
996
+ ctx.setBudget?.(undefined);
997
+ notice('Budget: off');
998
+ return true;
999
+ }
1000
+ if (sub === 'audit' || sub === 'small') {
1001
+ ctx.setBudget?.({ ...AUDIT_BUDGET_PRESET });
1002
+ const lines = [
1003
+ 'Budget: audit enabled',
1004
+ `maxToolCalls: ${AUDIT_BUDGET_PRESET.maxToolCalls}`,
1005
+ `maxApiCalls: ${AUDIT_BUDGET_PRESET.maxApiCalls}`,
1006
+ `maxReadFiles: ${AUDIT_BUDGET_PRESET.maxReadFiles}`,
1007
+ `maxShellCommands: ${AUDIT_BUDGET_PRESET.maxShellCommands}`,
1008
+ ];
1009
+ notice(lines.join('\n'));
1010
+ return true;
1011
+ }
1012
+ notice('Usage: /budget status|off|audit|small');
1013
+ return true;
1014
+ }
907
1015
  export const COMMANDS = [
908
1016
  { name: '/help', description: 'Show this help', handler: cmdHelp },
909
1017
  { name: '/setup', description: 'Settings: language, API key, theme, mode', handler: cmdSetup },
@@ -935,6 +1043,7 @@ export const COMMANDS = [
935
1043
  { name: '/browser-real-test', description: 'Smoke test on real websites', handler: cmdBrowserRealTest },
936
1044
  { name: '/last-browser-test', description: 'Show last browser test report', handler: cmdLastBrowserTest },
937
1045
  { name: '/chrome', description: 'Chrome mode: --headed|--headless|-s', handler: cmdChrome },
1046
+ { name: '/budget', description: 'Budget: /budget status|off|audit|small', handler: cmdBudget },
938
1047
  ];
939
1048
  export const COMMAND_NAMES = COMMANDS.map(c => c.name);
940
1049
  export const COMMAND_MAP = new Map(COMMANDS.map(c => [c.name, c.description]));