zames_pro 1.0.3 → 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/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import path from 'path'
3
3
  import fs from 'fs/promises'
4
- import chalk from 'chalk'
5
4
  import { fileURLToPath } from 'url'
6
5
  import * as readlinePromises from 'readline/promises'
6
+ import { theme } from './theme.js'
7
7
 
8
8
  import { DeepSeekBrowser } from './browser.js'
9
9
  import { createTools } from './tools.js'
@@ -61,8 +61,10 @@ const chatIdArg = getArg('--chat', null)
61
61
  // заставляет дослать его заново — например, если промпт обновился.
62
62
  const resendPrompt = hasFlag('--resend-prompt')
63
63
  // --new-chat: намеренно начать с чистого чата, игнорируя сохранённый
64
- // last-chat (полезно в watch-режиме, где по умолчанию чат восстанавливается).
65
- const newChatFlag = hasFlag('--new-chat')
64
+ // --resume-last: при старте вернуться в последний сохранённый чат.
65
+ // По умолчанию НЕ восстанавливаем — открывается новый чат.
66
+ // (Флаг --new-chat сохранён для совместимости и ничего не меняет.)
67
+ const resumeLastFlag = hasFlag('--resume-last')
66
68
 
67
69
  // ---------- last chat persistence ----------
68
70
  // Запоминаем последний открытый chat id, чтобы после перезапуска процесса
@@ -171,9 +173,9 @@ async function autoReload() {
171
173
  const { errors } = await reloadModules()
172
174
  if (errors.length) {
173
175
  console.error(
174
- chalk.yellow('⚠ авто-reload: часть модулей не загрузилась, работаю на прежней версии:'),
176
+ theme.warn('⚠ авто-reload: часть модулей не загрузилась, работаю на прежней версии:'),
175
177
  )
176
- for (const e of errors) console.error(chalk.yellow(' ' + e))
178
+ for (const e of errors) console.error(theme.warn(' ' + e))
177
179
  }
178
180
  }
179
181
 
