@rind-ai/cli 0.6.1 → 0.7.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/lib/tui/tui.js CHANGED
@@ -5,9 +5,16 @@ export const CURSOR_MARKER = "\x1b_pi:c\x07";
5
5
 
6
6
  const SYNC_START = "\x1b[?2026h";
7
7
  const SYNC_END = "\x1b[?2026l";
8
+ const SHOW_CURSOR = "\x1b[?25h";
9
+ const HIDE_CURSOR = "\x1b[?25l";
8
10
  const LINE_RESET = "\x1b[0m";
9
11
  const PASTE_ENABLE = "\x1b[?2004h";
10
12
  const PASTE_DISABLE = "\x1b[?2004l";
13
+ const KITTY_KEYBOARD_ENABLE = "\x1b[>7u\x1b[?u\x1b[c";
14
+ const KITTY_KEYBOARD_DISABLE = "\x1b[<u";
15
+ const MODIFY_OTHER_KEYS_ENABLE = "\x1b[>4;2m";
16
+ const MODIFY_OTHER_KEYS_DISABLE = "\x1b[>4;0m";
17
+ const KEYBOARD_QUERY_TIMEOUT_MS = 150;
11
18
  const DEFAULT_COLUMNS = 80;
12
19
  const DEFAULT_ROWS = 24;
13
20
  const DEFAULT_RENDER_INTERVAL_MS = 16;
@@ -40,14 +47,20 @@ export function createTui(options = {}) {
40
47
  let previousHeight = 0;
41
48
  let cursorRow = 0;
42
49
  let hardwareCursorRow = 0;
50
+ let hardwareCursorCol = 0;
43
51
  let maxLinesRendered = 0;
44
52
  let previousViewportTop = 0;
45
53
 
46
54
  let clearOnShrink = false;
47
55
  let cursorVisible = false;
48
56
  let replayRequested = false;
57
+ let keyboardQueryActive = false;
58
+ let kittyKeyboardActive = false;
59
+ let modifyOtherKeysActive = false;
60
+ let keyboardQueryTimer = null;
61
+ let keyboardProtocolPushed = false;
49
62
  const inputBuffer = createInputBuffer({
50
- onSequence: (sequence) => inputHandler?.(sequence),
63
+ onSequence: handleInputSequence,
51
64
  onPaste: (payload) => pasteHandler?.(payload),
52
65
  setTimeout: schedule,
53
66
  clearTimeout: cancelSchedule,
@@ -127,6 +140,7 @@ export function createTui(options = {}) {
127
140
  output.on("resize", handleResize);
128
141
  }
129
142
  write(PASTE_ENABLE);
143
+ enableKeyboardProtocol();
130
144
  hideCursor();
131
145
  requestRender();
132
146
  }
@@ -167,6 +181,7 @@ export function createTui(options = {}) {
167
181
  input.setRawMode(rawModeBeforeStart);
168
182
  }
169
183
  write(PASTE_DISABLE);
184
+ disableKeyboardProtocol();
170
185
  inputBuffer.clear();
171
186
  }
172
187
 
@@ -174,6 +189,64 @@ export function createTui(options = {}) {
174
189
  inputBuffer.feed(data);
175
190
  }
176
191
 
192
+ function handleInputSequence(sequence) {
193
+ const kittyFlags = keyboardQueryActive ? kittyKeyboardFlags(sequence) : null;
194
+ if (kittyFlags !== null) {
195
+ clearKeyboardQuery();
196
+ if (kittyFlags > 0) {
197
+ kittyKeyboardActive = true;
198
+ } else {
199
+ enableModifyOtherKeys();
200
+ }
201
+ return;
202
+ }
203
+ if (keyboardQueryActive && isDeviceAttributesResponse(sequence)) {
204
+ clearKeyboardQuery();
205
+ enableModifyOtherKeys();
206
+ return;
207
+ }
208
+ inputHandler?.(sequence);
209
+ }
210
+
211
+ function enableKeyboardProtocol() {
212
+ keyboardQueryActive = true;
213
+ keyboardProtocolPushed = true;
214
+ keyboardQueryTimer = schedule(() => {
215
+ keyboardQueryTimer = null;
216
+ keyboardQueryActive = false;
217
+ }, KEYBOARD_QUERY_TIMEOUT_MS);
218
+ write(KITTY_KEYBOARD_ENABLE);
219
+ }
220
+
221
+ function clearKeyboardQuery() {
222
+ keyboardQueryActive = false;
223
+ if (keyboardQueryTimer !== null) {
224
+ cancelSchedule(keyboardQueryTimer);
225
+ keyboardQueryTimer = null;
226
+ }
227
+ }
228
+
229
+ function disableKeyboardProtocol() {
230
+ if (keyboardProtocolPushed) {
231
+ write(KITTY_KEYBOARD_DISABLE);
232
+ }
233
+ if (modifyOtherKeysActive) {
234
+ write(MODIFY_OTHER_KEYS_DISABLE);
235
+ }
236
+ clearKeyboardQuery();
237
+ keyboardProtocolPushed = false;
238
+ kittyKeyboardActive = false;
239
+ modifyOtherKeysActive = false;
240
+ }
241
+
242
+ function enableModifyOtherKeys() {
243
+ if (kittyKeyboardActive || modifyOtherKeysActive) {
244
+ return;
245
+ }
246
+ write(MODIFY_OTHER_KEYS_ENABLE);
247
+ modifyOtherKeysActive = true;
248
+ }
249
+
177
250
  function handleResize() {
178
251
  requestRender();
179
252
  }
@@ -260,6 +333,7 @@ export function createTui(options = {}) {
260
333
  previousHeight = -1;
261
334
  cursorRow = 0;
262
335
  hardwareCursorRow = 0;
336
+ hardwareCursorCol = -1;
263
337
  maxLinesRendered = 0;
264
338
  previousViewportTop = 0;
265
339
  }
@@ -292,14 +366,15 @@ export function createTui(options = {}) {
292
366
  }
293
367
  buffer += writableLine(newLines[index], width);
294
368
  }
