@positronic/cli 0.0.55 → 0.0.57

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 (94) hide show
  1. package/dist/src/cli.js +142 -2
  2. package/dist/src/commands/auth.js +98 -0
  3. package/dist/src/commands/brain.js +3 -2
  4. package/dist/src/commands/helpers.js +48 -10
  5. package/dist/src/commands/project-config-manager.js +119 -0
  6. package/dist/src/commands/users.js +91 -0
  7. package/dist/src/components/agent-chat-view.js +125 -0
  8. package/dist/src/components/auth-list.js +56 -0
  9. package/dist/src/components/auth-login.js +209 -0
  10. package/dist/src/components/auth-logout.js +75 -0
  11. package/dist/src/components/auth-status.js +88 -0
  12. package/dist/src/components/brain-run.js +287 -254
  13. package/dist/src/components/brain-top-table.js +4 -0
  14. package/dist/src/components/event-detail.js +364 -0
  15. package/dist/src/components/events-view.js +379 -0
  16. package/dist/src/components/state-view.js +52 -0
  17. package/dist/src/components/top-navigator.js +80 -6
  18. package/dist/src/components/types.js +1 -0
  19. package/dist/src/components/users-create.js +293 -0
  20. package/dist/src/components/users-delete.js +294 -0
  21. package/dist/src/components/users-keys-add.js +156 -0
  22. package/dist/src/components/users-keys-list.js +119 -0
  23. package/dist/src/components/users-keys-remove.js +299 -0
  24. package/dist/src/components/users-list.js +109 -0
  25. package/dist/src/components/watch-keyboard.js +136 -0
  26. package/dist/src/components/watch-machine.js +573 -0
  27. package/dist/src/components/watch-resolver.js +3 -2
  28. package/dist/src/components/watch.js +390 -36
  29. package/dist/src/hooks/useApi.js +80 -42
  30. package/dist/src/lib/request-signer.js +208 -0
  31. package/dist/src/lib/ssh-key-utils.js +212 -0
  32. package/dist/src/utils/agent-utils.js +107 -0
  33. package/dist/types/cli.d.ts.map +1 -1
  34. package/dist/types/commands/auth.d.ts +36 -0
  35. package/dist/types/commands/auth.d.ts.map +1 -0
  36. package/dist/types/commands/brain.d.ts +2 -1
  37. package/dist/types/commands/brain.d.ts.map +1 -1
  38. package/dist/types/commands/helpers.d.ts.map +1 -1
  39. package/dist/types/commands/project-config-manager.d.ts +43 -0
  40. package/dist/types/commands/project-config-manager.d.ts.map +1 -1
  41. package/dist/types/commands/users.d.ts +33 -0
  42. package/dist/types/commands/users.d.ts.map +1 -0
  43. package/dist/types/components/agent-chat-view.d.ts +12 -0
  44. package/dist/types/components/agent-chat-view.d.ts.map +1 -0
  45. package/dist/types/components/auth-list.d.ts +7 -0
  46. package/dist/types/components/auth-list.d.ts.map +1 -0
  47. package/dist/types/components/auth-login.d.ts +9 -0
  48. package/dist/types/components/auth-login.d.ts.map +1 -0
  49. package/dist/types/components/auth-logout.d.ts +8 -0
  50. package/dist/types/components/auth-logout.d.ts.map +1 -0
  51. package/dist/types/components/auth-status.d.ts +7 -0
  52. package/dist/types/components/auth-status.d.ts.map +1 -0
  53. package/dist/types/components/brain-run.d.ts +11 -1
  54. package/dist/types/components/brain-run.d.ts.map +1 -1
  55. package/dist/types/components/brain-top-table.d.ts.map +1 -1
  56. package/dist/types/components/event-detail.d.ts +10 -0
  57. package/dist/types/components/event-detail.d.ts.map +1 -0
  58. package/dist/types/components/events-view.d.ts +13 -0
  59. package/dist/types/components/events-view.d.ts.map +1 -0
  60. package/dist/types/components/state-view.d.ts +13 -0
  61. package/dist/types/components/state-view.d.ts.map +1 -0
  62. package/dist/types/components/top-navigator.d.ts.map +1 -1
  63. package/dist/types/components/types.d.ts +11 -0
  64. package/dist/types/components/types.d.ts.map +1 -0
  65. package/dist/types/components/users-create.d.ts +6 -0
  66. package/dist/types/components/users-create.d.ts.map +1 -0
  67. package/dist/types/components/users-delete.d.ts +7 -0
  68. package/dist/types/components/users-delete.d.ts.map +1 -0
  69. package/dist/types/components/users-keys-add.d.ts +8 -0
  70. package/dist/types/components/users-keys-add.d.ts.map +1 -0
  71. package/dist/types/components/users-keys-list.d.ts +6 -0
  72. package/dist/types/components/users-keys-list.d.ts.map +1 -0
  73. package/dist/types/components/users-keys-remove.d.ts +8 -0
  74. package/dist/types/components/users-keys-remove.d.ts.map +1 -0
  75. package/dist/types/components/users-list.d.ts +2 -0
  76. package/dist/types/components/users-list.d.ts.map +1 -0
  77. package/dist/types/components/watch-keyboard.d.ts +56 -0
  78. package/dist/types/components/watch-keyboard.d.ts.map +1 -0
  79. package/dist/types/components/watch-machine.d.ts +171 -0
  80. package/dist/types/components/watch-machine.d.ts.map +1 -0
  81. package/dist/types/components/watch-resolver.d.ts +2 -1
  82. package/dist/types/components/watch-resolver.d.ts.map +1 -1
  83. package/dist/types/components/watch.d.ts +2 -1
  84. package/dist/types/components/watch.d.ts.map +1 -1
  85. package/dist/types/hooks/useApi.d.ts.map +1 -1
  86. package/dist/types/hooks/useBrainMachine.d.ts +9 -3
  87. package/dist/types/hooks/useBrainMachine.d.ts.map +1 -1
  88. package/dist/types/lib/request-signer.d.ts +51 -0
  89. package/dist/types/lib/request-signer.d.ts.map +1 -0
  90. package/dist/types/lib/ssh-key-utils.d.ts +45 -0
  91. package/dist/types/lib/ssh-key-utils.d.ts.map +1 -0
  92. package/dist/types/utils/agent-utils.d.ts +20 -0
  93. package/dist/types/utils/agent-utils.d.ts.map +1 -0
  94. package/package.json +7 -4
