@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.
- package/bin/cli.ts +4 -0
- package/dist/agents-docs.d.ts +5 -4
- package/dist/agents-docs.d.ts.map +1 -1
- package/dist/agents-docs.js +28 -8
- package/dist/agents-docs.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +19 -4
- package/dist/cli.js.map +1 -1
- package/dist/cmd/auth/apikey.d.ts +2 -0
- package/dist/cmd/auth/apikey.d.ts.map +1 -0
- package/dist/cmd/auth/apikey.js +31 -0
- package/dist/cmd/auth/apikey.js.map +1 -0
- package/dist/cmd/auth/index.d.ts.map +1 -1
- package/dist/cmd/auth/index.js +9 -1
- package/dist/cmd/auth/index.js.map +1 -1
- package/dist/cmd/build/ast.d.ts.map +1 -1
- package/dist/cmd/build/ast.js +103 -2
- package/dist/cmd/build/ast.js.map +1 -1
- package/dist/cmd/build/entry-generator.d.ts +2 -1
- package/dist/cmd/build/entry-generator.d.ts.map +1 -1
- package/dist/cmd/build/entry-generator.js +152 -9
- package/dist/cmd/build/entry-generator.js.map +1 -1
- package/dist/cmd/build/vite/agent-discovery.d.ts +1 -1
- package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/agent-discovery.js +7 -6
- package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
- package/dist/cmd/build/vite/index.d.ts.map +1 -1
- package/dist/cmd/build/vite/index.js +3 -2
- package/dist/cmd/build/vite/index.js.map +1 -1
- package/dist/cmd/build/vite/metadata-generator.js +1 -1
- package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
- package/dist/cmd/build/vite/registry-generator.d.ts +1 -1
- package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/registry-generator.js +115 -23
- package/dist/cmd/build/vite/registry-generator.js.map +1 -1
- package/dist/cmd/build/vite/route-discovery.d.ts +6 -0
- package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/route-discovery.js +19 -0
- package/dist/cmd/build/vite/route-discovery.js.map +1 -1
- package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-builder.js +3 -2
- package/dist/cmd/build/vite/vite-builder.js.map +1 -1
- package/dist/cmd/cloud/deploy-fork.d.ts +32 -0
- package/dist/cmd/cloud/deploy-fork.d.ts.map +1 -0
- package/dist/cmd/cloud/deploy-fork.js +258 -0
- package/dist/cmd/cloud/deploy-fork.js.map +1 -0
- package/dist/cmd/cloud/deploy.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.js +125 -4
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/cmd/cloud/sandbox/create.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/create.js +18 -0
- package/dist/cmd/cloud/sandbox/create.js.map +1 -1
- package/dist/cmd/cloud/sandbox/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/delete.js +2 -6
- package/dist/cmd/cloud/sandbox/delete.js.map +1 -1
- package/dist/cmd/cloud/sandbox/download.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/download.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/download.js +89 -0
- package/dist/cmd/cloud/sandbox/download.js.map +1 -0
- package/dist/cmd/cloud/sandbox/env.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/env.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/env.js +90 -0
- package/dist/cmd/cloud/sandbox/env.js.map +1 -0
- package/dist/cmd/cloud/sandbox/get.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/get.js +24 -0
- package/dist/cmd/cloud/sandbox/get.js.map +1 -1
- package/dist/cmd/cloud/sandbox/index.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/index.js +14 -0
- package/dist/cmd/cloud/sandbox/index.js.map +1 -1
- package/dist/cmd/cloud/sandbox/ls.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/ls.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/ls.js +119 -0
- package/dist/cmd/cloud/sandbox/ls.js.map +1 -0
- package/dist/cmd/cloud/sandbox/mkdir.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/mkdir.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/mkdir.js +59 -0
- package/dist/cmd/cloud/sandbox/mkdir.js.map +1 -0
- package/dist/cmd/cloud/sandbox/rm.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/rm.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/rm.js +45 -0
- package/dist/cmd/cloud/sandbox/rm.js.map +1 -0
- package/dist/cmd/cloud/sandbox/rmdir.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/rmdir.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/rmdir.js +59 -0
- package/dist/cmd/cloud/sandbox/rmdir.js.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/create.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/create.js +0 -2
- package/dist/cmd/cloud/sandbox/snapshot/create.js.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/get.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/get.js +0 -2
- package/dist/cmd/cloud/sandbox/snapshot/get.js.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/list.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/list.js +0 -3
- package/dist/cmd/cloud/sandbox/snapshot/list.js.map +1 -1
- package/dist/cmd/cloud/sandbox/upload.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/upload.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/upload.js +77 -0
- package/dist/cmd/cloud/sandbox/upload.js.map +1 -0
- package/dist/cmd/cloud/ssh.d.ts.map +1 -1
- package/dist/cmd/cloud/ssh.js +9 -3
- package/dist/cmd/cloud/ssh.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +34 -19
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/dev/sync.d.ts.map +1 -1
- package/dist/cmd/dev/sync.js +8 -14
- package/dist/cmd/dev/sync.js.map +1 -1
- package/dist/cmd/git/account/add.d.ts +17 -0
- package/dist/cmd/git/account/add.d.ts.map +1 -0
- package/dist/cmd/git/account/add.js +244 -0
- package/dist/cmd/git/account/add.js.map +1 -0
- package/dist/cmd/git/account/index.d.ts +3 -0
- package/dist/cmd/git/account/index.d.ts.map +1 -0
- package/dist/cmd/git/account/index.js +11 -0
- package/dist/cmd/git/account/index.js.map +1 -0
- package/dist/cmd/git/account/list.d.ts +2 -0
- package/dist/cmd/git/account/list.d.ts.map +1 -0
- package/dist/cmd/git/account/list.js +111 -0
- package/dist/cmd/git/account/list.js.map +1 -0
- package/dist/cmd/git/account/remove.d.ts +2 -0
- package/dist/cmd/git/account/remove.d.ts.map +1 -0
- package/dist/cmd/git/account/remove.js +171 -0
- package/dist/cmd/git/account/remove.js.map +1 -0
- package/dist/cmd/git/index.d.ts +3 -0
- package/dist/cmd/git/index.d.ts.map +1 -0
- package/dist/cmd/git/index.js +19 -0
- package/dist/cmd/git/index.js.map +1 -0
- package/dist/cmd/git/link.d.ts +32 -0
- package/dist/cmd/git/link.d.ts.map +1 -0
- package/dist/cmd/git/link.js +357 -0
- package/dist/cmd/git/link.js.map +1 -0
- package/dist/cmd/git/list.d.ts +2 -0
- package/dist/cmd/git/list.d.ts.map +1 -0
- package/dist/cmd/git/list.js +137 -0
- package/dist/cmd/git/list.js.map +1 -0
- package/dist/cmd/git/status.d.ts +2 -0
- package/dist/cmd/git/status.d.ts.map +1 -0
- package/dist/cmd/git/status.js +119 -0
- package/dist/cmd/git/status.js.map +1 -0
- package/dist/cmd/git/unlink.d.ts +2 -0
- package/dist/cmd/git/unlink.d.ts.map +1 -0
- package/dist/cmd/git/unlink.js +98 -0
- package/dist/cmd/git/unlink.js.map +1 -0
- package/dist/cmd/index.d.ts.map +1 -1
- package/dist/cmd/index.js +2 -0
- package/dist/cmd/index.js.map +1 -1
- package/dist/cmd/integration/api.d.ts +61 -0
- package/dist/cmd/integration/api.d.ts.map +1 -0
- package/dist/cmd/integration/api.js +176 -0
- package/dist/cmd/integration/api.js.map +1 -0
- package/dist/cmd/integration/github/connect.d.ts +2 -0
- package/dist/cmd/integration/github/connect.d.ts.map +1 -0
- package/dist/cmd/integration/github/connect.js +197 -0
- package/dist/cmd/integration/github/connect.js.map +1 -0
- package/dist/cmd/integration/github/disconnect.d.ts +2 -0
- package/dist/cmd/integration/github/disconnect.d.ts.map +1 -0
- package/dist/cmd/integration/github/disconnect.js +121 -0
- package/dist/cmd/integration/github/disconnect.js.map +1 -0
- package/dist/cmd/integration/github/index.d.ts +2 -0
- package/dist/cmd/integration/github/index.d.ts.map +1 -0
- package/dist/cmd/integration/github/index.js +21 -0
- package/dist/cmd/integration/github/index.js.map +1 -0
- package/dist/cmd/integration/index.d.ts +2 -0
- package/dist/cmd/integration/index.d.ts.map +1 -0
- package/dist/cmd/integration/index.js +16 -0
- package/dist/cmd/integration/index.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +25 -1
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +2 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +5 -0
- package/dist/errors.js.map +1 -1
- package/dist/log-collector.d.ts +30 -0
- package/dist/log-collector.d.ts.map +1 -0
- package/dist/log-collector.js +74 -0
- package/dist/log-collector.js.map +1 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +2 -1
- package/dist/output.js.map +1 -1
- package/dist/steps.d.ts.map +1 -1
- package/dist/steps.js +48 -3
- package/dist/steps.js.map +1 -1
- package/dist/tui/box.d.ts.map +1 -1
- package/dist/tui/box.js +1 -6
- package/dist/tui/box.js.map +1 -1
- package/dist/tui/symbols.d.ts.map +1 -1
- package/dist/tui/symbols.js +4 -0
- package/dist/tui/symbols.js.map +1 -1
- package/dist/tui.d.ts +21 -12
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +74 -25
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +73 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -1
- package/dist/typescript-errors.d.ts.map +1 -1
- package/dist/typescript-errors.js +2 -2
- package/dist/typescript-errors.js.map +1 -1
- package/package.json +6 -6
- package/src/agents-docs.ts +42 -8
- package/src/cli.ts +20 -4
- package/src/cmd/auth/apikey.ts +36 -0
- package/src/cmd/auth/index.ts +9 -1
- package/src/cmd/build/ast.ts +120 -2
- package/src/cmd/build/entry-generator.ts +157 -10
- package/src/cmd/build/vite/agent-discovery.ts +8 -5
- package/src/cmd/build/vite/index.ts +3 -2
- package/src/cmd/build/vite/metadata-generator.ts +1 -1
- package/src/cmd/build/vite/registry-generator.ts +125 -24
- package/src/cmd/build/vite/route-discovery.ts +20 -0
- package/src/cmd/build/vite/vite-builder.ts +3 -2
- package/src/cmd/cloud/deploy-fork.ts +296 -0
- package/src/cmd/cloud/deploy.ts +148 -4
- package/src/cmd/cloud/sandbox/create.ts +22 -0
- package/src/cmd/cloud/sandbox/delete.ts +2 -6
- package/src/cmd/cloud/sandbox/download.ts +96 -0
- package/src/cmd/cloud/sandbox/env.ts +104 -0
- package/src/cmd/cloud/sandbox/get.ts +22 -0
- package/src/cmd/cloud/sandbox/index.ts +14 -0
- package/src/cmd/cloud/sandbox/ls.ts +126 -0
- package/src/cmd/cloud/sandbox/mkdir.ts +65 -0
- package/src/cmd/cloud/sandbox/rm.ts +51 -0
- package/src/cmd/cloud/sandbox/rmdir.ts +65 -0
- package/src/cmd/cloud/sandbox/snapshot/create.ts +0 -2
- package/src/cmd/cloud/sandbox/snapshot/get.ts +0 -2
- package/src/cmd/cloud/sandbox/snapshot/list.ts +0 -3
- package/src/cmd/cloud/sandbox/upload.ts +83 -0
- package/src/cmd/cloud/ssh.ts +13 -3
- package/src/cmd/dev/index.ts +49 -31
- package/src/cmd/dev/sync.ts +26 -30
- package/src/cmd/git/account/add.ts +317 -0
- package/src/cmd/git/account/index.ts +12 -0
- package/src/cmd/git/account/list.ts +139 -0
- package/src/cmd/git/account/remove.ts +212 -0
- package/src/cmd/git/index.ts +20 -0
- package/src/cmd/git/link.ts +468 -0
- package/src/cmd/git/list.ts +161 -0
- package/src/cmd/git/status.ts +144 -0
- package/src/cmd/git/unlink.ts +117 -0
- package/src/cmd/index.ts +2 -0
- package/src/cmd/integration/api.ts +379 -0
- package/src/cmd/integration/github/connect.ts +242 -0
- package/src/cmd/integration/github/disconnect.ts +149 -0
- package/src/cmd/integration/github/index.ts +21 -0
- package/src/cmd/integration/index.ts +16 -0
- package/src/config.ts +35 -1
- package/src/errors.ts +7 -0
- package/src/log-collector.ts +77 -0
- package/src/output.ts +2 -1
- package/src/steps.ts +52 -4
- package/src/tui/box.ts +1 -7
- package/src/tui/symbols.ts +5 -0
- package/src/tui.ts +77 -25
- package/src/types.ts +89 -0
- 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
|
-
: '
|
|
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
|
+
}
|
package/src/cmd/cloud/deploy.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
-
|
|
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('
|
|
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', '
|
|
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
|
|
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;
|