295
- buffer += SYNC_END;
296
- write(buffer);
297
369
  cursorRow = Math.max(0, newLines.length - 1);
298
370
  hardwareCursorRow = cursorRow;
371
+ hardwareCursorCol = -1;
372
+ buffer += hardwareCursorSequence(cursorPos, newLines);
373
+ buffer += SYNC_END;
374
+ write(buffer);
299
375
  maxLinesRendered = clear ? newLines.length : Math.max(maxLinesRendered, newLines.length);
300
376
  const bufferLength = Math.max(height, newLines.length);
301
377
  previousViewportTop = Math.max(0, bufferLength - height);
302
- positionHardwareCursor(cursorPos, newLines);
303
378
  previousLines = newLines;
304
379
  previousWidth = width;
305
380
  previousHeight = height;
@@ -352,7 +427,7 @@ export function createTui(options = {}) {
352
427
  const appendStart = appendedLines && firstChanged === previousLines.length && firstChanged > 0;
353
428
 
354
429
  if (firstChanged === -1) {
355
- positionHardwareCursor(cursorPos, newLines);
430
+ emitCursorSequence(cursorPos, newLines);
356
431
  previousViewportTop = prevViewportTop;
357
432
  previousHeight = height;
358
433
  return;
@@ -373,6 +448,9 @@ export function createTui(options = {}) {
373
448
  return;
374
449
  }
375
450
  let buffer = SYNC_START;
451
+ cursorRow = targetRow;
452
+ hardwareCursorRow = targetRow;
453
+ hardwareCursorCol = -1;
376
454
  const lineDiff = computeLineDiff(targetRow);
377
455
  if (lineDiff > 0) {
378
456
  buffer += `\x1b[${lineDiff}B`;
@@ -394,12 +472,11 @@ export function createTui(options = {}) {
394
472
  if (moveBack > 0) {
395
473
  buffer += `\x1b[${moveBack}A`;
396
474
  }
475
+ buffer += hardwareCursorSequence(cursorPos, newLines);
397
476
  buffer += SYNC_END;
398
477
  write(buffer);
399
- cursorRow = targetRow;
400
- hardwareCursorRow = targetRow;
401
478
  }
402
- positionHardwareCursor(cursorPos, newLines);
479
+ emitCursorSequence(cursorPos, newLines);
403
480
  previousLines = newLines;
404
481
  previousWidth = width;
405
482
  previousHeight = height;
@@ -461,14 +538,15 @@ export function createTui(options = {}) {
461
538
  buffer += `\x1b[${extraLines}A`;
462
539
  }
463
540
 
464
- buffer += SYNC_END;
465
- write(buffer);
466
-
467
541
  cursorRow = Math.max(0, newLines.length - 1);
468
542
  hardwareCursorRow = finalCursorRow;
543
+ hardwareCursorCol = -1;
469
544
  maxLinesRendered = Math.max(maxLinesRendered, newLines.length);
470
545
  previousViewportTop = Math.max(prevViewportTop, finalCursorRow - height + 1);
471
- positionHardwareCursor(cursorPos, newLines);
546
+ buffer += hardwareCursorSequence(cursorPos, newLines);
547
+ buffer += SYNC_END;
548
+ write(buffer);
549
+
472
550
  previousLines = newLines;
473
551
  previousWidth = width;
474
552
  previousHeight = height;
@@ -510,38 +588,49 @@ export function createTui(options = {}) {
510
588
  return null;
511
589
  }
512
590
 
513
- function positionHardwareCursor(cursorPos, lines) {
514
- const totalLines = lines.length;
515
- if (!cursorPos || totalLines <= 0) {
516
- if (cursorVisible) {
517
- hideCursor();
591
+ function hardwareCursorSequence(cursorPos, lines) {
592
+ if (!cursorPos || lines.length <= 0) {
593
+ if (!cursorVisible) {
594
+ return "";
518
595
  }
519
- return;
596
+ cursorVisible = false;
597
+ return HIDE_CURSOR;
520
598
  }
521
- let targetRow;
522
- let targetCol;
523
- targetRow = Math.max(0, Math.min(cursorPos.row, totalLines - 1));
524
- targetCol = Math.max(0, cursorPos.col);
599
+ const targetRow = Math.max(0, Math.min(cursorPos.row, lines.length - 1));
600
+ const targetCol = Math.max(0, cursorPos.col);
601
+ let sequence = "";
525
602
  const rowDelta = targetRow - hardwareCursorRow;
526
- let buffer = "";
527
603
  if (rowDelta > 0) {
528
- buffer += `\x1b[${rowDelta}B`;
604
+ sequence += `\x1b[${rowDelta}B`;
529
605
  } else if (rowDelta < 0) {
530
- buffer += `\x1b[${-rowDelta}A`;
606
+ sequence += `\x1b[${-rowDelta}A`;
607
+ }
608
+ if (hardwareCursorCol !== targetCol) {
609
+ sequence += `\x1b[${targetCol + 1}G`;
610
+ }
611
+ if (!cursorVisible) {
612
+ sequence += SHOW_CURSOR;
531
613
  }
532
- buffer += `\x1b[${targetCol + 1}G`;
533
- write(buffer);
534
614
  hardwareCursorRow = targetRow;
535
- showCursor();
615
+ hardwareCursorCol = targetCol;
616
+ cursorVisible = true;
617
+ return sequence;
618
+ }
619
+
620
+ function emitCursorSequence(cursorPos, lines) {
621
+ const sequence = hardwareCursorSequence(cursorPos, lines);
622
+ if (sequence) {
623
+ write(`${SYNC_START}${sequence}${SYNC_END}`);
624
+ }
536
625
  }
537
626
 
538
627
  function hideCursor() {
539
- write("\x1b[?25l");
628
+ write(HIDE_CURSOR);
540
629
  cursorVisible = false;
541
630
  }
542
631
 
543
632
  function showCursor() {
544
- write("\x1b[?25h");
633
+ write(SHOW_CURSOR);
545
634
  cursorVisible = true;
546
635
  }
547
636
 
@@ -589,3 +678,12 @@ function finitePositive(value, fallback) {
589
678
  const number = Number(value);
590
679
  return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
591
680
  }
681
+
682
+ function kittyKeyboardFlags(sequence) {
683
+ const match = String(sequence || "").match(/^\x1b\[\?(\d+)u$/);
684
+ return match ? Number(match[1]) : null;
685
+ }
686
+
687
+ function isDeviceAttributesResponse(sequence) {
688
+ return /^\x1b\[\?[\d;]*c$/.test(sequence);
689
+ }
@@ -1,6 +1,11 @@
1
1
  import { runtimeMethods } from "./runtime-protocol.js";
2
2
 
3
- export function createTurnController({ request, state, output, refreshGoalState = () => {}, onTurnStart = () => {} }) {
3
+ export function createTurnController({
4
+ request,
5
+ state,
6
+ output,
7
+ onTurnStart = () => {},
8
+ }) {
4
9
  function submit(text, extra = {}) {
5
10
  if (state.activeTurn) {
6
11
  void submitQueuedInput(runtimeMethods.sessionSteer, text, text, "steering");
@@ -41,14 +46,18 @@ export function createTurnController({ request, state, output, refreshGoalState
41
46
  }
42
47
 
43
48
  async function run(text, extra = {}) {
49
+ let requestSucceeded = false;
44
50
  try {
45
51
  await request(runtimeMethods.sessionPrompt, { input: text, ...extra });
52
+ requestSucceeded = true;
46
53
  } finally {
47
- void refreshGoalState();
48
- state.activeTurn = false;
49
- state.interruptRequested = false;
50
54
  output.closeAssistant();
51
55
  output.refreshInputState();
56
+ if (!requestSucceeded) {
57
+ state.activeTurn = false;
58
+ state.interruptRequested = false;
59
+ output.refreshInputState();
60
+ }
52
61
  }
53
62
  }
54
63
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rind-ai/cli",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Rind command-line client",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/Azzurroooo/rind.git",
@@ -16,10 +16,10 @@
16
16
  "node": ">=18"
17
17
  },
18
18
  "optionalDependencies": {
19
- "@rind-ai/runtime-win32-x64": "0.6.1",
20
- "@rind-ai/runtime-linux-x64": "0.6.1",
21
- "@rind-ai/runtime-darwin-x64": "0.6.1",
22
- "@rind-ai/runtime-darwin-arm64": "0.6.1"
19
+ "@rind-ai/runtime-win32-x64": "0.7.0",
20
+ "@rind-ai/runtime-linux-x64": "0.7.0",
21
+ "@rind-ai/runtime-darwin-x64": "0.7.0",
22
+ "@rind-ai/runtime-darwin-arm64": "0.7.0"
23
23
  },
24
24
  "publishConfig": {
25
25
  "access": "public",
@@ -1,5 +0,0 @@
1
- import { runFrontendCliApp } from "./frontend-cli-implementation.js";
2
-
3
- export function runFrontendCli(cliArgs = process.argv.slice(2)) {
4
- return runFrontendCliApp(cliArgs);
5
- }