@agentuity/cli 0.0.86 → 0.0.88

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 (119) hide show
  1. package/bin/cli.ts +7 -0
  2. package/dist/bun-path.d.ts.map +1 -1
  3. package/dist/bun-path.js +1 -3
  4. package/dist/bun-path.js.map +1 -1
  5. package/dist/cli.js +3 -3
  6. package/dist/cmd/ai/index.d.ts.map +1 -1
  7. package/dist/cmd/ai/index.js +1 -0
  8. package/dist/cmd/ai/index.js.map +1 -1
  9. package/dist/cmd/build/ast.d.ts.map +1 -1
  10. package/dist/cmd/build/ast.js +5 -0
  11. package/dist/cmd/build/ast.js.map +1 -1
  12. package/dist/cmd/build/bundler.d.ts.map +1 -1
  13. package/dist/cmd/build/bundler.js +152 -7
  14. package/dist/cmd/build/bundler.js.map +1 -1
  15. package/dist/cmd/build/config-loader.d.ts +16 -0
  16. package/dist/cmd/build/config-loader.d.ts.map +1 -0
  17. package/dist/cmd/build/config-loader.js +165 -0
  18. package/dist/cmd/build/config-loader.js.map +1 -0
  19. package/dist/cmd/build/patch/_util.js +6 -6
  20. package/dist/cmd/build/patch/_util.js.map +1 -1
  21. package/dist/cmd/build/patch/llm.js +1 -1
  22. package/dist/cmd/build/patch/llm.js.map +1 -1
  23. package/dist/cmd/build/plugin.d.ts.map +1 -1
  24. package/dist/cmd/build/plugin.js +36 -15
  25. package/dist/cmd/build/plugin.js.map +1 -1
  26. package/dist/cmd/build/route-discovery.d.ts +8 -4
  27. package/dist/cmd/build/route-discovery.d.ts.map +1 -1
  28. package/dist/cmd/build/route-discovery.js +10 -5
  29. package/dist/cmd/build/route-discovery.js.map +1 -1
  30. package/dist/cmd/build/workbench.d.ts +1 -0
  31. package/dist/cmd/build/workbench.d.ts.map +1 -1
  32. package/dist/cmd/build/workbench.js +8 -1
  33. package/dist/cmd/build/workbench.js.map +1 -1
  34. package/dist/cmd/cloud/index.d.ts.map +1 -1
  35. package/dist/cmd/cloud/index.js +2 -0
  36. package/dist/cmd/cloud/index.js.map +1 -1
  37. package/dist/cmd/cloud/redis/get.d.ts +2 -0
  38. package/dist/cmd/cloud/redis/get.d.ts.map +1 -0
  39. package/dist/cmd/cloud/redis/get.js +62 -0
  40. package/dist/cmd/cloud/redis/get.js.map +1 -0
  41. package/dist/cmd/cloud/redis/index.d.ts +2 -0
  42. package/dist/cmd/cloud/redis/index.d.ts.map +1 -0
  43. package/dist/cmd/cloud/redis/index.js +13 -0
  44. package/dist/cmd/cloud/redis/index.js.map +1 -0
  45. package/dist/cmd/cloud/scp/download.js +3 -3
  46. package/dist/cmd/cloud/scp/download.js.map +1 -1
  47. package/dist/cmd/cloud/scp/upload.js +3 -3
  48. package/dist/cmd/cloud/scp/upload.js.map +1 -1
  49. package/dist/cmd/cloud/ssh.js +3 -3
  50. package/dist/cmd/cloud/ssh.js.map +1 -1
  51. package/dist/cmd/dev/index.d.ts.map +1 -1
  52. package/dist/cmd/dev/index.js +5 -0
  53. package/dist/cmd/dev/index.js.map +1 -1
  54. package/dist/cmd/index.d.ts.map +1 -1
  55. package/dist/cmd/index.js +7 -0
  56. package/dist/cmd/index.js.map +1 -1
  57. package/dist/cmd/profile/create.d.ts.map +1 -1
  58. package/dist/cmd/profile/create.js +1 -0
  59. package/dist/cmd/profile/create.js.map +1 -1
  60. package/dist/cmd/upgrade/index.d.ts +20 -0
  61. package/dist/cmd/upgrade/index.d.ts.map +1 -0
  62. package/dist/cmd/upgrade/index.js +307 -0
  63. package/dist/cmd/upgrade/index.js.map +1 -0
  64. package/dist/cmd/version/index.d.ts.map +1 -1
  65. package/dist/cmd/version/index.js +1 -0
  66. package/dist/cmd/version/index.js.map +1 -1
  67. package/dist/config.d.ts +1 -1
  68. package/dist/config.d.ts.map +1 -1
  69. package/dist/config.js +12 -94
  70. package/dist/config.js.map +1 -1
  71. package/dist/index.d.ts +1 -1
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js.map +1 -1
  74. package/dist/tui.d.ts +5 -0
  75. package/dist/tui.d.ts.map +1 -1
  76. package/dist/tui.js +23 -0
  77. package/dist/tui.js.map +1 -1
  78. package/dist/types.d.ts +73 -0
  79. package/dist/types.d.ts.map +1 -1
  80. package/dist/types.js.map +1 -1
  81. package/dist/utils/dependency-checker.d.ts +20 -0
  82. package/dist/utils/dependency-checker.d.ts.map +1 -0
  83. package/dist/utils/dependency-checker.js +161 -0
  84. package/dist/utils/dependency-checker.js.map +1 -0
  85. package/dist/version-check.d.ts +13 -0
  86. package/dist/version-check.d.ts.map +1 -0
  87. package/dist/version-check.js +177 -0
  88. package/dist/version-check.js.map +1 -0
  89. package/package.json +6 -4
  90. package/src/bun-path.ts +1 -3
  91. package/src/cli.ts +3 -3
  92. package/src/cmd/ai/index.ts +1 -0
  93. package/src/cmd/build/ast.ts +7 -0
  94. package/src/cmd/build/bundler.ts +181 -8
  95. package/src/cmd/build/config-loader.ts +200 -0
  96. package/src/cmd/build/patch/_util.ts +6 -6
  97. package/src/cmd/build/patch/llm.ts +1 -1
  98. package/src/cmd/build/plugin.ts +40 -17
  99. package/src/cmd/build/route-discovery.ts +10 -5
  100. package/src/cmd/build/workbench.ts +9 -1
  101. package/src/cmd/cloud/index.ts +2 -0
  102. package/src/cmd/cloud/redis/get.ts +72 -0
  103. package/src/cmd/cloud/redis/index.ts +13 -0
  104. package/src/cmd/cloud/scp/download.ts +3 -3
  105. package/src/cmd/cloud/scp/upload.ts +3 -3
  106. package/src/cmd/cloud/ssh.ts +3 -3
  107. package/src/cmd/dev/index.ts +11 -0
  108. package/src/cmd/index.ts +8 -0
  109. package/src/cmd/profile/create.ts +1 -0
  110. package/src/cmd/project/download.ts +1 -1
  111. package/src/cmd/upgrade/index.ts +365 -0
  112. package/src/cmd/version/index.ts +1 -0
  113. package/src/config.ts +12 -121
  114. package/src/git-helper.ts +4 -4
  115. package/src/index.ts +4 -0
  116. package/src/tui.ts +27 -0
  117. package/src/types.ts +80 -0
  118. package/src/utils/dependency-checker.ts +207 -0
  119. package/src/version-check.ts +234 -0
