@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
package/dist/utils/output.js
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Utility for handling different output formats across CLI commands
|
|
3
3
|
*/
|
|
4
|
-
import YAML from
|
|
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 =
|
|
15
|
-
if (format ===
|
|
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 ===
|
|
19
|
+
if (format === "yaml") {
|
|
20
20
|
console.log(YAML.stringify(data));
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
|
-
if (format ===
|
|
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 ===
|
|
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 ===
|
|
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(
|
|
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 ===
|
|
86
|
+
if (options.output === "json") {
|
|
87
87
|
console.error(JSON.stringify({ error: error.message }, null, 2));
|
|
88
88
|
}
|
|
89
|
-
else if (options.output ===
|
|
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 ===
|
|
105
|
-
return
|
|
104
|
+
if (!format || format === "text") {
|
|
105
|
+
return "text";
|
|
106
106
|
}
|
|
107
|
-
if (format ===
|
|
108
|
-
return
|
|
107
|
+
if (format === "json") {
|
|
108
|
+
return "json";
|
|
109
109
|
}
|
|
110
|
-
if (format ===
|
|
111
|
-
return
|
|
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);
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import { writeFile, mkdir, chmod } from "fs/promises";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import { getClient } from "./client.js";
|
|
7
|
+
const execAsync = promisify(exec);
|
|
8
|
+
export function constructSSHConfig(options) {
|
|
9
|
+
return `Host ${options.hostname}
|
|
10
|
+
User ${options.username}
|
|
11
|
+
Hostname ${options.hostname}
|
|
12
|
+
Port ${options.port}
|
|
13
|
+
IdentityFile ${options.keyPath}
|
|
14
|
+
StrictHostKeyChecking no
|
|
15
|
+
UserKnownHostsFile /dev/null
|
|
16
|
+
ProxyCommand openssl s_client -connect ${options.hostname}:${options.port} -quiet
|
|
17
|
+
`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Get or create SSH key for a devbox
|
|
21
|
+
*/
|
|
22
|
+
export async function getSSHKey(devboxId) {
|
|
23
|
+
try {
|
|
24
|
+
const client = getClient();
|
|
25
|
+
const result = await client.devboxes.createSSHKey(devboxId);
|
|
26
|
+
if (!result || !result.ssh_private_key || !result.url) {
|
|
27
|
+
throw new Error("Failed to create SSH key");
|
|
28
|
+
}
|
|
29
|
+
// Create SSH keys directory
|
|
30
|
+
const sshDir = join(homedir(), ".runloop", "ssh_keys");
|
|
31
|
+
await mkdir(sshDir, { recursive: true });
|
|
32
|
+
// Save private key to file
|
|
33
|
+
const keyfilePath = join(sshDir, `${devboxId}.pem`);
|
|
34
|
+
await writeFile(keyfilePath, result.ssh_private_key, { mode: 0o600 });
|
|
35
|
+
await chmod(keyfilePath, 0o600);
|
|
36
|
+
return {
|
|
37
|
+
keyfilePath,
|
|
38
|
+
privateKey: result.ssh_private_key,
|
|
39
|
+
url: result.url,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error("Failed to create SSH key:", error);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Wait for a devbox to be ready
|
|
49
|
+
*/
|
|
50
|
+
export async function waitForReady(devboxId, timeoutSeconds = 180, pollIntervalSeconds = 3) {
|
|
51
|
+
const startTime = Date.now();
|
|
52
|
+
const client = getClient();
|
|
53
|
+
while (true) {
|
|
54
|
+
try {
|
|
55
|
+
const devbox = await client.devboxes.retrieve(devboxId);
|
|
56
|
+
const elapsed = (Date.now() - startTime) / 1000;
|
|
57
|
+
const remaining = timeoutSeconds - elapsed;
|
|
58
|
+
if (devbox.status === "running") {
|
|
59
|
+
console.log(`Devbox ${devboxId} is ready!`);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
else if (devbox.status === "failure") {
|
|
63
|
+
console.log(`Devbox ${devboxId} failed to start (status: ${devbox.status})`);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
else if (["shutdown", "suspended"].includes(devbox.status)) {
|
|
67
|
+
console.log(`Devbox ${devboxId} is not running (status: ${devbox.status})`);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log(`Devbox ${devboxId} is still ${devbox.status}... (elapsed: ${elapsed.toFixed(0)}s, remaining: ${remaining.toFixed(0)}s)`);
|
|
72
|
+
if (elapsed >= timeoutSeconds) {
|
|
73
|
+
console.log(`Timeout waiting for devbox ${devboxId} to be ready after ${timeoutSeconds} seconds`);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalSeconds * 1000));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const elapsed = (Date.now() - startTime) / 1000;
|
|
81
|
+
if (elapsed >= timeoutSeconds) {
|
|
82
|
+
console.log(`Timeout waiting for devbox ${devboxId} to be ready after ${timeoutSeconds} seconds (error: ${error})`);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
console.log(`Error checking devbox status: ${error}, retrying in ${pollIntervalSeconds} seconds...`);
|
|
86
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalSeconds * 1000));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get SSH URL based on environment
|
|
92
|
+
*/
|
|
93
|
+
export function getSSHUrl() {
|
|
94
|
+
const env = process.env.RUNLOOP_ENV?.toLowerCase();
|
|
95
|
+
return env === "dev" ? "ssh.runloop.pro:443" : "ssh.runloop.ai:443";
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get proxy command for SSH over HTTPS
|
|
99
|
+
*/
|
|
100
|
+
export function getProxyCommand() {
|
|
101
|
+
const sshUrl = getSSHUrl();
|
|
102
|
+
return `openssl s_client -quiet -verify_quiet -servername %h -connect ${sshUrl} 2>/dev/null`;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Execute SSH command
|
|
106
|
+
*/
|
|
107
|
+
export async function executeSSH(devboxId, user, keyfilePath, url, additionalArgs = []) {
|
|
108
|
+
const proxyCommand = getProxyCommand();
|
|
109
|
+
const command = [
|
|
110
|
+
"/usr/bin/ssh",
|
|
111
|
+
"-i",
|
|
112
|
+
keyfilePath,
|
|
113
|
+
"-o",
|
|
114
|
+
`ProxyCommand=${proxyCommand}`,
|
|
115
|
+
"-o",
|
|
116
|
+
"StrictHostKeyChecking=no",
|
|
117
|
+
...additionalArgs,
|
|
118
|
+
`${user}@${url}`,
|
|
119
|
+
];
|
|
120
|
+
try {
|
|
121
|
+
const { stdout, stderr } = await execAsync(command.join(" "));
|
|
122
|
+
if (stdout)
|
|
123
|
+
console.log(stdout);
|
|
124
|
+
if (stderr)
|
|
125
|
+
console.error(stderr);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
console.error("SSH command failed:", error);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Generate SSH config for a devbox
|
|
134
|
+
*/
|
|
135
|
+
export function generateSSHConfig(devboxId, user, keyfilePath, url) {
|
|
136
|
+
const proxyCommand = getProxyCommand();
|
|
137
|
+
return `
|
|
138
|
+
Host ${devboxId}
|
|
139
|
+
Hostname ${url}
|
|
140
|
+
User ${user}
|
|
141
|
+
IdentityFile ${keyfilePath}
|
|
142
|
+
StrictHostKeyChecking no
|
|
143
|
+
ProxyCommand ${proxyCommand}
|
|
144
|
+
`.trim();
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Check if SSH tools are available
|
|
148
|
+
*/
|
|
149
|
+
export async function checkSSHTools() {
|
|
150
|
+
try {
|
|
151
|
+
await execAsync("which ssh");
|
|
152
|
+
await execAsync("which scp");
|
|
153
|
+
await execAsync("which rsync");
|
|
154
|
+
await execAsync("which openssl");
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
package/dist/utils/sshSession.js
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
|
-
import { spawnSync } from
|
|
1
|
+
import { spawnSync } from "child_process";
|
|
2
2
|
export async function runSSHSession(config) {
|
|
3
3
|
// Reset terminal to fix input visibility issues
|
|
4
4
|
// This ensures the terminal is in a proper state after exiting Ink
|
|
5
|
-
spawnSync(
|
|
5
|
+
spawnSync("reset", [], { stdio: "inherit" });
|
|
6
6
|
console.clear();
|
|
7
7
|
console.log(`\nConnecting to devbox ${config.devboxName}...\n`);
|
|
8
8
|
// Spawn SSH in foreground with proper terminal settings
|
|
9
|
-
const result = spawnSync(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
const result = spawnSync("ssh", [
|
|
10
|
+
"-t", // Force pseudo-terminal allocation for proper input handling
|
|
11
|
+
"-i",
|
|
12
|
+
config.keyPath,
|
|
13
|
+
"-o",
|
|
14
|
+
`ProxyCommand=${config.proxyCommand}`,
|
|
15
|
+
"-o",
|
|
16
|
+
"StrictHostKeyChecking=no",
|
|
17
|
+
"-o",
|
|
18
|
+
"UserKnownHostsFile=/dev/null",
|
|
19
|
+
`${config.sshUser}@${config.url}`,
|
|
16
20
|
], {
|
|
17
|
-
stdio:
|
|
18
|
-
shell: false
|
|
21
|
+
stdio: "inherit",
|
|
22
|
+
shell: false,
|
|
19
23
|
});
|
|
20
24
|
return {
|
|
21
25
|
exitCode: result.status || 0,
|
|
22
26
|
shouldRestart: true,
|
|
23
|
-
returnToDevboxId: config.devboxId
|
|
27
|
+
returnToDevboxId: config.devboxId,
|
|
24
28
|
};
|
|
25
29
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color theme constants for the CLI application
|
|
3
|
+
* Centralized color definitions for easy theme customization
|
|
4
|
+
*/
|
|
5
|
+
export const colors = {
|
|
6
|
+
// Primary brand colors
|
|
7
|
+
primary: "cyan",
|
|
8
|
+
secondary: "magenta",
|
|
9
|
+
// Status colors
|
|
10
|
+
success: "green",
|
|
11
|
+
warning: "yellow",
|
|
12
|
+
error: "red",
|
|
13
|
+
info: "blue",
|
|
14
|
+
// UI colors
|
|
15
|
+
text: "white",
|
|
16
|
+
textDim: "gray",
|
|
17
|
+
border: "gray",
|
|
18
|
+
// Accent colors for menu items and highlights
|
|
19
|
+
accent1: "cyan",
|
|
20
|
+
accent2: "magenta",
|
|
21
|
+
accent3: "green",
|
|
22
|
+
};
|
package/dist/utils/url.js
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
export function getBaseUrl() {
|
|
10
10
|
const env = process.env.RUNLOOP_ENV?.toLowerCase();
|
|
11
11
|
switch (env) {
|
|
12
|
-
case
|
|
13
|
-
return
|
|
14
|
-
case
|
|
12
|
+
case "dev":
|
|
13
|
+
return "https://platform.runloop.pro";
|
|
14
|
+
case "prod":
|
|
15
15
|
default:
|
|
16
|
-
return
|
|
16
|
+
return "https://platform.runloop.ai";
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
/**
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runloop/rl-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Beautiful CLI for Runloop devbox management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"
|
|
7
|
+
"rli": "./dist/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "tsc",
|
|
@@ -14,7 +14,17 @@
|
|
|
14
14
|
"version:patch": "npm version patch",
|
|
15
15
|
"version:minor": "npm version minor",
|
|
16
16
|
"version:major": "npm version major",
|
|
17
|
-
"release": "npm run build && npm publish"
|
|
17
|
+
"release": "npm run build && npm publish",
|
|
18
|
+
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json}\"",
|
|
19
|
+
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json}\"",
|
|
20
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
21
|
+
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
|
22
|
+
"test": "jest",
|
|
23
|
+
"test:unit": "jest tests/__tests__/unit",
|
|
24
|
+
"test:integration": "jest tests/__tests__/integration",
|
|
25
|
+
"test:watch": "jest --watch",
|
|
26
|
+
"test:coverage": "jest --coverage",
|
|
27
|
+
"test:e2e": "RUN_E2E=1 jest tests/__tests__/integration"
|
|
18
28
|
},
|
|
19
29
|
"keywords": [
|
|
20
30
|
"runloop",
|
|
@@ -46,11 +56,15 @@
|
|
|
46
56
|
},
|
|
47
57
|
"dependencies": {
|
|
48
58
|
"@inkjs/ui": "^2.0.0",
|
|
49
|
-
"@
|
|
59
|
+
"@modelcontextprotocol/sdk": "^1.19.1",
|
|
60
|
+
"@runloop/api-client": "^0.58.0",
|
|
50
61
|
"@runloop/rl-cli": "^0.0.1",
|
|
62
|
+
"@types/express": "^5.0.3",
|
|
51
63
|
"chalk": "^5.3.0",
|
|
52
64
|
"commander": "^12.1.0",
|
|
53
65
|
"conf": "^13.0.1",
|
|
66
|
+
"dotenv": "^16.4.5",
|
|
67
|
+
"express": "^5.1.0",
|
|
54
68
|
"figures": "^6.1.0",
|
|
55
69
|
"gradient-string": "^2.0.2",
|
|
56
70
|
"ink": "^5.0.1",
|
|
@@ -63,8 +77,19 @@
|
|
|
63
77
|
"yaml": "^2.8.1"
|
|
64
78
|
},
|
|
65
79
|
"devDependencies": {
|
|
80
|
+
"@types/jest": "^29.5.0",
|
|
66
81
|
"@types/node": "^22.7.9",
|
|
67
82
|
"@types/react": "^18.3.11",
|
|
83
|
+
"@typescript-eslint/eslint-plugin": "^8.46.0",
|
|
84
|
+
"@typescript-eslint/parser": "^8.46.0",
|
|
85
|
+
"eslint": "^9.37.0",
|
|
86
|
+
"eslint-plugin-react": "^7.37.5",
|
|
87
|
+
"eslint-plugin-react-hooks": "^6.1.1",
|
|
88
|
+
"globals": "^16.4.0",
|
|
89
|
+
"jest": "^29.7.0",
|
|
90
|
+
"prettier": "^3.6.2",
|
|
91
|
+
"ts-jest": "^29.1.0",
|
|
92
|
+
"ts-node": "^10.9.0",
|
|
68
93
|
"typescript": "^5.6.3"
|
|
69
94
|
}
|
|
70
95
|
}
|