zames_pro 1.0.3 → 1.1.1
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/README.md +23 -38
- package/logo.png +0 -0
- package/package.json +14 -3
- package/src/agent-loop.js +5 -1
- package/src/browser.js +150 -13
- package/src/confirm.js +10 -10
- package/src/diff.js +21 -113
- package/src/index.js +361 -118
- package/src/markdown.js +49 -0
- package/src/self-review.js +30 -30
- package/src/spinner.js +53 -22
- package/src/theme.js +34 -0
package/src/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
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
|
-
import
|
|
5
|
+
import { theme } from './theme.js'
|
|
7
6
|
|
|
8
7
|
import { DeepSeekBrowser } from './browser.js'
|
|
9
8
|
import { createTools } from './tools.js'
|
|
@@ -61,8 +60,10 @@ const chatIdArg = getArg('--chat', null)
|
|
|
61
60
|
// заставляет дослать его заново — например, если промпт обновился.
|
|
62
61
|
const resendPrompt = hasFlag('--resend-prompt')
|
|
63
62
|
// --new-chat: намеренно начать с чистого чата, игнорируя сохранённый
|
|
64
|
-
// last
|
|
65
|
-
|
|
63
|
+
// --resume-last: при старте вернуться в последний сохранённый чат.
|
|
64
|
+
// По умолчанию НЕ восстанавливаем — открывается новый чат.
|
|
65
|
+
// (Флаг --new-chat сохранён для совместимости и ничего не меняет.)
|
|
66
|
+
const resumeLastFlag = hasFlag('--resume-last')
|
|
66
67
|
|
|
67
68
|
// ---------- last chat persistence ----------
|
|
68
69
|
// Запоминаем последний открытый chat id, чтобы после перезапуска процесса
|
|
@@ -171,9 +172,9 @@ async function autoReload() {
|
|
|
171
172
|
const { errors } = await reloadModules()
|
|
172
173
|
if (errors.length) {
|
|
173
174
|
console.error(
|
|
174
|
-
|
|
175
|
+
theme.warn('⚠ авто-reload: часть модулей не загрузилась, работаю на прежней версии:'),
|
|
175
176
|
)
|
|
176
|
-
for (const e of errors) console.error(
|
|
177
|
+
for (const e of errors) console.error(theme.warn(' ' + e))
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
180
|
|
|
@@ -182,13 +183,14 @@ async function autoReload() {
|
|
|
182
183
|
|
|
183
184
|
function printHelp() {
|
|
184
185
|
console.log(`
|
|
185
|
-
${
|
|
186
|
+
${theme.bold('zames')} — агент поверх chat.deepseek.com через Playwright
|
|
186
187
|
|
|
187
|
-
${
|
|
188
|
+
${theme.bold('Опции CLI:')}
|
|
188
189
|
--dir <path> рабочая директория агента
|
|
189
190
|
--task <text> задача одной строкой
|
|
190
191
|
--chat <id> продолжить существующий чат по id
|
|
191
|
-
--
|
|
192
|
+
--resume-last вернуться в последний сохранённый чат
|
|
193
|
+
--new-chat начать новый чат (по умолчанию и так новый)
|
|
192
194
|
--resend-prompt дослать system-prompt в существующий чат
|
|
193
195
|
--max-iter <n> лимит итераций (по умолчанию ${config.maxIterations})
|
|
194
196
|
--headless браузер без UI
|
|
@@ -198,7 +200,7 @@ ${chalk.bold('Опции CLI:')}
|
|
|
198
200
|
--version, -v показать версию
|
|
199
201
|
--help, -h эта справка
|
|
200
202
|
|
|
201
|
-
${
|
|
203
|
+
${theme.bold('Обычные команды:')}
|
|
202
204
|
/new, /clear новый чат (сброс контекста)
|
|
203
205
|
/chats список последних чатов DeepSeek
|
|
204
206
|
/resume <n> открыть чат №n из /chats
|
|
@@ -215,7 +217,7 @@ ${chalk.bold('Обычные команды:')}
|
|
|
215
217
|
/help, help справка
|
|
216
218
|
/exit, /quit, exit выход
|
|
217
219
|
|
|
218
|
-
${
|
|
220
|
+
${theme.bold('Самообзор (отладка агента):')}
|
|
219
221
|
/self-review [фокус] снять снапшот src/ и запустить ревью
|
|
220
222
|
после этой команды ты остаёшься В СНАПШОТЕ
|
|
221
223
|
и можешь писать «исправь ошибки» и т.п.
|
|
@@ -225,11 +227,12 @@ ${chalk.bold('Самообзор (отладка агента):')}
|
|
|
225
227
|
/self-diff <name> различия между текущим src/ и снапшотом
|
|
226
228
|
/self-apply <name> применить снапшот к src/ (с бэкапом)
|
|
227
229
|
|
|
228
|
-
${
|
|
230
|
+
${theme.bold('Файлы:')}
|
|
229
231
|
Логи: ${config.transcript.dir}
|
|
230
232
|
Undo: ~/.zames/undo
|
|
231
233
|
Профиль: ~/.zames/profile
|
|
232
234
|
Снапшоты: ~/.zames/snapshots
|
|
235
|
+
Временные: <проект>/tmp (в .gitignore, чистится при запуске)
|
|
233
236
|
Конфиг: ${CONFIG_PATHS.HOME_CONFIG}
|
|
234
237
|
${CONFIG_PATHS.PROJECT_CONFIG}
|
|
235
238
|
`)
|
|
@@ -239,19 +242,244 @@ function dirLabel(p) {
|
|
|
239
242
|
return path.basename(p) || p
|
|
240
243
|
}
|
|
241
244
|
|
|
245
|
+
// Временные файлы агента (одноразовые скрипты и т.п.) складываем в
|
|
246
|
+
// <проект>/tmp — эта папка в .gitignore и очищается при каждом запуске.
|
|
247
|
+
const TMP_DIR = path.join(__dirname, '..', 'tmp')
|
|
248
|
+
|
|
249
|
+
async function cleanTmpDir() {
|
|
250
|
+
try {
|
|
251
|
+
await fs.rm(TMP_DIR, { recursive: true, force: true })
|
|
252
|
+
await fs.mkdir(TMP_DIR, { recursive: true })
|
|
253
|
+
} catch (e) {
|
|
254
|
+
if (debug) console.error('tmp: не удалось очистить:', e.message)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Ввод строки в терминале с корректной обработкой вставки (Shift+Insert,
|
|
259
|
+
// Ctrl+Shift+V, правая кнопка мыши и т.п.).
|
|
260
|
+
//
|
|
261
|
+
// Зачем свой ридер вместо readline:
|
|
262
|
+
// 1) readline отправляет строку на ПЕРВОМ переводе строки. При вставке
|
|
263
|
+
// многострочного текста это приводило к немедленной отправке и к тому,
|
|
264
|
+
// что в чат уходила только первая строка. Здесь переводы строк внутри
|
|
265
|
+
// вставки заменяются на пробелы, а отправка происходит только по
|
|
266
|
+
// одиночному нажатию Enter.
|
|
267
|
+
// 2) Включаем bracketed paste mode (\x1b[?2004h): терминал оборачивает
|
|
268
|
+
// вставленный текст в маркеры \x1b[200~ … \x1b[201~, поэтому мы точно
|
|
269
|
+
// знаем, что это вставка, а не набор с клавиатуры, и Enter внутри неё
|
|
270
|
+
// не считается отправкой.
|
|
242
271
|
async function promptOnce(question) {
|
|
243
|
-
process.stdin
|
|
244
|
-
|
|
245
|
-
|
|
272
|
+
const stdin = process.stdin
|
|
273
|
+
const stdout = process.stdout
|
|
274
|
+
|
|
275
|
+
// Не-TTY (пайп, редирект): читаем всё до EOF одной строкой.
|
|
276
|
+
if (!stdin.isTTY || !stdin.setRawMode) {
|
|
277
|
+
const chunks = []
|
|
278
|
+
return await new Promise((resolve) => {
|
|
279
|
+
const onData = (b) => chunks.push(b)
|
|
280
|
+
const onEnd = () => {
|
|
281
|
+
stdin.removeListener('data', onData)
|
|
282
|
+
stdin.removeListener('end', onEnd)
|
|
283
|
+
resolve(Buffer.concat(chunks).toString('utf-8'))
|
|
284
|
+
}
|
|
285
|
+
stdin.on('data', onData)
|
|
286
|
+
stdin.on('end', onEnd)
|
|
287
|
+
stdin.resume()
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const wasRaw = stdin.isRaw
|
|
292
|
+
stdin.setRawMode(true)
|
|
293
|
+
stdin.resume()
|
|
294
|
+
|
|
295
|
+
// Bracketed paste включаем/выключаем парно.
|
|
296
|
+
stdout.write('\x1b[?2004h')
|
|
297
|
+
stdout.write(question)
|
|
298
|
+
|
|
299
|
+
let line = ''
|
|
300
|
+
let cursor = 0
|
|
301
|
+
let inPaste = false
|
|
302
|
+
const PASTE_START = '\x1b[200~'
|
|
303
|
+
const PASTE_END = '\x1b[201~'
|
|
304
|
+
|
|
305
|
+
const redraw = () => {
|
|
306
|
+
// Возвращаемся в начало строки, стираем и печатаем заново.
|
|
307
|
+
stdout.write(String.fromCharCode(13))
|
|
308
|
+
stdout.write('\x1b[K')
|
|
309
|
+
stdout.write(question + line)
|
|
310
|
+
// Ставим курсор в нужную позицию.
|
|
311
|
+
const back = line.length - cursor
|
|
312
|
+
if (back > 0) stdout.write('\x1b[' + back + 'D')
|
|
246
313
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
314
|
+
|
|
315
|
+
return await new Promise((resolve) => {
|
|
316
|
+
const finish = (value, submit) => {
|
|
317
|
+
stdin.removeListener('data', onData)
|
|
318
|
+
stdout.write('\x1b[?2004l')
|
|
319
|
+
if (stdin.setRawMode) stdin.setRawMode(wasRaw || false)
|
|
320
|
+
if (submit) stdout.write(String.fromCharCode(10))
|
|
321
|
+
resolve(value)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const insertText = (text) => {
|
|
325
|
+
// Нормализуем переводы строк: они приходят от многострочной вставки,
|
|
326
|
+
// но означают «отправить». Внутри сообщения заменяем на пробел, чтобы
|
|
327
|
+
// вся вставка ушла ОДНИМ сообщением.
|
|
328
|
+
const clean = text
|
|
329
|
+
.replace(/\r\n/g, ' ')
|
|
330
|
+
.replace(/\r/g, ' ')
|
|
331
|
+
.replace(/\n/g, ' ')
|
|
332
|
+
line = line.slice(0, cursor) + clean + line.slice(cursor)
|
|
333
|
+
cursor += clean.length
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const onData = (buf) => {
|
|
337
|
+
let s = buf.toString('utf-8')
|
|
338
|
+
|
|
339
|
+
// Fallback для терминалов без bracketed paste: если весь чанк — это
|
|
340
|
+
// «голый» перевод строки (один байт), значит нажали Enter → отправляем.
|
|
341
|
+
// Если переводы строк пришли ВМЕСТЕ с другим текстом в одном чанке —
|
|
342
|
+
// это вставка; такие переводы строк не отправляют сообщение, а
|
|
343
|
+
// заменяются на пробелы (см. insertText).
|
|
344
|
+
if (!inPaste && (s === '\r' || s === '\n')) {
|
|
345
|
+
return finish(line, true)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
while (s.length) {
|
|
349
|
+
if (inPaste) {
|
|
350
|
+
const end = s.indexOf(PASTE_END)
|
|
351
|
+
if (end === -1) {
|
|
352
|
+
insertText(s)
|
|
353
|
+
s = ''
|
|
354
|
+
} else {
|
|
355
|
+
insertText(s.slice(0, end))
|
|
356
|
+
s = s.slice(end + PASTE_END.length)
|
|
357
|
+
inPaste = false
|
|
358
|
+
}
|
|
359
|
+
redraw()
|
|
360
|
+
continue
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const start = s.indexOf(PASTE_START)
|
|
364
|
+
if (start !== -1) {
|
|
365
|
+
// Всё до маркера обрабатываем как обычный ввод.
|
|
366
|
+
const before = s.slice(0, start)
|
|
367
|
+
s = s.slice(start + PASTE_START.length)
|
|
368
|
+
inPaste = true
|
|
369
|
+
if (before) {
|
|
370
|
+
for (const ch of before) {
|
|
371
|
+
if (ch === '\r' || ch === '\n') { /* внутри вставки — пропускаем */ }
|
|
372
|
+
else insertText(ch)
|
|
373
|
+
}
|
|
374
|
+
redraw()
|
|
375
|
+
}
|
|
376
|
+
continue
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const ch = s[0]
|
|
380
|
+
const code = s.charCodeAt(0)
|
|
381
|
+
s = s.slice(1)
|
|
382
|
+
|
|
383
|
+
if (ch === '\r' || ch === '\n') {
|
|
384
|
+
// Перевод строки внутри чанка с другим текстом (вставка без
|
|
385
|
+
// bracketed paste): не отправляем, а вставляем пробел.
|
|
386
|
+
insertText(' ')
|
|
387
|
+
redraw()
|
|
388
|
+
continue
|
|
389
|
+
}
|
|
390
|
+
if (code === 3) {
|
|
391
|
+
// Ctrl+C — прерываем ввод.
|
|
392
|
+
return finish('', true)
|
|
393
|
+
}
|
|
394
|
+
if (code === 4) {
|
|
395
|
+
// Ctrl+D — как отправка пустой строки.
|
|
396
|
+
return finish(line, true)
|
|
397
|
+
}
|
|
398
|
+
if (code === 21) {
|
|
399
|
+
// Ctrl+U — стереть строку.
|
|
400
|
+
line = ''
|
|
401
|
+
cursor = 0
|
|
402
|
+
redraw()
|
|
403
|
+
continue
|
|
404
|
+
}
|
|
405
|
+
if (code === 127 || code === 8) {
|
|
406
|
+
// Backspace.
|
|
407
|
+
if (cursor > 0) {
|
|
408
|
+
line = line.slice(0, cursor - 1) + line.slice(cursor)
|
|
409
|
+
cursor--
|
|
410
|
+
redraw()
|
|
411
|
+
}
|
|
412
|
+
continue
|
|
413
|
+
}
|
|
414
|
+
if (ch === '\x1b') {
|
|
415
|
+
// Escape-последовательности (стрелки, Home/End, Delete…).
|
|
416
|
+
const rest = s
|
|
417
|
+
if (rest.startsWith('[D')) {
|
|
418
|
+
if (cursor > 0) cursor--
|
|
419
|
+
s = s.slice(2)
|
|
420
|
+
redraw()
|
|
421
|
+
continue
|
|
422
|
+
}
|
|
423
|
+
if (rest.startsWith('[C')) {
|
|
424
|
+
if (cursor < line.length) cursor++
|
|
425
|
+
s = s.slice(2)
|
|
426
|
+
redraw()
|
|
427
|
+
continue
|
|
428
|
+
}
|
|
429
|
+
if (rest.startsWith('[H') || rest.startsWith('[1~')) {
|
|
430
|
+
cursor = 0
|
|
431
|
+
s = s.slice(rest.startsWith('[1~') ? 3 : 2)
|
|
432
|
+
redraw()
|
|
433
|
+
continue
|
|
434
|
+
}
|
|
435
|
+
if (rest.startsWith('[F') || rest.startsWith('[4~')) {
|
|
436
|
+
cursor = line.length
|
|
437
|
+
s = s.slice(rest.startsWith('[4~') ? 3 : 2)
|
|
438
|
+
redraw()
|
|
439
|
+
continue
|
|
440
|
+
}
|
|
441
|
+
if (rest.startsWith('[3~')) {
|
|
442
|
+
// Delete.
|
|
443
|
+
if (cursor < line.length) {
|
|
444
|
+
line = line.slice(0, cursor) + line.slice(cursor + 1)
|
|
445
|
+
redraw()
|
|
446
|
+
}
|
|
447
|
+
s = s.slice(3)
|
|
448
|
+
continue
|
|
449
|
+
}
|
|
450
|
+
// Прочие ESC-последовательности пропускаем до буквы/тильды.
|
|
451
|
+
const m = s.match(/^\[[0-9;]*[A-Za-z~]/)
|
|
452
|
+
if (m) s = s.slice(m[0].length)
|
|
453
|
+
continue
|
|
454
|
+
}
|
|
455
|
+
if (code < 32) continue // прочие управляющие символы игнорируем
|
|
456
|
+
|
|
457
|
+
insertText(ch)
|
|
458
|
+
redraw()
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
stdin.on('data', onData)
|
|
250
463
|
})
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Слежение за клавиатурой во время работы агента.
|
|
467
|
+
// Esc — прервать текущую генерацию (клик Stop в браузере).
|
|
468
|
+
function watchEscape(onEscape) {
|
|
469
|
+
const stdin = process.stdin
|
|
470
|
+
if (!stdin.isTTY) return () => {}
|
|
471
|
+
const wasRaw = stdin.isRaw
|
|
472
|
+
if (stdin.setRawMode) stdin.setRawMode(true)
|
|
473
|
+
stdin.resume()
|
|
474
|
+
const handler = (buf) => {
|
|
475
|
+
if (buf.length === 1 && buf[0] === 27) {
|
|
476
|
+
onEscape()
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
stdin.on('data', handler)
|
|
480
|
+
return () => {
|
|
481
|
+
stdin.removeListener('data', handler)
|
|
482
|
+
if (stdin.setRawMode) stdin.setRawMode(wasRaw || false)
|
|
255
483
|
}
|
|
256
484
|
}
|
|
257
485
|
|
|
@@ -277,9 +505,15 @@ async function runTask(browser, tools, taskText, workdir, opts) {
|
|
|
277
505
|
const { transcript, freshChat, sendSystemPrompt } = opts
|
|
278
506
|
|
|
279
507
|
const ui = mod.createSpinner()
|
|
280
|
-
ui.taskHeader(taskText)
|
|
281
508
|
ui.thinking()
|
|
282
509
|
|
|
510
|
+
// Esc во время работы — прервать генерацию.
|
|
511
|
+
const stopWatching = watchEscape(() => {
|
|
512
|
+
ui.stop()
|
|
513
|
+
console.error(theme.warn('⏹ Esc — прерываю генерацию...'))
|
|
514
|
+
browser.stopGeneration().catch(() => {})
|
|
515
|
+
})
|
|
516
|
+
|
|
283
517
|
let finished = false
|
|
284
518
|
|
|
285
519
|
try {
|
|
@@ -299,13 +533,15 @@ async function runTask(browser, tools, taskText, workdir, opts) {
|
|
|
299
533
|
ui.assistant(msg)
|
|
300
534
|
finished = true
|
|
301
535
|
},
|
|
536
|
+
debugLog: debug,
|
|
302
537
|
})
|
|
303
538
|
} catch (e) {
|
|
304
539
|
ui.stop()
|
|
305
|
-
console.error(
|
|
540
|
+
console.error(theme.error('\n✖ Ошибка агента:'), e.message)
|
|
306
541
|
if (debug) console.error(e.stack)
|
|
307
542
|
transcript?.log('agent_error', { error: e.message })
|
|
308
543
|
} finally {
|
|
544
|
+
stopWatching()
|
|
309
545
|
if (!finished) ui.stop()
|
|
310
546
|
}
|
|
311
547
|
}
|
|
@@ -327,9 +563,11 @@ async function main() {
|
|
|
327
563
|
}
|
|
328
564
|
|
|
329
565
|
if (calibrate) {
|
|
330
|
-
console.log(
|
|
566
|
+
console.log(theme.warn('\n🔧 Режим калибровки селекторов\n'))
|
|
331
567
|
}
|
|
332
568
|
|
|
569
|
+
await cleanTmpDir()
|
|
570
|
+
|
|
333
571
|
let currentWorkdir
|
|
334
572
|
try {
|
|
335
573
|
currentWorkdir = await resolveWorkdir()
|
|
@@ -337,13 +575,13 @@ async function main() {
|
|
|
337
575
|
const sandboxRoot = currentWorkdir
|
|
338
576
|
} catch (e) {
|
|
339
577
|
console.error(
|
|
340
|
-
|
|
578
|
+
theme.error('Не удалось определить рабочую директорию:'),
|
|
341
579
|
e.message,
|
|
342
580
|
)
|
|
343
581
|
process.exit(1)
|
|
344
582
|
}
|
|
345
583
|
|
|
346
|
-
console.log(
|
|
584
|
+
console.log(theme.system(`Рабочая директория: ${currentWorkdir}`))
|
|
347
585
|
|
|
348
586
|
const transcript = new Transcript({
|
|
349
587
|
dir: config.transcript.dir,
|
|
@@ -351,7 +589,7 @@ async function main() {
|
|
|
351
589
|
sessionName: dirLabel(currentWorkdir),
|
|
352
590
|
})
|
|
353
591
|
if (transcript.file) {
|
|
354
|
-
console.log(
|
|
592
|
+
console.log(theme.system(`Транскрипт: ${transcript.file}`))
|
|
355
593
|
}
|
|
356
594
|
|
|
357
595
|
const undo = new UndoStore(config.undo)
|
|
@@ -372,7 +610,7 @@ async function main() {
|
|
|
372
610
|
await browser.waitForLogin()
|
|
373
611
|
} catch (e) {
|
|
374
612
|
bootSpinner.stop()
|
|
375
|
-
console.error(
|
|
613
|
+
console.error(theme.error('Не удалось запустить браузер:'), e.message)
|
|
376
614
|
if (debug) console.error(e.stack)
|
|
377
615
|
await browser.close().catch(() => {})
|
|
378
616
|
transcript.close()
|
|
@@ -386,21 +624,23 @@ async function main() {
|
|
|
386
624
|
let freshChat = true
|
|
387
625
|
let sendSystemPrompt = true
|
|
388
626
|
|
|
389
|
-
//
|
|
627
|
+
// По умолчанию — новый чат. Продолжить прошлый можно явно:
|
|
628
|
+
// --chat <id> открыть конкретный чат
|
|
629
|
+
// --resume-last вернуться в последний сохранённый чат
|
|
390
630
|
let resumeId = chatIdArg
|
|
391
|
-
if (!resumeId &&
|
|
631
|
+
if (!resumeId && resumeLastFlag) {
|
|
392
632
|
resumeId = await loadLastChat()
|
|
393
|
-
if (resumeId) console.log(
|
|
633
|
+
if (resumeId) console.log(theme.system(`Восстанавливаю чат ${resumeId}...`))
|
|
394
634
|
}
|
|
395
635
|
|
|
396
636
|
if (resumeId) {
|
|
397
637
|
try {
|
|
398
|
-
console.log(
|
|
638
|
+
console.log(theme.system(`Открываю чат ${resumeId}...`))
|
|
399
639
|
await browser.openChat(resumeId)
|
|
400
640
|
freshChat = false
|
|
401
641
|
sendSystemPrompt = resendPrompt
|
|
402
642
|
} catch (e) {
|
|
403
|
-
console.error(
|
|
643
|
+
console.error(theme.error(`Не удалось открыть чат: ${e.message}`))
|
|
404
644
|
}
|
|
405
645
|
}
|
|
406
646
|
|
|
@@ -418,7 +658,7 @@ async function main() {
|
|
|
418
658
|
}
|
|
419
659
|
|
|
420
660
|
console.log(
|
|
421
|
-
|
|
661
|
+
theme.system(
|
|
422
662
|
'Интерактивный режим. Введите задачу. Команды — /help. Выход — /exit.\n',
|
|
423
663
|
),
|
|
424
664
|
)
|
|
@@ -442,42 +682,45 @@ async function main() {
|
|
|
442
682
|
// висеть отдельным процессом после выхода агента.
|
|
443
683
|
await mod.closeWeb().catch(() => {})
|
|
444
684
|
transcript.close()
|
|
445
|
-
console.log(
|
|
685
|
+
console.log(theme.system('\nВыход.'))
|
|
446
686
|
process.exit(0)
|
|
447
687
|
})
|
|
448
688
|
|
|
449
|
-
//
|
|
689
|
+
// По умолчанию — новый чат. Продолжить прошлый можно явно:
|
|
690
|
+
// --chat <id> открыть конкретный чат
|
|
691
|
+
// --resume-last вернуться в последний сохранённый чат
|
|
450
692
|
let resumeId = chatIdArg
|
|
451
|
-
if (!resumeId &&
|
|
693
|
+
if (!resumeId && resumeLastFlag) {
|
|
452
694
|
resumeId = await loadLastChat()
|
|
695
|
+
if (resumeId) console.log(theme.system(`Восстанавливаю чат ${resumeId}...`))
|
|
453
696
|
}
|
|
454
697
|
|
|
455
698
|
if (resumeId) {
|
|
456
699
|
try {
|
|
457
|
-
console.log(
|
|
700
|
+
console.log(theme.system(`Открываю чат ${resumeId}...`))
|
|
458
701
|
await browser.openChat(resumeId)
|
|
459
702
|
currentChatId = resumeId
|
|
460
703
|
freshChatNext = false
|
|
461
704
|
sendSystemPromptNext = resendPrompt
|
|
462
705
|
await saveLastChat(resumeId)
|
|
463
|
-
console.log(
|
|
706
|
+
console.log(theme.system(`Чат открыт: ${resumeId}\n`))
|
|
464
707
|
} catch (e) {
|
|
465
|
-
console.error(
|
|
708
|
+
console.error(theme.error(`Не удалось открыть чат: ${e.message}`))
|
|
466
709
|
}
|
|
467
710
|
}
|
|
468
711
|
|
|
469
712
|
while (running) {
|
|
470
713
|
let input
|
|
471
714
|
try {
|
|
472
|
-
|
|
715
|
+
// Компактное приглашение: золотая стрелка + светло-голубая директория.
|
|
716
|
+
let tail
|
|
473
717
|
if (reviewMode) {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
label = `zames[${dirLabel(currentWorkdir)}|${currentChatId.slice(0, 6)}]> `
|
|
718
|
+
tail =
|
|
719
|
+
theme.warn('REVIEW') + theme.dim(':') + theme.dir(reviewMode.snapName)
|
|
477
720
|
} else {
|
|
478
|
-
|
|
721
|
+
tail = theme.dir(dirLabel(currentWorkdir))
|
|
479
722
|
}
|
|
480
|
-
input = await promptOnce(
|
|
723
|
+
input = await promptOnce(theme.prompt('❯ ') + tail + theme.dim(' › '))
|
|
481
724
|
} catch {
|
|
482
725
|
break
|
|
483
726
|
}
|
|
@@ -495,7 +738,7 @@ async function main() {
|
|
|
495
738
|
}
|
|
496
739
|
|
|
497
740
|
if (['/new', '/clear', 'new'].includes(lower)) {
|
|
498
|
-
console.log(
|
|
741
|
+
console.log(theme.system('Создаю новый чат...'))
|
|
499
742
|
try {
|
|
500
743
|
await browser.newChat()
|
|
501
744
|
freshChatNext = false
|
|
@@ -503,9 +746,9 @@ async function main() {
|
|
|
503
746
|
currentChatId = await browser.getCurrentChatId()
|
|
504
747
|
await saveLastChat(currentChatId)
|
|
505
748
|
transcript.log('new_chat')
|
|
506
|
-
console.log(
|
|
749
|
+
console.log(theme.system('Новый чат.\n'))
|
|
507
750
|
} catch (e) {
|
|
508
|
-
console.error(
|
|
751
|
+
console.error(theme.error('Не удалось создать новый чат:'), e.message)
|
|
509
752
|
}
|
|
510
753
|
continue
|
|
511
754
|
}
|
|
@@ -544,7 +787,7 @@ async function main() {
|
|
|
544
787
|
await saveLastChat(currentChatId)
|
|
545
788
|
|
|
546
789
|
console.log(
|
|
547
|
-
|
|
790
|
+
theme.user(
|
|
548
791
|
'\n💡 Теперь ты в режиме ревью. Просто пиши агенту, например:\n' +
|
|
549
792
|
' «исправь ошибки»\n' +
|
|
550
793
|
' «доработай обработку ошибок в ask()»\n' +
|
|
@@ -555,7 +798,7 @@ async function main() {
|
|
|
555
798
|
),
|
|
556
799
|
)
|
|
557
800
|
} catch (e) {
|
|
558
|
-
console.error(
|
|
801
|
+
console.error(theme.error('Самообзор провалился:'), e.message)
|
|
559
802
|
if (debug) console.error(e.stack)
|
|
560
803
|
}
|
|
561
804
|
continue
|
|
@@ -564,7 +807,7 @@ async function main() {
|
|
|
564
807
|
if (lower === '/self-fix' || lower.startsWith('/self-fix ')) {
|
|
565
808
|
const rest = trimmed.slice('/self-fix'.length).trim()
|
|
566
809
|
if (!rest) {
|
|
567
|
-
console.error(
|
|
810
|
+
console.error(theme.error('Использование: /self-fix <name> [фокус]'))
|
|
568
811
|
continue
|
|
569
812
|
}
|
|
570
813
|
const sp = rest.indexOf(' ')
|
|
@@ -574,7 +817,7 @@ async function main() {
|
|
|
574
817
|
const snapRoot = path.join(ZAMES_HOME, 'snapshots', name)
|
|
575
818
|
const stat = await fs.stat(snapRoot).catch(() => null)
|
|
576
819
|
if (!stat || !stat.isDirectory()) {
|
|
577
|
-
console.error(
|
|
820
|
+
console.error(theme.error(`Снапшот не найден: ${snapRoot}`))
|
|
578
821
|
continue
|
|
579
822
|
}
|
|
580
823
|
|
|
@@ -593,7 +836,7 @@ async function main() {
|
|
|
593
836
|
workdir: snapRoot,
|
|
594
837
|
tools,
|
|
595
838
|
})
|
|
596
|
-
console.log(
|
|
839
|
+
console.log(theme.system('Инициализирую review-чат для снапшота...'))
|
|
597
840
|
await browser.ask(sysPrompt, { timeout: 60_000 })
|
|
598
841
|
|
|
599
842
|
if (focus) {
|
|
@@ -611,7 +854,7 @@ async function main() {
|
|
|
611
854
|
onThinking: () => ui.thinking(),
|
|
612
855
|
onAssistantThought: (text) => {
|
|
613
856
|
ui.stop()
|
|
614
|
-
console.log(
|
|
857
|
+
console.log(theme.system('\n💭 ' + text.slice(0, 1200) + '\n'))
|
|
615
858
|
},
|
|
616
859
|
onToolCall: (name, toolArgs) => ui.toolCall(name, toolArgs),
|
|
617
860
|
onToolResult: (r) => ui.toolResult(r),
|
|
@@ -633,19 +876,19 @@ async function main() {
|
|
|
633
876
|
await saveLastChat(currentChatId)
|
|
634
877
|
|
|
635
878
|
console.log(
|
|
636
|
-
|
|
879
|
+
theme.user(
|
|
637
880
|
`\n💡 Режим ревью по снапшоту ${name}. Пиши агенту задачу или /self-done.\n`,
|
|
638
881
|
),
|
|
639
882
|
)
|
|
640
883
|
} catch (e) {
|
|
641
|
-
console.error(
|
|
884
|
+
console.error(theme.error('Не удалось войти в снапшот:'), e.message)
|
|
642
885
|
}
|
|
643
886
|
continue
|
|
644
887
|
}
|
|
645
888
|
|
|
646
889
|
if (lower === '/self-done') {
|
|
647
890
|
if (!reviewMode) {
|
|
648
|
-
console.log(
|
|
891
|
+
console.log(theme.system('Ты и так не в режиме ревью.'))
|
|
649
892
|
continue
|
|
650
893
|
}
|
|
651
894
|
const back = reviewMode.originalWorkdir
|
|
@@ -655,7 +898,7 @@ async function main() {
|
|
|
655
898
|
freshChatNext = true
|
|
656
899
|
sendSystemPromptNext = true
|
|
657
900
|
console.log(
|
|
658
|
-
|
|
901
|
+
theme.system(`Вернулся в ${back}. Следующая задача начнёт новый чат.\n`),
|
|
659
902
|
)
|
|
660
903
|
continue
|
|
661
904
|
}
|
|
@@ -664,7 +907,7 @@ async function main() {
|
|
|
664
907
|
try {
|
|
665
908
|
await mod.selfList({ config })
|
|
666
909
|
} catch (e) {
|
|
667
|
-
console.error(
|
|
910
|
+
console.error(theme.error('Ошибка:'), e.message)
|
|
668
911
|
}
|
|
669
912
|
continue
|
|
670
913
|
}
|
|
@@ -672,13 +915,13 @@ async function main() {
|
|
|
672
915
|
if (lower === '/self-diff' || lower.startsWith('/self-diff ')) {
|
|
673
916
|
const name = trimmed.slice('/self-diff'.length).trim()
|
|
674
917
|
if (!name) {
|
|
675
|
-
console.error(
|
|
918
|
+
console.error(theme.error('Использование: /self-diff <name>'))
|
|
676
919
|
continue
|
|
677
920
|
}
|
|
678
921
|
try {
|
|
679
922
|
await mod.selfDiff({ config, name })
|
|
680
923
|
} catch (e) {
|
|
681
|
-
console.error(
|
|
924
|
+
console.error(theme.error('Ошибка:'), e.message)
|
|
682
925
|
}
|
|
683
926
|
continue
|
|
684
927
|
}
|
|
@@ -686,13 +929,13 @@ async function main() {
|
|
|
686
929
|
if (lower === '/self-apply' || lower.startsWith('/self-apply ')) {
|
|
687
930
|
const name = trimmed.slice('/self-apply'.length).trim()
|
|
688
931
|
if (!name) {
|
|
689
|
-
console.error(
|
|
932
|
+
console.error(theme.error('Использование: /self-apply <name>'))
|
|
690
933
|
continue
|
|
691
934
|
}
|
|
692
935
|
try {
|
|
693
936
|
await mod.selfApply({ config, name })
|
|
694
937
|
} catch (e) {
|
|
695
|
-
console.error(
|
|
938
|
+
console.error(theme.error('Ошибка:'), e.message)
|
|
696
939
|
}
|
|
697
940
|
continue
|
|
698
941
|
}
|
|
@@ -707,23 +950,23 @@ async function main() {
|
|
|
707
950
|
spin.stop()
|
|
708
951
|
if (!lastChats.length) {
|
|
709
952
|
console.log(
|
|
710
|
-
|
|
953
|
+
theme.system(
|
|
711
954
|
'Чатов не найдено. Возможно, сайдбар свёрнут или селекторы устарели.',
|
|
712
955
|
),
|
|
713
956
|
)
|
|
714
957
|
} else {
|
|
715
|
-
console.log(
|
|
958
|
+
console.log(theme.system('Последние чаты DeepSeek:'))
|
|
716
959
|
lastChats.forEach((c, i) => {
|
|
717
960
|
const n = String(i + 1).padStart(2, ' ')
|
|
718
961
|
console.log(
|
|
719
|
-
` ${
|
|
962
|
+
` ${theme.user(n)}. ${c.title} ${theme.system('(' + c.id.slice(0, 8) + '…)')}`,
|
|
720
963
|
)
|
|
721
964
|
})
|
|
722
|
-
console.log(
|
|
965
|
+
console.log(theme.system('\nИспользуй /resume <n> для продолжения.\n'))
|
|
723
966
|
}
|
|
724
967
|
} catch (e) {
|
|
725
968
|
spin.stop()
|
|
726
|
-
console.error(
|
|
969
|
+
console.error(theme.error('Не удалось получить список:'), e.message)
|
|
727
970
|
}
|
|
728
971
|
continue
|
|
729
972
|
}
|
|
@@ -732,26 +975,26 @@ async function main() {
|
|
|
732
975
|
const arg = trimmed.slice(7).trim()
|
|
733
976
|
if (!arg) {
|
|
734
977
|
console.error(
|
|
735
|
-
|
|
978
|
+
theme.error('Использование: /resume <n> (или /chats для списка)'),
|
|
736
979
|
)
|
|
737
980
|
continue
|
|
738
981
|
}
|
|
739
982
|
const n = Number(arg)
|
|
740
983
|
if (!Number.isFinite(n) || n < 1) {
|
|
741
|
-
console.error(
|
|
984
|
+
console.error(theme.error('Нужен номер из /chats.'))
|
|
742
985
|
continue
|
|
743
986
|
}
|
|
744
987
|
if (!lastChats.length) {
|
|
745
|
-
console.error(
|
|
988
|
+
console.error(theme.error('Сначала выполни /chats.'))
|
|
746
989
|
continue
|
|
747
990
|
}
|
|
748
991
|
const pick = lastChats[n - 1]
|
|
749
992
|
if (!pick) {
|
|
750
|
-
console.error(
|
|
993
|
+
console.error(theme.error(`Нет чата №${n}. Всего: ${lastChats.length}.`))
|
|
751
994
|
continue
|
|
752
995
|
}
|
|
753
996
|
|
|
754
|
-
console.log(
|
|
997
|
+
console.log(theme.system(`Открываю: ${pick.title}`))
|
|
755
998
|
try {
|
|
756
999
|
await browser.openChat(pick.id)
|
|
757
1000
|
currentChatId = pick.id
|
|
@@ -760,94 +1003,94 @@ async function main() {
|
|
|
760
1003
|
await saveLastChat(pick.id)
|
|
761
1004
|
transcript.log('resume_chat', { id: pick.id, title: pick.title })
|
|
762
1005
|
console.log(
|
|
763
|
-
|
|
764
|
-
|
|
1006
|
+
theme.assistant(`Чат открыт.`) +
|
|
1007
|
+
theme.system(
|
|
765
1008
|
resendPrompt ? ' Системный промпт будет переслан на следующей задаче.\n' : ' Контекст чата сохранён. Системный промпт не пересылается (--resend-prompt чтобы дослать).\n',
|
|
766
1009
|
),
|
|
767
1010
|
)
|
|
768
1011
|
} catch (e) {
|
|
769
|
-
console.error(
|
|
1012
|
+
console.error(theme.error('Не удалось открыть чат:'), e.message)
|
|
770
1013
|
}
|
|
771
1014
|
continue
|
|
772
1015
|
}
|
|
773
1016
|
|
|
774
1017
|
if (lower === '/chat') {
|
|
775
1018
|
if (currentChatId) {
|
|
776
|
-
console.log(
|
|
1019
|
+
console.log(theme.system(`Текущий chat id: ${currentChatId}`))
|
|
777
1020
|
console.log(
|
|
778
|
-
|
|
1021
|
+
theme.system(
|
|
779
1022
|
`URL: https://chat.deepseek.com/a/chat/s/${currentChatId}`,
|
|
780
1023
|
),
|
|
781
1024
|
)
|
|
782
1025
|
} else {
|
|
783
1026
|
const id = await browser.getCurrentChatId()
|
|
784
1027
|
console.log(
|
|
785
|
-
|
|
1028
|
+
theme.system(id ? `Текущий chat id: ${id}` : 'Чат ещё не создан.'),
|
|
786
1029
|
)
|
|
787
1030
|
}
|
|
788
1031
|
continue
|
|
789
1032
|
}
|
|
790
1033
|
|
|
791
1034
|
if (lower === '/pwd') {
|
|
792
|
-
console.log(
|
|
1035
|
+
console.log(theme.system(currentWorkdir))
|
|
793
1036
|
continue
|
|
794
1037
|
}
|
|
795
1038
|
|
|
796
1039
|
if (lower === '/reload') {
|
|
797
|
-
console.log(
|
|
1040
|
+
console.log(theme.system('Перечитываю модули логики...'))
|
|
798
1041
|
try {
|
|
799
1042
|
const { count, errors } = await reloadModules()
|
|
800
1043
|
if (errors.length) {
|
|
801
|
-
console.error(
|
|
802
|
-
for (const e of errors) console.error(
|
|
1044
|
+
console.error(theme.error('Часть модулей не перезагрузилась:'))
|
|
1045
|
+
for (const e of errors) console.error(theme.error(' ' + e))
|
|
803
1046
|
} else {
|
|
804
1047
|
console.log(
|
|
805
|
-
|
|
1048
|
+
theme.assistant(
|
|
806
1049
|
`Перезагружено модулей: ${count}. Браузер и чат не тронуты.`,
|
|
807
1050
|
),
|
|
808
1051
|
)
|
|
809
1052
|
}
|
|
810
1053
|
} catch (e) {
|
|
811
|
-
console.error(
|
|
1054
|
+
console.error(theme.error('Ошибка reload:'), e.message)
|
|
812
1055
|
}
|
|
813
1056
|
continue
|
|
814
1057
|
}
|
|
815
1058
|
|
|
816
1059
|
if (lower === '/status') {
|
|
817
|
-
console.log(
|
|
1060
|
+
console.log(theme.system(`Рабочая директория: ${currentWorkdir}`))
|
|
818
1061
|
console.log(
|
|
819
|
-
|
|
1062
|
+
theme.system(`Режим ревью: ${reviewMode ? reviewMode.snapName : 'нет'}`),
|
|
820
1063
|
)
|
|
821
1064
|
if (reviewMode) {
|
|
822
1065
|
console.log(
|
|
823
|
-
|
|
1066
|
+
theme.system(`Исходная директория: ${reviewMode.originalWorkdir}`),
|
|
824
1067
|
)
|
|
825
1068
|
}
|
|
826
|
-
console.log(
|
|
1069
|
+
console.log(theme.system(`Текущий чат: ${currentChatId || '(нет)'}`))
|
|
827
1070
|
console.log(
|
|
828
|
-
|
|
1071
|
+
theme.system(
|
|
829
1072
|
`Fresh chat на след. задаче: ${freshChatNext ? 'да' : 'нет'}`,
|
|
830
1073
|
),
|
|
831
1074
|
)
|
|
832
1075
|
console.log(
|
|
833
|
-
|
|
1076
|
+
theme.system(
|
|
834
1077
|
`System prompt на след. задаче: ${sendSystemPromptNext ? 'да' : 'нет'}`,
|
|
835
1078
|
),
|
|
836
1079
|
)
|
|
837
1080
|
console.log(
|
|
838
|
-
|
|
1081
|
+
theme.system(`Resend prompt (--resend-prompt): ${resendPrompt ? 'да' : 'нет'}`),
|
|
839
1082
|
)
|
|
840
1083
|
console.log(
|
|
841
|
-
|
|
1084
|
+
theme.system(`Last chat: ${(await loadLastChat()) || 'нет'}`),
|
|
842
1085
|
)
|
|
843
1086
|
console.log(
|
|
844
|
-
|
|
1087
|
+
theme.system(`Dev mode (auto-reload): ${devMode ? 'вкл' : 'выкл'}`),
|
|
845
1088
|
)
|
|
846
|
-
console.log(
|
|
847
|
-
console.log(
|
|
848
|
-
console.log(
|
|
849
|
-
console.log(
|
|
850
|
-
console.log(
|
|
1089
|
+
console.log(theme.system(`Лимит итераций: ${maxIter}`))
|
|
1090
|
+
console.log(theme.system(`Headless: ${headless ? 'да' : 'нет'}`))
|
|
1091
|
+
console.log(theme.system(`Debug: ${debug ? 'да' : 'нет'}`))
|
|
1092
|
+
console.log(theme.system(`Undo: ${config.undo.enabled ? 'вкл' : 'выкл'}`))
|
|
1093
|
+
console.log(theme.system(`Транскрипт: ${transcript.file || 'выкл'}`))
|
|
851
1094
|
continue
|
|
852
1095
|
}
|
|
853
1096
|
|
|
@@ -857,7 +1100,7 @@ async function main() {
|
|
|
857
1100
|
}
|
|
858
1101
|
|
|
859
1102
|
if (lower === '/transcript') {
|
|
860
|
-
console.log(
|
|
1103
|
+
console.log(theme.system(transcript.file || '(выключен)'))
|
|
861
1104
|
continue
|
|
862
1105
|
}
|
|
863
1106
|
|
|
@@ -865,14 +1108,14 @@ async function main() {
|
|
|
865
1108
|
const result = await undo.undoLast()
|
|
866
1109
|
if (result.ok) {
|
|
867
1110
|
console.log(
|
|
868
|
-
|
|
869
|
-
|
|
1111
|
+
theme.assistant(`↶ Откатили: ${result.record.originalPath}`) +
|
|
1112
|
+
theme.system(
|
|
870
1113
|
result.record.existed ? ' (восстановлено)' : ' (удалено)',
|
|
871
1114
|
),
|
|
872
1115
|
)
|
|
873
1116
|
transcript.log('undo', { path: result.record.originalPath })
|
|
874
1117
|
} else {
|
|
875
|
-
console.error(
|
|
1118
|
+
console.error(theme.error(`Не удалось откатить: ${result.reason}`))
|
|
876
1119
|
}
|
|
877
1120
|
continue
|
|
878
1121
|
}
|
|
@@ -880,12 +1123,12 @@ async function main() {
|
|
|
880
1123
|
if (lower === '/undo-list' || lower === '/history') {
|
|
881
1124
|
const list = await undo.list(10)
|
|
882
1125
|
if (!list.length) {
|
|
883
|
-
console.log(
|
|
1126
|
+
console.log(theme.system('История пуста.'))
|
|
884
1127
|
} else {
|
|
885
1128
|
for (const r of list) {
|
|
886
1129
|
const stamp = new Date(r.stamp).toLocaleString()
|
|
887
1130
|
const flag = r.existed ? 'изменён' : 'создан'
|
|
888
|
-
console.log(
|
|
1131
|
+
console.log(theme.system(`${stamp} [${flag}] ${r.originalPath}`))
|
|
889
1132
|
}
|
|
890
1133
|
}
|
|
891
1134
|
continue
|
|
@@ -896,11 +1139,11 @@ async function main() {
|
|
|
896
1139
|
const file = path.join(config.transcript.dir, `dom-${stamp}.html`)
|
|
897
1140
|
try {
|
|
898
1141
|
const result = await browser.dumpDom(file)
|
|
899
|
-
console.log(
|
|
900
|
-
console.log(
|
|
1142
|
+
console.log(theme.assistant(`HTML сохранён: ${result.file}`))
|
|
1143
|
+
console.log(theme.system('Селекторы:'))
|
|
901
1144
|
console.log(JSON.stringify(result.selectors, null, 2))
|
|
902
1145
|
} catch (e) {
|
|
903
|
-
console.error(
|
|
1146
|
+
console.error(theme.error('Не удалось сохранить DOM:'), e.message)
|
|
904
1147
|
}
|
|
905
1148
|
continue
|
|
906
1149
|
}
|
|
@@ -913,34 +1156,34 @@ async function main() {
|
|
|
913
1156
|
: sandboxRoot
|
|
914
1157
|
const rel = path.relative(sandboxRoot, newDir)
|
|
915
1158
|
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
916
|
-
console.error(
|
|
1159
|
+
console.error(theme.error('Нельзя выйти за пределы: ' + sandboxRoot))
|
|
917
1160
|
continue
|
|
918
1161
|
}
|
|
919
1162
|
const stat = await fs.stat(newDir).catch(() => null)
|
|
920
1163
|
if (!stat || !stat.isDirectory()) {
|
|
921
|
-
console.error(
|
|
1164
|
+
console.error(theme.error('Не директория: ' + newDir))
|
|
922
1165
|
continue
|
|
923
1166
|
}
|
|
924
1167
|
if (newDir === currentWorkdir) {
|
|
925
|
-
console.log(
|
|
1168
|
+
console.log(theme.system('Уже здесь.'))
|
|
926
1169
|
continue
|
|
927
1170
|
}
|
|
928
1171
|
if (reviewMode) {
|
|
929
|
-
console.log(
|
|
1172
|
+
console.log(theme.system('Вышел из режима ревью (/cd).'))
|
|
930
1173
|
reviewMode = null
|
|
931
1174
|
}
|
|
932
1175
|
currentWorkdir = newDir
|
|
933
1176
|
freshChatNext = true
|
|
934
1177
|
sendSystemPromptNext = true
|
|
935
|
-
console.log(
|
|
1178
|
+
console.log(theme.system('Рабочая директория: ' + newDir))
|
|
936
1179
|
} catch (e) {
|
|
937
|
-
console.error(
|
|
1180
|
+
console.error(theme.error('Не удалось перейти: ' + e.message))
|
|
938
1181
|
}
|
|
939
1182
|
continue
|
|
940
1183
|
}
|
|
941
1184
|
|
|
942
1185
|
if (lower.startsWith('/')) {
|
|
943
|
-
console.error(
|
|
1186
|
+
console.error(theme.error(`Неизвестная команда: ${trimmed}. Набери /help.`))
|
|
944
1187
|
continue
|
|
945
1188
|
}
|
|
946
1189
|
|
|
@@ -972,7 +1215,7 @@ async function main() {
|
|
|
972
1215
|
}
|
|
973
1216
|
|
|
974
1217
|
main().catch((e) => {
|
|
975
|
-
console.error(
|
|
1218
|
+
console.error(theme.error('Критическая ошибка:'), e.message)
|
|
976
1219
|
if (debug) console.error(e.stack)
|
|
977
1220
|
process.exit(1)
|
|
978
1221
|
})
|