@agentuity/cli 0.0.104 → 0.0.106
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/bin/cli.ts +6 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +93 -21
- package/dist/cli.js.map +1 -1
- package/dist/cmd/ai/prompt/version.d.ts +1 -0
- package/dist/cmd/ai/prompt/version.d.ts.map +1 -1
- package/dist/cmd/ai/prompt/version.js +3 -2
- package/dist/cmd/ai/prompt/version.js.map +1 -1
- package/dist/cmd/build/ast.d.ts.map +1 -1
- package/dist/cmd/build/ast.js +179 -37
- package/dist/cmd/build/ast.js.map +1 -1
- package/dist/cmd/build/entry-generator.d.ts.map +1 -1
- package/dist/cmd/build/entry-generator.js +24 -14
- package/dist/cmd/build/entry-generator.js.map +1 -1
- package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/registry-generator.js +8 -9
- package/dist/cmd/build/vite/registry-generator.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
- package/dist/cmd/cloud/db/create.d.ts.map +1 -1
- package/dist/cmd/cloud/db/create.js +11 -2
- package/dist/cmd/cloud/db/create.js.map +1 -1
- package/dist/cmd/cloud/db/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/db/delete.js +13 -2
- package/dist/cmd/cloud/db/delete.js.map +1 -1
- package/dist/cmd/cloud/deploy.js +3 -3
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/cmd/cloud/env/delete.js +1 -1
- package/dist/cmd/cloud/env/delete.js.map +1 -1
- package/dist/cmd/cloud/env/import.js +4 -4
- package/dist/cmd/cloud/env/import.js.map +1 -1
- package/dist/cmd/cloud/env/pull.d.ts.map +1 -1
- package/dist/cmd/cloud/env/pull.js +7 -9
- package/dist/cmd/cloud/env/pull.js.map +1 -1
- package/dist/cmd/cloud/env/push.js +2 -2
- package/dist/cmd/cloud/env/push.js.map +1 -1
- package/dist/cmd/cloud/env/set.js +3 -3
- package/dist/cmd/cloud/env/set.js.map +1 -1
- package/dist/cmd/cloud/index.d.ts.map +1 -1
- package/dist/cmd/cloud/index.js +2 -0
- package/dist/cmd/cloud/index.js.map +1 -1
- package/dist/cmd/cloud/sandbox/cp.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/cp.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/cp.js +334 -0
- package/dist/cmd/cloud/sandbox/cp.js.map +1 -0
- package/dist/cmd/cloud/sandbox/create.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/create.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/create.js +105 -0
- package/dist/cmd/cloud/sandbox/create.js.map +1 -0
- package/dist/cmd/cloud/sandbox/delete.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/delete.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/delete.js +72 -0
- package/dist/cmd/cloud/sandbox/delete.js.map +1 -0
- package/dist/cmd/cloud/sandbox/exec.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/exec.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/exec.js +211 -0
- package/dist/cmd/cloud/sandbox/exec.js.map +1 -0
- package/dist/cmd/cloud/sandbox/execution/get.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/execution/get.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/execution/get.js +96 -0
- package/dist/cmd/cloud/sandbox/execution/get.js.map +1 -0
- package/dist/cmd/cloud/sandbox/execution/index.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/execution/index.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/execution/index.js +24 -0
- package/dist/cmd/cloud/sandbox/execution/index.js.map +1 -0
- package/dist/cmd/cloud/sandbox/execution/list.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/execution/list.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/execution/list.js +100 -0
- package/dist/cmd/cloud/sandbox/execution/list.js.map +1 -0
- package/dist/cmd/cloud/sandbox/get.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/get.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/get.js +95 -0
- package/dist/cmd/cloud/sandbox/get.js.map +1 -0
- package/dist/cmd/cloud/sandbox/index.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/index.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/index.js +45 -0
- package/dist/cmd/cloud/sandbox/index.js.map +1 -0
- package/dist/cmd/cloud/sandbox/list.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/list.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/list.js +120 -0
- package/dist/cmd/cloud/sandbox/list.js.map +1 -0
- package/dist/cmd/cloud/sandbox/run.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/run.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/run.js +152 -0
- package/dist/cmd/cloud/sandbox/run.js.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/create.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/snapshot/create.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/create.js +65 -0
- package/dist/cmd/cloud/sandbox/snapshot/create.js.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/delete.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/snapshot/delete.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/delete.js +66 -0
- package/dist/cmd/cloud/sandbox/snapshot/delete.js.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/get.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/snapshot/get.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/get.js +154 -0
- package/dist/cmd/cloud/sandbox/snapshot/get.js.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/index.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/snapshot/index.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/index.js +27 -0
- package/dist/cmd/cloud/sandbox/snapshot/index.js.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/list.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/snapshot/list.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/list.js +83 -0
- package/dist/cmd/cloud/sandbox/snapshot/list.js.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/tag.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/snapshot/tag.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/tag.js +63 -0
- package/dist/cmd/cloud/sandbox/snapshot/tag.js.map +1 -0
- package/dist/cmd/cloud/sandbox/util.d.ts +15 -0
- package/dist/cmd/cloud/sandbox/util.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/util.js +50 -0
- package/dist/cmd/cloud/sandbox/util.js.map +1 -0
- package/dist/cmd/cloud/secret/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/secret/delete.js +3 -3
- package/dist/cmd/cloud/secret/delete.js.map +1 -1
- package/dist/cmd/cloud/secret/import.js +6 -6
- package/dist/cmd/cloud/secret/import.js.map +1 -1
- package/dist/cmd/cloud/secret/index.d.ts.map +1 -1
- package/dist/cmd/cloud/secret/index.js +1 -0
- package/dist/cmd/cloud/secret/index.js.map +1 -1
- package/dist/cmd/cloud/secret/pull.d.ts.map +1 -1
- package/dist/cmd/cloud/secret/pull.js +7 -9
- package/dist/cmd/cloud/secret/pull.js.map +1 -1
- package/dist/cmd/cloud/secret/push.js +3 -3
- package/dist/cmd/cloud/secret/push.js.map +1 -1
- package/dist/cmd/cloud/secret/set.d.ts.map +1 -1
- package/dist/cmd/cloud/secret/set.js +3 -3
- package/dist/cmd/cloud/secret/set.js.map +1 -1
- package/dist/cmd/cloud/storage/create.d.ts.map +1 -1
- package/dist/cmd/cloud/storage/create.js +13 -2
- package/dist/cmd/cloud/storage/create.js.map +1 -1
- package/dist/cmd/cloud/storage/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/storage/delete.js +13 -2
- package/dist/cmd/cloud/storage/delete.js.map +1 -1
- package/dist/cmd/cloud/stream/list.d.ts.map +1 -1
- package/dist/cmd/cloud/stream/list.js +2 -13
- package/dist/cmd/cloud/stream/list.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +14 -1
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/profile/create.d.ts.map +1 -1
- package/dist/cmd/profile/create.js +1 -0
- package/dist/cmd/profile/create.js.map +1 -1
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.js +27 -10
- package/dist/cmd/project/template-flow.js.map +1 -1
- package/dist/config.d.ts +0 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/env-util.d.ts +16 -8
- package/dist/env-util.d.ts.map +1 -1
- package/dist/env-util.js +46 -18
- package/dist/env-util.js.map +1 -1
- package/dist/tui.d.ts +20 -3
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +82 -23
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +18 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/package.json +4 -4
- package/src/cli.ts +99 -21
- package/src/cmd/ai/prompt/api.md +26 -21
- package/src/cmd/ai/prompt/version.ts +3 -2
- package/src/cmd/build/ast.ts +214 -37
- package/src/cmd/build/entry-generator.ts +24 -14
- package/src/cmd/build/vite/registry-generator.ts +8 -11
- package/src/cmd/build/vite/vite-asset-server.ts +3 -1
- package/src/cmd/cloud/db/create.ts +13 -2
- package/src/cmd/cloud/db/delete.ts +15 -2
- package/src/cmd/cloud/deploy.ts +3 -3
- package/src/cmd/cloud/env/delete.ts +1 -1
- package/src/cmd/cloud/env/import.ts +4 -4
- package/src/cmd/cloud/env/pull.ts +7 -16
- package/src/cmd/cloud/env/push.ts +2 -2
- package/src/cmd/cloud/env/set.ts +3 -3
- package/src/cmd/cloud/index.ts +2 -0
- package/src/cmd/cloud/sandbox/cp.ts +531 -0
- package/src/cmd/cloud/sandbox/create.ts +114 -0
- package/src/cmd/cloud/sandbox/delete.ts +80 -0
- package/src/cmd/cloud/sandbox/exec.ts +254 -0
- package/src/cmd/cloud/sandbox/execution/get.ts +106 -0
- package/src/cmd/cloud/sandbox/execution/index.ts +25 -0
- package/src/cmd/cloud/sandbox/execution/list.ts +111 -0
- package/src/cmd/cloud/sandbox/get.ts +104 -0
- package/src/cmd/cloud/sandbox/index.ts +46 -0
- package/src/cmd/cloud/sandbox/list.ts +129 -0
- package/src/cmd/cloud/sandbox/run.ts +170 -0
- package/src/cmd/cloud/sandbox/snapshot/create.ts +71 -0
- package/src/cmd/cloud/sandbox/snapshot/delete.ts +74 -0
- package/src/cmd/cloud/sandbox/snapshot/get.ts +188 -0
- package/src/cmd/cloud/sandbox/snapshot/index.ts +28 -0
- package/src/cmd/cloud/sandbox/snapshot/list.ts +90 -0
- package/src/cmd/cloud/sandbox/snapshot/tag.ts +70 -0
- package/src/cmd/cloud/sandbox/util.ts +59 -0
- package/src/cmd/cloud/secret/delete.ts +8 -3
- package/src/cmd/cloud/secret/import.ts +6 -6
- package/src/cmd/cloud/secret/index.ts +1 -0
- package/src/cmd/cloud/secret/pull.ts +7 -16
- package/src/cmd/cloud/secret/push.ts +3 -3
- package/src/cmd/cloud/secret/set.ts +8 -3
- package/src/cmd/cloud/storage/create.ts +15 -2
- package/src/cmd/cloud/storage/delete.ts +15 -2
- package/src/cmd/cloud/stream/list.ts +2 -9
- package/src/cmd/dev/index.ts +18 -1
- package/src/cmd/profile/create.ts +1 -0
- package/src/cmd/project/template-flow.ts +29 -13
- package/src/config.ts +3 -0
- package/src/env-util.ts +52 -21
- package/src/tui.ts +131 -39
- package/src/types.ts +18 -16
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, statSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { dirname, resolve, basename, join, relative } from 'node:path';
|
|
4
|
+
import { createCommand } from '../../../types';
|
|
5
|
+
import * as tui from '../../../tui';
|
|
6
|
+
import { createSandboxClient } from './util';
|
|
7
|
+
import { getCommand } from '../../../command-prefix';
|
|
8
|
+
import {
|
|
9
|
+
sandboxWriteFiles,
|
|
10
|
+
sandboxReadFile,
|
|
11
|
+
sandboxExecute,
|
|
12
|
+
executionGet,
|
|
13
|
+
type APIClient,
|
|
14
|
+
} from '@agentuity/server';
|
|
15
|
+
import type { Logger, FileToWrite } from '@agentuity/core';
|
|
16
|
+
|
|
17
|
+
const POLL_INTERVAL_MS = 500;
|
|
18
|
+
const MAX_POLL_ATTEMPTS = 600;
|
|
19
|
+
|
|
20
|
+
interface ParsedPath {
|
|
21
|
+
sandboxId: string | null;
|
|
22
|
+
path: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parsePath(pathArg: string): ParsedPath {
|
|
26
|
+
const colonIndex = pathArg.indexOf(':');
|
|
27
|
+
if (colonIndex === -1) {
|
|
28
|
+
return { sandboxId: null, path: pathArg };
|
|
29
|
+
}
|
|
30
|
+
const prefix = pathArg.slice(0, colonIndex);
|
|
31
|
+
const path = pathArg.slice(colonIndex + 1);
|
|
32
|
+
if (prefix.startsWith('snbx_') || prefix.startsWith('sbx_')) {
|
|
33
|
+
return { sandboxId: prefix, path };
|
|
34
|
+
}
|
|
35
|
+
return { sandboxId: null, path: pathArg };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const SandboxCpResponseSchema = z.object({
|
|
39
|
+
source: z.string().describe('Source path'),
|
|
40
|
+
destination: z.string().describe('Destination path'),
|
|
41
|
+
bytesTransferred: z.number().describe('Number of bytes transferred'),
|
|
42
|
+
filesTransferred: z.number().describe('Number of files transferred'),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const cpSubcommand = createCommand({
|
|
46
|
+
name: 'cp',
|
|
47
|
+
aliases: ['copy'],
|
|
48
|
+
description: 'Copy files or directories to or from a sandbox',
|
|
49
|
+
tags: ['slow', 'requires-auth'],
|
|
50
|
+
requires: { auth: true, region: true, org: true },
|
|
51
|
+
examples: [
|
|
52
|
+
{
|
|
53
|
+
command: getCommand('cloud sandbox cp ./local-file.txt snbx_abc123:/path/to/file.txt'),
|
|
54
|
+
description: 'Copy a local file to a sandbox',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
command: getCommand('cloud sandbox cp snbx_abc123:/path/to/file.txt ./local-file.txt'),
|
|
58
|
+
description: 'Copy a file from a sandbox to local',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
command: getCommand('cloud sandbox cp --recursive ./local-dir snbx_abc123:/path/to/dir'),
|
|
62
|
+
description: 'Copy a local directory to a sandbox recursively',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
command: getCommand('cloud sandbox cp -r snbx_abc123:/path/to/dir ./local-dir'),
|
|
66
|
+
description: 'Copy a directory from a sandbox to local recursively',
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
schema: {
|
|
70
|
+
args: z.object({
|
|
71
|
+
source: z.string().describe('Source path (local path or sandboxId:/remote/path)'),
|
|
72
|
+
destination: z
|
|
73
|
+
.string()
|
|
74
|
+
.describe('Destination path (local path or sandboxId:/remote/path)'),
|
|
75
|
+
}),
|
|
76
|
+
options: z.object({
|
|
77
|
+
timeout: z.string().optional().describe('Operation timeout (e.g., "5m", "1h")'),
|
|
78
|
+
recursive: z.boolean().default(false).optional().describe('Copy directories recursively'),
|
|
79
|
+
}),
|
|
80
|
+
aliases: {
|
|
81
|
+
recursive: ['r'],
|
|
82
|
+
},
|
|
83
|
+
response: SandboxCpResponseSchema,
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
async handler(ctx) {
|
|
87
|
+
const { args, opts, options, auth, region, logger, orgId } = ctx;
|
|
88
|
+
|
|
89
|
+
const source = parsePath(args.source);
|
|
90
|
+
const destination = parsePath(args.destination);
|
|
91
|
+
|
|
92
|
+
if (source.sandboxId && destination.sandboxId) {
|
|
93
|
+
logger.fatal(
|
|
94
|
+
'Cannot copy between two sandboxes. Use a local path as source or destination.'
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!source.sandboxId && !destination.sandboxId) {
|
|
99
|
+
logger.fatal(
|
|
100
|
+
'At least one path must include a sandbox ID (e.g., snbx_abc123:/path/to/file)'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const client = createSandboxClient(logger, auth, region);
|
|
105
|
+
const recursive = opts.recursive ?? false;
|
|
106
|
+
|
|
107
|
+
if (source.sandboxId) {
|
|
108
|
+
return await downloadFromSandbox(
|
|
109
|
+
client,
|
|
110
|
+
logger,
|
|
111
|
+
orgId,
|
|
112
|
+
source.sandboxId,
|
|
113
|
+
source.path,
|
|
114
|
+
destination.path,
|
|
115
|
+
opts.timeout,
|
|
116
|
+
recursive,
|
|
117
|
+
options.json ?? false
|
|
118
|
+
);
|
|
119
|
+
} else {
|
|
120
|
+
return await uploadToSandbox(
|
|
121
|
+
client,
|
|
122
|
+
logger,
|
|
123
|
+
orgId,
|
|
124
|
+
destination.sandboxId!,
|
|
125
|
+
source.path,
|
|
126
|
+
destination.path,
|
|
127
|
+
opts.timeout,
|
|
128
|
+
recursive,
|
|
129
|
+
options.json ?? false
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
function getAllFiles(dirPath: string, basePath: string = dirPath): string[] {
|
|
136
|
+
const files: string[] = [];
|
|
137
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
138
|
+
|
|
139
|
+
for (const entry of entries) {
|
|
140
|
+
const fullPath = join(dirPath, entry.name);
|
|
141
|
+
if (entry.isDirectory()) {
|
|
142
|
+
files.push(...getAllFiles(fullPath, basePath));
|
|
143
|
+
} else if (entry.isFile()) {
|
|
144
|
+
files.push(fullPath);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return files;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function uploadToSandbox(
|
|
152
|
+
client: APIClient,
|
|
153
|
+
logger: Logger,
|
|
154
|
+
orgId: string,
|
|
155
|
+
sandboxId: string,
|
|
156
|
+
localPath: string,
|
|
157
|
+
remotePath: string,
|
|
158
|
+
timeout: string | undefined,
|
|
159
|
+
recursive: boolean,
|
|
160
|
+
jsonOutput: boolean
|
|
161
|
+
): Promise<z.infer<typeof SandboxCpResponseSchema>> {
|
|
162
|
+
const resolvedPath = resolve(localPath);
|
|
163
|
+
|
|
164
|
+
if (!(await Bun.file(resolvedPath).exists())) {
|
|
165
|
+
const stat = statSync(resolvedPath, { throwIfNoEntry: false });
|
|
166
|
+
if (!stat) {
|
|
167
|
+
logger.fatal(`Local path not found: ${localPath}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const stat = statSync(resolvedPath);
|
|
172
|
+
|
|
173
|
+
if (stat.isDirectory()) {
|
|
174
|
+
if (!recursive) {
|
|
175
|
+
logger.fatal(`${localPath} is a directory. Use -r/--recursive to copy directories.`);
|
|
176
|
+
}
|
|
177
|
+
return await uploadDirectory(
|
|
178
|
+
client,
|
|
179
|
+
logger,
|
|
180
|
+
orgId,
|
|
181
|
+
sandboxId,
|
|
182
|
+
resolvedPath,
|
|
183
|
+
remotePath,
|
|
184
|
+
timeout,
|
|
185
|
+
jsonOutput
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return await uploadSingleFile(
|
|
190
|
+
client,
|
|
191
|
+
logger,
|
|
192
|
+
orgId,
|
|
193
|
+
sandboxId,
|
|
194
|
+
resolvedPath,
|
|
195
|
+
localPath,
|
|
196
|
+
remotePath,
|
|
197
|
+
timeout,
|
|
198
|
+
jsonOutput
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function uploadSingleFile(
|
|
203
|
+
client: APIClient,
|
|
204
|
+
logger: Logger,
|
|
205
|
+
orgId: string,
|
|
206
|
+
sandboxId: string,
|
|
207
|
+
resolvedPath: string,
|
|
208
|
+
displayPath: string,
|
|
209
|
+
remotePath: string,
|
|
210
|
+
_timeout: string | undefined,
|
|
211
|
+
jsonOutput: boolean
|
|
212
|
+
): Promise<z.infer<typeof SandboxCpResponseSchema>> {
|
|
213
|
+
const buffer = readFileSync(resolvedPath);
|
|
214
|
+
|
|
215
|
+
let targetPath = remotePath;
|
|
216
|
+
if (!remotePath || remotePath === '' || remotePath.endsWith('/')) {
|
|
217
|
+
const baseDir = remotePath || '';
|
|
218
|
+
targetPath = baseDir ? baseDir + basename(resolvedPath) : basename(resolvedPath);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const files: FileToWrite[] = [{ path: targetPath, content: buffer }];
|
|
222
|
+
|
|
223
|
+
await sandboxWriteFiles(client, { sandboxId, files, orgId });
|
|
224
|
+
|
|
225
|
+
if (!jsonOutput) {
|
|
226
|
+
tui.success(`Copied ${displayPath} → ${sandboxId}:${targetPath} (${buffer.length} bytes)`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
source: displayPath,
|
|
231
|
+
destination: `${sandboxId}:${targetPath}`,
|
|
232
|
+
bytesTransferred: buffer.length,
|
|
233
|
+
filesTransferred: 1,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function uploadDirectory(
|
|
238
|
+
client: APIClient,
|
|
239
|
+
logger: Logger,
|
|
240
|
+
orgId: string,
|
|
241
|
+
sandboxId: string,
|
|
242
|
+
localDir: string,
|
|
243
|
+
remotePath: string,
|
|
244
|
+
_timeout: string | undefined,
|
|
245
|
+
jsonOutput: boolean
|
|
246
|
+
): Promise<z.infer<typeof SandboxCpResponseSchema>> {
|
|
247
|
+
const allFiles = getAllFiles(localDir);
|
|
248
|
+
|
|
249
|
+
if (allFiles.length === 0) {
|
|
250
|
+
logger.fatal(`Directory is empty: ${localDir}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const files: FileToWrite[] = [];
|
|
254
|
+
let totalBytes = 0;
|
|
255
|
+
const effectiveRemotePath = remotePath || basename(localDir);
|
|
256
|
+
const baseRemotePath = effectiveRemotePath.endsWith('/')
|
|
257
|
+
? effectiveRemotePath.slice(0, -1)
|
|
258
|
+
: effectiveRemotePath;
|
|
259
|
+
|
|
260
|
+
for (const filePath of allFiles) {
|
|
261
|
+
const relativePath = relative(localDir, filePath);
|
|
262
|
+
const targetPath = `${baseRemotePath}/${relativePath}`;
|
|
263
|
+
const buffer = readFileSync(filePath);
|
|
264
|
+
files.push({ path: targetPath, content: buffer });
|
|
265
|
+
totalBytes += buffer.length;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
await sandboxWriteFiles(client, { sandboxId, files, orgId });
|
|
269
|
+
|
|
270
|
+
if (!jsonOutput) {
|
|
271
|
+
tui.success(
|
|
272
|
+
`Copied ${localDir} → ${sandboxId}:${baseRemotePath} (${allFiles.length} files, ${totalBytes} bytes)`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
source: localDir,
|
|
278
|
+
destination: `${sandboxId}:${baseRemotePath}`,
|
|
279
|
+
bytesTransferred: totalBytes,
|
|
280
|
+
filesTransferred: allFiles.length,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function downloadFromSandbox(
|
|
285
|
+
client: APIClient,
|
|
286
|
+
logger: Logger,
|
|
287
|
+
orgId: string,
|
|
288
|
+
sandboxId: string,
|
|
289
|
+
remotePath: string,
|
|
290
|
+
localPath: string,
|
|
291
|
+
timeout: string | undefined,
|
|
292
|
+
recursive: boolean,
|
|
293
|
+
jsonOutput: boolean
|
|
294
|
+
): Promise<z.infer<typeof SandboxCpResponseSchema>> {
|
|
295
|
+
if (recursive) {
|
|
296
|
+
return await downloadDirectory(
|
|
297
|
+
client,
|
|
298
|
+
logger,
|
|
299
|
+
orgId,
|
|
300
|
+
sandboxId,
|
|
301
|
+
remotePath,
|
|
302
|
+
localPath,
|
|
303
|
+
timeout,
|
|
304
|
+
jsonOutput
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return await downloadSingleFile(
|
|
309
|
+
client,
|
|
310
|
+
logger,
|
|
311
|
+
orgId,
|
|
312
|
+
sandboxId,
|
|
313
|
+
remotePath,
|
|
314
|
+
localPath,
|
|
315
|
+
timeout,
|
|
316
|
+
jsonOutput
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function downloadSingleFile(
|
|
321
|
+
client: APIClient,
|
|
322
|
+
logger: Logger,
|
|
323
|
+
orgId: string,
|
|
324
|
+
sandboxId: string,
|
|
325
|
+
remotePath: string,
|
|
326
|
+
localPath: string,
|
|
327
|
+
_timeout: string | undefined,
|
|
328
|
+
jsonOutput: boolean
|
|
329
|
+
): Promise<z.infer<typeof SandboxCpResponseSchema>> {
|
|
330
|
+
const stream = await sandboxReadFile(client, { sandboxId, path: remotePath, orgId });
|
|
331
|
+
|
|
332
|
+
const chunks: Uint8Array[] = [];
|
|
333
|
+
const reader = stream.getReader();
|
|
334
|
+
while (true) {
|
|
335
|
+
const { done, value } = await reader.read();
|
|
336
|
+
if (done) break;
|
|
337
|
+
if (value) chunks.push(value);
|
|
338
|
+
}
|
|
339
|
+
const buffer = Buffer.concat(chunks);
|
|
340
|
+
|
|
341
|
+
let targetPath = localPath;
|
|
342
|
+
if (localPath.endsWith('/') || localPath === '.') {
|
|
343
|
+
targetPath = resolve(localPath, basename(remotePath));
|
|
344
|
+
} else {
|
|
345
|
+
targetPath = resolve(localPath);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const dir = dirname(targetPath);
|
|
349
|
+
mkdirSync(dir, { recursive: true });
|
|
350
|
+
|
|
351
|
+
writeFileSync(targetPath, buffer);
|
|
352
|
+
|
|
353
|
+
if (!jsonOutput) {
|
|
354
|
+
tui.success(`Copied ${sandboxId}:${remotePath} → ${targetPath} (${buffer.length} bytes)`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
source: `${sandboxId}:${remotePath}`,
|
|
359
|
+
destination: targetPath,
|
|
360
|
+
bytesTransferred: buffer.length,
|
|
361
|
+
filesTransferred: 1,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async function downloadDirectory(
|
|
366
|
+
client: APIClient,
|
|
367
|
+
logger: Logger,
|
|
368
|
+
orgId: string,
|
|
369
|
+
sandboxId: string,
|
|
370
|
+
remotePath: string,
|
|
371
|
+
localPath: string,
|
|
372
|
+
timeout: string | undefined,
|
|
373
|
+
jsonOutput: boolean
|
|
374
|
+
): Promise<z.infer<typeof SandboxCpResponseSchema>> {
|
|
375
|
+
const listExecution = await sandboxExecute(client, {
|
|
376
|
+
sandboxId,
|
|
377
|
+
options: {
|
|
378
|
+
command: ['find', remotePath, '-type', 'f'],
|
|
379
|
+
timeout,
|
|
380
|
+
},
|
|
381
|
+
orgId,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const listChunks: Buffer[] = [];
|
|
385
|
+
if (listExecution.stdoutStreamUrl) {
|
|
386
|
+
await streamToBuffer(listExecution.stdoutStreamUrl, listChunks, logger);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
await waitForExecution(client, orgId, listExecution.executionId, logger);
|
|
390
|
+
|
|
391
|
+
const fileList = Buffer.concat(listChunks)
|
|
392
|
+
.toString('utf-8')
|
|
393
|
+
.trim()
|
|
394
|
+
.split('\n')
|
|
395
|
+
.filter((f) => f.length > 0);
|
|
396
|
+
|
|
397
|
+
if (fileList.length === 0) {
|
|
398
|
+
logger.fatal(`No files found in directory: ${remotePath}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const baseRemotePath = remotePath.endsWith('/') ? remotePath.slice(0, -1) : remotePath;
|
|
402
|
+
const baseLocalPath = resolve(localPath);
|
|
403
|
+
let totalBytes = 0;
|
|
404
|
+
|
|
405
|
+
for (const remoteFile of fileList) {
|
|
406
|
+
const relativePath = remoteFile.startsWith(baseRemotePath + '/')
|
|
407
|
+
? remoteFile.slice(baseRemotePath.length + 1)
|
|
408
|
+
: basename(remoteFile);
|
|
409
|
+
|
|
410
|
+
const localFilePath = join(baseLocalPath, relativePath);
|
|
411
|
+
|
|
412
|
+
try {
|
|
413
|
+
const stream = await sandboxReadFile(client, { sandboxId, path: remoteFile, orgId });
|
|
414
|
+
const chunks: Uint8Array[] = [];
|
|
415
|
+
const reader = stream.getReader();
|
|
416
|
+
while (true) {
|
|
417
|
+
const { done, value } = await reader.read();
|
|
418
|
+
if (done) break;
|
|
419
|
+
if (value) chunks.push(value);
|
|
420
|
+
}
|
|
421
|
+
const buffer = Buffer.concat(chunks);
|
|
422
|
+
totalBytes += buffer.length;
|
|
423
|
+
|
|
424
|
+
const dir = dirname(localFilePath);
|
|
425
|
+
mkdirSync(dir, { recursive: true });
|
|
426
|
+
writeFileSync(localFilePath, buffer);
|
|
427
|
+
|
|
428
|
+
if (!jsonOutput) {
|
|
429
|
+
logger.info(`Downloaded ${remoteFile} (${buffer.length} bytes)`);
|
|
430
|
+
}
|
|
431
|
+
} catch (err) {
|
|
432
|
+
logger.warn(`Failed to read file: ${remoteFile}, skipping: ${err}`);
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (!jsonOutput) {
|
|
438
|
+
tui.success(
|
|
439
|
+
`Copied ${sandboxId}:${baseRemotePath} → ${baseLocalPath} (${fileList.length} files, ${totalBytes} bytes)`
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
source: `${sandboxId}:${baseRemotePath}`,
|
|
445
|
+
destination: baseLocalPath,
|
|
446
|
+
bytesTransferred: totalBytes,
|
|
447
|
+
filesTransferred: fileList.length,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async function waitForExecution(
|
|
452
|
+
client: APIClient,
|
|
453
|
+
orgId: string,
|
|
454
|
+
executionId: string,
|
|
455
|
+
logger: Logger
|
|
456
|
+
): Promise<void> {
|
|
457
|
+
let attempts = 0;
|
|
458
|
+
|
|
459
|
+
while (attempts < MAX_POLL_ATTEMPTS) {
|
|
460
|
+
await sleep(POLL_INTERVAL_MS);
|
|
461
|
+
attempts++;
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
const execInfo = await executionGet(client, { executionId, orgId });
|
|
465
|
+
|
|
466
|
+
if (
|
|
467
|
+
execInfo.status === 'completed' ||
|
|
468
|
+
execInfo.status === 'failed' ||
|
|
469
|
+
execInfo.status === 'timeout' ||
|
|
470
|
+
execInfo.status === 'cancelled'
|
|
471
|
+
) {
|
|
472
|
+
if (execInfo.status === 'failed' || execInfo.status === 'timeout') {
|
|
473
|
+
logger.fatal(`Execution ${execInfo.status}: ${executionId}`);
|
|
474
|
+
}
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
} catch (err) {
|
|
478
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
479
|
+
throw err;
|
|
480
|
+
}
|
|
481
|
+
logger.debug('poll error: %s', err);
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
logger.fatal('Execution timed out waiting for completion');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async function streamToBuffer(url: string, chunks: Buffer[], logger: Logger): Promise<void> {
|
|
490
|
+
const maxRetries = 10;
|
|
491
|
+
const retryDelay = 200;
|
|
492
|
+
|
|
493
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
494
|
+
try {
|
|
495
|
+
if (attempt > 0) {
|
|
496
|
+
logger.debug('stream retry attempt %d', attempt + 1);
|
|
497
|
+
await sleep(retryDelay);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const response = await fetch(url);
|
|
501
|
+
|
|
502
|
+
if (!response.ok || !response.body) {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const reader = response.body.getReader();
|
|
507
|
+
|
|
508
|
+
while (true) {
|
|
509
|
+
const { done, value } = await reader.read();
|
|
510
|
+
if (done) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (value) {
|
|
515
|
+
chunks.push(Buffer.from(value));
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
} catch (err) {
|
|
519
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
520
|
+
throw err;
|
|
521
|
+
}
|
|
522
|
+
logger.debug('stream error: %s', err);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function sleep(ms: number): Promise<void> {
|
|
528
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export default cpSubcommand;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createCommand } from '../../../types';
|
|
3
|
+
import * as tui from '../../../tui';
|
|
4
|
+
import { createSandboxClient, parseFileArgs } from './util';
|
|
5
|
+
import { getCommand } from '../../../command-prefix';
|
|
6
|
+
import { sandboxCreate } from '@agentuity/server';
|
|
7
|
+
|
|
8
|
+
const SandboxCreateResponseSchema = z.object({
|
|
9
|
+
sandboxId: z.string().describe('Unique sandbox identifier'),
|
|
10
|
+
status: z.string().describe('Current sandbox status'),
|
|
11
|
+
stdoutStreamUrl: z.string().optional().describe('URL to the stdout output stream'),
|
|
12
|
+
stderrStreamUrl: z.string().optional().describe('URL to the stderr output stream'),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const createSubcommand = createCommand({
|
|
16
|
+
name: 'create',
|
|
17
|
+
description: 'Create an interactive sandbox for multiple executions',
|
|
18
|
+
tags: ['slow', 'requires-auth'],
|
|
19
|
+
requires: { auth: true, region: true, org: true },
|
|
20
|
+
examples: [
|
|
21
|
+
{
|
|
22
|
+
command: getCommand('cloud sandbox create'),
|
|
23
|
+
description: 'Create a sandbox with default settings',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
command: getCommand('cloud sandbox create --memory 1Gi --cpu 1000m'),
|
|
27
|
+
description: 'Create a sandbox with resource limits',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
command: getCommand('cloud sandbox create --network --idle-timeout 30m'),
|
|
31
|
+
description: 'Create a sandbox with network and custom timeout',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
command: getCommand('cloud sandbox create --env KEY=VAL'),
|
|
35
|
+
description: 'Create a sandbox with a specific environment variable',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
schema: {
|
|
39
|
+
options: z.object({
|
|
40
|
+
memory: z.string().optional().describe('Memory limit (e.g., "500Mi", "1Gi")'),
|
|
41
|
+
cpu: z.string().optional().describe('CPU limit in millicores (e.g., "500m", "1000m")'),
|
|
42
|
+
disk: z.string().optional().describe('Disk limit (e.g., "500Mi", "1Gi")'),
|
|
43
|
+
network: z.boolean().optional().describe('Enable outbound network access'),
|
|
44
|
+
idleTimeout: z
|
|
45
|
+
.string()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe('Idle timeout before sandbox is reaped (e.g., "10m", "1h")'),
|
|
48
|
+
env: z.array(z.string()).optional().describe('Environment variables (KEY=VALUE)'),
|
|
49
|
+
file: z
|
|
50
|
+
.array(z.string())
|
|
51
|
+
.optional()
|
|
52
|
+
.describe('Files to create in sandbox (sandbox-path:local-path)'),
|
|
53
|
+
snapshot: z.string().optional().describe('Snapshot ID or tag to restore from'),
|
|
54
|
+
dependency: z
|
|
55
|
+
.array(z.string())
|
|
56
|
+
.optional()
|
|
57
|
+
.describe('Apt packages to install (can be specified multiple times)'),
|
|
58
|
+
}),
|
|
59
|
+
response: SandboxCreateResponseSchema,
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
async handler(ctx) {
|
|
63
|
+
const { opts, options, auth, region, logger, orgId } = ctx;
|
|
64
|
+
const client = createSandboxClient(logger, auth, region);
|
|
65
|
+
const started = Date.now();
|
|
66
|
+
|
|
67
|
+
const envMap: Record<string, string> = {};
|
|
68
|
+
if (opts.env) {
|
|
69
|
+
for (const e of opts.env) {
|
|
70
|
+
const [key, ...valueParts] = e.split('=');
|
|
71
|
+
if (key) {
|
|
72
|
+
envMap[key] = valueParts.join('=');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const files = parseFileArgs(opts.file);
|
|
78
|
+
const hasFiles = files.length > 0;
|
|
79
|
+
|
|
80
|
+
const result = await sandboxCreate(client, {
|
|
81
|
+
options: {
|
|
82
|
+
resources:
|
|
83
|
+
opts.memory || opts.cpu || opts.disk
|
|
84
|
+
? {
|
|
85
|
+
memory: opts.memory,
|
|
86
|
+
cpu: opts.cpu,
|
|
87
|
+
disk: opts.disk,
|
|
88
|
+
}
|
|
89
|
+
: undefined,
|
|
90
|
+
network: opts.network ? { enabled: true } : undefined,
|
|
91
|
+
timeout: opts.idleTimeout ? { idle: opts.idleTimeout } : undefined,
|
|
92
|
+
env: Object.keys(envMap).length > 0 ? envMap : undefined,
|
|
93
|
+
command: hasFiles ? { exec: [], files } : undefined,
|
|
94
|
+
snapshot: opts.snapshot,
|
|
95
|
+
dependencies: opts.dependency,
|
|
96
|
+
},
|
|
97
|
+
orgId,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (!options.json) {
|
|
101
|
+
const duration = Date.now() - started;
|
|
102
|
+
tui.success(`created sandbox ${tui.bold(result.sandboxId)} in ${duration}ms`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
sandboxId: result.sandboxId,
|
|
107
|
+
status: result.status,
|
|
108
|
+
stdoutStreamUrl: result.stdoutStreamUrl,
|
|
109
|
+
stderrStreamUrl: result.stderrStreamUrl,
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export default createSubcommand;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createCommand } from '../../../types';
|
|
3
|
+
import * as tui from '../../../tui';
|
|
4
|
+
import { createSandboxClient } from './util';
|
|
5
|
+
import { getCommand } from '../../../command-prefix';
|
|
6
|
+
import { sandboxDestroy } from '@agentuity/server';
|
|
7
|
+
|
|
8
|
+
const SandboxDeleteResponseSchema = z.object({
|
|
9
|
+
success: z.boolean().describe('Whether the operation succeeded'),
|
|
10
|
+
sandboxId: z.string().describe('Sandbox ID'),
|
|
11
|
+
durationMs: z.number().describe('Operation duration in milliseconds'),
|
|
12
|
+
message: z.string().optional().describe('Status message'),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const deleteSubcommand = createCommand({
|
|
16
|
+
name: 'delete',
|
|
17
|
+
aliases: ['del', 'rm', 'remove', 'destroy'],
|
|
18
|
+
description: 'Delete a sandbox',
|
|
19
|
+
tags: ['destructive', 'deletes-resource', 'slow', 'requires-auth'],
|
|
20
|
+
requires: { auth: true, region: true, org: true },
|
|
21
|
+
idempotent: true,
|
|
22
|
+
examples: [
|
|
23
|
+
{
|
|
24
|
+
command: getCommand('cloud sandbox delete abc123'),
|
|
25
|
+
description: 'Delete a sandbox',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
command: getCommand('cloud sandbox rm abc123'),
|
|
29
|
+
description: 'Delete using alias',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
command: getCommand('cloud sandbox rm abc123 --confirm'),
|
|
33
|
+
description: 'Delete without confirmation prompt',
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
schema: {
|
|
37
|
+
args: z.object({
|
|
38
|
+
sandboxId: z.string().describe('Sandbox ID'),
|
|
39
|
+
}),
|
|
40
|
+
options: z.object({
|
|
41
|
+
confirm: z.boolean().optional().default(false).describe('Skip confirmation prompt'),
|
|
42
|
+
}),
|
|
43
|
+
response: SandboxDeleteResponseSchema,
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
async handler(ctx) {
|
|
47
|
+
const { args, options, opts, auth, region, logger, orgId } = ctx;
|
|
48
|
+
|
|
49
|
+
if (!opts.confirm) {
|
|
50
|
+
const confirmed = await tui.confirm(`Delete sandbox "${args.sandboxId}"?`, false);
|
|
51
|
+
if (!confirmed) {
|
|
52
|
+
logger.info('Cancelled');
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
sandboxId: args.sandboxId,
|
|
56
|
+
durationMs: 0,
|
|
57
|
+
message: 'Cancelled',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const started = Date.now();
|
|
63
|
+
const client = createSandboxClient(logger, auth, region);
|
|
64
|
+
|
|
65
|
+
await sandboxDestroy(client, { sandboxId: args.sandboxId, orgId });
|
|
66
|
+
const durationMs = Date.now() - started;
|
|
67
|
+
|
|
68
|
+
if (!options.json) {
|
|
69
|
+
tui.success(`deleted sandbox ${tui.bold(args.sandboxId)} in ${durationMs}ms`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
success: true,
|
|
74
|
+
sandboxId: args.sandboxId,
|
|
75
|
+
durationMs,
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export default deleteSubcommand;
|