zames_pro 2.50.2 → 2.52.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/dist/i18n.js CHANGED
@@ -39,6 +39,10 @@ const CATALOG = {
39
39
  'common.unknown': { ru: 'неизвестно', en: 'unknown' },
40
40
  // ---------- help ----------
41
41
  'help.options': { ru: 'Опции CLI:', en: 'CLI options:' },
42
+ 'help.color_note': {
43
+ ru: 'Цвет отключается переменной окружения NO_COLOR=1.',
44
+ en: 'Disable color with the NO_COLOR=1 environment variable.',
45
+ },
42
46
  'help.opt.dir': {
43
47
  ru: 'рабочая директория агента',
44
48
  en: 'agent working directory',
@@ -95,6 +99,14 @@ const CATALOG = {
95
99
  ru: '↑ / ↓ история введённых сообщений',
96
100
  en: '↑ / ↓ input history',
97
101
  },
102
+ 'help.key.search': {
103
+ ru: 'Ctrl+R поиск по истории (обратный)',
104
+ en: 'Ctrl+R reverse-search the history',
105
+ },
106
+ 'help.key.undo': {
107
+ ru: 'Ctrl+_ отменить правку в строке ввода',
108
+ en: 'Ctrl+_ undo the last edit in the input line',
109
+ },
98
110
  'help.key.words': {
99
111
  ru: 'Ctrl+← / Ctrl+→ перемещение по словам',
100
112
  en: 'Ctrl+← / Ctrl+→ move by words',
@@ -140,6 +152,10 @@ const CATALOG = {
140
152
  ru: '/resume <n> открыть чат №n из /chats',
141
153
  en: '/resume <n> open chat #n from /chats',
142
154
  },
155
+ 'help.cmd.last': {
156
+ ru: '/last открыть последний чат этого каталога',
157
+ en: '/last open the last chat of this directory',
158
+ },
143
159
  'help.cmd.chat': {
144
160
  ru: '/chat показать текущий chat id',
145
161
  en: '/chat show current chat id',
@@ -351,6 +367,10 @@ const CATALOG = {
351
367
  ru: '/debug-dom сохранить HTML страницы (для отладки)',
352
368
  en: '/debug-dom dump page HTML (debug)',
353
369
  },
370
+ 'help.no_topic': {
371
+ ru: 'Нет команды по запросу «{v}». Попробуй /help без аргумента.',
372
+ en: 'No command matches "{v}". Try /help without arguments.',
373
+ },
354
374
  'help.cmd.help': {
355
375
  ru: '/help, help справка',
356
376
  en: '/help, help this help',
@@ -443,7 +463,15 @@ const CATALOG = {
443
463
  'help.files.profile': { ru: 'Профиль:', en: 'Profile:' },
444
464
  'help.files.snapshots': { ru: 'Снапшоты:', en: 'Snapshots:' },
445
465
  'help.files.tmp': { ru: 'Временные:', en: 'Temp:' },
466
+ 'help.files.tmp_note': {
467
+ ru: '(в .gitignore, очищается при старте)',
468
+ en: '(.gitignore, cleaned on start)',
469
+ },
446
470
  'help.files.config': { ru: 'Конфиг:', en: 'Config:' },
471
+ 'help.toggles_legend': {
472
+ ru: 'Индикаторы в статус-строке: 🧠 — глубокое размышление, 🌐 — поиск в интернете (видны, когда включены).',
473
+ en: 'Status-line icons: 🧠 — Deep thinking, 🌐 — web search (shown when enabled).',
474
+ },
447
475
  // ---------- spinner ----------
448
476
  'spinner.phrases': {
449
477
  ru: 'Анализирую запрос…|Изучаю контекст…|Планирую решение…|Обрабатываю данные…|Формулирую ответ…|Проверяю детали…|Собираю информацию…|Обдумываю варианты…|Структурирую мысли…|Готовлю решение…|Анализирую код…|Уточняю детали…|Выполняю вычисления…|Систематизирую данные…|Прорабатываю задачу…|Проверяю логику…|Оптимизирую подход…|Формирую выводы…',
@@ -464,6 +492,15 @@ const CATALOG = {
464
492
  'state.generating': { ru: '▶ генерация', en: '▶ generating' },
465
493
  'editor.more': { ru: '…ещё {n}', en: '…{n} more' },
466
494
  'editor.answer': { ru: '● Ответ', en: '● Answer' },
495
+ // Reverse search (Ctrl+R) status line. The query may be empty.
496
+ 'editor.search_prompt': {
497
+ ru: '(обратный поиск) `{q}` — Ctrl+R дальше, Enter принять, Esc отмена',
498
+ en: '(reverse-i-search) `{q}` — Ctrl+R next, Enter accept, Esc cancel',
499
+ },
500
+ 'editor.search_fail': {
501
+ ru: '(обратный поиск) `{q}` — не найдено, Esc отмена',
502
+ en: '(reverse-i-search) `{q}` — not found, Esc cancel',
503
+ },
467
504
  // ---------- status ----------
468
505
  'status.workdir': {
469
506
  ru: 'Рабочая директория: {v}',
@@ -539,10 +576,15 @@ const CATALOG = {
539
576
  en: 'Opening chat {id}...',
540
577
  },
541
578
  'msg.chat_opened': { ru: 'Чат открыт.', en: 'Chat opened.' },
579
+ 'msg.version': { ru: 'Версия: {v}', en: 'Version: {v}' },
542
580
  'msg.interactive': {
543
581
  ru: 'Интерактивный режим. Введите задачу. Команды — /help. Выход — /exit.',
544
582
  en: 'Interactive mode. Enter a task. Commands — /help. Exit — /exit.',
545
583
  },
584
+ 'msg.first_hint': {
585
+ ru: 'Подсказка: «/» — список команд, Ctrl+R — поиск по истории, картинку можно вставить через Ctrl+Shift+V.',
586
+ en: 'Hint: "/" lists commands, Ctrl+R searches history, paste an image with Ctrl+Shift+V.',
587
+ },
546
588
  'msg.queue_hint': {
547
589
  ru: 'Пока агент работает, можно печатать следующее сообщение — оно уйдёт в очередь (Enter — отправить, Esc — прервать).',
548
590
  en: 'While the agent works you can type the next message — it goes to the queue (Enter — send, Esc — abort).',
@@ -601,6 +643,99 @@ const CATALOG = {
601
643
  'dur.h': { ru: 'ч', en: 'h' },
602
644
  'dur.m': { ru: 'м', en: 'm' },
603
645
  'dur.s': { ru: 'с', en: 's' },
646
+ // ---------- /diff ----------
647
+ 'diff.no_changes': { ru: '(нет изменений)', en: '(no changes)' },
648
+ 'diff.more_lines': {
649
+ ru: '... [{n} строк ещё, полный вывод — Bash git diff]',
650
+ en: '... [{n} more lines, use Bash git diff for the full output]',
651
+ },
652
+ // ---------- /cost ----------
653
+ 'cost.title': { ru: 'Статистика сессии:', en: 'Session stats:' },
654
+ 'cost.context_known': {
655
+ ru: ' контекст: ~{n} токенов (DeepSeek accumulated_token_usage)',
656
+ en: ' context: ~{n} tokens (DeepSeek accumulated_token_usage)',
657
+ },
658
+ 'cost.context_unknown': {
659
+ ru: ' контекст: неизвестно (DeepSeek сообщит после первого ответа в чате)',
660
+ en: ' context: unknown (DeepSeek reports it after the first answer in a chat)',
661
+ },
662
+ 'cost.tasks': { ru: ' задач: {n}', en: ' tasks: {n}' },
663
+ 'cost.tool_calls': {
664
+ ru: ' вызовов инструментов: {n}',
665
+ en: ' tool calls: {n}',
666
+ },
667
+ 'cost.auto_compacts': { ru: ' авто-сжатий: {n}', en: ' auto-compacts: {n}' },
668
+ 'cost.by_tool': { ru: ' по инструментам:', en: ' by tool:' },
669
+ 'cost.duration': { ru: ' длительность: {dur}', en: ' duration: {dur}' },
670
+ 'cost.started': { ru: ' начало: {v}', en: ' started: {v}' },
671
+ 'cost.transcript': { ru: ' транскрипт: {v}', en: ' transcript: {v}' },
672
+ 'cost.off': { ru: '(выкл)', en: '(off)' },
673
+ // ---------- /doctor ----------
674
+ 'doctor.title': { ru: 'Диагностика:', en: 'Doctor:' },
675
+ 'doctor.repo': { ru: 'репозиторий ({v})', en: 'repo ({v})' },
676
+ 'doctor.detached': { ru: 'detached', en: 'detached' },
677
+ 'doctor.not_repo': { ru: 'не репозиторий', en: 'not a repository' },
678
+ 'doctor.loaded': { ru: 'загружен', en: 'loaded' },
679
+ 'doctor.error': { ru: 'ошибка: {v}', en: 'error: {v}' },
680
+ 'doctor.unknown': { ru: 'неизвестно', en: 'unknown' },
681
+ 'doctor.bundled': { ru: 'встроенный chromium', en: 'bundled chromium' },
682
+ 'doctor.auth_saved': {
683
+ ru: 'сессия сохранена (авто-вход готов)',
684
+ en: 'session saved (auto re-login ready)',
685
+ },
686
+ 'doctor.auth_none': { ru: 'нет сохранённой сессии', en: 'no saved session' },
687
+ 'doctor.clipboard_none': {
688
+ ru: 'инструмент не найден (вставка картинок отключена)',
689
+ en: 'no tool found (image paste disabled)',
690
+ },
691
+ 'doctor.mcp': {
692
+ ru: '{servers} сервер(ов), {tools} инструмент(ов)',
693
+ en: '{servers} server(s), {tools} tool(s)',
694
+ },
695
+ 'doctor.tokens': { ru: '{v} токенов', en: '{v} tokens' },
696
+ 'doctor.origin_ok': { ru: 'origin настроен', en: 'origin configured' },
697
+ 'doctor.origin_none': { ru: 'нет origin', en: 'no origin' },
698
+ 'doctor.send_pause': {
699
+ ru: '{n}с между отправками агента',
700
+ en: '{n}s between agent sends',
701
+ },
702
+ 'doctor.ssh': { ru: 'SSH-подключение', en: 'SSH remote' },
703
+ 'doctor.local': { ru: 'локальный терминал', en: 'local terminal' },
704
+ 'doctor.ua_cached': {
705
+ ru: 'UA закэширован (без перезапуска)',
706
+ en: 'UA cached (no relaunch)',
707
+ },
708
+ 'doctor.ua_not_cached': {
709
+ ru: 'UA ещё не закэширован (первый запуск перезапустится один раз)',
710
+ en: 'no cached UA yet (first start relaunches once)',
711
+ },
712
+ // ---------- /permissions ----------
713
+ 'perm.title': {
714
+ ru: 'Права инструментов (настройки подтверждений):',
715
+ en: 'Tool permissions (confirmation settings):',
716
+ },
717
+ 'perm.write': { ru: ' Write: {v}', en: ' Write: {v}' },
718
+ 'perm.edit': { ru: ' Edit: {v}', en: ' Edit: {v}' },
719
+ 'perm.bash': { ru: ' Bash: {v}', en: ' Bash: {v}' },
720
+ 'perm.ask': { ru: 'спрашивать', en: 'ask' },
721
+ 'perm.allow': { ru: 'разрешить', en: 'allow' },
722
+ 'perm.always': {
723
+ ru: ' Всегда подтверждать (regex):',
724
+ en: ' Always confirm (regex):',
725
+ },
726
+ 'perm.change_hint': {
727
+ ru: 'Изменить: /config set confirmation.write false (и .edit / .bash)',
728
+ en: 'Change with: /config set confirmation.write false (and .edit / .bash)',
729
+ },
730
+ // ---------- /add-dir ----------
731
+ 'adddir.usage': {
732
+ ru: 'Использование: /add-dir <path>',
733
+ en: 'Usage: /add-dir <path>',
734
+ },
735
+ 'adddir.same': {
736
+ ru: 'Это уже рабочая директория.',
737
+ en: 'This is already the working directory.',
738
+ },
604
739
  'msg.from_queue': { ru: '▶ Из очереди: ', en: '▶ From queue: ' },
605
740
  'msg.from_queue_batch': {
606
741
  ru: '▶ Из очереди ({n} сообщ.): ',
@@ -961,6 +1096,10 @@ const CATALOG = {
961
1096
  },
962
1097
  'chats.history_you': { ru: 'Вы', en: 'You' },
963
1098
  'chats.history_agent': { ru: 'Агент', en: 'Agent' },
1099
+ 'time.now': { ru: 'только что', en: 'just now' },
1100
+ 'time.min_ago': { ru: '{n} мин назад', en: '{n}m ago' },
1101
+ 'time.hour_ago': { ru: '{n} ч назад', en: '{n}h ago' },
1102
+ 'time.day_ago': { ru: '{n} дн назад', en: '{n}d ago' },
964
1103
  'sessions.dir': { ru: 'Папка сессий: {v}', en: 'Sessions dir: {v}' },
965
1104
  'sessions.none': {
966
1105
  ru: 'Сохранённых сессий нет. Они появятся после первой задачи/чата.',
@@ -1273,6 +1412,19 @@ const CATALOG = {
1273
1412
  en: '✔ Saved: {v} = {value}',
1274
1413
  },
1275
1414
  'cfg.menu.default': { ru: 'по умолчанию', en: 'default' },
1415
+ 'cfg.menu.filter_hint': {
1416
+ ru: '/ — поиск по настройкам, d — сбросить (дважды), * — изменено от дефолта',
1417
+ en: '/ search settings, d reset (press twice), * changed from default',
1418
+ },
1419
+ 'cfg.menu.filter_active': {
1420
+ ru: 'Фильтр: «{q}» (Esc/Enter — очистить)',
1421
+ en: 'Filter: "{q}" (Esc/Enter to clear)',
1422
+ },
1423
+ 'cfg.menu.no_match': { ru: '(нет совпадений)', en: '(no matches)' },
1424
+ 'cfg.menu.reset_confirm': {
1425
+ ru: 'Нажми d ещё раз, чтобы сбросить {v}',
1426
+ en: 'Press d again to reset {v}',
1427
+ },
1276
1428
  'cfg.menu.notty': {
1277
1429
  ru: 'Меню доступно только в интерактивном терминале. Используй /config list|get|set.',
1278
1430
  en: 'The menu needs an interactive terminal. Use /config list|get|set.',
package/dist/index.js CHANGED
@@ -17,12 +17,12 @@ import { translate, normalizeLocale, localeDisplayName, isLocale, } from './i18n
17
17
  import { Transcript } from './transcript.js';
18
18
  import { UndoStore } from './undo.js';
19
19
  import { selfReview, selfDiff, selfApply, selfList } from './self-review.js';
20
- import { formatDiff, diffGitArgs, parseTranscript, summarizeTranscript, renderCost, formatExport, defaultExportPath, renderDoctor, renderPermissions, resolveExtraDir, buildReviewPrompt, formatDuration, trimRestoredMessages, RESTORED_HISTORY_LIMIT, mergeMessages, hasQueuedJob, isSlashCommand, parseQueueCommand, parseLiveToggle, parseGoalCommand, isLiveConfigCommand, withGoal, formatQueueList, ctrlCEscalation, } from './commands.js';
20
+ import { formatDiff, diffGitArgs, parseTranscript, summarizeTranscript, renderCost, formatExport, defaultExportPath, renderDoctor, renderPermissions, resolveExtraDir, buildReviewPrompt, formatDuration, formatRelativeTime, trimRestoredMessages, RESTORED_HISTORY_LIMIT, mergeMessages, hasQueuedJob, isSlashCommand, parseQueueCommand, parseLiveToggle, parseGoalCommand, isLiveConfigCommand, withGoal, formatQueueList, ctrlCEscalation, } from './commands.js';
21
21
  import { performCompact } from './compact.js';
22
22
  import { Scheduler, parseInterval, formatInterval, formatJobLine, parseCron, } from './scheduler.js';
23
23
  import { renderMarkdown } from './markdown.js';
24
24
  import { closeWeb } from './web.js';
25
- import { saveSession, loadLastSession, readSession, listSessions, sessionsDir, } from './sessions.js';
25
+ import { saveSession, loadLastSession, readSession, listSessions, sessionsDir, loadHistory, saveHistory, } from './sessions.js';
26
26
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
27
27
  const NL = String.fromCharCode(10);
28
28
  // Floor for a /loop interval. A loop faster than the send throttle (15s)
@@ -292,10 +292,14 @@ ${theme.bold(t('help.options'))}
292
292
  --version, -v ${t('help.opt.version')}
293
293
  --help, -h ${t('help.opt.help')}
294
294
 
295
+ ${t('help.color_note')}
296
+
295
297
  ${theme.bold(t('help.while_working'))}
296
298
  ${t('help.key.queue')}
297
299
  ${t('help.key.history')}
300
+ ${t('help.key.search')}
298
301
  ${t('help.key.words')}
302
+ ${t('help.key.undo')}
299
303
  ${t('help.key.slash')}
300
304
  ${t('help.key.newline')}
301
305
  ${t('help.key.backslash')}
@@ -308,6 +312,7 @@ ${theme.bold(t('help.commands'))}
308
312
  ${t('help.cmd.resume_id')}
309
313
  ${t('help.cmd.chats')}
310
314
  ${t('help.cmd.resume')}
315
+ ${t('help.cmd.last')}
311
316
  ${t('help.cmd.chat')}
312
317
  ${t('help.cmd.cd')}
313
318
  ${t('help.cmd.pwd')}
@@ -316,20 +321,20 @@ ${theme.bold(t('help.commands'))}
316
321
  ${t('help.cmd.undo')}
317
322
  ${t('help.cmd.undo_list')}
318
323
  ${t('help.cmd.transcript')}
319
- ${t('help.cmd.diff')}
320
- ${t('help.cmd.cost')}
321
- ${t('help.cmd.export')}
322
- ${t('help.cmd.doctor')}
323
- ${t('help.cmd.permissions')}
324
- ${t('help.cmd.add_dir')}
325
- ${t('help.cmd.review')}
326
- ${t('help.cmd.goal')}
327
- ${t('help.cmd.loop')}
328
- ${t('help.cmd.cron')}
329
- ${t('help.cmd.jobs')}
330
- ${t('help.cmd.thinking')}
331
- ${t('help.cmd.web')}
332
- ${t('help.cmd.compact')}
324
+ ${t('help.cmd.diff')}
325
+ ${t('help.cmd.cost')}
326
+ ${t('help.cmd.export')}
327
+ ${t('help.cmd.doctor')}
328
+ ${t('help.cmd.permissions')}
329
+ ${t('help.cmd.add_dir')}
330
+ ${t('help.cmd.review')}
331
+ ${t('help.cmd.goal')}
332
+ ${t('help.cmd.loop')}
333
+ ${t('help.cmd.cron')}
334
+ ${t('help.cmd.jobs')}
335
+ ${t('help.cmd.thinking')}
336
+ ${t('help.cmd.web')}
337
+ ${t('help.cmd.compact')}
333
338
  ${t('help.cmd.queue')}
334
339
  ${t('help.cmd.tasks')}
335
340
  ${t('help.cmd.config')}
@@ -353,9 +358,11 @@ ${theme.bold(t('help.files'))}
353
358
  ${t('help.files.sessions')} ${sessionsDir()}
354
359
  ${t('help.files.profile')} ~/.zames/profile
355
360
  ${t('help.files.snapshots')} ~/.zames/snapshots
356
- ${t('help.files.tmp')} <project>/tmp (.gitignore, cleaned on start)
361
+ ${t('help.files.tmp')} <project>/tmp ${t('help.files.tmp_note')}
357
362
  ${t('help.files.config')} ${CONFIG_PATHS.HOME_CONFIG}
358
363
  ${CONFIG_PATHS.PROJECT_CONFIG}
364
+
365
+ ${theme.dim(t('help.toggles_legend'))}
359
366
  `);
360
367
  }
361
368
  function dirLabel(p) {
@@ -372,6 +379,7 @@ const SLASH_COMMANDS = [
372
379
  { name: '/chats', key: 'help.cmd.chats' },
373
380
  { name: '/resume', key: 'help.cmd.resume' },
374
381
  { name: '/resume-id', key: 'help.cmd.resume_id' },
382
+ { name: '/last', key: 'help.cmd.last' },
375
383
  { name: '/chat', key: 'help.cmd.chat' },
376
384
  { name: '/cd', key: 'help.cmd.cd' },
377
385
  { name: '/pwd', key: 'help.cmd.pwd' },
@@ -1190,9 +1198,14 @@ async function runTask(browser, tools, taskText, workdir, opts, attachments = []
1190
1198
  }
1191
1199
  // ---------- main ----------
1192
1200
  async function main() {
1201
+ // Read the package version once: --version prints it and the interactive
1202
+ // banner shows it (a version in the first lines is what a bug report needs).
1203
+ const pkgVersion = await fs
1204
+ .readFile(path.join(__dirname, '..', 'package.json'), 'utf-8')
1205
+ .then((raw) => JSON.parse(raw).version)
1206
+ .catch(() => '');
1193
1207
  if (hasFlag('--version') || hasFlag('-v')) {
1194
- const pkg = JSON.parse(await fs.readFile(path.join(__dirname, '..', 'package.json'), 'utf-8'));
1195
- console.log(pkg.version);
1208
+ console.log(pkgVersion || 'unknown');
1196
1209
  return;
1197
1210
  }
1198
1211
  if (hasFlag('--help') || hasFlag('-h')) {
@@ -1215,6 +1228,7 @@ async function main() {
1215
1228
  process.exit(1);
1216
1229
  }
1217
1230
  console.log(theme.system(t('msg.working_dir', { v: currentWorkdir })));
1231
+ console.log(theme.system(t('msg.version', { v: pkgVersion || '?' })));
1218
1232
  const transcript = new Transcript({
1219
1233
  dir: config.transcript.dir,
1220
1234
  enabled: config.transcript.enabled,
@@ -1327,6 +1341,7 @@ async function main() {
1327
1341
  }
1328
1342
  console.log(theme.system(t('msg.interactive')));
1329
1343
  console.log(theme.system(t('msg.queue_hint')));
1344
+ console.log(theme.dim(t('msg.first_hint')));
1330
1345
  let freshChatNext = true;
1331
1346
  let sendSystemPromptNext = true;
1332
1347
  // Resuming an existing chat already has the system-prompt at its start, so
@@ -1532,6 +1547,11 @@ async function main() {
1532
1547
  });
1533
1548
  editor = ed;
1534
1549
  ed.setTmpDir(TMP_DIR);
1550
+ // Restore the input history from the previous runs and persist every new
1551
+ // entry, so Ctrl+R / arrows can recall older messages after a restart.
1552
+ ed.history = loadHistory();
1553
+ ed._histIndex = ed.history.length;
1554
+ ed.onHistoryChange = (h) => saveHistory(h);
1535
1555
  // The token context right-aligned on the status line (above the input).
1536
1556
  // The editor pulls the number on every render, so it follows the live
1537
1557
  // DeepSeek counter (accumulated_token_usage) without a polling timer.
@@ -1946,6 +1966,7 @@ async function main() {
1946
1966
  fields: CONFIG_SCHEMA,
1947
1967
  t,
1948
1968
  get: (path) => getByPath(config, path),
1969
+ getDefault: (path) => getByPath(DEFAULTS, path),
1949
1970
  set: (field, raw) => configSetRaw(field, raw),
1950
1971
  reset: (field) => configResetField(field),
1951
1972
  });
@@ -2102,8 +2123,29 @@ async function main() {
2102
2123
  const lower = trimmed.toLowerCase();
2103
2124
  if (['/exit', '/quit', 'exit', 'quit'].includes(lower))
2104
2125
  break;
2105
- if (lower === '/help' || lower === 'help') {
2106
- printHelp();
2126
+ if (lower === '/help' ||
2127
+ lower === 'help' ||
2128
+ lower.startsWith('/help ') ||
2129
+ lower.startsWith('help ')) {
2130
+ const topic = trimmed.replace(/^\/?help\s*/i, '').trim();
2131
+ if (!topic) {
2132
+ printHelp();
2133
+ }
2134
+ else {
2135
+ // /help <command> — print the full description of ONE command instead
2136
+ // of making the operator scroll the whole list.
2137
+ const q = '/' + topic.replace(/^\//, '').toLowerCase();
2138
+ const match = buildSlashCommands().filter((c) => c.name.toLowerCase() === q ||
2139
+ c.name.toLowerCase().startsWith(q + ' '));
2140
+ if (!match.length) {
2141
+ console.error(theme.warn(t('help.no_topic', { v: topic })));
2142
+ }
2143
+ else {
2144
+ for (const c of match) {
2145
+ console.log(theme.assistant(c.name) + theme.dim(' ' + c.description));
2146
+ }
2147
+ }
2148
+ }
2107
2149
  continue;
2108
2150
  }
2109
2151
  if (['/new', '/clear', 'new'].includes(lower)) {
@@ -2401,12 +2443,47 @@ async function main() {
2401
2443
  const mark = s.id === currentChatId ? theme.user(' *') : '';
2402
2444
  const title = s.title ? ` ${s.title}` : '';
2403
2445
  const wd = s.workdir ? theme.dim(` [${dirLabel(s.workdir)}]`) : '';
2404
- console.log(` ${theme.user(n)}. ${s.id.slice(0, 8)}…${title}${wd}${mark}`);
2446
+ const age = formatRelativeTime(s.updatedAt, t);
2447
+ const ageText = age ? theme.dim(' ' + age) : '';
2448
+ console.log(` ${theme.user(n)}. ${s.id.slice(0, 8)}…${title}${wd}${ageText}${mark}`);
2405
2449
  });
2406
2450
  console.log(theme.system(t('sessions.restore_hint')));
2407
2451
  }
2408
2452
  continue;
2409
2453
  }
2454
+ if (lower === '/last') {
2455
+ // Quick resume of the last chat for THIS working directory — otherwise
2456
+ // finding it meant /chats (a network fetch) or copying the id.
2457
+ const last = loadLastSession(currentWorkdir);
2458
+ if (!last || !last.id) {
2459
+ console.error(theme.error(t('sessions.none')));
2460
+ continue;
2461
+ }
2462
+ if (editor)
2463
+ editor.lock(t('msg.input_locked'));
2464
+ try {
2465
+ if (editor)
2466
+ editor.clearScreen();
2467
+ console.log(theme.system(t('msg.opening_chat', { id: last.id })));
2468
+ await browser.openChat(last.id);
2469
+ currentChatId = last.id;
2470
+ freshChatNext = false;
2471
+ sendSystemPromptNext = promptOnResume();
2472
+ saveLastChat(last.id, currentWorkdir, last.title);
2473
+ transcript.log('resume_chat', { id: last.id, title: last.title });
2474
+ console.log(theme.assistant(t('msg.chat_opened')));
2475
+ loadSessionTodos(last.id);
2476
+ await printRestoredHistory(browser, editor, last.id);
2477
+ }
2478
+ catch (e) {
2479
+ console.error(theme.error(t('msg.open_chat_error', { v: '' })), e.message);
2480
+ }
2481
+ finally {
2482
+ if (editor)
2483
+ editor.unlock();
2484
+ }
2485
+ continue;
2486
+ }
2410
2487
  if (lower.startsWith('/resume-id ')) {
2411
2488
  const id = trimmed.slice('/resume-id'.length).trim();
2412
2489
  if (!id) {
@@ -2818,7 +2895,7 @@ async function main() {
2818
2895
  continue;
2819
2896
  }
2820
2897
  const out = await runGit(diffGitArgs(staged), currentWorkdir, 20_000);
2821
- console.log(theme.system(formatDiff(out, { maxLines: 400 })));
2898
+ console.log(theme.system(formatDiff(out, { maxLines: 400 }, t)));
2822
2899
  continue;
2823
2900
  }
2824
2901
  if (lower === '/cost' || lower === '/usage') {
@@ -2832,7 +2909,7 @@ async function main() {
2832
2909
  // best-effort
2833
2910
  }
2834
2911
  }
2835
- console.log(theme.system(renderCost(stats, transcript.file, browser.getLastTokenUsage())));
2912
+ console.log(theme.system(renderCost(stats, transcript.file, browser.getLastTokenUsage(), t)));
2836
2913
  continue;
2837
2914
  }
2838
2915
  if (lower === '/export' || lower.startsWith('/export ')) {
@@ -2907,7 +2984,7 @@ async function main() {
2907
2984
  sshRemote: !!(process.env.SSH_CONNECTION || process.env.SSH_TTY),
2908
2985
  headless: !!headless,
2909
2986
  headlessUaCached: await headlessUACacheReady().catch(() => false),
2910
- })));
2987
+ }, t)));
2911
2988
  continue;
2912
2989
  }
2913
2990
  if (lower === '/permissions' || lower === '/allowed-tools') {
@@ -2916,12 +2993,12 @@ async function main() {
2916
2993
  edit: config.confirmation.edit,
2917
2994
  bash: config.confirmation.bash,
2918
2995
  alwaysConfirm: config.confirmation.alwaysConfirm,
2919
- })));
2996
+ }, t)));
2920
2997
  continue;
2921
2998
  }
2922
2999
  if (lower === '/add-dir' || lower.startsWith('/add-dir ')) {
2923
3000
  const arg = trimmed.slice('/add-dir'.length).trim();
2924
- const res = resolveExtraDir(arg, currentWorkdir);
3001
+ const res = resolveExtraDir(arg, currentWorkdir, t);
2925
3002
  if ('error' in res) {
2926
3003
  console.error(theme.warn(res.error));
2927
3004
  continue;