@runloop/rl-cli 0.0.2 → 0.1.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.
Files changed (73) hide show
  1. package/README.md +64 -29
  2. package/dist/cli.js +420 -76
  3. package/dist/commands/auth.js +12 -10
  4. package/dist/commands/blueprint/create.js +108 -0
  5. package/dist/commands/blueprint/get.js +37 -0
  6. package/dist/commands/blueprint/list.js +303 -224
  7. package/dist/commands/blueprint/logs.js +40 -0
  8. package/dist/commands/blueprint/preview.js +45 -0
  9. package/dist/commands/devbox/create.js +10 -9
  10. package/dist/commands/devbox/delete.js +8 -8
  11. package/dist/commands/devbox/download.js +49 -0
  12. package/dist/commands/devbox/exec.js +23 -13
  13. package/dist/commands/devbox/execAsync.js +43 -0
  14. package/dist/commands/devbox/get.js +37 -0
  15. package/dist/commands/devbox/getAsync.js +37 -0
  16. package/dist/commands/devbox/list.js +390 -205
  17. package/dist/commands/devbox/logs.js +40 -0
  18. package/dist/commands/devbox/read.js +49 -0
  19. package/dist/commands/devbox/resume.js +37 -0
  20. package/dist/commands/devbox/rsync.js +118 -0
  21. package/dist/commands/devbox/scp.js +122 -0
  22. package/dist/commands/devbox/shutdown.js +37 -0
  23. package/dist/commands/devbox/ssh.js +104 -0
  24. package/dist/commands/devbox/suspend.js +37 -0
  25. package/dist/commands/devbox/tunnel.js +120 -0
  26. package/dist/commands/devbox/upload.js +10 -10
  27. package/dist/commands/devbox/write.js +51 -0
  28. package/dist/commands/mcp-http.js +37 -0
  29. package/dist/commands/mcp-install.js +120 -0
  30. package/dist/commands/mcp.js +30 -0
  31. package/dist/commands/menu.js +70 -0
  32. package/dist/commands/object/delete.js +37 -0
  33. package/dist/commands/object/download.js +88 -0
  34. package/dist/commands/object/get.js +37 -0
  35. package/dist/commands/object/list.js +112 -0
  36. package/dist/commands/object/upload.js +130 -0
  37. package/dist/commands/snapshot/create.js +12 -11
  38. package/dist/commands/snapshot/delete.js +8 -8
  39. package/dist/commands/snapshot/list.js +59 -91
  40. package/dist/commands/snapshot/status.js +37 -0
  41. package/dist/components/ActionsPopup.js +16 -13
  42. package/dist/components/Banner.js +5 -8
  43. package/dist/components/Breadcrumb.js +6 -6
  44. package/dist/components/DetailView.js +7 -4
  45. package/dist/components/DevboxActionsMenu.js +347 -189
  46. package/dist/components/DevboxCard.js +15 -14
  47. package/dist/components/DevboxCreatePage.js +147 -113
  48. package/dist/components/DevboxDetailPage.js +182 -103
  49. package/dist/components/ErrorMessage.js +5 -4
  50. package/dist/components/Header.js +4 -3
  51. package/dist/components/MainMenu.js +72 -0
  52. package/dist/components/MetadataDisplay.js +17 -9
  53. package/dist/components/OperationsMenu.js +6 -5
  54. package/dist/components/ResourceActionsMenu.js +117 -0
  55. package/dist/components/ResourceListView.js +213 -0
  56. package/dist/components/Spinner.js +5 -4
  57. package/dist/components/StatusBadge.js +81 -31
  58. package/dist/components/SuccessMessage.js +4 -3
  59. package/dist/components/Table.example.js +53 -23
  60. package/dist/components/Table.js +19 -11
  61. package/dist/hooks/useCursorPagination.js +125 -0
  62. package/dist/mcp/server-http.js +416 -0
  63. package/dist/mcp/server.js +397 -0
  64. package/dist/utils/CommandExecutor.js +22 -6
  65. package/dist/utils/client.js +20 -3
  66. package/dist/utils/config.js +40 -4
  67. package/dist/utils/interactiveCommand.js +14 -0
  68. package/dist/utils/output.js +17 -17
  69. package/dist/utils/ssh.js +160 -0
  70. package/dist/utils/sshSession.js +29 -0
  71. package/dist/utils/theme.js +22 -0
  72. package/dist/utils/url.js +39 -0
  73. 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 'ink';
6
- import { getClient } from './client.js';
7
- import { shouldUseNonInteractiveOutput, outputList, outputResult } from './output.js';
8
- import YAML from 'yaml';
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
@@ -28,9 +32,13 @@ export class CommandExecutor {
28
32
  return;
29
33
  }
30
34
  // Interactive mode
35
+ // Enter alternate screen buffer
36
+ process.stdout.write("\x1b[?1049h");
31
37
  console.clear();
