zames_pro 2.3.0 → 2.4.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/dist/index.js CHANGED
@@ -183,6 +183,7 @@ ${theme.bold('Пока агент работает:')}
183
183
  печать + Enter поставить сообщение в очередь (уйдёт после текущей задачи)
184
184
  ↑ / ↓ история введённых сообщений
185
185
  Ctrl+← / Ctrl+→ перемещение по словам
186
+ / + Tab подсказка и автодополнение slash-команд
186
187
  Ctrl+J новая строка (Shift+Enter в терминалах с поддержкой)
187
188
  Esc, Ctrl+C прервать текущую генерацию
188
189
 
@@ -229,6 +230,34 @@ ${theme.bold('Файлы:')}
229
230
  function dirLabel(p) {
230
231
  return path.basename(p) || p;
231
232
  }
233
+ // Список slash-команд для автодополнения при вводе «/» (Tab — дополнить).
234
+ const SLASH_COMMANDS = [
235
+ { name: '/help', description: 'справка по командам и опциям' },
236
+ { name: '/new', description: 'новый чат (сброс контекста)' },
237
+ { name: '/clear', description: 'то же, что /new' },
238
+ { name: '/sessions', description: 'список сохранённых сессий' },
239
+ { name: '/chats', description: 'последние чаты DeepSeek' },
240
+ { name: '/resume', description: 'открыть чат №n из /chats' },
241
+ { name: '/resume-id', description: 'восстановить сессию по полному id' },
242
+ { name: '/chat', description: 'показать текущий chat id' },
243
+ { name: '/cd', description: 'сменить рабочую директорию' },
244
+ { name: '/pwd', description: 'текущая рабочая директория' },
245
+ { name: '/status', description: 'состояние сессии' },
246
+ { name: '/reload', description: 'перечитать модули логики без перезапуска' },
247
+ { name: '/undo', description: 'откатить последнюю запись/правку' },
248
+ { name: '/undo-list', description: 'список того, что можно откатить' },
249
+ { name: '/transcript', description: 'путь к файлу транскрипта' },
250
+ { name: '/config', description: 'показать текущий конфиг' },
251
+ { name: '/debug-dom', description: 'сохранить HTML страницы (отладка)' },
252
+ { name: '/self-review', description: 'снапшот src/ и запуск ревью' },
253
+ { name: '/self-fix', description: 'продолжить в существующем снапшоте' },
254
+ { name: '/self-done', description: 'выйти из режима ревью' },
255
+ { name: '/self-list', description: 'список снапшотов' },
256
+ { name: '/self-diff', description: 'различия текущего src/ и снапшота' },
257
+ { name: '/self-apply', description: 'применить снапшот к src/ (с бэкапом)' },
258
+ { name: '/exit', description: 'выход' },
259
+ { name: '/quit', description: 'выход' },
260
+ ];
232
261
  // Временные файлы агента (одноразовые скрипты и т.п.) складываем в
233
262
  // <проект>/tmp — эта папка в .gitignore и очищается при каждом запуске.
234
263
  const TMP_DIR = path.join(__dirname, '..', 'tmp');
@@ -842,7 +871,10 @@ async function main() {
842
871
  return theme.prompt('❯ ') + tail + theme.dim(' › ');
843
872
  };
