@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
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared validation for gateway config create/update operations.
|
|
3
|
+
* Used by both CLI commands and UI components.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Validate and sanitize gateway config fields.
|
|
7
|
+
*
|
|
8
|
+
* @param input - The fields to validate
|
|
9
|
+
* @param opts.requireName - Whether name is required (true for create, false for update)
|
|
10
|
+
* @param opts.requireEndpoint - Whether endpoint is required (true for create, false for update)
|
|
11
|
+
*/
|
|
12
|
+
export function validateGatewayConfig(input, opts = {}) {
|
|
13
|
+
const errors = [];
|
|
14
|
+
const name = input.name?.trim();
|
|
15
|
+
const endpoint = input.endpoint?.trim();
|
|
16
|
+
const authType = input.authType?.toLowerCase();
|
|
17
|
+
const authKey = input.authKey?.trim();
|
|
18
|
+
// Name validation
|
|
19
|
+
if (opts.requireName && !name) {
|
|
20
|
+
errors.push("Name is required");
|
|
21
|
+
}
|
|
22
|
+
// Endpoint validation
|
|
23
|
+
if (opts.requireEndpoint && !endpoint) {
|
|
24
|
+
errors.push("Endpoint URL is required");
|
|
25
|
+
}
|
|
26
|
+
if (endpoint) {
|
|
27
|
+
if (!endpoint.startsWith("https://") && !endpoint.startsWith("http://")) {
|
|
28
|
+
errors.push("Endpoint must be a valid URL starting with https:// or http://");
|
|
29
|
+
}
|
|
30
|
+
// Basic URL structure validation
|
|
31
|
+
try {
|
|
32
|
+
new URL(endpoint);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
errors.push("Endpoint is not a valid URL");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Auth validation
|
|
39
|
+
if (authType && authType !== "bearer" && authType !== "header") {
|
|
40
|
+
errors.push('Auth type must be either "bearer" or "header"');
|
|
41
|
+
}
|
|
42
|
+
if (authType === "header" && !authKey) {
|
|
43
|
+
errors.push("Auth header key is required when using header authentication");
|
|
44
|
+
}
|
|
45
|
+
if (errors.length > 0) {
|
|
46
|
+
return { valid: false, errors };
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
valid: true,
|
|
50
|
+
errors: [],
|
|
51
|
+
sanitized: {
|
|
52
|
+
name,
|
|
53
|
+
endpoint,
|
|
54
|
+
authType,
|
|
55
|
+
authKey,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared time formatting utilities using the Temporal API.
|
|
3
|
+
*/
|
|
4
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
5
|
+
/**
|
|
6
|
+
* Get elapsed seconds since the given epoch millisecond timestamp.
|
|
7
|
+
*/
|
|
8
|
+
function getElapsedSeconds(timestampMs) {
|
|
9
|
+
const now = Temporal.Now.instant();
|
|
10
|
+
const then = Temporal.Instant.fromEpochMilliseconds(timestampMs);
|
|
11
|
+
return Math.floor(now.since(then).total("second"));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Format a relative timestamp in concise form.
|
|
15
|
+
* Examples: "5s ago", "3m ago", "2h ago", "14d ago", "3mo ago", "1y ago"
|
|
16
|
+
*
|
|
17
|
+
* Used for UI detail components.
|
|
18
|
+
*/
|
|
19
|
+
export function formatTimeAgo(timestampMs) {
|
|
20
|
+
const seconds = getElapsedSeconds(timestampMs);
|
|
21
|
+
if (seconds < 60)
|
|
22
|
+
return `${seconds}s ago`;
|
|
23
|
+
const minutes = Math.floor(seconds / 60);
|
|
24
|
+
if (minutes < 60)
|
|
25
|
+
return `${minutes}m ago`;
|
|
26
|
+
const hours = Math.floor(minutes / 60);
|
|
27
|
+
if (hours < 24)
|
|
28
|
+
return `${hours}h ago`;
|
|
29
|
+
const days = Math.floor(hours / 24);
|
|
30
|
+
if (days < 30)
|
|
31
|
+
return `${days}d ago`;
|
|
32
|
+
const months = Math.floor(days / 30);
|
|
33
|
+
if (months < 12)
|
|
34
|
+
return `${months}mo ago`;
|
|
35
|
+
const years = Math.floor(months / 12);
|
|
36
|
+
return `${years}y ago`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Format a relative timestamp in verbose form.
|
|
40
|
+
* Examples: "5 minutes ago", "3 hours ago", "14 days ago"
|
|
41
|
+
*
|
|
42
|
+
* Used for CLI text output (e.g. prune commands).
|
|
43
|
+
*/
|
|
44
|
+
export function formatRelativeTime(timestampMs) {
|
|
45
|
+
if (!timestampMs)
|
|
46
|
+
return "unknown time";
|
|
47
|
+
const seconds = getElapsedSeconds(timestampMs);
|
|
48
|
+
const minutes = Math.floor(seconds / 60);
|
|
49
|
+
const hours = Math.floor(seconds / 3600);
|
|
50
|
+
const days = Math.floor(seconds / 86400);
|
|
51
|
+
if (minutes < 60) {
|
|
52
|
+
return `${minutes} minute${minutes !== 1 ? "s" : ""} ago`;
|
|
53
|
+
}
|
|
54
|
+
else if (hours < 24) {
|
|
55
|
+
return `${hours} hour${hours !== 1 ? "s" : ""} ago`;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
return `${days} day${days !== 1 ? "s" : ""} ago`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Format a timestamp with HH:MM:SS time and a relative indicator.
|
|
63
|
+
* Examples: "14:30:05 (3m ago)", "01-15 09:00:00 (2d)"
|
|
64
|
+
*
|
|
65
|
+
* Used for resource list views.
|
|
66
|
+
*/
|
|
67
|
+
export function formatTimeAgoRich(timestampMs) {
|
|
68
|
+
const instant = Temporal.Instant.fromEpochMilliseconds(timestampMs);
|
|
69
|
+
const zdt = instant.toZonedDateTimeISO(Temporal.Now.timeZoneId());
|
|
70
|
+
const time = `${String(zdt.hour).padStart(2, "0")}:${String(zdt.minute).padStart(2, "0")}:${String(zdt.second).padStart(2, "0")}`;
|
|
71
|
+
const seconds = getElapsedSeconds(timestampMs);
|
|
72
|
+
// Less than 1 minute
|
|
73
|
+
if (seconds < 60)
|
|
74
|
+
return `${time} (${seconds}s ago)`;
|
|
75
|
+
const minutes = Math.floor(seconds / 60);
|
|
76
|
+
// Less than 1 hour
|
|
77
|
+
if (minutes < 60)
|
|
78
|
+
return `${time} (${minutes}m ago)`;
|
|
79
|
+
const hours = Math.floor(minutes / 60);
|
|
80
|
+
// Less than 24 hours
|
|
81
|
+
if (hours < 24)
|
|
82
|
+
return `${time} (${hours}hr ago)`;
|
|
83
|
+
const days = Math.floor(hours / 24);
|
|
84
|
+
const month = String(zdt.month).padStart(2, "0");
|
|
85
|
+
const day = String(zdt.day).padStart(2, "0");
|
|
86
|
+
const dateStr = `${month}-${day}`;
|
|
87
|
+
// 1-7 days - show date + time + relative
|
|
88
|
+
if (days <= 7) {
|
|
89
|
+
return `${dateStr} ${time} (${days}d)`;
|
|
90
|
+
}
|
|
91
|
+
// More than 7 days - just date + time
|
|
92
|
+
return `${dateStr} ${time}`;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Format a timestamp as "locale string (relative ago)".
|
|
96
|
+
* Example: "1/15/2025, 3:30:00 PM (2h ago)"
|
|
97
|
+
*
|
|
98
|
+
* Returns undefined if timestamp is falsy.
|
|
99
|
+
*/
|
|
100
|
+
export function formatTimestamp(timestamp) {
|
|
101
|
+
if (!timestamp)
|
|
102
|
+
return undefined;
|
|
103
|
+
const formatted = new Date(timestamp).toLocaleString();
|
|
104
|
+
const ago = formatTimeAgo(timestamp);
|
|
105
|
+
return `${formatted} (${ago})`;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Format a time range as "start → end" or "start (relative ago)" if no end.
|
|
109
|
+
*
|
|
110
|
+
* Returns undefined if createTime is falsy.
|
|
111
|
+
*/
|
|
112
|
+
export function formatTimeRange(createTime, endTime) {
|
|
113
|
+
if (!createTime)
|
|
114
|
+
return undefined;
|
|
115
|
+
const start = new Date(createTime).toLocaleString();
|
|
116
|
+
if (endTime) {
|
|
117
|
+
const end = new Date(endTime).toLocaleString();
|
|
118
|
+
return `${start} → ${end}`;
|
|
119
|
+
}
|
|
120
|
+
return `${start} (${formatTimeAgo(createTime)})`;
|
|
121
|
+
}
|
package/package.json
CHANGED
|
@@ -1,32 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runloop/rl-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Beautiful CLI for the Runloop platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"rli": "./dist/cli.js"
|
|
8
8
|
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
"build": "tsc",
|
|
11
|
-
"build:mcp": "npm run build && node scripts/build-mcp.js",
|
|
12
|
-
"dev": "tsc --watch",
|
|
13
|
-
"start": "node dist/cli.js",
|
|
14
|
-
"prepublishOnly": "npm run build",
|
|
15
|
-
"version:patch": "npm version patch",
|
|
16
|
-
"version:minor": "npm version minor",
|
|
17
|
-
"version:major": "npm version major",
|
|
18
|
-
"release": "npm run build && npm publish",
|
|
19
|
-
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json}\"",
|
|
20
|
-
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json}\"",
|
|
21
|
-
"lint": "eslint src --ext .ts,.tsx",
|
|
22
|
-
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
|
23
|
-
"test": "NODE_OPTIONS='--experimental-vm-modules' jest",
|
|
24
|
-
"test:watch": "NODE_OPTIONS='--experimental-vm-modules' jest --watch",
|
|
25
|
-
"test:coverage": "NODE_OPTIONS='--experimental-vm-modules' jest --coverage",
|
|
26
|
-
"test:components": "NODE_OPTIONS='--experimental-vm-modules' jest --config jest.components.config.js --coverage --forceExit",
|
|
27
|
-
"docs:commands": "npm run build && node scripts/generate-command-docs.js",
|
|
28
|
-
"prepare": "husky"
|
|
29
|
-
},
|
|
30
9
|
"keywords": [
|
|
31
10
|
"runloop",
|
|
32
11
|
"cli",
|
|
@@ -66,44 +45,65 @@
|
|
|
66
45
|
"access": "public"
|
|
67
46
|
},
|
|
68
47
|
"dependencies": {
|
|
69
|
-
"@
|
|
70
|
-
"@
|
|
71
|
-
"@
|
|
72
|
-
"
|
|
73
|
-
"
|
|
48
|
+
"@js-temporal/polyfill": "^0.5.1",
|
|
49
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
50
|
+
"@runloop/api-client": "1.6.0",
|
|
51
|
+
"@types/express": "^5.0.6",
|
|
52
|
+
"chalk": "^5.6.2",
|
|
53
|
+
"commander": "^14.0.2",
|
|
74
54
|
"conf": "^15.0.2",
|
|
75
55
|
"dotenv": "^17.2.3",
|
|
76
|
-
"express": "^5.1
|
|
56
|
+
"express": "^5.2.1",
|
|
77
57
|
"figures": "^6.1.0",
|
|
78
58
|
"gradient-string": "^3.0.0",
|
|
79
|
-
"ink": "^6.
|
|
59
|
+
"ink": "^6.6.0",
|
|
80
60
|
"ink-big-text": "^2.0.0",
|
|
81
61
|
"ink-gradient": "^3.0.0",
|
|
82
62
|
"ink-link": "^5.0.0",
|
|
83
63
|
"ink-spinner": "^5.0.0",
|
|
84
64
|
"ink-text-input": "^6.0.0",
|
|
85
65
|
"react": "19.2.0",
|
|
86
|
-
"yaml": "^2.8.
|
|
87
|
-
"zustand": "^5.0.
|
|
66
|
+
"yaml": "^2.8.2",
|
|
67
|
+
"zustand": "^5.0.10"
|
|
88
68
|
},
|
|
89
69
|
"devDependencies": {
|
|
90
70
|
"@anthropic-ai/mcpb": "^2.1.2",
|
|
91
|
-
"@types/jest": "^29.5.
|
|
92
|
-
"@types/node": "^22.7
|
|
93
|
-
"@types/react": "^19.2.
|
|
94
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
95
|
-
"@typescript-eslint/parser": "^8.
|
|
71
|
+
"@types/jest": "^29.5.14",
|
|
72
|
+
"@types/node": "^22.19.7",
|
|
73
|
+
"@types/react": "^19.2.10",
|
|
74
|
+
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
75
|
+
"@typescript-eslint/parser": "^8.54.0",
|
|
96
76
|
"esbuild": "^0.27.2",
|
|
97
|
-
"eslint": "^9.
|
|
77
|
+
"eslint": "^9.39.2",
|
|
98
78
|
"eslint-plugin-react": "^7.37.5",
|
|
99
79
|
"eslint-plugin-react-hooks": "^6.1.1",
|
|
100
|
-
"globals": "^16.
|
|
80
|
+
"globals": "^16.5.0",
|
|
101
81
|
"husky": "^9.1.7",
|
|
102
82
|
"ink-testing-library": "^4.0.0",
|
|
103
83
|
"jest": "^29.7.0",
|
|
104
|
-
"prettier": "^3.
|
|
105
|
-
"ts-jest": "^29.
|
|
106
|
-
"ts-node": "^10.9.
|
|
107
|
-
"typescript": "^5.
|
|
84
|
+
"prettier": "^3.8.1",
|
|
85
|
+
"ts-jest": "^29.4.6",
|
|
86
|
+
"ts-node": "^10.9.2",
|
|
87
|
+
"typescript": "^5.9.3"
|
|
88
|
+
},
|
|
89
|
+
"scripts": {
|
|
90
|
+
"build": "tsc",
|
|
91
|
+
"build:mcp": "pnpm run build && node scripts/build-mcp.js",
|
|
92
|
+
"dev": "tsc --watch",
|
|
93
|
+
"start": "node dist/cli.js",
|
|
94
|
+
"version:patch": "pnpm version patch",
|
|
95
|
+
"version:minor": "pnpm version minor",
|
|
96
|
+
"version:major": "pnpm version major",
|
|
97
|
+
"release": "pnpm run build && pnpm publish",
|
|
98
|
+
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json}\"",
|
|
99
|
+
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json}\"",
|
|
100
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
101
|
+
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
|
102
|
+
"test": "NODE_OPTIONS='--experimental-vm-modules' jest",
|
|
103
|
+
"test:watch": "NODE_OPTIONS='--experimental-vm-modules' jest --watch",
|
|
104
|
+
"test:coverage": "NODE_OPTIONS='--experimental-vm-modules' jest --coverage",
|
|
105
|
+
"test:components": "NODE_OPTIONS='--experimental-vm-modules' jest --config jest.components.config.js --coverage --forceExit",
|
|
106
|
+
"test:e2e": "NODE_OPTIONS='--experimental-vm-modules' jest --config jest.e2e.config.js --forceExit",
|
|
107
|
+
"docs:commands": "pnpm run build && node scripts/generate-command-docs.js"
|
|
108
108
|
}
|
|
109
|
-
}
|
|
109
|
+
}
|