@@ -182,13 +184,14 @@ async function autoReload() {
182
184
 
183
185
  function printHelp() {
184
186
  console.log(`
185
- ${chalk.bold('zames')} — агент поверх chat.deepseek.com через Playwright
187
+ ${theme.bold('zames')} — агент поверх chat.deepseek.com через Playwright
186
188
 
187
- ${chalk.bold('Опции CLI:')}
189
+ ${theme.bold('Опции CLI:')}
188
190
  --dir <path> рабочая директория агента
189
191
  --task <text> задача одной строкой
190
192
  --chat <id> продолжить существующий чат по id
191
- --new-chat начать новый чат (игнорировать сохранённый)
193
+ --resume-last вернуться в последний сохранённый чат
194
+ --new-chat начать новый чат (по умолчанию и так новый)
192
195
  --resend-prompt дослать system-prompt в существующий чат
193
196
  --max-iter <n> лимит итераций (по умолчанию ${config.maxIterations})
194
197
  --headless браузер без UI
@@ -198,7 +201,7 @@ ${chalk.bold('Опции CLI:')}
198
201
  --version, -v показать версию
199
202
  --help, -h эта справка
200
203
 
201
- ${chalk.bold('Обычные команды:')}
204
+ ${theme.bold('Обычные команды:')}
202
205
  /new, /clear новый чат (сброс контекста)
203
206
  /chats список последних чатов DeepSeek
204
207
  /resume <n> открыть чат №n из /chats
@@ -215,7 +218,7 @@ ${chalk.bold('Обычные команды:')}
215
218
  /help, help справка
216
219
  /exit, /quit, exit выход
217
220
 
218
- ${chalk.bold('Самообзор (отладка агента):')}
221
+ ${theme.bold('Самообзор (отладка агента):')}
219
222
  /self-review [фокус] снять снапшот src/ и запустить ревью
220
223
  после этой команды ты остаёшься В СНАПШОТЕ
221
224
  и можешь писать «исправь ошибки» и т.п.
@@ -225,11 +228,12 @@ ${chalk.bold('Самообзор (отладка агента):')}
225
228
  /self-diff <name> различия между текущим src/ и снапшотом
226
229
  /self-apply <name> применить снапшот к src/ (с бэкапом)
227
230
 
228
- ${chalk.bold('Файлы:')}
231
+ ${theme.bold('Файлы:')}
229
232
  Логи: ${config.transcript.dir}
230
233
  Undo: ~/.zames/undo
231
234
  Профиль: ~/.zames/profile
232
235
  Снапшоты: ~/.zames/snapshots
236
+ Временные: <проект>/tmp (в .gitignore, чистится при запуске)
233
237
  Конфиг: ${CONFIG_PATHS.HOME_CONFIG}
234
238
  ${CONFIG_PATHS.PROJECT_CONFIG}
235
239
  `)
@@ -239,6 +243,19 @@ function dirLabel(p) {
239
243
  return path.basename(p) || p
240
244
  }
241
245
 
246
+ // Временные файлы агента (одноразовые скрипты и т.п.) складываем в
247
+ // <проект>/tmp — эта папка в .gitignore и очищается при каждом запуске.
248
+ const TMP_DIR = path.join(__dirname, '..', 'tmp')
249
+
250
+ async function cleanTmpDir() {
251
+ try {
252
+ await fs.rm(TMP_DIR, { recursive: true, force: true })
253
+ await fs.mkdir(TMP_DIR, { recursive: true })
254
+ } catch (e) {
255
+ if (debug) console.error('tmp: не удалось очистить:', e.message)
256
+ }
257
+ }
258
+
242
259
  async function promptOnce(question) {
243
260
  process.stdin.resume()
244
261
  if (process.stdin.isTTY && process.stdin.setRawMode) {
@@ -255,6 +272,26 @@ async function promptOnce(question) {
255
272
  }
256
273
  }
257
274
 
275
+ // Слежение за клавиатурой во время работы агента.
276
+ // Esc — прервать текущую генерацию (клик Stop в браузере).
277
+ function watchEscape(onEscape) {
278
+ const stdin = process.stdin
279
+ if (!stdin.isTTY) return () => {}
280
+ const wasRaw = stdin.isRaw
281
+ if (stdin.setRawMode) stdin.setRawMode(true)
282
+ stdin.resume()
283
+ const handler = (buf) => {
284
+ if (buf.length === 1 && buf[0] === 27) {
285
+ onEscape()
286
+ }
287
+ }
288
+ stdin.on('data', handler)
289
+ return () => {
290
+ stdin.removeListener('data', handler)
291
+ if (stdin.setRawMode) stdin.setRawMode(wasRaw || false)
292
+ }
293
+ }
294
+
258
295
  // ---------- workdir resolution ----------
259
296
 
260
297
  // ---------- workdir resolution ----------
@@ -277,9 +314,15 @@ async function runTask(browser, tools, taskText, workdir, opts) {
277
314
  const { transcript, freshChat, sendSystemPrompt } = opts
278
315
 
279
316
  const ui = mod.createSpinner()
280
- ui.taskHeader(taskText)
281
317
  ui.thinking()
282
318
 
319
+ // Esc во время работы — прервать генерацию.
320
+ const stopWatching = watchEscape(() => {
321
+ ui.stop()
322
+ console.error(theme.warn('⏹ Esc — прерываю генерацию...'))
323
+ browser.stopGeneration().catch(() => {})
324
+ })
325
+
283
326
  let finished = false
284
327
 
285
328
  try {
@@ -299,13 +342,15 @@ async function runTask(browser, tools, taskText, workdir, opts) {
299
342
  ui.assistant(msg)
300
343
  finished = true
301
344
  },
345
+ debugLog: debug,
302
346
  })
303
347
  } catch (e) {
304
348
  ui.stop()
305
- console.error(chalk.red('\n✖ Ошибка агента:'), e.message)
349
+ console.error(theme.error('\n✖ Ошибка агента:'), e.message)
306
350
  if (debug) console.error(e.stack)
307
351
  transcript?.log('agent_error', { error: e.message })
308
352
  } finally {
353
+ stopWatching()
309
354
  if (!finished) ui.stop()
310
355
  }
311
356
  }
@@ -327,9 +372,11 @@ async function main() {
327
372
  }
328
373
 
329
374
  if (calibrate) {
330
- console.log(chalk.yellow('\n🔧 Режим калибровки селекторов\n'))
375
+ console.log(theme.warn('\n🔧 Режим калибровки селекторов\n'))
331
376
  }
332
377
 
378
+ await cleanTmpDir()
379
+
333
380
  let currentWorkdir
334
381
  try {
335
382
  currentWorkdir = await resolveWorkdir()
@@ -337,13 +384,13 @@ async function main() {
337
384
  const sandboxRoot = currentWorkdir
338
385
  } catch (e) {
339
386
  console.error(
340
- chalk.red('Не удалось определить рабочую директорию:'),
387
+ theme.error('Не удалось определить рабочую директорию:'),
341
388
  e.message,
342
389
  )
343
390
  process.exit(1)
344
391
  }
345
392
 
346
- console.log(chalk.gray(`Рабочая директория: ${currentWorkdir}`))
393
+ console.log(theme.system(`Рабочая директория: ${currentWorkdir}`))
347
394
 
348
395
  const transcript = new Transcript({
349
396
  dir: config.transcript.dir,
@@ -351,7 +398,7 @@ async function main() {
351
398
  sessionName: dirLabel(currentWorkdir),
352
399
  })
353
400
  if (transcript.file) {
354
- console.log(chalk.gray(`Транскрипт: ${transcript.file}`))
401
+ console.log(theme.system(`Транскрипт: ${transcript.file}`))
355
402
  }
356
403
 
357
404
  const undo = new UndoStore(config.undo)
@@ -372,7 +419,7 @@ async function main() {
372
419
  await browser.waitForLogin()
373
420
  } catch (e) {
374
421
  bootSpinner.stop()
375
- console.error(chalk.red('Не удалось запустить браузер:'), e.message)
422
+ console.error(theme.error('Не удалось запустить браузер:'), e.message)
376
423
  if (debug) console.error(e.stack)
377
424
  await browser.close().catch(() => {})
378
425
  transcript.close()
@@ -386,21 +433,23 @@ async function main() {
386
433
  let freshChat = true
387
434
  let sendSystemPrompt = true
388
435
 
389
- // Приоритет: явный --chat, иначе сохранённый last-chat (если не --new-chat).
436
+ // По умолчанию — новый чат. Продолжить прошлый можно явно:
437
+ // --chat <id> открыть конкретный чат
438
+ // --resume-last вернуться в последний сохранённый чат
390
439
  let resumeId = chatIdArg
391
- if (!resumeId && !newChatFlag) {
440
+ if (!resumeId && resumeLastFlag) {
392
441
  resumeId = await loadLastChat()
393
- if (resumeId) console.log(chalk.gray(`Восстанавливаю чат ${resumeId}...`))
442
+ if (resumeId) console.log(theme.system(`Восстанавливаю чат ${resumeId}...`))
394
443
  }
395
444
 
396
445
  if (resumeId) {
397
446
  try {
398
- console.log(chalk.gray(`Открываю чат ${resumeId}...`))
447
+ console.log(theme.system(`Открываю чат ${resumeId}...`))
399
448
  await browser.openChat(resumeId)
400
449
  freshChat = false
401
450
  sendSystemPrompt = resendPrompt
402
451
  } catch (e) {
403
- console.error(chalk.red(`Не удалось открыть чат: ${e.message}`))
452
+ console.error(theme.error(`Не удалось открыть чат: ${e.message}`))
404
453
  }
405
454
  }
406
455
 
@@ -418,7 +467,7 @@ async function main() {
418
467
  }
419
468
 
420
469
  console.log(
421
- chalk.gray(
470
+ theme.system(
422
471
  'Интерактивный режим. Введите задачу. Команды — /help. Выход — /exit.\n',
423
472
  ),
424
473
  )
@@ -442,42 +491,45 @@ async function main() {
442
491
  // висеть отдельным процессом после выхода агента.
443
492
  await mod.closeWeb().catch(() => {})
444
493
  transcript.close()
445
- console.log(chalk.gray('\nВыход.'))
494
+ console.log(theme.system('\nВыход.'))
446
495
  process.exit(0)
447
496
  })
448
497
 
449
- // Приоритет: явный --chat, иначе сохранённый last-chat (если не --new-chat).
498
+ // По умолчанию — новый чат. Продолжить прошлый можно явно:
499
+ // --chat <id> открыть конкретный чат
500
+ // --resume-last вернуться в последний сохранённый чат
450
501
  let resumeId = chatIdArg
451
- if (!resumeId && !newChatFlag) {
502
+ if (!resumeId && resumeLastFlag) {
452
503
  resumeId = await loadLastChat()
504
+ if (resumeId) console.log(theme.system(`Восстанавливаю чат ${resumeId}...`))
453
505
  }
454
506
 
455
507
  if (resumeId) {
456
508
  try {
457
- console.log(chalk.gray(`Открываю чат ${resumeId}...`))
509
+ console.log(theme.system(`Открываю чат ${resumeId}...`))
458
510
  await browser.openChat(resumeId)
459
511
  currentChatId = resumeId
460
512
  freshChatNext = false
461
513
  sendSystemPromptNext = resendPrompt
462
514
  await saveLastChat(resumeId)
463
- console.log(chalk.gray(`Чат открыт: ${resumeId}\n`))
515
+ console.log(theme.system(`Чат открыт: ${resumeId}\n`))
464
516
  } catch (e) {
465
- console.error(chalk.red(`Не удалось открыть чат: ${e.message}`))
517
+ console.error(theme.error(`Не удалось открыть чат: ${e.message}`))
466
518
  }
467
519
  }
468
520
 
469
521
  while (running) {
470
522
  let input
471
523
  try {
472
- let label
524
+ // Компактное приглашение: золотая стрелка + светло-голубая директория.
525
+ let tail
473
526
  if (reviewMode) {
474
- label = `zames[REVIEW:${reviewMode.snapName}]> `
475
- } else if (currentChatId) {
476
- label = `zames[${dirLabel(currentWorkdir)}|${currentChatId.slice(0, 6)}]> `
527
+ tail =
528
+ theme.warn('REVIEW') + theme.dim(':') + theme.dir(reviewMode.snapName)
477
529
  } else {
478
- label = `zames[${dirLabel(currentWorkdir)}]> `
530
+ tail = theme.dir(dirLabel(currentWorkdir))
479
531
  }
480
- input = await promptOnce(chalk.cyan(label))
532
+ input = await promptOnce(theme.prompt('❯ ') + tail + theme.dim(' › '))
481
533
  } catch {
482
534
  break
483
535
  }
@@ -495,7 +547,7 @@ async function main() {
495
547
  }
496
548
 
497
549
  if (['/new', '/clear', 'new'].includes(lower)) {
498
- console.log(chalk.gray('Создаю новый чат...'))
550
+ console.log(theme.system('Создаю новый чат...'))
499
551
  try {
500
552
  await browser.newChat()
501
553
  freshChatNext = false
@@ -503,9 +555,9 @@ async function main() {
503
555
  currentChatId = await browser.getCurrentChatId()
504
556
  await saveLastChat(currentChatId)
505
557
  transcript.log('new_chat')
506
- console.log(chalk.gray('Новый чат.\n'))
558
+ console.log(theme.system('Новый чат.\n'))
507
559
  } catch (e) {
508
- console.error(chalk.red('Не удалось создать новый чат:'), e.message)
560
+ console.error(theme.error('Не удалось создать новый чат:'), e.message)
509
561
  }
510
562
  continue
511
563
  }
@@ -544,7 +596,7 @@ async function main() {
544
596
  await saveLastChat(currentChatId)
545
597
 
546
598
  console.log(
547
- chalk.cyan(
599
+ theme.user(
548
600
  '\n💡 Теперь ты в режиме ревью. Просто пиши агенту, например:\n' +
549
601
  ' «исправь ошибки»\n' +
550
602
  ' «доработай обработку ошибок в ask()»\n' +
@@ -555,7 +607,7 @@ async function main() {
555
607
  ),
556
608
  )
557
609
  } catch (e) {
558
- console.error(chalk.red('Самообзор провалился:'), e.message)
610
+ console.error(theme.error('Самообзор провалился:'), e.message)
559
611
  if (debug) console.error(e.stack)
560
612
  }
561
613
  continue
@@ -564,7 +616,7 @@ async function main() {
564
616
  if (lower === '/self-fix' || lower.startsWith('/self-fix ')) {
565
617
  const rest = trimmed.slice('/self-fix'.length).trim()
566
618
  if (!rest) {
567
- console.error(chalk.red('Использование: /self-fix <name> [фокус]'))
619
+ console.error(theme.error('Использование: /self-fix <name> [фокус]'))
568
620
  continue
569
621
  }
570
622
  const sp = rest.indexOf(' ')
@@ -574,7 +626,7 @@ async function main() {
574
626
  const snapRoot = path.join(ZAMES_HOME, 'snapshots', name)
575
627
  const stat = await fs.stat(snapRoot).catch(() => null)
576
628
  if (!stat || !stat.isDirectory()) {
577
- console.error(chalk.red(`Снапшот не найден: ${snapRoot}`))
629
+ console.error(theme.error(`Снапшот не найден: ${snapRoot}`))
578
630
  continue
579
631
  }
580
632
 
@@ -593,7 +645,7 @@ async function main() {
593
645
  workdir: snapRoot,
594
646
  tools,
595
647
  })
596
- console.log(chalk.gray('Инициализирую review-чат для снапшота...'))
648
+ console.log(theme.system('Инициализирую review-чат для снапшота...'))
597
649
  await browser.ask(sysPrompt, { timeout: 60_000 })
598
650
 
599
651
  if (focus) {
@@ -611,7 +663,7 @@ async function main() {
611
663
  onThinking: () => ui.thinking(),
612
664
  onAssistantThought: (text) => {
613
665
  ui.stop()
614
- console.log(chalk.gray('\n💭 ' + text.slice(0, 1200) + '\n'))
666
+ console.log(theme.system('\n💭 ' + text.slice(0, 1200) + '\n'))
615
667
  },
616
668
  onToolCall: (name, toolArgs) => ui.toolCall(name, toolArgs),
617
669
  onToolResult: (r) => ui.toolResult(r),
@@ -633,19 +685,19 @@ async function main() {
633
685
  await saveLastChat(currentChatId)
634
686
 
635
687
  console.log(
636
- chalk.cyan(
688
+ theme.user(
637
689
  `\n💡 Режим ревью по снапшоту ${name}. Пиши агенту задачу или /self-done.\n`,
638
690
  ),
639
691
  )
640
692
  } catch (e) {
641
- console.error(chalk.red('Не удалось войти в снапшот:'), e.message)
693
+ console.error(theme.error('Не удалось войти в снапшот:'), e.message)
642
694
  }
643
695
  continue
644
696
  }
645
697
 
646
698
  if (lower === '/self-done') {
647
699
  if (!reviewMode) {
648
- console.log(chalk.gray('Ты и так не в режиме ревью.'))
700
+ console.log(theme.system('Ты и так не в режиме ревью.'))
649
701
  continue
650
702
  }
651
703
  const back = reviewMode.originalWorkdir
@@ -655,7 +707,7 @@ async function main() {
655
707
  freshChatNext = true
656
708
  sendSystemPromptNext = true
657
709
  console.log(
658
- chalk.gray(`Вернулся в ${back}. Следующая задача начнёт новый чат.\n`),
710
+ theme.system(`Вернулся в ${back}. Следующая задача начнёт новый чат.\n`),
659
711
  )
660
712
  continue
661
713
  }
@@ -664,7 +716,7 @@ async function main() {
664
716
  try {
665
717
  await mod.selfList({ config })
666
718
  } catch (e) {
667
- console.error(chalk.red('Ошибка:'), e.message)
719
+ console.error(theme.error('Ошибка:'), e.message)
668
720
  }
669
721
  continue
670
722
  }
@@ -672,13 +724,13 @@ async function main() {
672
724
  if (lower === '/self-diff' || lower.startsWith('/self-diff ')) {
673
725
  const name = trimmed.slice('/self-diff'.length).trim()
674
726
  if (!name) {
675
- console.error(chalk.red('Использование: /self-diff <name>'))
727
+ console.error(theme.error('Использование: /self-diff <name>'))
676
728
  continue
677
729
  }
678
730
  try {
679
731
  await mod.selfDiff({ config, name })
680
732
  } catch (e) {
681
- console.error(chalk.red('Ошибка:'), e.message)
733
+ console.error(theme.error('Ошибка:'), e.message)
682
734
  }
683
735
  continue
684
736
  }
@@ -686,13 +738,13 @@ async function main() {
686
738
  if (lower === '/self-apply' || lower.startsWith('/self-apply ')) {
687
739
  const name = trimmed.slice('/self-apply'.length).trim()
688
740
  if (!name) {
689
- console.error(chalk.red('Использование: /self-apply <name>'))
741
+ console.error(theme.error('Использование: /self-apply <name>'))
690
742
  continue
691
743
  }
692
744
  try {
693
745
  await mod.selfApply({ config, name })
694
746
  } catch (e) {
695
- console.error(chalk.red('Ошибка:'), e.message)
747
+ console.error(theme.error('Ошибка:'), e.message)
696
748
  }
697
749
  continue
698
750
  }
@@ -707,23 +759,23 @@ async function main() {
707
759
  spin.stop()
708
760
  if (!lastChats.length) {
709
761
  console.log(
710
- chalk.gray(
762
+ theme.system(
711
763
  'Чатов не найдено. Возможно, сайдбар свёрнут или селекторы устарели.',
712
764
  ),
713
765
  )
714
766
  } else {
715
- console.log(chalk.gray('Последние чаты DeepSeek:'))
767
+ console.log(theme.system('Последние чаты DeepSeek:'))
716
768
  lastChats.forEach((c, i) => {
717
769
  const n = String(i + 1).padStart(2, ' ')
718
770
  console.log(
719
- ` ${chalk.cyan(n)}. ${c.title} ${chalk.gray('(' + c.id.slice(0, 8) + '…)')}`,
771
+ ` ${theme.user(n)}. ${c.title} ${theme.system('(' + c.id.slice(0, 8) + '…)')}`,
720
772
  )
721
773
  })
722
- console.log(chalk.gray('\nИспользуй /resume <n> для продолжения.\n'))
774
+ console.log(theme.system('\nИспользуй /resume <n> для продолжения.\n'))
723
775
  }
724
776
  } catch (e) {
725
777
  spin.stop()
726
- console.error(chalk.red('Не удалось получить список:'), e.message)
778
+ console.error(theme.error('Не удалось получить список:'), e.message)
727
779
  }
728
780
  continue
729
781
  }
@@ -732,26 +784,26 @@ async function main() {
732
784
  const arg = trimmed.slice(7).trim()
733
785
  if (!arg) {
734
786
  console.error(
735
- chalk.red('Использование: /resume <n> (или /chats для списка)'),
787
+ theme.error('Использование: /resume <n> (или /chats для списка)'),
736
788
  )
737
789
  continue
738
790
  }
739
791
  const n = Number(arg)
740
792
  if (!Number.isFinite(n) || n < 1) {
741
- console.error(chalk.red('Нужен номер из /chats.'))
793
+ console.error(theme.error('Нужен номер из /chats.'))
742
794
  continue
743
795
  }
744
796
  if (!lastChats.length) {
745
- console.error(chalk.red('Сначала выполни /chats.'))
797
+ console.error(theme.error('Сначала выполни /chats.'))
746
798
  continue
747
799
  }
748
800
  const pick = lastChats[n - 1]
749
801
  if (!pick) {
750
- console.error(chalk.red(`Нет чата №${n}. Всего: ${lastChats.length}.`))
802
+ console.error(theme.error(`Нет чата №${n}. Всего: ${lastChats.length}.`))
751
803
  continue
752
804
  }
753
805
 
754
- console.log(chalk.gray(`Открываю: ${pick.title}`))
806
+ console.log(theme.system(`Открываю: ${pick.title}`))
755
807
  try {
756
808
  await browser.openChat(pick.id)
757
809
  currentChatId = pick.id
@@ -760,94 +812,94 @@ async function main() {
760
812
  await saveLastChat(pick.id)
761
813
  transcript.log('resume_chat', { id: pick.id, title: pick.title })
762
814
  console.log(
763
- chalk.green(`Чат открыт.`) +
764
- chalk.gray(
815
+ theme.assistant(`Чат открыт.`) +
816
+ theme.system(
765
817
  resendPrompt ? ' Системный промпт будет переслан на следующей задаче.\n' : ' Контекст чата сохранён. Системный промпт не пересылается (--resend-prompt чтобы дослать).\n',
766
818
  ),
767
819
  )
768
820
  } catch (e) {
769
- console.error(chalk.red('Не удалось открыть чат:'), e.message)
821
+ console.error(theme.error('Не удалось открыть чат:'), e.message)
770
822
  }
771
823
  continue
772
824
  }
773
825
 
774
826
  if (lower === '/chat') {
775
827
  if (currentChatId) {
776
- console.log(chalk.gray(`Текущий chat id: ${currentChatId}`))
828
+ console.log(theme.system(`Текущий chat id: ${currentChatId}`))
777
829
  console.log(
778
- chalk.gray(
830
+ theme.system(
779
831
  `URL: https://chat.deepseek.com/a/chat/s/${currentChatId}`,
780
832
  ),
781
833
  )
782
834
  } else {
783
835
  const id = await browser.getCurrentChatId()
784
836
  console.log(
785
- chalk.gray(id ? `Текущий chat id: ${id}` : 'Чат ещё не создан.'),
837
+ theme.system(id ? `Текущий chat id: ${id}` : 'Чат ещё не создан.'),
786
838
  )
787
839
  }
788
840
  continue
789
841
  }
790
842
 
791
843
  if (lower === '/pwd') {
792
- console.log(chalk.gray(currentWorkdir))
844
+ console.log(theme.system(currentWorkdir))
793
845
  continue
794
846
  }
795
847
 
796
848
  if (lower === '/reload') {
797
- console.log(chalk.gray('Перечитываю модули логики...'))
849
+ console.log(theme.system('Перечитываю модули логики...'))
798
850
  try {
799
851
  const { count, errors } = await reloadModules()
800
852
  if (errors.length) {
801
- console.error(chalk.red('Часть модулей не перезагрузилась:'))
802
- for (const e of errors) console.error(chalk.red(' ' + e))
853
+ console.error(theme.error('Часть модулей не перезагрузилась:'))
854
+ for (const e of errors) console.error(theme.error(' ' + e))
803
855
  } else {
804
856
  console.log(
805
- chalk.green(
857
+ theme.assistant(
806
858
  `Перезагружено модулей: ${count}. Браузер и чат не тронуты.`,
807
859
  ),
808
860
  )
809
861
  }
810
862
  } catch (e) {
811
- console.error(chalk.red('Ошибка reload:'), e.message)
863
+ console.error(theme.error('Ошибка reload:'), e.message)
812
864
  }
813
865
  continue
814
866
  }
815
867
 
816
868
  if (lower === '/status') {
817
- console.log(chalk.gray(`Рабочая директория: ${currentWorkdir}`))
869
+ console.log(theme.system(`Рабочая директория: ${currentWorkdir}`))
818
870
  console.log(
819
- chalk.gray(`Режим ревью: ${reviewMode ? reviewMode.snapName : 'нет'}`),
871
+ theme.system(`Режим ревью: ${reviewMode ? reviewMode.snapName : 'нет'}`),
820
872
  )
821
873
  if (reviewMode) {
822
874
  console.log(
823
- chalk.gray(`Исходная директория: ${reviewMode.originalWorkdir}`),
875
+ theme.system(`Исходная директория: ${reviewMode.originalWorkdir}`),
824
876
  )
825
877
  }
826
- console.log(chalk.gray(`Текущий чат: ${currentChatId || '(нет)'}`))
878
+ console.log(theme.system(`Текущий чат: ${currentChatId || '(нет)'}`))
827
879
  console.log(
828
- chalk.gray(
880
+ theme.system(
829
881
  `Fresh chat на след. задаче: ${freshChatNext ? 'да' : 'нет'}`,
830
882
  ),
831
883
  )
832
884
  console.log(
833
- chalk.gray(
885
+ theme.system(
834
886
  `System prompt на след. задаче: ${sendSystemPromptNext ? 'да' : 'нет'}`,
835
887
  ),
836
888
  )
837
889
  console.log(
838
- chalk.gray(`Resend prompt (--resend-prompt): ${resendPrompt ? 'да' : 'нет'}`),
890
+ theme.system(`Resend prompt (--resend-prompt): ${resendPrompt ? 'да' : 'нет'}`),
839
891
  )
840
892
  console.log(
841
- chalk.gray(`Last chat: ${(await loadLastChat()) || 'нет'}`),
893
+ theme.system(`Last chat: ${(await loadLastChat()) || 'нет'}`),
842
894
  )
843
895
  console.log(
844
- chalk.gray(`Dev mode (auto-reload): ${devMode ? 'вкл' : 'выкл'}`),
896
+ theme.system(`Dev mode (auto-reload): ${devMode ? 'вкл' : 'выкл'}`),
845
897
  )
846
- console.log(chalk.gray(`Лимит итераций: ${maxIter}`))
847
- console.log(chalk.gray(`Headless: ${headless ? 'да' : 'нет'}`))
848
- console.log(chalk.gray(`Debug: ${debug ? 'да' : 'нет'}`))
849
- console.log(chalk.gray(`Undo: ${config.undo.enabled ? 'вкл' : 'выкл'}`))
850
- console.log(chalk.gray(`Транскрипт: ${transcript.file || 'выкл'}`))
898
+ console.log(theme.system(`Лимит итераций: ${maxIter}`))
899
+ console.log(theme.system(`Headless: ${headless ? 'да' : 'нет'}`))
900
+ console.log(theme.system(`Debug: ${debug ? 'да' : 'нет'}`))
901
+ console.log(theme.system(`Undo: ${config.undo.enabled ? 'вкл' : 'выкл'}`))
902
+ console.log(theme.system(`Транскрипт: ${transcript.file || 'выкл'}`))
851
903
  continue
852
904
  }
853
905
 
@@ -857,7 +909,7 @@ async function main() {
857
909
  }
858
910
 
859
911
  if (lower === '/transcript') {
860
- console.log(chalk.gray(transcript.file || '(выключен)'))
912
+ console.log(theme.system(transcript.file || '(выключен)'))
861
913
  continue
862
914
  }
863
915
 
@@ -865,14 +917,14 @@ async function main() {
865
917
  const result = await undo.undoLast()
866
918
  if (result.ok) {
867
919
  console.log(
868
- chalk.green(`↶ Откатили: ${result.record.originalPath}`) +
869
- chalk.gray(
920
+ theme.assistant(`↶ Откатили: ${result.record.originalPath}`) +
921
+ theme.system(
870
922
  result.record.existed ? ' (восстановлено)' : ' (удалено)',
871
923
  ),
872
924
  )
873
925
  transcript.log('undo', { path: result.record.originalPath })
874
926
  } else {
875
- console.error(chalk.red(`Не удалось откатить: ${result.reason}`))
927
+ console.error(theme.error(`Не удалось откатить: ${result.reason}`))
876
928
  }
877
929
  continue
878
930
  }
@@ -880,12 +932,12 @@ async function main() {
880
932
  if (lower === '/undo-list' || lower === '/history') {
881
933
  const list = await undo.list(10)
882
934
  if (!list.length) {
883
- console.log(chalk.gray('История пуста.'))
935
+ console.log(theme.system('История пуста.'))
884
936
  } else {
885
937
  for (const r of list) {
886
938
  const stamp = new Date(r.stamp).toLocaleString()
887
939
  const flag = r.existed ? 'изменён' : 'создан'
888
- console.log(chalk.gray(`${stamp} [${flag}] ${r.originalPath}`))
940
+ console.log(theme.system(`${stamp} [${flag}] ${r.originalPath}`))
889
941
  }
890
942
  }
891
943
  continue
@@ -896,11 +948,11 @@ async function main() {
896
948
  const file = path.join(config.transcript.dir, `dom-${stamp}.html`)
897
949
  try {
898
950
  const result = await browser.dumpDom(file)
899
- console.log(chalk.green(`HTML сохранён: ${result.file}`))
900
- console.log(chalk.gray('Селекторы:'))
951
+ console.log(theme.assistant(`HTML сохранён: ${result.file}`))
952
+ console.log(theme.system('Селекторы:'))
901
953
  console.log(JSON.stringify(result.selectors, null, 2))
902
954
  } catch (e) {
903
- console.error(chalk.red('Не удалось сохранить DOM:'), e.message)
955
+ console.error(theme.error('Не удалось сохранить DOM:'), e.message)
904
956
  }
905
957
  continue
906
958
  }
@@ -913,34 +965,34 @@ async function main() {
913
965
  : sandboxRoot
914
966
  const rel = path.relative(sandboxRoot, newDir)
915
967
  if (rel.startsWith('..') || path.isAbsolute(rel)) {
916
- console.error(chalk.red('Нельзя выйти за пределы: ' + sandboxRoot))
968
+ console.error(theme.error('Нельзя выйти за пределы: ' + sandboxRoot))
917
969
  continue
918
970
  }
919
971
  const stat = await fs.stat(newDir).catch(() => null)
920
972
  if (!stat || !stat.isDirectory()) {
921
- console.error(chalk.red('Не директория: ' + newDir))
973
+ console.error(theme.error('Не директория: ' + newDir))
922
974
  continue
923
975
  }
924
976
  if (newDir === currentWorkdir) {
925
- console.log(chalk.gray('Уже здесь.'))
977
+ console.log(theme.system('Уже здесь.'))
926
978
  continue
927
979
  }
928
980
  if (reviewMode) {
929
- console.log(chalk.gray('Вышел из режима ревью (/cd).'))
981
+ console.log(theme.system('Вышел из режима ревью (/cd).'))
930
982
  reviewMode = null
931
983
  }
932
984
  currentWorkdir = newDir
933
985
  freshChatNext = true
934
986
  sendSystemPromptNext = true
935
- console.log(chalk.gray('Рабочая директория: ' + newDir))
987
+ console.log(theme.system('Рабочая директория: ' + newDir))
936
988
  } catch (e) {
937
- console.error(chalk.red('Не удалось перейти: ' + e.message))
989
+ console.error(theme.error('Не удалось перейти: ' + e.message))
938
990
  }
939
991
  continue
940
992
  }
941
993
 
942
994
  if (lower.startsWith('/')) {
943
- console.error(chalk.red(`Неизвестная команда: ${trimmed}. Набери /help.`))
995
+ console.error(theme.error(`Неизвестная команда: ${trimmed}. Набери /help.`))
944
996
  continue
945
997
  }
946
998
 
@@ -972,7 +1024,7 @@ async function main() {
972
1024
  }
973
1025
 
974
1026
  main().catch((e) => {
975
- console.error(chalk.red('Критическая ошибка:'), e.message)
1027
+ console.error(theme.error('Критическая ошибка:'), e.message)
976
1028
  if (debug) console.error(e.stack)
977
1029
  process.exit(1)
978
1030
  })