32
38
  const { waitUntilExit } = render(renderUI());
33
39
  await waitUntilExit();
40
+ // Exit alternate screen buffer
41
+ process.stdout.write("\x1b[?1049l");
34
42
  }
35
43
  /**
36
44
  * Execute a create/action command with automatic format handling
@@ -47,9 +55,13 @@ export class CommandExecutor {
47
55
  return;
48
56
  }
49
57
  // Interactive mode
58
+ // Enter alternate screen buffer
59
+ process.stdout.write("\x1b[?1049h");
50
60
  console.clear();
51
61
  const { waitUntilExit } = render(renderUI());
52
62
  await waitUntilExit();
63
+ // Exit alternate screen buffer
64
+ process.stdout.write("\x1b[?1049l");
53
65
  }
54
66
  /**
55
67
  * Execute a delete command with automatic format handling
@@ -58,7 +70,7 @@ export class CommandExecutor {
58
70
  if (shouldUseNonInteractiveOutput(this.options)) {
59
71
  try {
60
72
  await performDelete();
61
- outputResult({ id, status: 'deleted' }, this.options);
73
+ outputResult({ id, status: "deleted" }, this.options);
62
74
  }
63
75
  catch (err) {
64
76
  this.handleError(err);
@@ -66,8 +78,12 @@ export class CommandExecutor {
66
78
  return;
67
79
  }
68
80
  // Interactive mode
81
+ // Enter alternate screen buffer
82
+ process.stdout.write("\x1b[?1049h");
69
83
  const { waitUntilExit } = render(renderUI());
70
84
  await waitUntilExit();
85
+ // Exit alternate screen buffer
86
+ process.stdout.write("\x1b[?1049l");
71
87
  }
72
88
  /**
73
89
  * Fetch items from an async iterator with optional filtering and limits
@@ -92,7 +108,7 @@ export class CommandExecutor {
92
108
  * Handle errors consistently across all commands
93
109
  */
