@agentuity/cli 0.0.42 → 0.0.44

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 (249) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +1 -1
  3. package/bin/cli.ts +7 -5
  4. package/dist/api.d.ts +3 -3
  5. package/dist/api.d.ts.map +1 -1
  6. package/dist/auth.d.ts +10 -2
  7. package/dist/auth.d.ts.map +1 -1
  8. package/dist/banner.d.ts.map +1 -1
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cmd/auth/api.d.ts +4 -4
  11. package/dist/cmd/auth/api.d.ts.map +1 -1
  12. package/dist/cmd/auth/index.d.ts.map +1 -1
  13. package/dist/cmd/auth/login.d.ts.map +1 -1
  14. package/dist/cmd/auth/signup.d.ts.map +1 -1
  15. package/dist/cmd/auth/ssh/add.d.ts +2 -0
  16. package/dist/cmd/auth/ssh/add.d.ts.map +1 -0
  17. package/dist/cmd/auth/ssh/api.d.ts +16 -0
  18. package/dist/cmd/auth/ssh/api.d.ts.map +1 -0
  19. package/dist/cmd/auth/ssh/delete.d.ts +2 -0
  20. package/dist/cmd/auth/ssh/delete.d.ts.map +1 -0
  21. package/dist/cmd/auth/ssh/index.d.ts +3 -0
  22. package/dist/cmd/auth/ssh/index.d.ts.map +1 -0
  23. package/dist/cmd/auth/ssh/list.d.ts +2 -0
  24. package/dist/cmd/auth/ssh/list.d.ts.map +1 -0
  25. package/dist/cmd/auth/whoami.d.ts +2 -0
  26. package/dist/cmd/auth/whoami.d.ts.map +1 -0
  27. package/dist/cmd/bundle/ast.d.ts +14 -3
  28. package/dist/cmd/bundle/ast.d.ts.map +1 -1
  29. package/dist/cmd/bundle/ast.test.d.ts +2 -0
  30. package/dist/cmd/bundle/ast.test.d.ts.map +1 -0
  31. package/dist/cmd/bundle/bundler.d.ts +6 -1
  32. package/dist/cmd/bundle/bundler.d.ts.map +1 -1
  33. package/dist/cmd/bundle/file.d.ts.map +1 -1
  34. package/dist/cmd/bundle/fix-duplicate-exports.d.ts +2 -0
  35. package/dist/cmd/bundle/fix-duplicate-exports.d.ts.map +1 -0
  36. package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts +2 -0
  37. package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts.map +1 -0
  38. package/dist/cmd/bundle/index.d.ts +1 -1
  39. package/dist/cmd/bundle/index.d.ts.map +1 -1
  40. package/dist/cmd/bundle/plugin.d.ts +2 -0
  41. package/dist/cmd/bundle/plugin.d.ts.map +1 -1
  42. package/dist/cmd/cloud/deploy.d.ts.map +1 -0
  43. package/dist/cmd/cloud/domain.d.ts +17 -0
  44. package/dist/cmd/cloud/domain.d.ts.map +1 -0
  45. package/dist/cmd/cloud/index.d.ts.map +1 -0
  46. package/dist/cmd/cloud/resource/add.d.ts +2 -0
  47. package/dist/cmd/cloud/resource/add.d.ts.map +1 -0
  48. package/dist/cmd/cloud/resource/delete.d.ts +2 -0
  49. package/dist/cmd/cloud/resource/delete.d.ts.map +1 -0
  50. package/dist/cmd/cloud/resource/index.d.ts +3 -0
  51. package/dist/cmd/cloud/resource/index.d.ts.map +1 -0
  52. package/dist/cmd/cloud/resource/list.d.ts +2 -0
  53. package/dist/cmd/cloud/resource/list.d.ts.map +1 -0
  54. package/dist/cmd/cloud/scp/download.d.ts +2 -0
  55. package/dist/cmd/cloud/scp/download.d.ts.map +1 -0
  56. package/dist/cmd/cloud/scp/index.d.ts +3 -0
  57. package/dist/cmd/cloud/scp/index.d.ts.map +1 -0
  58. package/dist/cmd/cloud/scp/upload.d.ts +2 -0
  59. package/dist/cmd/cloud/scp/upload.d.ts.map +1 -0
  60. package/dist/cmd/cloud/ssh.d.ts +2 -0
  61. package/dist/cmd/cloud/ssh.d.ts.map +1 -0
  62. package/dist/cmd/dev/api.d.ts +18 -0
  63. package/dist/cmd/dev/api.d.ts.map +1 -0
  64. package/dist/cmd/dev/download.d.ts +11 -0
  65. package/dist/cmd/dev/download.d.ts.map +1 -0
  66. package/dist/cmd/dev/index.d.ts.map +1 -1
  67. package/dist/cmd/dev/templates.d.ts +3 -0
  68. package/dist/cmd/dev/templates.d.ts.map +1 -0
  69. package/dist/cmd/env/delete.d.ts +2 -0
  70. package/dist/cmd/env/delete.d.ts.map +1 -0
  71. package/dist/cmd/env/get.d.ts +2 -0
  72. package/dist/cmd/env/get.d.ts.map +1 -0
  73. package/dist/cmd/env/import.d.ts +2 -0
  74. package/dist/cmd/env/import.d.ts.map +1 -0
  75. package/dist/cmd/env/index.d.ts +2 -0
  76. package/dist/cmd/env/index.d.ts.map +1 -0
  77. package/dist/cmd/env/list.d.ts.map +1 -0
  78. package/dist/cmd/env/pull.d.ts +2 -0
  79. package/dist/cmd/env/pull.d.ts.map +1 -0
  80. package/dist/cmd/env/push.d.ts +2 -0
  81. package/dist/cmd/env/push.d.ts.map +1 -0
  82. package/dist/cmd/env/set.d.ts +2 -0
  83. package/dist/cmd/env/set.d.ts.map +1 -0
  84. package/dist/cmd/profile/show.d.ts.map +1 -1
  85. package/dist/cmd/project/create.d.ts.map +1 -1
  86. package/dist/cmd/project/delete.d.ts.map +1 -1
  87. package/dist/cmd/project/download.d.ts +1 -1
  88. package/dist/cmd/project/download.d.ts.map +1 -1
  89. package/dist/cmd/project/list.d.ts.map +1 -1
  90. package/dist/cmd/project/show.d.ts.map +1 -1
  91. package/dist/cmd/project/template-flow.d.ts +5 -1
  92. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  93. package/dist/cmd/secret/delete.d.ts +2 -0
  94. package/dist/cmd/secret/delete.d.ts.map +1 -0
  95. package/dist/cmd/secret/get.d.ts +2 -0
  96. package/dist/cmd/secret/get.d.ts.map +1 -0
  97. package/dist/cmd/secret/import.d.ts +2 -0
  98. package/dist/cmd/secret/import.d.ts.map +1 -0
  99. package/dist/cmd/secret/index.d.ts +2 -0
  100. package/dist/cmd/secret/index.d.ts.map +1 -0
  101. package/dist/cmd/secret/list.d.ts +2 -0
  102. package/dist/cmd/secret/list.d.ts.map +1 -0
  103. package/dist/cmd/secret/pull.d.ts +2 -0
  104. package/dist/cmd/secret/pull.d.ts.map +1 -0
  105. package/dist/cmd/secret/push.d.ts +2 -0
  106. package/dist/cmd/secret/push.d.ts.map +1 -0
  107. package/dist/cmd/secret/set.d.ts +2 -0
  108. package/dist/cmd/secret/set.d.ts.map +1 -0
  109. package/dist/cmd/version/index.d.ts.map +1 -1
  110. package/dist/config.d.ts +11 -3
  111. package/dist/config.d.ts.map +1 -1
  112. package/dist/crypto/box.d.ts +65 -0
  113. package/dist/crypto/box.d.ts.map +1 -0
  114. package/dist/crypto/box.test.d.ts +2 -0
  115. package/dist/crypto/box.test.d.ts.map +1 -0
  116. package/dist/download.d.ts.map +1 -1
  117. package/dist/env-util.d.ts +67 -0
  118. package/dist/env-util.d.ts.map +1 -0
  119. package/dist/env-util.test.d.ts +2 -0
  120. package/dist/env-util.test.d.ts.map +1 -0
  121. package/dist/index.d.ts +1 -1
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/schema-parser.d.ts.map +1 -1
  124. package/dist/steps.d.ts +4 -1
  125. package/dist/steps.d.ts.map +1 -1
  126. package/dist/terminal.d.ts.map +1 -1
  127. package/dist/tui.d.ts +32 -2
  128. package/dist/tui.d.ts.map +1 -1
  129. package/dist/types.d.ts +250 -127
  130. package/dist/types.d.ts.map +1 -1
  131. package/dist/utils/detectSubagent.d.ts +15 -0
  132. package/dist/utils/detectSubagent.d.ts.map +1 -0
  133. package/dist/utils/zip.d.ts +7 -0
  134. package/dist/utils/zip.d.ts.map +1 -0
  135. package/package.json +11 -3
  136. package/src/api-errors.md +2 -2
  137. package/src/api.ts +12 -7
  138. package/src/auth.ts +116 -7
  139. package/src/banner.ts +13 -6
  140. package/src/cli.ts +709 -36
  141. package/src/cmd/auth/api.ts +10 -16
  142. package/src/cmd/auth/index.ts +3 -1
  143. package/src/cmd/auth/login.ts +24 -8
  144. package/src/cmd/auth/signup.ts +15 -11
  145. package/src/cmd/auth/ssh/add.ts +263 -0
  146. package/src/cmd/auth/ssh/api.ts +94 -0
  147. package/src/cmd/auth/ssh/delete.ts +102 -0
  148. package/src/cmd/auth/ssh/index.ts +10 -0
  149. package/src/cmd/auth/ssh/list.ts +74 -0
  150. package/src/cmd/auth/whoami.ts +69 -0
  151. package/src/cmd/bundle/ast.test.ts +565 -0
  152. package/src/cmd/bundle/ast.ts +457 -44
  153. package/src/cmd/bundle/bundler.ts +255 -57
  154. package/src/cmd/bundle/file.ts +6 -12
  155. package/src/cmd/bundle/fix-duplicate-exports.test.ts +387 -0
  156. package/src/cmd/bundle/fix-duplicate-exports.ts +204 -0
  157. package/src/cmd/bundle/index.ts +11 -11
  158. package/src/cmd/bundle/patch/aisdk.ts +1 -1
  159. package/src/cmd/bundle/plugin.ts +373 -53
  160. package/src/cmd/cloud/deploy.ts +336 -0
  161. package/src/cmd/cloud/domain.ts +92 -0
  162. package/src/cmd/cloud/index.ts +11 -0
  163. package/src/cmd/cloud/resource/add.ts +56 -0
  164. package/src/cmd/cloud/resource/delete.ts +120 -0
  165. package/src/cmd/cloud/resource/index.ts +11 -0
  166. package/src/cmd/cloud/resource/list.ts +69 -0
  167. package/src/cmd/cloud/scp/download.ts +59 -0
  168. package/src/cmd/cloud/scp/index.ts +9 -0
  169. package/src/cmd/cloud/scp/upload.ts +62 -0
  170. package/src/cmd/cloud/ssh.ts +68 -0
  171. package/src/cmd/dev/api.ts +46 -0
  172. package/src/cmd/dev/download.ts +111 -0
  173. package/src/cmd/dev/index.ts +362 -34
  174. package/src/cmd/dev/templates.ts +84 -0
  175. package/src/cmd/env/delete.ts +47 -0
  176. package/src/cmd/env/get.ts +53 -0
  177. package/src/cmd/env/import.ts +102 -0
  178. package/src/cmd/env/index.ts +22 -0
  179. package/src/cmd/env/list.ts +56 -0
  180. package/src/cmd/env/pull.ts +80 -0
  181. package/src/cmd/env/push.ts +37 -0
  182. package/src/cmd/env/set.ts +71 -0
  183. package/src/cmd/index.ts +2 -2
  184. package/src/cmd/profile/show.ts +15 -6
  185. package/src/cmd/project/create.ts +7 -2
  186. package/src/cmd/project/delete.ts +75 -18
  187. package/src/cmd/project/download.ts +3 -3
  188. package/src/cmd/project/list.ts +8 -8
  189. package/src/cmd/project/show.ts +3 -7
  190. package/src/cmd/project/template-flow.ts +186 -48
  191. package/src/cmd/secret/delete.ts +40 -0
  192. package/src/cmd/secret/get.ts +54 -0
  193. package/src/cmd/secret/import.ts +64 -0
  194. package/src/cmd/secret/index.ts +22 -0
  195. package/src/cmd/secret/list.ts +56 -0
  196. package/src/cmd/secret/pull.ts +78 -0
  197. package/src/cmd/secret/push.ts +37 -0
  198. package/src/cmd/secret/set.ts +45 -0
  199. package/src/cmd/version/index.ts +2 -1
  200. package/src/config.ts +257 -27
  201. package/src/crypto/box.test.ts +431 -0
  202. package/src/crypto/box.ts +477 -0
  203. package/src/download.ts +1 -0
  204. package/src/env-util.test.ts +194 -0
  205. package/src/env-util.ts +290 -0
  206. package/src/index.ts +5 -1
  207. package/src/schema-parser.ts +2 -3
  208. package/src/steps.ts +144 -10
  209. package/src/terminal.ts +24 -23
  210. package/src/tui.ts +208 -68
  211. package/src/types.ts +292 -202
  212. package/src/utils/detectSubagent.ts +31 -0
  213. package/src/utils/zip.ts +38 -0
  214. package/dist/cmd/example/create-user.d.ts +0 -2
  215. package/dist/cmd/example/create-user.d.ts.map +0 -1
  216. package/dist/cmd/example/create.d.ts +0 -2
  217. package/dist/cmd/example/create.d.ts.map +0 -1
  218. package/dist/cmd/example/deploy.d.ts.map +0 -1
  219. package/dist/cmd/example/index.d.ts.map +0 -1
  220. package/dist/cmd/example/list.d.ts.map +0 -1
  221. package/dist/cmd/example/optional-auth.d.ts +0 -3
  222. package/dist/cmd/example/optional-auth.d.ts.map +0 -1
  223. package/dist/cmd/example/run-command.d.ts +0 -2
  224. package/dist/cmd/example/run-command.d.ts.map +0 -1
  225. package/dist/cmd/example/sound.d.ts +0 -3
  226. package/dist/cmd/example/sound.d.ts.map +0 -1
  227. package/dist/cmd/example/spinner.d.ts +0 -2
  228. package/dist/cmd/example/spinner.d.ts.map +0 -1
  229. package/dist/cmd/example/steps.d.ts +0 -2
  230. package/dist/cmd/example/steps.d.ts.map +0 -1
  231. package/dist/cmd/example/version.d.ts +0 -2
  232. package/dist/cmd/example/version.d.ts.map +0 -1
  233. package/dist/logger.d.ts +0 -24
  234. package/dist/logger.d.ts.map +0 -1
  235. package/src/cmd/example/create-user.ts +0 -38
  236. package/src/cmd/example/create.ts +0 -31
  237. package/src/cmd/example/deploy.ts +0 -36
  238. package/src/cmd/example/index.ts +0 -29
  239. package/src/cmd/example/list.ts +0 -32
  240. package/src/cmd/example/optional-auth.ts +0 -38
  241. package/src/cmd/example/run-command.ts +0 -45
  242. package/src/cmd/example/sound.ts +0 -14
  243. package/src/cmd/example/spinner.ts +0 -44
  244. package/src/cmd/example/steps.ts +0 -66
  245. package/src/cmd/example/version.ts +0 -13
  246. package/src/logger.ts +0 -235
  247. /package/dist/cmd/{example → cloud}/deploy.d.ts +0 -0
  248. /package/dist/cmd/{example → cloud}/index.d.ts +0 -0
  249. /package/dist/cmd/{example → env}/list.d.ts +0 -0
