@positronic/cli 0.0.50 → 0.0.52

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 (36) hide show
  1. package/dist/src/cli.js +37 -68
  2. package/dist/src/commands/brain.js +14 -23
  3. package/dist/src/commands/server.js +28 -41
  4. package/dist/src/components/brain-rerun.js +1 -1
  5. package/dist/src/components/brain-resolver.js +18 -49
  6. package/dist/src/components/brain-run.js +26 -43
  7. package/dist/src/components/brain-top-table.js +153 -0
  8. package/dist/src/components/brain-top.js +139 -0
  9. package/dist/src/components/project-select.js +32 -52
  10. package/dist/src/components/select-list.js +102 -0
  11. package/dist/src/components/top-navigator.js +259 -0
  12. package/dist/src/components/watch-resolver.js +592 -0
  13. package/dist/src/components/watch.js +114 -112
  14. package/dist/src/hooks/useBrainMachine.js +19 -0
  15. package/dist/types/cli.d.ts.map +1 -1
  16. package/dist/types/commands/brain.d.ts +6 -3
  17. package/dist/types/commands/brain.d.ts.map +1 -1
  18. package/dist/types/commands/server.d.ts.map +1 -1
  19. package/dist/types/components/brain-resolver.d.ts.map +1 -1
  20. package/dist/types/components/brain-run.d.ts.map +1 -1
  21. package/dist/types/components/brain-top-table.d.ts +22 -0
  22. package/dist/types/components/brain-top-table.d.ts.map +1 -0
  23. package/dist/types/components/brain-top.d.ts +6 -0
  24. package/dist/types/components/brain-top.d.ts.map +1 -0
  25. package/dist/types/components/project-select.d.ts.map +1 -1
  26. package/dist/types/components/select-list.d.ts +23 -0
  27. package/dist/types/components/select-list.d.ts.map +1 -0
  28. package/dist/types/components/top-navigator.d.ts +6 -0
  29. package/dist/types/components/top-navigator.d.ts.map +1 -0
  30. package/dist/types/components/watch-resolver.d.ts +15 -0
  31. package/dist/types/components/watch-resolver.d.ts.map +1 -0
  32. package/dist/types/components/watch.d.ts +3 -1
  33. package/dist/types/components/watch.d.ts.map +1 -1
  34. package/dist/types/hooks/useBrainMachine.d.ts +37 -0
  35. package/dist/types/hooks/useBrainMachine.d.ts.map +1 -0
  36. package/package.json +5 -4
