zames_pro 2.3.0 → 2.4.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/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).
@@ -141,7 +143,10 @@ export class LineEditor {
141
143
  history;
142
144
  _histIndex;
143
145
  _histDraft;
144
- constructor({ prompt = '> ' } = {}) {
146
+ // Подсказки slash-команд (показываются при вводе «/»).
147
+ slashCommands;
148
+ _suggestCount;
149
+ constructor({ prompt = '> ', commands = [] } = {}) {
145
150
  this.promptStr = prompt;
146
151
  this.buf = '';
147
152
  this.cursor = 0;
@@ -162,6 +167,42 @@ export class LineEditor {
162
167
  this.history = [];
163
168
  this._histIndex = 0;
164
169
  this._histDraft = '';
170
+ this.slashCommands = commands;
171
+ this._suggestCount = 0;
172
+ }
173
+ // Список подсказок для текущего ввода. Показываем, только когда строка
174
+ // начинается с «/» и это одно слово (без пробелов/переводов строк) — как
175
+ // автодополнение команд в Claude Code.
176
+ _suggestions() {
177
+ const b = this.buf;
178
+ if (!b.startsWith('/'))
179
+ return [];
180
+ if (b.includes(NL) || b.includes(' '))
181
+ return [];
182
+ const q = b.toLowerCase();
183
+ return this.slashCommands.filter((c) => c.name.toLowerCase().startsWith(q));
184
+ }
185
+ // Tab: дополнить команду до общего префикса; если совпадение одно —
186
+ // подставить его целиком и добавить пробел.
187
+ _completeCommand() {
188
+ const sugg = this._suggestions();
189
+ if (!sugg.length)
190
+ return;
191
+ if (sugg.length === 1) {
192
+ this.buf = sugg[0].name + ' ';
193
+ this.cursor = Array.from(this.buf).length;
194
+ return;
195
+ }
196
+ let prefix = sugg[0].name;
197
+ for (const c of sugg) {
198
+ while (!c.name.toLowerCase().startsWith(prefix.toLowerCase())) {
199
+ prefix = prefix.slice(0, -1);
200
+ }
201
+ }
202
+ if (prefix.length > this.buf.length) {
203
+ this.buf = prefix;
204
+ this.cursor = Array.from(this.buf).length;
205
+ }
165
206
  }
166
207
  start() {
167
208
  const stdin = process.stdin;
@@ -214,9 +255,29 @@ export class LineEditor {
214
255
  }
215
256
  const lay = layoutInput(this.promptStr, this.buf, this.cursor, cols);
216
257
  out += lay.rows.map((r) => r.prefix + r.text).join(NL);
258
+ // Подсказки slash-команд рисуем НИЖЕ строки ввода. Курсор потом
259
+ // возвращаем вверх, на строку ввода, поэтому cursorRowFromTop не меняется.
260
+ const sugg = this._suggestions();
261
+ const shown = sugg.slice(0, 8);
262
+ this._suggestCount = shown.length;
263
+ if (shown.length) {
264
+ const maxName = Math.max(...shown.map((c) => c.name.length));
265
+ const lines = shown.map((c) => {
266
+ const name = theme.prompt(c.name.padEnd(maxName));
267
+ const desc = theme.dim(' ' + c.description);
268
+ return ' ' + name + desc;
269
+ });
270
+ out += NL + lines.join(NL);
271
+ const hidden = sugg.length - shown.length;
272
+ if (hidden > 0)
273
+ out += NL + theme.dim(' …ещё ' + hidden);
274
+ }
217
275
  process.stdout.write(out);
218
276
  const lastRow = lay.rows.length - 1;
219
- const up = lastRow - lay.cursorRow;
277
+ // Курсор вверх: сначала на строку ввода внутри lay, затем ещё на
278
+ // строки подсказок (если они есть) — курсор должен стоять на вводе.
279
+ const linesBelow = shown.length ? shown.length + (sugg.length > shown.length ? 1 : 0) : 0;
280
+ const up = lastRow - lay.cursorRow + linesBelow;
220
281
  if (up > 0)
221
282
  process.stdout.write(ESC + '[' + up + 'A');
222
283
  process.stdout.write(CR);
@@ -550,6 +611,11 @@ export class LineEditor {
550
611
  this._render();
551
612
  continue;
552
613
  }
614
+ if (code === 9) {
615
+ this._completeCommand();
616
+ this._render();
617
+ continue;
618
+ }
553
619
  if (code === 21) {
554
620
  this.buf = '';
555
621
  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.0",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {