@agentuity/cli 1.0.10 → 1.0.12

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 (138) hide show
  1. package/dist/cmd/build/ast.d.ts +1 -1
  2. package/dist/cmd/build/ast.d.ts.map +1 -1
  3. package/dist/cmd/build/ast.js +103 -5
  4. package/dist/cmd/build/ast.js.map +1 -1
  5. package/dist/cmd/build/vite/config-loader.d.ts.map +1 -1
  6. package/dist/cmd/build/vite/config-loader.js +1 -1
  7. package/dist/cmd/build/vite/config-loader.js.map +1 -1
  8. package/dist/cmd/build/vite/index.d.ts +2 -0
  9. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  10. package/dist/cmd/build/vite/index.js +2 -1
  11. package/dist/cmd/build/vite/index.js.map +1 -1
  12. package/dist/cmd/build/vite/metadata-generator.d.ts +4 -1
  13. package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
  14. package/dist/cmd/build/vite/metadata-generator.js +1 -0
  15. package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
  16. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  17. package/dist/cmd/build/vite/route-discovery.js +23 -1
  18. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  19. package/dist/cmd/build/vite/vite-builder.d.ts +2 -0
  20. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  21. package/dist/cmd/build/vite/vite-builder.js +1 -0
  22. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  23. package/dist/cmd/build/vite-bundler.d.ts +2 -0
  24. package/dist/cmd/build/vite-bundler.d.ts.map +1 -1
  25. package/dist/cmd/build/vite-bundler.js +2 -1
  26. package/dist/cmd/build/vite-bundler.js.map +1 -1
  27. package/dist/cmd/cloud/db/list.d.ts.map +1 -1
  28. package/dist/cmd/cloud/db/list.js +14 -1
  29. package/dist/cmd/cloud/db/list.js.map +1 -1
  30. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  31. package/dist/cmd/cloud/deploy.js +36 -22
  32. package/dist/cmd/cloud/deploy.js.map +1 -1
  33. package/dist/cmd/cloud/queue/list.d.ts.map +1 -1
  34. package/dist/cmd/cloud/queue/list.js +10 -0
  35. package/dist/cmd/cloud/queue/list.js.map +1 -1
  36. package/dist/cmd/cloud/sandbox/list.d.ts.map +1 -1
  37. package/dist/cmd/cloud/sandbox/list.js +10 -0
  38. package/dist/cmd/cloud/sandbox/list.js.map +1 -1
  39. package/dist/cmd/cloud/sandbox/runtime/list.d.ts.map +1 -1
  40. package/dist/cmd/cloud/sandbox/runtime/list.js +10 -0
  41. package/dist/cmd/cloud/sandbox/runtime/list.js.map +1 -1
  42. package/dist/cmd/cloud/sandbox/snapshot/list.d.ts.map +1 -1
  43. package/dist/cmd/cloud/sandbox/snapshot/list.js +10 -0
  44. package/dist/cmd/cloud/sandbox/snapshot/list.js.map +1 -1
  45. package/dist/cmd/cloud/session/get.js +12 -12
  46. package/dist/cmd/cloud/session/get.js.map +1 -1
  47. package/dist/cmd/cloud/session/list.d.ts.map +1 -1
  48. package/dist/cmd/cloud/session/list.js +14 -4
  49. package/dist/cmd/cloud/session/list.js.map +1 -1
  50. package/dist/cmd/cloud/storage/list.d.ts.map +1 -1
  51. package/dist/cmd/cloud/storage/list.js +14 -1
  52. package/dist/cmd/cloud/storage/list.js.map +1 -1
  53. package/dist/cmd/cloud/stream/list.d.ts.map +1 -1
  54. package/dist/cmd/cloud/stream/list.js +10 -0
  55. package/dist/cmd/cloud/stream/list.js.map +1 -1
  56. package/dist/cmd/cloud/thread/list.d.ts.map +1 -1
  57. package/dist/cmd/cloud/thread/list.js +10 -0
  58. package/dist/cmd/cloud/thread/list.js.map +1 -1
  59. package/dist/cmd/project/domain/check.d.ts +2 -0
  60. package/dist/cmd/project/domain/check.d.ts.map +1 -0
  61. package/dist/cmd/project/domain/check.js +131 -0
  62. package/dist/cmd/project/domain/check.js.map +1 -0
  63. package/dist/cmd/project/domain/index.d.ts +2 -0
  64. package/dist/cmd/project/domain/index.d.ts.map +1 -0
  65. package/dist/cmd/project/domain/index.js +20 -0
  66. package/dist/cmd/project/domain/index.js.map +1 -0
  67. package/dist/cmd/project/hostname/get.d.ts +2 -0
  68. package/dist/cmd/project/hostname/get.d.ts.map +1 -0
  69. package/dist/cmd/project/hostname/get.js +50 -0
  70. package/dist/cmd/project/hostname/get.js.map +1 -0
  71. package/dist/cmd/project/hostname/index.d.ts +2 -0
  72. package/dist/cmd/project/hostname/index.d.ts.map +1 -0
  73. package/dist/cmd/project/hostname/index.js +18 -0
  74. package/dist/cmd/project/hostname/index.js.map +1 -0
  75. package/dist/cmd/project/hostname/set.d.ts +2 -0
  76. package/dist/cmd/project/hostname/set.d.ts.map +1 -0
  77. package/dist/cmd/project/hostname/set.js +100 -0
  78. package/dist/cmd/project/hostname/set.js.map +1 -0
  79. package/dist/cmd/project/index.d.ts.map +1 -1
  80. package/dist/cmd/project/index.js +12 -0
  81. package/dist/cmd/project/index.js.map +1 -1
  82. package/dist/cmd/upgrade/index.d.ts.map +1 -1
  83. package/dist/cmd/upgrade/index.js +14 -8
  84. package/dist/cmd/upgrade/index.js.map +1 -1
  85. package/dist/cmd/upgrade/npm-availability.d.ts +12 -0
  86. package/dist/cmd/upgrade/npm-availability.d.ts.map +1 -1
  87. package/dist/cmd/upgrade/npm-availability.js +85 -6
  88. package/dist/cmd/upgrade/npm-availability.js.map +1 -1
  89. package/dist/cmd/upgrade/npm-availability.test.d.ts +2 -0
  90. package/dist/cmd/upgrade/npm-availability.test.d.ts.map +1 -0
  91. package/dist/cmd/upgrade/npm-availability.test.js +48 -0
  92. package/dist/cmd/upgrade/npm-availability.test.js.map +1 -0
  93. package/dist/index.d.ts +1 -1
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +1 -1
  96. package/dist/index.js.map +1 -1
  97. package/dist/steps.d.ts +9 -0
  98. package/dist/steps.d.ts.map +1 -1
  99. package/dist/steps.js +131 -71
  100. package/dist/steps.js.map +1 -1
  101. package/dist/tui.d.ts +2 -2
  102. package/dist/tui.d.ts.map +1 -1
  103. package/dist/tui.js +6 -4
  104. package/dist/tui.js.map +1 -1
  105. package/dist/version-check.js +3 -6
  106. package/dist/version-check.js.map +1 -1
  107. package/package.json +6 -6
  108. package/src/cmd/build/ast.ts +141 -5
  109. package/src/cmd/build/vite/config-loader.ts +1 -3
  110. package/src/cmd/build/vite/index.ts +4 -0
  111. package/src/cmd/build/vite/metadata-generator.ts +5 -1
  112. package/src/cmd/build/vite/route-discovery.ts +34 -1
  113. package/src/cmd/build/vite/vite-builder.ts +3 -0
  114. package/src/cmd/build/vite-bundler.ts +4 -0
  115. package/src/cmd/cloud/db/list.ts +14 -1
  116. package/src/cmd/cloud/deploy.ts +46 -21
  117. package/src/cmd/cloud/queue/list.ts +10 -0
  118. package/src/cmd/cloud/sandbox/list.ts +10 -0
  119. package/src/cmd/cloud/sandbox/runtime/list.ts +10 -0
  120. package/src/cmd/cloud/sandbox/snapshot/list.ts +10 -0
  121. package/src/cmd/cloud/session/get.ts +12 -12
  122. package/src/cmd/cloud/session/list.ts +28 -18
  123. package/src/cmd/cloud/storage/list.ts +14 -1
  124. package/src/cmd/cloud/stream/list.ts +18 -8
  125. package/src/cmd/cloud/thread/list.ts +15 -5
  126. package/src/cmd/project/domain/check.ts +146 -0
  127. package/src/cmd/project/domain/index.ts +20 -0
  128. package/src/cmd/project/hostname/get.ts +54 -0
  129. package/src/cmd/project/hostname/index.ts +18 -0
  130. package/src/cmd/project/hostname/set.ts +123 -0
  131. package/src/cmd/project/index.ts +12 -0
  132. package/src/cmd/upgrade/index.ts +23 -9
  133. package/src/cmd/upgrade/npm-availability.test.ts +65 -0
  134. package/src/cmd/upgrade/npm-availability.ts +103 -6
  135. package/src/index.ts +1 -1
  136. package/src/steps.ts +139 -74
  137. package/src/tui.ts +6 -4
  138. package/src/version-check.ts +6 -6