@@ -0,0 +1,379 @@
1
+ function _array_like_to_array(arr, len) {
2
+ if (len == null || len > arr.length) len = arr.length;
3
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
4
+ return arr2;
5
+ }
6
+ function _array_with_holes(arr) {
7
+ if (Array.isArray(arr)) return arr;
8
+ }
9
+ function _iterable_to_array_limit(arr, i) {
10
+ var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
11
+ if (_i == null) return;
12
+ var _arr = [];
13
+ var _n = true;
14
+ var _d = false;
15
+ var _s, _e;
16
+ try {
17
+ for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
18
+ _arr.push(_s.value);
19
+ if (i && _arr.length === i) break;
20
+ }
21
+ } catch (err) {
22
+ _d = true;
23
+ _e = err;
24
+ } finally{
25
+ try {
26
+ if (!_n && _i["return"] != null) _i["return"]();
27
+ } finally{
28
+ if (_d) throw _e;
29
+ }
30
+ }
31
+ return _arr;
32
+ }
33
+ function _non_iterable_rest() {
34
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
35
+ }
36
+ function _sliced_to_array(arr, i) {
37
+ return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
38
+ }
39
+ function _unsupported_iterable_to_array(o, minLen) {
40
+ if (!o) return;
41
+ if (typeof o === "string") return _array_like_to_array(o, minLen);
42
+ var n = Object.prototype.toString.call(o).slice(8, -1);
43
+ if (n === "Object" && o.constructor) n = o.constructor.name;
44
+ if (n === "Map" || n === "Set") return Array.from(n);
45
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
46
+ }
47
+ import React, { useState, useEffect } from 'react';
48
+ import { Text, Box, useStdout, useInput } from 'ink';
49
+ import { BRAIN_EVENTS } from '@positronic/core';
50
+ import { EventDetail } from './event-detail.js';
51
+ // Format relative timestamp
52
+ function formatTimestamp(timestamp) {
53
+ var now = Date.now();
54
+ var diff = now - timestamp.getTime();
55
+ var seconds = Math.floor(diff / 1000);
56
+ if (seconds < 60) {
57
+ return "".concat(seconds, "s ago");
58
+ }
59
+ var minutes = Math.floor(seconds / 60);
60
+ if (minutes < 60) {
61
+ var secs = seconds % 60;
62
+ return secs > 0 ? "".concat(minutes, "m ").concat(secs, "s ago") : "".concat(minutes, "m ago");
63
+ }
64
+ var hours = Math.floor(minutes / 60);
65
+ var mins = minutes % 60;
66
+ return mins > 0 ? "".concat(hours, "h ").concat(mins, "m ago") : "".concat(hours, "h ago");
67
+ }
68
+ // Truncate text to a max length
69
+ function truncate(text, maxLength) {
70
+ if (text.length <= maxLength) return text;
71
+ return text.slice(0, maxLength - 3) + '...';
72
+ }
73
+ // Format event for display
74
+ function formatEvent(event) {
75
+ switch(event.type){
76
+ case BRAIN_EVENTS.START:
77
+ return {
78
+ symbol: '[>]',
79
+ text: 'Brain started: "'.concat(event.brainTitle, '"'),
80
+ color: 'yellow'
81
+ };
82
+ case BRAIN_EVENTS.COMPLETE:
83
+ return {
84
+ symbol: '[ok]',
85
+ text: 'Brain completed: "'.concat(event.brainTitle, '"'),
86
+ color: 'green'
87
+ };
88
+ case BRAIN_EVENTS.ERROR:
89
+ return {
90
+ symbol: '[!!]',
91
+ text: "Error: ".concat(event.error.message),
92
+ color: 'red'
93
+ };
94
+ case BRAIN_EVENTS.CANCELLED:
95
+ return {
96
+ symbol: '[x]',
97
+ text: 'Brain cancelled: "'.concat(event.brainTitle, '"'),
98
+ color: 'red'
99
+ };
100
+ case BRAIN_EVENTS.STEP_START:
101
+ return {
102
+ symbol: '[.]',
103
+ text: 'Step started: "'.concat(event.stepTitle, '"'),
104
+ color: 'yellow'
105
+ };
106
+ case BRAIN_EVENTS.STEP_COMPLETE:
107
+ return {
108
+ symbol: '[+]',
109
+ text: 'Step completed: "'.concat(event.stepTitle, '"'),
110
+ color: 'green'
111
+ };
112
+ case BRAIN_EVENTS.STEP_RETRY:
113
+ return {
114
+ symbol: '[?]',
115
+ text: 'Step retry: "'.concat(event.stepTitle, '" (attempt ').concat(event.attempt, ")"),
116
+ color: 'yellow'
117
+ };
118
+ case BRAIN_EVENTS.STEP_STATUS:
119
+ return {
120
+ symbol: '[-]',
121
+ text: "Step status update (".concat(event.steps.length, " steps)"),
122
+ color: 'gray'
123
+ };
124
+ case BRAIN_EVENTS.WEBHOOK:
125
+ return {
126
+ symbol: '[~]',
127
+ text: "Waiting for webhook",
128
+ color: 'cyan'
129
+ };
130
+ case BRAIN_EVENTS.WEBHOOK_RESPONSE:
131
+ return {
132
+ symbol: '[<]',
133
+ text: "Webhook response received",
134
+ color: 'cyan'
135
+ };
136
+ case BRAIN_EVENTS.AGENT_START:
137
+ return {
138
+ symbol: '[A]',
139
+ text: 'Agent started: "'.concat(event.stepTitle, '"'),
140
+ color: 'yellow'
141
+ };
142
+ case BRAIN_EVENTS.AGENT_ITERATION:
143
+ return {
144
+ symbol: '[#]',
145
+ text: "Agent iteration ".concat(event.iteration),
146
+ color: 'gray',
147
+ tokens: event.tokensThisIteration
148
+ };
149
+ case BRAIN_EVENTS.AGENT_TOOL_CALL:
150
+ return {
151
+ symbol: '[T]',
152
+ text: "Tool call: ".concat(event.toolName),
153
+ color: 'white'
154
+ };
155
+ case BRAIN_EVENTS.AGENT_TOOL_RESULT:
156
+ return {
157
+ symbol: '[R]',
158
+ text: "Tool result: ".concat(event.toolName),
159
+ color: 'white'
160
+ };
161
+ case BRAIN_EVENTS.AGENT_ASSISTANT_MESSAGE:
162
+ return {
163
+ symbol: '[M]',
164
+ text: "Assistant: ".concat(truncate(event.content, 50)),
165
+ color: 'white'
166
+ };
167
+ case BRAIN_EVENTS.AGENT_COMPLETE:
168
+ return {
169
+ symbol: '[A]',
170
+ text: 'Agent completed: "'.concat(event.terminalToolName, '" (').concat(event.totalIterations, " iter)"),
171
+ color: 'green',
172
+ tokens: event.totalTokens
173
+ };
174
+ case BRAIN_EVENTS.AGENT_TOKEN_LIMIT:
175
+ return {
176
+ symbol: '[!]',
177
+ text: "Token limit reached: ".concat(event.totalTokens, "/").concat(event.maxTokens),
178
+ color: 'red',
179
+ tokens: event.totalTokens
180
+ };
181
+ case BRAIN_EVENTS.AGENT_ITERATION_LIMIT:
182
+ return {
183
+ symbol: '[!]',
184
+ text: "Iteration limit reached: ".concat(event.iteration, "/").concat(event.maxIterations),
185
+ color: 'red',
186
+ tokens: event.totalTokens
187
+ };
188
+ case BRAIN_EVENTS.AGENT_WEBHOOK:
189
+ return {
190
+ symbol: '[W]',
191
+ text: "Agent webhook: ".concat(event.toolName),
192
+ color: 'cyan'
193
+ };
194
+ case BRAIN_EVENTS.AGENT_RAW_RESPONSE_MESSAGE:
195
+ return {
196
+ symbol: '[~]',
197
+ text: "Agent response (iteration ".concat(event.iteration, ")"),
198
+ color: 'gray'
199
+ };
200
+ case BRAIN_EVENTS.PAUSED:
201
+ return {
202
+ symbol: '[||]',
203
+ text: 'Brain paused: "'.concat(event.brainTitle, '"'),
204
+ color: 'cyan'
205
+ };
206
+ case BRAIN_EVENTS.RESUMED:
207
+ return {
208
+ symbol: '[>]',
209
+ text: 'Brain resumed: "'.concat(event.brainTitle, '"'),
210
+ color: 'green'
211
+ };
212
+ default:
213
+ return {
214
+ symbol: '[?]',
215
+ text: "Unknown event: ".concat(event.type),
216
+ color: 'gray'
217
+ };
218
+ }
219
+ }
220
+ // Calculate visible window with selection support
221
+ function calculateVisibleWindow(eventsLength, selectedIndex, maxVisible) {
222
+ if (selectedIndex === null) {
223
+ // Auto-scroll: show most recent
224
+ return {
225
+ start: Math.max(0, eventsLength - maxVisible),
226
+ end: eventsLength
227
+ };
228
+ }
229
+ // Keep selection centered when possible
230
+ var half = Math.floor(maxVisible / 2);
231
+ var start = Math.max(0, selectedIndex - half);
232
+ var end = Math.min(eventsLength, start + maxVisible);
233
+ // Adjust if we hit the end
234
+ if (end === eventsLength) {
235
+ start = Math.max(0, end - maxVisible);
236
+ }
237
+ return {
238
+ start: start,
239
+ end: end
240
+ };
241
+ }
242
+ var EventLine = function(param) {
243
+ var stored = param.stored, isSelected = param.isSelected;
244
+ var _formatEvent = formatEvent(stored.event), symbol = _formatEvent.symbol, text = _formatEvent.text, color = _formatEvent.color, tokens = _formatEvent.tokens;
245
+ var timestamp = formatTimestamp(stored.timestamp);
246
+ return /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, {
247
+ color: isSelected ? 'cyan' : undefined
248
+ }, isSelected ? '› ' : ' '), /*#__PURE__*/ React.createElement(Text, {
249
+ dimColor: true
250
+ }, timestamp.padEnd(12), " "), /*#__PURE__*/ React.createElement(Text, {
251
+ color: isSelected ? 'cyan' : color
252
+ }, symbol, " "), /*#__PURE__*/ React.createElement(Text, {
253
+ color: isSelected ? 'cyan' : undefined
254
+ }, text), tokens !== undefined && /*#__PURE__*/ React.createElement(Text, {
255
+ dimColor: true
256
+ }, " (", tokens.toLocaleString(), " tokens)"));
257
+ };
258
+ export var EventsView = function(param) {
259
+ var events = param.events, _param_totalTokens = param.totalTokens, totalTokens = _param_totalTokens === void 0 ? 0 : _param_totalTokens, _param_isActive = param.isActive, isActive = _param_isActive === void 0 ? true : _param_isActive, onModeChange = param.onModeChange, onViewState = param.onViewState, controlledSelectedIndex = param.selectedIndex, onSelectedIndexChange = param.onSelectedIndexChange;
260
+ var stdout = useStdout().stdout;
261
+ var terminalHeight = (stdout === null || stdout === void 0 ? void 0 : stdout.rows) || 24;
262
+ // Reserve lines for header, footer, margins, token total
263
+ var maxVisible = Math.max(5, terminalHeight - 8);
264
+ var _useState = _sliced_to_array(useState('list'), 2), mode = _useState[0], setMode = _useState[1];
265
+ var _useState1 = _sliced_to_array(useState(null), 2), internalSelectedIndex = _useState1[0], setInternalSelectedIndex = _useState1[1];
266
+ var _useState2 = _sliced_to_array(useState(0), 2), scrollOffset = _useState2[0], setScrollOffset = _useState2[1];
267
+ // Use controlled value if provided, otherwise use internal state
268
+ var isControlled = controlledSelectedIndex !== undefined;
269
+ var selectedIndex = isControlled ? controlledSelectedIndex : internalSelectedIndex;
270
+ var setSelectedIndex = function(index) {
271
+ if (isControlled) {
272
+ onSelectedIndexChange === null || onSelectedIndexChange === void 0 ? void 0 : onSelectedIndexChange(index);
273
+ } else {
274
+ setInternalSelectedIndex(index);
275
+ }
276
+ };
277
+ // Notify parent of mode changes
278
+ useEffect(function() {
279
+ if (onModeChange) {
280
+ if (mode === 'detail') {
281
+ onModeChange('detail');
282
+ } else if (selectedIndex !== null) {
283
+ onModeChange('navigating');
284
+ } else {
285
+ onModeChange('auto');
286
+ }
287
+ }
288
+ }, [
289
+ mode,
290
+ selectedIndex,
291
+ onModeChange
292
+ ]);
293
+ // Keep selection valid when events change
294
+ useEffect(function() {
295
+ if (selectedIndex !== null && events.length > 0) {
296
+ if (selectedIndex >= events.length) {
297
+ setSelectedIndex(events.length - 1);
298
+ }
299
+ }
300
+ }, [
301
+ events.length,
302
+ selectedIndex
303
+ ]);
304
+ // Keyboard handling
305
+ useInput(function(input, key) {
306
+ if (!isActive) return;
307
+ if (mode === 'list') {
308
+ if (key.upArrow || input === 'k') {
309
+ if (selectedIndex === null) {
310
+ // First navigation: start from last event
311
+ if (events.length > 0) {
312
+ setSelectedIndex(events.length - 1);
313
+ }
314
+ } else if (selectedIndex > 0) {
315
+ setSelectedIndex(selectedIndex - 1);
316
+ }
317
+ } else if (key.downArrow || input === 'j') {
318
+ if (selectedIndex === null) {
319
+ if (events.length > 0) {
320
+ setSelectedIndex(events.length - 1);
321
+ }
322
+ } else if (selectedIndex < events.length - 1) {
323
+ setSelectedIndex(selectedIndex + 1);
324
+ }
325
+ } else if (key.return && selectedIndex !== null && events.length > 0) {
326
+ setMode('detail');
327
+ setScrollOffset(0);
328
+ } else if (input === 's' && selectedIndex !== null && onViewState) {
329
+ // View state at selected event
330
+ onViewState(selectedIndex);
331
+ } else if (key.escape && selectedIndex !== null) {
332
+ // Return to auto-scroll mode
333
+ setSelectedIndex(null);
334
+ }
335
+ } else if (mode === 'detail') {
336
+ // Escape or 'b' to go back to list
337
+ if (key.escape || input === 'b') {
338
+ setMode('list');
339
+ }
340
+ }
341
+ }, {
342
+ isActive: isActive
343
+ });
344
+ // Detail view
345
+ if (mode === 'detail' && selectedIndex !== null && events[selectedIndex]) {
346
+ return /*#__PURE__*/ React.createElement(EventDetail, {
347
+ stored: events[selectedIndex],
348
+ scrollOffset: scrollOffset,
349
+ onScrollChange: setScrollOffset,
350
+ isActive: isActive
351
+ });
352
+ }
353
+ // List view
354
+ var _calculateVisibleWindow = calculateVisibleWindow(events.length, selectedIndex, maxVisible), start = _calculateVisibleWindow.start, end = _calculateVisibleWindow.end;
355
+ var visibleEvents = events.slice(start, end);
356
+ return /*#__PURE__*/ React.createElement(Box, {
357
+ flexDirection: "column"
358
+ }, /*#__PURE__*/ React.createElement(Box, {
359
+ marginBottom: 1
360
+ }, /*#__PURE__*/ React.createElement(Text, {
361
+ bold: true
362
+ }, "Events (", events.length, " total)"), selectedIndex !== null && /*#__PURE__*/ React.createElement(Text, {
363
+ dimColor: true
364
+ }, " • Selected: ", selectedIndex + 1)), visibleEvents.length === 0 ? /*#__PURE__*/ React.createElement(Text, {
365
+ dimColor: true
366
+ }, "Waiting for events...") : visibleEvents.map(function(stored, index) {
367
+ return /*#__PURE__*/ React.createElement(EventLine, {
368
+ key: start + index,
369
+ stored: stored,
370
+ isSelected: selectedIndex === start + index
371
+ });
372
+ }), totalTokens > 0 && /*#__PURE__*/ React.createElement(Box, {
373
+ marginTop: 1
374
+ }, /*#__PURE__*/ React.createElement(Text, {
375
+ dimColor: true
376
+ }, "Total tokens: "), /*#__PURE__*/ React.createElement(Text, {
377
+ bold: true
378
+ }, totalTokens.toLocaleString())));
379
+ };
@@ -0,0 +1,52 @@
1
+ import React from 'react';
2
+ import { Text, Box, useStdout, useInput } from 'ink';
3
+ export var StateView = function(param) {
4
+ var state = param.state, title = param.title, scrollOffset = param.scrollOffset, onScrollChange = param.onScrollChange, _param_isActive = param.isActive, isActive = _param_isActive === void 0 ? true : _param_isActive;
5
+ var stdout = useStdout().stdout;
6
+ var terminalHeight = (stdout === null || stdout === void 0 ? void 0 : stdout.rows) || 24;
7
+ // Reserve lines for header, footer, margins
8
+ var maxLines = Math.max(5, terminalHeight - 6);
9
+ var content = JSON.stringify(state, null, 2);
10
+ var lines = content.split('\n');
11
+ var totalLines = lines.length;
12
+ var maxScroll = Math.max(0, totalLines - maxLines);
13
+ // Page size keeps 2 lines of context
14
+ var pageSize = Math.max(1, maxLines - 2);
15
+ // Handle scrolling
16
+ useInput(function(input, key) {
17
+ if (!isActive) return;
18
+ if (key.upArrow || input === 'k') {
19
+ onScrollChange(Math.max(0, scrollOffset - 1));
20
+ } else if (key.downArrow || input === 'j') {
21
+ onScrollChange(Math.min(maxScroll, scrollOffset + 1));
22
+ } else if (input === ' ' && !key.shift) {
23
+ // Space = page down
24
+ onScrollChange(Math.min(maxScroll, scrollOffset + pageSize));
25
+ } else if (input === ' ' && key.shift) {
26
+ // Shift+Space = page up
27
+ onScrollChange(Math.max(0, scrollOffset - pageSize));
28
+ }
29
+ }, {
30
+ isActive: isActive
31
+ });
32
+ var visibleLines = lines.slice(scrollOffset, scrollOffset + maxLines);
33
+ return /*#__PURE__*/ React.createElement(Box, {
34
+ flexDirection: "column"
35
+ }, /*#__PURE__*/ React.createElement(Box, {
36
+ marginBottom: 1
37
+ }, /*#__PURE__*/ React.createElement(Text, {
38
+ bold: true,
39
+ color: "cyan"
40
+ }, title)), /*#__PURE__*/ React.createElement(Box, {
41
+ flexDirection: "column",
42
+ marginLeft: 2
43
+ }, visibleLines.map(function(line, i) {
44
+ return /*#__PURE__*/ React.createElement(Text, {
45
+ key: scrollOffset + i
46
+ }, line);
47
+ })), totalLines > maxLines && /*#__PURE__*/ React.createElement(Box, {
48
+ marginTop: 1
49
+ }, /*#__PURE__*/ React.createElement(Text, {
50
+ dimColor: true
51
+ }, "Lines ", scrollOffset + 1, "-", Math.min(scrollOffset + maxLines, totalLines), " of ", totalLines)));
52
+ };
@@ -47,7 +47,8 @@ function _unsupported_iterable_to_array(o, minLen) {
47
47
  import React, { useState, useEffect, useRef } from 'react';
48
48
  import { Text, Box, useStdout, useInput, useApp } from 'ink';
49
49
  import { EventSource } from 'eventsource';
50
- import { getApiBaseUrl, isApiLocalDevMode } from '../commands/helpers.js';
50
+ import { getApiBaseUrl, isApiLocalDevMode, apiClient } from '../commands/helpers.js';
51
+ import { STATUS } from '@positronic/core';
51
52
  import { useApiDelete } from '../hooks/useApi.js';
52
53
  import { ErrorComponent } from './error.js';
53
54
  import { BrainTopTable } from './brain-top-table.js';
@@ -70,6 +71,11 @@ export var TopNavigator = function(param) {
70
71
  var _useState8 = _sliced_to_array(useState(false), 2), isKilling = _useState8[0], setIsKilling = _useState8[1];
71
72
  var _useState9 = _sliced_to_array(useState(null), 2), killMessage = _useState9[0], setKillMessage = _useState9[1];
72
73
  var _useApiDelete = useApiDelete('brain'), killBrain = _useApiDelete.execute, killError = _useApiDelete.error;
74
+ // Pause/resume state (for list mode)
75
+ var _useState10 = _sliced_to_array(useState(false), 2), isPausing = _useState10[0], setIsPausing = _useState10[1];
76
+ var _useState11 = _sliced_to_array(useState(null), 2), pauseMessage = _useState11[0], setPauseMessage = _useState11[1];
77
+ var _useState12 = _sliced_to_array(useState(false), 2), isResuming = _useState12[0], setIsResuming = _useState12[1];
78
+ var _useState13 = _sliced_to_array(useState(null), 2), resumeMessage = _useState13[0], setResumeMessage = _useState13[1];
73
79
  var eventSourceRef = useRef(null);
74
80
  var hasReceivedDataRef = useRef(false);
75
81
  // Filter brains client-side
@@ -192,12 +198,63 @@ export var TopNavigator = function(param) {
192
198
  }
193
199
  } else if (input === 'x' && filteredBrains.length > 0 && !isKilling) {
194
200
  setConfirmingKill(true);
201
+ } else if (input === 'p' && filteredBrains.length > 0 && !isPausing) {
202
+ var brain2 = filteredBrains[selectedIndex];
203
+ if (brain2 && brain2.status === STATUS.RUNNING) {
204
+ setIsPausing(true);
205
+ apiClient.fetch("/brains/runs/".concat(brain2.brainRunId, "/signals"), {
206
+ method: 'POST',
207
+ headers: {
208
+ 'Content-Type': 'application/json'
209
+ },
210
+ body: JSON.stringify({
211
+ type: 'PAUSE'
212
+ })
213
+ }).then(function(res) {
214
+ if (res.status === 202) {
215
+ setPauseMessage("Paused: ".concat(brain2.brainTitle));
216
+ setTimeout(function() {
217
+ return setPauseMessage(null);
218
+ }, 2000);
219
+ }
220
+ }).catch(function() {
221
+ // Silently ignore - user can retry
222
+ }).finally(function() {
223
+ return setIsPausing(false);
224
+ });
225
+ }
226
+ } else if (input === 'r' && filteredBrains.length > 0 && !isResuming) {
227
+ var brain3 = filteredBrains[selectedIndex];
228
+ if (brain3 && brain3.status === STATUS.PAUSED) {
229
+ setIsResuming(true);
230
+ apiClient.fetch("/brains/runs/".concat(brain3.brainRunId, "/signals"), {
231
+ method: 'POST',
232
+ headers: {
233
+ 'Content-Type': 'application/json'
234
+ },
235
+ body: JSON.stringify({
236
+ type: 'RESUME'
237
+ })
238
+ }).then(function(res) {
239
+ if (res.status === 202) {
240
+ setResumeMessage("Resumed: ".concat(brain3.brainTitle));
241
+ setTimeout(function() {
242
+ return setResumeMessage(null);
243
+ }, 2000);
244
+ }
245
+ }).catch(function() {
246
+ // Silently ignore - user can retry
247
+ }).finally(function() {
248
+ return setIsResuming(false);
249
+ });
250
+ }
195
251
  } else if (input === 'q' || key.escape) {
196
252
  exit();
197
253
  }
198
254
  } else if (mode === 'detail') {
199
- // Detail mode navigation - b or escape goes back to list
200
- if (input === 'b' || key.escape) {
255
+ // Detail mode navigation - only escape goes back to list
256
+ // 'b' is reserved for Watch internal navigation (back from agent-chat, state view, etc.)
257
+ if (key.escape) {
201
258
  setSelectedRunId(null);
202
259
  setMode('list');
203
260
  }
@@ -227,7 +284,7 @@ export var TopNavigator = function(param) {
227
284
  return /*#__PURE__*/ React.createElement(Watch, {
228
285
  runId: selectedRunId,
229
286
  manageScreenBuffer: false,
230
- footer: "b back x kill"
287
+ footer: "s state | e events | a agents | x kill | esc list"
231
288
  });
232
289
  }
233
290
  // List mode - show connecting state
@@ -235,10 +292,19 @@ export var TopNavigator = function(param) {
235
292
  return /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, null, "Connecting to watch service..."));
236
293
  }