@@ -0,0 +1,153 @@
1
+ import React from 'react';
2
+ import { Text, Box } from 'ink';
3
+ import { STATUS } from '@positronic/core';
4
+ // Helper to format relative time
5
+ var formatRelativeTime = function(timestamp) {
6
+ var now = Date.now();
7
+ var diffMs = now - timestamp;
8
+ var diffSecs = Math.floor(diffMs / 1000);
9
+ var diffMins = Math.floor(diffMs / (1000 * 60));
10
+ var diffHours = Math.floor(diffMs / (1000 * 60 * 60));
11
+ var diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
12
+ if (diffSecs < 60) {
13
+ return 'just now';
14
+ } else if (diffMins < 60) {
15
+ return "".concat(diffMins, " min ago");
16
+ } else if (diffHours < 24) {
17
+ return "".concat(diffHours, " hr ago");
18
+ } else {
19
+ return "".concat(diffDays, " day").concat(diffDays === 1 ? '' : 's', " ago");
20
+ }
21
+ };
22
+ // Helper to format live duration
23
+ var formatDuration = function(startedAt) {
24
+ var durationMs = Date.now() - startedAt;
25
+ var totalSeconds = Math.floor(durationMs / 1000);
26
+ var seconds = totalSeconds % 60;
27
+ var minutes = Math.floor(totalSeconds / 60) % 60;
28
+ var hours = Math.floor(totalSeconds / 3600);
29
+ if (hours > 0) {
30
+ return "".concat(hours, "h ").concat(minutes, "m");
31
+ } else if (minutes > 0) {
32
+ return "".concat(minutes, "m ").concat(seconds, "s");
33
+ } else {
34
+ return "".concat(seconds, "s");
35
+ }
36
+ };
37
+ // Helper to get status color
38
+ var getStatusColor = function(status) {
39
+ switch(status){
40
+ case STATUS.COMPLETE:
41
+ return 'green';
42
+ case STATUS.ERROR:
43
+ return 'red';
44
+ case STATUS.RUNNING:
45
+ return 'yellow';
46
+ case STATUS.CANCELLED:
47
+ return 'gray';
48
+ default:
49
+ return 'white';
50
+ }
51
+ };
52
+ // Helper to pad text to column width
53
+ var padRight = function(text, width) {
54
+ return text + ' '.repeat(Math.max(0, width - text.length));
55
+ };
56
+ // Helper to truncate text
57
+ var truncate = function(text, maxWidth) {
58
+ if (text.length <= maxWidth) return text;
59
+ return text.substring(0, maxWidth - 3) + '...';
60
+ };
61
+ // Column definitions
62
+ var columns = {
63
+ brain: {
64
+ header: 'Brain',
65
+ width: 25
66
+ },
67
+ runId: {
68
+ header: 'Run ID',
69
+ width: 36
70
+ },
71
+ status: {
72
+ header: 'Status',
73
+ width: 10
74
+ },
75
+ started: {
76
+ header: 'Started',
77
+ width: 12
78
+ },
79
+ duration: {
80
+ header: 'Duration',
81
+ width: 10
82
+ }
83
+ };
84
+ export var BrainTopTable = function(param) {
85
+ var runningBrains = param.runningBrains, selectedIndex = param.selectedIndex, _param_interactive = param.interactive, interactive = _param_interactive === void 0 ? false : _param_interactive, brainFilter = param.brainFilter, _param_footer = param.footer, footer = _param_footer === void 0 ? 'Updates automatically. Press Ctrl+C to exit.' : _param_footer;
86
+ // Filter brains client-side
87
+ var filteredBrains = brainFilter ? runningBrains.filter(function(b) {
88
+ return b.brainTitle.toLowerCase().includes(brainFilter.toLowerCase());
89
+ }) : runningBrains;
90
+ if (filteredBrains.length === 0) {
91
+ return /*#__PURE__*/ React.createElement(Box, {
92
+ flexDirection: "column"
93
+ }, /*#__PURE__*/ React.createElement(Text, null, "No running brains", brainFilter ? ' matching "'.concat(brainFilter, '"') : ''), /*#__PURE__*/ React.createElement(Box, {
94
+ marginTop: 1
95
+ }, /*#__PURE__*/ React.createElement(Text, {
96
+ dimColor: true
97
+ }, 'Tip: Run a brain with "px run ', '<brain-name>', '" to see it here')));
98
+ }
99
+ // Selection indicator width (for interactive mode)
100
+ var selectorWidth = interactive ? 2 : 0;
101
+ return /*#__PURE__*/ React.createElement(Box, {
102
+ flexDirection: "column",
103
+ paddingTop: 1,
104
+ paddingBottom: 1
105
+ }, /*#__PURE__*/ React.createElement(Text, {
106
+ bold: true
107
+ }, "Running brains (", filteredBrains.length, ")", brainFilter ? ' matching "'.concat(brainFilter, '"') : '', ":"), /*#__PURE__*/ React.createElement(Box, {
108
+ marginTop: 1,
109
+ flexDirection: "column"
110
+ }, /*#__PURE__*/ React.createElement(Box, null, interactive && /*#__PURE__*/ React.createElement(Text, null, ' '), /*#__PURE__*/ React.createElement(Text, {
111
+ bold: true,
112
+ color: "cyan"
113
+ }, padRight(columns.brain.header, columns.brain.width)), /*#__PURE__*/ React.createElement(Text, null, " "), /*#__PURE__*/ React.createElement(Text, {
114
+ bold: true,
115
+ color: "cyan"
116
+ }, padRight(columns.runId.header, columns.runId.width)), /*#__PURE__*/ React.createElement(Text, null, " "), /*#__PURE__*/ React.createElement(Text, {
117
+ bold: true,
118
+ color: "cyan"
119
+ }, padRight(columns.status.header, columns.status.width)), /*#__PURE__*/ React.createElement(Text, null, " "), /*#__PURE__*/ React.createElement(Text, {
120
+ bold: true,
121
+ color: "cyan"
122
+ }, padRight(columns.started.header, columns.started.width)), /*#__PURE__*/ React.createElement(Text, null, " "), /*#__PURE__*/ React.createElement(Text, {
123
+ bold: true,
124
+ color: "cyan"
125
+ }, padRight(columns.duration.header, columns.duration.width))), /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, {
126
+ dimColor: true
127
+ }, '─'.repeat(97 + selectorWidth))), filteredBrains.map(function(brain, index) {
128
+ var duration = brain.startedAt ? formatDuration(brain.startedAt) : 'N/A';
129
+ var started = brain.startedAt ? formatRelativeTime(brain.startedAt) : formatRelativeTime(brain.createdAt);
130
+ var isSelected = interactive && index === selectedIndex;
131
+ return /*#__PURE__*/ React.createElement(Box, {
132
+ key: brain.brainRunId
133
+ }, interactive && /*#__PURE__*/ React.createElement(Text, {
134
+ color: isSelected ? 'cyan' : undefined
135
+ }, isSelected ? '▶ ' : ' '), /*#__PURE__*/ React.createElement(Text, {
136
+ color: isSelected ? 'cyan' : undefined
137
+ }, padRight(truncate(brain.brainTitle, columns.brain.width), columns.brain.width)), /*#__PURE__*/ React.createElement(Text, null, " "), /*#__PURE__*/ React.createElement(Text, {
138
+ dimColor: !isSelected,
139
+ color: isSelected ? 'cyan' : undefined
140
+ }, padRight(brain.brainRunId, columns.runId.width)), /*#__PURE__*/ React.createElement(Text, null, " "), /*#__PURE__*/ React.createElement(Text, {
141
+ color: isSelected ? 'cyan' : getStatusColor(brain.status)
142
+ }, padRight(brain.status, columns.status.width)), /*#__PURE__*/ React.createElement(Text, null, " "), /*#__PURE__*/ React.createElement(Text, {
143
+ dimColor: !isSelected,
144
+ color: isSelected ? 'cyan' : undefined
145
+ }, padRight(started, columns.started.width)), /*#__PURE__*/ React.createElement(Text, null, " "), /*#__PURE__*/ React.createElement(Text, {
146
+ color: isSelected ? 'cyan' : undefined
147
+ }, padRight(duration, columns.duration.width)));
148
+ })), /*#__PURE__*/ React.createElement(Box, {
149
+ marginTop: 1
150
+ }, /*#__PURE__*/ React.createElement(Text, {
151
+ dimColor: true
152
+ }, footer)));
153
+ };
@@ -0,0 +1,139 @@
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, useRef } from 'react';
48
+ import { Text, Box, useStdout } from 'ink';
49
+ import { EventSource } from 'eventsource';
50
+ import { getApiBaseUrl, isApiLocalDevMode } from '../commands/helpers.js';
51
+ import { ErrorComponent } from './error.js';
52
+ import { BrainTopTable } from './brain-top-table.js';
53
+ export var BrainTop = function(param) {
54
+ var brainFilter = param.brainFilter;
55
+ var write = useStdout().write;
56
+ var _useState = _sliced_to_array(useState([]), 2), runningBrains = _useState[0], setRunningBrains = _useState[1];
57
+ var _useState1 = _sliced_to_array(useState(null), 2), error = _useState1[0], setError = _useState1[1];
58
+ var _useState2 = _sliced_to_array(useState(false), 2), isConnected = _useState2[0], setIsConnected = _useState2[1];
59
+ var _useState3 = _sliced_to_array(useState(0), 2), setTick = _useState3[1];
60
+ var eventSourceRef = useRef(null);
61
+ var hasReceivedDataRef = useRef(false);
62
+ // Enter alternate screen buffer on mount, exit on unmount
63
+ // Skip in test environment to avoid interfering with test output capture
64
+ useEffect(function() {
65
+ if (process.env.NODE_ENV === 'test') {
66
+ return;
67
+ }
68
+ // Enter alternate screen buffer and clear
69
+ write('\x1B[?1049h\x1B[2J\x1B[H');
70
+ return function() {
71
+ // Exit alternate screen buffer
72
+ write('\x1B[?1049l');
73
+ };
74
+ }, [
75
+ write
76
+ ]);
77
+ // Update tick every second to refresh duration display
78
+ useEffect(function() {
79
+ var interval = setInterval(function() {
80
+ setTick(function(t) {
81
+ return t + 1;
82
+ });
83
+ }, 1000);
84
+ return function() {
85
+ return clearInterval(interval);
86
+ };
87
+ }, []);
88
+ // Connect to EventSource for live updates
89
+ useEffect(function() {
90
+ var baseUrl = getApiBaseUrl();
91
+ var url = "".concat(baseUrl, "/brains/watch");
92
+ var es = new EventSource(url);
93
+ eventSourceRef.current = es;
94
+ setIsConnected(false);
95
+ setError(null);
96
+ es.onopen = function() {
97
+ setIsConnected(true);
98
+ setError(null);
99
+ };
100
+ es.onmessage = function(event) {
101
+ try {
102
+ var data = JSON.parse(event.data);
103
+ setRunningBrains(data.runningBrains || []);
104
+ hasReceivedDataRef.current = true;
105
+ } catch (e) {
106
+ setError(new Error("Error parsing event data: ".concat(e.message)));
107
+ }
108
+ };
109
+ es.onerror = function() {
110
+ // Only show error if we haven't received any data yet
111
+ // (nock closes connection after sending data, triggering reconnect error)
112
+ if (!hasReceivedDataRef.current) {
113
+ var errorMessage = isApiLocalDevMode() ? 'Error connecting to the local development server. Is it running? Start it with "positronic server" or "px s".' : 'Connection failed. Please check your network connection and verify the project URL is correct.';
114
+ setError(new Error(errorMessage));
115
+ setIsConnected(false);
116
+ }
117
+ es.close();
118
+ };
119
+ return function() {
120
+ es.close();
121
+ };
122
+ }, []);
123
+ if (error) {
124
+ return /*#__PURE__*/ React.createElement(ErrorComponent, {
125
+ error: {
126
+ title: 'Connection Error',
127
+ message: error.message,
128
+ details: error.stack
129
+ }
130
+ });
131
+ }
132
+ if (!isConnected) {
133
+ return /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, null, "Connecting to watch service..."));
134
+ }
135
+ return /*#__PURE__*/ React.createElement(BrainTopTable, {
136
+ runningBrains: runningBrains,
137
+ brainFilter: brainFilter
138
+ });
139
+ };
@@ -45,38 +45,15 @@ function _unsupported_iterable_to_array(o, minLen) {
45
45
  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
46
46
  }