@@ -0,0 +1,336 @@
1
+ import { join, resolve } from 'node:path';
2
+ import { createPublicKey } from 'node:crypto';
3
+ import { createReadStream, createWriteStream } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { createSubcommand } from '../../types';
6
+ import * as tui from '../../tui';
7
+ import { saveProjectDir } from '../../config';
8
+ import { runSteps, stepSuccess, stepSkipped, stepError, Step, ProgressCallback } from '../../steps';
9
+ import { bundle } from '../bundle/bundler';
10
+ import { loadBuildMetadata } from '../../config';
11
+ import {
12
+ projectEnvUpdate,
13
+ projectDeploymentCreate,
14
+ projectDeploymentUpdate,
15
+ projectDeploymentComplete,
16
+ type Deployment,
17
+ type BuildMetadata,
18
+ type DeploymentInstructions,
19
+ type DeploymentComplete,
20
+ } from '@agentuity/server';
21
+ import {
22
+ findEnvFile,
23
+ readEnvFile,
24
+ filterAgentuitySdkKeys,
25
+ splitEnvAndSecrets,
26
+ } from '../../env-util';
27
+ import { zipDir } from '../../utils/zip';
28
+ import { encryptFIPSKEMDEMStream } from '../../crypto/box';
29
+ import { checkCustomDomainForDNS } from './domain';
30
+
31
+ export const deploySubcommand = createSubcommand({
32
+ name: 'deploy',
33
+ description: 'Deploy project to the Agentuity Cloud',
34
+ toplevel: true,
35
+ requires: { auth: true, project: true, apiClient: true },
36
+
37
+ async handler(ctx) {
38
+ const { project, apiClient, projectDir, config, options } = ctx;
39
+
40
+ let deployment: Deployment | undefined;
41
+ let build: BuildMetadata | undefined;
42
+ let instructions: DeploymentInstructions | undefined;
43
+ let complete: DeploymentComplete | undefined;
44
+
45
+ try {
46
+ await saveProjectDir(projectDir);
47
+
48
+ await runSteps(
49
+ [
50
+ !project.deployment?.domains?.length
51
+ ? null
52
+ : {
53
+ label: `Validate Custom Domain${project.deployment.domains.length > 1 ? `s: ${project.deployment.domains.join(', ')}` : `: ${project.deployment.domains[0]}`}`,
54
+ run: async () => {
55
+ if (project.deployment?.domains?.length) {
56
+ const result = await checkCustomDomainForDNS(
57
+ project.projectId,
58
+ project.deployment.domains,
59
+ config
60
+ );
61
+ for (const r of result) {
62
+ if (r.success) {
63
+ continue;
64
+ }
65
+ if (r.message) {
66
+ return stepError(r.message);
67
+ }
68
+ return stepError('unknown dns error'); // shouldn't get here
69
+ }
70
+ return stepSuccess();
71
+ }
72
+ return stepSkipped();
73
+ },
74
+ },
75
+ {
76
+ label: 'Sync Env & Secrets',
77
+ run: async () => {
78
+ try {
79
+ // Read local env file (.env.production or .env)
80
+ const envFilePath = await findEnvFile(projectDir);
81
+ const localEnv = await readEnvFile(envFilePath);
82
+
83
+ // Filter out AGENTUITY_ keys
84
+ const filteredEnv = filterAgentuitySdkKeys(localEnv);
85
+
86
+ if (Object.keys(filteredEnv).length === 0) {
87
+ return stepSkipped('no variables to sync');
88
+ }
89
+
90
+ // Split into env and secrets
91
+ const { env, secrets } = splitEnvAndSecrets(filteredEnv);
92
+
93
+ if (Object.keys(env).length === 0 && Object.keys(secrets).length === 0) {
94
+ return stepSkipped('no variables to sync');
95
+ }
96
+
97
+ // Push to cloud
98
+ await projectEnvUpdate(apiClient, {
99
+ id: project.projectId,
100
+ env,
101
+ secrets,
102
+ });
103
+
104
+ return stepSuccess();
105
+ } catch (ex) {
106
+ // Non-fatal: log warning but continue deployment
107
+ const _ex = ex as Error;
108
+ return stepSkipped(_ex.message ?? 'failed to sync env variables');
109
+ }
110
+ },
111
+ },
112
+ {
113
+ label: 'Create Deployment',
114
+ run: async () => {
115
+ try {
116
+ deployment = await projectDeploymentCreate(
117
+ apiClient,
118
+ project.projectId,
119
+ project.deployment
120
+ );
121
+ return stepSuccess();
122
+ } catch (ex) {
123
+ return stepError(ex instanceof Error ? ex.message : String(ex));
124
+ }
125
+ },
126
+ },
127
+ {
128
+ label: 'Build, Verify and Package',
129
+ run: async () => {
130
+ if (!deployment) {
131
+ return stepError('deployment was null');
132
+ }
133
+ try {
134
+ await bundle({
135
+ rootDir: resolve(projectDir),
136
+ dev: false,
137
+ deploymentId: deployment.id,
138
+ orgId: deployment.orgId,
139
+ projectId: project.projectId,
140
+ project,
141
+ });
142
+ build = await loadBuildMetadata(join(projectDir, '.agentuity'));
143
+ instructions = await projectDeploymentUpdate(
144
+ apiClient,
145
+ deployment.id,
146
+ build
147
+ );
148
+ return stepSuccess();
149
+ } catch (ex) {
150
+ const _ex = ex as Error;
151
+ return stepError(_ex.message ?? 'Error building your project');
152
+ }
153
+ },
154
+ },
155
+ {
156
+ type: 'progress',
157
+ label: 'Encrypt and Upload Deployment',
158
+ run: async (progress: ProgressCallback) => {
159
+ if (!deployment) {
160
+ return stepError('deployment was null');
161
+ }
162
+ if (!instructions) {
163
+ return stepError('deployment instructions were null');
164
+ }
165
+
166
+ progress(5);
167
+ ctx.logger.trace('Starting deployment zip creation');
168
+ // zip up the assets folder
169
+ const deploymentZip = join(tmpdir(), `${deployment.id}.zip`);
170
+ await zipDir(join(projectDir, '.agentuity'), deploymentZip, {
171
+ filter: (_filename: string, relative: string) => {
172
+ // we don't include assets in the deployment
173
+ if (relative.startsWith('web/assets/')) {
174
+ return false;
175
+ }
176
+ if (relative.startsWith('web/chunk/')) {
177
+ return false;
178
+ }
179
+ if (relative.startsWith('web/public/')) {
180
+ return false;
181
+ }
182
+ // exclude sourcemaps from deployment
183
+ if (relative.endsWith('.map')) {
184
+ return false;
185
+ }
186
+ return true;
187
+ },
188
+ });
189
+ ctx.logger.trace(`Deployment zip created: ${deploymentZip}`);
190
+
191
+ progress(20);
192
+ // Encrypt the deployment zip using the public key from deployment
193
+ const encryptedZip = join(tmpdir(), `${deployment.id}.enc.zip`);
194
+ try {
195
+ ctx.logger.trace('Creating public key');
196
+ const publicKey = createPublicKey({
197
+ key: deployment.publicKey,
198
+ format: 'pem',
199
+ type: 'spki',
200
+ });
201
+
202
+ ctx.logger.trace('Creating read/write streams');
203
+ const src = createReadStream(deploymentZip);
204
+ const dst = createWriteStream(encryptedZip);
205
+
206
+ ctx.logger.trace('Starting encryption');
207
+ // Wait for encryption to complete
208
+ await encryptFIPSKEMDEMStream(publicKey, src, dst);
209
+ ctx.logger.trace('Encryption complete');
210
+
211
+ progress(40);
212
+ ctx.logger.trace('Waiting for stream to finish');
213
+ // End stream and wait for it to finish writing
214
+ await new Promise<void>((resolve, reject) => {
215
+ dst.once('finish', resolve);
216
+ dst.once('error', reject);
217
+ dst.end();
218
+ });
219
+ ctx.logger.trace('Stream finished');
220
+
221
+ progress(50);
222
+ ctx.logger.trace(`Uploading deployment to ${instructions.deployment}`);
223
+ const zipfile = Bun.file(encryptedZip);
224
+ const fileSize = await zipfile.size;
225
+ ctx.logger.trace(`Upload file size: ${fileSize} bytes`);
226
+ const resp = await fetch(instructions.deployment, {
227
+ method: 'PUT',
228
+ duplex: 'half',
229
+ headers: {
230
+ 'Content-Type': 'application/zip',
231
+ },
232
+ body: zipfile,
233
+ });
234
+ ctx.logger.trace(`Upload response: ${resp.status}`);
235
+ if (!resp.ok) {
236
+ return stepError(`Error uploading deployment: ${await resp.text()}`);
237
+ }
238
+
239
+ progress(70);
240
+ ctx.logger.trace('Consuming response body');
241
+ // Wait for response body to be consumed before deleting
242
+ await resp.arrayBuffer();
243
+ ctx.logger.trace('Deleting encrypted zip');
244
+ await zipfile.delete();
245
+ } finally {
246
+ ctx.logger.trace('Cleanup');
247
+ // Cleanup in case of error
248
+ if (await Bun.file(encryptedZip).exists()) {
249
+ await Bun.file(encryptedZip).delete();
250
+ }
251
+ await Bun.file(deploymentZip).delete();
252
+ }
253
+
254
+ progress(80);
255
+ if (build?.assets) {
256
+ ctx.logger.trace(`Uploading ${build.assets.length} assets`);
257
+ if (!instructions.assets) {
258
+ return stepError(
259
+ 'server did not provide asset upload URLs; upload aborted'
260
+ );
261
+ }
262
+
263
+ const promises: Promise<Response>[] = [];
264
+ for (const asset of build.assets) {
265
+ const assetUrl = instructions.assets[asset.filename];
266
+ if (!assetUrl) {
267
+ return stepError(
268
+ `server did not provide upload URL for asset "${asset.filename}"; upload aborted`
269
+ );
270
+ }
271
+
272
+ const file = Bun.file(
273
+ join(projectDir, '.agentuity', 'web', asset.filename)
274
+ );
275
+ promises.push(
276
+ fetch(assetUrl, {
277
+ method: 'PUT',
278
+ duplex: 'half',
279
+ headers: {
280
+ 'Content-Type': asset.contentType,
281
+ },
282
+ body: file,
283
+ })
284
+ );
285
+ }
286
+ ctx.logger.trace('Waiting for asset uploads');
287
+ const resps = await Promise.all(promises);
288
+ for (const r of resps) {
289
+ if (!r.ok) {
290
+ return stepError(`error uploading asset: ${await r.text()}`);
291
+ }
292
+ }
293
+ ctx.logger.trace('Asset uploads complete');
294
+ progress(95);
295
+ }
296
+
297
+ progress(100);
298
+ return stepSuccess();
299
+ },
300
+ },
301
+ {
302
+ label: 'Provision Deployment',
303
+ run: async () => {
304
+ if (!deployment) {
305
+ return stepError('deployment was null');
306
+ }
307
+ complete = await projectDeploymentComplete(apiClient, deployment.id);
308
+ return stepSuccess();
309
+ },
310
+ },
311
+ ].filter(Boolean) as Step[],
312
+ options.logLevel
313
+ );
314
+ tui.success('Your project was deployed!');
315
+
316
+ if (complete?.publicUrls && deployment) {
317
+ tui.arrow(tui.bold(tui.padRight('Deployment ID:', 17)) + tui.link(deployment.id));
318
+ if (complete.publicUrls.custom?.length) {
319
+ for (const url of complete.publicUrls.custom) {
320
+ tui.arrow(tui.bold(tui.padRight('Deployment URL:', 17)) + tui.link(url));
321
+ }
322
+ } else {
323
+ tui.arrow(
324
+ tui.bold(tui.padRight('Deployment URL:', 17)) +
325
+ tui.link(complete.publicUrls.deployment)
326
+ );
327
+ tui.arrow(
328
+ tui.bold(tui.padRight('Project URL:', 17)) + tui.link(complete.publicUrls.latest)
329
+ );
330
+ }
331
+ }
332
+ } catch (ex) {
333
+ tui.fatal(`unexpected error trying to deploy project. ${ex}`);
334
+ }
335
+ },
336
+ });
@@ -0,0 +1,92 @@
1
+ import * as dns from 'node:dns';
2
+ import type { Config } from '../../types';
3
+
4
+ export interface DNSResult {
5
+ domain: string;
6
+ success: boolean;
7
+ message?: string;
8
+ }
9
+
10
+ /**
11
+ * This function will check for each of the custom domains and make sure they are correctly
12
+ * configured in DNS
13
+ *
14
+ * @param projectId the project id
15
+ * @param config Config
16
+ * @param domains array of domains to check
17
+ * @returns
18
+ */
19
+ export async function checkCustomDomainForDNS(
20
+ projectId: string,
21
+ domains: string[],
22
+ config?: Config | null
23
+ ): Promise<DNSResult[]> {
24
+ const suffix = config?.overrides?.api_url?.includes('agentuity.io')
25
+ ? 'agentuity.io'
26
+ : 'agentuity.com';
27
+ const id = Bun.hash.xxHash64(`${projectId}latest`).toString(16);
28
+ const proxy = `p${id}.${suffix}`;
29
+
30
+ return Promise.all(
31
+ domains.map(async (domain) => {
32
+ try {
33
+ const timeoutMs = 5000;
34
+ let timeoutId: Timer | undefined;
35
+
36
+ const timeoutPromise = new Promise<never>((_, reject) => {
37
+ timeoutId = setTimeout(() => {
38
+ reject(new Error(`DNS lookup timed out after ${timeoutMs}ms`));
39
+ }, timeoutMs);
40
+ });
41
+
42
+ const result = await Promise.race([
43
+ dns.promises.resolveCname(domain),
44
+ timeoutPromise,
45
+ ]).finally(() => {
46
+ if (timeoutId) clearTimeout(timeoutId);
47
+ });
48
+
49
+ if (result.length) {
50
+ if (result[0] === proxy) {
51
+ return {
52
+ domain,
53
+ success: true,
54
+ };
55
+ }
56
+ return {
57
+ domain,
58
+ success: false,
59
+ message: `DNS record for ${domain} must have a CNAME record with the value: ${proxy}`,
60
+ };
61
+ }
62
+ } catch (ex) {
63
+ const _ex = ex as { code: string; message?: string };
64
+ if (_ex.message?.includes('timed out')) {
65
+ return {
66
+ domain,
67
+ success: false,
68
+ message: `DNS lookup for ${domain} timed out after 5 seconds. Please check your DNS configuration.`,
69
+ };
70
+ }
71
+ if (_ex.code !== 'ENOTFOUND') {
72
+ const errMsg =
73
+ ex instanceof Error
74
+ ? ex.message
75
+ : typeof ex === 'string'
76
+ ? ex
77
+ : JSON.stringify(ex);
78
+ return {
79
+ domain,
80
+ success: false,
81
+ message: `DNS returned an error resolving ${domain}: ${errMsg}`,
82
+ };
83
+ }
84
+ }
85
+ return {
86
+ domain,
87
+ success: false,
88
+ message: `To enable the custom domain ${domain}, create a CNAME DNS record with the value: ${proxy} and a TTL of 600`,
89
+ };
90
+ })
91
+ );
92
+ }
@@ -0,0 +1,11 @@
1
+ import { createCommand } from '../../types';
2
+ import { deploySubcommand } from './deploy';
3
+ import { resourceSubcommand } from './resource';
4
+ import { sshSubcommand } from './ssh';
5
+ import { scpSubcommand } from './scp';
6
+
7
+ export const command = createCommand({
8
+ name: 'cloud',
9
+ description: 'Cloud related commands',
10
+ subcommands: [deploySubcommand, resourceSubcommand, sshSubcommand, scpSubcommand],
11
+ });
@@ -0,0 +1,56 @@
1
+ import { z } from 'zod';
2
+ import { createResources } 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
+
8
+ export const addSubcommand = createSubcommand({
9
+ name: 'add',
10
+ aliases: ['create'],
11
+ description: 'Add a cloud resource for an organization',
12
+ requires: { auth: true, org: true, region: true },
13
+ schema: {
14
+ options: z.object({
15
+ type: z.enum(['database', 'storage']).optional().describe('Resource type'),
16
+ }),
17
+ },
18
+
19
+ async handler(ctx) {
20
+ const { logger, opts, orgId, region, config, auth } = ctx;
21
+
22
+ // Determine resource type
23
+ let resourceType = opts.type;
24
+ if (!resourceType) {
25
+ const response = await enquirer.prompt<{ type: 'database' | 'storage' }>({
26
+ type: 'select',
27
+ name: 'type',
28
+ message: 'Select resource type:',
29
+ choices: [
30
+ { name: 'database', message: 'Database (PostgreSQL)' },
31
+ { name: 'storage', message: 'Storage (S3)' },
32
+ ],
33
+ });
34
+ resourceType = response.type;
35
+ }
36
+
37
+ // Map user-friendly type to API type
38
+ const apiType = resourceType === 'database' ? 'db' : 's3';
39
+
40
+ const catalystClient = getCatalystAPIClient(config, logger, auth);
41
+
42
+ const created = await tui.spinner({
43
+ message: `Creating ${resourceType} in ${region}`,
44
+ clearOnSuccess: true,
45
+ callback: async () => {
46
+ return createResources(catalystClient, orgId, region!, [{ type: apiType }]);
47
+ },
48
+ });
49
+
50
+ if (created.length > 0) {
51
+ tui.success(`Created ${resourceType}: ${tui.bold(created[0].name)}`);
52
+ } else {
53
+ tui.fatal('Failed to create resource');
54
+ }
55
+ },
56
+ });
@@ -0,0 +1,120 @@
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
+
8
+ export const deleteSubcommand = createSubcommand({
9
+ name: 'delete',
10
+ description: 'Delete cloud resource(s) for an organization',
11
+ aliases: ['rm', 'del', 'remove'],
12
+ requires: { auth: true, org: true, region: true },
13
+ schema: {
14
+ options: z.object({
15
+ type: z.enum(['db', 's3']).optional().describe('Resource type (db or s3)'),
16
+ name: z.string().optional().describe('Resource name to delete'),
17
+ confirm: z.boolean().optional().describe('Skip confirmation prompts'),
18
+ }),
19
+ },
20
+
21
+ async handler(ctx) {
22
+ const { logger, opts, config, orgId, region, auth } = ctx;
23
+
24
+ const catalystClient = getCatalystAPIClient(config, logger, auth);
25
+
26
+ // Determine what to delete
27
+ let resourcesToDelete: Array<{ type: 'db' | 's3'; name: string }> = [];
28
+
29
+ if (opts.type && opts.name) {
30
+ // Command line arguments provided
31
+ resourcesToDelete = [{ type: opts.type, name: opts.name }];
32
+ } else {
33
+ // Fetch resources and prompt for selection
34
+ const resources = await tui.spinner({
35
+ message: `Fetching resources for ${orgId} in ${region}`,
36
+ clearOnSuccess: true,
37
+ callback: async () => {
38
+ return listResources(catalystClient, orgId, region!);
39
+ },
40
+ });
41
+
42
+ if (resources.db.length === 0 && resources.s3.length === 0) {
43
+ tui.info('No resources found to delete');
44
+ return;
45
+ }
46
+
47
+ // Build choices for multi-select and resource map
48
+ const choices: Array<{ name: string; message: string }> = [];
49
+ const resourceMap = new Map<string, { type: 'db' | 's3'; name: string }>();
50
+
51
+ for (const db of resources.db) {
52
+ const key = `db:${db.name}`;
53
+ choices.push({
54
+ name: key,
55
+ message: `Database: ${db.name}`,
56
+ });
57
+ resourceMap.set(key, { type: 'db', name: db.name });
58
+ }
59
+
60
+ for (const s3 of resources.s3) {
61
+ const key = `s3:${s3.bucket_name}`;
62
+ choices.push({
63
+ name: key,
64
+ message: `Storage: ${s3.bucket_name}`,
65
+ });
66
+ resourceMap.set(key, { type: 's3', name: s3.bucket_name });
67
+ }
68
+
69
+ const response = await enquirer.prompt<{ resources: string[] }>({
70
+ type: 'multiselect',
71
+ name: 'resources',
72
+ message: 'Select resource(s) to delete:',
73
+ choices,
74
+ });
75
+
76
+ // Map selected keys back to resource objects
77
+ resourcesToDelete = response.resources
78
+ .map((key) => resourceMap.get(key))
79
+ .filter((r): r is { type: 'db' | 's3'; name: string } => r !== undefined);
80
+ }
81
+
82
+ if (resourcesToDelete.length === 0) {
83
+ tui.info('No resources selected for deletion');
84
+ return;
85
+ }
86
+
87
+ // Confirm deletion
88
+ if (!opts.confirm) {
89
+ const resourceNames = resourcesToDelete.map((r) => `${r.type}:${r.name}`).join(', ');
90
+ tui.warning(`You are about to delete: ${tui.bold(resourceNames)}`);
91
+
92
+ const confirm = await enquirer.prompt<{ confirm: boolean }>({
93
+ type: 'confirm',
94
+ name: 'confirm',
95
+ message: 'Are you sure you want to delete these resources?',
96
+ initial: false,
97
+ });
98
+
99
+ if (!confirm.confirm) {
100
+ tui.info('Deletion cancelled');
101
+ return;
102
+ }
103
+ }
104
+
105
+ // Delete resources
106
+ const deleted = await tui.spinner({
107
+ message: `Deleting ${resourcesToDelete.length} resource(s)`,
108
+ clearOnSuccess: true,
109
+ callback: async () => {
110
+ return deleteResources(catalystClient, orgId, region!, resourcesToDelete);
111
+ },
112
+ });
113
+
114
+ if (deleted.length > 0) {
115
+ tui.success(`Deleted ${deleted.length} resource(s): ${deleted.join(', ')}`);
116
+ } else {
117
+ tui.error('Failed to delete resources');
118
+ }
119
+ },
120
+ });
@@ -0,0 +1,11 @@
1
+ import type { SubcommandDefinition } from '../../../types';
2
+ import { listSubcommand } from './list';
3
+ import { addSubcommand } from './add';
4
+ import { deleteSubcommand } from './delete';
5
+
6
+ export const resourceSubcommand: SubcommandDefinition = {
7
+ name: 'resource',
8
+ aliases: ['resources'],
9
+ description: 'Manage cloud resources',
10
+ subcommands: [listSubcommand, addSubcommand, deleteSubcommand],
11
+ };
@@ -0,0 +1,69 @@
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
+
7
+ export const listSubcommand = createSubcommand({
8
+ name: 'list',
9
+ description: 'List cloud resources for an organization',
10
+ aliases: ['ls'],
11
+ requires: { auth: true, org: true, region: true },
12
+ schema: {
13
+ options: z.object({
14
+ format: z
15
+ .enum(['text', 'json'])
16
+ .optional()
17
+ .default('text')
18
+ .describe('Output format (text or json)'),
19
+ }),
20
+ },
21
+
22
+ async handler(ctx) {
23
+ const { logger, opts, orgId, region, config, auth } = ctx;
24
+
25
+ const catalystClient = getCatalystAPIClient(config, logger, auth);
26
+
27
+ const resources = await tui.spinner({
28
+ message: `Fetching resources for ${orgId} in ${region}`,
29
+ clearOnSuccess: true,
30
+ callback: async () => {
31
+ return listResources(catalystClient, orgId, region);
32
+ },
33
+ });
34
+
35
+ // Output based on format
36
+ if (opts.format === 'json') {
37
+ console.log(JSON.stringify(resources, null, 2));
38
+ } else {
39
+ // Text table format
40
+ if (resources.db.length === 0 && resources.s3.length === 0) {
41
+ tui.info('No resources found');
42
+ return;
43
+ }
44
+
45
+ if (resources.db.length > 0) {
46
+ tui.info(tui.bold('Databases'));
47
+ tui.newline();
48
+ for (const db of resources.db) {
49
+ console.log(tui.bold(db.name));
50
+ if (db.url) console.log(` URL: ${tui.muted(db.url)}`);
51
+ tui.newline();
52
+ }
53
+ }
54
+
55
+ if (resources.s3.length > 0) {
56
+ tui.info(tui.bold('Storage'));
57
+ tui.newline();
58
+ for (const s3 of resources.s3) {
59
+ console.log(tui.bold(s3.bucket_name));
60
+ if (s3.access_key) console.log(` Access Key: ${tui.muted(s3.access_key)}`);
61
+ if (s3.secret_key) console.log(` Secret Key: ${tui.muted(s3.secret_key)}`);
62
+ if (s3.region) console.log(` Region: ${tui.muted(s3.region)}`);
63
+ if (s3.endpoint) console.log(` Endpoint: ${tui.muted(s3.endpoint)}`);
64
+ tui.newline();
65
+ }
66
+ }
67
+ }
68
+ },
69
+ });