@agentuity/cli 0.0.111 → 0.1.0
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 +12 -2
- 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.map +1 -1
- package/dist/cmd/build/vite/agent-discovery.js +4 -3
- 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 +2 -1
- package/dist/cmd/build/vite/index.js.map +1 -1
- package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/registry-generator.js +45 -0
- package/dist/cmd/build/vite/registry-generator.js.map +1 -1
- package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-builder.js +2 -1
- 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 +62 -3
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/cmd/cloud/sandbox/get.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/get.js +19 -0
- package/dist/cmd/cloud/sandbox/get.js.map +1 -1
- 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 +18 -12
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/upgrade/index.d.ts.map +1 -1
- package/dist/cmd/upgrade/index.js +3 -3
- package/dist/cmd/upgrade/index.js.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.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 +71 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/typescript-errors.d.ts.map +1 -1
- package/dist/typescript-errors.js +42 -10
- package/dist/typescript-errors.js.map +1 -1
- package/package.json +5 -5
- package/src/agents-docs.ts +42 -8
- package/src/cli.ts +12 -2
- 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 +4 -1
- package/src/cmd/build/vite/index.ts +2 -1
- package/src/cmd/build/vite/registry-generator.ts +47 -0
- package/src/cmd/build/vite/vite-builder.ts +2 -1
- package/src/cmd/cloud/deploy-fork.ts +296 -0
- package/src/cmd/cloud/deploy.ts +70 -3
- package/src/cmd/cloud/sandbox/get.ts +17 -0
- package/src/cmd/cloud/ssh.ts +13 -3
- package/src/cmd/dev/index.ts +18 -13
- package/src/cmd/upgrade/index.ts +3 -4
- package/src/config.ts +1 -1
- 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 +85 -0
- package/src/typescript-errors.ts +46 -9
|
@@ -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
|
|
|
@@ -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
|
@@ -53,6 +53,7 @@ import * as domain from '../../domain';
|
|
|
53
53
|
import { ErrorCode } from '../../errors';
|
|
54
54
|
import { typecheck } from '../build/typecheck';
|
|
55
55
|
import { BuildReportCollector, setGlobalCollector, clearGlobalCollector } from '../../build-report';
|
|
56
|
+
import { runForkedDeploy } from './deploy-fork';
|
|
56
57
|
|
|
57
58
|
const DeploymentCancelledError = StructuredError(
|
|
58
59
|
'DeploymentCancelled',
|
|
@@ -107,6 +108,11 @@ export const deploySubcommand = createSubcommand({
|
|
|
107
108
|
.describe(
|
|
108
109
|
'file path to save build report JSON with errors, warnings, and diagnostics'
|
|
109
110
|
),
|
|
111
|
+
childMode: z
|
|
112
|
+
.boolean()
|
|
113
|
+
.optional()
|
|
114
|
+
.default(false)
|
|
115
|
+
.describe('Internal: run as forked child process'),
|
|
110
116
|
})
|
|
111
117
|
),
|
|
112
118
|
response: DeployResponseSchema,
|
|
@@ -140,8 +146,67 @@ export const deploySubcommand = createSubcommand({
|
|
|
140
146
|
);
|
|
141
147
|
}
|
|
142
148
|
|
|
143
|
-
// Check
|
|
149
|
+
// Check if we're running as a forked child process
|
|
150
|
+
const isChildProcess = opts.childMode || process.env.AGENTUITY_FORK_PARENT === '1';
|
|
144
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
|
+
}
|
|
145
210
|
let useExistingDeployment = false;
|
|
146
211
|
if (deploymentEnv) {
|
|
147
212
|
const ExistingDeploymentSchema = z.object({
|
|
@@ -267,7 +332,9 @@ export const deploySubcommand = createSubcommand({
|
|
|
267
332
|
label: 'Sync Env & Secrets',
|
|
268
333
|
run: async () => {
|
|
269
334
|
try {
|
|
270
|
-
|
|
335
|
+
const isCIBuild =
|
|
336
|
+
useExistingDeployment && process.env.AGENTUITY_FORK_PARENT !== '1';
|
|
337
|
+
if (isCIBuild) {
|
|
271
338
|
return stepSkipped('skipped in CI build');
|
|
272
339
|
}
|
|
273
340
|
// Read env file
|
|
@@ -307,7 +374,7 @@ export const deploySubcommand = createSubcommand({
|
|
|
307
374
|
label: 'Create Deployment',
|
|
308
375
|
run: async () => {
|
|
309
376
|
if (useExistingDeployment) {
|
|
310
|
-
return stepSkipped('
|
|
377
|
+
return stepSkipped('using pre-created deployment');
|
|
311
378
|
}
|
|
312
379
|
try {
|
|
313
380
|
deployment = await projectDeploymentCreate(
|
|
@@ -5,6 +5,12 @@ import { createSandboxClient } from './util';
|
|
|
5
5
|
import { getCommand } from '../../../command-prefix';
|
|
6
6
|
import { sandboxGet } from '@agentuity/server';
|
|
7
7
|
|
|
8
|
+
const SandboxResourcesSchema = z.object({
|
|
9
|
+
memory: z.string().optional().describe('Memory limit (e.g., "512Mi", "1Gi")'),
|
|
10
|
+
cpu: z.string().optional().describe('CPU limit (e.g., "500m", "1000m")'),
|
|
11
|
+
disk: z.string().optional().describe('Disk limit (e.g., "1Gi", "10Gi")'),
|
|
12
|
+
});
|
|
13
|
+
|
|
8
14
|
const SandboxGetResponseSchema = z.object({
|
|
9
15
|
sandboxId: z.string().describe('Sandbox ID'),
|
|
10
16
|
status: z.string().describe('Current status'),
|
|
@@ -17,6 +23,7 @@ const SandboxGetResponseSchema = z.object({
|
|
|
17
23
|
stderrStreamUrl: z.string().optional().describe('URL to stderr output stream'),
|
|
18
24
|
dependencies: z.array(z.string()).optional().describe('Apt packages installed'),
|
|
19
25
|
metadata: z.record(z.string(), z.unknown()).optional().describe('User-defined metadata'),
|
|
26
|
+
resources: SandboxResourcesSchema.optional().describe('Resource limits'),
|
|
20
27
|
});
|
|
21
28
|
|
|
22
29
|
export const getSubcommand = createCommand({
|
|
@@ -85,6 +92,15 @@ export const getSubcommand = createCommand({
|
|
|
85
92
|
if (result.dependencies && result.dependencies.length > 0) {
|
|
86
93
|
console.log(`${tui.muted('Dependencies:')} ${result.dependencies.join(', ')}`);
|
|
87
94
|
}
|
|
95
|
+
if (result.resources) {
|
|
96
|
+
const resourceParts: string[] = [];
|
|
97
|
+
if (result.resources.memory) resourceParts.push(`memory=${result.resources.memory}`);
|
|
98
|
+
if (result.resources.cpu) resourceParts.push(`cpu=${result.resources.cpu}`);
|
|
99
|
+
if (result.resources.disk) resourceParts.push(`disk=${result.resources.disk}`);
|
|
100
|
+
if (resourceParts.length > 0) {
|
|
101
|
+
console.log(`${tui.muted('Resources:')} ${resourceParts.join(', ')}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
88
104
|
if (result.metadata && Object.keys(result.metadata).length > 0) {
|
|
89
105
|
console.log(`${tui.muted('Metadata:')} ${JSON.stringify(result.metadata)}`);
|
|
90
106
|
}
|
|
@@ -102,6 +118,7 @@ export const getSubcommand = createCommand({
|
|
|
102
118
|
stderrStreamUrl: result.stderrStreamUrl,
|
|
103
119
|
dependencies: result.dependencies,
|
|
104
120
|
metadata: result.metadata,
|
|
121
|
+
resources: result.resources,
|
|
105
122
|
};
|
|
106
123
|
},
|
|
107
124
|
});
|
package/src/cmd/cloud/ssh.ts
CHANGED
|
@@ -4,7 +4,7 @@ import * as tui from '../../tui';
|
|
|
4
4
|
import { getIONHost } from '../../config';
|
|
5
5
|
import { getCommand } from '../../command-prefix';
|
|
6
6
|
const args = z.object({
|
|
7
|
-
identifier: z.string().optional().describe('The project or
|
|
7
|
+
identifier: z.string().optional().describe('The project, deployment, or sandbox id to use'),
|
|
8
8
|
command: z.string().optional().describe('The command to run'),
|
|
9
9
|
});
|
|
10
10
|
|
|
@@ -14,7 +14,7 @@ const options = z.object({
|
|
|
14
14
|
|
|
15
15
|
export const sshSubcommand = createSubcommand({
|
|
16
16
|
name: 'ssh',
|
|
17
|
-
description: 'SSH into a cloud project',
|
|
17
|
+
description: 'SSH into a cloud project or sandbox',
|
|
18
18
|
tags: ['read-only', 'slow', 'requires-auth', 'requires-deployment'],
|
|
19
19
|
idempotent: true,
|
|
20
20
|
examples: [
|
|
@@ -24,6 +24,10 @@ export const sshSubcommand = createSubcommand({
|
|
|
24
24
|
command: getCommand('cloud ssh deploy_abc123xyz'),
|
|
25
25
|
description: 'SSH into specific deployment',
|
|
26
26
|
},
|
|
27
|
+
{
|
|
28
|
+
command: getCommand('cloud ssh sbx_abc123xyz'),
|
|
29
|
+
description: 'SSH into a sandbox',
|
|
30
|
+
},
|
|
27
31
|
{ command: getCommand("cloud ssh 'ps aux'"), description: 'Run command and exit' },
|
|
28
32
|
{
|
|
29
33
|
command: getCommand("cloud ssh proj_abc123xyz 'tail -f /var/log/app.log'"),
|
|
@@ -47,7 +51,13 @@ export const sshSubcommand = createSubcommand({
|
|
|
47
51
|
let identifier = args?.identifier;
|
|
48
52
|
let command = args?.command;
|
|
49
53
|
|
|
50
|
-
if (
|
|
54
|
+
if (
|
|
55
|
+
!(
|
|
56
|
+
identifier?.startsWith('proj_') ||
|
|
57
|
+
identifier?.startsWith('deploy_') ||
|
|
58
|
+
identifier?.startsWith('sbx_')
|
|
59
|
+
)
|
|
60
|
+
) {
|
|
51
61
|
command = identifier;
|
|
52
62
|
identifier = undefined;
|
|
53
63
|
}
|
package/src/cmd/dev/index.ts
CHANGED
|
@@ -707,7 +707,7 @@ export const command = createCommand({
|
|
|
707
707
|
generateRouteRegistry(srcDir, routeInfoList);
|
|
708
708
|
logger.debug('Agent and route registries generated for dev mode');
|
|
709
709
|
|
|
710
|
-
// Step 3: Generate entry file with workbench config
|
|
710
|
+
// Step 3: Generate entry file with workbench and analytics config
|
|
711
711
|
// Note: vitePort is NOT passed here - the app reads process.env.VITE_PORT at runtime
|
|
712
712
|
const { generateEntryFile } = await import('../build/entry-generator');
|
|
713
713
|
await generateEntryFile({
|
|
@@ -717,6 +717,7 @@ export const command = createCommand({
|
|
|
717
717
|
logger,
|
|
718
718
|
mode: 'dev',
|
|
719
719
|
workbench: workbenchConfigData.enabled ? workbenchConfigData : undefined,
|
|
720
|
+
analytics: agentuityConfig?.analytics,
|
|
720
721
|
});
|
|
721
722
|
|
|
722
723
|
// Step 4: Bundle the app with LLM patches (dev mode = no minification)
|
|
@@ -783,19 +784,24 @@ export const command = createCommand({
|
|
|
783
784
|
console.log('');
|
|
784
785
|
fileWatcher.resume();
|
|
785
786
|
// wait for a file change or shutdown to trigger a recompile
|
|
786
|
-
while (
|
|
787
|
-
if (shutdownRequested) {
|
|
788
|
-
return;
|
|
789
|
-
}
|
|
790
|
-
if (shouldRestart) {
|
|
791
|
-
break;
|
|
792
|
-
}
|
|
787
|
+
while (!shutdownRequested && !shouldRestart) {
|
|
793
788
|
await tui.spinner({
|
|
794
789
|
message: 'Waiting for changes...',
|
|
795
790
|
clearOnSuccess: true,
|
|
796
|
-
callback: () =>
|
|
791
|
+
callback: async () => {
|
|
792
|
+
// Check more frequently so CTRL+C is responsive
|
|
793
|
+
for (let i = 0; i < 10; i++) {
|
|
794
|
+
if (shutdownRequested || shouldRestart) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
await Bun.sleep(100);
|
|
798
|
+
}
|
|
799
|
+
},
|
|
797
800
|
});
|
|
798
801
|
}
|
|
802
|
+
if (shutdownRequested) {
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
799
805
|
}
|
|
800
806
|
} catch (error) {
|
|
801
807
|
tui.error(`Failed to build dev bundle: ${error}`);
|
|
@@ -817,10 +823,6 @@ export const command = createCommand({
|
|
|
817
823
|
}
|
|
818
824
|
|
|
819
825
|
try {
|
|
820
|
-
// Set environment variables for LLM provider patches BEFORE starting server
|
|
821
|
-
// These must be set so the bundled patches can route LLM calls through AI Gateway
|
|
822
|
-
const serviceUrls = getServiceUrls(project?.region);
|
|
823
|
-
|
|
824
826
|
// Load SDK key from project .env files for AI Gateway routing
|
|
825
827
|
// This must be set so the bundled AI SDK patches can inject the API key
|
|
826
828
|
if (!process.env.AGENTUITY_SDK_KEY) {
|
|
@@ -848,6 +850,9 @@ export const command = createCommand({
|
|
|
848
850
|
process.env.AGENTUITY_PORT = process.env.PORT;
|
|
849
851
|
|
|
850
852
|
if (project) {
|
|
853
|
+
// Set environment variables for LLM provider patches
|
|
854
|
+
// These must be set so the bundled patches can route LLM calls through AI Gateway
|
|
855
|
+
const serviceUrls = getServiceUrls(project.region);
|
|
851
856
|
process.env.AGENTUITY_TRANSPORT_URL = serviceUrls.catalyst;
|
|
852
857
|
process.env.AGENTUITY_CATALYST_URL = serviceUrls.catalyst;
|
|
853
858
|
process.env.AGENTUITY_VECTOR_URL = serviceUrls.vector;
|
package/src/cmd/upgrade/index.ts
CHANGED
|
@@ -59,10 +59,9 @@ export function isRunningFromExecutable(): boolean {
|
|
|
59
59
|
const scriptPath = process.argv[1] || '';
|
|
60
60
|
|
|
61
61
|
// Check if running from compiled binary (uses Bun's virtual filesystem)
|
|
62
|
-
// When compiled with `bun build --compile`, the path is in the virtual /$bunfs/root/ directory
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (isCompiledBinary) {
|
|
62
|
+
// When compiled with `bun build --compile`, the script path is in the virtual /$bunfs/root/ directory
|
|
63
|
+
// Note: process.argv[0] is the executable path (e.g., /usr/local/bin/agentuity), not 'bun'
|
|
64
|
+
if (scriptPath.startsWith('/$bunfs/root/')) {
|
|
66
65
|
return true;
|
|
67
66
|
}
|
|
68
67
|
|
package/src/config.ts
CHANGED
|
@@ -556,7 +556,7 @@ export const InitialProjectConfigSchema = z.intersection(
|
|
|
556
556
|
type InitialProjectConfig = z.infer<typeof InitialProjectConfigSchema>;
|
|
557
557
|
|
|
558
558
|
export async function createProjectConfig(dir: string, config: InitialProjectConfig) {
|
|
559
|
-
const { sdkKey, ...sanitizedConfig } = config;
|
|
559
|
+
const { sdkKey, skipGitSetup: _skipGitSetup, ...sanitizedConfig } = config;
|
|
560
560
|
|
|
561
561
|
// generate the project config
|
|
562
562
|
const configPath = join(dir, 'agentuity.json');
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log Collector for clean build logs
|
|
3
|
+
*
|
|
4
|
+
* Provides a mechanism to collect clean, non-animated log output
|
|
5
|
+
* for streaming to external services (like Pulse) while keeping
|
|
6
|
+
* animated TUI output for the user's terminal.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* - Set AGENTUITY_CLEAN_LOGS_FILE env var to a file path
|
|
10
|
+
* - TUI components call appendLog() for final state messages
|
|
11
|
+
* - Logs are written to the file for the parent process to read
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { appendFileSync, writeFileSync } from 'node:fs';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get the clean logs file path from environment
|
|
18
|
+
*/
|
|
19
|
+
function getCleanLogsFile(): string | undefined {
|
|
20
|
+
return process.env.AGENTUITY_CLEAN_LOGS_FILE;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Disable log collection (called on write errors to prevent repeated failures)
|
|
25
|
+
*/
|
|
26
|
+
function disableLogCollection(error: unknown): void {
|
|
27
|
+
console.debug('Log collection disabled due to write error: %s', error);
|
|
28
|
+
delete process.env.AGENTUITY_CLEAN_LOGS_FILE;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if log collection is enabled (via environment variable)
|
|
33
|
+
*/
|
|
34
|
+
export function isLogCollectionEnabled(): boolean {
|
|
35
|
+
return !!getCleanLogsFile();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Initialize the clean logs file (clears any existing content)
|
|
40
|
+
*/
|
|
41
|
+
export function initCleanLogsFile(filePath: string): void {
|
|
42
|
+
try {
|
|
43
|
+
writeFileSync(filePath, '');
|
|
44
|
+
process.env.AGENTUITY_CLEAN_LOGS_FILE = filePath;
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.debug('Failed to initialize clean logs file: %s', err);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Append a clean log line (no ANSI codes, no animation)
|
|
52
|
+
* Only appends if collection is enabled
|
|
53
|
+
*/
|
|
54
|
+
export function appendLog(message: string): void {
|
|
55
|
+
const file = getCleanLogsFile();
|
|
56
|
+
if (file) {
|
|
57
|
+
try {
|
|
58
|
+
appendFileSync(file, message + '\n');
|
|
59
|
+
} catch (err) {
|
|
60
|
+
disableLogCollection(err);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Append multiple log lines
|
|
67
|
+
*/
|
|
68
|
+
export function appendLogs(messages: string[]): void {
|
|
69
|
+
const file = getCleanLogsFile();
|
|
70
|
+
if (file) {
|
|
71
|
+
try {
|
|
72
|
+
appendFileSync(file, messages.join('\n') + '\n');
|
|
73
|
+
} catch (err) {
|
|
74
|
+
disableLogCollection(err);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/output.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GlobalOptions } from './types';
|
|
2
|
+
import { isTTYLike } from './tui';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Output formatting utilities for agent-friendly CLI
|
|
@@ -59,7 +60,7 @@ export function shouldDisableColors(options: GlobalOptions): boolean {
|
|
|
59
60
|
return false;
|
|
60
61
|
}
|
|
61
62
|
// auto mode - disable in JSON/quiet mode or non-TTY
|
|
62
|
-
return options.json === true || options.quiet === true || !
|
|
63
|
+
return options.json === true || options.quiet === true || !isTTYLike();
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
/**
|