zames_pro 1.1.0 → 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 CHANGED
@@ -2,69 +2,54 @@
2
2
 
3
3
  ![zames logo](https://raw.githubusercontent.com/Viqto0r/zames_pro/master/logo.png)
4
4
 
5
- Terminal coding agent, работающий поверх chat.deepseek.com через Playwright.
6
- По духу похож на Claude Code / Codex CLI: запускается в текущей директории, читает и правит файлы, запускает команды, коммитит в git.
5
+ A terminal coding agent that works on top of [chat.deepseek.com](https://chat.deepseek.com/) through Playwright.
6
+ In spirit it is similar to Claude Code / Codex CLI: it starts in the current
7
+ directory, reads and edits files, runs commands, and commits to git.
7
8
 
8
- ## Требования
9
+ ## Requirements
9
10
 
10
11
  - Node.js >= 18
11
- - Google Chrome или Chromium (используется через Playwright)
12
- - Аккаунт DeepSeek (вход выполняется вручную в открывшемся браузере при первом запуске)
12
+ - Google Chrome or Chromium (used through Playwright)
13
+ - A DeepSeek account (you log in manually in the browser window that opens on first launch)
13
14
 
14
- ## Установка
15
+ ## Installation
15
16
 
16
- `bash
17
+ ```bash
17
18
  npm install -g zames_pro
18
19
  npx playwright install chromium
19
- `
20
+ ```
20
21
 
21
- ## Запуск
22
+ ## Usage
22
23
 
23
- Перейди в папку проекта и запусти:
24
+ Go to your project folder and run:
24
25
 
25
- `bash
26
+ ```bash
26
27
  zames
27
- `
28
+ ```
28
29
 
29
- Агент работает в директории запуска и не может выходить за её пределы (sandbox).
30
+ The agent works inside the directory it was started in and cannot leave it (sandbox).
30
31
 
31
- ### Опции
32
+ ### Options
32
33
 
33
34
  ```
34
- zames --task <текст задачи>
35
+ zames --task <task text>
35
36
  zames --chat <id>
36
37
  zames --new-chat
37
38
  zames --resend-prompt
38
- zames --dir <путь>
39
+ zames --dir <path>
39
40
  zames --headless
40
41
  zames --debug
41
- zames --dev
42
42
  zames --version
43
43
  zames --help
44
44
  ```
45
45
 
46
- ## Конфигурация
47
-
48
- Глобальный конфиг: `~/.zames/config.json`
49
- Локальный (в проекте): `.zamesrc.json`
50
-
51
- Данные агента хранятся в `~/.zames`: профиль браузера, логи, история undo, снапшоты self-review.
52
-
53
- ## Разработка
54
-
55
- Для работы над самим агентом используй dev-режим:
56
-
57
- `bash
58
- npm run dev
59
- `
46
+ ## Configuration
60
47
 
61
- В этом режиме агент перед каждой задачей автоматически перечитывает модули логики (tools, agent-loop, system-prompt и др.) с диска — правки подхватываются без перезапуска браузера и без потери контекста чата. В обычном режиме (zames, npm start) авто-перечитывание выключено.
48
+ Global config: `~/.zames/config.json`
49
+ Local (per project): `.zamesrc.json`
62
50
 
63
- Служебные команды:
64
- - /reload — принудительно перечитать модули логики
65
- - /self-review — снять снапшот src/ и запустить самообзор
66
- - /status — состояние сессии
51
+ Agent data is stored in `~/.zames`: browser profile, logs, undo history, self-review snapshots.
67
52
 
68
- ## Лицензия и авторство
53
+ ## License
69
54
 
70
55
  MIT
package/logo.png CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.js CHANGED
@@ -2,7 +2,6 @@
2
2
  import path from 'path'
3
3
  import fs from 'fs/promises'
4
4
  import { fileURLToPath } from 'url'
5
- import * as readlinePromises from 'readline/promises'
6
5
  import { theme } from './theme.js'
7
6
 
8
7
  import { DeepSeekBrowser } from './browser.js'
@@ -256,20 +255,212 @@ async function cleanTmpDir() {
256
255
  }
257
256
  }
258
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
+ // не считается отправкой.
259
271
  async function promptOnce(question) {
260
- process.stdin.resume()
261
- if (process.stdin.isTTY && process.stdin.setRawMode) {
262
- process.stdin.setRawMode(false)
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
+ })
263
289
  }
264
- const rl = readlinePromises.createInterface({
265
- input: process.stdin,
266
- output: process.stdout,
267
- })
268
- try {
269
- return await rl.question(question)
270
- } finally {
271
- rl.close()
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')
272
313
  }
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)
463
+ })
273
464
  }
274
465
 
275
466
  // Слежение за клавиатурой во время работы агента.
package/src/spinner.js CHANGED
@@ -2,6 +2,44 @@ import ora from 'ora'
2
2
  import { theme } from './theme.js'
3
3
  import { renderMarkdown } from './markdown.js'
4
4
 
5
+ // Фразы для спиннера «агент работает». Выбираются в случайном порядке.
6
+ const THINKING_PHRASES = [
7
+ 'Замешиваю кал…',
8
+ 'Взбиваю кал до однородной массы…',
9
+ 'Мешаю кал с логикой…',
10
+ 'Взбалтываю кал в коктейль…',
11
+ 'Кручу венчиком по калу…',
12
+ 'Смешиваю кал с кофеином…',
13
+ 'Замешиваю тесто из кала…',
14
+ 'Взбиваю пену из кала…',
15
+ 'Перемешиваю кал лопатой…',
16
+ 'Замешиваю глину из кала…',
17
+ 'Гомогенизирую кал до состояния бетона…',
18
+ 'Взбалтываю кал до просветления…',
19
+ 'Блендерю кал в смузи…',
20
+ 'Замешиваю цемент из кала…',
21
+ 'Взбиваю кашу из кала…',
22
+ 'Мешаю кал с надеждой…',
23
+ 'Взбалтываю осадок из кала…',
24
+ 'Кручу блендером по калу…',
25
+ 'Смешиваю кал до неразличимости…',
26
+ 'Замешиваю раствор из кала…',
27
+ 'Взбиваю коктейль «кал»…',
28
+ 'Перемешиваю кал лопатой дедлайна…',
29
+ 'Замешиваю кашу из кала…',
30
+ 'Взбиваю пенку из кала…',
31
+ 'Гомогенизирую кал до однородности…',
32
+ 'Взбалтываю кал в бетономешалке…',
33
+ 'Замешиваю кал в тесто…',
34
+ 'Сбиваю кал в однородную массу…',
35
+ 'Размешиваю кал до просветления…',
36
+ 'Замешиваю кал из всего подряд…',
37
+ ]
38
+
39
+ function randomThinkingPhrase() {
40
+ return THINKING_PHRASES[Math.floor(Math.random() * THINKING_PHRASES.length)]
41
+ }
42
+
5
43
  export function createSpinner() {
6
44
  let spinner = null
7
45
 
@@ -15,7 +53,7 @@ export function createSpinner() {
15
53
  }
16
54
 
17
55
  return {
18
- thinking: () => start(theme.system('Думаю...')),
56
+ thinking: () => start(theme.system(randomThinkingPhrase())),
19
57
 
20
58
  toolCall: (name, args) => {
21
59
  stop()