47
47
  import React, { useState, useEffect } from 'react';
48
- import { Box, Text, useInput, useApp, useStdin } from 'ink';
49
- // Separate component for interactive selection that uses useInput
48
+ import { Box, Text, useStdin } from 'ink';
49
+ import { SelectList } from './select-list.js';
50
+ // Separate component for interactive selection
50
51
  var InteractiveProjectSelect = function(param) {
51
52
  var projects = param.projects, currentProject = param.currentProject, projectConfig = param.projectConfig;
52
- var _useState = _sliced_to_array(useState(function() {
53
- var currentIndex = projects.findIndex(function(p) {
54
- return p.name === currentProject;
55
- });
56
- return currentIndex >= 0 ? currentIndex : 0;
57
- }), 2), selectedIndex = _useState[0], setSelectedIndex = _useState[1];
58
- var _useState1 = _sliced_to_array(useState(null), 2), result = _useState1[0], setResult = _useState1[1];
59
- var exit = useApp().exit;
60
- useInput(function(input, key) {
61
- if (key.upArrow) {
62
- setSelectedIndex(function(prev) {
63
- return (prev - 1 + projects.length) % projects.length;
64
- });
65
- } else if (key.downArrow) {
66
- setSelectedIndex(function(prev) {
67
- return (prev + 1) % projects.length;
68
- });
69
- } else if (key.return) {
70
- var selectedProject = projects[selectedIndex];
71
- var selectResult = projectConfig.selectProject(selectedProject.name);
72
- setResult(selectResult);
73
- } else if (input === 'q' || key.escape) {
74
- exit();
75
- }
76
- });
53
+ var _useState = _sliced_to_array(useState(null), 2), result = _useState[0], setResult = _useState[1];
54
+ var _useState1 = _sliced_to_array(useState(null), 2), selectedProject = _useState1[0], setSelectedProject = _useState1[1];
77
55
  // If selection was made, show success
78
- if (result && result.success) {
79
- var selectedProject = projects[selectedIndex];
56
+ if (result && result.success && selectedProject) {
80
57
  return /*#__PURE__*/ React.createElement(Box, {
81
58
  flexDirection: "column"
82
59
  }, /*#__PURE__*/ React.createElement(Text, {
@@ -91,29 +68,32 @@ var InteractiveProjectSelect = function(param) {
91
68
  bold: true
92
69
  }, "URL:"), " ", selectedProject.url)));
93
70
  }
94
- // Show interactive selection UI
95
- return /*#__PURE__*/ React.createElement(Box, {
96
- flexDirection: "column"
97
- }, /*#__PURE__*/ React.createElement(Text, {
98
- bold: true
99
- }, "Select a project:"), /*#__PURE__*/ React.createElement(Box, {
100
- marginTop: 1,
101
- flexDirection: "column"
102
- }, projects.map(function(project, index) {
103
- var isSelected = index === selectedIndex;
104
- var isCurrent = project.name === currentProject;
105
- return /*#__PURE__*/ React.createElement(Box, {
106
- key: project.name
107
- }, /*#__PURE__*/ React.createElement(Text, {
108
- color: isSelected ? 'cyan' : undefined
109
- }, isSelected ? '▶ ' : ' ', project.name, isCurrent && /*#__PURE__*/ React.createElement(Text, {
110
- color: "green"
111
- }, " (current)")));
112
- })), /*#__PURE__*/ React.createElement(Box, {
113
- marginTop: 1
114
- }, /*#__PURE__*/ React.createElement(Text, {
115
- dimColor: true
116
- }, "Use arrow keys to navigate, Enter to select, q to quit")));
71
+ var currentIndex = projects.findIndex(function(p) {
72
+ return p.name === currentProject;
73
+ });
74
+ return /*#__PURE__*/ React.createElement(SelectList, {
75
+ items: projects.map(function(p) {
76
+ return {
77
+ id: p.name,
78
+ label: p.name,
79
+ extra: p.name === currentProject ? /*#__PURE__*/ React.createElement(Text, {
80
+ color: "green"
81
+ }, " (current)") : undefined
82
+ };
83
+ }),
84
+ header: "Select a project:",
85
+ initialIndex: currentIndex >= 0 ? currentIndex : 0,
86
+ onSelect: function(item) {
87
+ var project = projects.find(function(p) {
88
+ return p.name === item.label;
89
+ });
90
+ if (project) {
91
+ setSelectedProject(project);
92
+ var selectResult = projectConfig.selectProject(project.name);
93
+ setResult(selectResult);
94
+ }
95
+ }
96
+ });
117
97
  };