237
294
  // Build footer based on state
238
- var listFooter = 'j/k or ↑/↓ select • Enter watch • x kill • esc quit';
295
+ var listFooter;
239
296
  if (confirmingKill) {
240
297
  var brain = filteredBrains[selectedIndex];
241
298
  listFooter = 'Kill "'.concat(brain === null || brain === void 0 ? void 0 : brain.brainTitle, '"? (y/n)');
299
+ } else {
300
+ var selectedBrain = filteredBrains[selectedIndex];
301
+ var pauseResumeAction = '';
302
+ if ((selectedBrain === null || selectedBrain === void 0 ? void 0 : selectedBrain.status) === STATUS.RUNNING) {
303
+ pauseResumeAction = 'p pause • ';
304
+ } else if ((selectedBrain === null || selectedBrain === void 0 ? void 0 : selectedBrain.status) === STATUS.PAUSED) {
305
+ pauseResumeAction = 'r resume • ';
306
+ }
307
+ listFooter = "j/k or ↑/↓ select • Enter watch • ".concat(pauseResumeAction, "x kill • esc quit");
242
308
  }
243
309
  // List mode - show table
244
310
  return /*#__PURE__*/ React.createElement(Box, {
@@ -253,7 +319,15 @@ export var TopNavigator = function(param) {
253
319
  color: "yellow"
254
320
  }, "Killing brain...")), killMessage && /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, {
255
321
  color: "green"
256
- }, killMessage)), killError && /*#__PURE__*/ React.createElement(ErrorComponent, {
322
+ }, killMessage)), isPausing && /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, {
323
+ color: "yellow"
324
+ }, "Pausing brain...")), pauseMessage && /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, {
325
+ color: "cyan"
326
+ }, pauseMessage)), isResuming && /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, {
327
+ color: "yellow"
328
+ }, "Resuming brain...")), resumeMessage && /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, {
329
+ color: "green"
330
+ }, resumeMessage)), killError && /*#__PURE__*/ React.createElement(ErrorComponent, {
257
331
  error: killError
258
332
  }));
259
333
  };
@@ -0,0 +1 @@
1
+ export { };