@agentuity/cli 0.0.43 → 0.0.45

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 (209) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/api.d.ts +3 -3
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/auth.d.ts +10 -2
  6. package/dist/auth.d.ts.map +1 -1
  7. package/dist/banner.d.ts.map +1 -1
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cmd/auth/api.d.ts +4 -4
  10. package/dist/cmd/auth/api.d.ts.map +1 -1
  11. package/dist/cmd/auth/index.d.ts.map +1 -1
  12. package/dist/cmd/auth/login.d.ts.map +1 -1
  13. package/dist/cmd/auth/signup.d.ts.map +1 -1
  14. package/dist/cmd/auth/ssh/add.d.ts +2 -0
  15. package/dist/cmd/auth/ssh/add.d.ts.map +1 -0
  16. package/dist/cmd/auth/ssh/api.d.ts +16 -0
  17. package/dist/cmd/auth/ssh/api.d.ts.map +1 -0
  18. package/dist/cmd/auth/ssh/delete.d.ts +2 -0
  19. package/dist/cmd/auth/ssh/delete.d.ts.map +1 -0
  20. package/dist/cmd/auth/ssh/index.d.ts +3 -0
  21. package/dist/cmd/auth/ssh/index.d.ts.map +1 -0
  22. package/dist/cmd/auth/ssh/list.d.ts +2 -0
  23. package/dist/cmd/auth/ssh/list.d.ts.map +1 -0
  24. package/dist/cmd/auth/whoami.d.ts.map +1 -1
  25. package/dist/cmd/bundle/ast.d.ts +14 -3
  26. package/dist/cmd/bundle/ast.d.ts.map +1 -1
  27. package/dist/cmd/bundle/ast.test.d.ts +2 -0
  28. package/dist/cmd/bundle/ast.test.d.ts.map +1 -0
  29. package/dist/cmd/bundle/bundler.d.ts +6 -1
  30. package/dist/cmd/bundle/bundler.d.ts.map +1 -1
  31. package/dist/cmd/bundle/file.d.ts.map +1 -1
  32. package/dist/cmd/bundle/fix-duplicate-exports.d.ts +2 -0
  33. package/dist/cmd/bundle/fix-duplicate-exports.d.ts.map +1 -0
  34. package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts +2 -0
  35. package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts.map +1 -0
  36. package/dist/cmd/bundle/plugin.d.ts +2 -0
  37. package/dist/cmd/bundle/plugin.d.ts.map +1 -1
  38. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  39. package/dist/cmd/cloud/domain.d.ts +17 -0
  40. package/dist/cmd/cloud/domain.d.ts.map +1 -0
  41. package/dist/cmd/cloud/index.d.ts.map +1 -1
  42. package/dist/cmd/cloud/resource/add.d.ts +2 -0
  43. package/dist/cmd/cloud/resource/add.d.ts.map +1 -0
  44. package/dist/cmd/cloud/resource/delete.d.ts +2 -0
  45. package/dist/cmd/cloud/resource/delete.d.ts.map +1 -0
  46. package/dist/cmd/cloud/resource/index.d.ts +3 -0
  47. package/dist/cmd/cloud/resource/index.d.ts.map +1 -0
  48. package/dist/cmd/cloud/resource/list.d.ts +2 -0
  49. package/dist/cmd/cloud/resource/list.d.ts.map +1 -0
  50. package/dist/cmd/cloud/scp/download.d.ts +2 -0
  51. package/dist/cmd/cloud/scp/download.d.ts.map +1 -0
  52. package/dist/cmd/cloud/scp/index.d.ts +3 -0
  53. package/dist/cmd/cloud/scp/index.d.ts.map +1 -0
  54. package/dist/cmd/cloud/scp/upload.d.ts +2 -0
  55. package/dist/cmd/cloud/scp/upload.d.ts.map +1 -0
  56. package/dist/cmd/cloud/ssh.d.ts +2 -0
  57. package/dist/cmd/cloud/ssh.d.ts.map +1 -0
  58. package/dist/cmd/dev/api.d.ts +18 -0
  59. package/dist/cmd/dev/api.d.ts.map +1 -0
  60. package/dist/cmd/dev/download.d.ts +11 -0
  61. package/dist/cmd/dev/download.d.ts.map +1 -0
  62. package/dist/cmd/dev/index.d.ts.map +1 -1
  63. package/dist/cmd/dev/templates.d.ts +3 -0
  64. package/dist/cmd/dev/templates.d.ts.map +1 -0
  65. package/dist/cmd/env/delete.d.ts.map +1 -1
  66. package/dist/cmd/env/get.d.ts.map +1 -1
  67. package/dist/cmd/env/import.d.ts.map +1 -1
  68. package/dist/cmd/env/list.d.ts.map +1 -1
  69. package/dist/cmd/env/pull.d.ts.map +1 -1
  70. package/dist/cmd/env/push.d.ts.map +1 -1
  71. package/dist/cmd/env/set.d.ts.map +1 -1
  72. package/dist/cmd/profile/show.d.ts.map +1 -1
  73. package/dist/cmd/project/create.d.ts.map +1 -1
  74. package/dist/cmd/project/delete.d.ts.map +1 -1
  75. package/dist/cmd/project/list.d.ts.map +1 -1
  76. package/dist/cmd/project/show.d.ts.map +1 -1
  77. package/dist/cmd/project/template-flow.d.ts +4 -0
  78. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  79. package/dist/cmd/secret/delete.d.ts.map +1 -1
  80. package/dist/cmd/secret/get.d.ts.map +1 -1
  81. package/dist/cmd/secret/import.d.ts.map +1 -1
  82. package/dist/cmd/secret/list.d.ts.map +1 -1
  83. package/dist/cmd/secret/pull.d.ts.map +1 -1
  84. package/dist/cmd/secret/push.d.ts.map +1 -1
  85. package/dist/cmd/secret/set.d.ts.map +1 -1
  86. package/dist/config.d.ts +9 -3
  87. package/dist/config.d.ts.map +1 -1
  88. package/dist/crypto/box.d.ts +65 -0
  89. package/dist/crypto/box.d.ts.map +1 -0
  90. package/dist/crypto/box.test.d.ts +2 -0
  91. package/dist/crypto/box.test.d.ts.map +1 -0
  92. package/dist/download.d.ts.map +1 -1
  93. package/dist/steps.d.ts +4 -1
  94. package/dist/steps.d.ts.map +1 -1
  95. package/dist/terminal.d.ts.map +1 -1
  96. package/dist/tui.d.ts +31 -1
  97. package/dist/tui.d.ts.map +1 -1
  98. package/dist/types.d.ts +249 -126
  99. package/dist/types.d.ts.map +1 -1
  100. package/dist/utils/detectSubagent.d.ts +15 -0
  101. package/dist/utils/detectSubagent.d.ts.map +1 -0
  102. package/dist/utils/zip.d.ts +7 -0
  103. package/dist/utils/zip.d.ts.map +1 -0
  104. package/package.json +11 -3
  105. package/src/api-errors.md +2 -2
  106. package/src/api.ts +12 -7
  107. package/src/auth.ts +116 -7
  108. package/src/banner.ts +13 -6
  109. package/src/cli.ts +695 -63
  110. package/src/cmd/auth/api.ts +10 -16
  111. package/src/cmd/auth/index.ts +2 -1
  112. package/src/cmd/auth/login.ts +24 -8
  113. package/src/cmd/auth/signup.ts +15 -11
  114. package/src/cmd/auth/ssh/add.ts +263 -0
  115. package/src/cmd/auth/ssh/api.ts +94 -0
  116. package/src/cmd/auth/ssh/delete.ts +102 -0
  117. package/src/cmd/auth/ssh/index.ts +10 -0
  118. package/src/cmd/auth/ssh/list.ts +74 -0
  119. package/src/cmd/auth/whoami.ts +13 -13
  120. package/src/cmd/bundle/ast.test.ts +565 -0
  121. package/src/cmd/bundle/ast.ts +457 -44
  122. package/src/cmd/bundle/bundler.ts +255 -57
  123. package/src/cmd/bundle/file.ts +6 -12
  124. package/src/cmd/bundle/fix-duplicate-exports.test.ts +387 -0
  125. package/src/cmd/bundle/fix-duplicate-exports.ts +204 -0
  126. package/src/cmd/bundle/index.ts +9 -9
  127. package/src/cmd/bundle/patch/aisdk.ts +1 -1
  128. package/src/cmd/bundle/plugin.ts +373 -53
  129. package/src/cmd/cloud/deploy.ts +300 -93
  130. package/src/cmd/cloud/domain.ts +92 -0
  131. package/src/cmd/cloud/index.ts +4 -1
  132. package/src/cmd/cloud/resource/add.ts +56 -0
  133. package/src/cmd/cloud/resource/delete.ts +120 -0
  134. package/src/cmd/cloud/resource/index.ts +11 -0
  135. package/src/cmd/cloud/resource/list.ts +69 -0
  136. package/src/cmd/cloud/scp/download.ts +59 -0
  137. package/src/cmd/cloud/scp/index.ts +9 -0
  138. package/src/cmd/cloud/scp/upload.ts +62 -0
  139. package/src/cmd/cloud/ssh.ts +68 -0
  140. package/src/cmd/dev/api.ts +46 -0
  141. package/src/cmd/dev/download.ts +111 -0
  142. package/src/cmd/dev/index.ts +360 -34
  143. package/src/cmd/dev/templates.ts +84 -0
  144. package/src/cmd/env/delete.ts +5 -20
  145. package/src/cmd/env/get.ts +5 -18
  146. package/src/cmd/env/import.ts +5 -20
  147. package/src/cmd/env/list.ts +5 -18
  148. package/src/cmd/env/pull.ts +10 -23
  149. package/src/cmd/env/push.ts +5 -23
  150. package/src/cmd/env/set.ts +5 -20
  151. package/src/cmd/index.ts +2 -2
  152. package/src/cmd/profile/show.ts +15 -6
  153. package/src/cmd/project/create.ts +7 -2
  154. package/src/cmd/project/delete.ts +75 -18
  155. package/src/cmd/project/download.ts +2 -2
  156. package/src/cmd/project/list.ts +8 -8
  157. package/src/cmd/project/show.ts +3 -7
  158. package/src/cmd/project/template-flow.ts +170 -72
  159. package/src/cmd/secret/delete.ts +5 -20
  160. package/src/cmd/secret/get.ts +5 -18
  161. package/src/cmd/secret/import.ts +5 -20
  162. package/src/cmd/secret/list.ts +5 -18
  163. package/src/cmd/secret/pull.ts +10 -23
  164. package/src/cmd/secret/push.ts +5 -23
  165. package/src/cmd/secret/set.ts +5 -20
  166. package/src/config.ts +224 -24
  167. package/src/crypto/box.test.ts +431 -0
  168. package/src/crypto/box.ts +477 -0
  169. package/src/download.ts +1 -0
  170. package/src/env-util.test.ts +1 -1
  171. package/src/steps.ts +65 -6
  172. package/src/terminal.ts +24 -23
  173. package/src/tui.ts +192 -61
  174. package/src/types.ts +291 -201
  175. package/src/utils/detectSubagent.ts +31 -0
  176. package/src/utils/zip.ts +38 -0
  177. package/dist/cmd/example/create-user.d.ts +0 -2
  178. package/dist/cmd/example/create-user.d.ts.map +0 -1
  179. package/dist/cmd/example/create.d.ts +0 -2
  180. package/dist/cmd/example/create.d.ts.map +0 -1
  181. package/dist/cmd/example/deploy.d.ts +0 -2
  182. package/dist/cmd/example/deploy.d.ts.map +0 -1
  183. package/dist/cmd/example/index.d.ts +0 -2
  184. package/dist/cmd/example/index.d.ts.map +0 -1
  185. package/dist/cmd/example/list.d.ts +0 -2
  186. package/dist/cmd/example/list.d.ts.map +0 -1
  187. package/dist/cmd/example/optional-auth.d.ts +0 -3
  188. package/dist/cmd/example/optional-auth.d.ts.map +0 -1
  189. package/dist/cmd/example/run-command.d.ts +0 -2
  190. package/dist/cmd/example/run-command.d.ts.map +0 -1
  191. package/dist/cmd/example/sound.d.ts +0 -3
  192. package/dist/cmd/example/sound.d.ts.map +0 -1
  193. package/dist/cmd/example/spinner.d.ts +0 -2
  194. package/dist/cmd/example/spinner.d.ts.map +0 -1
  195. package/dist/cmd/example/steps.d.ts +0 -2
  196. package/dist/cmd/example/steps.d.ts.map +0 -1
  197. package/dist/cmd/example/version.d.ts +0 -2
  198. package/dist/cmd/example/version.d.ts.map +0 -1
  199. package/src/cmd/example/create-user.ts +0 -38
  200. package/src/cmd/example/create.ts +0 -31
  201. package/src/cmd/example/deploy.ts +0 -36
  202. package/src/cmd/example/index.ts +0 -29
  203. package/src/cmd/example/list.ts +0 -32
  204. package/src/cmd/example/optional-auth.ts +0 -38
  205. package/src/cmd/example/run-command.ts +0 -45
  206. package/src/cmd/example/sound.ts +0 -14
  207. package/src/cmd/example/spinner.ts +0 -44
  208. package/src/cmd/example/steps.ts +0 -66
  209. package/src/cmd/example/version.ts +0 -13