118
98
  export var ProjectSelect = function(param) {
119
99
  var name = param.name, projectConfig = param.projectConfig;
@@ -0,0 +1,102 @@
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 } from 'react';
48
+ import { Box, Text, useInput, useApp } from 'ink';
49
+ /**
50
+ * SelectList - A reusable component for keyboard-navigable list selection.
51
+ *
52
+ * Manages its own selectedIndex state and handles keyboard navigation.
53
+ * Use arrow keys to navigate, Enter to select, q/Escape to cancel.
54
+ */ export var SelectList = function(param) {
55
+ var items = param.items, header = param.header, onSelect = param.onSelect, onCancel = param.onCancel, _param_initialIndex = param.initialIndex, initialIndex = _param_initialIndex === void 0 ? 0 : _param_initialIndex, _param_footer = param.footer, footer = _param_footer === void 0 ? 'Use arrow keys to navigate, Enter to select, q to quit' : _param_footer;
56
+ var _useState = _sliced_to_array(useState(initialIndex), 2), selectedIndex = _useState[0], setSelectedIndex = _useState[1];
57
+ var exit = useApp().exit;
58
+ useInput(function(input, key) {
59
+ if (key.upArrow) {
60
+ setSelectedIndex(function(prev) {
61
+ return (prev - 1 + items.length) % items.length;
62
+ });
63
+ } else if (key.downArrow) {
64
+ setSelectedIndex(function(prev) {
65
+ return (prev + 1) % items.length;
66
+ });
67
+ } else if (key.return) {
68
+ onSelect(items[selectedIndex], selectedIndex);
69
+ } else if (input === 'q' || key.escape) {
70
+ if (onCancel) {
71
+ onCancel();
72
+ } else {
73
+ exit();
74
+ }
75
+ }
76
+ });
77
+ return /*#__PURE__*/ React.createElement(Box, {
78
+ flexDirection: "column"
79
+ }, /*#__PURE__*/ React.createElement(Text, {
80
+ bold: true
81
+ }, header), /*#__PURE__*/ React.createElement(Box, {
82
+ marginTop: 1,
83
+ flexDirection: "column"
84
+ }, items.map(function(item, index) {
85
+ var isSelected = index === selectedIndex;
86
+ return /*#__PURE__*/ React.createElement(Box, {
87
+ key: item.id,
88
+ flexDirection: "column",
89
+ marginBottom: item.description ? 1 : 0
90
+ }, /*#__PURE__*/ React.createElement(Text, {
91
+ color: isSelected ? 'cyan' : undefined
92
+ }, isSelected ? '▶ ' : ' ', /*#__PURE__*/ React.createElement(Text, {
93
+ bold: true
94
+ }, item.label), item.extra), item.description && /*#__PURE__*/ React.createElement(Text, {
95
+ dimColor: true
96
+ }, ' ', item.description));
97
+ })), /*#__PURE__*/ React.createElement(Box, {
98
+ marginTop: 1
99
+ }, /*#__PURE__*/ React.createElement(Text, {
100
+ dimColor: true
101
+ }, footer)));
102
+ };