zames_pro 2.16.0 → 2.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/input.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { theme } from './theme.js';
2
2
  import { renderMarkdown } from './markdown.js';
3
3
  import { randomThinkingPhrase, stripEllipsis } from './spinner.js';
4
+ import { translate } from './i18n.js';
4
5
  import { AttachmentStore, parseImagePaste, looksLikeFilePath, } from './attachments.js';
5
6
  // A permanent input line at the bottom of the terminal + a status/output area above it.
6
7
  //
@@ -70,7 +71,6 @@ export function expandPastes(pastes, text) {
70
71
  // We align the width to the maximum (3) so the hint doesn't shift.
71
72
  const DOTS = ['', '.', '..', '...'];
72
73
  const DOTS_PAD = ' ';
73
- const HINT = theme.dim(' · Esc — стоп');
74
74
  function safeJson(v) {
75
75
  try {
76
76
  return JSON.stringify(v);
@@ -184,6 +184,9 @@ export class LineEditor {
184
184
  onCtrlC;
185
185
  _inPaste;
186
186
  _dotTimer;
187
+ // True while the dot-animation timer is running. Lets sendPause() update
188
+ // only the base text (remaining seconds) without restarting the timer.
189
+ _animating;
187
190
  _dotPhase;
188
191
  _thinkBase;
189
192
  _wasRaw;
@@ -212,7 +215,11 @@ export class LineEditor {
212
215
  // user could type and send a message mid-operation, and it would be queued
213
216
  // and sent right after the operation, breaking the restored session.
214
217
  locked;
215
- constructor({ prompt = '> ', commands = [] } = {}) {
218
+ // Interface language for the editor's own labels (hint, answer marker,
219
+ // pause status). Everything the OPERATOR sees must be localized.
220
+ locale;
221
+ constructor({ prompt = '> ', commands = [], locale = 'ru' } = {}) {
222
+ this.locale = locale;
216
223
  this.promptStr = prompt;
217
224
  this.buf = '';
218
225
  this.cursor = 0;
@@ -226,6 +233,7 @@ export class LineEditor {
226
233
  this.onCtrlC = null;
227
234
  this._inPaste = false;
228
235
  this._dotTimer = null;
236
+ this._animating = false;
229
237
  this._dotPhase = 0;
230
238
  this._thinkBase = '';
231
239
  this._wasRaw = false;
@@ -362,6 +370,11 @@ export class LineEditor {
362
370
  this.promptStr = str;
363
371
  this._render();
364
372
  }
373
+ // Update the interface language (labels: hint, answer marker, pause).
374
+ setLocale(locale) {
375
+ this.locale = locale;
376
+ this._render();
377
+ }
365
378
  // Update slash-command descriptions (interface language change).
366
379
  setCommands(commands) {
367
380
  this.slashCommands = commands;
@@ -412,7 +425,7 @@ export class LineEditor {
412
425
  out += NL + lines.join(NL);
413
426
  const hidden = sugg.length - shown.length;
414
427
  if (hidden > 0)
415
- out += NL + theme.dim(' …ещё ' + hidden);
428
+ out += NL + theme.dim(' ' + translate(this.locale)('editor.more', { n: hidden }));
416
429
  }
417
430
  process.stdout.write(out);
418
431
  const lastRow = lay.rows.length - 1;
@@ -449,36 +462,65 @@ export class LineEditor {
449
462
  clearInterval(this._dotTimer);
450
463
  this._dotTimer = null;
451
464
  }
465
+ this._animating = false;
452
466
  }
453
467
  _dots(n) {
454
468
  // The dots are the same color as the base and of fixed width — otherwise
455
469
  // the hint on the right "jumps" when the animation phase changes.
456
470
  return theme.brown(DOTS[n] + DOTS_PAD.slice(DOTS[n].length));
457
471
  }
472
+ // Status-line hint ("Esc — stop"), localized.
473
+ _hint() {
474
+ return theme.dim(' · ' + translate(this.locale)('spinner.hint'));
475
+ }
458
476
  _startThinking() {
459
477
  if (this.pendingText) {
460
- this.setStatus(theme.prompt('✎ ') + this.pendingText + HINT);
478
+ this.setStatus(theme.prompt('✎ ') + this.pendingText + this._hint());
461
479
  return;
462
480
  }
463
- this._thinkBase = theme.brown(stripEllipsis(randomThinkingPhrase()));
481
+ this._startAnimated(randomThinkingPhrase());
482
+ }
483
+ // Animated status: a brown base text plus a growing "running" dot sequence.
484
+ // Shared by the thinking spinner and the send-pause indicator, so the pause
485
+ // is animated too (previously the pause was a static console line and the
486
+ // dots stayed frozen).
487
+ _startAnimated(baseText) {
488
+ this._thinkBase = theme.brown(stripEllipsis(baseText));
464
489
  this._dotPhase = 0;
465
- this.setStatus(this._thinkBase + this._dots(0) + HINT);
490
+ this.setStatus(this._thinkBase + this._dots(0) + this._hint());
466
491
  this._stopDots();
467
492
  this._dotTimer = setInterval(() => {
468
493
  this._dotPhase = (this._dotPhase + 1) % DOTS.length;
469
- this.setStatus(this._thinkBase + this._dots(this._dotPhase) + HINT);
494
+ this.setStatus(this._thinkBase + this._dots(this._dotPhase) + this._hint());
470
495
  }, 400);
496
+ this._animating = true;
471
497
  if (this._dotTimer.unref)
472
498
  this._dotTimer.unref();
473
499
  }
474
500
  thinking() {
475
501
  this._startThinking();
476
502
  }
503
+ // The agent is waiting out the send-interval pause before a real send.
504
+ // Show it as an ANIMATED status with the remaining seconds. The browser
505
+ // calls this repeatedly (once per second), so we update only the base text
506
+ // and keep the running dot timer — otherwise the dots would restart from
507
+ // zero on every update and look frozen on a single dot.
508
+ sendPause(seconds) {
509
+ if (this.pendingText)
510
+ return;
511
+ const label = translate(this.locale)('spinner.pause', { n: seconds });
512
+ if (this._animating && this._dotTimer) {
513
+ this._thinkBase = theme.brown(stripEllipsis(label));
514
+ this.setStatus(this._thinkBase + this._dots(this._dotPhase) + this._hint());
515
+ return;
516
+ }
517
+ this._startAnimated(label);
518
+ }
477
519
  setPending(text) {
478
520
  this.pendingText = text && String(text).length ? String(text) : null;
479
521
  if (this.pendingText) {
480
522
  this._stopDots();
481
- this.setStatus(theme.prompt('✎ ') + this.pendingText + HINT);
523
+ this.setStatus(theme.prompt('✎ ') + this.pendingText + this._hint());
482
524
  }
483
525
  else {
484
526
  this._startThinking();
@@ -500,7 +542,7 @@ export class LineEditor {
500
542
  assistant(msg) {
501
543
  this.stop();
502
544
  const rendered = renderMarkdown(msg);
503
- this.printAbove(NL + theme.assistant('● Ответ') + NL + rendered + NL + theme.dim('─'.repeat(60)));
545
+ this.printAbove(NL + theme.assistant(translate(this.locale)('editor.answer')) + NL + rendered + NL + theme.dim('─'.repeat(60)));
504
546
  }
505
547
  // Warning to the operator (e.g. the agent stopped suspiciously).
506
548
  // Printed above the input line without overwriting it.
package/dist/spinner.js CHANGED
@@ -20,6 +20,12 @@ export function createSpinner(locale = 'ru') {
20
20
  let dotTimer = null;
21
21
  let dotPhase = 0;
22
22
  let pending = null;
23
+ // Current animated base text and its dot renderer. Kept in variables (not
24
+ // captured in the interval closure) so sendPause() can update the text
25
+ // without restarting the dot animation — otherwise the dots would reset to
26
+ // zero every second and look frozen on a single dot.
27
+ let animBase = '';
28
+ let animating = false;
23
29
  // Dot animation: start from an empty string (0 dots), then grow.
24
30
  // We align the width to the maximum (3) so the hint doesn't shift.
25
31
  const DOTS = ['', '.', '..', '...'];
@@ -35,19 +41,23 @@ export function createSpinner(locale = 'ru') {
35
41
  clearInterval(dotTimer);
36
42
  dotTimer = null;
37
43
  }
44
+ animating = false;
38
45
  if (spinner)
39
46
  spinner.stop();
40
47
  };
41
48
  // Hint in the status line: while the agent works the terminal is live,
42
49
  // so you can type the next message. Without it this is not obvious.
43
50
  const HINT = theme.dim(' · ' + translate(locale)('spinner.hint'));
44
- // Start the animated status: brown text + "running" dots.
45
- const startThinking = () => {
46
- const base = theme.brown(stripEllipsis(randomThinkingPhrase(locale)));
51
+ // Run the animated status line: a brown base text plus a growing/shrinking
52
+ // "running" dot sequence. Shared by the thinking spinner and the send-pause
53
+ // indicator, so the pause is animated too (before, it was a static line and
54
+ // the dots did not move — the operator saw a frozen spinner).
55
+ const startAnimated = (baseText) => {
56
+ animBase = theme.brown(stripEllipsis(baseText));
47
57
  // The dots are the same color as the base and of fixed width — otherwise
48
58
  // the hint on the right "jumps" when the animation phase changes.
49
59
  const dots = (n) => theme.brown(DOTS[n] + DOTS_PAD.slice(DOTS[n].length));
50
- start(base + dots(0) + HINT);
60
+ start(animBase + dots(0) + HINT);
51
61
  dotPhase = 0;
52
62
  if (dotTimer)
53
63
  clearInterval(dotTimer);
@@ -55,11 +65,16 @@ export function createSpinner(locale = 'ru') {
55
65
  if (!spinner)
56
66
  return;
57
67
  dotPhase = (dotPhase + 1) % DOTS.length;
58
- spinner.text = base + dots(dotPhase) + HINT;
68
+ spinner.text = animBase + dots(dotPhase) + HINT;
59
69
  }, 400);
70
+ animating = true;
60
71
  if (dotTimer.unref)
61
72
  dotTimer.unref();
62
73
  };
74
+ // Start the animated status: brown text + "running" dots.
75
+ const startThinking = () => {
76
+ startAnimated(randomThinkingPhrase(locale));
77
+ };
63
78
  // Show the typed but not yet sent text instead of the spinner.
64
79
  // Lets you type a message right while the agent is working.
65
80
  const showPending = () => {
@@ -67,6 +82,7 @@ export function createSpinner(locale = 'ru') {
67
82
  clearInterval(dotTimer);
68
83
  dotTimer = null;
69
84
  }
85
+ animating = false;
70
86
  if (!spinner)
71
87
  spinner = ora('');
72
88
  spinner.start();
@@ -80,6 +96,23 @@ export function createSpinner(locale = 'ru') {
80
96
  }
81
97
  startThinking();
82
98
  },
99
+ // The agent is waiting out the send-interval pause before a real send.
100
+ // Show it as an ANIMATED status line with the remaining seconds, so the
101
+ // spinner keeps moving during the pause (previously a static console line
102
+ // was printed and the dots stayed frozen).
103
+ sendPause: (seconds) => {
104
+ if (pending)
105
+ return;
106
+ const label = translate(locale)('spinner.pause', { n: seconds });
107
+ // Update only the base text while the dot animation is already running,
108
+ // so the countdown refreshes without resetting the dots.
109
+ if (animating && dotTimer && spinner) {
110
+ animBase = theme.brown(stripEllipsis(label));
111
+ spinner.text = animBase + theme.brown(DOTS[dotPhase] + DOTS_PAD.slice(DOTS[dotPhase].length)) + HINT;
112
+ return;
113
+ }
114
+ startAnimated(label);
115
+ },
83
116
  // The text the user types while the agent is working.
84
117
  // Empty string / null — restore the regular spinner.
85
118
  setPending: (text) => {
@@ -106,7 +139,7 @@ export function createSpinner(locale = 'ru') {
106
139
  const rendered = renderMarkdown(msg);
107
140
  // Model answer marker: helps visually separate it from the user's input
108
141
  // (which is highlighted by the prompt with a golden arrow).
109
- console.log(NL + theme.assistant('● Ответ') + NL);
142
+ console.log(NL + theme.assistant(translate(locale)('editor.answer')) + NL);
110
143
  console.log(rendered);
111
144
  console.log(theme.dim('─'.repeat(60)) + NL);
112
145
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.16.0",
3
+ "version": "2.18.0",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,7 +15,7 @@
15
15
  ],
16
16
  "scripts": {
17
17
  "start": "node ./dist/index.js",
18
- "dev": "tsx ./src/index.ts --dev",
18
+ "dev": "ZAMES_NET_DEBUG=1 tsx ./src/index.ts --dev",
19
19
  "build": "tsc -p tsconfig.build.json",
20
20
  "typecheck": "tsc --noEmit",
21
21
  "prepublishOnly": "npm run build",