@rind-ai/cli 0.4.1 → 0.6.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.
Files changed (49) hide show
  1. package/bin/rind.js +5 -5
  2. package/lib/assistant-renderer.js +179 -265
  3. package/lib/choice-menu-state.js +46 -46
  4. package/lib/cli-input-actions.js +548 -0
  5. package/lib/cli-output-controller.js +460 -0
  6. package/lib/cli-runtime-controller.js +350 -0
  7. package/lib/cli-state-store.js +32 -0
  8. package/lib/cli-state.js +41 -0
  9. package/lib/command-controller.js +159 -126
  10. package/lib/compact-context-state.js +22 -22
  11. package/lib/components/assistant-message.js +169 -0
  12. package/lib/components/composer-area.js +25 -0
  13. package/lib/components/dynamic-block.js +20 -0
  14. package/lib/components/monitor-stack.js +35 -0
  15. package/lib/components/text-block.js +47 -0
  16. package/lib/components/tool-block.js +122 -0
  17. package/lib/composer-terminal.js +224 -203
  18. package/lib/event-controller.js +243 -242
  19. package/lib/frontend-cli-implementation.js +656 -1111
  20. package/lib/input-controller.js +75 -94
  21. package/lib/input-errors.js +3 -3
  22. package/lib/interrupt-state.js +9 -9
  23. package/lib/line-editor.js +541 -541
  24. package/lib/local-slash-commands.js +217 -0
  25. package/lib/markdown-lines.js +103 -0
  26. package/lib/model-menu-state.js +50 -50
  27. package/lib/one-shot-progress.js +145 -0
  28. package/lib/one-shot.js +228 -0
  29. package/lib/question-menu-state.js +61 -0
  30. package/lib/rendering.js +1295 -1037
  31. package/lib/runtime-client.js +241 -193
  32. package/lib/runtime-env.js +21 -21
  33. package/lib/runtime-protocol.js +122 -15
  34. package/lib/slash-command-mode.js +16 -27
  35. package/lib/slash-menu-state.js +59 -59
  36. package/lib/{background-controller.js → task-monitor-controller.js} +411 -289
  37. package/lib/terminal-key.js +97 -97
  38. package/lib/text-width.js +335 -151
  39. package/lib/theme-menu-state.js +31 -0
  40. package/lib/theme.js +134 -0
  41. package/lib/tool-display.js +680 -0
  42. package/lib/tui/component.js +55 -0
  43. package/lib/tui/cursor.js +29 -0
  44. package/lib/tui/input-buffer.js +172 -0
  45. package/lib/tui/tui.js +591 -0
  46. package/lib/turn-controller.js +68 -78
  47. package/package.json +28 -28
  48. package/lib/assistant-stream-buffer.js +0 -25
  49. package/lib/terminal-ui.js +0 -581
