@agentuity/cli 1.0.44 → 1.0.46

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 (38) hide show
  1. package/bin/cli.ts +189 -143
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +45 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/cmd/cloud/sandbox/cp.d.ts.map +1 -1
  6. package/dist/cmd/cloud/sandbox/cp.js +69 -13
  7. package/dist/cmd/cloud/sandbox/cp.js.map +1 -1
  8. package/dist/cmd/cloud/sandbox/events.d.ts +3 -0
  9. package/dist/cmd/cloud/sandbox/events.d.ts.map +1 -0
  10. package/dist/cmd/cloud/sandbox/events.js +92 -0
  11. package/dist/cmd/cloud/sandbox/events.js.map +1 -0
  12. package/dist/cmd/cloud/sandbox/exec.d.ts.map +1 -1
  13. package/dist/cmd/cloud/sandbox/exec.js +22 -0
  14. package/dist/cmd/cloud/sandbox/exec.js.map +1 -1
  15. package/dist/cmd/cloud/sandbox/execution/get.d.ts.map +1 -1
  16. package/dist/cmd/cloud/sandbox/execution/get.js +5 -0
  17. package/dist/cmd/cloud/sandbox/execution/get.js.map +1 -1
  18. package/dist/cmd/cloud/sandbox/execution/list.d.ts.map +1 -1
  19. package/dist/cmd/cloud/sandbox/execution/list.js +12 -7
  20. package/dist/cmd/cloud/sandbox/execution/list.js.map +1 -1
  21. package/dist/cmd/cloud/sandbox/get.d.ts.map +1 -1
  22. package/dist/cmd/cloud/sandbox/get.js +1 -0
  23. package/dist/cmd/cloud/sandbox/get.js.map +1 -1
  24. package/dist/cmd/cloud/sandbox/index.d.ts.map +1 -1
  25. package/dist/cmd/cloud/sandbox/index.js +2 -0
  26. package/dist/cmd/cloud/sandbox/index.js.map +1 -1
  27. package/dist/cmd/cloud/sandbox/util.js +1 -1
  28. package/dist/cmd/cloud/sandbox/util.js.map +1 -1
  29. package/package.json +6 -6
  30. package/src/cli.ts +56 -2
  31. package/src/cmd/cloud/sandbox/cp.ts +89 -14
  32. package/src/cmd/cloud/sandbox/events.ts +108 -0
  33. package/src/cmd/cloud/sandbox/exec.ts +27 -0
  34. package/src/cmd/cloud/sandbox/execution/get.ts +5 -0
  35. package/src/cmd/cloud/sandbox/execution/list.ts +13 -14
  36. package/src/cmd/cloud/sandbox/get.ts +1 -0
  37. package/src/cmd/cloud/sandbox/index.ts +2 -0
  38. package/src/cmd/cloud/sandbox/util.ts +1 -1
package/bin/cli.ts CHANGED
@@ -22,7 +22,7 @@ import type { CommandContext, LogLevel } from '../src/types';
22
22
  import { generateCLISchema } from '../src/schema-generator';
23
23
  import { generateAIHelp } from '../src/ai-help';
24
24
  import { setOutputOptions } from '../src/output';
25
- import type { GlobalOptions } from '../src/types';
25
+ import type { Config, GlobalOptions } from '../src/types';
26
26
  import { ensureBunOnPath } from '../src/bun-path';
27
27
  import { checkForUpdates } from '../src/version-check';
28
28
  import { closeDatabase } from '../src/cache';
@@ -154,151 +154,165 @@ if (!hasHelp) {
154
154
  }
155
155
  const earlyOpts = program.opts();
156
156
 
157
- // Detect or override terminal color scheme
158
- let colorScheme = await detectColorScheme();
159
- if (earlyOpts.colorScheme === 'light' || earlyOpts.colorScheme === 'dark') {
160
- colorScheme = earlyOpts.colorScheme;
161
- if (process.env.DEBUG_COLORS) {
162
- console.log(`[DEBUG] Using --color-scheme=${colorScheme} flag`);
163
- }
157
+ // Set JSON error format early, BEFORE any code that might throw,
158
+ // so that startup failures also use the JSON error formatter.
159
+ // Note: --error-format defaults to 'text', so we must check !== 'json'
160
+ // rather than !earlyOpts.errorFormat (which would always be false).
161
+ if (earlyOpts.json && earlyOpts.errorFormat !== 'json') {
162
+ earlyOpts.errorFormat = 'json';
164
163
  }
165
- setColorScheme(colorScheme);
164
+ setOutputOptions(earlyOpts as GlobalOptions);
166
165
 
167
- // Debug: show detected color scheme
168
- if (process.env.DEBUG_COLORS) {
169
- console.log(`[DEBUG] Color scheme: ${colorScheme}`);
170
- }
166
+ // Variables that may or may not be initialized by the time an error occurs.
167
+ // Hoisted here so the centralized error handler can reference them.
168
+ let config: Config | null | undefined;
169
+ let logger: ReturnType<typeof createCompositeLogger> | undefined;
171
170
 
172
- // Create logger instance with global options
173
- // In quiet or JSON mode, suppress most logging
174
- const effectiveLogLevel =
175
- earlyOpts.quiet || earlyOpts.json ? 'error' : (earlyOpts.logLevel as LogLevel) || 'info';
176
- const consoleLogger = new ConsoleLogger(
177
- effectiveLogLevel,
178
- earlyOpts.logTimestamp || false,
179
- colorScheme
180
- );
181
- consoleLogger.setShowPrefix(earlyOpts.logPrefix !== false);
182
-
183
- // Use the parsed operands from Commander.js parseOptions() which correctly
184
- // separates command names from option values. parsedOperands contains only
185
- // positional arguments (command/subcommand names), not flag values.
186
- // For help mode, parsedOperands is empty so we fall back to extracting from preprocessedArgs.
187
- const commandArgs = hasHelp
188
- ? preprocessedArgs.filter((arg) => !arg.startsWith('-'))
189
- : parsedOperands;
190
-
191
- // Check if we should skip internal logging based on command or help flags
192
- // We need to check the commands first to see if skipInternalLogging is set
193
- const earlyCommandName = commandArgs[0];
194
- const earlySubcommandName = commandArgs[1];
195
- const commands = await discoverCommands();
196
- const earlyCommandDef = commands.find((cmd) => cmd.name === earlyCommandName);
197
- const earlySubcommandDef = earlySubcommandName
198
- ? earlyCommandDef?.subcommands?.find((sub) => sub.name === earlySubcommandName)
199
- : undefined;
200
-
201
- // Skip internal logging if:
202
- // 1. Help flag is present
203
- // 2. No command provided (shows help)
204
- // 3. Command has skipInternalLogging set
205
- // 4. Subcommand has skipInternalLogging set
206
- const shouldSkipInternalLogging =
207
- hasHelp ||
208
- commandArgs.length === 0 ||
209
- earlyCommandDef?.skipInternalLogging ||
210
- earlySubcommandDef?.skipInternalLogging;
211
-
212
- // Create internal logger for trace/debug logging (always at trace level)
213
- const internalLogger = createInternalLogger(version, getPackageName());
214
-
215
- // Disable or initialize the internal logger based on command flags
216
- if (shouldSkipInternalLogging) {
217
- internalLogger.disable();
218
- } else {
219
- const command = commandArgs.length > 0 ? commandArgs.join(' ') : 'help';
220
- // Extract --dir from argv if present (for project context in logs)
221
- const projectDirArg = getProjectDirFromArgs();
222
- // Filter out command/subcommand names from args by position, not by value.
223
- // The first N entries of preprocessedArgs that are not flags are the command tokens.
224
- // Skip the leading command tokens to get only the actual arguments.
225
- let commandTokensToSkip = commandArgs.length;
226
- const filteredArgs: string[] = [];
227
- for (const arg of preprocessedArgs) {
228
- if (commandTokensToSkip > 0 && !arg.startsWith('-') && commandArgs.includes(arg)) {
229
- commandTokensToSkip--;
230
- } else {
231
- filteredArgs.push(arg);
171
+ /**
172
+ * Main startup and command execution.
173
+ *
174
+ * Wrapped in a function so that the entire boot sequence (config loading,
175
+ * command discovery, update checks, command registration, and execution)
176
+ * is enclosed in a single try/catch whose error handler respects --json.
177
+ */
178
+ async function main() {
179
+ // Detect or override terminal color scheme
180
+ let colorScheme = await detectColorScheme();
181
+ if (earlyOpts.colorScheme === 'light' || earlyOpts.colorScheme === 'dark') {
182
+ colorScheme = earlyOpts.colorScheme;
183
+ if (process.env.DEBUG_COLORS) {
184
+ console.log(`[DEBUG] Using --color-scheme=${colorScheme} flag`);
232
185
  }
233
186
  }
234
- internalLogger.init(command, filteredArgs, undefined, projectDirArg);
235
-
236
- // Set session ID in environment so forked child processes can share the same log file
237
- process.env.AGENTUITY_INTERNAL_SESSION_ID = internalLogger.getSessionId();
187
+ setColorScheme(colorScheme);
238
188
 
239
- // Set detected agent in session logs
240
- const detectedAgent = getExecutingAgent();
241
- if (detectedAgent) {
242
- internalLogger.setDetectedAgent(detectedAgent);
189
+ // Debug: show detected color scheme
190
+ if (process.env.DEBUG_COLORS) {
191
+ console.log(`[DEBUG] Color scheme: ${colorScheme}`);
243
192
  }
244
- }
245
-
246
- // Create composite logger that writes to both console and internal log
247
- const logger = createCompositeLogger(consoleLogger, internalLogger);
248
193
 
249
- // Set version check skip flag from CLI option
250
- if (earlyOpts.skipVersionCheck) {
251
- process.env.AGENTUITY_SKIP_VERSION_CHECK = '1';
252
- }
194
+ // Create logger instance with global options
195
+ // In quiet or JSON mode, suppress most logging
196
+ const effectiveLogLevel =
197
+ earlyOpts.quiet || earlyOpts.json ? 'error' : (earlyOpts.logLevel as LogLevel) || 'info';
198
+ const consoleLogger = new ConsoleLogger(
199
+ effectiveLogLevel,
200
+ earlyOpts.logTimestamp || false,
201
+ colorScheme
202
+ );
203
+ consoleLogger.setShowPrefix(earlyOpts.logPrefix !== false);
204
+
205
+ // Use the parsed operands from Commander.js parseOptions() which correctly
206
+ // separates command names from option values. parsedOperands contains only
207
+ // positional arguments (command/subcommand names), not flag values.
208
+ // For help mode, parsedOperands is empty so we fall back to extracting from preprocessedArgs.
209
+ const commandArgs = hasHelp
210
+ ? preprocessedArgs.filter((arg) => !arg.startsWith('-'))
211
+ : parsedOperands;
212
+
213
+ // Check if we should skip internal logging based on command or help flags
214
+ // We need to check the commands first to see if skipInternalLogging is set
215
+ const earlyCommandName = commandArgs[0];
216
+ const earlySubcommandName = commandArgs[1];
217
+ const commands = await discoverCommands();
218
+ const earlyCommandDef = commands.find((cmd) => cmd.name === earlyCommandName);
219
+ const earlySubcommandDef = earlySubcommandName
220
+ ? earlyCommandDef?.subcommands?.find((sub) => sub.name === earlySubcommandName)
221
+ : undefined;
222
+
223
+ // Skip internal logging if:
224
+ // 1. Help flag is present
225
+ // 2. No command provided (shows help)
226
+ // 3. Command has skipInternalLogging set
227
+ // 4. Subcommand has skipInternalLogging set
228
+ const shouldSkipInternalLogging =
229
+ hasHelp ||
230
+ commandArgs.length === 0 ||
231
+ earlyCommandDef?.skipInternalLogging ||
232
+ earlySubcommandDef?.skipInternalLogging;
233
+
234
+ // Create internal logger for trace/debug logging (always at trace level)
235
+ const internalLogger = createInternalLogger(version, getPackageName());
236
+
237
+ // Disable or initialize the internal logger based on command flags
238
+ if (shouldSkipInternalLogging) {
239
+ internalLogger.disable();
240
+ } else {
241
+ const command = commandArgs.length > 0 ? commandArgs.join(' ') : 'help';
242
+ // Extract --dir from argv if present (for project context in logs)
243
+ const projectDirArg = getProjectDirFromArgs();
244
+ // Filter out command/subcommand names from args by position, not by value.
245
+ // The first N entries of preprocessedArgs that are not flags are the command tokens.
246
+ // Skip the leading command tokens to get only the actual arguments.
247
+ let commandTokensToSkip = commandArgs.length;
248
+ const filteredArgs: string[] = [];
249
+ for (const arg of preprocessedArgs) {
250
+ if (commandTokensToSkip > 0 && !arg.startsWith('-') && commandArgs.includes(arg)) {
251
+ commandTokensToSkip--;
252
+ } else {
253
+ filteredArgs.push(arg);
254
+ }
255
+ }
256
+ internalLogger.init(command, filteredArgs, undefined, projectDirArg);
253
257
 
254
- const config = await loadConfig(earlyOpts.config, false, earlyOpts.profile);
258
+ // Set session ID in environment so forked child processes can share the same log file
259
+ process.env.AGENTUITY_INTERNAL_SESSION_ID = internalLogger.getSessionId();
255
260
 
256
- // Update internal logger with userId if available from auth (keychain or config)
257
- try {
258
- const auth = await getAuth();
259
- if (auth?.userId) {
260
- internalLogger.setUserId(auth.userId);
261
+ // Set detected agent in session logs
262
+ const detectedAgent = getExecutingAgent();
263
+ if (detectedAgent) {
264
+ internalLogger.setDetectedAgent(detectedAgent);
265
+ }
261
266
  }
262
- } catch {
263
- // Ignore auth errors - user might not be logged in
264
- }
265
267
 
266
- const ctx = {
267
- config,
268
- logger,
269
- options: earlyOpts,
270
- getExecutingAgent,
271
- };
268
+ // Create composite logger that writes to both console and internal log
269
+ logger = createCompositeLogger(consoleLogger, internalLogger);
272
270
 
273
- // Set global output options for utilities to use
274
- // When --json is used, automatically set error format to json
275
- if (earlyOpts.json && !earlyOpts.errorFormat) {
276
- earlyOpts.errorFormat = 'json';
277
- }
278
- setOutputOptions(earlyOpts as GlobalOptions);
271
+ // Set version check skip flag from CLI option
272
+ if (earlyOpts.skipVersionCheck) {
273
+ process.env.AGENTUITY_SKIP_VERSION_CHECK = '1';
274
+ }
279
275
 
280
- // Check for updates before running commands (may upgrade and re-exec)
281
- // Find the command being run to check if it opts out of upgrade check
282
- // Use parsedOperands from Commander's parseOptions which correctly separates
283
- // command names from option values (e.g., "--file myfile" won't treat "myfile" as a command)
284
- // Also find the subcommand if present (e.g., "auth whoami" -> command="auth", subcommand="whoami")
285
- const commandName = parsedOperands[0];
286
- const subcommandName = parsedOperands[1];
287
- const commandDef = commands.find((cmd) => cmd.name === commandName);
288
- const subcommandDef = subcommandName
289
- ? commandDef?.subcommands?.find((sub) => sub.name === subcommandName)
290
- : undefined;
276
+ config = await loadConfig(earlyOpts.config, false, earlyOpts.profile);
291
277
 
292
- await checkForUpdates(config, logger, earlyOpts, commandDef, subcommandDef, preprocessedArgs);
278
+ // Update internal logger with userId if available from auth (keychain or config)
279
+ try {
280
+ const auth = await getAuth();
281
+ if (auth?.userId) {
282
+ internalLogger.setUserId(auth.userId);
283
+ }
284
+ } catch {
285
+ // Ignore auth errors - user might not be logged in
286
+ }
293
287
 
294
- // Generate and store CLI schema globally for the schema command
295
- const cliSchema = generateCLISchema(program, commands, version);
296
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
297
- (global as any).__CLI_SCHEMA__ = cliSchema;
288
+ const ctx = {
289
+ config,
290
+ logger,
291
+ options: earlyOpts,
292
+ getExecutingAgent,
293
+ };
294
+
295
+ // Check for updates before running commands (may upgrade and re-exec)
296
+ // Find the command being run to check if it opts out of upgrade check
297
+ // Use parsedOperands from Commander's parseOptions which correctly separates
298
+ // command names from option values (e.g., "--file myfile" won't treat "myfile" as a command)
299
+ // Also find the subcommand if present (e.g., "auth whoami" -> command="auth", subcommand="whoami")
300
+ const commandName = parsedOperands[0];
301
+ const subcommandName = parsedOperands[1];
302
+ const commandDef = commands.find((cmd) => cmd.name === commandName);
303
+ const subcommandDef = subcommandName
304
+ ? commandDef?.subcommands?.find((sub) => sub.name === subcommandName)
305
+ : undefined;
306
+
307
+ await checkForUpdates(config, logger, earlyOpts, commandDef, subcommandDef, preprocessedArgs);
308
+
309
+ // Generate and store CLI schema globally for the schema command
310
+ const cliSchema = generateCLISchema(program, commands, version);
311
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
312
+ (global as any).__CLI_SCHEMA__ = cliSchema;
298
313
 
299
- await registerCommands(program, commands, ctx as unknown as CommandContext);
314
+ await registerCommands(program, commands, ctx as unknown as CommandContext);
300
315
 
301
- try {
302
316
  await program.parseAsync(process.argv);
303
317
  // Ensure clean exit after successful command execution.
304
318
  // In TTY environments, process.stdin may keep the event loop alive
@@ -308,6 +322,10 @@ try {
308
322
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
309
323
  const exit = (globalThis as any).AGENTUITY_PROCESS_EXIT || process.exit;
310
324
  exit(0);
325
+ }
326
+
327
+ try {
328
+ await main();
311
329
  } catch (error) {
312
330
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
313
331
  const exit = (globalThis as any).AGENTUITY_PROCESS_EXIT || process.exit;
@@ -369,19 +387,40 @@ try {
369
387
  }
370
388
 
371
389
  if (earlyOpts.errorFormat === 'json') {
372
- const message = errorWithMessage?.message ?? String(error);
373
- const code = isStructuredError(error) ? ErrorCode.API_ERROR : ErrorCode.INTERNAL_ERROR;
390
+ let message = errorWithMessage?.message ?? String(error);
391
+ // Classify the error: treat as API_ERROR if it's a structured error
392
+ // OR if the error carries a numeric status/statusCode (e.g. ServiceException)
393
+ const hasStatus =
394
+ typeof error === 'object' &&
395
+ error !== null &&
396
+ (('status' in error && typeof (error as any).status === 'number') ||
397
+ ('statusCode' in error && typeof (error as any).statusCode === 'number'));
398
+ const code =
399
+ isStructuredError(error) || hasStatus ? ErrorCode.API_ERROR : ErrorCode.INTERNAL_ERROR;
374
400
  const details: Record<string, unknown> = {};
375
401
  if (isStructuredError(error) && errorWithMessage._tag) {
376
402
  details.tag = errorWithMessage._tag;
377
403
  }
378
- if (
379
- typeof error === 'object' &&
380
- error !== null &&
381
- 'status' in error &&
382
- typeof (error as any).status === 'number'
383
- ) {
384
- details.status = (error as any).status;
404
+ if (typeof error === 'object' && error !== null) {
405
+ // Support both error.status (APIErrorResponse) and error.statusCode (ServiceException)
406
+ if ('status' in error && typeof (error as any).status === 'number') {
407
+ details.status = (error as any).status;
408
+ } else if ('statusCode' in error && typeof (error as any).statusCode === 'number') {
409
+ details.status = (error as any).statusCode;
410
+ }
411
+ if ('sessionId' in error && (error as any).sessionId) {
412
+ details.sessionId = (error as any).sessionId;
413
+ }
414
+ }
415
+ // Try to parse embedded JSON in API error messages
416
+ // API errors often have messages like: '{"success":false,"message":"not found: ..."}'
417
+ try {
418
+ const parsed = JSON.parse(message);
419
+ if (parsed && typeof parsed === 'object' && typeof parsed.message === 'string') {
420
+ message = parsed.message;
421
+ }
422
+ } catch {
423
+ // Not JSON, use message as-is
385
424
  }
386
425
  console.error(
387
426
  formatErrorJSON(
@@ -389,13 +428,20 @@ try {
389
428
  )
390
429
  );
391
430
  } else if (isStructuredError(error)) {
392
- logger.error(error);
431
+ // Use the composite logger if available; fall back to console.error
432
+ // if the error occurred before logger initialization completed
433
+ if (logger) {
434
+ logger.error(error);
435
+ } else {
436
+ console.error(String(error));
437
+ }
393
438
  } else {
394
- logger.error(
395
- 'CLI error: %s %s',
396
- errorWithMessage?.message ? errorWithMessage.message : String(error),
397
- error
398
- );
439
+ const msg = errorWithMessage?.message ? errorWithMessage.message : String(error);
440
+ if (logger) {
441
+ logger.error('CLI error: %s %s', msg, error);
442
+ } else {
443
+ console.error(`CLI error: ${msg}`);
444
+ }
399
445
  }
400
446
  closeDatabase();
401
447
  exit(1);
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EACX,iBAAiB,EAEjB,cAAc,EAEd,MAAM,EAGN,MAAM,EAGN,MAAM,SAAS,CAAC;AAWjB,OAAO,EAAE,KAAK,UAAU,EAAyB,MAAM,mBAAmB,CAAC;AAqd3E,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAoPjE;AAkFD,MAAM,WAAW,oBAAoB;IACpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,OAAO,EAAE,UAAU,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE,CAAC;CAC3C;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAyJ3F;AAirCD,wBAAsB,gBAAgB,CACrC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,OAAO,EAAE,cAAc,GACrB,OAAO,CAAC,IAAI,CAAC,CA6Of"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EACX,iBAAiB,EAEjB,cAAc,EAEd,MAAM,EAGN,MAAM,EAGN,MAAM,SAAS,CAAC;AAWjB,OAAO,EAAE,KAAK,UAAU,EAAyB,MAAM,mBAAmB,CAAC;AA0d3E,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAqSjE;AAkFD,MAAM,WAAW,oBAAoB;IACpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,OAAO,EAAE,UAAU,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE,CAAC;CAC3C;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAyJ3F;AAirCD,wBAAsB,gBAAgB,CACrC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,OAAO,EAAE,cAAc,GACrB,OAAO,CAAC,IAAI,CAAC,CA6Of"}
package/dist/cli.js CHANGED
@@ -11,9 +11,9 @@ import * as tui from './tui';
11
11
  import { parseArgsSchema, parseOptionsSchema, buildValidationInputAsync } from './schema-parser';
12
12
  import { defaultProfileName, loadProjectConfig, saveProjectId, saveRegion } from './config';
13
13
  import { APIClient, getAPIBaseURL, getAppBaseURL } from './api';
14
- import { ErrorCode, ExitCode, createError, exitWithError } from './errors';
14
+ import { ErrorCode, ExitCode, createError, exitWithError, formatErrorJSON } from './errors';
15
15
  import { getCommand } from './command-prefix';
16
- import { isValidateMode, outputValidation } from './output';
16
+ import { getOutputOptions, isValidateMode, outputValidation, } from './output';
17
17
  import { StructuredError } from '@agentuity/core';
18
18
  import { setProgram } from './program-ref';
19
19
  import { generateIntroPrompt } from './cmd/ai/intro';
@@ -413,6 +413,12 @@ export async function createCLI(version) {
413
413
  // Handle unknown commands
414
414
  program.on('command:*', (operands) => {
415
415
  const unknownCommand = operands[0];
416
+ const opts = getOutputOptions();
417
+ if (opts?.json || opts?.errorFormat === 'json') {
418
+ console.error(formatErrorJSON(createError(ErrorCode.UNKNOWN_COMMAND, `unknown command '${unknownCommand}'`)));
419
+ process.exit(1);
420
+ return;
421
+ }
416
422
  console.error(`error: unknown command '${unknownCommand}'`);
417
423
  console.error();
418
424
  const availableCommands = program.commands.map((cmd) => cmd.name());
@@ -426,13 +432,50 @@ export async function createCLI(version) {
426
432
  console.error(`Run '${getCommand('--help')}' for usage information.`);
427
433
  process.exit(1);
428
434
  });
435
+ // Track whether a JSON error was already emitted by outputError
436
+ // so we can suppress Commander's help-after-error text in JSON mode
437
+ let jsonErrorEmitted = false;
429
438
  // Custom error handling for argument/command parsing errors
430
439
  program.configureOutput({
440
+ writeErr: (str) => {
441
+ // In JSON mode, suppress Commander's help-after-error text
442
+ // (we already emitted a structured JSON error in outputError)
443
+ const opts = getOutputOptions();
444
+ if (jsonErrorEmitted && (opts?.json || opts?.errorFormat === 'json')) {
445
+ return;
446
+ }
447
+ process.stderr.write(str);
448
+ },
431
449
  outputError: (str, write) => {
432
450
  // Suppress "unknown option '--help'" error since we handle help flags specially
433
451
  if (str.includes("unknown option '--help'")) {
434
452
  return;
435
453
  }
454
+ // In JSON mode, output structured JSON errors for all Commander parsing errors
455
+ const opts = getOutputOptions();
456
+ if (opts?.json || opts?.errorFormat === 'json') {
457
+ // Strip "error: " prefix and trailing newline for clean message
458
+ let message = str.replace(/^error:\s*/, '').replace(/\n$/, '');
459
+ let code = ErrorCode.INVALID_OPTION;
460
+ if (str.includes('unknown command') || str.includes('too many arguments')) {
461
+ code = ErrorCode.UNKNOWN_COMMAND;
462
+ }
463
+ else if (str.includes('missing required argument')) {
464
+ code = ErrorCode.MISSING_ARGUMENT;
465
+ }
466
+ // Extract Commander's "Did you mean" suggestion into a separate field
467
+ let suggestions;
468
+ const suggestionMatch = message.match(/\n\(Did you mean (.+)\?\)/);
469
+ if (suggestionMatch?.[1] != null) {
470
+ suggestions = [suggestionMatch[1]];
471
+ message = message.replace(/\n\(Did you mean .+\?\)/, '');
472
+ }
473
+ // Write directly to stderr (not via write/writeErr) to avoid
474
+ // self-suppression — writeErr suppresses output when jsonErrorEmitted is true
475
+ jsonErrorEmitted = true;
476
+ process.stderr.write(formatErrorJSON(createError(code, message, undefined, suggestions)) + '\n');
477
+ return;
478
+ }
436
479
  // Intercept commander.js error messages
437
480
  if (str.includes('too many arguments') || str.includes('unknown command')) {
438
481
  // Extract potential command name from error context