@agentuity/cli 0.0.59 → 0.0.60
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/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -1
- package/dist/cli.js.map +1 -1
- package/dist/cmd/ai/capabilities/show.d.ts.map +1 -1
- package/dist/cmd/ai/capabilities/show.js +11 -4
- package/dist/cmd/ai/capabilities/show.js.map +1 -1
- package/dist/cmd/cloud/db/create.d.ts +2 -0
- package/dist/cmd/cloud/db/create.d.ts.map +1 -0
- package/dist/cmd/cloud/db/create.js +68 -0
- package/dist/cmd/cloud/db/create.js.map +1 -0
- package/dist/cmd/cloud/db/delete.d.ts.map +1 -0
- package/dist/cmd/cloud/db/delete.js +106 -0
- package/dist/cmd/cloud/db/delete.js.map +1 -0
- package/dist/cmd/cloud/db/get.d.ts +2 -0
- package/dist/cmd/cloud/db/get.d.ts.map +1 -0
- package/dist/cmd/cloud/db/get.js +66 -0
- package/dist/cmd/cloud/db/get.js.map +1 -0
- package/dist/cmd/cloud/db/index.d.ts +2 -0
- package/dist/cmd/cloud/db/index.d.ts.map +1 -0
- package/dist/cmd/cloud/db/index.js +14 -0
- package/dist/cmd/cloud/db/index.js.map +1 -0
- package/dist/cmd/cloud/db/list.d.ts.map +1 -0
- package/dist/cmd/cloud/db/list.js +75 -0
- package/dist/cmd/cloud/db/list.js.map +1 -0
- package/dist/cmd/cloud/db/sql.d.ts +2 -0
- package/dist/cmd/cloud/db/sql.d.ts.map +1 -0
- package/dist/cmd/cloud/db/sql.js +102 -0
- package/dist/cmd/cloud/db/sql.js.map +1 -0
- package/dist/cmd/cloud/env/get.d.ts.map +1 -1
- package/dist/cmd/cloud/env/get.js +2 -3
- package/dist/cmd/cloud/env/get.js.map +1 -1
- package/dist/cmd/cloud/env/list.d.ts.map +1 -1
- package/dist/cmd/cloud/env/list.js +1 -2
- package/dist/cmd/cloud/env/list.js.map +1 -1
- package/dist/cmd/cloud/index.d.ts.map +1 -1
- package/dist/cmd/cloud/index.js +4 -2
- package/dist/cmd/cloud/index.js.map +1 -1
- package/dist/cmd/cloud/secret/get.d.ts.map +1 -1
- package/dist/cmd/cloud/secret/get.js +2 -3
- package/dist/cmd/cloud/secret/get.js.map +1 -1
- package/dist/cmd/cloud/secret/list.d.ts.map +1 -1
- package/dist/cmd/cloud/secret/list.js +1 -2
- package/dist/cmd/cloud/secret/list.js.map +1 -1
- package/dist/cmd/cloud/storage/create.d.ts +2 -0
- package/dist/cmd/cloud/storage/create.d.ts.map +1 -0
- package/dist/cmd/cloud/storage/create.js +61 -0
- package/dist/cmd/cloud/storage/create.js.map +1 -0
- package/dist/cmd/cloud/storage/delete.d.ts +2 -0
- package/dist/cmd/cloud/storage/delete.d.ts.map +1 -0
- package/dist/cmd/cloud/storage/delete.js +167 -0
- package/dist/cmd/cloud/storage/delete.js.map +1 -0
- package/dist/cmd/cloud/storage/download.d.ts +2 -0
- package/dist/cmd/cloud/storage/download.d.ts.map +1 -0
- package/dist/cmd/cloud/storage/download.js +131 -0
- package/dist/cmd/cloud/storage/download.js.map +1 -0
- package/dist/cmd/cloud/storage/get.d.ts +2 -0
- package/dist/cmd/cloud/storage/get.d.ts.map +1 -0
- package/dist/cmd/cloud/storage/get.js +86 -0
- package/dist/cmd/cloud/storage/get.js.map +1 -0
- package/dist/cmd/cloud/storage/index.d.ts +2 -0
- package/dist/cmd/cloud/storage/index.d.ts.map +1 -0
- package/dist/cmd/cloud/storage/index.js +22 -0
- package/dist/cmd/cloud/storage/index.js.map +1 -0
- package/dist/cmd/cloud/storage/list.d.ts +2 -0
- package/dist/cmd/cloud/storage/list.d.ts.map +1 -0
- package/dist/cmd/cloud/storage/list.js +170 -0
- package/dist/cmd/cloud/storage/list.js.map +1 -0
- package/dist/cmd/cloud/storage/upload.d.ts +2 -0
- package/dist/cmd/cloud/storage/upload.d.ts.map +1 -0
- package/dist/cmd/cloud/storage/upload.js +133 -0
- package/dist/cmd/cloud/storage/upload.js.map +1 -0
- package/dist/cmd/cloud/storage/utils.d.ts +18 -0
- package/dist/cmd/cloud/storage/utils.d.ts.map +1 -0
- package/dist/cmd/cloud/storage/utils.js +21 -0
- package/dist/cmd/cloud/storage/utils.js.map +1 -0
- package/dist/tui.d.ts +2 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +3 -0
- package/dist/tui.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +3 -3
- package/src/cli.ts +15 -1
- package/src/cmd/ai/capabilities/show.ts +11 -4
- package/src/cmd/cloud/db/create.ts +72 -0
- package/src/cmd/cloud/db/delete.ts +118 -0
- package/src/cmd/cloud/db/get.ts +75 -0
- package/src/cmd/cloud/db/index.ts +14 -0
- package/src/cmd/cloud/db/list.ts +83 -0
- package/src/cmd/cloud/db/sql.ts +125 -0
- package/src/cmd/cloud/env/get.ts +2 -3
- package/src/cmd/cloud/env/list.ts +1 -2
- package/src/cmd/cloud/index.ts +5 -2
- package/src/cmd/cloud/secret/get.ts +2 -3
- package/src/cmd/cloud/secret/list.ts +1 -2
- package/src/cmd/cloud/storage/create.ts +65 -0
- package/src/cmd/cloud/storage/delete.ts +195 -0
- package/src/cmd/cloud/storage/download.ts +154 -0
- package/src/cmd/cloud/storage/get.ts +97 -0
- package/src/cmd/cloud/storage/index.ts +22 -0
- package/src/cmd/cloud/storage/list.ts +197 -0
- package/src/cmd/cloud/storage/upload.ts +150 -0
- package/src/cmd/cloud/storage/utils.ts +26 -0
- package/src/tui.ts +4 -0
- package/src/version.ts +1 -1
- package/dist/cmd/cloud/resource/add.d.ts +0 -2
- package/dist/cmd/cloud/resource/add.d.ts.map +0 -1
- package/dist/cmd/cloud/resource/add.js +0 -70
- package/dist/cmd/cloud/resource/add.js.map +0 -1
- package/dist/cmd/cloud/resource/delete.d.ts.map +0 -1
- package/dist/cmd/cloud/resource/delete.js +0 -126
- package/dist/cmd/cloud/resource/delete.js.map +0 -1
- package/dist/cmd/cloud/resource/index.d.ts +0 -2
- package/dist/cmd/cloud/resource/index.d.ts.map +0 -1
- package/dist/cmd/cloud/resource/index.js +0 -12
- package/dist/cmd/cloud/resource/index.js.map +0 -1
- package/dist/cmd/cloud/resource/list.d.ts.map +0 -1
- package/dist/cmd/cloud/resource/list.js +0 -100
- package/dist/cmd/cloud/resource/list.js.map +0 -1
- package/src/cmd/cloud/resource/add.ts +0 -75
- package/src/cmd/cloud/resource/delete.ts +0 -141
- package/src/cmd/cloud/resource/index.ts +0 -12
- package/src/cmd/cloud/resource/list.ts +0 -105
- /package/dist/cmd/cloud/{resource → db}/delete.d.ts +0 -0
- /package/dist/cmd/cloud/{resource → db}/list.d.ts +0 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { listResources, deleteResources } from '@agentuity/server';
|
|
3
|
+
import enquirer from 'enquirer';
|
|
4
|
+
import { createSubcommand } from '../../../types';
|
|
5
|
+
import * as tui from '../../../tui';
|
|
6
|
+
import { getCatalystAPIClient } from '../../../config';
|
|
7
|
+
import { getCommand } from '../../../command-prefix';
|
|
8
|
+
import { isDryRunMode, outputDryRun } from '../../../explain';
|
|
9
|
+
import { ErrorCode } from '../../../errors';
|
|
10
|
+
import { createS3Client } from './utils';
|
|
11
|
+
|
|
12
|
+
export const deleteSubcommand = createSubcommand({
|
|
13
|
+
name: 'delete',
|
|
14
|
+
aliases: ['rm', 'del', 'remove'],
|
|
15
|
+
description: 'Delete a storage resource or file',
|
|
16
|
+
tags: ['destructive', 'deletes-resource', 'slow', 'requires-auth', 'requires-deployment'],
|
|
17
|
+
idempotent: false,
|
|
18
|
+
requires: { auth: true, org: true, region: true },
|
|
19
|
+
examples: [
|
|
20
|
+
getCommand('cloud storage delete my-bucket'),
|
|
21
|
+
getCommand('cloud storage rm my-bucket file.txt'),
|
|
22
|
+
getCommand('cloud storage delete'),
|
|
23
|
+
getCommand('--dry-run cloud storage delete my-bucket'),
|
|
24
|
+
],
|
|
25
|
+
schema: {
|
|
26
|
+
args: z.object({
|
|
27
|
+
name: z.string().optional().describe('Bucket name'),
|
|
28
|
+
filename: z.string().optional().describe('File path to delete from bucket'),
|
|
29
|
+
}),
|
|
30
|
+
options: z.object({
|
|
31
|
+
confirm: z.boolean().optional().describe('Skip confirmation prompts'),
|
|
32
|
+
}),
|
|
33
|
+
response: z.object({
|
|
34
|
+
success: z.boolean().describe('Whether deletion succeeded'),
|
|
35
|
+
name: z.string().describe('Deleted bucket or file name'),
|
|
36
|
+
}),
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
async handler(ctx) {
|
|
40
|
+
const { logger, args, opts, config, orgId, region, auth, options } = ctx;
|
|
41
|
+
|
|
42
|
+
const catalystClient = getCatalystAPIClient(config, logger, auth);
|
|
43
|
+
|
|
44
|
+
const resources = await tui.spinner({
|
|
45
|
+
message: `Fetching storage for ${orgId} in ${region}`,
|
|
46
|
+
clearOnSuccess: true,
|
|
47
|
+
callback: async () => {
|
|
48
|
+
return listResources(catalystClient, orgId, region!);
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
let bucketName = args.name;
|
|
53
|
+
|
|
54
|
+
if (!bucketName) {
|
|
55
|
+
if (resources.s3.length === 0) {
|
|
56
|
+
tui.info('No storage buckets found to delete');
|
|
57
|
+
return { success: false, name: '' };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const response = await enquirer.prompt<{ bucket: string }>({
|
|
61
|
+
type: 'select',
|
|
62
|
+
name: 'bucket',
|
|
63
|
+
message: 'Select storage bucket to delete:',
|
|
64
|
+
choices: resources.s3.map((s3) => ({
|
|
65
|
+
name: s3.bucket_name,
|
|
66
|
+
message: s3.bucket_name,
|
|
67
|
+
})),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
bucketName = response.bucket;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// If filename is provided, delete the file from the bucket
|
|
74
|
+
if (args.filename) {
|
|
75
|
+
const bucket = resources.s3.find((s3) => s3.bucket_name === bucketName);
|
|
76
|
+
|
|
77
|
+
if (!bucket) {
|
|
78
|
+
tui.fatal(`Storage bucket '${bucketName}' not found`, ErrorCode.RESOURCE_NOT_FOUND);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!bucket.access_key || !bucket.secret_key || !bucket.endpoint) {
|
|
82
|
+
tui.fatal(
|
|
83
|
+
`Storage bucket '${bucketName}' is missing credentials`,
|
|
84
|
+
ErrorCode.CONFIG_INVALID
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Handle dry-run mode
|
|
89
|
+
if (isDryRunMode(options)) {
|
|
90
|
+
outputDryRun(`Would delete file ${args.filename} from bucket ${bucketName}`, options);
|
|
91
|
+
if (!options.json) {
|
|
92
|
+
tui.newline();
|
|
93
|
+
tui.info('[DRY RUN] File deletion skipped');
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
success: false,
|
|
97
|
+
name: args.filename,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!opts.confirm) {
|
|
102
|
+
tui.warning(
|
|
103
|
+
`You are about to delete file: ${tui.bold(args.filename)} from bucket: ${tui.bold(bucketName)}`
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const confirm = await enquirer.prompt<{ confirm: boolean }>({
|
|
107
|
+
type: 'confirm',
|
|
108
|
+
name: 'confirm',
|
|
109
|
+
message: 'Are you sure you want to delete this file?',
|
|
110
|
+
initial: false,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (!confirm.confirm) {
|
|
114
|
+
tui.info('Deletion cancelled');
|
|
115
|
+
return { success: false, name: args.filename };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const s3Client = createS3Client({
|
|
120
|
+
endpoint: bucket.endpoint,
|
|
121
|
+
access_key: bucket.access_key,
|
|
122
|
+
secret_key: bucket.secret_key,
|
|
123
|
+
region: bucket.region,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await tui.spinner({
|
|
127
|
+
message: `Deleting ${args.filename} from ${bucketName}`,
|
|
128
|
+
clearOnSuccess: true,
|
|
129
|
+
callback: async () => {
|
|
130
|
+
await s3Client.delete(args.filename!);
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (!options.json) {
|
|
135
|
+
tui.success(`Deleted file: ${tui.bold(args.filename)} from ${tui.bold(bucketName)}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
success: true,
|
|
140
|
+
name: args.filename,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Otherwise, delete the bucket
|
|
145
|
+
// Handle dry-run mode
|
|
146
|
+
if (isDryRunMode(options)) {
|
|
147
|
+
outputDryRun(`Would delete storage bucket: ${bucketName}`, options);
|
|
148
|
+
if (!options.json) {
|
|
149
|
+
tui.newline();
|
|
150
|
+
tui.info('[DRY RUN] Storage bucket deletion skipped');
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
success: false,
|
|
154
|
+
name: bucketName,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!opts.confirm) {
|
|
159
|
+
tui.warning(`You are about to delete storage bucket: ${tui.bold(bucketName)}`);
|
|
160
|
+
|
|
161
|
+
const confirm = await enquirer.prompt<{ confirm: boolean }>({
|
|
162
|
+
type: 'confirm',
|
|
163
|
+
name: 'confirm',
|
|
164
|
+
message: 'Are you sure you want to delete this storage bucket?',
|
|
165
|
+
initial: false,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (!confirm.confirm) {
|
|
169
|
+
tui.info('Deletion cancelled');
|
|
170
|
+
return { success: false, name: bucketName };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const deleted = await tui.spinner({
|
|
175
|
+
message: `Deleting storage bucket ${bucketName}`,
|
|
176
|
+
clearOnSuccess: true,
|
|
177
|
+
callback: async () => {
|
|
178
|
+
return deleteResources(catalystClient, orgId, region!, [
|
|
179
|
+
{ type: 's3', name: bucketName },
|
|
180
|
+
]);
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
if (deleted.length > 0) {
|
|
185
|
+
tui.success(`Deleted storage bucket: ${tui.bold(deleted[0])}`);
|
|
186
|
+
return {
|
|
187
|
+
success: true,
|
|
188
|
+
name: deleted[0],
|
|
189
|
+
};
|
|
190
|
+
} else {
|
|
191
|
+
tui.error('Failed to delete storage bucket');
|
|
192
|
+
return { success: false, name: bucketName };
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { listResources } from '@agentuity/server';
|
|
3
|
+
import { createSubcommand } from '../../../types';
|
|
4
|
+
import * as tui from '../../../tui';
|
|
5
|
+
import { getCatalystAPIClient } from '../../../config';
|
|
6
|
+
import { getCommand } from '../../../command-prefix';
|
|
7
|
+
import { ErrorCode } from '../../../errors';
|
|
8
|
+
import { createS3Client } from './utils';
|
|
9
|
+
|
|
10
|
+
export const downloadSubcommand = createSubcommand({
|
|
11
|
+
name: 'download',
|
|
12
|
+
description: 'Download a file from storage bucket',
|
|
13
|
+
tags: ['read-only', 'requires-auth'],
|
|
14
|
+
requires: { auth: true, org: true, region: true },
|
|
15
|
+
idempotent: true,
|
|
16
|
+
examples: [
|
|
17
|
+
`${getCommand('cloud storage download')} my-bucket file.txt`,
|
|
18
|
+
`${getCommand('cloud storage download')} my-bucket file.txt output.txt`,
|
|
19
|
+
`${getCommand('cloud storage download')} my-bucket file.txt - > output.txt`,
|
|
20
|
+
`${getCommand('cloud storage download')} my-bucket file.txt --metadata`,
|
|
21
|
+
],
|
|
22
|
+
schema: {
|
|
23
|
+
args: z.object({
|
|
24
|
+
name: z.string().describe('Bucket name'),
|
|
25
|
+
filename: z.string().describe('File path to download'),
|
|
26
|
+
output: z.string().optional().describe('Output file path or "-" for STDOUT'),
|
|
27
|
+
}),
|
|
28
|
+
options: z.object({
|
|
29
|
+
metadata: z.boolean().optional().describe('Download metadata only (not file contents)'),
|
|
30
|
+
}),
|
|
31
|
+
response: z.object({
|
|
32
|
+
success: z.boolean().describe('Whether download succeeded'),
|
|
33
|
+
bucket: z.string().describe('Bucket name'),
|
|
34
|
+
filename: z.string().describe('Downloaded filename'),
|
|
35
|
+
size: z.number().optional().describe('File size in bytes'),
|
|
36
|
+
contentType: z.string().optional().describe('Content type'),
|
|
37
|
+
lastModified: z.string().optional().describe('Last modified timestamp'),
|
|
38
|
+
}),
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
async handler(ctx) {
|
|
42
|
+
const { logger, args, opts, options, orgId, region, config, auth } = ctx;
|
|
43
|
+
|
|
44
|
+
const catalystClient = getCatalystAPIClient(config, logger, auth);
|
|
45
|
+
|
|
46
|
+
// Fetch bucket credentials
|
|
47
|
+
const resources = await tui.spinner({
|
|
48
|
+
message: `Fetching credentials for ${args.name}`,
|
|
49
|
+
clearOnSuccess: true,
|
|
50
|
+
callback: async () => {
|
|
51
|
+
return listResources(catalystClient, orgId, region);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const bucket = resources.s3.find((s3) => s3.bucket_name === args.name);
|
|
56
|
+
|
|
57
|
+
if (!bucket) {
|
|
58
|
+
tui.fatal(`Storage bucket '${args.name}' not found`, ErrorCode.RESOURCE_NOT_FOUND);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!bucket.access_key || !bucket.secret_key || !bucket.endpoint) {
|
|
62
|
+
tui.fatal(
|
|
63
|
+
`Storage bucket '${args.name}' is missing credentials`,
|
|
64
|
+
ErrorCode.CONFIG_INVALID
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Initialize S3 client
|
|
69
|
+
const s3Client = createS3Client({
|
|
70
|
+
endpoint: bucket.endpoint,
|
|
71
|
+
access_key: bucket.access_key,
|
|
72
|
+
secret_key: bucket.secret_key,
|
|
73
|
+
region: bucket.region,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (opts.metadata) {
|
|
77
|
+
// Download metadata only
|
|
78
|
+
const metadata = await tui.spinner({
|
|
79
|
+
message: `Fetching metadata for ${args.filename}`,
|
|
80
|
+
clearOnSuccess: true,
|
|
81
|
+
callback: async () => {
|
|
82
|
+
return s3Client.stat(args.filename);
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (!options.json) {
|
|
87
|
+
console.log(tui.bold('File: ') + args.filename);
|
|
88
|
+
console.log(tui.bold('Bucket: ') + args.name);
|
|
89
|
+
if (metadata.size) {
|
|
90
|
+
console.log(tui.bold('Size: ') + metadata.size + ' bytes');
|
|
91
|
+
}
|
|
92
|
+
if (metadata.type) {
|
|
93
|
+
console.log(tui.bold('Content Type: ') + metadata.type);
|
|
94
|
+
}
|
|
95
|
+
if (metadata.lastModified) {
|
|
96
|
+
console.log(tui.bold('Last Modified: ') + metadata.lastModified);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
success: true,
|
|
102
|
+
bucket: args.name,
|
|
103
|
+
filename: args.filename,
|
|
104
|
+
size: metadata.size,
|
|
105
|
+
contentType: metadata.type,
|
|
106
|
+
lastModified: metadata.lastModified?.toISOString(),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Download file content
|
|
111
|
+
const s3File = s3Client.file(args.filename);
|
|
112
|
+
|
|
113
|
+
const fileContent = await tui.spinner({
|
|
114
|
+
message: `Downloading ${args.filename} from ${args.name}`,
|
|
115
|
+
clearOnSuccess: true,
|
|
116
|
+
callback: async () => {
|
|
117
|
+
return Buffer.from(await s3File.arrayBuffer());
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Write to output or STDOUT
|
|
122
|
+
const outputPath = args.output || '-';
|
|
123
|
+
|
|
124
|
+
if (outputPath === '-') {
|
|
125
|
+
// Write to STDOUT
|
|
126
|
+
// When outputting to STDOUT in JSON mode, we can't mix file content with JSON
|
|
127
|
+
if (options.json) {
|
|
128
|
+
tui.fatal(
|
|
129
|
+
'Cannot use --json with STDOUT output. Use --metadata to get JSON metadata, or specify an output file.',
|
|
130
|
+
ErrorCode.INVALID_ARGUMENT
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
process.stdout.write(fileContent);
|
|
135
|
+
} else {
|
|
136
|
+
// Write to file
|
|
137
|
+
await Bun.write(outputPath, fileContent);
|
|
138
|
+
if (!options.json) {
|
|
139
|
+
tui.success(
|
|
140
|
+
`Downloaded ${tui.bold(args.filename)} to ${tui.bold(outputPath)} (${fileContent.length} bytes)`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
success: true,
|
|
147
|
+
bucket: args.name,
|
|
148
|
+
filename: args.filename,
|
|
149
|
+
size: fileContent.length,
|
|
150
|
+
contentType: s3File.type,
|
|
151
|
+
lastModified: undefined,
|
|
152
|
+
};
|
|
153
|
+
},
|
|
154
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { listResources } from '@agentuity/server';
|
|
3
|
+
import { createSubcommand } from '../../../types';
|
|
4
|
+
import * as tui from '../../../tui';
|
|
5
|
+
import { getCatalystAPIClient } from '../../../config';
|
|
6
|
+
import { getCommand } from '../../../command-prefix';
|
|
7
|
+
import { ErrorCode } from '../../../errors';
|
|
8
|
+
|
|
9
|
+
const StorageGetResponseSchema = z.object({
|
|
10
|
+
bucket_name: z.string().describe('Storage bucket name'),
|
|
11
|
+
access_key: z.string().optional().describe('S3 access key'),
|
|
12
|
+
secret_key: z.string().optional().describe('S3 secret key'),
|
|
13
|
+
region: z.string().optional().describe('S3 region'),
|
|
14
|
+
endpoint: z.string().optional().describe('S3 endpoint URL'),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const getSubcommand = createSubcommand({
|
|
18
|
+
name: 'get',
|
|
19
|
+
aliases: ['show'],
|
|
20
|
+
description: 'Show details about a specific storage bucket',
|
|
21
|
+
tags: ['read-only', 'fast', 'requires-auth'],
|
|
22
|
+
requires: { auth: true, org: true, region: true },
|
|
23
|
+
idempotent: true,
|
|
24
|
+
examples: [
|
|
25
|
+
`${getCommand('cloud storage get')} my-bucket`,
|
|
26
|
+
`${getCommand('cloud storage show')} my-bucket`,
|
|
27
|
+
`${getCommand('cloud storage get')} my-bucket --show-credentials`,
|
|
28
|
+
],
|
|
29
|
+
schema: {
|
|
30
|
+
args: z.object({
|
|
31
|
+
name: z.string().describe('Bucket name'),
|
|
32
|
+
}),
|
|
33
|
+
options: z.object({
|
|
34
|
+
showCredentials: z
|
|
35
|
+
.boolean()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe(
|
|
38
|
+
'Show credentials in plain text (default: masked in terminal, unmasked in JSON)'
|
|
39
|
+
),
|
|
40
|
+
}),
|
|
41
|
+
response: StorageGetResponseSchema,
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
async handler(ctx) {
|
|
45
|
+
const { logger, args, opts, options, orgId, region, config, auth } = ctx;
|
|
46
|
+
|
|
47
|
+
const catalystClient = getCatalystAPIClient(config, logger, auth);
|
|
48
|
+
|
|
49
|
+
const resources = await tui.spinner({
|
|
50
|
+
message: `Fetching storage bucket ${args.name}`,
|
|
51
|
+
clearOnSuccess: true,
|
|
52
|
+
callback: async () => {
|
|
53
|
+
return listResources(catalystClient, orgId, region);
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const bucket = resources.s3.find((s3) => s3.bucket_name === args.name);
|
|
58
|
+
|
|
59
|
+
if (!bucket) {
|
|
60
|
+
tui.fatal(`Storage bucket '${args.name}' not found`, ErrorCode.RESOURCE_NOT_FOUND);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Mask credentials in terminal output by default, unless --show-credentials is passed
|
|
64
|
+
const shouldShowCredentials = opts.showCredentials === true;
|
|
65
|
+
const shouldMask = !options.json && !shouldShowCredentials;
|
|
66
|
+
|
|
67
|
+
if (!options.json) {
|
|
68
|
+
console.log(tui.bold('Bucket Name: ') + bucket.bucket_name);
|
|
69
|
+
if (bucket.access_key) {
|
|
70
|
+
const displayAccessKey = shouldMask
|
|
71
|
+
? tui.maskSecret(bucket.access_key)
|
|
72
|
+
: bucket.access_key;
|
|
73
|
+
console.log(tui.bold('Access Key: ') + displayAccessKey);
|
|
74
|
+
}
|
|
75
|
+
if (bucket.secret_key) {
|
|
76
|
+
const displaySecretKey = shouldMask
|
|
77
|
+
? tui.maskSecret(bucket.secret_key)
|
|
78
|
+
: bucket.secret_key;
|
|
79
|
+
console.log(tui.bold('Secret Key: ') + displaySecretKey);
|
|
80
|
+
}
|
|
81
|
+
if (bucket.region) {
|
|
82
|
+
console.log(tui.bold('Region: ') + bucket.region);
|
|
83
|
+
}
|
|
84
|
+
if (bucket.endpoint) {
|
|
85
|
+
console.log(tui.bold('Endpoint: ') + bucket.endpoint);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
bucket_name: bucket.bucket_name,
|
|
91
|
+
access_key: bucket.access_key ?? undefined,
|
|
92
|
+
secret_key: bucket.secret_key ?? undefined,
|
|
93
|
+
region: bucket.region ?? undefined,
|
|
94
|
+
endpoint: bucket.endpoint ?? undefined,
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createCommand } from '../../../types';
|
|
2
|
+
import { createSubcommand } from './create';
|
|
3
|
+
import { listSubcommand } from './list';
|
|
4
|
+
import { deleteSubcommand } from './delete';
|
|
5
|
+
import { getSubcommand } from './get';
|
|
6
|
+
import { uploadSubcommand } from './upload';
|
|
7
|
+
import { downloadSubcommand } from './download';
|
|
8
|
+
|
|
9
|
+
export const storageCommand = createCommand({
|
|
10
|
+
name: 'storage',
|
|
11
|
+
aliases: ['s3'],
|
|
12
|
+
description: 'Manage storage resources',
|
|
13
|
+
tags: ['slow', 'requires-auth', 'requires-deployment'],
|
|
14
|
+
subcommands: [
|
|
15
|
+
createSubcommand,
|
|
16
|
+
listSubcommand,
|
|
17
|
+
getSubcommand,
|
|
18
|
+
uploadSubcommand,
|
|
19
|
+
downloadSubcommand,
|
|
20
|
+
deleteSubcommand,
|
|
21
|
+
],
|
|
22
|
+
});
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { listResources } from '@agentuity/server';
|
|
3
|
+
import { createSubcommand } from '../../../types';
|
|
4
|
+
import * as tui from '../../../tui';
|
|
5
|
+
import { getCatalystAPIClient } from '../../../config';
|
|
6
|
+
import { getCommand } from '../../../command-prefix';
|
|
7
|
+
import { ErrorCode } from '../../../errors';
|
|
8
|
+
import { createS3Client } from './utils';
|
|
9
|
+
|
|
10
|
+
const StorageListResponseSchema = z.object({
|
|
11
|
+
buckets: z
|
|
12
|
+
.array(
|
|
13
|
+
z.object({
|
|
14
|
+
bucket_name: z.string().describe('Storage bucket name'),
|
|
15
|
+
access_key: z.string().optional().describe('S3 access key'),
|
|
16
|
+
secret_key: z.string().optional().describe('S3 secret key'),
|
|
17
|
+
region: z.string().optional().describe('S3 region'),
|
|
18
|
+
endpoint: z.string().optional().describe('S3 endpoint URL'),
|
|
19
|
+
})
|
|
20
|
+
)
|
|
21
|
+
.optional()
|
|
22
|
+
.describe('List of storage resources'),
|
|
23
|
+
files: z
|
|
24
|
+
.array(
|
|
25
|
+
z.object({
|
|
26
|
+
key: z.string().describe('File key/path'),
|
|
27
|
+
size: z.number().describe('File size in bytes'),
|
|
28
|
+
lastModified: z.string().describe('Last modified timestamp'),
|
|
29
|
+
})
|
|
30
|
+
)
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('List of files in bucket'),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const listSubcommand = createSubcommand({
|
|
36
|
+
name: 'list',
|
|
37
|
+
aliases: ['ls'],
|
|
38
|
+
description: 'List storage resources or files in a bucket',
|
|
39
|
+
tags: ['read-only', 'fast', 'requires-auth'],
|
|
40
|
+
requires: { auth: true, org: true, region: true },
|
|
41
|
+
idempotent: true,
|
|
42
|
+
examples: [
|
|
43
|
+
getCommand('cloud storage list'),
|
|
44
|
+
getCommand('cloud storage list my-bucket'),
|
|
45
|
+
getCommand('cloud storage list my-bucket path/prefix'),
|
|
46
|
+
getCommand('--json cloud storage list'),
|
|
47
|
+
getCommand('cloud storage ls'),
|
|
48
|
+
getCommand('cloud storage list --show-credentials'),
|
|
49
|
+
],
|
|
50
|
+
schema: {
|
|
51
|
+
args: z.object({
|
|
52
|
+
name: z.string().optional().describe('Bucket name to list files from'),
|
|
53
|
+
prefix: z.string().optional().describe('Path prefix to filter files'),
|
|
54
|
+
}),
|
|
55
|
+
options: z.object({
|
|
56
|
+
showCredentials: z
|
|
57
|
+
.boolean()
|
|
58
|
+
.optional()
|
|
59
|
+
.describe(
|
|
60
|
+
'Show credentials in plain text (default: masked in terminal, unmasked in JSON)'
|
|
61
|
+
),
|
|
62
|
+
}),
|
|
63
|
+
response: StorageListResponseSchema,
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async handler(ctx) {
|
|
67
|
+
const { logger, args, opts, options, orgId, region, config, auth } = ctx;
|
|
68
|
+
|
|
69
|
+
const catalystClient = getCatalystAPIClient(config, logger, auth);
|
|
70
|
+
|
|
71
|
+
const resources = await tui.spinner({
|
|
72
|
+
message: `Fetching storage for ${orgId} in ${region}`,
|
|
73
|
+
clearOnSuccess: true,
|
|
74
|
+
callback: async () => {
|
|
75
|
+
return listResources(catalystClient, orgId, region);
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// If bucket name is provided, list files in the bucket
|
|
80
|
+
if (args.name) {
|
|
81
|
+
const bucket = resources.s3.find((s3) => s3.bucket_name === args.name);
|
|
82
|
+
|
|
83
|
+
if (!bucket) {
|
|
84
|
+
tui.fatal(`Storage bucket '${args.name}' not found`, ErrorCode.RESOURCE_NOT_FOUND);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!bucket.access_key || !bucket.secret_key || !bucket.endpoint) {
|
|
88
|
+
tui.fatal(
|
|
89
|
+
`Storage bucket '${args.name}' is missing credentials`,
|
|
90
|
+
ErrorCode.CONFIG_INVALID
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const s3Client = createS3Client({
|
|
95
|
+
endpoint: bucket.endpoint,
|
|
96
|
+
access_key: bucket.access_key,
|
|
97
|
+
secret_key: bucket.secret_key,
|
|
98
|
+
region: bucket.region,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const result = await tui.spinner({
|
|
102
|
+
message: `Listing files in ${args.name}${args.prefix ? ` with prefix ${args.prefix}` : ''}`,
|
|
103
|
+
clearOnSuccess: true,
|
|
104
|
+
callback: async () => {
|
|
105
|
+
return s3Client.list(
|
|
106
|
+
args.prefix
|
|
107
|
+
? {
|
|
108
|
+
prefix: args.prefix,
|
|
109
|
+
}
|
|
110
|
+
: null
|
|
111
|
+
);
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const objects = result.contents || [];
|
|
116
|
+
|
|
117
|
+
if (!options.json) {
|
|
118
|
+
if (objects.length === 0) {
|
|
119
|
+
tui.info('No files found');
|
|
120
|
+
} else {
|
|
121
|
+
tui.info(
|
|
122
|
+
tui.bold(`Files in ${args.name}${args.prefix ? ` (prefix: ${args.prefix})` : ''}`)
|
|
123
|
+
);
|
|
124
|
+
tui.newline();
|
|
125
|
+
for (const obj of objects) {
|
|
126
|
+
console.log(
|
|
127
|
+
`${obj.key} ${tui.muted(`(${obj.size} bytes, ${obj.lastModified})`)}`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
files: objects.map((obj) => {
|
|
135
|
+
const lastModified = obj.lastModified;
|
|
136
|
+
let lastModifiedStr = '';
|
|
137
|
+
if (typeof lastModified === 'string') {
|
|
138
|
+
lastModifiedStr = lastModified;
|
|
139
|
+
} else if (
|
|
140
|
+
lastModified &&
|
|
141
|
+
typeof lastModified === 'object' &&
|
|
142
|
+
'toISOString' in lastModified
|
|
143
|
+
) {
|
|
144
|
+
lastModifiedStr = (lastModified as Date).toISOString();
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
key: obj.key,
|
|
148
|
+
size: obj.size ?? 0,
|
|
149
|
+
lastModified: lastModifiedStr,
|
|
150
|
+
};
|
|
151
|
+
}),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Otherwise, list buckets
|
|
156
|
+
// Mask credentials in terminal output by default, unless --show-credentials is passed
|
|
157
|
+
const shouldShowCredentials = opts.showCredentials === true;
|
|
158
|
+
const shouldMask = !options.json && !shouldShowCredentials;
|
|
159
|
+
|
|
160
|
+
if (!options.json) {
|
|
161
|
+
if (resources.s3.length === 0) {
|
|
162
|
+
tui.info('No storage buckets found');
|
|
163
|
+
} else {
|
|
164
|
+
tui.info(tui.bold('Storage'));
|
|
165
|
+
tui.newline();
|
|
166
|
+
for (const s3 of resources.s3) {
|
|
167
|
+
console.log(tui.bold(s3.bucket_name));
|
|
168
|
+
if (s3.access_key) {
|
|
169
|
+
const displayAccessKey = shouldMask
|
|
170
|
+
? tui.maskSecret(s3.access_key)
|
|
171
|
+
: s3.access_key;
|
|
172
|
+
console.log(` Access Key: ${tui.muted(displayAccessKey)}`);
|
|
173
|
+
}
|
|
174
|
+
if (s3.secret_key) {
|
|
175
|
+
const displaySecretKey = shouldMask
|
|
176
|
+
? tui.maskSecret(s3.secret_key)
|
|
177
|
+
: s3.secret_key;
|
|
178
|
+
console.log(` Secret Key: ${tui.muted(displaySecretKey)}`);
|
|
179
|
+
}
|
|
180
|
+
if (s3.region) console.log(` Region: ${tui.muted(s3.region)}`);
|
|
181
|
+
if (s3.endpoint) console.log(` Endpoint: ${tui.muted(s3.endpoint)}`);
|
|
182
|
+
tui.newline();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
buckets: resources.s3.map((s3) => ({
|
|
189
|
+
bucket_name: s3.bucket_name,
|
|
190
|
+
access_key: s3.access_key ?? undefined,
|
|
191
|
+
secret_key: s3.secret_key ?? undefined,
|
|
192
|
+
region: s3.region ?? undefined,
|
|
193
|
+
endpoint: s3.endpoint ?? undefined,
|
|
194
|
+
})),
|
|
195
|
+
};
|
|
196
|
+
},
|
|
197
|
+
});
|