@agentuity/cli 0.0.110 → 0.0.112

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 (258) hide show
  1. package/bin/cli.ts +4 -0
  2. package/dist/agents-docs.d.ts +5 -4
  3. package/dist/agents-docs.d.ts.map +1 -1
  4. package/dist/agents-docs.js +28 -8
  5. package/dist/agents-docs.js.map +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +19 -4
  8. package/dist/cli.js.map +1 -1
  9. package/dist/cmd/auth/apikey.d.ts +2 -0
  10. package/dist/cmd/auth/apikey.d.ts.map +1 -0
  11. package/dist/cmd/auth/apikey.js +31 -0
  12. package/dist/cmd/auth/apikey.js.map +1 -0
  13. package/dist/cmd/auth/index.d.ts.map +1 -1
  14. package/dist/cmd/auth/index.js +9 -1
  15. package/dist/cmd/auth/index.js.map +1 -1
  16. package/dist/cmd/build/ast.d.ts.map +1 -1
  17. package/dist/cmd/build/ast.js +103 -2
  18. package/dist/cmd/build/ast.js.map +1 -1
  19. package/dist/cmd/build/entry-generator.d.ts +2 -1
  20. package/dist/cmd/build/entry-generator.d.ts.map +1 -1
  21. package/dist/cmd/build/entry-generator.js +152 -9
  22. package/dist/cmd/build/entry-generator.js.map +1 -1
  23. package/dist/cmd/build/vite/agent-discovery.d.ts +1 -1
  24. package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
  25. package/dist/cmd/build/vite/agent-discovery.js +7 -6
  26. package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
  27. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  28. package/dist/cmd/build/vite/index.js +3 -2
  29. package/dist/cmd/build/vite/index.js.map +1 -1
  30. package/dist/cmd/build/vite/metadata-generator.js +1 -1
  31. package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
  32. package/dist/cmd/build/vite/registry-generator.d.ts +1 -1
  33. package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
  34. package/dist/cmd/build/vite/registry-generator.js +115 -23
  35. package/dist/cmd/build/vite/registry-generator.js.map +1 -1
  36. package/dist/cmd/build/vite/route-discovery.d.ts +6 -0
  37. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  38. package/dist/cmd/build/vite/route-discovery.js +19 -0
  39. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  40. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  41. package/dist/cmd/build/vite/vite-builder.js +3 -2
  42. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  43. package/dist/cmd/cloud/deploy-fork.d.ts +32 -0
  44. package/dist/cmd/cloud/deploy-fork.d.ts.map +1 -0
  45. package/dist/cmd/cloud/deploy-fork.js +258 -0
  46. package/dist/cmd/cloud/deploy-fork.js.map +1 -0
  47. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  48. package/dist/cmd/cloud/deploy.js +125 -4
  49. package/dist/cmd/cloud/deploy.js.map +1 -1
  50. package/dist/cmd/cloud/sandbox/create.d.ts.map +1 -1
  51. package/dist/cmd/cloud/sandbox/create.js +18 -0
  52. package/dist/cmd/cloud/sandbox/create.js.map +1 -1
  53. package/dist/cmd/cloud/sandbox/delete.d.ts.map +1 -1
  54. package/dist/cmd/cloud/sandbox/delete.js +2 -6
  55. package/dist/cmd/cloud/sandbox/delete.js.map +1 -1
  56. package/dist/cmd/cloud/sandbox/download.d.ts +3 -0
  57. package/dist/cmd/cloud/sandbox/download.d.ts.map +1 -0
  58. package/dist/cmd/cloud/sandbox/download.js +89 -0
  59. package/dist/cmd/cloud/sandbox/download.js.map +1 -0
  60. package/dist/cmd/cloud/sandbox/env.d.ts +3 -0
  61. package/dist/cmd/cloud/sandbox/env.d.ts.map +1 -0
  62. package/dist/cmd/cloud/sandbox/env.js +90 -0
  63. package/dist/cmd/cloud/sandbox/env.js.map +1 -0
  64. package/dist/cmd/cloud/sandbox/get.d.ts.map +1 -1
  65. package/dist/cmd/cloud/sandbox/get.js +24 -0
  66. package/dist/cmd/cloud/sandbox/get.js.map +1 -1
  67. package/dist/cmd/cloud/sandbox/index.d.ts.map +1 -1
  68. package/dist/cmd/cloud/sandbox/index.js +14 -0
  69. package/dist/cmd/cloud/sandbox/index.js.map +1 -1
  70. package/dist/cmd/cloud/sandbox/ls.d.ts +3 -0
  71. package/dist/cmd/cloud/sandbox/ls.d.ts.map +1 -0
  72. package/dist/cmd/cloud/sandbox/ls.js +119 -0
  73. package/dist/cmd/cloud/sandbox/ls.js.map +1 -0
  74. package/dist/cmd/cloud/sandbox/mkdir.d.ts +3 -0
  75. package/dist/cmd/cloud/sandbox/mkdir.d.ts.map +1 -0
  76. package/dist/cmd/cloud/sandbox/mkdir.js +59 -0
  77. package/dist/cmd/cloud/sandbox/mkdir.js.map +1 -0
  78. package/dist/cmd/cloud/sandbox/rm.d.ts +3 -0
  79. package/dist/cmd/cloud/sandbox/rm.d.ts.map +1 -0
  80. package/dist/cmd/cloud/sandbox/rm.js +45 -0
  81. package/dist/cmd/cloud/sandbox/rm.js.map +1 -0
  82. package/dist/cmd/cloud/sandbox/rmdir.d.ts +3 -0
  83. package/dist/cmd/cloud/sandbox/rmdir.d.ts.map +1 -0
  84. package/dist/cmd/cloud/sandbox/rmdir.js +59 -0
  85. package/dist/cmd/cloud/sandbox/rmdir.js.map +1 -0
  86. package/dist/cmd/cloud/sandbox/snapshot/create.d.ts.map +1 -1
  87. package/dist/cmd/cloud/sandbox/snapshot/create.js +0 -2
  88. package/dist/cmd/cloud/sandbox/snapshot/create.js.map +1 -1
  89. package/dist/cmd/cloud/sandbox/snapshot/get.d.ts.map +1 -1
  90. package/dist/cmd/cloud/sandbox/snapshot/get.js +0 -2
  91. package/dist/cmd/cloud/sandbox/snapshot/get.js.map +1 -1
  92. package/dist/cmd/cloud/sandbox/snapshot/list.d.ts.map +1 -1
  93. package/dist/cmd/cloud/sandbox/snapshot/list.js +0 -3
  94. package/dist/cmd/cloud/sandbox/snapshot/list.js.map +1 -1
  95. package/dist/cmd/cloud/sandbox/upload.d.ts +3 -0
  96. package/dist/cmd/cloud/sandbox/upload.d.ts.map +1 -0
  97. package/dist/cmd/cloud/sandbox/upload.js +77 -0
  98. package/dist/cmd/cloud/sandbox/upload.js.map +1 -0
  99. package/dist/cmd/cloud/ssh.d.ts.map +1 -1
  100. package/dist/cmd/cloud/ssh.js +9 -3
  101. package/dist/cmd/cloud/ssh.js.map +1 -1
  102. package/dist/cmd/dev/index.d.ts.map +1 -1
  103. package/dist/cmd/dev/index.js +34 -19
  104. package/dist/cmd/dev/index.js.map +1 -1
  105. package/dist/cmd/dev/sync.d.ts.map +1 -1
  106. package/dist/cmd/dev/sync.js +8 -14
  107. package/dist/cmd/dev/sync.js.map +1 -1
  108. package/dist/cmd/git/account/add.d.ts +17 -0
  109. package/dist/cmd/git/account/add.d.ts.map +1 -0
  110. package/dist/cmd/git/account/add.js +244 -0
  111. package/dist/cmd/git/account/add.js.map +1 -0
  112. package/dist/cmd/git/account/index.d.ts +3 -0
  113. package/dist/cmd/git/account/index.d.ts.map +1 -0
  114. package/dist/cmd/git/account/index.js +11 -0
  115. package/dist/cmd/git/account/index.js.map +1 -0
  116. package/dist/cmd/git/account/list.d.ts +2 -0
  117. package/dist/cmd/git/account/list.d.ts.map +1 -0
  118. package/dist/cmd/git/account/list.js +111 -0
  119. package/dist/cmd/git/account/list.js.map +1 -0
  120. package/dist/cmd/git/account/remove.d.ts +2 -0
  121. package/dist/cmd/git/account/remove.d.ts.map +1 -0
  122. package/dist/cmd/git/account/remove.js +171 -0
  123. package/dist/cmd/git/account/remove.js.map +1 -0
  124. package/dist/cmd/git/index.d.ts +3 -0
  125. package/dist/cmd/git/index.d.ts.map +1 -0
  126. package/dist/cmd/git/index.js +19 -0
  127. package/dist/cmd/git/index.js.map +1 -0
  128. package/dist/cmd/git/link.d.ts +32 -0
  129. package/dist/cmd/git/link.d.ts.map +1 -0
  130. package/dist/cmd/git/link.js +357 -0
  131. package/dist/cmd/git/link.js.map +1 -0
  132. package/dist/cmd/git/list.d.ts +2 -0
  133. package/dist/cmd/git/list.d.ts.map +1 -0
  134. package/dist/cmd/git/list.js +137 -0
  135. package/dist/cmd/git/list.js.map +1 -0
  136. package/dist/cmd/git/status.d.ts +2 -0
  137. package/dist/cmd/git/status.d.ts.map +1 -0
  138. package/dist/cmd/git/status.js +119 -0
  139. package/dist/cmd/git/status.js.map +1 -0
  140. package/dist/cmd/git/unlink.d.ts +2 -0
  141. package/dist/cmd/git/unlink.d.ts.map +1 -0
  142. package/dist/cmd/git/unlink.js +98 -0
  143. package/dist/cmd/git/unlink.js.map +1 -0
  144. package/dist/cmd/index.d.ts.map +1 -1
  145. package/dist/cmd/index.js +2 -0
  146. package/dist/cmd/index.js.map +1 -1
  147. package/dist/cmd/integration/api.d.ts +61 -0
  148. package/dist/cmd/integration/api.d.ts.map +1 -0
  149. package/dist/cmd/integration/api.js +176 -0
  150. package/dist/cmd/integration/api.js.map +1 -0
  151. package/dist/cmd/integration/github/connect.d.ts +2 -0
  152. package/dist/cmd/integration/github/connect.d.ts.map +1 -0
  153. package/dist/cmd/integration/github/connect.js +197 -0
  154. package/dist/cmd/integration/github/connect.js.map +1 -0
  155. package/dist/cmd/integration/github/disconnect.d.ts +2 -0
  156. package/dist/cmd/integration/github/disconnect.d.ts.map +1 -0
  157. package/dist/cmd/integration/github/disconnect.js +121 -0
  158. package/dist/cmd/integration/github/disconnect.js.map +1 -0
  159. package/dist/cmd/integration/github/index.d.ts +2 -0
  160. package/dist/cmd/integration/github/index.d.ts.map +1 -0
  161. package/dist/cmd/integration/github/index.js +21 -0
  162. package/dist/cmd/integration/github/index.js.map +1 -0
  163. package/dist/cmd/integration/index.d.ts +2 -0
  164. package/dist/cmd/integration/index.d.ts.map +1 -0
  165. package/dist/cmd/integration/index.js +16 -0
  166. package/dist/cmd/integration/index.js.map +1 -0
  167. package/dist/config.d.ts +2 -0
  168. package/dist/config.d.ts.map +1 -1
  169. package/dist/config.js +25 -1
  170. package/dist/config.js.map +1 -1
  171. package/dist/errors.d.ts +2 -1
  172. package/dist/errors.d.ts.map +1 -1
  173. package/dist/errors.js +5 -0
  174. package/dist/errors.js.map +1 -1
  175. package/dist/log-collector.d.ts +30 -0
  176. package/dist/log-collector.d.ts.map +1 -0
  177. package/dist/log-collector.js +74 -0
  178. package/dist/log-collector.js.map +1 -0
  179. package/dist/output.d.ts.map +1 -1
  180. package/dist/output.js +2 -1
  181. package/dist/output.js.map +1 -1
  182. package/dist/steps.d.ts.map +1 -1
  183. package/dist/steps.js +48 -3
  184. package/dist/steps.js.map +1 -1
  185. package/dist/tui/box.d.ts.map +1 -1
  186. package/dist/tui/box.js +1 -6
  187. package/dist/tui/box.js.map +1 -1
  188. package/dist/tui/symbols.d.ts.map +1 -1
  189. package/dist/tui/symbols.js +4 -0
  190. package/dist/tui/symbols.js.map +1 -1
  191. package/dist/tui.d.ts +21 -12
  192. package/dist/tui.d.ts.map +1 -1
  193. package/dist/tui.js +74 -25
  194. package/dist/tui.js.map +1 -1
  195. package/dist/types.d.ts +73 -1
  196. package/dist/types.d.ts.map +1 -1
  197. package/dist/types.js +4 -0
  198. package/dist/types.js.map +1 -1
  199. package/dist/typescript-errors.d.ts.map +1 -1
  200. package/dist/typescript-errors.js +2 -2
  201. package/dist/typescript-errors.js.map +1 -1
  202. package/package.json +6 -6
  203. package/src/agents-docs.ts +42 -8
  204. package/src/cli.ts +20 -4
  205. package/src/cmd/auth/apikey.ts +36 -0
  206. package/src/cmd/auth/index.ts +9 -1
  207. package/src/cmd/build/ast.ts +120 -2
  208. package/src/cmd/build/entry-generator.ts +157 -10
  209. package/src/cmd/build/vite/agent-discovery.ts +8 -5
  210. package/src/cmd/build/vite/index.ts +3 -2
  211. package/src/cmd/build/vite/metadata-generator.ts +1 -1
  212. package/src/cmd/build/vite/registry-generator.ts +125 -24
  213. package/src/cmd/build/vite/route-discovery.ts +20 -0
  214. package/src/cmd/build/vite/vite-builder.ts +3 -2
  215. package/src/cmd/cloud/deploy-fork.ts +296 -0
  216. package/src/cmd/cloud/deploy.ts +148 -4
  217. package/src/cmd/cloud/sandbox/create.ts +22 -0
  218. package/src/cmd/cloud/sandbox/delete.ts +2 -6
  219. package/src/cmd/cloud/sandbox/download.ts +96 -0
  220. package/src/cmd/cloud/sandbox/env.ts +104 -0
  221. package/src/cmd/cloud/sandbox/get.ts +22 -0
  222. package/src/cmd/cloud/sandbox/index.ts +14 -0
  223. package/src/cmd/cloud/sandbox/ls.ts +126 -0
  224. package/src/cmd/cloud/sandbox/mkdir.ts +65 -0
  225. package/src/cmd/cloud/sandbox/rm.ts +51 -0
  226. package/src/cmd/cloud/sandbox/rmdir.ts +65 -0
  227. package/src/cmd/cloud/sandbox/snapshot/create.ts +0 -2
  228. package/src/cmd/cloud/sandbox/snapshot/get.ts +0 -2
  229. package/src/cmd/cloud/sandbox/snapshot/list.ts +0 -3
  230. package/src/cmd/cloud/sandbox/upload.ts +83 -0
  231. package/src/cmd/cloud/ssh.ts +13 -3
  232. package/src/cmd/dev/index.ts +49 -31
  233. package/src/cmd/dev/sync.ts +26 -30
  234. package/src/cmd/git/account/add.ts +317 -0
  235. package/src/cmd/git/account/index.ts +12 -0
  236. package/src/cmd/git/account/list.ts +139 -0
  237. package/src/cmd/git/account/remove.ts +212 -0
  238. package/src/cmd/git/index.ts +20 -0
  239. package/src/cmd/git/link.ts +468 -0
  240. package/src/cmd/git/list.ts +161 -0
  241. package/src/cmd/git/status.ts +144 -0
  242. package/src/cmd/git/unlink.ts +117 -0
  243. package/src/cmd/index.ts +2 -0
  244. package/src/cmd/integration/api.ts +379 -0
  245. package/src/cmd/integration/github/connect.ts +242 -0
  246. package/src/cmd/integration/github/disconnect.ts +149 -0
  247. package/src/cmd/integration/github/index.ts +21 -0
  248. package/src/cmd/integration/index.ts +16 -0
  249. package/src/config.ts +35 -1
  250. package/src/errors.ts +7 -0
  251. package/src/log-collector.ts +77 -0
  252. package/src/output.ts +2 -1
  253. package/src/steps.ts +52 -4
  254. package/src/tui/box.ts +1 -7
  255. package/src/tui/symbols.ts +5 -0
  256. package/src/tui.ts +77 -25
  257. package/src/types.ts +89 -0
  258. package/src/typescript-errors.ts +2 -1
