@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
@@ -0,0 +1,55 @@
1
+ export class Component {
2
+ render(width) {
3
+ return [];
4
+ }
5
+
6
+ invalidate() {}
7
+ }
8
+
9
+ export class Container extends Component {
10
+ constructor() {
11
+ super();
12
+ this.children = [];
13
+ }
14
+
15
+ addChild(child) {
16
+ this.children.push(child);
17
+ return child;
18
+ }
19
+
20
+ removeChild(child) {
21
+ const index = this.children.indexOf(child);
22
+ if (index !== -1) {
23
+ this.children.splice(index, 1);
24
+ }
25
+ }
26
+
27
+ clear() {
28
+ this.children.length = 0;
29
+ }
30
+
31
+ get length() {
32
+ return this.children.length;
33
+ }
34
+
35
+ render(width) {
36
+ const lines = [];
37
+ for (const child of this.children) {
38
+ const rendered = child.render(width);
39
+ if (Array.isArray(rendered)) {
40
+ for (const line of rendered) {
41
+ lines.push(typeof line === "string" ? line : String(line ?? ""));
42
+ }
43
+ }
44
+ }
45
+ return lines;
46
+ }
47
+
48
+ invalidate() {
49
+ for (const child of this.children) {
50
+ if (typeof child.invalidate === "function") {
51
+ child.invalidate();
52
+ }
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,29 @@
1
+ import { CURSOR_MARKER } from "./tui.js";
2
+ import { graphemes, textWidth } from "../text-width.js";
3
+
4
+ const ANSI_SEQUENCE = /\x1b\[[0-?]*[ -/]*[@-~]/g;
5
+
6
+ export function insertCursorMarker(line, column) {
7
+ const text = String(line || "");
8
+ const target = Math.max(0, Math.floor(Number(column) || 0));
9
+ let width = 0;
10
+ let position = 0;
11
+ while (position < text.length) {
12
+ if (text[position] === "\x1b") {
13
+ const match = text.slice(position).match(ANSI_SEQUENCE);
14
+ if (match && match.index === 0) {
15
+ position += match[0].length;
16
+ continue;
17
+ }
18
+ }
19
+ const codePoint = text.codePointAt(position);
20
+ const segment = String.fromCodePoint(codePoint);
21
+ const segmentWidth = textWidth(segment);
22
+ if (width + segmentWidth > target) {
23
+ break;
24
+ }
25
+ width += segmentWidth;
26
+ position += segment.length;
27
+ }
28
+ return `${text.slice(0, position)}${CURSOR_MARKER}${text.slice(position)}`;
29
+ }
@@ -0,0 +1,172 @@
1
+ const ESC = "\x1b";
2
+ const PASTE_START = "\x1b[200~";
3
+ const PASTE_END = "\x1b[201~";
4
+ const DEFAULT_INPUT_TIMEOUT_MS = 10;
5
+
6
+ export function createInputBuffer(options = {}) {
7
+ const onSequence = typeof options.onSequence === "function" ? options.onSequence : () => {};
8
+ const onPaste = typeof options.onPaste === "function" ? options.onPaste : () => {};
9
+ const schedule = options.setTimeout || setTimeout;
10
+ const cancelSchedule = options.clearTimeout || clearTimeout;
11
+ const timeoutMs = Number.isFinite(options.timeoutMs) ? options.timeoutMs : DEFAULT_INPUT_TIMEOUT_MS;
12
+
13
+ let buffer = "";
14
+ let timer = null;
15
+ let pasteMode = false;
16
+ let pasteBuffer = "";
17
+
18
+ function feed(data) {
19
+ const value = Buffer.isBuffer(data) ? data.toString("utf8") : String(data || "");
20
+ if (!value && !buffer) {
21
+ return;
22
+ }
23
+ buffer += value;
24
+ processBuffer();
25
+ }
26
+
27
+ function flush() {
28
+ clearTimer();
29
+ if (pasteMode || !buffer) {
30
+ return;
31
+ }
32
+ const value = buffer;
33
+ buffer = "";
34
+ emit(value);
35
+ }
36
+
37
+ function clear() {
38
+ clearTimer();
39
+ buffer = "";
40
+ pasteMode = false;
41
+ pasteBuffer = "";
42
+ }
43
+
44
+ function clearTimer() {
45
+ if (timer === null) {
46
+ return;
47
+ }
48
+ cancelSchedule(timer);
49
+ timer = null;
50
+ }
51
+
52
+ function emit(sequence) {
53
+ if (sequence) {
54
+ onSequence(sequence);
55
+ }
56
+ }
57
+
58
+ function processBuffer() {
59
+ if (pasteMode) {
60
+ pasteBuffer += buffer;
61
+ buffer = "";
62
+ const endIndex = pasteBuffer.indexOf(PASTE_END);
63
+ if (endIndex === -1) {
64
+ return;
65
+ }
66
+ finishPaste(endIndex);
67
+ return;
68
+ }
69
+
70
+ const pasteIndex = buffer.indexOf(PASTE_START);
71
+ if (pasteIndex !== -1) {
72
+ const beforePaste = buffer.slice(0, pasteIndex);
73
+ const pasteContent = buffer.slice(pasteIndex + PASTE_START.length);
74
+ if (beforePaste) {
75
+ emitSequences(beforePaste);
76
+ }
77
+ pasteMode = true;
78
+ pasteBuffer = pasteContent;
79
+ buffer = "";
80
+ const endIndex = pasteBuffer.indexOf(PASTE_END);
81
+ if (endIndex !== -1) {
82
+ finishPaste(endIndex);
83
+ }
84
+ return;
85
+ }
86
+
87
+ emitSequences(buffer);
88
+ }
89
+
90
+ function finishPaste(endIndex) {
91
+ const content = pasteBuffer.slice(0, endIndex);
92
+ const remaining = pasteBuffer.slice(endIndex + PASTE_END.length);
93
+ pasteMode = false;
94
+ pasteBuffer = "";
95
+ onPaste(content);
96
+ if (remaining) {
97
+ feed(remaining);
98
+ }
99
+ }
100
+
101
+ function emitSequences(value) {
102
+ const parsed = splitSequences(value);
103
+ buffer = parsed.remainder;
104
+ for (const sequence of parsed.sequences) {
105
+ emit(sequence);
106
+ }
107
+ if (buffer) {
108
+ clearTimer();
109
+ timer = schedule(() => {
110
+ timer = null;
111
+ flush();
112
+ }, timeoutMs);
113
+ } else {
114
+ clearTimer();
115
+ }
116
+ }
117
+
118
+ return { feed, flush, clear };
119
+ }
120
+
121
+ export function splitSequences(value) {
122
+ const sequences = [];
123
+ let position = 0;
124
+ while (position < value.length) {
125
+ if (value[position] !== ESC) {
126
+ const codePoint = value.codePointAt(position);
127
+ const character = String.fromCodePoint(codePoint);
128
+ sequences.push(character);
129
+ position += character.length;
130
+ continue;
131
+ }
132
+
133
+ const end = findEscapeEnd(value, position);
134
+ if (end === -1) {
135
+ return { sequences, remainder: value.slice(position) };
136
+ }
137
+ sequences.push(value.slice(position, end));
138
+ position = end;
139
+ }
140
+ return { sequences, remainder: "" };
141
+ }
142
+
143
+ function findEscapeEnd(value, start) {
144
+ if (start + 1 >= value.length) {
145
+ return -1;
146
+ }
147
+ const type = value[start + 1];
148
+ if (type === "[") {
149
+ for (let index = start + 2; index < value.length; index += 1) {
150
+ const code = value.charCodeAt(index);
151
+ if (code >= 0x40 && code <= 0x7e) {
152
+ return index + 1;
153
+ }
154
+ }
155
+ return -1;
156
+ }
157
+ if (type === "O") {
158
+ return start + 3 <= value.length ? start + 3 : -1;
159
+ }
160
+ if (type === "]" || type === "P" || type === "_") {
161
+ const bell = value.indexOf("\x07", start + 2);
162
+ const stringTerminator = value.indexOf(`${ESC}\\`, start + 2);
163
+ if (bell === -1 && stringTerminator === -1) {
164
+ return -1;
165
+ }
166
+ if (bell !== -1 && (stringTerminator === -1 || bell < stringTerminator)) {
167
+ return bell + 1;
168
+ }
169
+ return stringTerminator + 2;
170
+ }
171
+ return start + 1 + String.fromCodePoint(value.codePointAt(start + 1)).length;
172
+ }