@runloop/rl-cli 0.0.3 ā 0.1.1
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 +64 -29
- package/dist/cli.js +401 -92
- package/dist/commands/auth.js +12 -11
- package/dist/commands/blueprint/create.js +108 -0
- package/dist/commands/blueprint/get.js +37 -0
- package/dist/commands/blueprint/list.js +293 -225
- package/dist/commands/blueprint/logs.js +40 -0
- package/dist/commands/blueprint/preview.js +45 -0
- package/dist/commands/devbox/create.js +10 -9
- package/dist/commands/devbox/delete.js +8 -8
- package/dist/commands/devbox/download.js +49 -0
- package/dist/commands/devbox/exec.js +23 -13
- package/dist/commands/devbox/execAsync.js +43 -0
- package/dist/commands/devbox/get.js +37 -0
- package/dist/commands/devbox/getAsync.js +37 -0
- package/dist/commands/devbox/list.js +328 -190
- package/dist/commands/devbox/logs.js +40 -0
- package/dist/commands/devbox/read.js +49 -0
- package/dist/commands/devbox/resume.js +37 -0
- package/dist/commands/devbox/rsync.js +118 -0
- package/dist/commands/devbox/scp.js +122 -0
- package/dist/commands/devbox/shutdown.js +37 -0
- package/dist/commands/devbox/ssh.js +104 -0
- package/dist/commands/devbox/suspend.js +37 -0
- package/dist/commands/devbox/tunnel.js +120 -0
- package/dist/commands/devbox/upload.js +10 -10
- package/dist/commands/devbox/write.js +51 -0
- package/dist/commands/mcp-http.js +37 -0
- package/dist/commands/mcp-install.js +120 -0
- package/dist/commands/mcp.js +30 -0
- package/dist/commands/menu.js +20 -20
- package/dist/commands/object/delete.js +37 -0
- package/dist/commands/object/download.js +88 -0
- package/dist/commands/object/get.js +37 -0
- package/dist/commands/object/list.js +112 -0
- package/dist/commands/object/upload.js +130 -0
- package/dist/commands/snapshot/create.js +12 -11
- package/dist/commands/snapshot/delete.js +8 -8
- package/dist/commands/snapshot/list.js +56 -97
- package/dist/commands/snapshot/status.js +37 -0
- package/dist/components/ActionsPopup.js +16 -13
- package/dist/components/Banner.js +4 -4
- package/dist/components/Breadcrumb.js +55 -5
- package/dist/components/DetailView.js +7 -4
- package/dist/components/DevboxActionsMenu.js +315 -178
- package/dist/components/DevboxCard.js +15 -14
- package/dist/components/DevboxCreatePage.js +147 -113
- package/dist/components/DevboxDetailPage.js +180 -102
- package/dist/components/ErrorMessage.js +5 -4
- package/dist/components/Header.js +4 -3
- package/dist/components/MainMenu.js +34 -33
- package/dist/components/MetadataDisplay.js +17 -9
- package/dist/components/OperationsMenu.js +6 -5
- package/dist/components/ResourceActionsMenu.js +117 -0
- package/dist/components/ResourceListView.js +213 -0
- package/dist/components/Spinner.js +5 -4
- package/dist/components/StatusBadge.js +81 -31
- package/dist/components/SuccessMessage.js +4 -3
- package/dist/components/Table.example.js +53 -23
- package/dist/components/Table.js +19 -11
- package/dist/hooks/useCursorPagination.js +125 -0
- package/dist/mcp/server-http.js +416 -0
- package/dist/mcp/server.js +397 -0
- package/dist/utils/CommandExecutor.js +16 -12
- package/dist/utils/client.js +7 -7
- package/dist/utils/config.js +130 -4
- package/dist/utils/interactiveCommand.js +2 -2
- package/dist/utils/output.js +17 -17
- package/dist/utils/ssh.js +160 -0
- package/dist/utils/sshSession.js +16 -12
- package/dist/utils/theme.js +22 -0
- package/dist/utils/url.js +4 -4
- package/package.json +29 -4
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { getClient } from "../utils/client.js";
|
|
6
|
+
// Define available tools for the MCP server
|
|
7
|
+
const TOOLS = [
|
|
8
|
+
{
|
|
9
|
+
name: "list_devboxes",
|
|
10
|
+
description: "List all devboxes with optional filtering by status",
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
status: {
|
|
15
|
+
type: "string",
|
|
16
|
+
description: "Filter by status (running, provisioning, suspended, etc.)",
|
|
17
|
+
enum: [
|
|
18
|
+
"running",
|
|
19
|
+
"provisioning",
|
|
20
|
+
"initializing",
|
|
21
|
+
"suspended",
|
|
22
|
+
"shutdown",
|
|
23
|
+
"failure",
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
limit: {
|
|
27
|
+
type: "number",
|
|
28
|
+
description: "Maximum number of devboxes to return",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "get_devbox",
|
|
35
|
+
description: "Get detailed information about a specific devbox by ID",
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {
|
|
39
|
+
id: {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "The devbox ID",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
required: ["id"],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "create_devbox",
|
|
49
|
+
description: "Create a new devbox with specified configuration",
|
|
50
|
+
inputSchema: {
|
|
51
|
+
type: "object",
|
|
52
|
+
properties: {
|
|
53
|
+
name: {
|
|
54
|
+
type: "string",
|
|
55
|
+
description: "Name for the devbox",
|
|
56
|
+
},
|
|
57
|
+
blueprint_id: {
|
|
58
|
+
type: "string",
|
|
59
|
+
description: "Blueprint ID to use as template",
|
|
60
|
+
},
|
|
61
|
+
snapshot_id: {
|
|
62
|
+
type: "string",
|
|
63
|
+
description: "Snapshot ID to restore from",
|
|
64
|
+
},
|
|
65
|
+
entrypoint: {
|
|
66
|
+
type: "string",
|
|
67
|
+
description: "Entrypoint script to run on startup",
|
|
68
|
+
},
|
|
69
|
+
environment_variables: {
|
|
70
|
+
type: "object",
|
|
71
|
+
description: "Environment variables as key-value pairs",
|
|
72
|
+
},
|
|
73
|
+
resource_size: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "Resource size (SMALL, MEDIUM, LARGE, XLARGE)",
|
|
76
|
+
enum: ["SMALL", "MEDIUM", "LARGE", "XLARGE"],
|
|
77
|
+
},
|
|
78
|
+
keep_alive_seconds: {
|
|
79
|
+
type: "number",
|
|
80
|
+
description: "Keep alive time in seconds",
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "execute_command",
|
|
87
|
+
description: "Execute a command on a devbox and get the result",
|
|
88
|
+
inputSchema: {
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: {
|
|
91
|
+
devbox_id: {
|
|
92
|
+
type: "string",
|
|
93
|
+
description: "The devbox ID to execute the command on",
|
|
94
|
+
},
|
|
95
|
+
command: {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: "The command to execute",
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
required: ["devbox_id", "command"],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "shutdown_devbox",
|
|
105
|
+
description: "Shutdown a devbox by ID",
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: "object",
|
|
108
|
+
properties: {
|
|
109
|
+
id: {
|
|
110
|
+
type: "string",
|
|
111
|
+
description: "The devbox ID to shutdown",
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
required: ["id"],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "suspend_devbox",
|
|
119
|
+
description: "Suspend a devbox by ID",
|
|
120
|
+
inputSchema: {
|
|
121
|
+
type: "object",
|
|
122
|
+
properties: {
|
|
123
|
+
id: {
|
|
124
|
+
type: "string",
|
|
125
|
+
description: "The devbox ID to suspend",
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
required: ["id"],
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "resume_devbox",
|
|
133
|
+
description: "Resume a suspended devbox by ID",
|
|
134
|
+
inputSchema: {
|
|
135
|
+
type: "object",
|
|
136
|
+
properties: {
|
|
137
|
+
id: {
|
|
138
|
+
type: "string",
|
|
139
|
+
description: "The devbox ID to resume",
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
required: ["id"],
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "list_blueprints",
|
|
147
|
+
description: "List all available blueprints",
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: "object",
|
|
150
|
+
properties: {
|
|
151
|
+
limit: {
|
|
152
|
+
type: "number",
|
|
153
|
+
description: "Maximum number of blueprints to return",
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "list_snapshots",
|
|
160
|
+
description: "List all snapshots",
|
|
161
|
+
inputSchema: {
|
|
162
|
+
type: "object",
|
|
163
|
+
properties: {
|
|
164
|
+
devbox_id: {
|
|
165
|
+
type: "string",
|
|
166
|
+
description: "Filter snapshots by devbox ID",
|
|
167
|
+
},
|
|
168
|
+
limit: {
|
|
169
|
+
type: "number",
|
|
170
|
+
description: "Maximum number of snapshots to return",
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: "create_snapshot",
|
|
177
|
+
description: "Create a snapshot of a devbox",
|
|
178
|
+
inputSchema: {
|
|
179
|
+
type: "object",
|
|
180
|
+
properties: {
|
|
181
|
+
devbox_id: {
|
|
182
|
+
type: "string",
|
|
183
|
+
description: "The devbox ID to snapshot",
|
|
184
|
+
},
|
|
185
|
+
name: {
|
|
186
|
+
type: "string",
|
|
187
|
+
description: "Name for the snapshot",
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
required: ["devbox_id"],
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
];
|
|
194
|
+
// Create the MCP server
|
|
195
|
+
const server = new Server({
|
|
196
|
+
name: "runloop-mcp-server",
|
|
197
|
+
version: "1.0.0",
|
|
198
|
+
}, {
|
|
199
|
+
capabilities: {
|
|
200
|
+
tools: {},
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
// Handle tool listing
|
|
204
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
205
|
+
return {
|
|
206
|
+
tools: TOOLS,
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
// Handle tool execution
|
|
210
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
211
|
+
const { name, arguments: args } = request.params;
|
|
212
|
+
try {
|
|
213
|
+
const client = getClient();
|
|
214
|
+
if (!args) {
|
|
215
|
+
throw new Error("Missing arguments");
|
|
216
|
+
}
|
|
217
|
+
switch (name) {
|
|
218
|
+
case "list_devboxes": {
|
|
219
|
+
const result = await client.devboxes.list({
|
|
220
|
+
status: args.status,
|
|
221
|
+
limit: args.limit,
|
|
222
|
+
});
|
|
223
|
+
return {
|
|
224
|
+
content: [
|
|
225
|
+
{
|
|
226
|
+
type: "text",
|
|
227
|
+
text: JSON.stringify(result, null, 2),
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
case "get_devbox": {
|
|
233
|
+
const result = await client.devboxes.retrieve(args.id);
|
|
234
|
+
return {
|
|
235
|
+
content: [
|
|
236
|
+
{
|
|
237
|
+
type: "text",
|
|
238
|
+
text: JSON.stringify(result, null, 2),
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
case "create_devbox": {
|
|
244
|
+
const createParams = {};
|
|
245
|
+
if (args.name)
|
|
246
|
+
createParams.name = args.name;
|
|
247
|
+
if (args.blueprint_id)
|
|
248
|
+
createParams.blueprint_id = args.blueprint_id;
|
|
249
|
+
if (args.snapshot_id)
|
|
250
|
+
createParams.snapshot_id = args.snapshot_id;
|
|
251
|
+
if (args.entrypoint)
|
|
252
|
+
createParams.entrypoint = args.entrypoint;
|
|
253
|
+
if (args.environment_variables)
|
|
254
|
+
createParams.environment_variables = args.environment_variables;
|
|
255
|
+
if (args.resource_size) {
|
|
256
|
+
createParams.launch_parameters = {
|
|
257
|
+
resource_size_request: args.resource_size,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
if (args.keep_alive_seconds) {
|
|
261
|
+
if (!createParams.launch_parameters)
|
|
262
|
+
createParams.launch_parameters = {};
|
|
263
|
+
createParams.launch_parameters.keep_alive_time_seconds =
|
|
264
|
+
args.keep_alive_seconds;
|
|
265
|
+
}
|
|
266
|
+
const result = await client.devboxes.create(createParams);
|
|
267
|
+
return {
|
|
268
|
+
content: [
|
|
269
|
+
{
|
|
270
|
+
type: "text",
|
|
271
|
+
text: JSON.stringify(result, null, 2),
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
case "execute_command": {
|
|
277
|
+
const result = await client.devboxes.executeSync(args.devbox_id, {
|
|
278
|
+
command: args.command,
|
|
279
|
+
});
|
|
280
|
+
return {
|
|
281
|
+
content: [
|
|
282
|
+
{
|
|
283
|
+
type: "text",
|
|
284
|
+
text: JSON.stringify(result, null, 2),
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
case "shutdown_devbox": {
|
|
290
|
+
const result = await client.devboxes.shutdown(args.id);
|
|
291
|
+
return {
|
|
292
|
+
content: [
|
|
293
|
+
{
|
|
294
|
+
type: "text",
|
|
295
|
+
text: JSON.stringify(result, null, 2),
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
case "suspend_devbox": {
|
|
301
|
+
const result = await client.devboxes.suspend(args.id);
|
|
302
|
+
return {
|
|
303
|
+
content: [
|
|
304
|
+
{
|
|
305
|
+
type: "text",
|
|
306
|
+
text: JSON.stringify(result, null, 2),
|
|
307
|
+
},
|
|
308
|
+
],
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
case "resume_devbox": {
|
|
312
|
+
const result = await client.devboxes.resume(args.id);
|
|
313
|
+
return {
|
|
314
|
+
content: [
|
|
315
|
+
{
|
|
316
|
+
type: "text",
|
|
317
|
+
text: JSON.stringify(result, null, 2),
|
|
318
|
+
},
|
|
319
|
+
],
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
case "list_blueprints": {
|
|
323
|
+
const result = await client.blueprints.list({
|
|
324
|
+
limit: args.limit,
|
|
325
|
+
});
|
|
326
|
+
return {
|
|
327
|
+
content: [
|
|
328
|
+
{
|
|
329
|
+
type: "text",
|
|
330
|
+
text: JSON.stringify(result, null, 2),
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
case "list_snapshots": {
|
|
336
|
+
const params = {};
|
|
337
|
+
if (args.devbox_id)
|
|
338
|
+
params.devbox_id = args.devbox_id;
|
|
339
|
+
const allSnapshots = [];
|
|
340
|
+
let count = 0;
|
|
341
|
+
const limit = args.limit || 100;
|
|
342
|
+
for await (const snapshot of client.devboxes.listDiskSnapshots(params)) {
|
|
343
|
+
allSnapshots.push(snapshot);
|
|
344
|
+
count++;
|
|
345
|
+
if (count >= limit)
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
content: [
|
|
350
|
+
{
|
|
351
|
+
type: "text",
|
|
352
|
+
text: JSON.stringify(allSnapshots, null, 2),
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
case "create_snapshot": {
|
|
358
|
+
const params = {};
|
|
359
|
+
if (args.name)
|
|
360
|
+
params.name = args.name;
|
|
361
|
+
const result = await client.devboxes.snapshotDisk(args.devbox_id, params);
|
|
362
|
+
return {
|
|
363
|
+
content: [
|
|
364
|
+
{
|
|
365
|
+
type: "text",
|
|
366
|
+
text: JSON.stringify(result, null, 2),
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
default:
|
|
372
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
return {
|
|
377
|
+
content: [
|
|
378
|
+
{
|
|
379
|
+
type: "text",
|
|
380
|
+
text: `Error: ${error.message}`,
|
|
381
|
+
},
|
|
382
|
+
],
|
|
383
|
+
isError: true,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
// Start the server
|
|
388
|
+
async function main() {
|
|
389
|
+
const transport = new StdioServerTransport();
|
|
390
|
+
await server.connect(transport);
|
|
391
|
+
// Log to stderr so it doesn't interfere with MCP protocol on stdout
|
|
392
|
+
console.error("Runloop MCP server running on stdio");
|
|
393
|
+
}
|
|
394
|
+
main().catch((error) => {
|
|
395
|
+
console.error("Fatal error in main():", error);
|
|
396
|
+
process.exit(1);
|
|
397
|
+
});
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
* Shared class for executing commands with different output formats
|
|
3
3
|
* Reduces code duplication across all command files
|
|
4
4
|
*/
|
|
5
|
-
import { render } from
|
|
6
|
-
import { getClient } from
|
|
7
|
-
import { shouldUseNonInteractiveOutput, outputList, outputResult } from
|
|
8
|
-
import YAML from
|
|
5
|
+
import { render } from "ink";
|
|
6
|
+
import { getClient } from "./client.js";
|
|
7
|
+
import { shouldUseNonInteractiveOutput, outputList, outputResult, } from "./output.js";
|
|
8
|
+
import YAML from "yaml";
|
|
9
9
|
export class CommandExecutor {
|
|
10
10
|
options;
|
|
11
11
|
constructor(options = {}) {
|
|
12
12
|
this.options = options;
|
|
13
|
+
// Set default output format to json if none specified
|
|
14
|
+
if (!this.options.output) {
|
|
15
|
+
this.options.output = "json";
|
|
16
|
+
}
|
|
13
17
|
}
|
|
14
18
|
/**
|
|
15
19
|
* Execute a list command with automatic format handling
|
|
@@ -29,12 +33,12 @@ export class CommandExecutor {
|
|
|
29
33
|
}
|
|
30
34
|
// Interactive mode
|
|
31
35
|
// Enter alternate screen buffer
|
|
32
|
-
process.stdout.write(
|
|
36
|
+
process.stdout.write("\x1b[?1049h");
|
|
33
37
|
console.clear();
|
|
34
38
|
const { waitUntilExit } = render(renderUI());
|
|
35
39
|
await waitUntilExit();
|
|
36
40
|
// Exit alternate screen buffer
|
|
37
|
-
process.stdout.write(
|
|
41
|
+
process.stdout.write("\x1b[?1049l");
|
|
38
42
|
}
|
|
39
43
|
/**
|
|
40
44
|
* Execute a create/action command with automatic format handling
|
|
@@ -52,12 +56,12 @@ export class CommandExecutor {
|
|
|
52
56
|
}
|
|
53
57
|
// Interactive mode
|
|
54
58
|
// Enter alternate screen buffer
|
|
55
|
-
process.stdout.write(
|
|
59
|
+
process.stdout.write("\x1b[?1049h");
|
|
56
60
|
console.clear();
|
|
57
61
|
const { waitUntilExit } = render(renderUI());
|
|
58
62
|
await waitUntilExit();
|
|
59
63
|
// Exit alternate screen buffer
|
|
60
|
-
process.stdout.write(
|
|
64
|
+
process.stdout.write("\x1b[?1049l");
|
|
61
65
|
}
|
|
62
66
|
/**
|
|
63
67
|
* Execute a delete command with automatic format handling
|
|
@@ -66,7 +70,7 @@ export class CommandExecutor {
|
|
|
66
70
|
if (shouldUseNonInteractiveOutput(this.options)) {
|
|
67
71
|
try {
|
|
68
72
|
await performDelete();
|
|
69
|
-
outputResult({ id, status:
|
|
73
|
+
outputResult({ id, status: "deleted" }, this.options);
|
|
70
74
|
}
|
|
71
75
|
catch (err) {
|
|
72
76
|
this.handleError(err);
|
|
@@ -75,11 +79,11 @@ export class CommandExecutor {
|
|
|
75
79
|
}
|
|
76
80
|
// Interactive mode
|
|
77
81
|
// Enter alternate screen buffer
|
|
78
|
-
process.stdout.write(
|
|
82
|
+
process.stdout.write("\x1b[?1049h");
|
|
79
83
|
const { waitUntilExit } = render(renderUI());
|
|
80
84
|
await waitUntilExit();
|
|
81
85
|
// Exit alternate screen buffer
|
|
82
|
-
process.stdout.write(
|
|
86
|
+
process.stdout.write("\x1b[?1049l");
|
|
83
87
|
}
|
|
84
88
|
/**
|
|
85
89
|
* Fetch items from an async iterator with optional filtering and limits
|
|
@@ -104,7 +108,7 @@ export class CommandExecutor {
|
|
|
104
108
|
* Handle errors consistently across all commands
|
|
105
109
|
*/
|
|
106
110
|
handleError(error) {
|
|
107
|
-
if (this.options.output ===
|
|
111
|
+
if (this.options.output === "yaml") {
|
|
108
112
|
console.error(YAML.stringify({ error: error.message }));
|
|
109
113
|
}
|
|
110
114
|
else {
|
package/dist/utils/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import Runloop from
|
|
2
|
-
import { getConfig } from
|
|
1
|
+
import Runloop from "@runloop/api-client";
|
|
2
|
+
import { getConfig } from "./config.js";
|
|
3
3
|
/**
|
|
4
4
|
* Get the base URL based on RUNLOOP_ENV environment variable
|
|
5
5
|
* - dev: https://api.runloop.pro
|
|
@@ -8,17 +8,17 @@ import { getConfig } from './config.js';
|
|
|
8
8
|
function getBaseUrl() {
|
|
9
9
|
const env = process.env.RUNLOOP_ENV?.toLowerCase();
|
|
10
10
|
switch (env) {
|
|
11
|
-
case
|
|
12
|
-
return
|
|
13
|
-
case
|
|
11
|
+
case "dev":
|
|
12
|
+
return "https://api.runloop.pro";
|
|
13
|
+
case "prod":
|
|
14
14
|
default:
|
|
15
|
-
return
|
|
15
|
+
return "https://api.runloop.ai";
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
export function getClient() {
|
|
19
19
|
const config = getConfig();
|
|
20
20
|
if (!config.apiKey) {
|
|
21
|
-
throw new Error(
|
|
21
|
+
throw new Error("API key not configured. Run: rli auth");
|
|
22
22
|
}
|
|
23
23
|
const baseURL = getBaseUrl();
|
|
24
24
|
return new Runloop({
|
package/dist/utils/config.js
CHANGED
|
@@ -1,17 +1,143 @@
|
|
|
1
|
-
import Conf from
|
|
1
|
+
import Conf from "conf";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { existsSync, statSync, mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { dirname } from "path";
|
|
2
7
|
const config = new Conf({
|
|
3
|
-
projectName:
|
|
8
|
+
projectName: "runloop-cli",
|
|
4
9
|
});
|
|
5
10
|
export function getConfig() {
|
|
6
11
|
// Check environment variable first, then fall back to stored config
|
|
7
|
-
const apiKey = process.env.RUNLOOP_API_KEY || config.get(
|
|
12
|
+
const apiKey = process.env.RUNLOOP_API_KEY || config.get("apiKey");
|
|
8
13
|
return {
|
|
9
14
|
apiKey,
|
|
10
15
|
};
|
|
11
16
|
}
|
|
12
17
|
export function setApiKey(apiKey) {
|
|
13
|
-
config.set(
|
|
18
|
+
config.set("apiKey", apiKey);
|
|
14
19
|
}
|
|
15
20
|
export function clearConfig() {
|
|
16
21
|
config.clear();
|
|
17
22
|
}
|
|
23
|
+
export function baseUrl() {
|
|
24
|
+
return process.env.RUNLOOP_ENV === "dev"
|
|
25
|
+
? "https://api.runloop.pro"
|
|
26
|
+
: "https://api.runloop.ai";
|
|
27
|
+
}
|
|
28
|
+
export function sshUrl() {
|
|
29
|
+
return process.env.RUNLOOP_ENV === "dev"
|
|
30
|
+
? "ssh.runloop.pro:443"
|
|
31
|
+
: "ssh.runloop.ai:443";
|
|
32
|
+
}
|
|
33
|
+
export function getCacheDir() {
|
|
34
|
+
return join(homedir(), ".cache", "rl-cli");
|
|
35
|
+
}
|
|
36
|
+
function getCurrentVersion() {
|
|
37
|
+
try {
|
|
38
|
+
// First try environment variable (when installed via npm)
|
|
39
|
+
if (process.env.npm_package_version) {
|
|
40
|
+
return process.env.npm_package_version;
|
|
41
|
+
}
|
|
42
|
+
// Fall back to reading package.json directly
|
|
43
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
44
|
+
const __dirname = dirname(__filename);
|
|
45
|
+
// When running from dist/, we need to go up two levels to find package.json
|
|
46
|
+
const packageJsonPath = join(__dirname, "../../package.json");
|
|
47
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
48
|
+
return packageJson.version;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
// Ultimate fallback
|
|
52
|
+
return "0.1.0";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function shouldCheckForUpdates() {
|
|
56
|
+
const cacheDir = getCacheDir();
|
|
57
|
+
const cacheFile = join(cacheDir, "last_update_check");
|
|
58
|
+
if (!existsSync(cacheFile)) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
const stats = statSync(cacheFile);
|
|
62
|
+
const hoursSinceUpdate = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60);
|
|
63
|
+
return hoursSinceUpdate >= 6;
|
|
64
|
+
}
|
|
65
|
+
export function hasCachedUpdateInfo() {
|
|
66
|
+
const cacheDir = getCacheDir();
|
|
67
|
+
const cacheFile = join(cacheDir, "last_update_check");
|
|
68
|
+
return existsSync(cacheFile);
|
|
69
|
+
}
|
|
70
|
+
export function updateCheckCache() {
|
|
71
|
+
const cacheDir = getCacheDir();
|
|
72
|
+
const cacheFile = join(cacheDir, "last_update_check");
|
|
73
|
+
// Create cache directory if it doesn't exist
|
|
74
|
+
if (!existsSync(cacheDir)) {
|
|
75
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
// Touch the cache file
|
|
78
|
+
writeFileSync(cacheFile, "");
|
|
79
|
+
}
|
|
80
|
+
export async function checkForUpdates(force = false) {
|
|
81
|
+
const currentVersion = getCurrentVersion();
|
|
82
|
+
// Always show cached result if available and not forcing
|
|
83
|
+
if (!force && hasCachedUpdateInfo() && !shouldCheckForUpdates()) {
|
|
84
|
+
// Show cached update info (we know there's an update available)
|
|
85
|
+
console.error(`\nš Update available: ${currentVersion} ā 0.1.0\n` +
|
|
86
|
+
` Run: npm install -g @runloop/rl-cli@latest\n\n`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// Only fetch from npm if cache is expired or forcing
|
|
90
|
+
if (!force && !shouldCheckForUpdates()) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const response = await fetch("https://registry.npmjs.org/@runloop/rl-cli/latest");
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
if (force) {
|
|
97
|
+
console.error("ā Failed to check for updates\n");
|
|
98
|
+
}
|
|
99
|
+
return; // Silently fail if we can't check
|
|
100
|
+
}
|
|
101
|
+
const data = await response.json();
|
|
102
|
+
const latestVersion = data.version;
|
|
103
|
+
if (force) {
|
|
104
|
+
console.error(`Current version: ${currentVersion}\n`);
|
|
105
|
+
console.error(`Latest version: ${latestVersion}\n`);
|
|
106
|
+
}
|
|
107
|
+
if (latestVersion && latestVersion !== currentVersion) {
|
|
108
|
+
// Check if current version is older than latest
|
|
109
|
+
const isUpdateAvailable = compareVersions(latestVersion, currentVersion) > 0;
|
|
110
|
+
if (isUpdateAvailable) {
|
|
111
|
+
console.error(`\nš Update available: ${currentVersion} ā ${latestVersion}\n` +
|
|
112
|
+
` Run: npm install -g @runloop/rl-cli@latest\n\n`);
|
|
113
|
+
}
|
|
114
|
+
else if (force) {
|
|
115
|
+
console.error("ā
You're running the latest version!\n");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else if (force) {
|
|
119
|
+
console.error("ā
You're running the latest version!\n");
|
|
120
|
+
}
|
|
121
|
+
// Update the cache to indicate we've checked
|
|
122
|
+
updateCheckCache();
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
if (force) {
|
|
126
|
+
console.error(`ā Error checking for updates: ${error}\n`);
|
|
127
|
+
}
|
|
128
|
+
// Silently fail - don't interrupt the user's workflow
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function compareVersions(version1, version2) {
|
|
132
|
+
const v1parts = version1.split('.').map(Number);
|
|
133
|
+
const v2parts = version2.split('.').map(Number);
|
|
134
|
+
for (let i = 0; i < Math.max(v1parts.length, v2parts.length); i++) {
|
|
135
|
+
const v1part = v1parts[i] || 0;
|
|
136
|
+
const v2part = v2parts[i] || 0;
|
|
137
|
+
if (v1part > v2part)
|
|
138
|
+
return 1;
|
|
139
|
+
if (v1part < v2part)
|
|
140
|
+
return -1;
|
|
141
|
+
}
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export async function runInteractiveCommand(command) {
|
|
5
5
|
// Enter alternate screen buffer
|
|
6
|
-
process.stdout.write(
|
|
6
|
+
process.stdout.write("\x1b[?1049h");
|
|
7
7
|
try {
|
|
8
8
|
await command();
|
|
9
9
|
}
|
|
10
10
|
finally {
|
|
11
11
|
// Exit alternate screen buffer
|
|
12
|
-
process.stdout.write(
|
|
12
|
+
process.stdout.write("\x1b[?1049l");
|
|
13
13
|
}
|
|
14
14
|
}
|