@agentuity/cli 0.0.105 → 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.
Files changed (204) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +93 -21
  3. package/dist/cli.js.map +1 -1
  4. package/dist/cmd/build/ast.d.ts.map +1 -1
  5. package/dist/cmd/build/ast.js +130 -4
  6. package/dist/cmd/build/ast.js.map +1 -1
  7. package/dist/cmd/build/entry-generator.d.ts.map +1 -1
  8. package/dist/cmd/build/entry-generator.js +8 -2
  9. package/dist/cmd/build/entry-generator.js.map +1 -1
  10. package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
  11. package/dist/cmd/build/vite/registry-generator.js +8 -9
  12. package/dist/cmd/build/vite/registry-generator.js.map +1 -1
  13. package/dist/cmd/cloud/db/create.d.ts.map +1 -1
  14. package/dist/cmd/cloud/db/create.js +11 -2
  15. package/dist/cmd/cloud/db/create.js.map +1 -1
  16. package/dist/cmd/cloud/db/delete.d.ts.map +1 -1
  17. package/dist/cmd/cloud/db/delete.js +13 -2
  18. package/dist/cmd/cloud/db/delete.js.map +1 -1
  19. package/dist/cmd/cloud/deploy.js +3 -3
  20. package/dist/cmd/cloud/deploy.js.map +1 -1
  21. package/dist/cmd/cloud/env/delete.js +1 -1
  22. package/dist/cmd/cloud/env/delete.js.map +1 -1
  23. package/dist/cmd/cloud/env/import.js +4 -4
  24. package/dist/cmd/cloud/env/import.js.map +1 -1
  25. package/dist/cmd/cloud/env/pull.d.ts.map +1 -1
  26. package/dist/cmd/cloud/env/pull.js +7 -9
  27. package/dist/cmd/cloud/env/pull.js.map +1 -1
  28. package/dist/cmd/cloud/env/push.js +2 -2
  29. package/dist/cmd/cloud/env/push.js.map +1 -1
  30. package/dist/cmd/cloud/env/set.js +3 -3
  31. package/dist/cmd/cloud/env/set.js.map +1 -1
  32. package/dist/cmd/cloud/index.d.ts.map +1 -1
  33. package/dist/cmd/cloud/index.js +2 -0
  34. package/dist/cmd/cloud/index.js.map +1 -1
  35. package/dist/cmd/cloud/sandbox/cp.d.ts +3 -0
  36. package/dist/cmd/cloud/sandbox/cp.d.ts.map +1 -0
  37. package/dist/cmd/cloud/sandbox/cp.js +334 -0
  38. package/dist/cmd/cloud/sandbox/cp.js.map +1 -0
  39. package/dist/cmd/cloud/sandbox/create.d.ts +3 -0
  40. package/dist/cmd/cloud/sandbox/create.d.ts.map +1 -0
  41. package/dist/cmd/cloud/sandbox/create.js +105 -0
  42. package/dist/cmd/cloud/sandbox/create.js.map +1 -0
  43. package/dist/cmd/cloud/sandbox/delete.d.ts +3 -0
  44. package/dist/cmd/cloud/sandbox/delete.d.ts.map +1 -0
  45. package/dist/cmd/cloud/sandbox/delete.js +72 -0
  46. package/dist/cmd/cloud/sandbox/delete.js.map +1 -0
  47. package/dist/cmd/cloud/sandbox/exec.d.ts +3 -0
  48. package/dist/cmd/cloud/sandbox/exec.d.ts.map +1 -0
  49. package/dist/cmd/cloud/sandbox/exec.js +211 -0
  50. package/dist/cmd/cloud/sandbox/exec.js.map +1 -0
  51. package/dist/cmd/cloud/sandbox/execution/get.d.ts +3 -0
  52. package/dist/cmd/cloud/sandbox/execution/get.d.ts.map +1 -0
  53. package/dist/cmd/cloud/sandbox/execution/get.js +96 -0
  54. package/dist/cmd/cloud/sandbox/execution/get.js.map +1 -0
  55. package/dist/cmd/cloud/sandbox/execution/index.d.ts +3 -0
  56. package/dist/cmd/cloud/sandbox/execution/index.d.ts.map +1 -0
  57. package/dist/cmd/cloud/sandbox/execution/index.js +24 -0
  58. package/dist/cmd/cloud/sandbox/execution/index.js.map +1 -0
  59. package/dist/cmd/cloud/sandbox/execution/list.d.ts +3 -0
  60. package/dist/cmd/cloud/sandbox/execution/list.d.ts.map +1 -0
  61. package/dist/cmd/cloud/sandbox/execution/list.js +100 -0
  62. package/dist/cmd/cloud/sandbox/execution/list.js.map +1 -0
  63. package/dist/cmd/cloud/sandbox/get.d.ts +3 -0
  64. package/dist/cmd/cloud/sandbox/get.d.ts.map +1 -0
  65. package/dist/cmd/cloud/sandbox/get.js +95 -0
  66. package/dist/cmd/cloud/sandbox/get.js.map +1 -0
  67. package/dist/cmd/cloud/sandbox/index.d.ts +3 -0
  68. package/dist/cmd/cloud/sandbox/index.d.ts.map +1 -0
  69. package/dist/cmd/cloud/sandbox/index.js +45 -0
  70. package/dist/cmd/cloud/sandbox/index.js.map +1 -0
  71. package/dist/cmd/cloud/sandbox/list.d.ts +3 -0
  72. package/dist/cmd/cloud/sandbox/list.d.ts.map +1 -0
  73. package/dist/cmd/cloud/sandbox/list.js +120 -0
  74. package/dist/cmd/cloud/sandbox/list.js.map +1 -0
  75. package/dist/cmd/cloud/sandbox/run.d.ts +3 -0
  76. package/dist/cmd/cloud/sandbox/run.d.ts.map +1 -0
  77. package/dist/cmd/cloud/sandbox/run.js +152 -0
  78. package/dist/cmd/cloud/sandbox/run.js.map +1 -0
  79. package/dist/cmd/cloud/sandbox/snapshot/create.d.ts +3 -0
  80. package/dist/cmd/cloud/sandbox/snapshot/create.d.ts.map +1 -0
  81. package/dist/cmd/cloud/sandbox/snapshot/create.js +65 -0
  82. package/dist/cmd/cloud/sandbox/snapshot/create.js.map +1 -0
  83. package/dist/cmd/cloud/sandbox/snapshot/delete.d.ts +3 -0
  84. package/dist/cmd/cloud/sandbox/snapshot/delete.d.ts.map +1 -0
  85. package/dist/cmd/cloud/sandbox/snapshot/delete.js +66 -0
  86. package/dist/cmd/cloud/sandbox/snapshot/delete.js.map +1 -0
  87. package/dist/cmd/cloud/sandbox/snapshot/get.d.ts +3 -0
  88. package/dist/cmd/cloud/sandbox/snapshot/get.d.ts.map +1 -0
  89. package/dist/cmd/cloud/sandbox/snapshot/get.js +154 -0
  90. package/dist/cmd/cloud/sandbox/snapshot/get.js.map +1 -0
  91. package/dist/cmd/cloud/sandbox/snapshot/index.d.ts +3 -0
  92. package/dist/cmd/cloud/sandbox/snapshot/index.d.ts.map +1 -0
  93. package/dist/cmd/cloud/sandbox/snapshot/index.js +27 -0
  94. package/dist/cmd/cloud/sandbox/snapshot/index.js.map +1 -0
  95. package/dist/cmd/cloud/sandbox/snapshot/list.d.ts +3 -0
  96. package/dist/cmd/cloud/sandbox/snapshot/list.d.ts.map +1 -0
  97. package/dist/cmd/cloud/sandbox/snapshot/list.js +83 -0
  98. package/dist/cmd/cloud/sandbox/snapshot/list.js.map +1 -0
  99. package/dist/cmd/cloud/sandbox/snapshot/tag.d.ts +3 -0
  100. package/dist/cmd/cloud/sandbox/snapshot/tag.d.ts.map +1 -0
  101. package/dist/cmd/cloud/sandbox/snapshot/tag.js +63 -0
  102. package/dist/cmd/cloud/sandbox/snapshot/tag.js.map +1 -0
  103. package/dist/cmd/cloud/sandbox/util.d.ts +15 -0
  104. package/dist/cmd/cloud/sandbox/util.d.ts.map +1 -0
  105. package/dist/cmd/cloud/sandbox/util.js +50 -0
  106. package/dist/cmd/cloud/sandbox/util.js.map +1 -0
  107. package/dist/cmd/cloud/secret/delete.d.ts.map +1 -1
  108. package/dist/cmd/cloud/secret/delete.js +3 -3
  109. package/dist/cmd/cloud/secret/delete.js.map +1 -1
  110. package/dist/cmd/cloud/secret/import.js +6 -6
  111. package/dist/cmd/cloud/secret/import.js.map +1 -1
  112. package/dist/cmd/cloud/secret/index.d.ts.map +1 -1
  113. package/dist/cmd/cloud/secret/index.js +1 -0
  114. package/dist/cmd/cloud/secret/index.js.map +1 -1
  115. package/dist/cmd/cloud/secret/pull.d.ts.map +1 -1
  116. package/dist/cmd/cloud/secret/pull.js +7 -9
  117. package/dist/cmd/cloud/secret/pull.js.map +1 -1
  118. package/dist/cmd/cloud/secret/push.js +3 -3
  119. package/dist/cmd/cloud/secret/push.js.map +1 -1
  120. package/dist/cmd/cloud/secret/set.d.ts.map +1 -1
  121. package/dist/cmd/cloud/secret/set.js +3 -3
  122. package/dist/cmd/cloud/secret/set.js.map +1 -1
  123. package/dist/cmd/cloud/storage/create.d.ts.map +1 -1
  124. package/dist/cmd/cloud/storage/create.js +13 -2
  125. package/dist/cmd/cloud/storage/create.js.map +1 -1
  126. package/dist/cmd/cloud/storage/delete.d.ts.map +1 -1
  127. package/dist/cmd/cloud/storage/delete.js +13 -2
  128. package/dist/cmd/cloud/storage/delete.js.map +1 -1
  129. package/dist/cmd/cloud/stream/list.d.ts.map +1 -1
  130. package/dist/cmd/cloud/stream/list.js +2 -13
  131. package/dist/cmd/cloud/stream/list.js.map +1 -1
  132. package/dist/cmd/dev/index.d.ts.map +1 -1
  133. package/dist/cmd/dev/index.js +1 -0
  134. package/dist/cmd/dev/index.js.map +1 -1
  135. package/dist/cmd/profile/create.d.ts.map +1 -1
  136. package/dist/cmd/profile/create.js +1 -0
  137. package/dist/cmd/profile/create.js.map +1 -1
  138. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  139. package/dist/cmd/project/template-flow.js +27 -10
  140. package/dist/cmd/project/template-flow.js.map +1 -1
  141. package/dist/config.d.ts +0 -2
  142. package/dist/config.d.ts.map +1 -1
  143. package/dist/config.js +3 -0
  144. package/dist/config.js.map +1 -1
  145. package/dist/env-util.d.ts +16 -8
  146. package/dist/env-util.d.ts.map +1 -1
  147. package/dist/env-util.js +46 -18
  148. package/dist/env-util.js.map +1 -1
  149. package/dist/tui.d.ts +20 -3
  150. package/dist/tui.d.ts.map +1 -1
  151. package/dist/tui.js +82 -23
  152. package/dist/tui.js.map +1 -1
  153. package/dist/types.d.ts +18 -4
  154. package/dist/types.d.ts.map +1 -1
  155. package/dist/types.js +1 -0
  156. package/dist/types.js.map +1 -1
  157. package/package.json +4 -4
  158. package/src/cli.ts +99 -21
  159. package/src/cmd/build/ast.ts +163 -4
  160. package/src/cmd/build/entry-generator.ts +8 -2
  161. package/src/cmd/build/vite/registry-generator.ts +8 -11
  162. package/src/cmd/cloud/db/create.ts +13 -2
  163. package/src/cmd/cloud/db/delete.ts +15 -2
  164. package/src/cmd/cloud/deploy.ts +3 -3
  165. package/src/cmd/cloud/env/delete.ts +1 -1
  166. package/src/cmd/cloud/env/import.ts +4 -4
  167. package/src/cmd/cloud/env/pull.ts +7 -16
  168. package/src/cmd/cloud/env/push.ts +2 -2
  169. package/src/cmd/cloud/env/set.ts +3 -3
  170. package/src/cmd/cloud/index.ts +2 -0
  171. package/src/cmd/cloud/sandbox/cp.ts +531 -0
  172. package/src/cmd/cloud/sandbox/create.ts +114 -0
  173. package/src/cmd/cloud/sandbox/delete.ts +80 -0
  174. package/src/cmd/cloud/sandbox/exec.ts +254 -0
  175. package/src/cmd/cloud/sandbox/execution/get.ts +106 -0
  176. package/src/cmd/cloud/sandbox/execution/index.ts +25 -0
  177. package/src/cmd/cloud/sandbox/execution/list.ts +111 -0
  178. package/src/cmd/cloud/sandbox/get.ts +104 -0
  179. package/src/cmd/cloud/sandbox/index.ts +46 -0
  180. package/src/cmd/cloud/sandbox/list.ts +129 -0
  181. package/src/cmd/cloud/sandbox/run.ts +170 -0
  182. package/src/cmd/cloud/sandbox/snapshot/create.ts +71 -0
  183. package/src/cmd/cloud/sandbox/snapshot/delete.ts +74 -0
  184. package/src/cmd/cloud/sandbox/snapshot/get.ts +188 -0
  185. package/src/cmd/cloud/sandbox/snapshot/index.ts +28 -0
  186. package/src/cmd/cloud/sandbox/snapshot/list.ts +90 -0
  187. package/src/cmd/cloud/sandbox/snapshot/tag.ts +70 -0
  188. package/src/cmd/cloud/sandbox/util.ts +59 -0
  189. package/src/cmd/cloud/secret/delete.ts +8 -3
  190. package/src/cmd/cloud/secret/import.ts +6 -6
  191. package/src/cmd/cloud/secret/index.ts +1 -0
  192. package/src/cmd/cloud/secret/pull.ts +7 -16
  193. package/src/cmd/cloud/secret/push.ts +3 -3
  194. package/src/cmd/cloud/secret/set.ts +8 -3
  195. package/src/cmd/cloud/storage/create.ts +15 -2
  196. package/src/cmd/cloud/storage/delete.ts +15 -2
  197. package/src/cmd/cloud/stream/list.ts +2 -9
  198. package/src/cmd/dev/index.ts +1 -0
  199. package/src/cmd/profile/create.ts +1 -0
  200. package/src/cmd/project/template-flow.ts +29 -13
  201. package/src/config.ts +3 -0
  202. package/src/env-util.ts +52 -21
  203. package/src/tui.ts +131 -39
  204. package/src/types.ts +18 -16