@@ -1,129 +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';
1
5
  import { createSubcommand } from '../../types';
2
- import { z } from 'zod';
3
- import { join } from 'node:path';
4
6
  import * as tui from '../../tui';
5
- import { loadProjectConfig, saveProjectDir } from '../../config';
6
- import { runSteps, stepSuccess, stepSkipped, stepError } from '../../steps';
7
+ import { saveProjectDir } from '../../config';
8
+ import { runSteps, stepSuccess, stepSkipped, stepError, Step, ProgressCallback } from '../../steps';
7
9
  import { bundle } from '../bundle/bundler';
8
10
  import { loadBuildMetadata } from '../../config';
9
- import { projectEnvUpdate } from '@agentuity/server';
10
- import { getAPIBaseURL, APIClient } from '../../api';
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';
11
21
  import {
12
22
  findEnvFile,
13
23
  readEnvFile,
14
24
  filterAgentuitySdkKeys,
15
25
  splitEnvAndSecrets,
16
26
  } from '../../env-util';
27
+ import { zipDir } from '../../utils/zip';
28
+ import { encryptFIPSKEMDEMStream } from '../../crypto/box';
29
+ import { checkCustomDomainForDNS } from './domain';
17
30
 
18
31
  export const deploySubcommand = createSubcommand({
19
32
  name: 'deploy',
20
33
  description: 'Deploy project to the Agentuity Cloud',
21
34
  toplevel: true,
22
- requiresAuth: true,
23
- schema: {
24
- options: z.object({
25
- dir: z.string().optional().describe('Directory to use for the project'),
26
- }),
27
- },
35
+ requires: { auth: true, project: true, apiClient: true },
28
36
 
29
37
  async handler(ctx) {
30
- const { opts, config } = ctx;
31
- const dir = opts?.dir ?? process.cwd();
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
+
32
45
  try {
33
- const project = await loadProjectConfig(dir);
34
- if (!project) {
35
- console.log(project); // FIXME
36
- }
37
- await saveProjectDir(dir);
46
+ await saveProjectDir(projectDir);
38
47
 
39
- const apiUrl = getAPIBaseURL(config);
40
- const client = new APIClient(apiUrl, config);
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);
41
82
 
42
- await runSteps([
43
- {
44
- label: 'Sync Environment Variables',
45
- run: async () => {
46
- try {
47
- // Read local env file (.env.production or .env)
48
- const envFilePath = await findEnvFile(dir);
49
- const localEnv = await readEnvFile(envFilePath);
83
+ // Filter out AGENTUITY_ keys
84
+ const filteredEnv = filterAgentuitySdkKeys(localEnv);
50
85
 
51
- // Filter out AGENTUITY_ keys
52
- const filteredEnv = filterAgentuitySdkKeys(localEnv);
86
+ if (Object.keys(filteredEnv).length === 0) {
87
+ return stepSkipped('no variables to sync');
88
+ }
53
89
 
54
- if (Object.keys(filteredEnv).length === 0) {
55
- return stepSkipped('no environment variables to sync');
56
- }
90
+ // Split into env and secrets
91
+ const { env, secrets } = splitEnvAndSecrets(filteredEnv);
57
92
 
58
- // Split into env and secrets
59
- const { env, secrets } = splitEnvAndSecrets(filteredEnv);
93
+ if (Object.keys(env).length === 0 && Object.keys(secrets).length === 0) {
94
+ return stepSkipped('no variables to sync');
95
+ }
60
96
 
61
- // Push to cloud
62
- await projectEnvUpdate(client, {
63
- id: project.projectId,
64
- env,
65
- secrets,
66
- });
97
+ // Push to cloud
98
+ await projectEnvUpdate(apiClient, {
99
+ id: project.projectId,
100
+ env,
101
+ secrets,
102
+ });
67
103
 
68
- return stepSuccess();
69
- } catch (ex) {
70
- // Non-fatal: log warning but continue deployment
71
- const _ex = ex as Error;
72
- return stepSkipped(_ex.message ?? 'failed to sync env variables');
73
- }
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
+ },
74
111
  },
75
- },
76
- {
77
- label: 'Create Deployment',
78
- run: async () => {
79
- // TODO: implement
80
- await Bun.sleep(1500);
81
- return stepSuccess();
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
+ },
82
154
  },
83
- },
84
- {
85
- label: 'Build, Verify and Package',
86
- run: async () => {
87
- try {
88
- await bundle({
89
- rootDir: dir,
90
- dev: false,
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
+ },
91
188
  });
92
- await loadBuildMetadata(join(dir, '.agentuity'));
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);
93
298
  return stepSuccess();
94
- } catch (ex) {
95
- const _ex = ex as Error;
96
- return stepError(_ex.message ?? 'Error building your project');
97
- }
299
+ },
98
300
  },
99
- },
100
- {
101
- label: 'Encrypt and Upload Deployment',
102
- run: async () => {
103
- // TODO: implement
104
- await Bun.sleep(800);
105
- return stepSkipped('already up to date');
106
- },
107
- },
108
- {
109
- label: 'Provision Services',
110
- run: async () => {
111
- // TODO: implement
112
- await Bun.sleep(1200);
113
- return stepSuccess();
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
+ },
114
310
  },
