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 +33 -1
- package/dist/input.js +80 -4
- package/package.json +1 -1
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({
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|