@@ -3,13 +3,7 @@ import { join } from 'node:path';
3
3
  import { createSubcommand } from '../../../types';
4
4
  import * as tui from '../../../tui';
5
5
  import { projectGet } from '@agentuity/server';
6
- import {
7
- findEnvFile,
8
- findExistingEnvFile,
9
- readEnvFile,
10
- writeEnvFile,
11
- mergeEnvVars,
12
- } from '../../../env-util';
6
+ import { findExistingEnvFile, readEnvFile, writeEnvFile, mergeEnvVars } from '../../../env-util';
13
7
  import { getCommand } from '../../../command-prefix';
14
8
 
15
9
  const EnvPullResponseSchema = z.object({
@@ -21,7 +15,7 @@ const EnvPullResponseSchema = z.object({
21
15
 
22
16
  export const pullSubcommand = createSubcommand({
23
17
  name: 'pull',
24
- description: 'Pull environment variables from cloud to local .env.production file',
18
+ description: 'Pull environment variables from cloud to local .env file',
25
19
  tags: ['slow', 'requires-auth', 'requires-project'],
26
20
  idempotent: true,
27
21
  examples: [
@@ -45,14 +39,11 @@ export const pullSubcommand = createSubcommand({
45
39
  return projectGet(apiClient, { id: project.projectId, mask: false });
46
40
  });
47
41
 
48
- const cloudEnv = projectData.env || {};
42
+ const cloudEnv = { ...projectData.env, ...projectData.secrets }; // env pull with actually do both secrets and env since thats likely what the user would want
49
43
 
50
- // Read current local env from existing file (.env.production or .env)
51
- const existingEnvPath = await findExistingEnvFile(projectDir);
52
- const localEnv = await readEnvFile(existingEnvPath);
53
-
54
- // Target file is always .env.production
55
- const targetEnvPath = await findEnvFile(projectDir);
44
+ // Target file is always .env
45
+ const targetEnvPath = await findExistingEnvFile(projectDir);
46
+ const localEnv = await readEnvFile(targetEnvPath);
56
47
 
57
48
  // Merge: cloud values override local if force=true, otherwise keep local
58
49
  let mergedEnv: Record<string, string>;
@@ -64,7 +55,7 @@ export const pullSubcommand = createSubcommand({
64
55
  mergedEnv = mergeEnvVars(cloudEnv, localEnv);
65
56
  }
66
57
 
67
- // Write to .env.production (skip AGENTUITY_ keys)
58
+ // Write to .env (skip AGENTUITY_ keys)
68
59
  await writeEnvFile(targetEnvPath, mergedEnv, {
69
60
  skipKeys: Object.keys(mergedEnv).filter((k) => k.startsWith('AGENTUITY_')),
70
61
  });
@@ -13,7 +13,7 @@ const EnvPushResponseSchema = z.object({
13
13
 
14
14
  export const pushSubcommand = createSubcommand({
15
15
  name: 'push',
16
- description: 'Push environment variables from local .env.production file to cloud',
16
+ description: 'Push environment variables from local .env file to cloud',
17
17
  tags: [
18
18
  'mutating',
19
19
  'updates-resource',
@@ -33,7 +33,7 @@ export const pushSubcommand = createSubcommand({
33
33
  async handler(ctx) {
34
34
  const { apiClient, project, projectDir } = ctx;
35
35
 
36
- // Read local env file (prefer .env.production, fallback to .env)
36
+ // Read local env file (prefer .env)
37
37
  const envFilePath = await findExistingEnvFile(projectDir);
38
38
  const localEnv = await readEnvFile(envFilePath);
39
39
 
@@ -3,7 +3,7 @@ import { createSubcommand } from '../../../types';
3
3
  import * as tui from '../../../tui';
4
4
  import { projectEnvUpdate } from '@agentuity/server';
5
5
  import {
6
- findEnvFile,
6
+ findExistingEnvFile,
7
7
  readEnvFile,
8
8
  writeEnvFile,
9
9
  filterAgentuitySdkKeys,
@@ -75,8 +75,8 @@ export const setSubcommand = createSubcommand({
75
75
  });
76
76
  });
77
77
 
78
- // Update local .env.production file
79
- const envFilePath = await findEnvFile(projectDir);
78
+ // Update local .env file
79
+ const envFilePath = await findExistingEnvFile(projectDir);
80
80
  const currentEnv = await readEnvFile(envFilePath);
81
81
  currentEnv[args.key] = args.value;
82
82
 
@@ -15,6 +15,7 @@ import secretCommand from './secret';
15
15
  import apikeyCommand from './apikey';
16
16
  import streamCommand from './stream';
17
17
  import vectorCommand from './vector';
18
+ import sandboxCommand from './sandbox';
18
19
  import { getCommand } from '../../command-prefix';
19
20
 
20
21
  export const command = createCommand({
@@ -31,6 +32,7 @@ export const command = createCommand({
31
32
  agentCommand,
32
33
  streamCommand,
33
34
  vectorCommand,
35
+ sandboxCommand,
34
36
  envCommand,
35
37
  secretCommand,
36
38
  deploySubcommand,
@@ -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;