@@ -1,581 +0,0 @@
1
- import { textWidth } from "./text-width.js";
2
-
3
- const ESC = "\x1b";
4
- const PASTE_START = "\x1b[200~";
5
- const PASTE_END = "\x1b[201~";
6
- const SYNCHRONIZED_OUTPUT_START = "\x1b[?2026h";
7
- const SYNCHRONIZED_OUTPUT_END = "\x1b[?2026l";
8
- const LINE_RESET = "\x1b[0m";
9
- const DEFAULT_COLUMNS = 80;
10
- const DEFAULT_ROWS = 24;
11
- const DEFAULT_INPUT_TIMEOUT_MS = 10;
12
- const DEFAULT_RENDER_INTERVAL_MS = 16;
13
-
14
- export function createTerminalUI(options = {}) {
15
- const input = options.input || process.stdin;
16
- const output = options.output || process.stdout;
17
- const renderFrame = typeof options.render === "function" ? options.render : () => [];
18
- const schedule = options.setTimeout || setTimeout;
19
- const cancelSchedule = options.clearTimeout || clearTimeout;
20
- const now = options.now || Date.now;
21
-
22
- let started = false;
23
- let rawModeBeforeStart = false;
24
- let inputHandler = () => {};
25
- let pasteHandler = () => {};
26
- let resizeHandler;
27
- let inputBuffer = "";
28
- let inputTimer = null;
29
- let pasteMode = false;
30
- let pasteBuffer = "";
31
- let renderRequested = false;
32
- let renderForceRequested = false;
33
- let renderTimer = null;
34
- let renderMicrotaskQueued = false;
35
- let lastRenderAt = 0;
36
- let hasRendered = false;
37
- let suspended = 0;
38
- let previousLines = [];
39
- let previousWidth = 0;
40
- let previousHeight = 0;
41
- let cursor = { row: 0, column: 0 };
42
- let rendered = false;
43
-
44
- function columns() {
45
- return finitePositive(output.columns, DEFAULT_COLUMNS);
46
- }
47
-
48
- function rows() {
49
- return finitePositive(output.rows, DEFAULT_ROWS);
50
- }
51
-
52
- function write(value) {
53
- if (value) {
54
- output.write(value);
55
- }
56
- }
57
-
58
- function start(startOptions = {}) {
59
- if (started) {
60
- return;
61
- }
62
- started = true;
63
- if (typeof startOptions.onInput === "function") {
64
- inputHandler = startOptions.onInput;
65
- }
66
- if (typeof startOptions.onPaste === "function") {
67
- pasteHandler = startOptions.onPaste;
68
- }
69
-
70
- rawModeBeforeStart = Boolean(input.isRaw);
71
- if (typeof input.setRawMode === "function") {
72
- input.setRawMode(true);
73
- }
74
- input.setEncoding("utf8");
75
- input.resume();
76
- input.on("data", handleInputData);
77
-
78
- resizeHandler = () => {
79
- requestRender(true);
80
- };
81
- output.on("resize", resizeHandler);
82
-
83
- write("\x1b[?2004h");
84
- requestRender();
85
- }
86
-
87
- function stop() {
88
- if (!started) {
89
- return;
90
- }
91
- started = false;
92
- clearInputTimer();
93
- clearRenderTimer();
94
- clearFrame();
95
- input.off("data", handleInputData);
96
- if (resizeHandler) {
97
- output.off("resize", resizeHandler);
98
- resizeHandler = undefined;
99
- }
100
- input.pause();
101
- if (typeof input.setRawMode === "function") {
102
- input.setRawMode(rawModeBeforeStart);
103
- }
104
- write("\x1b[?2004l");
105
- clearInputBuffer();
106
- }
107
-
108
- function handleInputData(data) {
109
- feedInput(data);
110
- }
111
-
112
- function feedInput(data) {
113
- const value = Buffer.isBuffer(data) ? data.toString("utf8") : String(data || "");
114
- if (!value && !inputBuffer) {
115
- return;
116
- }
117
- inputBuffer += value;
118
- processInputBuffer();
119
- }
120
-
121
- function flushInput() {
122
- clearInputTimer();
123
- if (pasteMode) {
124
- return;
125
- }
126
- if (!inputBuffer) {
127
- return;
128
- }
129
- const value = inputBuffer;
130
- inputBuffer = "";
131
- emitInput(value);
132
- }
133
-
134
- function clearInputBuffer() {
135
- clearInputTimer();
136
- inputBuffer = "";
137
- pasteMode = false;
138
- pasteBuffer = "";
139
- }
140
-
141
- function processInputBuffer() {
142
- if (pasteMode) {
143
- pasteBuffer += inputBuffer;
144
- inputBuffer = "";
145
- const endIndex = pasteBuffer.indexOf(PASTE_END);
146
- if (endIndex === -1) {
147
- return;
148
- }
149
- const content = pasteBuffer.slice(0, endIndex);
150
- const remaining = pasteBuffer.slice(endIndex + PASTE_END.length);
151
- pasteMode = false;
152
- pasteBuffer = "";
153
- pasteHandler(content);
154
- if (remaining) {
155
- feedInput(remaining);
156
- }
157
- return;
158
- }
159
-
160
- const pasteIndex = inputBuffer.indexOf(PASTE_START);
161
- if (pasteIndex !== -1) {
162
- const buffered = inputBuffer;
163
- const beforePaste = buffered.slice(0, pasteIndex);
164
- if (beforePaste) {
165
- emitSequences(beforePaste);
166
- }
167
- inputBuffer = buffered.slice(pasteIndex + PASTE_START.length);
168
- pasteMode = true;
169
- pasteBuffer = inputBuffer;
170
- inputBuffer = "";
171
- const endIndex = pasteBuffer.indexOf(PASTE_END);
172
- if (endIndex !== -1) {
173
- const content = pasteBuffer.slice(0, endIndex);
174
- const remaining = pasteBuffer.slice(endIndex + PASTE_END.length);
175
- pasteMode = false;
176
- pasteBuffer = "";
177
- pasteHandler(content);
178
- if (remaining) {
179
- feedInput(remaining);
180
- }
181
- }
182
- return;
183
- }
184
-
185
- emitSequences(inputBuffer);
186
- }
187
-
188
- function emitSequences(value) {
189
- const parsed = splitSequences(value);
190
- inputBuffer = parsed.remainder;
191
- for (const sequence of parsed.sequences) {
192
- emitInput(sequence);
193
- }
194
- if (inputBuffer) {
195
- clearInputTimer();
196
- inputTimer = schedule(() => {
197
- inputTimer = null;
198
- flushInput();
199
- }, DEFAULT_INPUT_TIMEOUT_MS);
200
- } else {
201
- clearInputTimer();
202
- }
203
- }
204
-
205
- function emitInput(sequence) {
206
- if (sequence) {
207
- inputHandler(sequence);
208
- }
209
- }
210
-
211
- function clearInputTimer() {
212
- if (inputTimer === null) {
213
- return;
214
- }
215
- cancelSchedule(inputTimer);
216
- inputTimer = null;
217
- }
218
-
219
- function requestRender(force = false) {
220
- const immediate = force === true;
221
- renderRequested = true;
222
- renderForceRequested ||= immediate;
223
- if (immediate) {
224
- clearRenderTimer();
225
- queueRenderMicrotask(true);
226
- return;
227
- }
228
- if (renderMicrotaskQueued || renderTimer !== null) {
229
- return;
230
- }
231
- queueRenderMicrotask(false);
232
- }
233
-
234
- function clearRenderTimer() {
235
- if (renderTimer === null) {
236
- return;
237
- }
238
- cancelSchedule(renderTimer);
239
- renderTimer = null;
240
- }
241
-
242
- function queueRenderMicrotask(force) {
243
- if (renderMicrotaskQueued) {
244
- return;
245
- }
246
- renderMicrotaskQueued = true;
247
- queueMicrotask(() => {
248
- renderMicrotaskQueued = false;
249
- const forceRender = renderForceRequested || force;
250
- renderForceRequested = false;
251
- scheduleRender(forceRender);
252
- });
253
- }
254
-
255
- function scheduleRender(force) {
256
- if (!renderRequested || suspended > 0 || !started) {
257
- return;
258
- }
259
- if (force) {
260
- renderTimer = schedule(() => {
261
- renderTimer = null;
262
- flushRender();
263
- }, 0);
264
- return;
265
- }
266
- const elapsed = hasRendered ? now() - lastRenderAt : DEFAULT_RENDER_INTERVAL_MS;
267
- const delay = Math.max(0, DEFAULT_RENDER_INTERVAL_MS - elapsed);
268
- renderTimer = schedule(() => {
269
- renderTimer = null;
270
- flushRender();
271
- }, delay);
272
- }
273
-
274
- function flushRender() {
275
- if (!renderRequested || suspended > 0 || !started) {
276
- return;
277
- }
278
- renderRequested = false;
279
- lastRenderAt = now();
280
- hasRendered = true;
281
- renderFrameNow();
282
- if (renderRequested) {
283
- queueRenderMicrotask(false);
284
- }
285
- }
286
-
287
- function renderFrameNow() {
288
- const width = columns();
289
- const height = rows();
290
- const frame = fitFrame(normalizeFrame(renderFrame(width, height), width), height);
291
- if (!frame.lines.length) {
292
- if (rendered) {
293
- clearFrame();
294
- }
295
- previousLines = [];
296
- previousWidth = frame.width;
297
- previousHeight = height;
298
- cursor = frame.cursor;
299
- return;
300
- }
301
- if (!rendered || frame.width !== previousWidth || height !== previousHeight) {
302
- if (rendered) {
303
- clearFrame();
304
- }
305
- renderFull(frame);
306
- return;
307
- }
308
- renderDiff(frame);
309
- }
310
-
311
- function renderFull(frame) {
312
- const lines = frame.lines;
313
- let buffer = SYNCHRONIZED_OUTPUT_START;
314
- for (let index = 0; index < lines.length; index += 1) {
315
- if (index > 0) {
316
- buffer += "\r\n";
317
- }
318
- buffer += "\x1b[2K";
319
- buffer += lines[index];
320
- buffer += LINE_RESET;
321
- }
322
- buffer += cursorSequence(frame.cursor, lines.length - 1, lines.length ? visibleLineWidth(lines.at(-1)) : 0);
323
- buffer += SYNCHRONIZED_OUTPUT_END;
324
- write(buffer);
325
- previousLines = lines;
326
- previousWidth = frame.width;
327
- previousHeight = rows();
328
- cursor = frame.cursor;
329
- rendered = true;
330
- }
331
-
332
- function renderDiff(frame) {
333
- const nextLines = frame.lines;
334
- if (previousLines.length > nextLines.length) {
335
- clearFrame();
336
- renderFull(frame);
337
- return;
338
- }
339
- const appendedLines = nextLines.length > previousLines.length;
340
- let firstChanged = -1;
341
- let lastChanged = -1;
342
- const lineCount = Math.max(previousLines.length, nextLines.length);
343
- for (let index = 0; index < lineCount; index += 1) {
344
- if ((previousLines[index] || "") !== (nextLines[index] || "")) {
345
- firstChanged = firstChanged === -1 ? index : firstChanged;
346
- lastChanged = index;
347
- }
348
- }
349
- if (appendedLines) {
350
- firstChanged = firstChanged === -1 ? previousLines.length : firstChanged;
351
- lastChanged = nextLines.length - 1;
352
- }
353
- if (firstChanged === -1 && cursor.row === frame.cursor.row && cursor.column === frame.cursor.column) {
354
- return;
355
- }
356
-
357
- let buffer = SYNCHRONIZED_OUTPUT_START;
358
- if (firstChanged !== -1) {
359
- const appendStart = appendedLines && firstChanged === previousLines.length && firstChanged > 0;
360
- const firstRenderRow = appendStart ? firstChanged - 1 : firstChanged;
361
- buffer += moveRows(firstRenderRow - cursor.row);
362
- buffer += appendStart ? "\r\n" : "\r";
363
- const renderEnd = Math.min(lastChanged, nextLines.length - 1);
364
- for (let index = firstChanged; index <= renderEnd; index += 1) {
365
- if (index > firstChanged) {
366
- buffer += "\r\n";
367
- }
368
- buffer += "\x1b[2K";
369
- buffer += nextLines[index] || "";
370
- buffer += LINE_RESET;
371
- }
372
- buffer += cursorSequence(frame.cursor, Math.max(0, renderEnd), visibleLineWidth(nextLines[renderEnd] || ""));
373
- buffer += SYNCHRONIZED_OUTPUT_END;
374
- write(buffer);
375
- } else {
376
- buffer += moveRows(frame.cursor.row - cursor.row);
377
- buffer += `\x1b[${frame.cursor.column + 1}G`;
378
- buffer += SYNCHRONIZED_OUTPUT_END;
379
- write(buffer);
380
- }
381
- previousLines = nextLines;
382
- cursor = frame.cursor;
383
- }
384
-
385
- function cursorSequence(target, currentRow, currentColumn) {
386
- const rowDelta = target.row - currentRow;
387
- const columnDelta = target.column - currentColumn;
388
- let buffer = moveRows(rowDelta);
389
- if (columnDelta !== 0 || rowDelta !== 0) {
390
- buffer += `\x1b[${target.column + 1}G`;
391
- }
392
- return buffer;
393
- }
394
-
395
- function moveRows(delta) {
396
- if (delta > 0) {
397
- return `\x1b[${delta}B`;
398
- }
399
- if (delta < 0) {
400
- return `\x1b[${-delta}A`;
401
- }
402
- return "";
403
- }
404
-
405
- function clearFrame() {
406
- if (!rendered) {
407
- return;
408
- }
409
- let buffer = SYNCHRONIZED_OUTPUT_START;
410
- buffer += moveRows(-cursor.row);
411
- buffer += "\r\x1b[J";
412
- buffer += SYNCHRONIZED_OUTPUT_END;
413
- write(buffer);
414
- previousLines = [];
415
- previousWidth = 0;
416
- previousHeight = 0;
417
- cursor = { row: 0, column: 0 };
418
- rendered = false;
419
- }
420
-
421
- function suspend() {
422
- suspended += 1;
423
- if (suspended === 1) {
424
- clearFrame();
425
- }
426
- let resumed = false;
427
- return () => {
428
- if (resumed) {
429
- return;
430
- }
431
- resumed = true;
432
- suspended = Math.max(0, suspended - 1);
433
- if (suspended === 0 && renderRequested) {
434
- queueRenderMicrotask(false);
435
- }
436
- };
437
- }
438
-
439
- function withSuspended(action, options = {}) {
440
- const resume = suspend();
441
- try {
442
- return action();
443
- } finally {
444
- resume();
445
- if (options.render !== false) {
446
- requestRender();
447
- }
448
- }
449
- }
450
-
451
- return {
452
- start,
453
- stop,
454
- requestRender,
455
- withSuspended,
456
- };
457
- }
458
-
459
- function splitSequences(value) {
460
- const sequences = [];
461
- let position = 0;
462
- while (position < value.length) {
463
- if (value[position] !== ESC) {
464
- const codePoint = value.codePointAt(position);
465
- const character = String.fromCodePoint(codePoint);
466
- sequences.push(character);
467
- position += character.length;
468
- continue;
469
- }
470
-
471
- const end = findEscapeEnd(value, position);
472
- if (end === -1) {
473
- return { sequences, remainder: value.slice(position) };
474
- }
475
- sequences.push(value.slice(position, end));
476
- position = end;
477
- }
478
- return { sequences, remainder: "" };
479
- }
480
-
481
- function findEscapeEnd(value, start) {
482
- if (start + 1 >= value.length) {
483
- return -1;
484
- }
485
- const type = value[start + 1];
486
- if (type === "[") {
487
- for (let index = start + 2; index < value.length; index += 1) {
488
- const code = value.charCodeAt(index);
489
- if (code >= 0x40 && code <= 0x7e) {
490
- return index + 1;
491
- }
492
- }
493
- return -1;
494
- }
495
- if (type === "O") {
496
- return start + 3 <= value.length ? start + 3 : -1;
497
- }
498
- if (type === "]" || type === "P" || type === "_") {
499
- const bell = value.indexOf("\x07", start + 2);
500
- const stringTerminator = value.indexOf(`${ESC}\\`, start + 2);
501
- if (bell === -1 && stringTerminator === -1) {
502
- return -1;
503
- }
504
- if (bell !== -1 && (stringTerminator === -1 || bell < stringTerminator)) {
505
- return bell + 1;
506
- }
507
- return stringTerminator + 2;
508
- }
509
- return start + 1 + String.fromCodePoint(value.codePointAt(start + 1)).length;
510
- }
511
-
512
- function normalizeFrame(frame, fallbackWidth = DEFAULT_COLUMNS) {
513
- const lines = Array.isArray(frame?.lines)
514
- ? frame.lines.map((line) => String(line ?? ""))
515
- : [];
516
- const width = fallbackWidth;
517
- const fallbackRow = Math.max(0, lines.length - 1);
518
- const row = clampInteger(frame?.cursorRow, 0, Math.max(0, lines.length - 1), fallbackRow);
519
- const fallbackColumn = lines.length ? visibleLineWidth(lines[row]) : 0;
520
- const cursor = {
521
- row,
522
- column: clampInteger(frame?.cursorColumn, 0, fallbackColumn, fallbackColumn),
523
- };
524
- const focusRow = clampInteger(frame?.focusRow, 0, Math.max(0, lines.length - 1), row);
525
- return { lines, width, cursor, focusRow };
526
- }
527
-
528
- function fitFrame(frame, height) {
529
- const lineCount = finitePositive(height, DEFAULT_ROWS);
530
- if (frame.lines.length <= lineCount) {
531
- return frame;
532
- }
533
- const firstFocusRow = Math.min(frame.cursor.row, frame.focusRow);
534
- const lastFocusRow = Math.max(frame.cursor.row, frame.focusRow);
535
- if (lastFocusRow - firstFocusRow >= lineCount && lineCount > 1) {
536
- const focusLineCount = lineCount - 1;
537
- const focusStart = Math.min(
538
- Math.max(0, frame.focusRow - focusLineCount + 1),
539
- frame.lines.length - focusLineCount,
540
- );
541
- return {
542
- ...frame,
543
- lines: [
544
- frame.lines[frame.cursor.row],
545
- ...frame.lines.slice(focusStart, focusStart + focusLineCount),
546
- ],
547
- cursor: {
548
- row: 0,
549
- column: frame.cursor.column,
550
- },
551
- focusRow: 1 + frame.focusRow - focusStart,
552
- };
553
- }
554
- const start = Math.min(firstFocusRow, frame.lines.length - lineCount);
555
- return {
556
- ...frame,
557
- lines: frame.lines.slice(start, start + lineCount),
558
- cursor: {
559
- row: frame.cursor.row - start,
560
- column: frame.cursor.column,
561
- },
562
- focusRow: frame.focusRow - start,
563
- };
564
- }
565
-
566
- function visibleLineWidth(line) {
567
- return textWidth(line);
568
- }
569
-
570
- function finitePositive(value, fallback) {
571
- const number = Number(value);
572
- return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
573
- }
574
-
575
- function clampInteger(value, min, max, fallback) {
576
- const number = Number(value);
577
- if (!Number.isFinite(number)) {
578
- return fallback;
579
- }
580
- return Math.max(min, Math.min(max, Math.floor(number)));
581
- }