@runloop/rl-cli 1.8.0 → 1.10.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.
- package/README.md +21 -7
- package/dist/cli.js +0 -0
- package/dist/commands/blueprint/delete.js +21 -0
- package/dist/commands/blueprint/list.js +226 -174
- package/dist/commands/blueprint/prune.js +13 -28
- package/dist/commands/devbox/create.js +41 -0
- package/dist/commands/devbox/list.js +142 -110
- package/dist/commands/devbox/rsync.js +69 -41
- package/dist/commands/devbox/scp.js +180 -39
- package/dist/commands/devbox/tunnel.js +4 -19
- package/dist/commands/gateway-config/create.js +53 -0
- package/dist/commands/gateway-config/delete.js +21 -0
- package/dist/commands/gateway-config/get.js +18 -0
- package/dist/commands/gateway-config/list.js +493 -0
- package/dist/commands/gateway-config/update.js +70 -0
- package/dist/commands/snapshot/list.js +11 -2
- package/dist/commands/snapshot/prune.js +265 -0
- package/dist/components/BenchmarkMenu.js +23 -3
- package/dist/components/DetailedInfoView.js +20 -0
- package/dist/components/DevboxActionsMenu.js +26 -62
- package/dist/components/DevboxCreatePage.js +763 -15
- package/dist/components/DevboxDetailPage.js +73 -24
- package/dist/components/GatewayConfigCreatePage.js +272 -0
- package/dist/components/LogsViewer.js +6 -40
- package/dist/components/ResourceDetailPage.js +143 -160
- package/dist/components/ResourceListView.js +3 -33
- package/dist/components/ResourcePicker.js +234 -0
- package/dist/components/SecretCreatePage.js +71 -27
- package/dist/components/SettingsMenu.js +12 -2
- package/dist/components/StateHistory.js +1 -20
- package/dist/components/StatusBadge.js +9 -2
- package/dist/components/StreamingLogsViewer.js +8 -42
- package/dist/components/form/FormTextInput.js +4 -2
- package/dist/components/resourceDetailTypes.js +18 -0
- package/dist/hooks/useInputHandler.js +103 -0
- package/dist/router/Router.js +79 -2
- package/dist/screens/BenchmarkDetailScreen.js +163 -0
- package/dist/screens/BenchmarkJobCreateScreen.js +524 -0
- package/dist/screens/BenchmarkJobDetailScreen.js +614 -0
- package/dist/screens/BenchmarkJobListScreen.js +479 -0
- package/dist/screens/BenchmarkListScreen.js +266 -0
- package/dist/screens/BenchmarkMenuScreen.js +6 -0
- package/dist/screens/BenchmarkRunDetailScreen.js +258 -22
- package/dist/screens/BenchmarkRunListScreen.js +21 -1
- package/dist/screens/BlueprintDetailScreen.js +5 -1
- package/dist/screens/DevboxCreateScreen.js +2 -2
- package/dist/screens/GatewayConfigDetailScreen.js +236 -0
- package/dist/screens/GatewayConfigListScreen.js +7 -0
- package/dist/screens/ScenarioRunDetailScreen.js +6 -0
- package/dist/screens/SecretDetailScreen.js +26 -2
- package/dist/screens/SettingsMenuScreen.js +3 -0
- package/dist/screens/SnapshotDetailScreen.js +6 -0
- package/dist/services/agentService.js +42 -0
- package/dist/services/benchmarkJobService.js +122 -0
- package/dist/services/benchmarkService.js +47 -0
- package/dist/services/gatewayConfigService.js +153 -0
- package/dist/services/scenarioService.js +34 -0
- package/dist/store/benchmarkJobStore.js +66 -0
- package/dist/store/benchmarkStore.js +63 -0
- package/dist/store/gatewayConfigStore.js +83 -0
- package/dist/utils/browser.js +22 -0
- package/dist/utils/clipboard.js +41 -0
- package/dist/utils/commands.js +105 -9
- package/dist/utils/gatewayConfigValidation.js +58 -0
- package/dist/utils/time.js +121 -0
- package/package.json +43 -43
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* DevboxDetailPage - Detail page for devboxes
|
|
4
4
|
* Uses the generic ResourceDetailPage component with devbox-specific customizations
|
|
@@ -12,26 +12,7 @@ import { ResourceDetailPage, } from "./ResourceDetailPage.js";
|
|
|
12
12
|
import { getDevboxUrl } from "../utils/url.js";
|
|
13
13
|
import { colors } from "../utils/theme.js";
|
|
14
14
|
import { getDevbox } from "../services/devboxService.js";
|
|
15
|
-
|
|
16
|
-
const formatTimeAgo = (timestamp) => {
|
|
17
|
-
const seconds = Math.floor((Date.now() - timestamp) / 1000);
|
|
18
|
-
if (seconds < 60)
|
|
19
|
-
return `${seconds}s ago`;
|
|
20
|
-
const minutes = Math.floor(seconds / 60);
|
|
21
|
-
if (minutes < 60)
|
|
22
|
-
return `${minutes}m ago`;
|
|
23
|
-
const hours = Math.floor(minutes / 60);
|
|
24
|
-
if (hours < 24)
|
|
25
|
-
return `${hours}h ago`;
|
|
26
|
-
const days = Math.floor(hours / 24);
|
|
27
|
-
if (days < 30)
|
|
28
|
-
return `${days}d ago`;
|
|
29
|
-
const months = Math.floor(days / 30);
|
|
30
|
-
if (months < 12)
|
|
31
|
-
return `${months}mo ago`;
|
|
32
|
-
const years = Math.floor(months / 12);
|
|
33
|
-
return `${years}y ago`;
|
|
34
|
-
};
|
|
15
|
+
import { formatTimeAgo } from "../utils/time.js";
|
|
35
16
|
export const DevboxDetailPage = ({ devbox: initialDevbox, onBack, }) => {
|
|
36
17
|
const [showActions, setShowActions] = React.useState(false);
|
|
37
18
|
const [selectedOperationKey, setSelectedOperationKey] = React.useState(null);
|
|
@@ -104,7 +85,9 @@ export const DevboxDetailPage = ({ devbox: initialDevbox, onBack, }) => {
|
|
|
104
85
|
];
|
|
105
86
|
// Filter operations based on devbox status
|
|
106
87
|
const getFilteredOperations = (devbox) => {
|
|
107
|
-
|
|
88
|
+
const hasTunnel = !!(devbox.tunnel && devbox.tunnel.tunnel_key);
|
|
89
|
+
return allOperations
|
|
90
|
+
.filter((op) => {
|
|
108
91
|
const status = devbox.status;
|
|
109
92
|
// When suspended: logs and resume
|
|
110
93
|
if (status === "suspended") {
|
|
@@ -122,6 +105,20 @@ export const DevboxDetailPage = ({ devbox: initialDevbox, onBack, }) => {
|
|
|
122
105
|
}
|
|
123
106
|
// Default for transitional states (provisioning, initializing)
|
|
124
107
|
return op.key === "logs" || op.key === "delete";
|
|
108
|
+
})
|
|
109
|
+
.map((op) => {
|
|
110
|
+
// Dynamic tunnel label based on whether tunnel is active
|
|
111
|
+
if (op.key === "tunnel") {
|
|
112
|
+
return hasTunnel
|
|
113
|
+
? {
|
|
114
|
+
...op,
|
|
115
|
+
label: "Tunnel (Active)",
|
|
116
|
+
color: colors.success,
|
|
117
|
+
icon: figures.tick,
|
|
118
|
+
}
|
|
119
|
+
: op;
|
|
120
|
+
}
|
|
121
|
+
return op;
|
|
125
122
|
});
|
|
126
123
|
};
|
|
127
124
|
// Build detail sections for the devbox
|
|
@@ -210,10 +207,28 @@ export const DevboxDetailPage = ({ devbox: initialDevbox, onBack, }) => {
|
|
|
210
207
|
});
|
|
211
208
|
}
|
|
212
209
|
// Source
|
|
213
|
-
if (devbox.blueprint_id
|
|
210
|
+
if (devbox.blueprint_id) {
|
|
211
|
+
detailFields.push({
|
|
212
|
+
label: "Source",
|
|
213
|
+
value: _jsx(Text, { color: colors.success, children: devbox.blueprint_id }),
|
|
214
|
+
action: {
|
|
215
|
+
type: "navigate",
|
|
216
|
+
screen: "blueprint-detail",
|
|
217
|
+
params: { blueprintId: devbox.blueprint_id },
|
|
218
|
+
hint: "View Blueprint",
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
else if (devbox.snapshot_id) {
|
|
214
223
|
detailFields.push({
|
|
215
224
|
label: "Source",
|
|
216
|
-
value:
|
|
225
|
+
value: _jsx(Text, { color: colors.success, children: devbox.snapshot_id }),
|
|
226
|
+
action: {
|
|
227
|
+
type: "navigate",
|
|
228
|
+
screen: "snapshot-detail",
|
|
229
|
+
params: { snapshotId: devbox.snapshot_id },
|
|
230
|
+
hint: "View Snapshot",
|
|
231
|
+
},
|
|
217
232
|
});
|
|
218
233
|
}
|
|
219
234
|
// Network Policy
|
|
@@ -221,6 +236,28 @@ export const DevboxDetailPage = ({ devbox: initialDevbox, onBack, }) => {
|
|
|
221
236
|
detailFields.push({
|
|
222
237
|
label: "Network Policy",
|
|
223
238
|
value: _jsx(Text, { color: colors.info, children: lp.network_policy_id }),
|
|
239
|
+
action: {
|
|
240
|
+
type: "navigate",
|
|
241
|
+
screen: "network-policy-detail",
|
|
242
|
+
params: { networkPolicyId: lp.network_policy_id },
|
|
243
|
+
hint: "View Policy",
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
// Tunnel status - always show when running
|
|
248
|
+
if (devbox.tunnel && devbox.tunnel.tunnel_key) {
|
|
249
|
+
const tunnelKey = devbox.tunnel.tunnel_key;
|
|
250
|
+
const authMode = devbox.tunnel.auth_mode;
|
|
251
|
+
const tunnelUrl = `https://{port}-${tunnelKey}.tunnel.runloop.ai`;
|
|
252
|
+
detailFields.push({
|
|
253
|
+
label: "Tunnel",
|
|
254
|
+
value: (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.success, bold: true, children: [figures.tick, " Active"] }), _jsx(Text, { color: colors.textDim, children: " \u2022 " }), _jsx(Text, { color: colors.success, children: tunnelUrl }), authMode === "authenticated" && (_jsx(Text, { color: colors.warning, children: " (authenticated)" }))] })),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
else if (devbox.status === "running") {
|
|
258
|
+
detailFields.push({
|
|
259
|
+
label: "Tunnel",
|
|
260
|
+
value: (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.cross, " Off"] })),
|
|
224
261
|
});
|
|
225
262
|
}
|
|
226
263
|
// Initiator
|
|
@@ -350,6 +387,18 @@ export const DevboxDetailPage = ({ devbox: initialDevbox, onBack, }) => {
|
|
|
350
387
|
}
|
|
351
388
|
lines.push(_jsx(Text, { children: " " }, "launch-space"));
|
|
352
389
|
}
|
|
390
|
+
// Tunnel Information
|
|
391
|
+
if (devbox.tunnel && devbox.tunnel.tunnel_key) {
|
|
392
|
+
lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Tunnel" }, "tunnel-title"));
|
|
393
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Tunnel Key: ", devbox.tunnel.tunnel_key] }, "tunnel-key"));
|
|
394
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Auth Mode: ", devbox.tunnel.auth_mode] }, "tunnel-auth"));
|
|
395
|
+
const tunnelUrl = `https://{port}-${devbox.tunnel.tunnel_key}.tunnel.runloop.ai`;
|
|
396
|
+
lines.push(_jsxs(Text, { color: colors.success, children: [" ", "Tunnel URL: ", tunnelUrl] }, "tunnel-url"));
|
|
397
|
+
if (devbox.tunnel.auth_token) {
|
|
398
|
+
lines.push(_jsxs(Text, { color: colors.warning, children: [" ", "Auth Token: ", devbox.tunnel.auth_token] }, "tunnel-token"));
|
|
399
|
+
}
|
|
400
|
+
lines.push(_jsx(Text, { children: " " }, "tunnel-space"));
|
|
401
|
+
}
|
|
353
402
|
// Source
|
|
354
403
|
if (devbox.blueprint_id || devbox.snapshot_id) {
|
|
355
404
|
lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Source" }, "source-title"));
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import figures from "figures";
|
|
5
|
+
import { getClient } from "../utils/client.js";
|
|
6
|
+
import { SpinnerComponent } from "./Spinner.js";
|
|
7
|
+
import { ErrorMessage } from "./ErrorMessage.js";
|
|
8
|
+
import { SuccessMessage } from "./SuccessMessage.js";
|
|
9
|
+
import { Breadcrumb } from "./Breadcrumb.js";
|
|
10
|
+
import { NavigationTips } from "./NavigationTips.js";
|
|
11
|
+
import { FormTextInput, FormSelect, FormActionButton, useFormSelectNavigation, } from "./form/index.js";
|
|
12
|
+
import { colors } from "../utils/theme.js";
|
|
13
|
+
import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
|
|
14
|
+
import { validateGatewayConfig } from "../utils/gatewayConfigValidation.js";
|
|
15
|
+
const authTypes = ["bearer", "header"];
|
|
16
|
+
export const GatewayConfigCreatePage = ({ onBack, onCreate, initialConfig, }) => {
|
|
17
|
+
const isEditing = !!initialConfig?.id;
|
|
18
|
+
const [currentField, setCurrentField] = React.useState("create");
|
|
19
|
+
// Normalize auth type from API to match our options (lowercase)
|
|
20
|
+
const normalizeAuthType = (type) => {
|
|
21
|
+
const normalized = (type || "").toLowerCase();
|
|
22
|
+
if (normalized === "header" || normalized === "bearer") {
|
|
23
|
+
return normalized;
|
|
24
|
+
}
|
|
25
|
+
return "bearer"; // default
|
|
26
|
+
};
|
|
27
|
+
const [formData, setFormData] = React.useState({
|
|
28
|
+
name: initialConfig?.name || "",
|
|
29
|
+
endpoint: initialConfig?.endpoint || "",
|
|
30
|
+
auth_type: normalizeAuthType(initialConfig?.auth_mechanism?.type),
|
|
31
|
+
auth_key: initialConfig?.auth_mechanism?.key || "",
|
|
32
|
+
description: initialConfig?.description || "",
|
|
33
|
+
});
|
|
34
|
+
const [creating, setCreating] = React.useState(false);
|
|
35
|
+
const [result, setResult] = React.useState(null);
|
|
36
|
+
const [error, setError] = React.useState(null);
|
|
37
|
+
const fields = [
|
|
38
|
+
{
|
|
39
|
+
key: "create",
|
|
40
|
+
label: isEditing
|
|
41
|
+
? "Update AI Gateway Config"
|
|
42
|
+
: "Create AI Gateway Config",
|
|
43
|
+
type: "action",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
key: "name",
|
|
47
|
+
label: "Name (required)",
|
|
48
|
+
type: "text",
|
|
49
|
+
placeholder: "my-gateway",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
key: "endpoint",
|
|
53
|
+
label: "Endpoint URL (required)",
|
|
54
|
+
type: "text",
|
|
55
|
+
placeholder: "https://api.example.com",
|
|
56
|
+
},
|
|
57
|
+
{ key: "auth_type", label: "Auth Type", type: "select" },
|
|
58
|
+
{
|
|
59
|
+
key: "auth_key",
|
|
60
|
+
label: "Auth Header Key (for header type)",
|
|
61
|
+
type: "text",
|
|
62
|
+
placeholder: "x-api-key",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
key: "description",
|
|
66
|
+
label: "Description (optional)",
|
|
67
|
+
type: "text",
|
|
68
|
+
placeholder: "Gateway for...",
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
const currentFieldIndex = fields.findIndex((f) => f.key === currentField);
|
|
72
|
+
// Handle Ctrl+C to exit
|
|
73
|
+
useExitOnCtrlC();
|
|
74
|
+
// Select navigation handlers using shared hook
|
|
75
|
+
const handleAuthTypeNav = useFormSelectNavigation(formData.auth_type, authTypes, (value) => {
|
|
76
|
+
setFormData({
|
|
77
|
+
...formData,
|
|
78
|
+
auth_type: value,
|
|
79
|
+
// Clear auth_key if switching from header to bearer
|
|
80
|
+
auth_key: value !== "header" ? "" : formData.auth_key,
|
|
81
|
+
});
|
|
82
|
+
// If switching away from header and currently on auth_key field, move to next field
|
|
83
|
+
if (value !== "header" && currentField === "auth_key") {
|
|
84
|
+
setCurrentField("description");
|
|
85
|
+
}
|
|
86
|
+
}, currentField === "auth_type");
|
|
87
|
+
// Main form input handler
|
|
88
|
+
useInput((input, key) => {
|
|
89
|
+
// Handle result screen
|
|
90
|
+
if (result) {
|
|
91
|
+
if (input === "q" || key.escape || key.return) {
|
|
92
|
+
if (onCreate) {
|
|
93
|
+
onCreate(result);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
onBack();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// Handle error screen
|
|
102
|
+
if (error) {
|
|
103
|
+
if (input === "r" || key.return) {
|
|
104
|
+
// Retry - clear error and return to form
|
|
105
|
+
setError(null);
|
|
106
|
+
}
|
|
107
|
+
else if (input === "q" || key.escape) {
|
|
108
|
+
// Quit - go back to list
|
|
109
|
+
onBack();
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Handle creating state
|
|
114
|
+
if (creating) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Back to list
|
|
118
|
+
if (input === "q" || key.escape) {
|
|
119
|
+
onBack();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Submit form with Ctrl+S
|
|
123
|
+
if (input === "s" && key.ctrl) {
|
|
124
|
+
handleCreate();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// Handle Enter on any field to submit
|
|
128
|
+
if (key.return) {
|
|
129
|
+
handleCreate();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Handle select field navigation using shared hooks
|
|
133
|
+
if (handleAuthTypeNav(input, key))
|
|
134
|
+
return;
|
|
135
|
+
// Navigation (up/down arrows and tab/shift+tab)
|
|
136
|
+
// Skip auth_key field if auth_type is not "header"
|
|
137
|
+
const getNextField = (direction) => {
|
|
138
|
+
let nextIndex = direction === "up" ? currentFieldIndex - 1 : currentFieldIndex + 1;
|
|
139
|
+
while (nextIndex >= 0 && nextIndex < fields.length) {
|
|
140
|
+
const nextField = fields[nextIndex].key;
|
|
141
|
+
// Skip auth_key if auth_type is not header
|
|
142
|
+
if (nextField === "auth_key" && formData.auth_type !== "header") {
|
|
143
|
+
nextIndex = direction === "up" ? nextIndex - 1 : nextIndex + 1;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
return nextField;
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
};
|
|
150
|
+
if ((key.upArrow || (key.tab && key.shift)) && currentFieldIndex > 0) {
|
|
151
|
+
const nextField = getNextField("up");
|
|
152
|
+
if (nextField) {
|
|
153
|
+
setCurrentField(nextField);
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if ((key.downArrow || (key.tab && !key.shift)) &&
|
|
158
|
+
currentFieldIndex < fields.length - 1) {
|
|
159
|
+
const nextField = getNextField("down");
|
|
160
|
+
if (nextField) {
|
|
161
|
+
setCurrentField(nextField);
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}, { isActive: true });
|
|
166
|
+
const handleCreate = async () => {
|
|
167
|
+
// Validate using shared validation
|
|
168
|
+
const validation = validateGatewayConfig({
|
|
169
|
+
name: formData.name,
|
|
170
|
+
endpoint: formData.endpoint,
|
|
171
|
+
authType: formData.auth_type,
|
|
172
|
+
authKey: formData.auth_key,
|
|
173
|
+
}, { requireName: true, requireEndpoint: true });
|
|
174
|
+
if (!validation.valid) {
|
|
175
|
+
setError(new Error(validation.errors.join("\n")));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const { sanitized } = validation;
|
|
179
|
+
setCreating(true);
|
|
180
|
+
setError(null);
|
|
181
|
+
try {
|
|
182
|
+
const client = getClient();
|
|
183
|
+
const authMechanism = {
|
|
184
|
+
type: sanitized.authType,
|
|
185
|
+
};
|
|
186
|
+
if (sanitized.authType === "header" && sanitized.authKey) {
|
|
187
|
+
authMechanism.key = sanitized.authKey;
|
|
188
|
+
}
|
|
189
|
+
let config;
|
|
190
|
+
if (isEditing && initialConfig?.id) {
|
|
191
|
+
// Update existing config
|
|
192
|
+
config = await client.gatewayConfigs.update(initialConfig.id, {
|
|
193
|
+
name: sanitized.name,
|
|
194
|
+
endpoint: sanitized.endpoint,
|
|
195
|
+
auth_mechanism: authMechanism,
|
|
196
|
+
description: formData.description.trim() || undefined,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
// Create new config
|
|
201
|
+
config = await client.gatewayConfigs.create({
|
|
202
|
+
name: sanitized.name,
|
|
203
|
+
endpoint: sanitized.endpoint,
|
|
204
|
+
auth_mechanism: authMechanism,
|
|
205
|
+
description: formData.description.trim() || undefined,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
setResult(config);
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
setError(err);
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
setCreating(false);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
// Result screen
|
|
218
|
+
if (result) {
|
|
219
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
220
|
+
{ label: "AI Gateway Configs" },
|
|
221
|
+
{ label: isEditing ? "Update" : "Create", active: true },
|
|
222
|
+
] }), _jsx(SuccessMessage, { message: `AI gateway config ${isEditing ? "updated" : "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 || "(none)"] }) }), _jsx(Box, { children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Endpoint: ", result.endpoint] }) })] }), _jsx(NavigationTips, { tips: [{ key: "Enter/q/esc", label: "Return to list" }] })] }));
|
|
223
|
+
}
|
|
224
|
+
// Error screen
|
|
225
|
+
if (error) {
|
|
226
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
227
|
+
{ label: "AI Gateway Configs" },
|
|
228
|
+
{ label: isEditing ? "Update" : "Create", active: true },
|
|
229
|
+
] }), _jsx(ErrorMessage, { message: `Failed to ${isEditing ? "update" : "create"} AI gateway config`, error: error }), _jsx(NavigationTips, { tips: [
|
|
230
|
+
{ key: "Enter/r", label: "Retry" },
|
|
231
|
+
{ key: "q/esc", label: "Cancel" },
|
|
232
|
+
] })] }));
|
|
233
|
+
}
|
|
234
|
+
// Creating screen
|
|
235
|
+
if (creating) {
|
|
236
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
237
|
+
{ label: "AI Gateway Configs" },
|
|
238
|
+
{ label: isEditing ? "Update" : "Create", active: true },
|
|
239
|
+
] }), _jsx(SpinnerComponent, { message: `${isEditing ? "Updating" : "Creating"} AI gateway config...` })] }));
|
|
240
|
+
}
|
|
241
|
+
// Form screen
|
|
242
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
243
|
+
{ label: "AI Gateway Configs" },
|
|
244
|
+
{ label: isEditing ? "Update" : "Create", active: true },
|
|
245
|
+
] }), _jsx(Box, { flexDirection: "column", marginBottom: 1, children: fields.map((field) => {
|
|
246
|
+
const isActive = currentField === field.key;
|
|
247
|
+
const fieldData = formData[field.key];
|
|
248
|
+
if (field.type === "action") {
|
|
249
|
+
return (_jsx(FormActionButton, { label: field.label, isActive: isActive, hint: `[Enter to ${isEditing ? "update" : "create"}]` }, field.key));
|
|
250
|
+
}
|
|
251
|
+
if (field.type === "text") {
|
|
252
|
+
// Skip auth_key field if auth type is bearer
|
|
253
|
+
if (field.key === "auth_key" && formData.auth_type !== "header") {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
return (_jsx(FormTextInput, { label: field.label, value: String(fieldData || ""), onChange: (value) => setFormData({ ...formData, [field.key]: value }), onSubmit: handleCreate, isActive: isActive, placeholder: field.placeholder }, field.key));
|
|
257
|
+
}
|
|
258
|
+
if (field.type === "select") {
|
|
259
|
+
const value = fieldData;
|
|
260
|
+
return (_jsx(FormSelect, { label: field.label, value: value || "", options: authTypes, onChange: (newValue) => setFormData({
|
|
261
|
+
...formData,
|
|
262
|
+
[field.key]: newValue,
|
|
263
|
+
// Clear auth_key if switching from header to bearer
|
|
264
|
+
auth_key: newValue !== "header" ? "" : formData.auth_key,
|
|
265
|
+
}), isActive: isActive }, field.key));
|
|
266
|
+
}
|
|
267
|
+
return null;
|
|
268
|
+
}) }), _jsx(Box, { marginLeft: 2, marginBottom: 1, children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.info, " Auth Types:"] }) }), _jsxs(Box, { marginLeft: 4, flexDirection: "column", children: [_jsx(Text, { color: colors.textDim, dimColor: true, children: "\u2022 bearer: Uses Bearer token authentication" }), _jsx(Text, { color: colors.textDim, dimColor: true, children: "\u2022 header: Uses custom header (specify header key)" })] }), _jsx(NavigationTips, { showArrows: true, tips: [
|
|
269
|
+
{ key: "Enter", label: isEditing ? "Update" : "Create" },
|
|
270
|
+
{ key: "q", label: "Cancel" },
|
|
271
|
+
] })] }));
|
|
272
|
+
};
|
|
@@ -9,6 +9,7 @@ import figures from "figures";
|
|
|
9
9
|
import { Breadcrumb } from "./Breadcrumb.js";
|
|
10
10
|
import { NavigationTips } from "./NavigationTips.js";
|
|
11
11
|
import { colors } from "../utils/theme.js";
|
|
12
|
+
import { copyToClipboard } from "../utils/clipboard.js";
|
|
12
13
|
import { useViewportHeight } from "../hooks/useViewportHeight.js";
|
|
13
14
|
import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
|
|
14
15
|
import { parseAnyLogEntry } from "../utils/logFormatter.js";
|
|
@@ -71,42 +72,10 @@ export const LogsViewer = ({ logs, breadcrumbItems = [{ label: "Logs", active: t
|
|
|
71
72
|
return `${parts.timestamp} ${parts.level} [${parts.source}] ${shell}${cmd}${parts.message} ${exitCode}`.trim();
|
|
72
73
|
})
|
|
73
74
|
.join("\n");
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
let args;
|
|
79
|
-
if (platform === "darwin") {
|
|
80
|
-
command = "pbcopy";
|
|
81
|
-
args = [];
|
|
82
|
-
}
|
|
83
|
-
else if (platform === "win32") {
|
|
84
|
-
command = "clip";
|
|
85
|
-
args = [];
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
command = "xclip";
|
|
89
|
-
args = ["-selection", "clipboard"];
|
|
90
|
-
}
|
|
91
|
-
const proc = spawn(command, args);
|
|
92
|
-
proc.stdin.write(text);
|
|
93
|
-
proc.stdin.end();
|
|
94
|
-
proc.on("exit", (code) => {
|
|
95
|
-
if (code === 0) {
|
|
96
|
-
setCopyStatus("Copied to clipboard!");
|
|
97
|
-
setTimeout(() => setCopyStatus(null), 2000);
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
setCopyStatus("Failed to copy");
|
|
101
|
-
setTimeout(() => setCopyStatus(null), 2000);
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
proc.on("error", () => {
|
|
105
|
-
setCopyStatus("Copy not supported");
|
|
106
|
-
setTimeout(() => setCopyStatus(null), 2000);
|
|
107
|
-
});
|
|
108
|
-
};
|
|
109
|
-
copyToClipboard(logsText);
|
|
75
|
+
copyToClipboard(logsText).then((status) => {
|
|
76
|
+
setCopyStatus(status);
|
|
77
|
+
setTimeout(() => setCopyStatus(null), 2000);
|
|
78
|
+
});
|
|
110
79
|
}
|
|
111
80
|
else if (input === "q" || key.escape || key.return) {
|
|
112
81
|
onBack();
|
|
@@ -121,9 +90,7 @@ export const LogsViewer = ({ logs, breadcrumbItems = [{ label: "Logs", active: t
|
|
|
121
90
|
// Helper to sanitize log message
|
|
122
91
|
const sanitizeMessage = (message) => {
|
|
123
92
|
// Strip ANSI escape sequences (colors, cursor movement, etc.)
|
|
124
|
-
const strippedAnsi = message.replace(
|
|
125
|
-
// eslint-disable-next-line no-control-regex
|
|
126
|
-
/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
93
|
+
const strippedAnsi = message.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
127
94
|
// Replace control characters with spaces
|
|
128
95
|
return (strippedAnsi
|
|
129
96
|
.replace(/\r\n/g, " ")
|
|
@@ -131,7 +98,6 @@ export const LogsViewer = ({ logs, breadcrumbItems = [{ label: "Logs", active: t
|
|
|
131
98
|
.replace(/\r/g, " ")
|
|
132
99
|
.replace(/\t/g, " ")
|
|
133
100
|
// Remove any other control characters (ASCII 0-31 except space)
|
|
134
|
-
// eslint-disable-next-line no-control-regex
|
|
135
101
|
.replace(/[\x00-\x1F]/g, ""));
|
|
136
102
|
};
|
|
137
103
|
// Helper to calculate how many lines a log entry will take when wrapped
|