@runloop/rl-cli 1.7.0 → 1.8.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.
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React from "react";
3
3
  import { Box, Text, useInput, useApp, useStdout } from "ink";
4
4
  import figures from "figures";
@@ -10,7 +10,8 @@ import { colors } from "../utils/theme.js";
10
10
  import { execCommand } from "../utils/exec.js";
11
11
  import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
12
12
  import { useUpdateCheck } from "../hooks/useUpdateCheck.js";
13
- const menuItems = [
13
+ import { useBetaFeatures } from "../store/betaFeatureStore.js";
14
+ const allMenuItems = [
14
15
  {
15
16
  key: "devboxes",
16
17
  label: "Devboxes",
@@ -40,10 +41,18 @@ const menuItems = [
40
41
  color: colors.secondary,
41
42
  },
42
43
  {
43
- key: "network-policies",
44
- label: "Network Policies",
45
- description: "Manage egress network access rules",
46
- icon: "",
44
+ key: "benchmarks",
45
+ label: "Benchmarks",
46
+ description: "Performance testing and evaluation",
47
+ icon: "",
48
+ color: colors.success,
49
+ betaFeature: "benchmarks",
50
+ },
51
+ {
52
+ key: "settings",
53
+ label: "Settings",
54
+ description: "Network policies, secrets, and more",
55
+ icon: "⚙",
47
56
  color: colors.info,
48
57
  },
49
58
  ];
@@ -56,10 +65,22 @@ function getLayoutMode(height) {
56
65
  return "compact"; // No banner + simple items + short descriptions
57
66
  return "minimal"; // No banner + labels only
58
67
  }
68
+ // Helper component for rendering beta badge
69
+ const BetaBadge = () => (_jsxs(Text, { color: colors.warning, bold: true, children: [" ", "[BETA]"] }));
59
70
  export const MainMenu = ({ onSelect }) => {
60
71
  const { exit } = useApp();
61
72
  const [selectedIndex, setSelectedIndex] = React.useState(0);
62
73
  const { stdout } = useStdout();
74
+ const { isFeatureEnabled } = useBetaFeatures();
75
+ // Filter menu items based on beta feature flags
76
+ const menuItems = React.useMemo(() => {
77
+ return allMenuItems.filter((item) => {
78
+ if (item.betaFeature) {
79
+ return isFeatureEnabled(item.betaFeature);
80
+ }
81
+ return true;
82
+ });
83
+ }, [isFeatureEnabled]);
63
84
  // Get raw terminal dimensions, responding to resize events
64
85
  // Default to 20 rows / 80 cols if we can't detect
65
86
  const getTerminalDimensions = React.useCallback(() => {
@@ -89,6 +110,19 @@ export const MainMenu = ({ onSelect }) => {
89
110
  const { updateAvailable } = useUpdateCheck();
90
111
  // Handle Ctrl+C to exit
91
112
  useExitOnCtrlC();
113
+ // Helper to select menu item by key (if available in filtered list)
114
+ const selectByKey = React.useCallback((key) => {
115
+ if (menuItems.some((item) => item.key === key)) {
116
+ onSelect(key);
117
+ }
118
+ }, [menuItems, onSelect]);
119
+ // Helper to select menu item by number (1-indexed, based on filtered list)
120
+ const selectByNumber = React.useCallback((num) => {
121
+ const index = num - 1;
122
+ if (index >= 0 && index < menuItems.length) {
123
+ onSelect(menuItems[index].key);
124
+ }
125
+ }, [menuItems, onSelect]);
92
126
  useInput((input, key) => {
93
127
  if (key.upArrow && selectedIndex > 0) {
94
128
  setSelectedIndex(selectedIndex - 1);
@@ -102,20 +136,26 @@ export const MainMenu = ({ onSelect }) => {
102
136
  else if (key.escape) {
103
137
  exit();
104
138
  }
105
- else if (input === "d" || input === "1") {
106
- onSelect("devboxes");
139
+ else if (input === "d") {
140
+ selectByKey("devboxes");
141
+ }
142
+ else if (input === "b") {
143
+ selectByKey("blueprints");
144
+ }
145
+ else if (input === "s") {
146
+ selectByKey("snapshots");
107
147
  }
108
- else if (input === "b" || input === "2") {
109
- onSelect("blueprints");
148
+ else if (input === "o") {
149
+ selectByKey("objects");
110
150
  }
111
- else if (input === "s" || input === "3") {
112
- onSelect("snapshots");
151
+ else if (input === "e") {
152
+ selectByKey("benchmarks");
113
153
  }
114
- else if (input === "o" || input === "4") {
115
- onSelect("objects");
154
+ else if (input === "n") {
155
+ selectByKey("settings");
116
156
  }
117
- else if (input === "n" || input === "5") {
118
- onSelect("network-policies");
157
+ else if (input >= "1" && input <= "9") {
158
+ selectByNumber(parseInt(input, 10));
119
159
  }
120
160
  else if (input === "u" && updateAvailable) {
121
161
  // Release terminal and exec into update command (never returns)
@@ -127,8 +167,9 @@ export const MainMenu = ({ onSelect }) => {
127
167
  });
128
168
  const layoutMode = getLayoutMode(terminalHeight);
129
169
  // Navigation tips for all layouts
170
+ const quickSelectRange = `1-${menuItems.length}`;
130
171
  const navTips = (_jsx(NavigationTips, { showArrows: true, paddingX: 2, tips: [
131
- { key: "1-5", label: "Quick select" },
172
+ { key: quickSelectRange, label: "Quick select" },
132
173
  { key: "Enter", label: "Select" },
133
174
  { key: "Esc", label: "Quit" },
134
175
  { key: "u", label: "Update", condition: !!updateAvailable },
@@ -137,14 +178,14 @@ export const MainMenu = ({ onSelect }) => {
137
178
  if (layoutMode === "minimal") {
138
179
  return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { paddingX: 2, children: [_jsx(Text, { color: colors.primary, bold: true, children: "RUNLOOP" }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "v", VERSION] })] }), _jsx(Box, { flexDirection: "column", paddingX: 2, children: menuItems.map((item, index) => {
139
180
  const isSelected = index === selectedIndex;
140
- return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsxs(Text, { color: item.color, children: [" ", item.icon, " "] }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", index + 1, "]"] })] }, item.key));
181
+ return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsxs(Text, { color: item.color, children: [" ", item.icon, " "] }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), item.betaFeature && _jsx(BetaBadge, {}), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", index + 1, "]"] })] }, item.key));
141
182
  }) }), navTips] }));
142
183
  }
143
184
  // Compact layout - no banner, simple items with descriptions (or no descriptions if narrow)
144
185
  if (layoutMode === "compact") {
145
186
  return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Breadcrumb, { items: [{ label: "Home", active: true }], showVersionCheck: true }), _jsxs(Box, { paddingX: 2, children: [_jsx(Text, { color: colors.primary, bold: true, children: "RUNLOOP.ai" }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022 v", VERSION] })] }), _jsx(Box, { flexDirection: "column", paddingX: 2, children: menuItems.map((item, index) => {
146
187
  const isSelected = index === selectedIndex;
147
- return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsxs(Text, { color: item.color, children: [" ", item.icon, " "] }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), _jsx(Text, { color: colors.textDim, dimColor: true, children: isNarrow
188
+ return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsxs(Text, { color: item.color, children: [" ", item.icon, " "] }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), item.betaFeature && _jsx(BetaBadge, {}), _jsx(Text, { color: colors.textDim, dimColor: true, children: isNarrow
148
189
  ? ` [${index + 1}]`
149
190
  : ` - ${item.description} [${index + 1}]` })] }, item.key));
150
191
  }) }), navTips] }));
@@ -155,7 +196,7 @@ export const MainMenu = ({ onSelect }) => {
155
196
  ? ` • v${VERSION}`
156
197
  : ` • Cloud development environments • v${VERSION}` })] }), _jsx(Box, { flexDirection: "column", paddingX: 2, children: menuItems.map((item, index) => {
157
198
  const isSelected = index === selectedIndex;
158
- return (_jsxs(Box, { marginBottom: 0, children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsx(Text, { children: " " }), _jsx(Text, { color: item.color, bold: true, children: item.icon }), _jsx(Text, { children: " " }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), !isNarrow && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "- ", item.description] })), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", index + 1, "]"] })] }, item.key));
199
+ return (_jsxs(Box, { marginBottom: 0, children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsx(Text, { children: " " }), _jsx(Text, { color: item.color, bold: true, children: item.icon }), _jsx(Text, { children: " " }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), item.betaFeature && _jsx(BetaBadge, {}), !isNarrow && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "- ", item.description] })), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", index + 1, "]"] })] }, item.key));
159
200
  }) }), navTips] }));
160
201
  }
161
202
  // Full layout - big banner, bordered items (or simple items if narrow)
@@ -163,11 +204,11 @@ export const MainMenu = ({ onSelect }) => {
163
204
  // Narrow layout - no borders, compact items
164
205
  _jsx(Box, { flexDirection: "column", marginTop: 1, children: menuItems.map((item, index) => {
165
206
  const isSelected = index === selectedIndex;
166
- return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsxs(Text, { color: item.color, children: [" ", item.icon, " "] }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", index + 1, "]"] })] }, item.key));
207
+ return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsxs(Text, { color: item.color, children: [" ", item.icon, " "] }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), item.betaFeature && _jsx(BetaBadge, {}), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", index + 1, "]"] })] }, item.key));
167
208
  }) })) : (
168
209
  // Wide layout - bordered items with descriptions
169
210
  menuItems.map((item, index) => {
170
211
  const isSelected = index === selectedIndex;
171
- return (_jsxs(Box, { paddingX: 2, paddingY: 0, borderStyle: "single", borderColor: isSelected ? item.color : colors.border, marginTop: index === 0 ? 1 : 0, flexShrink: 0, children: [isSelected && (_jsxs(_Fragment, { children: [_jsx(Text, { color: item.color, bold: true, children: figures.pointer }), _jsx(Text, { children: " " })] })), _jsx(Text, { color: item.color, bold: true, children: item.icon }), _jsx(Text, { children: " " }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), _jsx(Text, { color: colors.textDim, children: " " }), _jsx(Text, { color: colors.textDim, dimColor: true, children: item.description }), _jsx(Text, { children: " " }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["[", index + 1, "]"] })] }, item.key));
212
+ return (_jsxs(Box, { paddingX: 2, paddingY: 0, borderStyle: "single", borderColor: isSelected ? item.color : colors.border, marginTop: index === 0 ? 1 : 0, flexShrink: 0, children: [isSelected && (_jsxs(_Fragment, { children: [_jsx(Text, { color: item.color, bold: true, children: figures.pointer }), _jsx(Text, { children: " " })] })), _jsx(Text, { color: item.color, bold: true, children: item.icon }), _jsx(Text, { children: " " }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), item.betaFeature && _jsx(BetaBadge, {}), _jsx(Text, { color: colors.textDim, children: " " }), _jsx(Text, { color: colors.textDim, dimColor: true, children: item.description }), _jsx(Text, { children: " " }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["[", index + 1, "]"] })] }, item.key));
172
213
  }))] }), navTips] }));
173
214
  };
@@ -0,0 +1,185 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * SecretCreatePage - Form for creating a new secret
4
+ */
5
+ import React from "react";
6
+ import { Box, Text, useInput } from "ink";
7
+ import figures from "figures";
8
+ import { getClient } from "../utils/client.js";
9
+ import { SpinnerComponent } from "./Spinner.js";
10
+ import { ErrorMessage } from "./ErrorMessage.js";
11
+ import { SuccessMessage } from "./SuccessMessage.js";
12
+ import { Breadcrumb } from "./Breadcrumb.js";
13
+ import { NavigationTips } from "./NavigationTips.js";
14
+ import { FormTextInput, FormActionButton } from "./form/index.js";
15
+ import { colors } from "../utils/theme.js";
16
+ import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
17
+ export const SecretCreatePage = ({ onBack, onCreate, }) => {
18
+ const [currentField, setCurrentField] = React.useState("submit");
19
+ const [formData, setFormData] = React.useState({
20
+ name: "",
21
+ value: "",
22
+ });
23
+ const [submitting, setSubmitting] = React.useState(false);
24
+ const [result, setResult] = React.useState(null);
25
+ const [error, setError] = React.useState(null);
26
+ const [validationError, setValidationError] = React.useState(null);
27
+ const fields = [
28
+ { key: "submit", label: "Create Secret", type: "action" },
29
+ { key: "name", label: "Name (required)", type: "text" },
30
+ { key: "value", label: "Value (required)", type: "password" },
31
+ ];
32
+ const currentFieldIndex = fields.findIndex((f) => f.key === currentField);
33
+ // Handle Ctrl+C to exit
34
+ useExitOnCtrlC();
35
+ // Main form input handler
36
+ useInput((input, key) => {
37
+ // Handle result screen
38
+ if (result) {
39
+ if (input === "q" || key.escape || key.return) {
40
+ if (onCreate) {
41
+ onCreate(result);
42
+ }
43
+ else {
44
+ onBack();
45
+ }
46
+ }
47
+ return;
48
+ }
49
+ // Handle error screen
50
+ if (error) {
51
+ if (input === "r" || key.return) {
52
+ // Retry - clear error and return to form
53
+ setError(null);
54
+ }
55
+ else if (input === "q" || key.escape) {
56
+ // Quit - go back to list
57
+ onBack();
58
+ }
59
+ return;
60
+ }
61
+ // Handle submitting state
62
+ if (submitting) {
63
+ return;
64
+ }
65
+ // Back to list
66
+ if (input === "q" || key.escape) {
67
+ onBack();
68
+ return;
69
+ }
70
+ // Submit form with Ctrl+S
71
+ if (input === "s" && key.ctrl) {
72
+ handleSubmit();
73
+ return;
74
+ }
75
+ // Handle Enter on any field to submit
76
+ if (key.return) {
77
+ handleSubmit();
78
+ return;
79
+ }
80
+ // Navigation between fields (up/down arrows and tab/shift+tab)
81
+ if ((key.upArrow || (key.tab && key.shift)) && currentFieldIndex > 0) {
82
+ setCurrentField(fields[currentFieldIndex - 1].key);
83
+ return;
84
+ }
85
+ if ((key.downArrow || (key.tab && !key.shift)) &&
86
+ currentFieldIndex < fields.length - 1) {
87
+ setCurrentField(fields[currentFieldIndex + 1].key);
88
+ return;
89
+ }
90
+ });
91
+ const handleSubmit = async () => {
92
+ // Validate required fields
93
+ if (!formData.name.trim()) {
94
+ setValidationError("Name is required");
95
+ setCurrentField("name");
96
+ return;
97
+ }
98
+ if (!formData.value) {
99
+ setValidationError("Value is required");
100
+ setCurrentField("value");
101
+ return;
102
+ }
103
+ setSubmitting(true);
104
+ setError(null);
105
+ setValidationError(null);
106
+ try {
107
+ const client = getClient();
108
+ const secret = await client.secrets.create({
109
+ name: formData.name.trim(),
110
+ value: formData.value,
111
+ });
112
+ setResult(secret);
113
+ }
114
+ catch (err) {
115
+ setError(err);
116
+ }
117
+ finally {
118
+ setSubmitting(false);
119
+ }
120
+ };
121
+ // Result screen
122
+ if (result) {
123
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
124
+ { label: "Settings" },
125
+ { label: "Secrets" },
126
+ { label: "Create", active: true },
127
+ ] }), _jsx(SuccessMessage, { message: "Secret created successfully!" }), _jsxs(Box, { marginLeft: 2, flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: ["ID:", " "] }), _jsx(Text, { color: colors.idColor, children: result.id })] }), _jsx(Box, { children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Name: ", result.name] }) })] }), _jsx(Box, { marginTop: 1, marginLeft: 2, children: _jsxs(Text, { color: colors.info, children: [figures.info, " The secret value has been securely stored and cannot be retrieved."] }) }), _jsx(NavigationTips, { tips: [{ key: "Enter/q/esc", label: "View secret details" }] })] }));
128
+ }
129
+ // Error screen
130
+ if (error) {
131
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
132
+ { label: "Settings" },
133
+ { label: "Secrets" },
134
+ { label: "Create", active: true },
135
+ ] }), _jsx(ErrorMessage, { message: "Failed to create secret", error: error }), _jsx(NavigationTips, { tips: [
136
+ { key: "Enter/r", label: "Retry" },
137
+ { key: "q/esc", label: "Cancel" },
138
+ ] })] }));
139
+ }
140
+ // Submitting screen
141
+ if (submitting) {
142
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
143
+ { label: "Settings" },
144
+ { label: "Secrets" },
145
+ { label: "Create", active: true },
146
+ ] }), _jsx(SpinnerComponent, { message: "Creating secret..." })] }));
147
+ }
148
+ // Form screen
149
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
150
+ { label: "Settings" },
151
+ { label: "Secrets" },
152
+ { label: "Create", active: true },
153
+ ] }), _jsx(Box, { borderStyle: "round", borderColor: colors.info, paddingX: 1, paddingY: 0, marginBottom: 1, children: _jsxs(Text, { color: colors.info, children: [figures.info, " ", _jsx(Text, { bold: true, children: "Note:" }), " Secret values are", " ", _jsx(Text, { bold: true, children: "write-only" }), ". Once created, the value cannot be retrieved or viewed. To change a secret, delete it and create a new one."] }) }), _jsx(Box, { flexDirection: "column", marginBottom: 1, children: fields.map((field) => {
154
+ const isActive = currentField === field.key;
155
+ if (field.type === "action") {
156
+ return (_jsx(FormActionButton, { label: field.label, isActive: isActive, hint: "[Enter to create]" }, field.key));
157
+ }
158
+ if (field.type === "text") {
159
+ const value = formData[field.key];
160
+ const hasError = field.key === "name" && validationError === "Name is required";
161
+ return (_jsx(FormTextInput, { label: field.label, value: value, onChange: (newValue) => {
162
+ setFormData({ ...formData, [field.key]: newValue });
163
+ if (validationError) {
164
+ setValidationError(null);
165
+ }
166
+ }, onSubmit: handleSubmit, isActive: isActive, placeholder: "my-secret-name", error: hasError ? validationError : undefined }, field.key));
167
+ }
168
+ if (field.type === "password") {
169
+ const value = formData[field.key];
170
+ const hasError = field.key === "value" && validationError === "Value is required";
171
+ // Display masked value when not active
172
+ const maskedValue = "*".repeat(value.length);
173
+ return (_jsx(FormTextInput, { label: field.label, value: isActive ? value : maskedValue, onChange: (newValue) => {
174
+ setFormData({ ...formData, [field.key]: newValue });
175
+ if (validationError) {
176
+ setValidationError(null);
177
+ }
178
+ }, onSubmit: handleSubmit, isActive: isActive, placeholder: "Enter secret value", error: hasError ? validationError : undefined }, field.key));
179
+ }
180
+ return null;
181
+ }) }), _jsx(NavigationTips, { showArrows: true, tips: [
182
+ { key: "Enter", label: "Create" },
183
+ { key: "q", label: "Cancel" },
184
+ ] })] }));
185
+ };
@@ -0,0 +1,85 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { Box, Text, useInput, useApp, useStdout } from "ink";
4
+ import figures from "figures";
5
+ import { Breadcrumb } from "./Breadcrumb.js";
6
+ import { NavigationTips } from "./NavigationTips.js";
7
+ import { colors } from "../utils/theme.js";
8
+ import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
9
+ const settingsMenuItems = [
10
+ {
11
+ key: "network-policies",
12
+ label: "Network Policies",
13
+ description: "Manage egress network access rules",
14
+ icon: "◇",
15
+ color: colors.info,
16
+ },
17
+ {
18
+ key: "secrets",
19
+ label: "Secrets",
20
+ description: "Manage sensitive values and credentials",
21
+ icon: "◆",
22
+ color: colors.warning,
23
+ },
24
+ ];
25
+ export const SettingsMenu = ({ onSelect, onBack }) => {
26
+ const { exit } = useApp();
27
+ const [selectedIndex, setSelectedIndex] = React.useState(0);
28
+ const { stdout } = useStdout();
29
+ // Get terminal dimensions for responsive layout
30
+ const getTerminalDimensions = React.useCallback(() => {
31
+ return {
32
+ height: stdout?.rows && stdout.rows > 0 ? stdout.rows : 20,
33
+ width: stdout?.columns && stdout.columns > 0 ? stdout.columns : 80,
34
+ };
35
+ }, [stdout]);
36
+ const [terminalDimensions, setTerminalDimensions] = React.useState(getTerminalDimensions);
37
+ React.useEffect(() => {
38
+ setTerminalDimensions(getTerminalDimensions());
39
+ if (!stdout)
40
+ return;
41
+ const handleResize = () => {
42
+ setTerminalDimensions(getTerminalDimensions());
43
+ };
44
+ stdout.on("resize", handleResize);
45
+ return () => {
46
+ stdout.off("resize", handleResize);
47
+ };
48
+ }, [stdout, getTerminalDimensions]);
49
+ const terminalWidth = terminalDimensions.width;
50
+ const isNarrow = terminalWidth < 70;
51
+ // Handle Ctrl+C to exit
52
+ useExitOnCtrlC();
53
+ useInput((input, key) => {
54
+ if (key.upArrow && selectedIndex > 0) {
55
+ setSelectedIndex(selectedIndex - 1);
56
+ }
57
+ else if (key.downArrow && selectedIndex < settingsMenuItems.length - 1) {
58
+ setSelectedIndex(selectedIndex + 1);
59
+ }
60
+ else if (key.return) {
61
+ onSelect(settingsMenuItems[selectedIndex].key);
62
+ }
63
+ else if (key.escape) {
64
+ onBack();
65
+ }
66
+ else if (input === "n" || input === "1") {
67
+ onSelect("network-policies");
68
+ }
69
+ else if (input === "s" || input === "2") {
70
+ onSelect("secrets");
71
+ }
72
+ else if (input === "q") {
73
+ exit();
74
+ }
75
+ });
76
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Breadcrumb, { items: [{ label: "Home" }, { label: "Settings", active: true }] }), _jsxs(Box, { paddingX: 2, marginBottom: 1, children: [_jsx(Text, { color: colors.primary, bold: true, children: "Settings" }), _jsx(Text, { color: colors.textDim, dimColor: true, children: isNarrow ? "" : " • Configure your environment" })] }), _jsx(Box, { flexDirection: "column", paddingX: 2, children: settingsMenuItems.map((item, index) => {
77
+ const isSelected = index === selectedIndex;
78
+ return (_jsxs(Box, { marginBottom: 0, children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsx(Text, { children: " " }), _jsx(Text, { color: item.color, bold: true, children: item.icon }), _jsx(Text, { children: " " }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), !isNarrow && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "- ", item.description] })), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", index + 1, "]"] })] }, item.key));
79
+ }) }), _jsx(NavigationTips, { showArrows: true, paddingX: 2, tips: [
80
+ { key: "1-2", label: "Quick select" },
81
+ { key: "Enter", label: "Select" },
82
+ { key: "Esc", label: "Back" },
83
+ { key: "q", label: "Quit" },
84
+ ] })] }));
85
+ };
@@ -104,6 +104,79 @@ export const getStatusDisplay = (status) => {
104
104
  text: "BUILDING ",
105
105
  label: "Building: In Progress",
106
106
  };
107
+ // === BENCHMARK/SCENARIO STATES ===
108
+ case "completed":
109
+ return {
110
+ icon: figures.tick,
111
+ color: colors.success,
112
+ text: "COMPLETED ",
113
+ label: "Completed",
114
+ };
115
+ case "canceled":
116
+ return {
117
+ icon: figures.cross,
118
+ color: colors.textDim,
119
+ text: "CANCELED ",
120
+ label: "Canceled",
121
+ };
122
+ case "scoring":
123
+ return {
124
+ icon: figures.arrowUp,
125
+ color: colors.warning,
126
+ text: "SCORING ",
127
+ label: "Scoring",
128
+ };
129
+ case "scored":
130
+ return {
131
+ icon: figures.tick,
132
+ color: colors.info,
133
+ text: "SCORED ",
134
+ label: "Scored",
135
+ };
136
+ case "timeout":
137
+ return {
138
+ icon: figures.warning,
139
+ color: colors.error,
140
+ text: "TIMEOUT ",
141
+ label: "Timeout",
142
+ };
143
+ // === GENERIC STATES ===
144
+ case "active":
145
+ return {
146
+ icon: figures.tick,
147
+ color: colors.success,
148
+ text: "ACTIVE ",
149
+ label: "Active",
150
+ };
151
+ // === STORAGE OBJECT STATES ===
152
+ case "UPLOADING":
153
+ return {
154
+ icon: figures.arrowUp,
155
+ color: colors.warning,
156
+ text: "UPLOADING ",
157
+ label: "Uploading",
158
+ };
159
+ case "READ_ONLY":
160
+ return {
161
+ icon: figures.tick,
162
+ color: colors.success,
163
+ text: "READ_ONLY ",
164
+ label: "Read Only",
165
+ };
166
+ case "DELETED":
167
+ return {
168
+ icon: figures.cross,
169
+ color: colors.textDim,
170
+ text: "DELETED ",
171
+ label: "Deleted",
172
+ };
173
+ case "ERROR":
174
+ return {
175
+ icon: figures.warning,
176
+ color: colors.error,
177
+ text: "ERROR ",
178
+ label: "Error",
179
+ };
107
180
  default:
108
181
  // Truncate and pad any unknown status to 10 chars to match column width
109
182
  const truncated = status.toUpperCase().slice(0, 10);