package/src/steps.ts CHANGED
@@ -11,6 +11,19 @@ import { ValidationInputError, ValidationOutputError, type IssuesType } from '@a
11
11
  import { clearLastLines, isTTYLike } from './tui';
12
12
  import { appendLog, isLogCollectionEnabled } from './log-collector';
13
13
 
14
+ /**
15
+ * Error thrown when step execution is interrupted by a signal (e.g., Ctrl+C).
16
+ * Callers can catch this to perform cleanup before exiting.
17
+ */
18
+ export class StepInterruptError extends Error {
19
+ public readonly exitCode: number;
20
+ constructor(exitCode = 130) {
21
+ super('Step execution interrupted');
22
+ this.name = 'StepInterruptError';
23
+ this.exitCode = exitCode;
24
+ }
25
+ }
26
+
14
27
  // Spinner frames
15
28
  const FRAMES = ['◐', '◓', '◑', '◒'];
16
29
 
@@ -77,6 +90,7 @@ export const stepError = (message: string, cause?: Error, output?: string[]): St
77
90
  */
78
91
  export interface StepContext {
79
92
  progress: (n: number) => void;
93
+ signal: AbortSignal;
80
94
  }
81
95
 
82
96
  /**
@@ -300,14 +314,6 @@ export function pauseStepUI(clear = false): () => void {
300
314
  };
301
315
  }
302
316
 
303
- /**
304
- * Get exit function (Bun.exit or process.exit)
305
- */
306
- function getExitFn(): (code: number) => never {
307
- const bunExit = (globalThis as { Bun?: { exit?: (code: number) => never } }).Bun?.exit;
308
- return typeof bunExit === 'function' ? bunExit : process.exit.bind(process);
309
- }
310
-
311
317
  /**
312
318
  * Install interrupt handlers (SIGINT/SIGTERM + raw mode)
313
319
  */