844
873
  if (process.stdin.isTTY && process.stdout.isTTY) {
845
- const ed = new LineEditor({ prompt: buildPrompt() });
874
+ const ed = new LineEditor({
875
+ prompt: buildPrompt(),
876
+ commands: SLASH_COMMANDS,
877
+ });
846
878
  editor = ed;
847
879
  ed.onSubmit = (text) => {
848
880
  pendingQueue.push(text);
package/dist/input.js CHANGED
@@ -14,7 +14,9 @@ import { randomThinkingPhrase, stripEllipsis } from './spinner.js';
14
14
  // * перемещение по словам: Ctrl+←/→ (и Alt+B/Alt+F), удаление слова
15
15
  // Ctrl+W; Ctrl+K — удалить до конца строки;
16
16
  // * история введённых сообщений: ↑/↓ на краях ввода (внутри многострочного
17
- // ввода стрелки двигают по строкам, как в обычном терминале).
17
+ // ввода стрелки двигают по строкам, как в обычном терминале);
18
+ // * подсказки slash-команд: при вводе «/» под строкой показываются
19
+ // подходящие команды с описанием, Tab — дополнить.
18
20
  //
19
21
  // Все управляющие символы собираются из кодов, чтобы в файле не было
20
22
  // «сырых» ESC/CR/LF в строковых литералах (см. AGENTS.md).
@@ -36,6 +38,14 @@ function safeJson(v) {
36
38
  return String(v);
37
39
  }
38
40
  }
41
+ // Сколько визуальных строк занимает текст при данной ширине терминала
42
+ // (с учётом переноса). Нужно, чтобы курсор/стирание блока не сбивались,
43
+ // когда статус длиннее ширины терминала и переносится на 2+ строки.
44
+ export function visRows(s, cols) {
45
+ const width = Math.max(1, Number(cols) || 80);
46
+ const len = visLen(s);
47
+ return Math.max(1, Math.ceil(len / width));
48
+ }
39
49
  // Видимая длина строки без ANSI-последовательностей.
40
50
  export function visLen(s) {
41
51
  s = String(s);
@@ -141,7 +151,10 @@ export class LineEditor {
141
151
  history;
142
152
  _histIndex;
143
153
  _histDraft;
144
- constructor({ prompt = '> ' } = {}) {
154
+ // Подсказки slash-команд (показываются при вводе «/»).
155
+ slashCommands;
156
+ _suggestCount;
157
+ constructor({ prompt = '> ', commands = [] } = {}) {
145
158
  this.promptStr = prompt;
146
159
  this.buf = '';
147
160
  this.cursor = 0;
@@ -162,6 +175,42 @@ export class LineEditor {
162
175
  this.history = [];
163
176
  this._histIndex = 0;
164
177
  this._histDraft = '';
178
+ this.slashCommands = commands;
179
+ this._suggestCount = 0;
180
+ }
181
+ // Список подсказок для текущего ввода. Показываем, только когда строка
182
+ // начинается с «/» и это одно слово (без пробелов/переводов строк) — как
183
+ // автодополнение команд в Claude Code.
184
+ _suggestions() {
185
+ const b = this.buf;
186
+ if (!b.startsWith('/'))
187
+ return [];
188
+ if (b.includes(NL) || b.includes(' '))
189
+ return [];
190
+ const q = b.toLowerCase();
191
+ return this.slashCommands.filter((c) => c.name.toLowerCase().startsWith(q));
192
+ }
193
+ // Tab: дополнить команду до общего префикса; если совпадение одно —
194
+ // подставить его целиком и добавить пробел.
195
+ _completeCommand() {
196
+ const sugg = this._suggestions();
197
+ if (!sugg.length)
198
+ return;
199
+ if (sugg.length === 1) {
200
+ this.buf = sugg[0].name + ' ';
201
+ this.cursor = Array.from(this.buf).length;
202
+ return;
203
+ }
204
+ let prefix = sugg[0].name;
205
+ for (const c of sugg) {
206
+ while (!c.name.toLowerCase().startsWith(prefix.toLowerCase())) {
207
+ prefix = prefix.slice(0, -1);
208
+ }
209
+ }
210
+ if (prefix.length > this.buf.length) {
211
+ this.buf = prefix;
212
+ this.cursor = Array.from(this.buf).length;
213
+ }
165
214
  }
166
215
  start() {
167
216
  const stdin = process.stdin;
@@ -210,13 +259,35 @@ export class LineEditor {
210
259
  let top = 0;
211
260
  if (this.statusText) {
212
261
  out += this.statusText + NL;
213
- top = 1;
262
+ // Статус может переноситься на несколько строк — учитываем это,
263
+ // иначе стирание блока промахивается и статусы «стопкой» копятся.
264
+ top = visRows(this.statusText, cols);
214
265
  }
215
266
  const lay = layoutInput(this.promptStr, this.buf, this.cursor, cols);
216
267
  out += lay.rows.map((r) => r.prefix + r.text).join(NL);
268
+ // Подсказки slash-команд рисуем НИЖЕ строки ввода. Курсор потом
269
+ // возвращаем вверх, на строку ввода, поэтому cursorRowFromTop не меняется.
270
+ const sugg = this._suggestions();
271
+ const shown = sugg.slice(0, 8);
272
+ this._suggestCount = shown.length;
273
+ if (shown.length) {
274
+ const maxName = Math.max(...shown.map((c) => c.name.length));
275
+ const lines = shown.map((c) => {
276
+ const name = theme.prompt(c.name.padEnd(maxName));
277
+ const desc = theme.dim(' ' + c.description);
278
+ return ' ' + name + desc;
279
+ });
280
+ out += NL + lines.join(NL);
281
+ const hidden = sugg.length - shown.length;
282
+ if (hidden > 0)
283
+ out += NL + theme.dim(' …ещё ' + hidden);
284
+ }
217
285
  process.stdout.write(out);
218
286
  const lastRow = lay.rows.length - 1;
219
- const up = lastRow - lay.cursorRow;
287
+ // Курсор вверх: сначала на строку ввода внутри lay, затем ещё на
288
+ // строки подсказок (если они есть) — курсор должен стоять на вводе.
289
+ const linesBelow = shown.length ? shown.length + (sugg.length > shown.length ? 1 : 0) : 0;
290
+ const up = lastRow - lay.cursorRow + linesBelow;
220
291
  if (up > 0)
221
292
  process.stdout.write(ESC + '[' + up + 'A');
222
293
  process.stdout.write(CR);
@@ -550,6 +621,11 @@ export class LineEditor {
550
621
  this._render();
551
622
  continue;
552
623
  }
624
+ if (code === 9) {
625
+ this._completeCommand();
626
+ this._render();
627
+ continue;
628
+ }
553
629
  if (code === 21) {
554
630
  this.buf = '';
555
631
  this.cursor = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {