@agentuity/cli 0.0.56 → 0.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.
Files changed (60) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +41 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/cmd/build/ast.d.ts +4 -10
  5. package/dist/cmd/build/ast.d.ts.map +1 -1
  6. package/dist/cmd/build/ast.js +9 -10
  7. package/dist/cmd/build/ast.js.map +1 -1
  8. package/dist/cmd/build/bundler.d.ts +25 -2
  9. package/dist/cmd/build/bundler.d.ts.map +1 -1
  10. package/dist/cmd/build/bundler.js +138 -47
  11. package/dist/cmd/build/bundler.js.map +1 -1
  12. package/dist/cmd/build/plugin.js +7 -7
  13. package/dist/cmd/build/plugin.js.map +1 -1
  14. package/dist/cmd/build/workbench-templates.d.ts +4 -0
  15. package/dist/cmd/build/workbench-templates.d.ts.map +1 -0
  16. package/dist/cmd/build/workbench-templates.js +49 -0
  17. package/dist/cmd/build/workbench-templates.js.map +1 -0
  18. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  19. package/dist/cmd/cloud/deploy.js +11 -3
  20. package/dist/cmd/cloud/deploy.js.map +1 -1
  21. package/dist/cmd/cloud/deployment/show.d.ts.map +1 -1
  22. package/dist/cmd/cloud/deployment/show.js +73 -20
  23. package/dist/cmd/cloud/deployment/show.js.map +1 -1
  24. package/dist/cmd/cloud/session/get.js.map +1 -1
  25. package/dist/cmd/dev/index.d.ts.map +1 -1
  26. package/dist/cmd/dev/index.js +102 -54
  27. package/dist/cmd/dev/index.js.map +1 -1
  28. package/dist/cmd/index.d.ts.map +1 -1
  29. package/dist/cmd/index.js +2 -0
  30. package/dist/cmd/index.js.map +1 -1
  31. package/dist/schema-generator.d.ts +1 -1
  32. package/dist/schema-generator.d.ts.map +1 -1
  33. package/dist/schema-parser.d.ts +2 -1
  34. package/dist/schema-parser.d.ts.map +1 -1
  35. package/dist/schema-parser.js +18 -2
  36. package/dist/schema-parser.js.map +1 -1
  37. package/dist/steps.d.ts +2 -1
  38. package/dist/steps.d.ts.map +1 -1
  39. package/dist/steps.js +26 -3
  40. package/dist/steps.js.map +1 -1
  41. package/dist/tui.d.ts.map +1 -1
  42. package/dist/tui.js +9 -10
  43. package/dist/tui.js.map +1 -1
  44. package/dist/types.d.ts +15 -0
  45. package/dist/types.d.ts.map +1 -1
  46. package/package.json +3 -3
  47. package/src/cli.ts +49 -2
  48. package/src/cmd/build/ast.ts +15 -27
  49. package/src/cmd/build/bundler.ts +157 -45
  50. package/src/cmd/build/plugin.ts +8 -8
  51. package/src/cmd/build/workbench-templates.ts +52 -0
  52. package/src/cmd/cloud/deploy.ts +11 -3
  53. package/src/cmd/cloud/deployment/show.ts +60 -17
  54. package/src/cmd/cloud/session/get.ts +5 -5
  55. package/src/cmd/dev/index.ts +113 -58
  56. package/src/cmd/index.ts +2 -0
  57. package/src/schema-generator.ts +1 -1
  58. package/src/schema-parser.ts +19 -4
  59. package/src/steps.ts +27 -4
  60. package/src/tui.ts +11 -12
@@ -1,3 +1,4 @@
1
+ import { z } from 'zod';
1
2
  import { join, resolve } from 'node:path';
2
3
  import { createPublicKey } from 'node:crypto';
3
4
  import { createReadStream, createWriteStream } from 'node:fs';
@@ -26,9 +27,9 @@ import {
26
27
  } from '../../env-util';
27
28
  import { zipDir } from '../../utils/zip';