@@ -367,19 +373,24 @@ async function runStepsTUI(steps: Step[]): Promise<void> {
367
373
  let currentStepIndex = -1;
368
374
  let currentFrameIndex = 0;
369
375
 
376
+ // Create abort controller for cooperative cancellation
377
+ const abortController = new AbortController();
378
+
370
379
  // Hide cursor
371
380
  process.stdout.write('\x1B[?25l');
372
381
 
373
382
  // Set up interrupt handler
374
- const exit = getExitFn();
383
+ let restoreInterrupts: (() => void) | null = null;
375
384
  const onInterrupt = () => {
376
385
  if (interrupted) return;
377
386
  interrupted = true;
387
+ abortController.abort();
378
388
  if (activeInterval) clearInterval(activeInterval);
389
+ restoreInterrupts?.();
390
+ restoreInterrupts = null;
379
391
  process.stdout.write('\x1B[?25h\n'); // Show cursor
380
- exit(130);
381
392
  };
382
- const restoreInterrupts = installInterruptHandlers(onInterrupt);
393
+ restoreInterrupts = installInterruptHandlers(onInterrupt);
383
394
 
384
395
  // Force re-render function
385
396
  const forceRerender = (skipMove = false) => {
@@ -476,7 +487,10 @@ async function runStepsTUI(steps: Step[]): Promise<void> {
476
487
 
477
488
  // Run the step
478
489
  try {
479
- const outcome = await step.run({ progress: progressCallback });
490
+ const outcome = await step.run({
491
+ progress: progressCallback,
492
+ signal: abortController.signal,
493
+ });
480
494
 
481
495
  // Update state from outcome
482
496
  if (outcome.status === 'success') {
@@ -493,6 +507,11 @@ async function runStepsTUI(steps: Step[]): Promise<void> {
493
507
  stepState.output = outcome.output;
494
508
  }
495
509
  } catch (err) {
510
+ // If the step was aborted due to cancellation, treat as interrupt
511
+ if (err instanceof Error && err.name === 'AbortError') {
512
+ interrupted = true;
513
+ throw new StepInterruptError();
514
+ }
496
515
  stepState.status = 'error';
497
516
  stepState.errorMessage = err instanceof Error ? err.message : String(err);
498
517
  stepState.errorCause = err instanceof Error ? err : undefined;
@@ -522,6 +541,14 @@ async function runStepsTUI(steps: Step[]): Promise<void> {
522
541
 
523
542
  // Handle errors
524
543
  if (stepState.status === 'error') {
544
+ // If the error is due to abort/cancellation, treat as interrupt
545
+ if (
546
+ stepState.errorCause instanceof Error &&
547
+ stepState.errorCause.name === 'AbortError'
548
+ ) {
549
+ interrupted = true;
550
+ throw new StepInterruptError();
551
+ }
525
552
  const errorColor = getColor('red');
526
553
  const errorMsg = stepState.errorMessage || 'An unknown error occurred';
527
554
  console.error(`\n${errorColor}Error: ${errorMsg}${COLORS.reset}`);
@@ -537,13 +564,19 @@ async function runStepsTUI(steps: Step[]): Promise<void> {
537
564
  }
538
565
  }
539
566
 
567
+ // If interrupted during step execution, throw so callers' finally blocks run
568
+ if (interrupted) {
569
+ throw new StepInterruptError();
570
+ }
571
+
540
572
  // Show cursor
541
573
  process.stdout.write('\x1B[?25h');
542
574
  } catch (err) {
543
575
  process.stdout.write('\x1B[?25h');
544
576
  throw err;
545
577
  } finally {
546
- restoreInterrupts();
578
+ restoreInterrupts?.();
579
+ restoreInterrupts = null;
547
580
  getTotalLinesFn = null; // Clear pause capability
548
581
  forceRerenderFn = null;
549
582
  }
@@ -558,76 +591,108 @@ async function runStepsPlain(steps: Step[]): Promise<void> {
558
591
  const yellowColor = getColor('yellow');
559
592
  const redColor = getColor('red');
560
593
 
561
- for (const step of steps) {
562
- let outcome: StepOutcome;
563
-
564
- try {
565
- outcome = await step.run({ progress: () => {} });
566
- } catch (err) {
567
- outcome = {
568
- status: 'error',
569
- message: err instanceof Error ? err.message : String(err),
570
- cause: err instanceof Error ? err : undefined,
571
- };
572
- }
594
+ const abortController = new AbortController();
595
+ let interrupted = false;
596
+ const onInterrupt = () => {
597
+ if (interrupted) return;
598
+ interrupted = true;
599
+ abortController.abort();
600
+ };
601
+ process.on('SIGINT', onInterrupt);
602
+ process.on('SIGTERM', onInterrupt);
573
603
 
574
- // Build step state for clean log emission
575
- const stepState: StepState = {
576
- label: step.label,
577
- status: outcome.status,
578
- output: outcome.output,
579
- skipReason: outcome.status === 'skipped' ? outcome.reason : undefined,
580
- errorMessage: outcome.status === 'error' ? outcome.message : undefined,
581
- errorCause: outcome.status === 'error' ? outcome.cause : undefined,
582
- };
604
+ try {
605
+ for (const step of steps) {
606
+ if (abortController.signal.aborted) break;
583
607
 
584
- // Emit clean log for this step
585
- emitCleanStepLog(stepState);
608
+ let outcome: StepOutcome;
586
609
 
587
- // Print final state
588
- if (outcome.status === 'success') {
589
- console.log(`${greenColor}${ICONS.success}${COLORS.reset} ${step.label}`);
590
- if (outcome.output && outcome.output.length > 0) {
591
- console.log(`${grayColor}╭─ Output${COLORS.reset}`);
592
- for (const line of outcome.output) {
593
- console.log(`${grayColor}│${COLORS.reset} ${line}`);
610
+ try {
611
+ outcome = await step.run({ progress: () => {}, signal: abortController.signal });
612
+ } catch (err) {
613
+ // If the step was aborted due to cancellation, treat as interrupt
614
+ if (err instanceof Error && err.name === 'AbortError') {
615
+ interrupted = true;
616
+ throw new StepInterruptError();
594
617
  }
595
- console.log(`${grayColor}╰─${COLORS.reset}`);
596
- console.log('');
618
+ outcome = {
619
+ status: 'error',
620
+ message: err instanceof Error ? err.message : String(err),
621
+ cause: err instanceof Error ? err : undefined,
622
+ };
597
623
  }
598
- } else if (outcome.status === 'skipped') {
599
- const reason = outcome.reason ? ` ${grayColor}(${outcome.reason})${COLORS.reset}` : '';
600
- console.log(`${yellowColor}${ICONS.skipped}${COLORS.reset} ${step.label}${reason}`);
601
- if (outcome.output && outcome.output.length > 0) {
602
- console.log(`${grayColor}╭─ Output${COLORS.reset}`);
603
- for (const line of outcome.output) {
604
- console.log(`${grayColor}│${COLORS.reset} ${line}`);
624
+
625
+ // Build step state for clean log emission
626
+ const stepState: StepState = {
627
+ label: step.label,
628
+ status: outcome.status,
629
+ output: outcome.output,
630
+ skipReason: outcome.status === 'skipped' ? outcome.reason : undefined,
631
+ errorMessage: outcome.status === 'error' ? outcome.message : undefined,
632
+ errorCause: outcome.status === 'error' ? outcome.cause : undefined,
633
+ };
634
+
635
+ // Emit clean log for this step
636
+ emitCleanStepLog(stepState);
637
+
638
+ // Print final state
639
+ if (outcome.status === 'success') {
640
+ console.log(`${greenColor}${ICONS.success}${COLORS.reset} ${step.label}`);
641
+ if (outcome.output && outcome.output.length > 0) {
642
+ console.log(`${grayColor}╭─ Output${COLORS.reset}`);
643
+ for (const line of outcome.output) {
644
+ console.log(`${grayColor}│${COLORS.reset} ${line}`);
645
+ }
646
+ console.log(`${grayColor}╰─${COLORS.reset}`);
647
+ console.log('');
605
648
  }
606
- console.log(`${grayColor}╰─${COLORS.reset}`);
607
- console.log('');
608
- }
609
- } else {
610
- console.log(`${redColor}${ICONS.error}${COLORS.reset} ${step.label}`);
611
- if (outcome.output && outcome.output.length > 0) {
612
- console.log(`${grayColor}╭─ Output${COLORS.reset}`);
613
- for (const line of outcome.output) {
614
- console.log(`${grayColor}│${COLORS.reset} ${line}`);
649
+ } else if (outcome.status === 'skipped') {
650
+ const reason = outcome.reason ? ` ${grayColor}(${outcome.reason})${COLORS.reset}` : '';
651
+ console.log(`${yellowColor}${ICONS.skipped}${COLORS.reset} ${step.label}${reason}`);
652
+ if (outcome.output && outcome.output.length > 0) {
653
+ console.log(`${grayColor}╭─ Output${COLORS.reset}`);
654
+ for (const line of outcome.output) {
655
+ console.log(`${grayColor}│${COLORS.reset} ${line}`);
656
+ }
657
+ console.log(`${grayColor}╰─${COLORS.reset}`);
658
+ console.log('');
615
659
  }
616
- console.log(`${grayColor}╰─${COLORS.reset}`);
617
- console.log('');
618
- }
619
- const errorColor = getColor('red');
620
- const errorMsg = outcome.message || 'An unknown error occurred';
621
- console.error(`\n${errorColor}Error: ${errorMsg}${COLORS.reset}`);
622
- if (
623
- outcome.cause instanceof ValidationInputError ||
624
- outcome.cause instanceof ValidationOutputError
625
- ) {
626
- printValidationIssues(outcome.cause.issues);
660
+ } else {
661
+ // If the error is due to abort/cancellation, treat as interrupt
662
+ if (outcome.cause instanceof Error && outcome.cause.name === 'AbortError') {
663
+ interrupted = true;
664
+ throw new StepInterruptError();
665
+ }
666
+ console.log(`${redColor}${ICONS.error}${COLORS.reset} ${step.label}`);
667
+ if (outcome.output && outcome.output.length > 0) {
668
+ console.log(`${grayColor}╭─ Output${COLORS.reset}`);
669
+ for (const line of outcome.output) {
670
+ console.log(`${grayColor}│${COLORS.reset} ${line}`);
671
+ }
672
+ console.log(`${grayColor}╰─${COLORS.reset}`);
673
+ console.log('');
674
+ }
675
+ const errorColor = getColor('red');
676
+ const errorMsg = outcome.message || 'An unknown error occurred';
677
+ console.error(`\n${errorColor}Error: ${errorMsg}${COLORS.reset}`);
678
+ if (
679
+ outcome.cause instanceof ValidationInputError ||
680
+ outcome.cause instanceof ValidationOutputError
681
+ ) {
682
+ printValidationIssues(outcome.cause.issues);
683
+ }
684
+ console.error('');
685
+ process.exit(1);
627
686
  }
628
- console.error('');
629
- process.exit(1);
630
687
  }
688
+
689
+ // If interrupted during step execution, throw so callers' finally blocks run
690
+ if (interrupted) {
691
+ throw new StepInterruptError();
692
+ }
693
+ } finally {
694
+ process.off('SIGINT', onInterrupt);
695
+ process.off('SIGTERM', onInterrupt);
631
696
  }
632
697
  }
633
698
 
package/src/tui.ts CHANGED
@@ -737,7 +737,7 @@ export function banner(title: string, body: string, options?: BannerOptions): vo
737
737
  /**
738
738
  * Wait for any key press before continuing
739
739
  * Displays a prompt message and waits for user input
740
- * Exits with code 1 if CTRL+C is pressed
740
+ * Raises SIGINT if CTRL+C is pressed
741
741
  */
742
742
  export async function waitForAnyKey(message = 'Press Enter to continue...'): Promise<void> {
743
743
  process.stdout.write(muted(message));
@@ -765,7 +765,8 @@ export async function waitForAnyKey(message = 'Press Enter to continue...'): Pro
765
765
  // Check for CTRL+C (character code 3)
766
766
  if (data.length === 1 && data[0] === 3) {
767
767
  console.log('\n');
768
- process.exit(1);
768
+ process.kill(process.pid, 'SIGINT');
769
+ return;
769
770
  }
770
771
 
771
772
  console.log('');
@@ -777,7 +778,7 @@ export async function waitForAnyKey(message = 'Press Enter to continue...'): Pro
777
778
  /**
778
779
  * Prompts user with a yes/no question
779
780
  * Returns true for yes, false for no
780
- * Exits with code 1 if CTRL+C is pressed
781
+ * Raises SIGINT if CTRL+C is pressed
781
782
  */
782
783
  export async function confirm(message: string, defaultValue = true): Promise<boolean> {
783
784
  const suffix = defaultValue ? '[Y/n]' : '[y/N]';
@@ -805,7 +806,8 @@ export async function confirm(message: string, defaultValue = true): Promise<boo
805
806
  // Check for CTRL+C (character code 3)
806
807
  if (data.length === 1 && data[0] === 3) {
807
808
  console.log('\n');
808
- process.exit(1);
809
+ process.kill(process.pid, 'SIGINT');
810
+ return;
809
811
  }
810
812
 
811
813
  const input = data.toString().trim().toLowerCase();
@@ -4,7 +4,6 @@ import { fetchLatestVersion } from './cmd/upgrade';
4
4
  import { getVersion, getCompareUrl, getReleaseUrl, toTag } from './version';
5
5
  import * as tui from './tui';
6
6
  import { saveConfig } from './config';
7
- import { $ } from 'bun';
8
7
  import { tmpdir } from 'node:os';
9
8
  import { getExecutingAgent } from './agent-detection';
10
9
 
@@ -174,13 +173,14 @@ async function performUpgrade(logger: Logger, targetVersion: string): Promise<vo
174
173
 
175
174
  // Use bun to install the specific version globally with retry for CDN propagation delays
176
175
  // Run from tmpdir to avoid interference from any local package.json/node_modules
177
- const { installWithRetry } = await import('./cmd/upgrade/npm-availability');
176
+ const { installWithRetry, spawnWithTimeout } = await import('./cmd/upgrade/npm-availability');
178
177
  await installWithRetry(
179
178
  async () => {
180
- const result = await $`bun add -g @agentuity/cli@${npmVersion}`
181
- .cwd(tmpdir())
182
- .quiet()
183
- .nothrow();
179
+ // spawnWithTimeout kills the process if it exceeds 30s
180
+ const result = await spawnWithTimeout(
181
+ ['bun', 'add', '-g', `@agentuity/cli@${npmVersion}`],
182
+ { cwd: tmpdir(), timeout: 30_000 }
183
+ );
184
184
  return { exitCode: result.exitCode, stderr: result.stderr };
185
185
  },
186
186
  {