package/src/config.ts CHANGED
@@ -407,81 +407,6 @@ function getPlaceholderValue(schema: z.ZodTypeAny): string {
407
407
  }
408
408
  }
409
409
 
410
- function extractDefaultValue(schema: z.ZodTypeAny): unknown {
411
- let unwrapped = schema;
412
-
413
- // Unwrap optional layers
414
- while (unwrapped instanceof z.ZodOptional) {
415
- unwrapped = (unwrapped._def as unknown as { innerType: z.ZodTypeAny }).innerType;
416
- }
417
-
418
- // Check if it's a ZodDefault (has defaultValue in def or _def)
419
- const checkDef = (obj: unknown): unknown => {
420
- if (typeof obj !== 'object' || obj === null) return undefined;
421
- const anyObj = obj as Record<string, unknown>;
422
-
423
- // Check `def` property first (used in some Zod versions)
424
- if ('def' in anyObj && typeof anyObj.def === 'object' && anyObj.def !== null) {
425
- const def = anyObj.def as Record<string, unknown>;
426
- if (def.type === 'default' && 'defaultValue' in def) {
427
- const val = def.defaultValue;
428
- return typeof val === 'function' ? (val as () => unknown)() : val;
429
- }
430
- }
431
-
432
- // Check `_def` property (standard Zod property)
433
- if ('_def' in anyObj && typeof anyObj._def === 'object' && anyObj._def !== null) {
434
- const def = anyObj._def as Record<string, unknown>;
435
- if (def.type === 'default' && 'defaultValue' in def) {
436
- const val = def.defaultValue;
437
- return typeof val === 'function' ? (val as () => unknown)() : val;
438
- }
439
- }
440
-
441
- return undefined;
442
- };
443
-
444
- return checkDef(unwrapped);
445
- }
446
-
447
- function getValueWithDefaults(schema: z.ZodTypeAny, providedValue: unknown): unknown {
448
- // If value is explicitly provided, use it
449
- if (providedValue !== undefined) {
450
- return providedValue;
451
- }
452
-
453
- // Try to extract default value
454
- const defaultValue = extractDefaultValue(schema);
455
- if (defaultValue !== undefined) {
456
- return defaultValue;
457
- }
458
-
459
- // For optional fields without defaults, check if it's an object
460
- let unwrapped = schema;
461
- if (schema instanceof z.ZodOptional) {
462
- unwrapped = (schema._def as unknown as { innerType: z.ZodTypeAny }).innerType;
463
- }
464
-
465
- // If it's an object schema, recursively populate defaults
466
- if (unwrapped instanceof z.ZodObject) {
467
- const shape = unwrapped.shape;
468
- const result: Record<string, unknown> = {};
469
- let hasAnyDefaults = false;
470
-
471
- for (const [key, fieldSchema] of Object.entries(shape)) {
472
- const fieldValue = getValueWithDefaults(fieldSchema as z.ZodTypeAny, undefined);
473
- if (fieldValue !== undefined) {
474
- result[key] = fieldValue;
475
- hasAnyDefaults = true;
476
- }
477
- }
478
-
479
- return hasAnyDefaults ? result : undefined;
480
- }
481
-
482
- return undefined;
483
- }
484
-
485
410
  export function generateYAMLTemplate(name: string): string {
486
411
  const lines: string[] = [];
487
412
 
@@ -552,39 +477,6 @@ class ProjectConfigNotFoundExpection extends Error {
552
477
 
553
478
  type ProjectConfig = z.infer<typeof ProjectSchema>;
554
479
 
555
- function generateJSON5WithComments(
556
- jsonSchema: string,
557
- schema: z.ZodObject<z.ZodRawShape>,
558
- data: Record<string, unknown>
559
- ): string {
560
- const lines: string[] = ['{'];
561
- const shape = schema.shape;
562
- const keys = Object.keys(shape);
563
-
564
- lines.push(` "$schema": "${jsonSchema}",`);
565
-
566
- for (let i = 0; i < keys.length; i++) {
567
- const key = keys[i];
568
- const fieldSchema = shape[key] as z.ZodTypeAny;
569
- const description = getSchemaDescription(fieldSchema);
570
- const providedValue = data[key];
571
-
572
- if (description) {
573
- lines.push(` // ${description}`);
574
- }
575
-
576
- // Get value with defaults applied
577
- const valueWithDefaults = getValueWithDefaults(fieldSchema, providedValue);
578
- const safeValue = valueWithDefaults === undefined ? null : valueWithDefaults;
579
- const jsonValue = JSON.stringify(safeValue, null, 2).replace(/\n/g, '\n ');
580
- const comma = i < keys.length - 1 ? ',' : '';
581
- lines.push(` ${JSON.stringify(key)}: ${jsonValue}${comma}`);
582
- }
583
-
584
- lines.push('}');
585
- return lines.join('\n');
586
- }
587
-
588
480
  export async function loadProjectConfig(
589
481
  dir: string,
590
482
  config?: Config | null
@@ -636,12 +528,11 @@ export async function createProjectConfig(dir: string, config: InitialProjectCon
636
528
 
637
529
  // generate the project config
638
530
  const configPath = join(dir, 'agentuity.json');
639
- const json5Content = generateJSON5WithComments(
640
- 'https://agentuity.dev/schema/cli/v1/agentuity.json',
641
- ProjectSchema,
642
- sanitizedConfig
643
- );
644
- await Bun.write(configPath, json5Content + '\n');
531
+ const configData = {
532
+ $schema: 'https://agentuity.dev/schema/cli/v1/agentuity.json',
533
+ ...sanitizedConfig,
534
+ };
535
+ await Bun.write(configPath, JSON.stringify(configData, null, 2) + '\n');
645
536
 
646
537
  // generate the .env file with initial secret
647
538
  const envPath = join(dir, '.env');
@@ -656,9 +547,6 @@ export async function createProjectConfig(dir: string, config: InitialProjectCon
656
547
  mkdirSync(vscodeDir);
657
548
 
658
549
  const settings = {
659
- 'files.associations': {
660
- 'agentuity.json': 'jsonc',
661
- },
662
550
  'search.exclude': {
663
551
  '**/.git/**': true,
664
552
  '**/node_modules/**': true,
@@ -743,12 +631,15 @@ export function getCatalystAPIClient(logger: Logger, auth: AuthData, region: str
743
631
  return new ServerAPIClient(catalystUrl, logger, auth.apiKey);
744
632
  }
745
633
 
746
- export function getIONHost(config: Config | null) {
747
- if (config?.name === 'local') {
634
+ export function getIONHost(config: Config | null, region: string) {
635
+ if (config?.overrides?.ion_url) {
636
+ const url = new URL(config.overrides.ion_url);
637
+ return url.hostname;
638
+ }
639
+ if (config?.name === 'local' || region === 'local') {
748
640
  return 'ion.agentuity.io';
749
641
  }
750
- const url = new URL(config?.overrides?.ion_url ?? 'https://ion.agentuity.cloud');
751
- return url.hostname;
642
+ return `ion-${region}.agentuity.cloud`;
752
643
  }
753
644
 
754
645
  export function getStreamURL(region: string, config: Config | null) {
package/src/git-helper.ts CHANGED
@@ -1,17 +1,17 @@
1
1
  /**
2
2
  * Git helper utilities for detecting and using git safely.
3
- *
3
+ *
4
4
  * On macOS, git may be a stub that triggers Xcode Command Line Tools installation popup.
5
5
  * This helper detects the real git binary and provides safe wrappers.
6
6
  */
7
7
 
8
8
  /**
9
9
  * Check if git is available and is the real git binary (not the macOS stub).
10
- *
10
+ *
11
11
  * On macOS without Xcode CLT installed, /usr/bin/git exists but it's a stub that
12
12
  * triggers a popup asking to install developer tools. We detect this by checking
13
13
  * if Xcode Command Line Tools are installed using `xcode-select -p`.
14
- *
14
+ *
15
15
  * @returns true if git is available and functional, false otherwise
16
16
  */
17
17
  export async function isGitAvailable(): Promise<boolean> {
@@ -28,7 +28,7 @@ export async function isGitAvailable(): Promise<boolean> {
28
28
  stdout: 'pipe',
29
29
  stderr: 'pipe',
30
30
  });
31
-
31
+
32
32
  // If xcode-select -p fails, CLT are not installed, git is just a stub
33
33
  if (result.exitCode !== 0) {
34
34
  return false;
package/src/index.ts CHANGED
@@ -104,6 +104,10 @@ export type {
104
104
  Profile,
105
105
  AuthData,
106
106
  CommandSchemas,
107
+ BuildPhase,
108
+ BuildContext,
109
+ BuildConfig,
110
+ BuildConfigFunction,
107
111
  } from './types';
108
112
  export { createSubcommand, createCommand } from './types';
109
113
  export type { ColorScheme } from './terminal';
package/src/tui.ts CHANGED
@@ -15,6 +15,25 @@ import { type APIClient as APIClientType } from './api';
15
15
  import { getExitCode } from './errors';
16
16
  import { maskSecret } from './env-util';
17
17
 
18
+ // Install global exit handler to always restore terminal cursor
19
+ // This ensures cursor is restored even when process.exit() is called directly
20
+ let exitHandlerInstalled = false;
21
+ function ensureCursorRestoration(): void {
22
+ if (exitHandlerInstalled) return;
23
+ exitHandlerInstalled = true;
24
+
25
+ const restoreCursor = () => {
26
+ // Restore cursor visibility
27
+ process.stderr.write('\x1B[?25h');
28
+ };
29
+
30
+ // Handle process exit
31
+ process.on('exit', restoreCursor);
32
+ }
33
+
34
+ // Install handler immediately when module loads
35
+ ensureCursorRestoration();
36
+
18
37
  // Re-export maskSecret for convenience
19
38
  export { maskSecret };
20
39
 
@@ -327,6 +346,14 @@ export function newline(): void {
327
346
  process.stderr.write('\n');
328
347
  }
329
348
 
349
+ /**
350
+ * Print plain text output without any prefix or icon
351
+ * Use for primary command output that shouldn't have semantic formatting
352
+ */
353
+ export function output(message: string): void {
354
+ console.log(message);
355
+ }
356
+
330
357
  /**
331
358
  * Get the display width of a string, handling ANSI codes and OSC 8 hyperlinks
332
359
  *
package/src/types.ts CHANGED
@@ -63,6 +63,79 @@ export type Config = zod.infer<typeof ConfigSchema>;
63
63
 
64
64
  export type LogLevel = 'debug' | 'trace' | 'info' | 'warn' | 'error';
65
65
 
66
+ /**
67
+ * Build phases for the bundler
68
+ */
69
+ export type BuildPhase = 'api' | 'web' | 'workbench';
70
+
71
+ /**
72
+ * Context provided to the build config function
73
+ */
74
+ export interface BuildContext {
75
+ /**
76
+ * The root directory of the project
77
+ */
78
+ rootDir: string;
79
+ /**
80
+ * Whether this is a development build
81
+ */
82
+ dev: boolean;
83
+ /**
84
+ * The output directory for the build
85
+ */
86
+ outDir: string;
87
+ /**
88
+ * The source directory
89
+ */
90
+ srcDir: string;
91
+ /**
92
+ * Organization ID (if available)
93
+ */
94
+ orgId?: string;
95
+ /**
96
+ * Project ID (if available)
97
+ */
98
+ projectId?: string;
99
+ /**
100
+ * Deployment region
101
+ */
102
+ region: string;
103
+ /**
104
+ * Logger instance
105
+ */
106
+ logger: Logger;
107
+ }
108
+
109
+ /**
110
+ * User-provided build configuration for a specific phase
111
+ */
112
+ export interface BuildConfig {
113
+ /**
114
+ * Additional Bun plugins to apply during bundling
115
+ * These are added AFTER the Agentuity plugin
116
+ */
117
+ plugins?: Array<import('bun').BunPlugin>;
118
+ /**
119
+ * Additional external modules to exclude from bundling
120
+ * These are merged with Agentuity's default externals
121
+ */
122
+ external?: string[];
123
+ /**
124
+ * Additional define constants for code replacement
125
+ * These are merged with Agentuity's default defines
126
+ * Note: Cannot override process.env.AGENTUITY_* or process.env.NODE_ENV
127
+ */
128
+ define?: Record<string, string>;
129
+ }
130
+
131
+ /**
132
+ * Configuration function that users export from agentuity.config.ts
133
+ */
134
+ export type BuildConfigFunction = (
135
+ phase: BuildPhase,
136
+ context: BuildContext
137
+ ) => BuildConfig | Promise<BuildConfig>;
138
+
66
139
  export interface Profile {
67
140
  name: string;
68
141
  filename: string;
@@ -89,6 +162,7 @@ export interface GlobalOptions {
89
162
  explain?: boolean;
90
163
  dryRun?: boolean;
91
164
  validate?: boolean;
165
+ skipVersionCheck?: boolean;
92
166
  }
93
167
 
94
168
  export interface PaginationInfo {
@@ -282,6 +356,8 @@ export function createCommand<
282
356
  banner?: true;
283
357
  aliases?: string[];
284
358
  hidden?: boolean;
359
+ executable?: boolean;
360
+ skipUpgradeCheck?: boolean;
285
361
  requires?: R;
286
362
  optional?: O;
287
363
  examples?: CommandExample[];
@@ -318,6 +394,8 @@ type CommandDefBase =
318
394
  description: string;
319
395
  aliases?: string[];
320
396
  banner?: boolean;
397
+ executable?: boolean;
398
+ skipUpgradeCheck?: boolean;
321
399
  examples?: CommandExample[];
322
400
  idempotent?: boolean;
323
401
  prerequisites?: string[];
@@ -332,6 +410,8 @@ type CommandDefBase =
332
410
  description: string;
333
411
  aliases?: string[];
334
412
  banner?: boolean;
413
+ executable?: boolean;
414
+ skipUpgradeCheck?: boolean;
335
415
  examples?: CommandExample[];
336
416
  idempotent?: boolean;
337
417
  prerequisites?: string[];
@@ -0,0 +1,207 @@
1
+ import { $ } from 'bun';
2
+ import { join } from 'node:path';
3
+ import { readFileSync, writeFileSync } from 'node:fs';
4
+ import { getVersion } from '../version';
5
+ import type { Logger } from '../types';
6
+
7
+ interface PackageJson {
8
+ dependencies?: Record<string, string>;
9
+ devDependencies?: Record<string, string>;
10
+ }
11
+
12
+ export interface UpgradeResult {
13
+ upgraded: string[];
14
+ skipped: string[];
15
+ failed: string[];
16
+ }
17
+
18
+ /**
19
+ * Checks if a version specifier should be upgraded
20
+ * @param specifier - The version specifier from package.json (e.g., "latest", "^1.0.0", "1.2.3")
21
+ * @returns true if the package should be upgraded
22
+ */
23
+ export function shouldUpgradeVersion(specifier: string): boolean {
24
+ // Always upgrade "latest" and "*"
25
+ if (specifier === 'latest' || specifier === '*') {
26
+ return true;
27
+ }
28
+
29
+ // Skip pinned versions (exact semver like "1.2.3")
30
+ if (/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/.test(specifier)) {
31
+ return false;
32
+ }
33
+
34
+ // Upgrade ranges (^1.0.0, ~1.0.0, >=1.0.0, etc.)
35
+ // Check if the specifier is a range pattern
36
+ if (/^[~^>=<]/.test(specifier)) {
37
+ return true;
38
+ }
39
+
40
+ // Default to not upgrading if we can't determine
41
+ return false;
42
+ }
43
+
44
+ /**
45
+ * Check and upgrade @agentuity/* dependencies to match CLI version
46
+ * @param projectDir - Root directory of the user's project
47
+ * @param logger - Logger instance
48
+ * @returns Result of the upgrade operation
49
+ */
50
+ export async function checkAndUpgradeDependencies(
51
+ projectDir: string,
52
+ logger: Logger
53
+ ): Promise<UpgradeResult> {
54
+ const result: UpgradeResult = {
55
+ upgraded: [],
56
+ skipped: [],
57
+ failed: [],
58
+ };
59
+
60
+ // Skip in CI/non-interactive environments
61
+ if (!process.stdin.isTTY) {
62
+ logger.debug('Skipping dependency check in non-interactive environment');
63
+ return result;
64
+ }
65
+
66
+ const packageJsonPath = join(projectDir, 'package.json');
67
+ const cliVersion = getVersion();
68
+
69
+ logger.debug('CLI version: %s', cliVersion);
70
+
71
+ // Read package.json
72
+ let packageJson: PackageJson;
73
+ try {
74
+ packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
75
+ } catch (error) {
76
+ logger.debug('Failed to read package.json: %s', error);
77
+ return result;
78
+ }
79
+
80
+ // Collect all @agentuity/* packages and their original specifiers
81
+ const allDeps = {
82
+ ...packageJson.dependencies,
83
+ ...packageJson.devDependencies,
84
+ };
85
+
86
+ const agentuitPackages = Object.entries(allDeps)
87
+ .filter(([name]) => name.startsWith('@agentuity/'))
88
+ .map(([name, specifier]) => ({ name, specifier }));
89
+
90
+ if (agentuitPackages.length === 0) {
91
+ logger.debug('No @agentuity/* packages found in package.json');
92
+ return result;
93
+ }
94
+
95
+ // Check which packages need upgrading
96
+ const packagesToUpgrade = agentuitPackages.filter(({ specifier }) =>
97
+ shouldUpgradeVersion(specifier)
98
+ );
99
+
100
+ if (packagesToUpgrade.length === 0) {
101
+ logger.debug('All @agentuity/* packages are pinned, skipping upgrade');
102
+ for (const pkg of agentuitPackages) {
103
+ result.skipped.push(pkg.name);
104
+ }
105
+ return result;
106
+ }
107
+
108
+ // Check if CLI version is different from installed packages
109
+ let needsUpgrade = false;
110
+ for (const { name } of packagesToUpgrade) {
111
+ try {
112
+ const installedPackageJson = JSON.parse(
113
+ readFileSync(join(projectDir, 'node_modules', name, 'package.json'), 'utf-8')
114
+ );
115
+ const installedVersion: string = installedPackageJson.version;
116
+ if (installedVersion !== cliVersion) {
117
+ logger.debug(
118
+ '%s: installed=%s, cli=%s (needs upgrade)',
119
+ name,
120
+ installedVersion,
121
+ cliVersion
122
+ );
123
+ needsUpgrade = true;
124
+ } else {
125
+ logger.debug('%s: already at correct version %s', name, installedVersion);
126
+ }
127
+ } catch {
128
+ // Package not installed or can't read version - needs upgrade
129
+ logger.debug('%s: not installed or unreadable, needs upgrade', name);
130
+ needsUpgrade = true;
131
+ }
132
+ }
133
+
134
+ if (!needsUpgrade) {
135
+ logger.debug('All @agentuity/* packages are already at CLI version');
136
+ for (const pkg of packagesToUpgrade) {
137
+ result.skipped.push(pkg.name);
138
+ }
139
+ return result;
140
+ }
141
+
142
+ // Upgrade packages
143
+ logger.debug('Upgrading %d @agentuity/* package(s) to %s', packagesToUpgrade.length, cliVersion);
144
+
145
+ for (const { name } of packagesToUpgrade) {
146
+ try {
147
+ logger.debug('Installing %s@%s', name, cliVersion);
148
+ const installResult = await $`bun add ${name}@${cliVersion}`
149
+ .cwd(projectDir)
150
+ .quiet()
151
+ .nothrow();
152
+
153
+ if (installResult.exitCode !== 0) {
154
+ logger.error(
155
+ 'Failed to install %s@%s: %s',
156
+ name,
157
+ cliVersion,
158
+ installResult.stderr.toString()
159
+ );
160
+ result.failed.push(name);
161
+ } else {
162
+ logger.debug('Successfully installed %s@%s', name, cliVersion);
163
+ result.upgraded.push(name);
164
+ }
165
+ } catch (_error) {
166
+ logger.error('Error installing %s: %s', name, _error);
167
+ result.failed.push(name);
168
+ }
169
+ }
170
+
171
+ // Restore original version specifiers in package.json
172
+ // (bun add replaces them with specific versions)
173
+ if (result.upgraded.length > 0) {
174
+ try {
175
+ const updatedPackageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
176
+ let modified = false;
177
+
178
+ for (const { name, specifier } of packagesToUpgrade) {
179
+ // Only restore if we successfully upgraded
180
+ if (!result.upgraded.includes(name)) {
181
+ continue;
182
+ }
183
+
184
+ // Check both dependencies and devDependencies
185
+ if (updatedPackageJson.dependencies?.[name]) {
186
+ updatedPackageJson.dependencies[name] = specifier;
187
+ modified = true;
188
+ logger.debug('Restored %s to "%s" in dependencies', name, specifier);
189
+ }
190
+ if (updatedPackageJson.devDependencies?.[name]) {
191
+ updatedPackageJson.devDependencies[name] = specifier;
192
+ modified = true;
193
+ logger.debug('Restored %s to "%s" in devDependencies', name, specifier);
194
+ }
195
+ }
196
+
197
+ if (modified) {
198
+ writeFileSync(packageJsonPath, JSON.stringify(updatedPackageJson, null, 2) + '\n');
199
+ logger.debug('Restored original version specifiers in package.json');
200
+ }
201
+ } catch (_error) {
202
+ logger.warn('Failed to restore version specifiers in package.json: %s', _error);
203
+ }
204
+ }
205
+
206
+ return result;
207
+ }