28
29
  import { encryptFIPSKEMDEMStream } from '../../crypto/box';
29
- import { checkCustomDomainForDNS } from './domain';
30
+ import { DeployOptionsSchema } from '../build/bundler';
30
31
  import { getCommand } from '../../command-prefix';
31
- import { z } from 'zod';
32
+ import { checkCustomDomainForDNS } from './domain';
32
33
 
33
34
  const DeployResponseSchema = z.object({
34
35
  success: z.boolean().describe('Whether deployment succeeded'),
@@ -58,17 +59,19 @@ export const deploySubcommand = createSubcommand({
58
59
  examples: [
59
60
  `${getCommand('cloud deploy')} # Deploy current project`,
60
61
  `${getCommand('cloud deploy')} --log-level=debug # Deploy with verbose output`,
62
+ `${getCommand('cloud deploy')} --tag a --tag b # Deploy with specific tags`,
61
63
  ],
62
64
  toplevel: true,
63
65
  idempotent: false,
64
66
  requires: { auth: true, project: true, apiClient: true },
65
67
  prerequisites: ['auth login'],
66
68
  schema: {
69
+ options: DeployOptionsSchema,
67
70
  response: DeployResponseSchema,
68
71
  },
69
72
 
70
73
  async handler(ctx) {
71
- const { project, apiClient, projectDir, config, options } = ctx;
74
+ const { project, apiClient, projectDir, config, options, opts } = ctx;
72
75
 
73
76
  let deployment: Deployment | undefined;
74
77
  let build: BuildMetadata | undefined;
@@ -171,6 +174,11 @@ export const deploySubcommand = createSubcommand({
171
174
  orgId: deployment.orgId,
172
175
  projectId: project.projectId,
173
176
  project,
177
+ commitUrl: opts?.commitUrl,
178
+ logsUrl: opts?.logsUrl,
179
+ provider: opts?.provider,
180
+ trigger: opts?.trigger,
181
+ tag: opts?.tag,
174
182
  });
175
183
  build = await loadBuildMetadata(join(projectDir, '.agentuity'));
176
184
  instructions = await projectDeploymentUpdate(
@@ -15,7 +15,39 @@ const DeploymentShowResponseSchema = z.object({
15
15
  tags: z.array(z.string()).describe('Deployment tags'),
16
16
  customDomains: z.array(z.string()).optional().describe('Custom domains'),
17
17
  cloudRegion: z.string().optional().describe('Cloud region'),
18
- metadata: z.any().optional().describe('Deployment metadata'),
18
+ metadata: z
19
+ .object({
20
+ git: z
21
+ .object({
22
+ repo: z.string().optional(),
23
+ commit: z.string().optional(),
24
+ message: z.string().optional(),
25
+ branch: z.string().optional(),
26
+ url: z.string().optional(),
27
+ trigger: z.string().optional(),
28
+ provider: z.string().optional(),
29
+ event: z.string().optional(),
30
+ buildUrl: z.string().optional(),
31
+ pull_request: z
32
+ .object({
33
+ number: z.number(),
34
+ url: z.string().optional(),
35
+ commentId: z.string().optional(),
36
+ })
37
+ .optional(),
38
+ })
39
+ .optional(),
40
+ build: z
41
+ .object({
42
+ agentuity: z.string().optional(),
43
+ bun: z.string().optional(),
44
+ platform: z.string().optional(),
45
+ arch: z.string().optional(),
46
+ })
47
+ .optional(),
48
+ })
49
+ .optional()
50
+ .describe('Deployment metadata'),
19
51
  });
20
52
 
21
53
  export const showSubcommand = createSubcommand({
@@ -49,8 +81,6 @@ export const showSubcommand = createSubcommand({
49
81
 
50
82
  // Skip TUI output in JSON mode
51
83
  if (!options.json) {
52
- tui.banner(`Deployment ${deployment.id}`, `State: ${deployment.state || 'unknown'}`);
53
-
54
84
  console.log(tui.bold('ID: ') + deployment.id);
55
85
  console.log(tui.bold('Project: ') + projectId);
56
86
  console.log(tui.bold('State: ') + (deployment.state || 'unknown'));
@@ -74,22 +104,35 @@ export const showSubcommand = createSubcommand({
74
104
  console.log(tui.bold('Region: ') + deployment.cloudRegion);
75
105
  }
76
106
 
77
- // Metadata
78
- const origin = deployment.metadata?.origin;
79
- if (origin?.commit) {
107
+ // Git metadata
108
+ const git = deployment.metadata?.git;
109
+ if (git) {
80
110
  tui.newline();
81
- tui.info('Origin Information');
82
- if (origin.trigger) console.log(` Trigger: ${origin.trigger}`);
83
- if (origin.provider) console.log(` Provider: ${origin.provider}`);
84
- if (origin.event) console.log(` Event: ${origin.event}`);
85
- if (origin.branch) console.log(` Branch: ${origin.branch}`);
86
-
87
- if (origin.commit) {
88
- console.log(` Commit: ${origin.commit.hash}`);
89
- if (origin.commit.message) console.log(` Message: ${origin.commit.message}`);
90
- if (origin.commit.author?.name)
91
- console.log(` Author: ${origin.commit.author.name}`);
111
+ tui.info('Git Information');
112
+ if (git.repo) console.log(` Repo: ${git.repo}`);
113
+ if (git.branch) console.log(` Branch: ${git.branch}`);
114
+ if (git.commit) console.log(` Commit: ${git.commit}`);
115
+ if (git.message) console.log(` Message: ${git.message}`);
116
+ if (git.url) console.log(` URL: ${git.url}`);
117
+ if (git.trigger) console.log(` Trigger: ${git.trigger}`);
118
+ if (git.provider) console.log(` Provider: ${git.provider}`);
119
+ if (git.event) console.log(` Event: ${git.event}`);
120
+ if (git.pull_request) {
121
+ console.log(` PR: #${git.pull_request.number}`);
122
+ if (git.pull_request.url) console.log(` PR URL: ${git.pull_request.url}`);
92
123
  }
124
+ if (git.buildUrl) console.log(` Build: ${git.buildUrl}`);
125
+ }
126
+
127
+ // Build metadata
128
+ const build = deployment.metadata?.build;
129
+ if (build) {
130
+ tui.newline();
131
+ tui.info('Build Information');
132
+ if (build.agentuity) console.log(` Agentuity: ${build.agentuity}`);
133
+ if (build.bun) console.log(` Bun: ${build.bun}`);
134
+ if (build.platform) console.log(` Platform: ${build.platform}`);
135
+ if (build.arch) console.log(` Arch: ${build.arch}`);
93
136
  }
94
137
  }
95
138
 
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { createSubcommand } from '../../../types';
3
3
  import * as tui from '../../../tui';
4
- import { sessionGet, type SpanNode } from '@agentuity/server';
4
+ import { sessionGet, type SpanNode, type EvalRun, type AgentInfo } from '@agentuity/server';
5
5
  import { getCommand } from '../../../command-prefix';
6
6
  import { ErrorCode } from '../../../errors';
7
7
  import { getCatalystAPIClient } from '../../../config';
@@ -95,7 +95,7 @@ function printTimeline(node: SpanNode, prefix: string, isLast = true): void {
95
95
 
96
96
  const childPrefix = prefix + (isLast ? ' ' : '│ ');
97
97
  const children = node.children ?? [];
98
- children.forEach((child, index) => {
98
+ children.forEach((child: SpanNode, index: number) => {
99
99
  printTimeline(child, childPrefix, index === children.length - 1);
100
100
  });
101
101
  }
@@ -142,7 +142,7 @@ export const getSubcommand = createSubcommand({
142
142
  route_id: session.route_id,
143
143
  thread_id: session.thread_id,
144
144
  agents: enriched.agents,
145
- eval_runs: enriched.evalRuns.map((run) => ({
145
+ eval_runs: enriched.evalRuns.map((run: EvalRun) => ({
146
146
  id: run.id,
147
147
  eval_id: run.eval_id,
148
148
  created_at: run.created_at,
@@ -189,7 +189,7 @@ export const getSubcommand = createSubcommand({
189
189
  }
190
190
  if (enriched.agents.length > 0) {
191
191
  const agentDisplay = enriched.agents
192
- .map((agent) => `${agent.name} ${tui.muted(`(${agent.identifier})`)}`)
192
+ .map((agent: AgentInfo) => `${agent.name} ${tui.muted(`(${agent.identifier})`)}`)
193
193
  .join(', ');
194
194
  console.log(tui.bold('Agents: ') + agentDisplay);
195
195
  }
@@ -206,7 +206,7 @@ export const getSubcommand = createSubcommand({
206
206
  if (enriched.evalRuns.length > 0) {
207
207
  console.log('');
208
208
  console.log(tui.bold('Eval Runs:'));
209
- const evalTableData = enriched.evalRuns.map((run) => ({
209
+ const evalTableData = enriched.evalRuns.map((run: EvalRun) => ({
210
210
  ID: run.id,
211
211
  'Eval ID': run.eval_id,
212
212
  Success: run.success ? tui.colorSuccess('✓') : tui.colorError('✗'),
@@ -238,48 +238,52 @@ export const command = createCommand({
238
238
  let gravityClient: Bun.Subprocess | undefined;
239
239
  let initialStartupComplete = false;
240
240
 
241
- if (gravityBin && devmode && project) {
242
- const sdkKey = await loadProjectSDKKey(rootDir);
243
- if (!sdkKey) {
244
- tui.warning(`Couldn't find the AGENTUITY_SDK_KEY in ${rootDir} .env file`);
245
- } else {
246
- const gravityBinExists = await Bun.file(gravityBin).exists();
247
- if (!gravityBinExists) {
248
- logger.error(
249
- `Gravity binary not found at ${gravityBin}, skipping gravity client startup`
250
- );
251
- } else {
252
- try {
253
- gravityClient = Bun.spawn(
254
- [
255
- gravityBin,
256
- '--endpoint-id',
257
- devmode.id,
258
- '--port',
259
- env.PORT,
260
- '--url',
261
- config?.overrides?.gravity_url ?? 'grpc://devmode.agentuity.com',
262
- '--log-level',
263
- process.env.AGENTUITY_GRAVITY_LOG_LEVEL ?? 'error',
264
- ],
265
- {
266
- cwd: rootDir,
267
- stdout: 'inherit',
268
- stderr: 'inherit',
269
- stdin: 'ignore',
270
- env: { ...env, AGENTUITY_SDK_KEY: sdkKey },
271
- }
272
- );
273
- gravityClient.exited.then(() => {
274
- logger.debug('gravity client exited');
275
- });
276
- } catch (err) {
277
- logger.error(
278
- 'Failed to spawn gravity client: %s',
279
- err instanceof Error ? err.message : String(err)
280
- );
241
+ const sdkKey = await loadProjectSDKKey(rootDir);
242
+ if (!sdkKey) {
243
+ tui.warning(`Couldn't find the AGENTUITY_SDK_KEY in ${rootDir} .env file`);
244
+ }
245
+ const gravityBinExists = gravityBin ? await Bun.file(gravityBin).exists() : undefined;
246
+ if (!gravityBinExists) {
247
+ logger.error(`Gravity binary not found at ${gravityBin}, skipping gravity client startup`);
248
+ }
249
+
250
+ async function restartGravityClient() {
251
+ if (gravityClient) {
252
+ gravityClient.kill('SIGINT');
253
+ gravityClient.kill();
254
+ }
255
+ if (!devmode) {
256
+ return;
257
+ }
258
+ try {
259
+ gravityClient = Bun.spawn(
260
+ [
261
+ gravityBin!,
262
+ '--endpoint-id',
263
+ devmode.id,
264
+ '--port',
265
+ env.PORT!,
266
+ '--url',
267
+ config?.overrides?.gravity_url ?? 'grpc://devmode.agentuity.com',
268
+ '--log-level',
269
+ process.env.AGENTUITY_GRAVITY_LOG_LEVEL ?? 'error',
270
+ ],
271
+ {
272
+ cwd: rootDir,
273
+ stdout: 'inherit',
274
+ stderr: 'inherit',
275
+ stdin: 'ignore',
276
+ env: { ...env, AGENTUITY_SDK_KEY: sdkKey },
281
277
  }
282
- }
278
+ );
279
+ gravityClient.exited.then(() => {
280
+ logger.debug('gravity client exited');
281
+ });
282
+ } catch (err) {
283
+ logger.error(
284
+ 'Failed to spawn gravity client: %s',
285
+ err instanceof Error ? err.message : String(err)
286
+ );
283
287
  }
284
288
  }
285
289
 
@@ -307,14 +311,42 @@ export const command = createCommand({
307
311
  }
308
312
  }
309
313
 
314
+ let lastErrorLineCount = 0;
315
+ let showedRestartMessage = false;
316
+
317
+ function clearRestartMessage() {
318
+ if (showedRestartMessage) {
319
+ process.stdout.write('\x1b[1A\x1b[2K');
320
+ showedRestartMessage = false;
321
+ }
322
+ }
323
+
310
324
  function failure(msg: string) {
311
325
  failed = true;
312
326
  failures++;
327
+ // Exit immediately on initial startup failure
328
+ if (!initialStartupComplete) {
329
+ tui.fatal(msg);
330
+ }
331
+ // During hot reload, show error but don't exit unless too many failures
313
332
  if (failures >= 5) {
314
333
  tui.error(msg);
315
334
  tui.fatal('too many failures, exiting');
316
335
  } else {
317
- setImmediate(() => tui.error(msg));
336
+ // Ensure we're on a new line before printing error
337
+ tui.error(msg);
338
+ // Track lines: 1 for "✗ Building..." + 1 for error message
339
+ lastErrorLineCount = 2;
340
+ }
341
+ }
342
+
343
+ function clearLastError() {
344
+ if (lastErrorLineCount > 0) {
345
+ // Move cursor up and clear each line
346
+ for (let i = 0; i < lastErrorLineCount; i++) {
347
+ process.stdout.write('\x1b[1A\x1b[2K');
348
+ }
349
+ lastErrorLineCount = 0;
318
350
  }
319
351
  }
320
352
 
@@ -406,6 +438,7 @@ export const command = createCommand({
406
438
  logger.trace('Server is running, killing before restart');
407
439
  checkRestartThrottle();
408
440
  tui.info('Restarting on file change');
441
+ showedRestartMessage = true;
409
442
  await kill();
410
443
  logger.trace('Server killed, continuing with restart');
411
444
  // Continue with restart after kill completes
@@ -413,11 +446,16 @@ export const command = createCommand({
413
446
  logger.trace('Initial server start');
414
447
  }
415
448
  logger.trace('Starting typecheck and build...');
416
- await tui.spinner({
417
- message: 'Building project',
418
- clearOnSuccess: true,
419
- callback: async () => {
420
- try {
449
+
450
+ // Clear any previous error before starting new build
451
+ clearLastError();
452
+ clearRestartMessage();
453
+
454
+ try {
455
+ await tui.spinner({
456
+ message: 'Building...',
457
+ clearOnSuccess: true,
458
+ callback: async () => {
421
459
  logger.trace('Bundle starting...');
422
460
  building = true;
423
461
  await bundle({
@@ -431,7 +469,7 @@ export const command = createCommand({
431
469
  buildCompletedAt = Date.now();
432
470
  logger.trace('Bundle completed successfully');
433
471
  logger.trace('tsc starting...');
434
- await tui.runCommand({
472
+ const tscExitCode = await tui.runCommand({
435
473
  command: 'tsc',
436
474
  cmd: ['bunx', 'tsc', '--noEmit'],
437
475
  cwd: rootDir,
@@ -440,15 +478,32 @@ export const command = createCommand({
440
478
  maxLinesOutput: 2,
441
479
  maxLinesOnFailure: 15,
442
480
  });
481
+ if (tscExitCode !== 0) {
482
+ logger.trace('tsc failed with exit code %d', tscExitCode);
483
+ failure('Type check failed');
484
+ return;
485
+ }
443
486
  logger.trace('tsc completed successfully');
444
- } catch (error) {
445
- building = false;
446
- logger.trace('Bundle failed: %s', error);
447
- failure('Build failed');
448
- return;
487
+ await restartGravityClient();
488
+ },
489
+ });
490
+ } catch (error) {
491
+ building = false;
492
+ const e = error as Error;
493
+ if (e.constructor.name === 'AggregateError') {
494
+ const ex = e as AggregateError;
495
+ for (const err of ex.errors) {
496
+ if (err) {
497
+ failure(err);
498
+ return;
499
+ }
449
500
  }
450
- },
451
- });
501
+ return;
502
+ }
503
+ const errorMsg = error instanceof Error ? error.message : String(error);
504
+ failure(errorMsg);
505
+ return;
506
+ }
452
507
 
453
508
  logger.trace('Typecheck and build completed');
454
509
 
@@ -457,6 +512,9 @@ export const command = createCommand({
457
512
  return;
458
513
  }
459
514
 
515
+ // Reset failure counter on successful build
516
+ failures = 0;
517
+
460
518
  logger.trace('Checking if app file exists: %s', appPath);
461
519
  if (!existsSync(appPath)) {
462
520
  logger.trace('App file not found: %s', appPath);
@@ -548,9 +606,6 @@ export const command = createCommand({
548
606
 
549
607
  if (showInitialReadyMessage) {
550
608
  showInitialReadyMessage = false;
551
- // Clear any lingering spinner/command output - clear everything below cursor
552
- process.stderr.write('\x1B[J'); // Clear from cursor to end of screen
553
- process.stdout.write('\x1B[J'); // Clear from cursor to end of screen
554
609
  logger.info('DevMode ready 🚀');
555
610
  logger.trace('Initial ready message logged');
556
611
  // Mark initial startup complete immediately to prevent watcher restarts
package/src/cmd/index.ts CHANGED
@@ -38,6 +38,8 @@ export async function discoverCommands(): Promise<CommandDefinition[]> {
38
38
  prerequisites: (subcommand as any).prerequisites,
39
39
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
40
  tags: (subcommand as any).tags,
41
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
+ examples: (subcommand as any).examples,
41
43
  };
42
44
  commands.push(alias as CommandDefinition);
43
45
  }
@@ -13,7 +13,7 @@ export interface SchemaArgument {
13
13
 
14
14
  export interface SchemaOption {
15
15
  name: string;
16
- type: 'string' | 'number' | 'boolean';
16
+ type: 'string' | 'number' | 'boolean' | 'array';
17
17
  required: boolean;
18
18
  default?: unknown;
19
19
  description?: string;
@@ -13,9 +13,10 @@ export interface ParsedArgs {
13
13
  export interface ParsedOption {
14
14
  name: string;
15
15
  description?: string;
16
- type: 'string' | 'number' | 'boolean';
16
+ type: 'string' | 'number' | 'boolean' | 'array';
17
17
  hasDefault?: boolean;
18
18
  defaultValue?: unknown;
19
+ enumValues?: string[];
19
20
  }
20
21
 
21
22
  interface ZodTypeDef {
@@ -167,11 +168,21 @@ export function parseOptionsSchema(schema: ZodType): ParsedOption[] {
167
168
  ? (defaultInfo.defaultValue as () => unknown)()
168
169
  : defaultInfo.defaultValue;
169
170
 
170
- let type: 'string' | 'number' | 'boolean' = 'string';
171
+ let type: 'string' | 'number' | 'boolean' | 'array' = 'string';
172
+ let enumValues: string[] | undefined;
171
173
  if (typeId === 'ZodNumber' || typeId === 'number') {
172
174
  type = 'number';
173
175
  } else if (typeId === 'ZodBoolean' || typeId === 'boolean') {
174
176
  type = 'boolean';
177
+ } else if (typeId === 'ZodArray' || typeId === 'array') {
178
+ type = 'array';
179
+ } else if (typeId === 'ZodEnum' || typeId === 'enum') {
180
+ // Extract enum values from Zod 4's def.entries
181
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
182
+ const def = (unwrapped as any)?._def;
183
+ if (def?.entries && typeof def.entries === 'object') {
184
+ enumValues = Object.values(def.entries as Record<string, string>);
185
+ }
175
186
  }
176
187
 
177
188
  options.push({
@@ -180,6 +191,7 @@ export function parseOptionsSchema(schema: ZodType): ParsedOption[] {
180
191
  description,
181
192
  hasDefault: defaultInfo.hasDefault,
182
193
  defaultValue,
194
+ enumValues,
183
195
  });
184
196
  }
185
197
 
@@ -203,8 +215,11 @@ export function buildValidationInput(
203
215
  if (schemas.options) {
204
216
  const parsed = parseOptionsSchema(schemas.options);
205
217
  for (const opt of parsed) {
206
- // Always include the option value (even if undefined) so zod can apply defaults
207
- result.options[opt.name] = rawOptions[opt.name];
218
+ // Only include the option if it has a value - omitting undefined allows Zod to apply defaults
219
+ const value = rawOptions[opt.name];
220
+ if (value !== undefined) {
221
+ result.options[opt.name] = value;
222
+ }
208
223
  }
209
224
  }
210
225
 
package/src/steps.ts CHANGED
@@ -7,6 +7,7 @@
7
7
 
8
8
  import type { ColorScheme } from './terminal';
9
9
  import type { LogLevel } from './types';
10
+ import { ValidationError } from '@agentuity/server';
10
11
 
11
12
  /**
12
13
  * Get the appropriate exit function (Bun.exit or process.exit)
@@ -108,14 +109,18 @@ function getColor(colorKey: keyof typeof COLORS): string {
108
109
  export type StepOutcome =
109
110
  | { status: 'success' }
110
111
  | { status: 'skipped'; reason?: string }
111
- | { status: 'error'; message: string };
112
+ | { status: 'error'; message: string; cause?: Error };
112
113
 
113
114
  /**
114
115
  * Helper functions for creating step outcomes
115
116
  */
116
117
  export const stepSuccess = (): StepOutcome => ({ status: 'success' });
117
118
  export const stepSkipped = (reason?: string): StepOutcome => ({ status: 'skipped', reason });
118
- export const stepError = (message: string): StepOutcome => ({ status: 'error', message });
119
+ export const stepError = (message: string, cause?: Error): StepOutcome => ({
120
+ status: 'error',
121
+ message,
122
+ cause,
123
+ });
119
124
 
120
125
  /**
121
126
  * Progress callback function
@@ -260,6 +265,7 @@ async function runStepsTUI(state: StepState[]): Promise<void> {
260
265
  step.outcome = {
261
266
  status: 'error',
262
267
  message: err instanceof Error ? err.message : String(err),
268
+ cause: err instanceof Error ? err : undefined,
263
269
  };
264
270
  }
265
271
 
@@ -276,7 +282,15 @@ async function runStepsTUI(state: StepState[]): Promise<void> {
276
282
  // If error, show error message and exit
277
283
  if (step.outcome?.status === 'error') {
278
284
  const errorColor = getColor('red');
279
- console.error(`\n${errorColor}Error: ${step.outcome.message}${COLORS.reset}\n`);
285
+ console.error(`\n${errorColor}Error: ${step.outcome.message}${COLORS.reset}`);
286
+ if (step.outcome.cause instanceof ValidationError) {
287
+ console.error(`${errorColor}Validation details:${COLORS.reset}`);
288
+ for (const issue of step.outcome.cause.issues) {
289
+ const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';
290
+ console.error(` ${path}: ${issue.message}`);
291
+ }
292
+ }
293
+ console.error('');
280
294
  process.stdout.write('\x1B[?25h'); // Show cursor
281
295
  process.exit(1);
282
296
  }
@@ -312,6 +326,7 @@ async function runStepsPlain(state: StepState[]): Promise<void> {
312
326
  step.outcome = {
313
327
  status: 'error',
314
328
  message: err instanceof Error ? err.message : String(err),
329
+ cause: err instanceof Error ? err : undefined,
315
330
  };
316
331
  }
317
332
 
@@ -326,7 +341,15 @@ async function runStepsPlain(state: StepState[]): Promise<void> {
326
341
  } else if (step.outcome?.status === 'error') {
327
342
  console.log(`${redColor}${ICONS.error}${COLORS.reset} ${step.label}`);
328
343
  const errorColor = getColor('red');
329
- console.error(`\n${errorColor}Error: ${step.outcome.message}${COLORS.reset}\n`);
344
+ console.error(`\n${errorColor}Error: ${step.outcome.message}${COLORS.reset}`);
345
+ if (step.outcome.cause instanceof ValidationError) {
346
+ console.error(`${errorColor}Validation details:${COLORS.reset}`);
347
+ for (const issue of step.outcome.cause.issues) {
348
+ const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';
349
+ console.error(` ${path}: ${issue.message}`);
350
+ }
351
+ }
352
+ console.error('');
330
353
  process.exit(1);
331
354
  }
332
355
  }
package/src/tui.ts CHANGED
@@ -859,8 +859,8 @@ export async function spinner<T>(
859
859
  let frameIndex = 0;
860
860
  let currentProgress: number | undefined;
861
861
 
862
- // Hide cursor
863
- process.stderr.write('\x1B[?25l');
862
+ // Save cursor position and hide cursor
863
+ process.stderr.write('\x1B[s\x1B[?25l');
864
864
 
865
865
  // Start animation
866
866
  const interval = setInterval(() => {
@@ -893,9 +893,12 @@ export async function spinner<T>(
893
893
  ? await options.callback()
894
894
  : await options.callback;
895
895
 
896
- // Clear interval and line
896
+ // Stop animation first
897
897
  clearInterval(interval);
898
- process.stderr.write('\r\x1B[K');
898
+
899
+ // Restore cursor position, clear to end of screen, show cursor
900
+ // This removes spinner and any partial output that happened during animation
901
+ process.stderr.write('\x1B[u\x1B[J\x1B[?25h');
899
902
 
900
903
  // If clearOnSuccess is false, show success message
901
904
  if (!options.clearOnSuccess) {
@@ -904,22 +907,18 @@ export async function spinner<T>(
904
907
  console.error(`${successColor}${ICONS.success} ${message}${reset}`);
905
908
  }
906
909
 
907
- // Show cursor
908
- process.stderr.write('\x1B[?25h');
909
-
910
910
  return result;
911
911
  } catch (err) {
912
- // Clear interval and line
912
+ // Stop animation first
913
913
  clearInterval(interval);
914
- process.stderr.write('\r\x1B[K');
914
+
915
+ // Restore cursor position, clear to end of screen, show cursor
916
+ process.stderr.write('\x1B[u\x1B[J\x1B[?25h');
915
917
 
916
918
  // Show error
917
919
  const errorColor = getColor('error');
918
920
  console.error(`${errorColor}${ICONS.error} ${message}${reset}`);
919
921
 
920
- // Show cursor
921
- process.stderr.write('\x1B[?25h');
922
-
923
922
  throw err;
924
923
  }
925
924
  }