@agentuity/cli 1.0.56 → 1.0.58
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/dist/cmd/build/ci.d.ts.map +1 -1
- package/dist/cmd/build/ci.js +21 -5
- package/dist/cmd/build/ci.js.map +1 -1
- package/dist/cmd/cloud/deploy-fork.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy-fork.js +36 -15
- package/dist/cmd/cloud/deploy-fork.js.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/build.js +2 -2
- package/dist/cmd/cloud/sandbox/snapshot/build.js.map +1 -1
- package/dist/errors.d.ts +24 -10
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +42 -12
- package/dist/errors.js.map +1 -1
- package/dist/schema-generator.d.ts.map +1 -1
- package/dist/schema-generator.js +2 -12
- package/dist/schema-generator.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +6 -0
- package/dist/tui.js.map +1 -1
- package/dist/utils/stream-capture.d.ts +9 -0
- package/dist/utils/stream-capture.d.ts.map +1 -0
- package/dist/utils/stream-capture.js +34 -0
- package/dist/utils/stream-capture.js.map +1 -0
- package/package.json +6 -6
- package/src/cmd/build/ci.ts +21 -5
- package/src/cmd/cloud/deploy-fork.ts +39 -16
- package/src/cmd/cloud/sandbox/snapshot/build.ts +2 -2
- package/src/errors.ts +44 -12
- package/src/schema-generator.ts +2 -12
- package/src/tui.ts +6 -0
- package/src/utils/stream-capture.ts +39 -0
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { spawn, type Subprocess } from 'bun';
|
|
14
14
|
import { tmpdir } from 'node:os';
|
|
15
15
|
import { join } from 'node:path';
|
|
16
|
-
import { existsSync, readFileSync, unlinkSync } from 'node:fs';
|
|
16
|
+
import { appendFileSync, createWriteStream, existsSync, readFileSync, unlinkSync } from 'node:fs';
|
|
17
17
|
import type { APIClient } from '../../api';
|
|
18
18
|
import { getUserAgent } from '../../api';
|
|
19
19
|
import { isUnicode } from '../../tui/symbols';
|
|
@@ -37,12 +37,14 @@ export interface ForkDeployResult {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* Stream data to a Pulse stream URL
|
|
40
|
+
* Stream data to a Pulse stream URL.
|
|
41
|
+
* Accepts a string, Blob/BunFile, or ReadableStream as the body to avoid
|
|
42
|
+
* loading large outputs into memory.
|
|
41
43
|
*/
|
|
42
44
|
async function streamToPulse(
|
|
43
45
|
streamURL: string,
|
|
44
46
|
sdkKey: string,
|
|
45
|
-
data: string
|
|
47
|
+
data: string | Blob | ReadableStream<Uint8Array>,
|
|
46
48
|
logger: Logger
|
|
47
49
|
): Promise<void> {
|
|
48
50
|
try {
|
|
@@ -74,7 +76,8 @@ export async function runForkedDeploy(options: ForkDeployOptions): Promise<ForkD
|
|
|
74
76
|
const buildLogsStreamURL = deployment.buildLogsStreamURL;
|
|
75
77
|
const reportFile = join(tmpdir(), `agentuity-deploy-${deploymentId}.json`);
|
|
76
78
|
const cleanLogsFile = join(tmpdir(), `agentuity-deploy-${deploymentId}-logs.txt`);
|
|
77
|
-
|
|
79
|
+
const rawLogsFile = join(tmpdir(), `agentuity-deploy-${deploymentId}-raw.txt`);
|
|
80
|
+
const rawLogsWriter = createWriteStream(rawLogsFile);
|
|
78
81
|
let proc: Subprocess | null = null;
|
|
79
82
|
let cancelled = false;
|
|
80
83
|
|
|
@@ -195,7 +198,6 @@ export async function runForkedDeploy(options: ForkDeployOptions): Promise<ForkD
|
|
|
195
198
|
|
|
196
199
|
const handleOutput = async (stream: ReadableStream<Uint8Array>, isStderr: boolean) => {
|
|
197
200
|
const reader = stream.getReader();
|
|
198
|
-
const decoder = new TextDecoder();
|
|
199
201
|
const target = isStderr ? process.stderr : process.stdout;
|
|
200
202
|
|
|
201
203
|
try {
|
|
@@ -203,8 +205,9 @@ export async function runForkedDeploy(options: ForkDeployOptions): Promise<ForkD
|
|
|
203
205
|
const { done, value } = await reader.read();
|
|
204
206
|
if (done) break;
|
|
205
207
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
+
// Stream raw bytes to disk instead of accumulating in memory.
|
|
209
|
+
// This prevents OOM / ERR_STRING_TOO_LONG crashes on large builds.
|
|
210
|
+
rawLogsWriter.write(value);
|
|
208
211
|
target.write(value);
|
|
209
212
|
}
|
|
210
213
|
} catch (err) {
|
|
@@ -223,6 +226,11 @@ export async function runForkedDeploy(options: ForkDeployOptions): Promise<ForkD
|
|
|
223
226
|
|
|
224
227
|
await Promise.all([stdoutPromise, stderrPromise]);
|
|
225
228
|
|
|
229
|
+
// Close the raw logs writer so the file is fully flushed before reading
|
|
230
|
+
await new Promise<void>((resolve) => {
|
|
231
|
+
rawLogsWriter.end(resolve);
|
|
232
|
+
});
|
|
233
|
+
|
|
226
234
|
const exitCode = await proc.exited;
|
|
227
235
|
logger.debug('Child process exited with code: %d', exitCode);
|
|
228
236
|
|
|
@@ -249,12 +257,11 @@ export async function runForkedDeploy(options: ForkDeployOptions): Promise<ForkD
|
|
|
249
257
|
logger.debug('Failed to read clean logs file: %s', err);
|
|
250
258
|
}
|
|
251
259
|
}
|
|
252
|
-
// Fall back to raw output if no clean logs
|
|
253
|
-
if (!logsContent && outputBuffer) {
|
|
254
|
-
logsContent = outputBuffer;
|
|
255
|
-
}
|
|
256
260
|
if (logsContent) {
|
|
257
261
|
await streamToPulse(buildLogsStreamURL, sdkKey, logsContent, logger);
|
|
262
|
+
} else if (existsSync(rawLogsFile)) {
|
|
263
|
+
// Stream raw logs file directly to Pulse without loading into memory
|
|
264
|
+
await streamToPulse(buildLogsStreamURL, sdkKey, Bun.file(rawLogsFile), logger);
|
|
258
265
|
}
|
|
259
266
|
}
|
|
260
267
|
|
|
@@ -307,11 +314,27 @@ export async function runForkedDeploy(options: ForkDeployOptions): Promise<ForkD
|
|
|
307
314
|
// ignore
|
|
308
315
|
}
|
|
309
316
|
}
|
|
310
|
-
if (
|
|
311
|
-
logsContent
|
|
317
|
+
if (logsContent) {
|
|
318
|
+
logsContent += `\n\n--- FORK ERROR ---\n${errorMessage}\n`;
|
|
319
|
+
await streamToPulse(buildLogsStreamURL, sdkKey, logsContent, logger);
|
|
320
|
+
} else {
|
|
321
|
+
// Append error to raw logs file and stream it without loading into memory
|
|
322
|
+
try {
|
|
323
|
+
appendFileSync(rawLogsFile, `\n\n--- FORK ERROR ---\n${errorMessage}\n`);
|
|
324
|
+
} catch {
|
|
325
|
+
// ignore — file may not exist if child never produced output
|
|
326
|
+
}
|
|
327
|
+
if (existsSync(rawLogsFile)) {
|
|
328
|
+
await streamToPulse(buildLogsStreamURL, sdkKey, Bun.file(rawLogsFile), logger);
|
|
329
|
+
} else {
|
|
330
|
+
await streamToPulse(
|
|
331
|
+
buildLogsStreamURL,
|
|
332
|
+
sdkKey,
|
|
333
|
+
`--- FORK ERROR ---\n${errorMessage}\n`,
|
|
334
|
+
logger
|
|
335
|
+
);
|
|
336
|
+
}
|
|
312
337
|
}
|
|
313
|
-
logsContent += `\n\n--- FORK ERROR ---\n${errorMessage}\n`;
|
|
314
|
-
await streamToPulse(buildLogsStreamURL, sdkKey, logsContent, logger);
|
|
315
338
|
}
|
|
316
339
|
|
|
317
340
|
try {
|
|
@@ -360,7 +383,7 @@ export async function runForkedDeploy(options: ForkDeployOptions): Promise<ForkD
|
|
|
360
383
|
process.off('SIGTERM', sigtermHandler);
|
|
361
384
|
|
|
362
385
|
// Clean up temp files
|
|
363
|
-
for (const file of [reportFile, cleanLogsFile]) {
|
|
386
|
+
for (const file of [reportFile, cleanLogsFile, rawLogsFile]) {
|
|
364
387
|
if (existsSync(file)) {
|
|
365
388
|
try {
|
|
366
389
|
unlinkSync(file);
|
|
@@ -17,7 +17,7 @@ import { z } from 'zod';
|
|
|
17
17
|
import { getCommand } from '../../../../command-prefix';
|
|
18
18
|
import { getCatalystAPIClient } from '../../../../config';
|
|
19
19
|
import { encryptFIPSKEMDEMStream } from '../../../../crypto/box';
|
|
20
|
-
import { ErrorCode } from '../../../../errors';
|
|
20
|
+
import { ErrorCode, getExitCode } from '../../../../errors';
|
|
21
21
|
import * as tui from '../../../../tui';
|
|
22
22
|
import { createCommand } from '../../../../types';
|
|
23
23
|
import { validateAptDependencies } from '../../../../utils/apt-validator';
|
|
@@ -941,7 +941,7 @@ export const buildSubcommand = createCommand({
|
|
|
941
941
|
2
|
|
942
942
|
)
|
|
943
943
|
);
|
|
944
|
-
process.exit(ErrorCode.MALWARE_DETECTED);
|
|
944
|
+
process.exit(getExitCode(ErrorCode.MALWARE_DETECTED));
|
|
945
945
|
}
|
|
946
946
|
|
|
947
947
|
console.log('');
|
package/src/errors.ts
CHANGED
|
@@ -1,22 +1,51 @@
|
|
|
1
1
|
import type { Logger } from './types';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Standard exit codes for the CLI
|
|
4
|
+
* Standard exit codes for the CLI.
|
|
5
|
+
*
|
|
6
|
+
* Values start at 10 to avoid collisions with Unix signal numbers (1-9)
|
|
7
|
+
* and shell conventions (e.g. 2 = misuse of builtins, 9 = SIGKILL/OOM).
|
|
8
|
+
* Range 10-125 is safe — below shell-reserved 126-128 and signal-death
|
|
9
|
+
* codes 128+N.
|
|
10
|
+
*
|
|
11
|
+
* 0 and 1 are kept as universal success/failure codes.
|
|
5
12
|
*/
|
|
6
13
|
export enum ExitCode {
|
|
7
14
|
SUCCESS = 0,
|
|
8
15
|
GENERAL_ERROR = 1,
|
|
9
|
-
VALIDATION_ERROR =
|
|
10
|
-
AUTH_ERROR =
|
|
11
|
-
NOT_FOUND =
|
|
12
|
-
PERMISSION_ERROR =
|
|
13
|
-
NETWORK_ERROR =
|
|
14
|
-
FILE_ERROR =
|
|
15
|
-
USER_CANCELLED =
|
|
16
|
-
BUILD_FAILED =
|
|
17
|
-
SECURITY_ERROR =
|
|
16
|
+
VALIDATION_ERROR = 10,
|
|
17
|
+
AUTH_ERROR = 11,
|
|
18
|
+
NOT_FOUND = 12,
|
|
19
|
+
PERMISSION_ERROR = 13,
|
|
20
|
+
NETWORK_ERROR = 14,
|
|
21
|
+
FILE_ERROR = 15,
|
|
22
|
+
USER_CANCELLED = 16,
|
|
23
|
+
BUILD_FAILED = 17,
|
|
24
|
+
SECURITY_ERROR = 18,
|
|
25
|
+
PAYMENT_REQUIRED = 19,
|
|
26
|
+
UPGRADE_REQUIRED = 20,
|
|
18
27
|
}
|
|
19
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Human-readable descriptions for each exit code.
|
|
31
|
+
* This is the single source of truth consumed by the schema generator and AI help.
|
|
32
|
+
*/
|
|
33
|
+
export const exitCodeDescriptions: Record<number, string> = {
|
|
34
|
+
[ExitCode.SUCCESS]: 'Success',
|
|
35
|
+
[ExitCode.GENERAL_ERROR]: 'General error',
|
|
36
|
+
[ExitCode.VALIDATION_ERROR]: 'Validation error (invalid arguments or options)',
|
|
37
|
+
[ExitCode.AUTH_ERROR]: 'Authentication error (login required or credentials invalid)',
|
|
38
|
+
[ExitCode.NOT_FOUND]: 'Resource not found (project, file, deployment, etc.)',
|
|
39
|
+
[ExitCode.PERMISSION_ERROR]: 'Permission denied (insufficient access rights)',
|
|
40
|
+
[ExitCode.NETWORK_ERROR]: 'Network error (API unreachable or timeout)',
|
|
41
|
+
[ExitCode.FILE_ERROR]: 'File system error (file read/write failed)',
|
|
42
|
+
[ExitCode.USER_CANCELLED]: 'User cancelled (operation aborted by user)',
|
|
43
|
+
[ExitCode.BUILD_FAILED]: 'Build failed',
|
|
44
|
+
[ExitCode.SECURITY_ERROR]: 'Security error (malware detected)',
|
|
45
|
+
[ExitCode.PAYMENT_REQUIRED]: 'Payment required (plan upgrade needed)',
|
|
46
|
+
[ExitCode.UPGRADE_REQUIRED]: 'Upgrade required (CLI version too old)',
|
|
47
|
+
};
|
|
48
|
+
|
|
20
49
|
/**
|
|
21
50
|
* Standard error codes for the CLI
|
|
22
51
|
*/
|
|
@@ -152,7 +181,11 @@ export function getExitCode(errorCode: ErrorCode): ExitCode {
|
|
|
152
181
|
|
|
153
182
|
// Payment required - user needs to upgrade their plan
|
|
154
183
|
case ErrorCode.PAYMENT_REQUIRED:
|
|
155
|
-
return ExitCode.
|
|
184
|
+
return ExitCode.PAYMENT_REQUIRED;
|
|
185
|
+
|
|
186
|
+
// Upgrade required - CLI version too old
|
|
187
|
+
case ErrorCode.UPGRADE_REQUIRED:
|
|
188
|
+
return ExitCode.UPGRADE_REQUIRED;
|
|
156
189
|
|
|
157
190
|
// Resource conflicts and other errors
|
|
158
191
|
case ErrorCode.RESOURCE_ALREADY_EXISTS:
|
|
@@ -162,7 +195,6 @@ export function getExitCode(errorCode: ErrorCode): ExitCode {
|
|
|
162
195
|
case ErrorCode.RUNTIME_ERROR:
|
|
163
196
|
case ErrorCode.INTERNAL_ERROR:
|
|
164
197
|
case ErrorCode.NOT_IMPLEMENTED:
|
|
165
|
-
case ErrorCode.UPGRADE_REQUIRED:
|
|
166
198
|
default:
|
|
167
199
|
return ExitCode.GENERAL_ERROR;
|
|
168
200
|
}
|
package/src/schema-generator.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import type { CommandDefinition, SubcommandDefinition, CommandSchemas } from './types';
|
|
3
|
+
import { exitCodeDescriptions } from './errors';
|
|
3
4
|
import { parseArgsSchema, parseOptionsSchema } from './schema-parser';
|
|
4
5
|
import * as z from 'zod';
|
|
5
6
|
|
|
@@ -313,18 +314,7 @@ export function generateCLISchema(
|
|
|
313
314
|
name: 'agentuity',
|
|
314
315
|
version,
|
|
315
316
|
description: 'Agentuity CLI',
|
|
316
|
-
exitCodes: {
|
|
317
|
-
0: 'Success',
|
|
318
|
-
1: 'General error',
|
|
319
|
-
2: 'Validation error (invalid arguments or options)',
|
|
320
|
-
3: 'Authentication error (login required or credentials invalid)',
|
|
321
|
-
4: 'Resource not found (project, file, deployment, etc.)',
|
|
322
|
-
5: 'Permission denied (insufficient access rights)',
|
|
323
|
-
6: 'Network error (API unreachable or timeout)',
|
|
324
|
-
7: 'File system error (file read/write failed)',
|
|
325
|
-
8: 'User cancelled (operation aborted by user)',
|
|
326
|
-
9: 'Build failed',
|
|
327
|
-
},
|
|
317
|
+
exitCodes: { ...exitCodeDescriptions },
|
|
328
318
|
globalOptions: [
|
|
329
319
|
{
|
|
330
320
|
name: 'config',
|
package/src/tui.ts
CHANGED
|
@@ -25,6 +25,12 @@ function ensureCursorRestoration(): void {
|
|
|
25
25
|
exitHandlerInstalled = true;
|
|
26
26
|
|
|
27
27
|
const restoreCursor = () => {
|
|
28
|
+
// Only write ANSI escape sequences when stderr is a real terminal.
|
|
29
|
+
// Writing to non-TTY streams (pipes, command substitution, etc.)
|
|
30
|
+
// pollutes captured output with invisible control characters.
|
|
31
|
+
if (!process.stderr.isTTY) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
28
34
|
// Skip cursor restoration in CI - terminals don't support these sequences
|
|
29
35
|
if (process.env.CI) {
|
|
30
36
|
return;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createWriteStream } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stream a ReadableStream of raw bytes to a file on disk.
|
|
5
|
+
*
|
|
6
|
+
* This mirrors the pattern used by the deploy fork wrapper to capture child
|
|
7
|
+
* process stdout/stderr without accumulating the output in memory. Returns
|
|
8
|
+
* the total number of bytes written.
|
|
9
|
+
*/
|
|
10
|
+
export async function captureStreamToFile(
|
|
11
|
+
stream: ReadableStream<Uint8Array>,
|
|
12
|
+
filePath: string
|
|
13
|
+
): Promise<number> {
|
|
14
|
+
const writer = createWriteStream(filePath);
|
|
15
|
+
const reader = stream.getReader();
|
|
16
|
+
let totalBytes = 0;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
while (true) {
|
|
20
|
+
const { done, value } = await reader.read();
|
|
21
|
+
if (done) break;
|
|
22
|
+
|
|
23
|
+
const ok = writer.write(value);
|
|
24
|
+
totalBytes += value.byteLength;
|
|
25
|
+
|
|
26
|
+
// Respect backpressure: wait for drain when the internal buffer is full
|
|
27
|
+
if (!ok) {
|
|
28
|
+
await new Promise<void>((resolve) => writer.once('drain', resolve));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} finally {
|
|
32
|
+
await new Promise<void>((resolve, reject) => {
|
|
33
|
+
writer.once('error', reject);
|
|
34
|
+
writer.end(resolve);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return totalBytes;
|
|
39
|
+
}
|