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 +22 -37
- package/logo.png +0 -0
- package/package.json +1 -1
- package/src/index.js +203 -12
- package/src/spinner.js +39 -1
package/README.md
CHANGED
|
@@ -2,69 +2,54 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
12
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
+
```bash
|
|
26
27
|
zames
|
|
27
|
-
|
|
28
|
+
```
|
|
28
29
|
|
|
29
|
-
|
|
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
|
-
|
|
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
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
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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()
|