@agentuity/cli 1.0.44 → 1.0.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/bin/cli.ts +189 -143
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +45 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/cmd/cloud/sandbox/cp.d.ts.map +1 -1
  6. package/dist/cmd/cloud/sandbox/cp.js +69 -13
  7. package/dist/cmd/cloud/sandbox/cp.js.map +1 -1
  8. package/dist/cmd/cloud/sandbox/events.d.ts +3 -0
  9. package/dist/cmd/cloud/sandbox/events.d.ts.map +1 -0
  10. package/dist/cmd/cloud/sandbox/events.js +92 -0
  11. package/dist/cmd/cloud/sandbox/events.js.map +1 -0
  12. package/dist/cmd/cloud/sandbox/exec.d.ts.map +1 -1
  13. package/dist/cmd/cloud/sandbox/exec.js +22 -0
  14. package/dist/cmd/cloud/sandbox/exec.js.map +1 -1
  15. package/dist/cmd/cloud/sandbox/execution/get.d.ts.map +1 -1
  16. package/dist/cmd/cloud/sandbox/execution/get.js +5 -0
  17. package/dist/cmd/cloud/sandbox/execution/get.js.map +1 -1
  18. package/dist/cmd/cloud/sandbox/execution/list.d.ts.map +1 -1
  19. package/dist/cmd/cloud/sandbox/execution/list.js +12 -7
  20. package/dist/cmd/cloud/sandbox/execution/list.js.map +1 -1
  21. package/dist/cmd/cloud/sandbox/get.d.ts.map +1 -1
  22. package/dist/cmd/cloud/sandbox/get.js +1 -0
  23. package/dist/cmd/cloud/sandbox/get.js.map +1 -1
  24. package/dist/cmd/cloud/sandbox/index.d.ts.map +1 -1
  25. package/dist/cmd/cloud/sandbox/index.js +2 -0
  26. package/dist/cmd/cloud/sandbox/index.js.map +1 -1
  27. package/dist/cmd/cloud/sandbox/util.js +1 -1
  28. package/dist/cmd/cloud/sandbox/util.js.map +1 -1
  29. package/package.json +6 -6
  30. package/src/cli.ts +56 -2
  31. package/src/cmd/cloud/sandbox/cp.ts +89 -14
  32. package/src/cmd/cloud/sandbox/events.ts +108 -0
  33. package/src/cmd/cloud/sandbox/exec.ts +27 -0
  34. package/src/cmd/cloud/sandbox/execution/get.ts +5 -0
  35. package/src/cmd/cloud/sandbox/execution/list.ts +13 -14
  36. package/src/cmd/cloud/sandbox/get.ts +1 -0
  37. package/src/cmd/cloud/sandbox/index.ts +2 -0
  38. package/src/cmd/cloud/sandbox/util.ts +1 -1
@@ -45,32 +45,41 @@ const SandboxCpResponseSchema = z.object({
45
45
  directoriesCreated: z
46
46
  .array(z.string())
47
47
  .optional()
48
- .describe('Parent directories auto-created on the destination'),
48
+ .describe(
49
+ 'Parent directories that were auto-created on the destination (not present in --strict mode or when all directories already exist)'
50
+ ),
49
51
  });
50
52
 