115
- },
116
- ]);
311
+ ].filter(Boolean) as Step[],
312
+ options.logLevel
313
+ );
117
314
  tui.success('Your project was deployed!');
118
- tui.arrow(tui.link('https://project-123455666332.agentuity.run'));
119
- } catch (ex) {
120
- const _ex = ex as Error;
121
- if (_ex.name === 'ProjectConfigNotFoundExpection') {
122
- tui.fatal(
123
- `The directory ${dir} does not contain a valid Agentuity project. Missing agentuity.json`
124
- );
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
+ }
125
331
  }
126
- tui.fatal(`unxpected error trying to deploy project. ${ex}`);
332
+ } catch (ex) {
333
+ tui.fatal(`unexpected error trying to deploy project. ${ex}`);
127
334
  }
128
335
  },
129
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
+ }
@@ -1,8 +1,11 @@
1
1
  import { createCommand } from '../../types';
2
2
  import { deploySubcommand } from './deploy';
3
+ import { resourceSubcommand } from './resource';
4
+ import { sshSubcommand } from './ssh';
5
+ import { scpSubcommand } from './scp';
3
6
 
4
7
  export const command = createCommand({
5
8
  name: 'cloud',
6
9
  description: 'Cloud related commands',
7
- subcommands: [deploySubcommand],
10
+ subcommands: [deploySubcommand, resourceSubcommand, sshSubcommand, scpSubcommand],
8
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
+ });