@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,242 +1,243 @@
1
- import { runtimeEventType } from "./runtime-protocol.js";
2
- import {
3
- cancelledText,
4
- contextBuiltLine,
5
- errorLine,
6
- goalText,
7
- planUpdatedLine,
8
- toolProgressLine,
9
- toolRequestedLine,
10
- toolResultLine,
11
- toolStartedLine,
12
- turnCompletedLine,
13
- } from "./rendering.js";
14
-
15
- export function createEventController({
16
- state = {},
17
- input = {},
18
- output = {},
19
- background = {},
20
- }) {
21
- const announcedTools = new Set();
22
- const pendingFileChanges = new Map();
23
- const pendingPlanInputs = new Map();
24
- let toolStats = { completed: 0, failed: 0 };
25
-
26
- async function handle(message) {
27
- if (state.runtimeClosing) {
28
- return;
29
- }
30
- const event = message?.event;
31
- const eventType = runtimeEventType(message);
32
- if (!event || typeof event !== "object") {
33
- if (state.debug) {
34
- output.debug?.(`Ignoring runtime event without payload: ${eventType || "unknown"}`);
35
- }
36
- return;
37
- }
38
- switch (eventType) {
39
- case "assistant_delta":
40
- output.assistantAppend?.(event.text || "");
41
- return;
42
- case "context_built": {
43
- if (output.handleContextBuilt?.(event)) {
44
- output.resetContextUsage?.();
45
- }
46
- const line = contextBuiltLine(event);
47
- if (line) {
48
- output.closeAssistant?.();
49
- output.log?.(line);
50
- }
51
- return;
52
- }
53
- case "tool_input_started":
54
- output.closeAssistant?.();
55
- rememberPlanInputStart(event);
56
- if (isAnnounced(event)) {
57
- return;
58
- }
59
- output.log?.(toolStartedLine(event));
60
- return;
61
- case "tool_input_delta":
62
- appendPlanInput(event);
63
- return;
64
- case "tool_input_ended":
65
- return;
66
- case "tool_requested":
67
- output.closeAssistant?.();
68
- rememberPlanInputPreview(event);
69
- background.recordCommand?.(event);
70
- if (isAnnounced(event)) {
71
- return;
72
- }
73
- output.log?.(toolRequestedLine(event));
74
- return;
75
- case "tool_call_started":
76
- output.closeAssistant?.();
77
- if (alreadyAnnounced(event)) {
78
- return;
79
- }
80
- output.log?.(toolStartedLine(event));
81
- return;
82
- case "tool_result": {
83
- output.closeAssistant?.();
84
- const fileChange = pendingFileChanges.get(event.tool_call_id);
85
- pendingFileChanges.delete(event.tool_call_id);
86
- const planInput = takePlanInput(event);
87
- background.recordResult?.(event);
88
- recordToolResult(event);
89
- const plan = event.tool_name === "update_plan" && event.status === "completed"
90
- ? parsePlanInput(planInput)
91
- : null;
92
- const goal = event.tool_name === "update_goal" && event.status === "completed"
93
- ? parseToolData(event.result)
94
- : null;
95
- if (goal?.status) {
96
- output.updateGoal?.(goal);
97
- }
98
- output.log?.(goal?.status ? goalText(goal) : plan ? planUpdatedLine(plan) : toolResultLine(event, fileChange));
99
- return;
100
- }
101
- case "file_change":
102
- if (event.tool_call_id) {
103
- pendingFileChanges.set(event.tool_call_id, event);
104
- }
105
- return;
106
- case "tool_progress": {
107
- output.closeAssistant?.();
108
- const line = toolProgressLine(event);
109
- if (line) {
110
- output.log?.(line);
111
- }
112
- return;
113
- }
114
- case "token_stats_updated":
115
- output.closeAssistant?.();
116
- output.setStats?.(event.stats && typeof event.stats === "object" ? event.stats : {});
117
- if (!state.activeTurn) {
118
- output.redraw?.();
119
- }
120
- return;
121
- case "user_question_requested":
122
- await input.answerQuestion?.(event);
123
- return;
124
- case "turn_failed":
125
- output.clearCompactContext?.();
126
- output.closeAssistant?.();
127
- output.log?.(errorLine(event.error));
128
- resetTurnState();
129
- return;
130
- case "turn_cancelled":
131
- output.clearCompactContext?.();
132
- output.closeAssistant?.();
133
- output.log?.(cancelledText());
134
- resetTurnState();
135
- return;
136
- case "turn_completed":
137
- output.clearCompactContext?.();
138
- output.closeAssistant?.();
139
- output.log?.(turnCompletedLine(event, toolStats));
140
- resetTurnState();
141
- return;
142
- default:
143
- if (state.debug) {
144
- output.debug?.(`Ignoring unknown runtime event: ${eventType || "unknown"}`);
145
- }
146
- }
147
- }
148
-
149
- function resetTurnState() {
150
- toolStats = { completed: 0, failed: 0 };
151
- announcedTools.clear();
152
- pendingFileChanges.clear();
153
- pendingPlanInputs.clear();
154
- output.resetTurnTools?.();
155
- }
156
-
157
- function isAnnounced(event) {
158
- if (!event.tool_call_id) {
159
- return false;
160
- }
161
- if (announcedTools.has(event.tool_call_id)) {
162
- return true;
163
- }
164
- announcedTools.add(event.tool_call_id);
165
- return false;
166
- }
167
-
168
- function alreadyAnnounced(event) {
169
- return Boolean(event.tool_call_id && announcedTools.has(event.tool_call_id));
170
- }
171
-
172
- function recordToolResult(event) {
173
- if (event.status === "failed") {
174
- toolStats.failed += 1;
175
- return;
176
- }
177
- toolStats.completed += 1;
178
- }
179
-
180
- function rememberPlanInputStart(event) {
181
- if (event.tool_name === "update_plan" && event.tool_call_id) {
182
- pendingPlanInputs.set(event.tool_call_id, "");
183
- }
184
- }
185
-
186
- function appendPlanInput(event) {
187
- if (event.tool_name !== "update_plan" || !event.tool_call_id) {
188
- return;
189
- }
190
- const current = pendingPlanInputs.get(event.tool_call_id);
191
- if (current !== undefined) {
192
- pendingPlanInputs.set(event.tool_call_id, current + String(event.delta || ""));
193
- }
194
- }
195
-
196
- function rememberPlanInputPreview(event) {
197
- if (event.tool_name !== "update_plan" || !event.tool_call_id) {
198
- return;
199
- }
200
- const current = pendingPlanInputs.get(event.tool_call_id);
201
- if (!current) {
202
- pendingPlanInputs.set(event.tool_call_id, String(event.args_preview || ""));
203
- }
204
- }
205
-
206
- function takePlanInput(event) {
207
- if (event.tool_name !== "update_plan" || !event.tool_call_id) {
208
- return "";
209
- }
210
- const value = pendingPlanInputs.get(event.tool_call_id) || "";
211
- pendingPlanInputs.delete(event.tool_call_id);
212
- return value;
213
- }
214
-
215
- return {
216
- handle,
217
- closeAssistant: () => output.closeAssistant?.(),
218
- resetTurnState,
219
- };
220
- }
221
-
222
- function parsePlanInput(value) {
223
- const args = parseObject(value);
224
- return Array.isArray(args.plan) ? args.plan : null;
225
- }
226
-
227
- function parseToolData(value) {
228
- const parsed = parseObject(value);
229
- return parsed.data && typeof parsed.data === "object" ? parsed.data : {};
230
- }
231
-
232
- function parseObject(value) {
233
- if (value && typeof value === "object") {
234
- return value;
235
- }
236
- try {
237
- const parsed = JSON.parse(String(value || ""));
238
- return parsed && typeof parsed === "object" ? parsed : {};
239
- } catch {
240
- return {};
241
- }
242
- }
1
+ import { runtimeEventType } from "./runtime-protocol.js";
2
+ import {
3
+ cancelledText,
4
+ contextBuiltLine,
5
+ errorLine,
6
+ goalContinuedLine,
7
+ planUpdatedLine,
8
+ turnCompletedLine,
9
+ } from "./rendering.js";
10
+
11
+ export function createEventController({
12
+ state = {},
13
+ input = {},
14
+ output = {},
15
+ monitor = {},
16
+ }) {
17
+ const pendingFileChanges = new Map();
18
+ const pendingPlanInputs = new Map();
19
+ let toolStats = { completed: 0, failed: 0 };
20
+
21
+ async function handle(message) {
22
+ if (state.runtimeClosing) {
23
+ return;
24
+ }
25
+ const event = message?.event;
26
+ const eventType = runtimeEventType(message);
27
+ if (!event || typeof event !== "object") {
28
+ if (state.debug) {
29
+ output.debug?.(`Ignoring runtime event without payload: ${eventType || "unknown"}`);
30
+ }
31
+ return;
32
+ }
33
+ switch (eventType) {
34
+ case "assistant_delta":
35
+ output.assistantAppend?.(event.text || "");
36
+ return;
37
+ case "context_built": {
38
+ if (output.handleContextBuilt?.(event)) {
39
+ output.resetContextUsage?.();
40
+ }
41
+ const line = contextBuiltLine(event);
42
+ if (line) {
43
+ output.closeAssistant?.();
44
+ output.log?.(() => line);
45
+ }
46
+ return;
47
+ }
48
+ case "tool_input_started":
49
+ output.closeAssistant?.();
50
+ rememberPlanInputStart(event);
51
+ output.beginTool?.(event);
52
+ return;
53
+ case "tool_input_delta":
54
+ appendPlanInput(event);
55
+ return;
56
+ case "tool_input_ended":
57
+ return;
58
+ case "tool_requested":
59
+ output.closeAssistant?.();
60
+ rememberPlanInputPreview(event);
61
+ monitor.recordCommand?.(event);
62
+ monitor.recordDelegateRequest?.(event);
63
+ output.beginTool?.(event);
64
+ return;
65
+ case "tool_call_started":
66
+ output.closeAssistant?.();
67
+ output.beginTool?.(event);
68
+ return;
69
+ case "tool_result": {
70
+ output.closeAssistant?.();
71
+ const fileChange = pendingFileChanges.get(event.tool_call_id);
72
+ pendingFileChanges.delete(event.tool_call_id);
73
+ const planInput = takePlanInput(event);
74
+ monitor.recordResult?.(event);
75
+ monitor.recordDelegateResult?.(event);
76
+ recordToolResult(event);
77
+ const plan = event.tool_name === "update_plan" && event.status === "completed"
78
+ ? parsePlanInput(planInput)
79
+ : null;
80
+ const goal = event.tool_name === "update_goal" && event.status === "completed"
81
+ ? parseToolData(event.result)
82
+ : null;
83
+ if (goal?.status) {
84
+ output.updateGoal?.(goal);
85
+ }
86
+ if (plan) {
87
+ output.log?.(() => planUpdatedLine(plan));
88
+ }
89
+ output.finishTool?.(event, fileChange);
90
+ return;
91
+ }
92
+ case "file_change":
93
+ if (event.tool_call_id) {
94
+ pendingFileChanges.set(event.tool_call_id, event);
95
+ }
96
+ return;
97
+ case "tool_progress": {
98
+ const message = progressMessage(event.payload);
99
+ if (message) {
100
+ output.closeAssistant?.();
101
+ output.updateToolProgress?.(event.tool_call_id, message);
102
+ }
103
+ return;
104
+ }
105
+ case "token_stats_updated":
106
+ output.closeAssistant?.();
107
+ output.setStats?.(event.stats && typeof event.stats === "object" ? event.stats : {});
108
+ if (!state.activeTurn) {
109
+ output.redraw?.();
110
+ }
111
+ return;
112
+ case "user_question_requested":
113
+ await input.answerQuestion?.(event);
114
+ return;
115
+ case "queued_input_delivered":
116
+ output.setGoalChasing?.(false);
117
+ output.deliverQueuedInput?.(event.input || "", event.mode || "steering", event.input_id || "");
118
+ return;
119
+ case "goal_continued":
120
+ output.closeAssistant?.();
121
+ output.setGoalChasing?.(true);
122
+ output.log?.(() => goalContinuedLine(event.round));
123
+ return;
124
+ case "turn_failed":
125
+ output.clearQueuedInputs?.();
126
+ output.clearCompactContext?.();
127
+ output.closeAssistant?.();
128
+ output.setGoalChasing?.(false);
129
+ output.log?.(() => errorLine(event.error));
130
+ resetTurnState();
131
+ return;
132
+ case "turn_cancelled":
133
+ output.clearQueuedInputs?.();
134
+ output.clearCompactContext?.();
135
+ output.closeAssistant?.();
136
+ output.setGoalChasing?.(false);
137
+ output.log?.(() => cancelledText());
138
+ resetTurnState();
139
+ return;
140
+ case "turn_completed":
141
+ output.clearQueuedInputs?.();
142
+ output.clearCompactContext?.();
143
+ output.closeAssistant?.();
144
+ output.setGoalChasing?.(false);
145
+ output.log?.(turnCompletedLine(event, toolStats));
146
+ resetTurnState();
147
+ return;
148
+ default:
149
+ if (state.debug) {
150
+ output.debug?.(`Ignoring unknown runtime event: ${eventType || "unknown"}`);
151
+ }
152
+ }
153
+ }
154
+
155
+ function progressMessage(payload) {
156
+ if (!payload || typeof payload !== "object") {
157
+ return "";
158
+ }
159
+ for (const key of ["message", "status", "text"]) {
160
+ const value = String(payload[key] || "").trim();
161
+ if (value) {
162
+ return value;
163
+ }
164
+ }
165
+ return "";
166
+ }
167
+
168
+ function resetTurnState() {
169
+ toolStats = { completed: 0, failed: 0 };
170
+ pendingFileChanges.clear();
171
+ pendingPlanInputs.clear();
172
+ monitor.clearDelegates?.();
173
+ }
174
+
175
+ function recordToolResult(event) {
176
+ if (event.status === "failed") {
177
+ toolStats.failed += 1;
178
+ return;
179
+ }
180
+ toolStats.completed += 1;
181
+ }
182
+
183
+ function rememberPlanInputStart(event) {
184
+ if (event.tool_name === "update_plan" && event.tool_call_id) {
185
+ pendingPlanInputs.set(event.tool_call_id, "");
186
+ }
187
+ }
188
+
189
+ function appendPlanInput(event) {
190
+ if (event.tool_name !== "update_plan" || !event.tool_call_id) {
191
+ return;
192
+ }
193
+ const current = pendingPlanInputs.get(event.tool_call_id);
194
+ if (current !== undefined) {
195
+ pendingPlanInputs.set(event.tool_call_id, current + String(event.delta || ""));
196
+ }
197
+ }
198
+
199
+ function rememberPlanInputPreview(event) {
200
+ if (event.tool_name !== "update_plan" || !event.tool_call_id) {
201
+ return;
202
+ }
203
+ const current = pendingPlanInputs.get(event.tool_call_id);
204
+ if (!current) {
205
+ pendingPlanInputs.set(event.tool_call_id, String(event.args_preview || ""));
206
+ }
207
+ }
208
+
209
+ function takePlanInput(event) {
210
+ if (event.tool_name !== "update_plan" || !event.tool_call_id) {
211
+ return "";
212
+ }
213
+ const value = pendingPlanInputs.get(event.tool_call_id) || "";
214
+ pendingPlanInputs.delete(event.tool_call_id);
215
+ return value;
216
+ }
217
+
218
+ return {
219
+ handle,
220
+ };
221
+ }
222
+
223
+ function parsePlanInput(value) {
224
+ const args = parseObject(value);
225
+ return Array.isArray(args.plan) ? args.plan : null;
226
+ }
227
+
228
+ function parseToolData(value) {
229
+ const parsed = parseObject(value);
230
+ return parsed.data && typeof parsed.data === "object" ? parsed.data : {};
231
+ }
232
+
233
+ function parseObject(value) {
234
+ if (value && typeof value === "object") {
235
+ return value;
236
+ }
237
+ try {
238
+ const parsed = JSON.parse(String(value || ""));
239
+ return parsed && typeof parsed === "object" ? parsed : {};
240
+ } catch {
241
+ return {};
242
+ }
243
+ }