@agentuity/cli 1.0.15 → 1.0.17
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/dist/cmd/build/entry-generator.d.ts.map +1 -1
- package/dist/cmd/build/entry-generator.js +5 -2
- package/dist/cmd/build/entry-generator.js.map +1 -1
- package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/metadata-generator.js +1 -37
- package/dist/cmd/build/vite/metadata-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 +15 -2
- package/dist/cmd/build/vite/registry-generator.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.js +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.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 +42 -39
- package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
- package/dist/cmd/cloud/sandbox/get.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/get.js +5 -0
- package/dist/cmd/cloud/sandbox/get.js.map +1 -1
- package/dist/cmd/cloud/sandbox/list.js +1 -1
- package/dist/cmd/cloud/sandbox/list.js.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/build.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/build.js +32 -1
- package/dist/cmd/cloud/sandbox/snapshot/build.js.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/generate.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/generate.js +7 -0
- package/dist/cmd/cloud/sandbox/snapshot/generate.js.map +1 -1
- package/dist/cmd/cloud/storage/create.d.ts.map +1 -1
- package/dist/cmd/cloud/storage/create.js +58 -26
- 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 +108 -16
- package/dist/cmd/cloud/storage/delete.js.map +1 -1
- package/package.json +6 -6
- package/src/cmd/build/entry-generator.ts +6 -2
- package/src/cmd/build/vite/metadata-generator.ts +1 -39
- package/src/cmd/build/vite/registry-generator.ts +17 -2
- package/src/cmd/build/vite/vite-asset-server-config.ts +1 -1
- package/src/cmd/build/vite/vite-asset-server.ts +53 -40
- package/src/cmd/cloud/sandbox/get.ts +5 -0
- package/src/cmd/cloud/sandbox/list.ts +1 -1
- package/src/cmd/cloud/sandbox/snapshot/build.ts +41 -0
- package/src/cmd/cloud/sandbox/snapshot/generate.ts +7 -0
- package/src/cmd/cloud/storage/create.ts +62 -27
- package/src/cmd/cloud/storage/delete.ts +136 -17
|
@@ -35,6 +35,12 @@ description: My sandbox snapshot
|
|
|
35
35
|
# - ffmpeg
|
|
36
36
|
# - imagemagick=8:6.9*
|
|
37
37
|
|
|
38
|
+
# Optional: npm/bun packages to install globally
|
|
39
|
+
# Installed via 'bun install -g' at sandbox startup
|
|
40
|
+
# packages:
|
|
41
|
+
# - opencode-ai
|
|
42
|
+
# - typescript
|
|
43
|
+
|
|
38
44
|
# Optional: Files to include from the build context directory
|
|
39
45
|
# Supports glob patterns and negative patterns (prefix with !)
|
|
40
46
|
# Files are placed in /home/agentuity/ in the sandbox
|
|
@@ -68,6 +74,7 @@ const TEMPLATE_JSON = {
|
|
|
68
74
|
name: 'my-snapshot',
|
|
69
75
|
description: 'My sandbox snapshot',
|
|
70
76
|
dependencies: ['curl'],
|
|
77
|
+
packages: ['opencode-ai'],
|
|
71
78
|
files: ['src/**', '*.js', '!**/*.test.js'],
|
|
72
79
|
env: {
|
|
73
80
|
NODE_ENV: 'production',
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { createResources } from '@agentuity/server';
|
|
2
|
+
import { createResources, APIError, validateBucketName } from '@agentuity/server';
|
|
3
3
|
import { createSubcommand as defineSubcommand } from '../../../types';
|
|
4
4
|
import * as tui from '../../../tui';
|
|
5
5
|
import { getCatalystAPIClient } from '../../../config';
|
|
6
6
|
import { getCommand } from '../../../command-prefix';
|
|
7
7
|
import { isDryRunMode, outputDryRun } from '../../../explain';
|
|
8
|
+
import { ErrorCode } from '../../../errors';
|
|
8
9
|
import { addResourceEnvVars } from '../../../env-util';
|
|
9
10
|
|
|
10
11
|
export const createSubcommand = defineSubcommand({
|
|
@@ -23,6 +24,10 @@ export const createSubcommand = defineSubcommand({
|
|
|
23
24
|
command: getCommand('cloud storage new'),
|
|
24
25
|
description: 'Alias for "cloud storage create" (shorthand "new")',
|
|
25
26
|
},
|
|
27
|
+
{
|
|
28
|
+
command: getCommand('cloud storage create --name my-bucket'),
|
|
29
|
+
description: 'Create a new cloud storage bucket with a custom name',
|
|
30
|
+
},
|
|
26
31
|
{
|
|
27
32
|
command: getCommand('--dry-run cloud storage create'),
|
|
28
33
|
description: 'Dry-run: display what would be created without making changes',
|
|
@@ -30,6 +35,7 @@ export const createSubcommand = defineSubcommand({
|
|
|
30
35
|
],
|
|
31
36
|
schema: {
|
|
32
37
|
options: z.object({
|
|
38
|
+
name: z.string().optional().describe('Custom bucket name'),
|
|
33
39
|
description: z.string().optional().describe('Optional description for the bucket'),
|
|
34
40
|
}),
|
|
35
41
|
response: z.object({
|
|
@@ -41,9 +47,22 @@ export const createSubcommand = defineSubcommand({
|
|
|
41
47
|
async handler(ctx) {
|
|
42
48
|
const { logger, orgId, region, auth, options, opts } = ctx;
|
|
43
49
|
|
|
50
|
+
// Validate bucket name if provided
|
|
51
|
+
if (opts.name) {
|
|
52
|
+
const validation = validateBucketName(opts.name);
|
|
53
|
+
if (!validation.valid) {
|
|
54
|
+
tui.fatal(validation.error!, ErrorCode.INVALID_ARGUMENT);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
44
58
|
// Handle dry-run mode
|
|
45
59
|
if (isDryRunMode(options)) {
|
|
46
|
-
|
|
60
|
+
let message = opts.name
|
|
61
|
+
? `Would create storage bucket with name: ${opts.name} in region: ${region}`
|
|
62
|
+
: `Would create storage bucket in region: ${region}`;
|
|
63
|
+
if (opts.description) {
|
|
64
|
+
message += ` with description: "${opts.description}"`;
|
|
65
|
+
}
|
|
47
66
|
outputDryRun(message, options);
|
|
48
67
|
if (!options.json) {
|
|
49
68
|
tui.newline();
|
|
@@ -51,41 +70,57 @@ export const createSubcommand = defineSubcommand({
|
|
|
51
70
|
}
|
|
52
71
|
return {
|
|
53
72
|
success: false,
|
|
54
|
-
name: 'dry-run-bucket',
|
|
73
|
+
name: opts.name || 'dry-run-bucket',
|
|
55
74
|
};
|
|
56
75
|
}
|
|
57
76
|
|
|
58
77
|
const catalystClient = getCatalystAPIClient(logger, auth, region);
|
|
59
78
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
try {
|
|
80
|
+
const created = await tui.spinner({
|
|
81
|
+
message: `Creating storage in ${region}`,
|
|
82
|
+
clearOnSuccess: true,
|
|
83
|
+
callback: async () => {
|
|
84
|
+
return createResources(catalystClient, orgId, region!, [
|
|
85
|
+
{ type: 's3', name: opts.name, description: opts.description },
|
|
86
|
+
]);
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const resource = created[0];
|
|
91
|
+
if (resource) {
|
|
92
|
+
// Write environment variables to .env if running inside a project
|
|
93
|
+
if (ctx.projectDir && resource.env && Object.keys(resource.env).length > 0) {
|
|
94
|
+
await addResourceEnvVars(ctx.projectDir, resource.env);
|
|
95
|
+
if (!options.json) {
|
|
96
|
+
tui.info('Environment variables written to .env');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
69
99
|
|
|
70
|
-
const resource = created[0];
|
|
71
|
-
if (resource) {
|
|
72
|
-
// Write environment variables to .env if running inside a project
|
|
73
|
-
if (ctx.projectDir && resource.env && Object.keys(resource.env).length > 0) {
|
|
74
|
-
await addResourceEnvVars(ctx.projectDir, resource.env);
|
|
75
100
|
if (!options.json) {
|
|
76
|
-
tui.
|
|
101
|
+
tui.success(`Created storage: ${tui.bold(resource.name)}`);
|
|
77
102
|
}
|
|
103
|
+
return {
|
|
104
|
+
success: true,
|
|
105
|
+
name: resource.name,
|
|
106
|
+
};
|
|
107
|
+
} else {
|
|
108
|
+
tui.fatal('Failed to create storage');
|
|
78
109
|
}
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
|
|
110
|
+
} catch (ex) {
|
|
111
|
+
if (ex instanceof APIError) {
|
|
112
|
+
if (ex.status === 409) {
|
|
113
|
+
const bucketName = opts.name || 'auto-generated';
|
|
114
|
+
tui.fatal(
|
|
115
|
+
`bucket with the name "${bucketName}" already exists. Use another name or don't specify --name for a unique name to be generated automatically.`,
|
|
116
|
+
ErrorCode.INVALID_ARGUMENT
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
if (ex.status === 400) {
|
|
120
|
+
tui.fatal(ex.message || 'invalid bucket name', ErrorCode.INVALID_ARGUMENT);
|
|
121
|
+
}
|
|
82
122
|
}
|
|
83
|
-
|
|
84
|
-
success: true,
|
|
85
|
-
name: resource.name,
|
|
86
|
-
};
|
|
87
|
-
} else {
|
|
88
|
-
tui.fatal('Failed to create storage');
|
|
123
|
+
throw ex;
|
|
89
124
|
}
|
|
90
125
|
},
|
|
91
126
|
});
|
|
@@ -14,7 +14,7 @@ import { getResourceInfo, setResourceInfo, deleteResourceRegion } from '../../..
|
|
|
14
14
|
export const deleteSubcommand = createSubcommand({
|
|
15
15
|
name: 'delete',
|
|
16
16
|
aliases: ['rm', 'del', 'remove'],
|
|
17
|
-
description: 'Delete a storage resource or
|
|
17
|
+
description: 'Delete a storage resource, file, or folder',
|
|
18
18
|
tags: ['destructive', 'deletes-resource', 'slow', 'requires-auth', 'requires-deployment'],
|
|
19
19
|
idempotent: false,
|
|
20
20
|
requires: { auth: true },
|
|
@@ -32,6 +32,10 @@ export const deleteSubcommand = createSubcommand({
|
|
|
32
32
|
command: getCommand('cloud storage delete'),
|
|
33
33
|
description: 'Interactive selection to delete a bucket',
|
|
34
34
|
},
|
|
35
|
+
{
|
|
36
|
+
command: getCommand('cloud storage delete my-bucket path/to/folder'),
|
|
37
|
+
description: 'Delete a folder and all its contents from a bucket',
|
|
38
|
+
},
|
|
35
39
|
{
|
|
36
40
|
command: getCommand('--dry-run cloud storage delete my-bucket'),
|
|
37
41
|
description: 'Dry-run: show what would be deleted without making changes',
|
|
@@ -48,6 +52,7 @@ export const deleteSubcommand = createSubcommand({
|
|
|
48
52
|
response: z.object({
|
|
49
53
|
success: z.boolean().describe('Whether deletion succeeded'),
|
|
50
54
|
name: z.string().describe('Deleted bucket or file name'),
|
|
55
|
+
count: z.number().optional().describe('Number of files deleted (for folder deletion)'),
|
|
51
56
|
}),
|
|
52
57
|
},
|
|
53
58
|
|
|
@@ -116,7 +121,7 @@ export const deleteSubcommand = createSubcommand({
|
|
|
116
121
|
bucketName = response.bucket;
|
|
117
122
|
}
|
|
118
123
|
|
|
119
|
-
// If filename is provided, delete the file from the bucket
|
|
124
|
+
// If filename is provided, delete the file or folder from the bucket
|
|
120
125
|
if (args.filename) {
|
|
121
126
|
const bucket = resources.s3.find((s3) => s3.bucket_name === bucketName);
|
|
122
127
|
|
|
@@ -131,22 +136,143 @@ export const deleteSubcommand = createSubcommand({
|
|
|
131
136
|
);
|
|
132
137
|
}
|
|
133
138
|
|
|
139
|
+
const s3Client = createS3Client({
|
|
140
|
+
endpoint: bucket.endpoint,
|
|
141
|
+
access_key: bucket.access_key,
|
|
142
|
+
secret_key: bucket.secret_key,
|
|
143
|
+
region: bucket.region,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const filePath = args.filename;
|
|
147
|
+
|
|
148
|
+
// Check if path represents a folder by listing objects under it
|
|
149
|
+
const folderPrefix = filePath.endsWith('/') ? filePath : filePath + '/';
|
|
150
|
+
const folderContents: Array<{ key: string }> = [];
|
|
151
|
+
let continuationToken: string | undefined;
|
|
152
|
+
let isTruncated = false;
|
|
153
|
+
|
|
154
|
+
do {
|
|
155
|
+
const folderResult = await s3Client.list({
|
|
156
|
+
prefix: folderPrefix,
|
|
157
|
+
...(continuationToken ? { continuationToken } : {}),
|
|
158
|
+
});
|
|
159
|
+
if (folderResult.contents) {
|
|
160
|
+
folderContents.push(...folderResult.contents);
|
|
161
|
+
}
|
|
162
|
+
continuationToken = folderResult.nextContinuationToken;
|
|
163
|
+
isTruncated = folderResult.isTruncated ?? false;
|
|
164
|
+
} while (isTruncated);
|
|
165
|
+
|
|
166
|
+
if (folderContents.length > 0) {
|
|
167
|
+
// Path is a folder — recursive delete
|
|
168
|
+
const keysToDelete = folderContents.map((obj: { key: string }) => obj.key);
|
|
169
|
+
|
|
170
|
+
// Handle dry-run mode
|
|
171
|
+
if (isDryRunMode(options)) {
|
|
172
|
+
outputDryRun(
|
|
173
|
+
`Would delete ${keysToDelete.length} file${keysToDelete.length === 1 ? '' : 's'} under ${folderPrefix} from bucket ${bucketName}`,
|
|
174
|
+
options
|
|
175
|
+
);
|
|
176
|
+
if (!options.json) {
|
|
177
|
+
tui.newline();
|
|
178
|
+
tui.info('[DRY RUN] Folder deletion skipped');
|
|
179
|
+
}
|
|
180
|
+
return { success: false, name: filePath, count: keysToDelete.length };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Confirm
|
|
184
|
+
if (!opts.confirm) {
|
|
185
|
+
tui.warning(
|
|
186
|
+
`You are about to delete ${tui.bold(String(keysToDelete.length))} file${keysToDelete.length === 1 ? '' : 's'} under folder: ${tui.bold(folderPrefix)} from bucket: ${tui.bold(bucketName)}`
|
|
187
|
+
);
|
|
188
|
+
const confirm = await enquirer.prompt<{ confirm: boolean }>({
|
|
189
|
+
type: 'confirm',
|
|
190
|
+
name: 'confirm',
|
|
191
|
+
message: 'Are you sure you want to delete this folder and all its contents?',
|
|
192
|
+
initial: false,
|
|
193
|
+
});
|
|
194
|
+
if (!confirm.confirm) {
|
|
195
|
+
tui.info('Deletion cancelled');
|
|
196
|
+
return { success: false, name: filePath };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Delete all files
|
|
201
|
+
await tui.spinner({
|
|
202
|
+
message: `Deleting ${keysToDelete.length} file${keysToDelete.length === 1 ? '' : 's'} under ${folderPrefix} from ${bucketName}`,
|
|
203
|
+
clearOnSuccess: true,
|
|
204
|
+
callback: async () => {
|
|
205
|
+
const errors: Array<{ key: string; error: string }> = [];
|
|
206
|
+
for (const key of keysToDelete) {
|
|
207
|
+
try {
|
|
208
|
+
await s3Client.delete(key);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
errors.push({
|
|
211
|
+
key,
|
|
212
|
+
error: err instanceof Error ? err.message : String(err),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (errors.length > 0) {
|
|
217
|
+
const failedKeys = errors.map((e) => e.key).join(', ');
|
|
218
|
+
throw new Error(
|
|
219
|
+
`Failed to delete ${errors.length} file${errors.length === 1 ? '' : 's'}: ${failedKeys}`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Also delete the exact file if it exists (handles file+folder name conflicts)
|
|
226
|
+
// Skip if filePath was already deleted as part of folder contents (e.g., trailing-slash folder markers)
|
|
227
|
+
if (!keysToDelete.includes(filePath)) {
|
|
228
|
+
const exactFileCheck = await s3Client.list({ prefix: filePath });
|
|
229
|
+
const exactFile = (exactFileCheck.contents || []).find(
|
|
230
|
+
(obj: { key: string }) => obj.key === filePath
|
|
231
|
+
);
|
|
232
|
+
if (exactFile) {
|
|
233
|
+
await s3Client.delete(filePath);
|
|
234
|
+
keysToDelete.push(filePath);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!options.json) {
|
|
239
|
+
tui.success(
|
|
240
|
+
`Deleted ${tui.bold(String(keysToDelete.length))} file${keysToDelete.length === 1 ? '' : 's'} under ${tui.bold(folderPrefix)} from ${tui.bold(bucketName)}`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return { success: true, name: filePath, count: keysToDelete.length };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Not a folder — check if exact file exists
|
|
248
|
+
const fileResult = await s3Client.list({ prefix: filePath });
|
|
249
|
+
const exactMatch = (fileResult.contents || []).find(
|
|
250
|
+
(obj: { key: string }) => obj.key === filePath
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
if (!exactMatch) {
|
|
254
|
+
tui.fatal(
|
|
255
|
+
`No file or folder found at '${filePath}' in bucket '${bucketName}'`,
|
|
256
|
+
ErrorCode.RESOURCE_NOT_FOUND
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
134
260
|
// Handle dry-run mode
|
|
135
261
|
if (isDryRunMode(options)) {
|
|
136
|
-
outputDryRun(`Would delete file ${
|
|
262
|
+
outputDryRun(`Would delete file ${filePath} from bucket ${bucketName}`, options);
|
|
137
263
|
if (!options.json) {
|
|
138
264
|
tui.newline();
|
|
139
265
|
tui.info('[DRY RUN] File deletion skipped');
|
|
140
266
|
}
|
|
141
267
|
return {
|
|
142
268
|
success: false,
|
|
143
|
-
name:
|
|
269
|
+
name: filePath,
|
|
144
270
|
};
|
|
145
271
|
}
|
|
146
272
|
|
|
147
273
|
if (!opts.confirm) {
|
|
148
274
|
tui.warning(
|
|
149
|
-
`You are about to delete file: ${tui.bold(
|
|
275
|
+
`You are about to delete file: ${tui.bold(filePath)} from bucket: ${tui.bold(bucketName)}`
|
|
150
276
|
);
|
|
151
277
|
|
|
152
278
|
const confirm = await enquirer.prompt<{ confirm: boolean }>({
|
|
@@ -158,32 +284,25 @@ export const deleteSubcommand = createSubcommand({
|
|
|
158
284
|
|
|
159
285
|
if (!confirm.confirm) {
|
|
160
286
|
tui.info('Deletion cancelled');
|
|
161
|
-
return { success: false, name:
|
|
287
|
+
return { success: false, name: filePath };
|
|
162
288
|
}
|
|
163
289
|
}
|
|
164
290
|
|
|
165
|
-
const s3Client = createS3Client({
|
|
166
|
-
endpoint: bucket.endpoint,
|
|
167
|
-
access_key: bucket.access_key,
|
|
168
|
-
secret_key: bucket.secret_key,
|
|
169
|
-
region: bucket.region,
|
|
170
|
-
});
|
|
171
|
-
|
|
172
291
|
await tui.spinner({
|
|
173
|
-
message: `Deleting ${
|
|
292
|
+
message: `Deleting ${filePath} from ${bucketName}`,
|
|
174
293
|
clearOnSuccess: true,
|
|
175
294
|
callback: async () => {
|
|
176
|
-
await s3Client.delete(
|
|
295
|
+
await s3Client.delete(filePath);
|
|
177
296
|
},
|
|
178
297
|
});
|
|
179
298
|
|
|
180
299
|
if (!options.json) {
|
|
181
|
-
tui.success(`Deleted file: ${tui.bold(
|
|
300
|
+
tui.success(`Deleted file: ${tui.bold(filePath)} from ${tui.bold(bucketName)}`);
|
|
182
301
|
}
|
|
183
302
|
|
|
184
303
|
return {
|
|
185
304
|
success: true,
|
|
186
|
-
name:
|
|
305
|
+
name: filePath,
|
|
187
306
|
};
|
|
188
307
|
}
|
|
189
308
|
|