51
53
  export const cpSubcommand = createCommand({
52
54
  name: 'cp',
53
55
  aliases: ['copy'],
54
- description: 'Copy files or directories to or from a sandbox',
56
+ description:
57
+ 'Copy files or directories to or from a sandbox. Parent directories are automatically created if they do not exist (similar to mkdir -p).',
55
58
  tags: ['slow', 'requires-auth'],
56
59
  requires: { auth: true, apiClient: true },
57
60
  examples: [
58
61
  {
59
- command: getCommand('cloud sandbox cp ./local-file.txt snbx_abc123:/path/to/file.txt'),
62
+ command: getCommand('cloud sandbox cp ./local-file.txt sbx_abc123:/path/to/file.txt'),
60
63
  description: 'Copy a local file to a sandbox',
61
64
  },
62
65
  {
63
- command: getCommand('cloud sandbox cp snbx_abc123:/path/to/file.txt ./local-file.txt'),
66
+ command: getCommand('cloud sandbox cp sbx_abc123:/path/to/file.txt ./local-file.txt'),
64
67
  description: 'Copy a file from a sandbox to local',
65
68
  },
66
69
  {
67
- command: getCommand('cloud sandbox cp --recursive ./local-dir snbx_abc123:/path/to/dir'),
70
+ command: getCommand('cloud sandbox cp --recursive ./local-dir sbx_abc123:/path/to/dir'),
68
71
  description: 'Copy a local directory to a sandbox recursively',
69
72
  },
70
73
  {
71
- command: getCommand('cloud sandbox cp -r snbx_abc123:/path/to/dir ./local-dir'),
74
+ command: getCommand('cloud sandbox cp -r sbx_abc123:/path/to/dir ./local-dir'),
72
75
  description: 'Copy a directory from a sandbox to local recursively',
73
76
  },
77
+ {
78
+ command: getCommand(
79
+ 'cloud sandbox cp --strict ./local-file.txt sbx_abc123:/path/to/file.txt'
80
+ ),
81
+ description: 'Copy a file, failing if the target directory does not exist',
82
+ },
74
83
  ],
75
84
  schema: {
76
85
  args: z.object({
@@ -82,6 +91,13 @@ export const cpSubcommand = createCommand({
82
91
  options: z.object({
83
92
  timeout: z.string().optional().describe('Operation timeout (e.g., "5m", "1h")'),
84
93
  recursive: z.boolean().default(false).optional().describe('Copy directories recursively'),
94
+ strict: z
95
+ .boolean()
96
+ .default(false)
97
+ .optional()
98
+ .describe(
99
+ 'Fail if the target parent directory does not exist instead of auto-creating it'
100
+ ),
85
101
  }),
86
102
  aliases: {
87
103
  recursive: ['r'],
@@ -103,7 +119,7 @@ export const cpSubcommand = createCommand({
103
119
 
104
120
  if (!source.sandboxId && !destination.sandboxId) {
105
121
  logger.fatal(
106
- 'At least one path must include a sandbox ID (e.g., snbx_abc123:/path/to/file)'
122
+ 'At least one path must include a sandbox ID (e.g., sbx_abc123:/path/to/file)'
107
123
  );
108
124
  }
109
125
 
@@ -115,6 +131,7 @@ export const cpSubcommand = createCommand({
115
131
 
116
132
  const client = createSandboxClient(logger, auth, region);
117
133
  const recursive = opts.recursive ?? false;
134
+ const strict = opts.strict ?? false;
118
135
 
119
136
  if (source.sandboxId) {
120
137
  return await downloadFromSandbox(
@@ -138,7 +155,8 @@ export const cpSubcommand = createCommand({
138
155
  destination.path,
139
156
  opts.timeout,
140
157
  recursive,
141
- options.json ?? false
158
+ options.json ?? false,
159
+ strict
142
160
  );
143
161
  }
144
162
  },
@@ -193,7 +211,8 @@ async function uploadToSandbox(
193
211
  remotePath: string,
194
212
  timeout: string | undefined,
195
213
  recursive: boolean,
196
- jsonOutput: boolean
214
+ jsonOutput: boolean,
215
+ strict: boolean
197
216
  ): Promise<z.infer<typeof SandboxCpResponseSchema>> {
198
217
  const resolvedPath = resolve(localPath);
199
218
 
@@ -218,7 +237,8 @@ async function uploadToSandbox(
218
237
  resolvedPath,
219
238
  remotePath,
220
239
  timeout,
221
- jsonOutput
240
+ jsonOutput,
241
+ strict
222
242
  );
223
243
  }
224
244
 
@@ -231,20 +251,22 @@ async function uploadToSandbox(
231
251
  localPath,
232
252
  remotePath,
233
253
  timeout,
234
- jsonOutput
254
+ jsonOutput,
255
+ strict
235
256
  );
236
257
  }
237
258
 
238
259
  async function uploadSingleFile(
239
260
  client: APIClient,
240
- _logger: Logger,
261
+ logger: Logger,
241
262
  orgId: string,
242
263
  sandboxId: string,
243
264
  resolvedPath: string,
244
265
  displayPath: string,
245
266
  remotePath: string,
246
267
  _timeout: string | undefined,
247
- jsonOutput: boolean
268
+ jsonOutput: boolean,
269
+ strict: boolean
248
270
  ): Promise<z.infer<typeof SandboxCpResponseSchema>> {
249
271
  const buffer = readFileSync(resolvedPath);
250
272
 
@@ -254,6 +276,32 @@ async function uploadSingleFile(
254
276
  targetPath = baseDir ? baseDir + basename(resolvedPath) : basename(resolvedPath);
255
277
  }
256
278
 
279
+ if (strict) {
280
+ const parentDir = dirname(targetPath);
281
+ const knownDirs = new Set(['/', '/home', '/home/agentuity']);
282
+ if (!knownDirs.has(parentDir)) {
283
+ const checkExecution = await sandboxExecute(client, {
284
+ sandboxId,
285
+ options: {
286
+ command: ['test', '-d', parentDir],
287
+ },
288
+ orgId,
289
+ });
290
+ await waitForExecution(client, orgId, checkExecution.executionId, logger);
291
+ const execInfo = await executionGet(client, {
292
+ executionId: checkExecution.executionId,
293
+ orgId,
294
+ });
295
+ if (execInfo.exitCode !== 0) {
296
+ logger.fatal(
297
+ `Target directory does not exist: ${parentDir}\n` +
298
+ `Use without --strict to auto-create parent directories, or create it first with:\n` +
299
+ ` ${getCommand(`cloud sandbox mkdir ${sandboxId} ${parentDir} -p`)}`
300
+ );
301
+ }
302
+ }
303
+ }
304
+
257
305
  const files: FileToWrite[] = [{ path: targetPath, content: buffer }];
258
306
 
259
307
  await sandboxWriteFiles(client, { sandboxId, files, orgId });
@@ -280,7 +328,8 @@ async function uploadDirectory(
280
328
  localDir: string,
281
329
  remotePath: string,
282
330
  _timeout: string | undefined,
283
- jsonOutput: boolean
331
+ jsonOutput: boolean,
332
+ strict: boolean
284
333
  ): Promise<z.infer<typeof SandboxCpResponseSchema>> {
285
334
  const allFiles = getAllFiles(localDir);
286
335
 
@@ -295,6 +344,32 @@ async function uploadDirectory(
295
344
  ? effectiveRemotePath.slice(0, -1)
296
345
  : effectiveRemotePath;
297
346
 
347
+ if (strict) {
348
+ const parentDir = dirname(baseRemotePath);
349
+ const knownDirs = new Set(['/', '/home', '/home/agentuity']);
350
+ if (!knownDirs.has(parentDir)) {
351
+ const checkExecution = await sandboxExecute(client, {
352
+ sandboxId,
353
+ options: {
354
+ command: ['test', '-d', parentDir],
355
+ },
356
+ orgId,
357
+ });
358
+ await waitForExecution(client, orgId, checkExecution.executionId, logger);
359
+ const execInfo = await executionGet(client, {
360
+ executionId: checkExecution.executionId,
361
+ orgId,
362
+ });
363
+ if (execInfo.exitCode !== 0) {
364
+ logger.fatal(
365
+ `Target directory does not exist: ${parentDir}\n` +
366
+ `Use without --strict to auto-create parent directories, or create it first with:\n` +
367
+ ` ${getCommand(`cloud sandbox mkdir ${sandboxId} ${parentDir} -p`)}`
368
+ );
369
+ }
370
+ }
371
+ }
372
+
298
373
  for (const filePath of allFiles) {
299
374
  const relativePath = toForwardSlash(relative(localDir, filePath));
300
375
  const targetPath = `${baseRemotePath}/${relativePath}`;
@@ -0,0 +1,108 @@
1
+ import { sandboxEventList } from '@agentuity/server';
2
+ import { z } from 'zod';
3
+ import { getCommand } from '../../../command-prefix';
4
+ import * as tui from '../../../tui';
5
+ import { createCommand } from '../../../types';
6
+ import { createSandboxClient, getSandboxRegion } from './util';
7
+
8
+ const SandboxEventInfoSchema = z.object({
9
+ eventId: z.string().describe('Event ID'),
10
+ sandboxId: z.string().describe('Sandbox ID'),
11
+ type: z.string().describe('Event type'),
12
+ event: z.record(z.string(), z.unknown()).describe('Event data'),
13
+ createdAt: z.string().describe('Creation timestamp'),
14
+ });
15
+
16
+ const SandboxEventListResponseSchema = z.object({
17
+ events: z.array(SandboxEventInfoSchema).describe('List of events'),
18
+ });
19
+
20
+ export const eventsSubcommand = createCommand({
21
+ name: 'events',
22
+ aliases: ['event'],
23
+ description: 'List events for a sandbox',
24
+ tags: ['read-only', 'fast', 'requires-auth'],
25
+ requires: { auth: true, org: true },
26
+ idempotent: true,
27
+ examples: [
28
+ {
29
+ command: getCommand('cloud sandbox events sbx_abc123'),
30
+ description: 'List events for a sandbox (oldest first)',
31
+ },
32
+ {
33
+ command: getCommand('cloud sandbox events sbx_abc123 --reverse'),
34
+ description: 'List events newest first',
35
+ },
36
+ {
37
+ command: getCommand('cloud sandbox events sbx_abc123 --limit 10'),
38
+ description: 'List events with a limit',
39
+ },
40
+ ],
41
+ schema: {
42
+ args: z.object({
43
+ sandboxId: z.string().describe('Sandbox ID'),
44
+ }),
45
+ options: z.object({
46
+ limit: z.number().optional().describe('Maximum number of results (default: 50, max: 100)'),
47
+ reverse: z.boolean().optional().describe('Reverse sort order (newest first)'),
48
+ orgId: z.string().optional().describe('filter by organization id'),
49
+ }),
50
+ response: SandboxEventListResponseSchema,
51
+ },
52
+
53
+ async handler(ctx) {
54
+ const { args, opts, options, auth, logger, orgId: ctxOrgId, config } = ctx;
55
+ const effectiveOrgId = opts?.orgId || ctxOrgId;
56
+ const region = await getSandboxRegion(
57
+ logger,
58
+ auth,
59
+ config?.name,
60
+ args.sandboxId,
61
+ effectiveOrgId,
62
+ config
63
+ );
64
+ const client = createSandboxClient(logger, auth, region);
65
+
66
+ const result = await sandboxEventList(client, {
67
+ sandboxId: args.sandboxId,
68
+ orgId: effectiveOrgId,
69
+ limit: opts.limit,
70
+ direction: opts.reverse ? 'desc' : undefined,
71
+ });
72
+
73
+ if (!options.json) {
74
+ if (result.events.length === 0) {
75
+ tui.info('No events found');
76
+ } else {
77
+ const tableData = result.events.map((evt) => {
78
+ return {
79
+ ID: evt.eventId,
80
+ Type: evt.type,
81
+ Created: evt.createdAt,
82
+ };
83
+ });
84
+ tui.table(tableData, [
85
+ { name: 'ID', alignment: 'left' },
86
+ { name: 'Type', alignment: 'left' },
87
+ { name: 'Created', alignment: 'left' },
88
+ ]);
89
+
90
+ tui.info(
91
+ `Total: ${result.events.length} ${tui.plural(result.events.length, 'event', 'events')}`
92
+ );
93
+ }
94
+ }
95
+
96
+ return {
97
+ events: result.events.map((e) => ({
98
+ eventId: e.eventId,
99
+ sandboxId: e.sandboxId,
100
+ type: e.type,
101
+ event: e.event,
102
+ createdAt: e.createdAt,
103
+ })),
104
+ };
105
+ },
106
+ });
107
+
108
+ export default eventsSubcommand;
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { Writable } from 'node:stream';
3
+ import { ErrorCode } from '../../../errors';
3
4
  import { createCommand } from '../../../types';
4
5
  import * as tui from '../../../tui';
5
6
  import { createSandboxClient } from './util';
@@ -24,6 +25,14 @@ const SandboxExecResponseSchema = z.object({
24
25
  .optional()
25
26
  .describe('Standard error output (only when separate streams are available)'),
26
27
  output: z.string().optional().describe('Combined stdout/stderr output'),
28
+ outputTruncated: z
29
+ .boolean()
30
+ .optional()
31
+ .describe('Whether the captured output was truncated due to size limits'),
32
+ autoResumed: z
33
+ .boolean()
34
+ .optional()
35
+ .describe('True if the sandbox was automatically resumed from a suspended state'),
27
36
  });
28
37
 
29
38
  export const execSubcommand = createCommand({
@@ -62,6 +71,18 @@ export const execSubcommand = createCommand({
62
71
  async handler(ctx) {
63
72
  const { args, opts, options, auth, logger, apiClient } = ctx;
64
73
 
74
+ // Validate timeout format if provided (fail fast before any network calls)
75
+ if (opts.timeout) {
76
+ // Go's time.ParseDuration accepts "0" or one-or-more number+unit tokens.
77
+ // Valid units: ns, us, µs (U+00B5), μs (U+03BC), ms, s, m, h
78
+ if (!/^(?:0|(\d+(\.\d+)?(ns|us|[µμ]s|ms|s|m|h))+)$/.test(opts.timeout)) {
79
+ tui.fatal(
80
+ `Invalid timeout format '${opts.timeout}': expected duration like '5s', '1m', '1h', '300ms'`,
81
+ ErrorCode.INVALID_ARGUMENT
82
+ );
83
+ }
84
+ }
85
+
65
86
  // Resolve sandbox to get region and orgId using CLI API
66
87
  const sandboxInfo = await sandboxResolve(apiClient, args.sandboxId);
67
88
  const { region, orgId } = sandboxInfo;
@@ -87,6 +108,10 @@ export const execSubcommand = createCommand({
87
108
  orgId,
88
109
  });
89
110
 
111
+ if (execution.autoResumed && !options.json) {
112
+ tui.warning('Sandbox was automatically resumed from suspended state');
113
+ }
114
+
90
115
  const stdoutStreamUrl = execution.stdoutStreamUrl;
91
116
  const stderrStreamUrl = execution.stderrStreamUrl;
92
117
  const streamAbortController = new AbortController();
@@ -213,6 +238,8 @@ export const execSubcommand = createCommand({
213
238
  stdout: options.json ? stdoutOutput : undefined,
214
239
  stderr: options.json ? stderrOutput : undefined,
215
240
  output: options.json ? output : undefined,
241
+ outputTruncated: finalExecution.outputTruncated ?? undefined,
242
+ autoResumed: execution.autoResumed ?? undefined,
216
243
  };
217
244
  } finally {
218
245
  process.off('SIGINT', handleSignal);
@@ -17,6 +17,10 @@ const ExecutionGetResponseSchema = z.object({
17
17
  error: z.string().optional().describe('Error message if failed'),
18
18
  stdoutStreamUrl: z.string().optional().describe('URL to stream stdout'),
19
19
  stderrStreamUrl: z.string().optional().describe('URL to stream stderr'),
20
+ outputTruncated: z
21
+ .boolean()
22
+ .optional()
23
+ .describe('Whether the captured output was truncated due to size limits'),
20
24
  });
21
25
 
22
26
  export const getSubcommand = createCommand({
@@ -108,6 +112,7 @@ export const getSubcommand = createCommand({
108
112
  error: result.error,
109
113
  stdoutStreamUrl: result.stdoutStreamUrl,
110
114
  stderrStreamUrl: result.stderrStreamUrl,
115
+ outputTruncated: result.outputTruncated,
111
116
  };
112
117
  },
113
118
  });
@@ -1,9 +1,9 @@
1
- import { executionList } from '@agentuity/server';
1
+ import { executionList, sandboxResolve } from '@agentuity/server';
2
2
  import { z } from 'zod';
3
3
  import { getCommand } from '../../../../command-prefix';
4
4
  import * as tui from '../../../../tui';
5
5
  import { createCommand } from '../../../../types';
6
- import { createSandboxClient, getSandboxRegion } from '../util';
6
+ import { createSandboxClient } from '../util';
7
7
 
8
8
  const ExecutionInfoSchema = z.object({
9
9
  executionId: z.string().describe('Execution ID'),
@@ -25,7 +25,7 @@ export const listSubcommand = createCommand({
25
25
  aliases: ['ls'],
26
26
  description: 'List executions for a sandbox',
27
27
  tags: ['read-only', 'fast', 'requires-auth'],
28
- requires: { auth: true, org: true },
28
+ requires: { auth: true, apiClient: true },
29
29
  idempotent: true,
30
30
  examples: [
31
31
  {
@@ -43,22 +43,21 @@ export const listSubcommand = createCommand({
43
43
  }),
44
44
  options: z.object({
45
45
  limit: z.number().optional().describe('Maximum number of results (default: 50, max: 100)'),
46
- orgId: z.string().optional().describe('filter by organization id'),
46
+ orgId: z
47
+ .string()
48
+ .optional()
49
+ .describe('Override organization ID (default: resolved from sandbox)'),
47
50
  }),
48
51
  response: ExecutionListResponseSchema,
49
52
  },
50
53
 
51
54
  async handler(ctx) {
52
- const { args, opts, options, auth, logger, orgId: ctxOrgId, config } = ctx;
53
- const effectiveOrgId = opts?.orgId || ctxOrgId;
54
- const region = await getSandboxRegion(
55
- logger,
56
- auth,
57
- config?.name,
58
- args.sandboxId,
59
- effectiveOrgId,
60
- config
61
- );
55
+ const { args, opts, options, auth, logger, apiClient } = ctx;
56
+
57
+ // Resolve sandbox to get region and orgId (like exec.ts does)
58
+ const sandboxInfo = await sandboxResolve(apiClient, args.sandboxId);
59
+ const { region, orgId: resolvedOrgId } = sandboxInfo;
60
+ const effectiveOrgId = opts?.orgId || resolvedOrgId;
62
61
  const client = createSandboxClient(logger, auth, region);
63
62
 
64
63
  const result = await executionList(client, {
@@ -79,6 +79,7 @@ export const getSubcommand = createCommand({
79
79
  const result = await sandboxGet(client, {
80
80
  sandboxId: args.sandboxId,
81
81
  orgId: sandboxInfo.orgId,
82
+ includeDeleted: true,
82
83
  });
83
84
 
84
85
  // Cache the region for future lookups
@@ -20,6 +20,7 @@ import { pauseSubcommand } from './pause';
20
20
  import { resumeSubcommand } from './resume';
21
21
  import { checkpointCommand } from './checkpoint';
22
22
  import { statsSubcommand } from './stats';
23
+ import { eventsSubcommand } from './events';
23
24
  import { getCommand } from '../../../command-prefix';
24
25
 
25
26
  export const command = createCommand({
@@ -63,6 +64,7 @@ export const command = createCommand({
63
64
  resumeSubcommand,
64
65
  checkpointCommand,
65
66
  statsSubcommand,
67
+ eventsSubcommand,
66
68
  ],
67
69
  requires: { auth: true, org: true },
68
70
  });
@@ -41,7 +41,7 @@ export async function getSandboxRegion(
41
41
  config
42
42
  );
43
43
 
44
- const sandbox = await sandboxGet(globalClient, { sandboxId, orgId });
44
+ const sandbox = await sandboxGet(globalClient, { sandboxId, orgId, includeDeleted: true });
45
45
  if (!sandbox.region) {
46
46
  tui.fatal(`Sandbox '${sandboxId}' has no region information`, ErrorCode.RESOURCE_NOT_FOUND);
47
47
  }