@@ -103,6 +103,7 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
103
103
  logger,
104
104
  mode: dev ? 'dev' : 'prod',
105
105
  workbench: workbenchConfig.enabled ? workbenchConfig : undefined,
106
+ analytics: config?.analytics,
106
107
  });
107
108
 
108
109
  // Finally, build with Bun.build
@@ -165,7 +166,7 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
165
166
  const isLocalRegion = options.region === 'local';
166
167
  const cdnDomain = isLocalRegion
167
168
  ? 'localstack-static-assets.t3.storage.dev'
168
- : 'static.agentuity.com';
169
+ : 'cdn.agentuity.com';
169
170
  const cdnBaseUrl =
170
171
  !dev && deploymentId ? `https://${cdnDomain}/${deploymentId}/client/` : undefined;
171
172
 
@@ -305,7 +306,7 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
305
306
  // Generate agent and route registries for type augmentation BEFORE builds
306
307
  // (TypeScript needs these files to exist during type checking)
307
308
  generateAgentRegistry(srcDir, agentMetadata);
308
- generateRouteRegistry(srcDir, routeInfoList);
309
+ await generateRouteRegistry(srcDir, routeInfoList);
309
310
  logger.debug('Agent and route registries generated');
310
311
 
311
312
  // Check if web frontend exists
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Deploy fork wrapper
3
+ *
4
+ * This module implements a fork-based deployment wrapper that:
5
+ * 1. Spawns the deploy command as a child process using bunx
6
+ * 2. Tees stdout/stderr to both the terminal and a Pulse stream
7
+ * 3. On failure, sends diagnostics to the API
8
+ *
9
+ * This approach captures crashes, Bun runtime issues, and all output
10
+ * for debugging failed deployments.
11
+ */
12
+
13
+ import { spawn, type Subprocess } from 'bun';
14
+ import { tmpdir } from 'node:os';
15
+ import { join } from 'node:path';
16
+ import { existsSync, readFileSync, unlinkSync } from 'node:fs';
17
+ import type { APIClient } from '../../api';
18
+ import { getUserAgent } from '../../api';
19
+ import { isUnicode } from '../../tui/symbols';
20
+ import { projectDeploymentFail, type ClientDiagnostics, type Deployment } from '@agentuity/server';
21
+ import type { Logger } from '@agentuity/core';
22
+
23
+ export interface ForkDeployOptions {
24
+ projectDir: string;
25
+ apiClient: APIClient;
26
+ logger: Logger;
27
+ sdkKey: string;
28
+ deployment: Deployment;
29
+ args: string[];
30
+ }
31
+
32
+ export interface ForkDeployResult {
33
+ success: boolean;
34
+ exitCode: number;
35
+ diagnostics?: ClientDiagnostics;
36
+ }
37
+
38
+ /**
39
+ * Stream data to a Pulse stream URL
40
+ */
41
+ async function streamToPulse(
42
+ streamURL: string,
43
+ sdkKey: string,
44
+ data: string,
45
+ logger: Logger
46
+ ): Promise<void> {
47
+ try {
48
+ const response = await fetch(streamURL, {
49
+ method: 'PUT',
50
+ headers: {
51
+ 'Content-Type': 'text/plain',
52
+ Authorization: `Bearer ${sdkKey}`,
53
+ 'User-Agent': getUserAgent(),
54
+ },
55
+ body: data,
56
+ });
57
+
58
+ if (!response.ok) {
59
+ logger.error('Failed to stream to Pulse: %s', response.status);
60
+ }
61
+ } catch (err) {
62
+ logger.error('Error streaming to Pulse: %s', err);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Run the deploy command as a forked child process
68
+ */
69
+ export async function runForkedDeploy(options: ForkDeployOptions): Promise<ForkDeployResult> {
70
+ const { projectDir, apiClient, logger, sdkKey, deployment, args } = options;
71
+
72
+ const deploymentId = deployment.id;
73
+ const buildLogsStreamURL = deployment.buildLogsStreamURL;
74
+ const reportFile = join(tmpdir(), `agentuity-deploy-${deploymentId}.json`);
75
+ const cleanLogsFile = join(tmpdir(), `agentuity-deploy-${deploymentId}-logs.txt`);
76
+ let outputBuffer = '';
77
+ let proc: Subprocess | null = null;
78
+
79
+ try {
80
+ const childArgs = [
81
+ 'agentuity',
82
+ 'deploy',
83
+ '--child-mode',
84
+ `--report-file=${reportFile}`,
85
+ ...args,
86
+ ];
87
+
88
+ // Pass the deployment info via environment variable (same format as CI builds)
89
+ const deploymentEnvValue = JSON.stringify({
90
+ id: deployment.id,
91
+ orgId: deployment.orgId,
92
+ publicKey: deployment.publicKey,
93
+ });
94
+
95
+ logger.debug('Spawning child deploy process: bunx %s', childArgs.join(' '));
96
+
97
+ // Get terminal dimensions to pass to child
98
+ const columns = process.stdout.columns || 80;
99
+ const rows = process.stdout.rows || 24;
100
+
101
+ proc = spawn({
102
+ cmd: ['bunx', ...childArgs],
103
+ cwd: projectDir,
104
+ env: {
105
+ ...process.env,
106
+ AGENTUITY_FORK_PARENT: '1',
107
+ AGENTUITY_DEPLOYMENT: deploymentEnvValue,
108
+ // Force color and unicode output since child stdout/stderr are piped (not TTY)
109
+ FORCE_COLOR: '1',
110
+ // Only force unicode if parent terminal supports it
111
+ ...(isUnicode ? { FORCE_UNICODE: '1' } : {}),
112
+ // Pass terminal dimensions
113
+ COLUMNS: String(columns),
114
+ LINES: String(rows),
115
+ // Enable clean log collection for Pulse streaming
116
+ AGENTUITY_CLEAN_LOGS_FILE: cleanLogsFile,
117
+ },
118
+ stdin: 'inherit',
119
+ stdout: 'pipe',
120
+ stderr: 'pipe',
121
+ });
122
+
123
+ const handleOutput = async (stream: ReadableStream<Uint8Array>, isStderr: boolean) => {
124
+ const reader = stream.getReader();
125
+ const decoder = new TextDecoder();
126
+ const target = isStderr ? process.stderr : process.stdout;
127
+
128
+ try {
129
+ while (true) {
130
+ const { done, value } = await reader.read();
131
+ if (done) break;
132
+
133
+ const text = decoder.decode(value, { stream: true });
134
+ outputBuffer += text;
135
+ target.write(value);
136
+ }
137
+ } catch (err) {
138
+ logger.debug('Stream read error: %s', err);
139
+ }
140
+ };
141
+
142
+ const stdoutPromise =
143
+ proc.stdout && typeof proc.stdout !== 'number'
144
+ ? handleOutput(proc.stdout, false)
145
+ : Promise.resolve();
146
+ const stderrPromise =
147
+ proc.stderr && typeof proc.stderr !== 'number'
148
+ ? handleOutput(proc.stderr, true)
149
+ : Promise.resolve();
150
+
151
+ await Promise.all([stdoutPromise, stderrPromise]);
152
+
153
+ const exitCode = await proc.exited;
154
+ logger.debug('Child process exited with code: %d', exitCode);
155
+
156
+ let diagnostics: ClientDiagnostics | undefined;
157
+
158
+ if (existsSync(reportFile)) {
159
+ try {
160
+ const reportContent = readFileSync(reportFile, 'utf-8');
161
+ diagnostics = JSON.parse(reportContent) as ClientDiagnostics;
162
+ unlinkSync(reportFile);
163
+ } catch (err) {
164
+ logger.debug('Failed to read report file: %s', err);
165
+ }
166
+ }
167
+
168
+ // Stream clean logs to Pulse (prefer clean logs over raw output)
169
+ if (buildLogsStreamURL) {
170
+ let logsContent = '';
171
+ if (existsSync(cleanLogsFile)) {
172
+ try {
173
+ logsContent = readFileSync(cleanLogsFile, 'utf-8');
174
+ unlinkSync(cleanLogsFile);
175
+ } catch (err) {
176
+ logger.debug('Failed to read clean logs file: %s', err);
177
+ }
178
+ }
179
+ // Fall back to raw output if no clean logs
180
+ if (!logsContent && outputBuffer) {
181
+ logsContent = outputBuffer;
182
+ }
183
+ if (logsContent) {
184
+ await streamToPulse(buildLogsStreamURL, sdkKey, logsContent, logger);
185
+ }
186
+ }
187
+
188
+ if (exitCode !== 0) {
189
+ const errorMessage = `Deploy process exited with code ${exitCode}`;
190
+
191
+ if (!diagnostics) {
192
+ diagnostics = {
193
+ success: false,
194
+ errors: [
195
+ {
196
+ type: 'general',
197
+ scope: 'deploy',
198
+ message: errorMessage,
199
+ code: 'DEPLOY_CRASH',
200
+ },
201
+ ],
202
+ warnings: [],
203
+ diagnostics: [],
204
+ error: errorMessage,
205
+ };
206
+ } else if (!diagnostics.error) {
207
+ diagnostics.error = errorMessage;
208
+ }
209
+
210
+ try {
211
+ await projectDeploymentFail(apiClient, deploymentId, {
212
+ error: errorMessage,
213
+ diagnostics,
214
+ });
215
+ } catch (err) {
216
+ logger.error('Failed to report deployment failure: %s', err);
217
+ }
218
+
219
+ return { success: false, exitCode, diagnostics };
220
+ }
221
+
222
+ return { success: true, exitCode, diagnostics };
223
+ } catch (err) {
224
+ const errorMessage = err instanceof Error ? err.message : String(err);
225
+ logger.error('Fork deploy error: %s', errorMessage);
226
+
227
+ if (buildLogsStreamURL) {
228
+ let logsContent = '';
229
+ if (existsSync(cleanLogsFile)) {
230
+ try {
231
+ logsContent = readFileSync(cleanLogsFile, 'utf-8');
232
+ unlinkSync(cleanLogsFile);
233
+ } catch {
234
+ // ignore
235
+ }
236
+ }
237
+ if (!logsContent) {
238
+ logsContent = outputBuffer;
239
+ }
240
+ logsContent += `\n\n--- FORK ERROR ---\n${errorMessage}\n`;
241
+ await streamToPulse(buildLogsStreamURL, sdkKey, logsContent, logger);
242
+ }
243
+
244
+ try {
245
+ await projectDeploymentFail(apiClient, deploymentId, {
246
+ error: errorMessage,
247
+ diagnostics: {
248
+ success: false,
249
+ errors: [
250
+ {
251
+ type: 'general',
252
+ scope: 'deploy',
253
+ message: errorMessage,
254
+ code: 'DEPLOY_FORK_ERROR',
255
+ },
256
+ ],
257
+ warnings: [],
258
+ diagnostics: [],
259
+ error: errorMessage,
260
+ },
261
+ });
262
+ } catch (failErr) {
263
+ logger.error('Failed to report deployment failure: %s', failErr);
264
+ }
265
+
266
+ return {
267
+ success: false,
268
+ exitCode: 1,
269
+ diagnostics: {
270
+ success: false,
271
+ errors: [
272
+ {
273
+ type: 'general',
274
+ scope: 'deploy',
275
+ message: errorMessage,
276
+ code: 'DEPLOY_FORK_ERROR',
277
+ },
278
+ ],
279
+ warnings: [],
280
+ diagnostics: [],
281
+ error: errorMessage,
282
+ },
283
+ };
284
+ } finally {
285
+ // Clean up temp files
286
+ for (const file of [reportFile, cleanLogsFile]) {
287
+ if (existsSync(file)) {
288
+ try {
289
+ unlinkSync(file);
290
+ } catch {
291
+ // ignore
292
+ }
293
+ }
294
+ }
295
+ }
296
+ }
@@ -8,7 +8,14 @@ import { isRunningFromExecutable } from '../upgrade';
8
8
  import { createSubcommand, DeployOptionsSchema } from '../../types';
9
9
  import { getUserAgent } from '../../api';
10
10
  import * as tui from '../../tui';
11
- import { saveProjectDir, getDefaultConfigDir, loadProjectSDKKey } from '../../config';
11
+ import {
12
+ saveProjectDir,
13
+ getDefaultConfigDir,
14
+ loadProjectSDKKey,
15
+ updateProjectConfig,
16
+ } from '../../config';
17
+ import { getProjectGithubStatus } from '../integration/api';
18
+ import { runGitLink } from '../git/link';
12
19
  import {
13
20
  runSteps,
14
21
  stepSuccess,
@@ -46,6 +53,7 @@ import * as domain from '../../domain';
46
53
  import { ErrorCode } from '../../errors';
47
54
  import { typecheck } from '../build/typecheck';
48
55
  import { BuildReportCollector, setGlobalCollector, clearGlobalCollector } from '../../build-report';
56
+ import { runForkedDeploy } from './deploy-fork';
49
57
 
50
58
  const DeploymentCancelledError = StructuredError(
51
59
  'DeploymentCancelled',
@@ -100,6 +108,11 @@ export const deploySubcommand = createSubcommand({
100
108
  .describe(
101
109
  'file path to save build report JSON with errors, warnings, and diagnostics'
102
110
  ),
111
+ childMode: z
112
+ .boolean()
113
+ .optional()
114
+ .default(false)
115
+ .describe('Internal: run as forked child process'),
103
116
  })
104
117
  ),
105
118
  response: DeployResponseSchema,
@@ -133,8 +146,67 @@ export const deploySubcommand = createSubcommand({
133
146
  );
134
147
  }
135
148
 
136
- // Check for pre-created deployment from CI build environment
149
+ // Check if we're running as a forked child process
150
+ const isChildProcess = opts.childMode || process.env.AGENTUITY_FORK_PARENT === '1';
137
151
  const deploymentEnv = process.env.AGENTUITY_DEPLOYMENT;
152
+
153
+ // If not in child mode and no pre-created deployment, run as fork wrapper to capture crashes
154
+ // (CI builds set AGENTUITY_DEPLOYMENT, fork wrapper also sets it for the child)
155
+ if (!isChildProcess && !deploymentEnv) {
156
+ logger.debug('Running deploy as fork wrapper');
157
+
158
+ // First, create the deployment to get the ID, publicKey, and stream URL
159
+ const deploymentConfig = project.deployment ?? {};
160
+ const initialDeployment = await projectDeploymentCreate(
161
+ apiClient,
162
+ project.projectId,
163
+ deploymentConfig
164
+ );
165
+
166
+ logger.debug('Created deployment: %s', initialDeployment.id);
167
+
168
+ // Build args to pass to child, excluding child-mode specific ones
169
+ const childArgs: string[] = [];
170
+ if (opts.logsUrl) childArgs.push(`--logs-url=${opts.logsUrl}`);
171
+ if (opts.trigger) childArgs.push(`--trigger=${opts.trigger}`);
172
+ if (opts.commitUrl) childArgs.push(`--commit-url=${opts.commitUrl}`);
173
+ if (opts.message) childArgs.push(`--message=${opts.message}`);
174
+ if (opts.commit) childArgs.push(`--commit=${opts.commit}`);
175
+ if (opts.branch) childArgs.push(`--branch=${opts.branch}`);
176
+ if (opts.provider) childArgs.push(`--provider=${opts.provider}`);
177
+ if (opts.repo) childArgs.push(`--repo=${opts.repo}`);
178
+ if (opts.event) childArgs.push(`--event=${opts.event}`);
179
+ if (opts.pullRequestNumber)
180
+ childArgs.push(`--pull-request-number=${opts.pullRequestNumber}`);
181
+ if (opts.pullRequestUrl) childArgs.push(`--pull-request-url=${opts.pullRequestUrl}`);
182
+
183
+ const result = await runForkedDeploy({
184
+ projectDir,
185
+ apiClient,
186
+ logger,
187
+ sdkKey: sdkKey!,
188
+ deployment: initialDeployment,
189
+ args: childArgs,
190
+ });
191
+
192
+ if (!result.success) {
193
+ const appUrl = getAppBaseURL(
194
+ process.env.AGENTUITY_REGION ?? config?.name,
195
+ config?.overrides
196
+ );
197
+ const deploymentLink = `${appUrl}/projects/${project.projectId}/deployments/${initialDeployment.id}`;
198
+ tui.fatal(
199
+ `Deployment failed: ${tui.link(deploymentLink, 'Deployment Page')}`,
200
+ ErrorCode.BUILD_FAILED
201
+ );
202
+ }
203
+
204
+ return {
205
+ success: true,
206
+ deploymentId: initialDeployment.id,
207
+ projectId: project.projectId,
208
+ };
209
+ }
138
210
  let useExistingDeployment = false;
139
211
  if (deploymentEnv) {
140
212
  const ExistingDeploymentSchema = z.object({
@@ -163,6 +235,76 @@ export const deploySubcommand = createSubcommand({
163
235
  try {
164
236
  await saveProjectDir(projectDir);
165
237
 
238
+ // Check GitHub status and prompt for setup if not linked
239
+ // Skip in non-TTY environments (CI, automated runs) to prevent hanging
240
+ const hasTTY = process.stdin.isTTY && process.stdout.isTTY;
241
+ if (!useExistingDeployment && !project.skipGitSetup && hasTTY) {
242
+ try {
243
+ const githubStatus = await getProjectGithubStatus(apiClient, project.projectId);
244
+
245
+ if (githubStatus.linked && githubStatus.autoDeploy) {
246
+ // GitHub is already set up with auto-deploy, tell user to push instead
247
+ tui.newline();
248
+ tui.info(
249
+ `This project is linked to ${tui.bold(githubStatus.repoFullName ?? 'GitHub')} with automatic deployments enabled.`
250
+ );
251
+ tui.newline();
252
+ tui.info(
253
+ `Push a commit to the ${tui.bold(githubStatus.branch ?? 'main')} branch to trigger a deployment.`
254
+ );
255
+ tui.newline();
256
+ throw new DeploymentCancelledError();
257
+ }
258
+
259
+ if (!githubStatus.linked) {
260
+ tui.newline();
261
+ const wantSetup = await tui.confirm(
262
+ 'Would you like to set up automatic deployments from GitHub?'
263
+ );
264
+
265
+ if (wantSetup) {
266
+ const result = await runGitLink({
267
+ apiClient,
268
+ projectId: project.projectId,
269
+ orgId: project.orgId,
270
+ logger,
271
+ skipAlreadyLinkedCheck: true,
272
+ config,
273
+ });
274
+
275
+ if (result.linked && result.autoDeploy) {
276
+ // GitHub linked with auto-deploy, tell user to push instead
277
+ tui.newline();
278
+ tui.info('GitHub integration set up successfully!');
279
+ tui.newline();
280
+ tui.info('Push a commit to trigger your first deployment.');
281
+ tui.newline();
282
+ throw new DeploymentCancelledError();
283
+ } else if (result.linked) {
284
+ // Linked but auto-deploy disabled, continue with manual deploy
285
+ tui.newline();
286
+ tui.info('GitHub repository linked. Continuing with deployment...');
287
+ tui.newline();
288
+ }
289
+ } else {
290
+ await updateProjectConfig(projectDir, { skipGitSetup: true }, config);
291
+ tui.newline();
292
+ tui.info(
293
+ `Skipping GitHub setup. Run ${tui.bold(getCommand('git link'))} later to enable it.`
294
+ );
295
+ tui.newline();
296
+ }
297
+ }
298
+ } catch (err) {
299
+ // Re-throw intentional cancellations
300
+ if (err instanceof DeploymentCancelledError) {
301
+ throw err;
302
+ }
303
+ // Log other errors as non-fatal and continue
304
+ logger.trace('Failed to check GitHub status: %s', err);
305
+ }
306
+ }
307
+
166
308
  await runSteps(
167
309
  [
168
310
  !project.deployment?.domains?.length
@@ -190,7 +332,9 @@ export const deploySubcommand = createSubcommand({
190
332
  label: 'Sync Env & Secrets',
191
333
  run: async () => {
192
334
  try {
193
- if (useExistingDeployment) {
335
+ const isCIBuild =
336
+ useExistingDeployment && process.env.AGENTUITY_FORK_PARENT !== '1';
337
+ if (isCIBuild) {
194
338
  return stepSkipped('skipped in CI build');
195
339
  }
196
340
  // Read env file
@@ -230,7 +374,7 @@ export const deploySubcommand = createSubcommand({
230
374
  label: 'Create Deployment',
231
375
  run: async () => {
232
376
  if (useExistingDeployment) {
233
- return stepSkipped('skipped in CI build');
377
+ return stepSkipped('using pre-created deployment');
234
378
  }
235
379
  try {
236
380
  deployment = await projectDeploymentCreate(
@@ -4,6 +4,12 @@ import * as tui from '../../../tui';
4
4
  import { createSandboxClient, parseFileArgs } from './util';
5
5
  import { getCommand } from '../../../command-prefix';
6
6
  import { sandboxCreate } from '@agentuity/server';
7
+ import { StructuredError } from '@agentuity/core';
8
+
9
+ const InvalidMetadataError = StructuredError(
10
+ 'InvalidMetadataError',
11
+ 'Metadata must be a valid JSON object'
12
+ );
7
13
 
8
14
  const SandboxCreateResponseSchema = z.object({
9
15
  sandboxId: z.string().describe('Unique sandbox identifier'),
@@ -55,6 +61,7 @@ export const createSubcommand = createCommand({
55
61
  .array(z.string())
56
62
  .optional()
57
63
  .describe('Apt packages to install (can be specified multiple times)'),
64
+ metadata: z.string().optional().describe('JSON object of user-defined metadata'),
58
65
  }),
59
66
  response: SandboxCreateResponseSchema,
60
67
  },
@@ -77,6 +84,20 @@ export const createSubcommand = createCommand({
77
84
  const files = parseFileArgs(opts.file);
78
85
  const hasFiles = files.length > 0;
79
86
 
87
+ let metadata: Record<string, unknown> | undefined;
88
+ if (opts.metadata) {
89
+ let parsed: unknown;
90
+ try {
91
+ parsed = JSON.parse(opts.metadata);
92
+ } catch {
93
+ throw new InvalidMetadataError();
94
+ }
95
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
96
+ throw new InvalidMetadataError();
97
+ }
98
+ metadata = parsed as Record<string, unknown>;
99
+ }
100
+
80
101
  const result = await sandboxCreate(client, {
81
102
  options: {
82
103
  resources:
@@ -93,6 +114,7 @@ export const createSubcommand = createCommand({
93
114
  command: hasFiles ? { exec: [], files } : undefined,
94
115
  snapshot: opts.snapshot,
95
116
  dependencies: opts.dependency,
117
+ metadata,
96
118
  },
97
119
  orgId,
98
120
  });
@@ -14,7 +14,7 @@ const SandboxDeleteResponseSchema = z.object({
14
14
 
15
15
  export const deleteSubcommand = createCommand({
16
16
  name: 'delete',
17
- aliases: ['del', 'rm', 'remove', 'destroy'],
17
+ aliases: ['del', 'remove', 'destroy'],
18
18
  description: 'Delete a sandbox',
19
19
  tags: ['destructive', 'deletes-resource', 'slow', 'requires-auth'],
20
20
  requires: { auth: true, region: true, org: true },
@@ -25,11 +25,7 @@ export const deleteSubcommand = createCommand({
25
25
  description: 'Delete a sandbox',
26
26
  },
27
27
  {
28
- command: getCommand('cloud sandbox rm abc123'),
29
- description: 'Delete using alias',
30
- },
31
- {
32
- command: getCommand('cloud sandbox rm abc123 --confirm'),
28
+ command: getCommand('cloud sandbox delete abc123 --confirm'),
33
29
  description: 'Delete without confirmation prompt',
34
30
  },
35
31
  ],
@@ -0,0 +1,96 @@
1
+ import { z } from 'zod';
2
+ import { writeFileSync } from 'node:fs';
3
+ import { createCommand } from '../../../types';
4
+ import * as tui from '../../../tui';
5
+ import { createSandboxClient } from './util';
6
+ import { getCommand } from '../../../command-prefix';
7
+ import { sandboxDownloadArchive } from '@agentuity/server';
8
+
9
+ export const downloadSubcommand = createCommand({
10
+ name: 'download',
11
+ aliases: ['dl'],
12
+ description: 'Download files from a sandbox as a compressed archive',
13
+ tags: ['slow', 'requires-auth'],
14
+ requires: { auth: true, region: true, org: true },
15
+ examples: [
16
+ {
17
+ command: getCommand('cloud sandbox download sbx_abc123 ./backup.tar.gz'),
18
+ description: 'Download sandbox files as tar.gz archive',
19
+ },
20
+ {
21
+ command: getCommand('cloud sandbox download sbx_abc123 ./backup.zip --format zip'),
22
+ description: 'Download sandbox files as zip archive',
23
+ },
24
+ {
25
+ command: getCommand('cloud sandbox download sbx_abc123 ./backup.tar.gz --path /subdir'),
26
+ description: 'Download only a specific directory',
27
+ },
28
+ ],
29
+ schema: {
30
+ args: z.object({
31
+ sandboxId: z.string().describe('The sandbox ID'),
32
+ output: z.string().describe('Output file path for the archive'),
33
+ }),
34
+ options: z.object({
35
+ path: z.string().optional().describe('Path in sandbox to download (defaults to root)'),
36
+ format: z
37
+ .enum(['zip', 'tar.gz'])
38
+ .default('tar.gz')
39
+ .optional()
40
+ .describe('Archive format (zip or tar.gz)'),
41
+ }),
42
+ response: z.object({
43
+ success: z.boolean(),
44
+ output: z.string(),
45
+ bytes: z.number(),
46
+ }),
47
+ },
48
+
49
+ async handler(ctx) {
50
+ const { args, opts, options, auth, region, logger, orgId } = ctx;
51
+
52
+ const client = createSandboxClient(logger, auth, region);
53
+ const format = opts.format || 'tar.gz';
54
+
55
+ const stream = await sandboxDownloadArchive(client, {
56
+ sandboxId: args.sandboxId,
57
+ path: opts.path || '.',
58
+ format,
59
+ orgId,
60
+ });
61
+
62
+ const chunks: Uint8Array[] = [];
63
+ const reader = stream.getReader();
64
+
65
+ while (true) {
66
+ const { done, value } = await reader.read();
67
+ if (done) break;
68
+ chunks.push(value);
69
+ }
70
+
71
+ const totalBytes = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
72
+ const buffer = new Uint8Array(totalBytes);
73
+ let offset = 0;
74
+ for (const chunk of chunks) {
75
+ buffer.set(chunk, offset);
76
+ offset += chunk.length;
77
+ }
78
+
79
+ writeFileSync(args.output, buffer);
80
+
81
+ if (!options.json) {
82
+ tui.success(`Downloaded ${formatSize(totalBytes)} to ${args.output}`);
83
+ }
84
+
85
+ return { success: true, output: args.output, bytes: totalBytes };
86
+ },
87
+ });
88
+
89
+ function formatSize(bytes: number): string {
90
+ if (bytes < 1024) return `${bytes} bytes`;
91
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
92
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
93
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
94
+ }
95
+
96
+ export default downloadSubcommand;