94
110
  handleError(error) {
95
- if (this.options.output === 'yaml') {
111
+ if (this.options.output === "yaml") {
96
112
  console.error(YAML.stringify({ error: error.message }));
97
113
  }
98
114
  else {
@@ -1,12 +1,29 @@
1
- import Runloop from '@runloop/api-client';
2
- import { getConfig } from './config.js';
1
+ import Runloop from "@runloop/api-client";
2
+ import { getConfig } from "./config.js";
3
+ /**
4
+ * Get the base URL based on RUNLOOP_ENV environment variable
5
+ * - dev: https://api.runloop.pro
6
+ * - prod or unset: https://api.runloop.ai (default)
7
+ */
8
+ function getBaseUrl() {
9
+ const env = process.env.RUNLOOP_ENV?.toLowerCase();
10
+ switch (env) {
11
+ case "dev":
12
+ return "https://api.runloop.pro";
13
+ case "prod":
14
+ default:
15
+ return "https://api.runloop.ai";
16
+ }
17
+ }
3
18
  export function getClient() {
4
19
  const config = getConfig();
5
20
  if (!config.apiKey) {
6
- throw new Error('API key not configured. Run: rln auth');
21
+ throw new Error("API key not configured. Run: rli auth");
7
22
  }
23
+ const baseURL = getBaseUrl();
8
24
  return new Runloop({
9
25
  bearerToken: config.apiKey,
26
+ baseURL,
10
27
  timeout: 10000, // 10 seconds instead of default 30 seconds
11
28
  maxRetries: 2, // 2 retries instead of default 5 (only for retryable errors)
12
29
  });
@@ -1,17 +1,53 @@
1
- import Conf from 'conf';
1
+ import Conf from "conf";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ import { existsSync, statSync, mkdirSync, writeFileSync } from "fs";
2
5
  const config = new Conf({
3
- projectName: 'runloop-cli',
6
+ projectName: "runloop-cli",
4
7
  });
5
8
  export function getConfig() {
6
9
  // Check environment variable first, then fall back to stored config
7
- const apiKey = process.env.RUNLOOP_API_KEY || config.get('apiKey');
10
+ const apiKey = process.env.RUNLOOP_API_KEY || config.get("apiKey");
8
11
  return {
9
12
  apiKey,
10
13
  };
11
14
  }
12
15
  export function setApiKey(apiKey) {
13
- config.set('apiKey', apiKey);
16
+ config.set("apiKey", apiKey);
14
17
  }
15
18
  export function clearConfig() {
16
19
  config.clear();
17
20
  }
21
+ export function baseUrl() {
22
+ return process.env.RUNLOOP_ENV === "dev"
23
+ ? "https://api.runloop.pro"
24
+ : "https://api.runloop.ai";
25
+ }
26
+ export function sshUrl() {
27
+ return process.env.RUNLOOP_ENV === "dev"
28
+ ? "ssh.runloop.pro:443"
29
+ : "ssh.runloop.ai:443";
30
+ }
31
+ export function getCacheDir() {
32
+ return join(homedir(), ".cache", "rl-cli");
33
+ }
34
+ export function shouldCheckForUpdates() {
35
+ const cacheDir = getCacheDir();
36
+ const cacheFile = join(cacheDir, "last_update_check");
37
+ if (!existsSync(cacheFile)) {
38
+ return true;
39
+ }
40
+ const stats = statSync(cacheFile);
41
+ const daysSinceUpdate = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60 * 24);
42
+ return daysSinceUpdate >= 1;
43
+ }
44
+ export function updateCheckCache() {
45
+ const cacheDir = getCacheDir();
46
+ const cacheFile = join(cacheDir, "last_update_check");
47
+ // Create cache directory if it doesn't exist
48
+ if (!existsSync(cacheDir)) {
49
+ mkdirSync(cacheDir, { recursive: true });
50
+ }
51
+ // Touch the cache file
52
+ writeFileSync(cacheFile, "");
53
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Wrapper for interactive commands that need alternate screen buffer management
3
+ */
4
+ export async function runInteractiveCommand(command) {
5
+ // Enter alternate screen buffer
6
+ process.stdout.write("\x1b[?1049h");
7
+ try {
8
+ await command();
9
+ }
10
+ finally {
11
+ // Exit alternate screen buffer
12
+ process.stdout.write("\x1b[?1049l");
13
+ }
14
+ }
@@ -1,31 +1,31 @@
1
1
  /**
2
2
  * Utility for handling different output formats across CLI commands
3
3
  */
4
- import YAML from 'yaml';
4
+ import YAML from "yaml";
5
5
  /**
6
6
  * Check if the command should use non-interactive output
7
7
  */
8
8
  export function shouldUseNonInteractiveOutput(options) {
9
- return !!options.output;
9
+ return !!options.output && options.output !== "interactive";
10
10
  }
11
11
  /**
12
12
  * Output data in the specified format
13
13
  */
14
- export function outputData(data, format = 'json') {
15
- if (format === 'json') {
14
+ export function outputData(data, format = "json") {
15
+ if (format === "json") {
16
16
  console.log(JSON.stringify(data, null, 2));
17
17
  return;
18
18
  }
19
- if (format === 'yaml') {
19
+ if (format === "yaml") {
20
20
  console.log(YAML.stringify(data));
21
21
  return;
22
22
  }
23
- if (format === 'text') {
23
+ if (format === "text") {
24
24
  // Simple text output
25
25
  if (Array.isArray(data)) {
26
26
  // For lists of complex objects, just output IDs
27
27
  data.forEach((item) => {
28
- if (typeof item === 'object' && item !== null && 'id' in item) {
28
+ if (typeof item === "object" && item !== null && "id" in item) {
29
29
  console.log(item.id);
30
30
  }
31
31
  else {
@@ -45,7 +45,7 @@ export function outputData(data, format = 'json') {
45
45
  * Format a single item as text output
46
46
  */
47
47
  function formatTextOutput(item) {
48
- if (typeof item === 'string') {
48
+ if (typeof item === "string") {
49
49
  return item;
50
50
  }
51
51
  // For objects, create a simple key: value format
@@ -55,7 +55,7 @@ function formatTextOutput(item) {
55
55
  lines.push(`${key}: ${value}`);
56
56
  }
57
57
  }
58
- return lines.join('\n');
58
+ return lines.join("\n");
59
59
  }
60
60
  /**
61
61
  * Output a single result (for create, delete, etc)
@@ -83,10 +83,10 @@ export function outputList(items, options) {
83
83
  */
84
84
  export function outputError(error, options) {
85
85
  if (shouldUseNonInteractiveOutput(options)) {
86
- if (options.output === 'json') {
86
+ if (options.output === "json") {
87
87
  console.error(JSON.stringify({ error: error.message }, null, 2));
88
88
  }
89
- else if (options.output === 'yaml') {
89
+ else if (options.output === "yaml") {
90
90
  console.error(YAML.stringify({ error: error.message }));
91
91
  }
92
92
  else {
@@ -101,14 +101,14 @@ export function outputError(error, options) {
101
101
  * Validate output format option
102
102
  */
103
103
  export function validateOutputFormat(format) {
104
- if (!format || format === 'text') {
105
- return 'text';
104
+ if (!format || format === "text") {
105
+ return "text";
106
106
  }
107
- if (format === 'json') {
108
- return 'json';
107
+ if (format === "json") {
108
+ return "json";
109
109
  }
110
- if (format === 'yaml') {
111
- return 'yaml';
110
+ if (format === "yaml") {
111
+ return "yaml";
112
112
  }
113
113
  console.error(`Unknown output format: ${format}. Valid options: text, json, yaml`);
114
114
  process.exit(1);