@agentuity/cli 1.0.7 → 1.0.9

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 (169) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +32 -4
  3. package/dist/cli.js.map +1 -1
  4. package/dist/cmd/ai/claude-code/constants.d.ts +13 -0
  5. package/dist/cmd/ai/claude-code/constants.d.ts.map +1 -0
  6. package/dist/cmd/ai/claude-code/constants.js +16 -0
  7. package/dist/cmd/ai/claude-code/constants.js.map +1 -0
  8. package/dist/cmd/ai/claude-code/index.d.ts +3 -0
  9. package/dist/cmd/ai/claude-code/index.d.ts.map +1 -0
  10. package/dist/cmd/ai/claude-code/index.js +22 -0
  11. package/dist/cmd/ai/claude-code/index.js.map +1 -0
  12. package/dist/cmd/ai/claude-code/install.d.ts +3 -0
  13. package/dist/cmd/ai/claude-code/install.d.ts.map +1 -0
  14. package/dist/cmd/ai/claude-code/install.js +133 -0
  15. package/dist/cmd/ai/claude-code/install.js.map +1 -0
  16. package/dist/cmd/ai/claude-code/uninstall.d.ts +3 -0
  17. package/dist/cmd/ai/claude-code/uninstall.d.ts.map +1 -0
  18. package/dist/cmd/ai/claude-code/uninstall.js +105 -0
  19. package/dist/cmd/ai/claude-code/uninstall.js.map +1 -0
  20. package/dist/cmd/ai/index.d.ts.map +1 -1
  21. package/dist/cmd/ai/index.js +6 -0
  22. package/dist/cmd/ai/index.js.map +1 -1
  23. package/dist/cmd/build/vite/db-rewrite.d.ts +50 -0
  24. package/dist/cmd/build/vite/db-rewrite.d.ts.map +1 -0
  25. package/dist/cmd/build/vite/db-rewrite.js +169 -0
  26. package/dist/cmd/build/vite/db-rewrite.js.map +1 -0
  27. package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
  28. package/dist/cmd/build/vite/registry-generator.js +7 -0
  29. package/dist/cmd/build/vite/registry-generator.js.map +1 -1
  30. package/dist/cmd/build/vite/server-bundler.d.ts.map +1 -1
  31. package/dist/cmd/build/vite/server-bundler.js +71 -1
  32. package/dist/cmd/build/vite/server-bundler.js.map +1 -1
  33. package/dist/cmd/canary/index.d.ts.map +1 -1
  34. package/dist/cmd/canary/index.js +3 -1
  35. package/dist/cmd/canary/index.js.map +1 -1
  36. package/dist/cmd/cloud/agent/list.d.ts.map +1 -1
  37. package/dist/cmd/cloud/agent/list.js +17 -4
  38. package/dist/cmd/cloud/agent/list.js.map +1 -1
  39. package/dist/cmd/cloud/apikey/list.d.ts.map +1 -1
  40. package/dist/cmd/cloud/apikey/list.js +3 -0
  41. package/dist/cmd/cloud/apikey/list.js.map +1 -1
  42. package/dist/cmd/cloud/db/list.d.ts.map +1 -1
  43. package/dist/cmd/cloud/db/list.js +2 -1
  44. package/dist/cmd/cloud/db/list.js.map +1 -1
  45. package/dist/cmd/cloud/deployment/list.d.ts.map +1 -1
  46. package/dist/cmd/cloud/deployment/list.js +14 -6
  47. package/dist/cmd/cloud/deployment/list.js.map +1 -1
  48. package/dist/cmd/cloud/deployment/remove.js +2 -2
  49. package/dist/cmd/cloud/deployment/remove.js.map +1 -1
  50. package/dist/cmd/cloud/deployment/rollback.js +2 -2
  51. package/dist/cmd/cloud/deployment/rollback.js.map +1 -1
  52. package/dist/cmd/cloud/deployment/show.js +2 -2
  53. package/dist/cmd/cloud/deployment/show.js.map +1 -1
  54. package/dist/cmd/cloud/deployment/undeploy.js +2 -2
  55. package/dist/cmd/cloud/deployment/undeploy.js.map +1 -1
  56. package/dist/cmd/cloud/env/list.d.ts.map +1 -1
  57. package/dist/cmd/cloud/env/list.js +14 -4
  58. package/dist/cmd/cloud/env/list.js.map +1 -1
  59. package/dist/cmd/cloud/eval/list.d.ts.map +1 -1
  60. package/dist/cmd/cloud/eval/list.js +6 -1
  61. package/dist/cmd/cloud/eval/list.js.map +1 -1
  62. package/dist/cmd/cloud/eval-run/list.d.ts.map +1 -1
  63. package/dist/cmd/cloud/eval-run/list.js +6 -1
  64. package/dist/cmd/cloud/eval-run/list.js.map +1 -1
  65. package/dist/cmd/cloud/keyvalue/list-namespaces.d.ts.map +1 -1
  66. package/dist/cmd/cloud/keyvalue/list-namespaces.js +5 -2
  67. package/dist/cmd/cloud/keyvalue/list-namespaces.js.map +1 -1
  68. package/dist/cmd/cloud/keyvalue/util.d.ts +1 -1
  69. package/dist/cmd/cloud/keyvalue/util.d.ts.map +1 -1
  70. package/dist/cmd/cloud/keyvalue/util.js +3 -2
  71. package/dist/cmd/cloud/keyvalue/util.js.map +1 -1
  72. package/dist/cmd/cloud/machine/list.d.ts.map +1 -1
  73. package/dist/cmd/cloud/machine/list.js +5 -2
  74. package/dist/cmd/cloud/machine/list.js.map +1 -1
  75. package/dist/cmd/cloud/queue/list.d.ts.map +1 -1
  76. package/dist/cmd/cloud/queue/list.js +3 -1
  77. package/dist/cmd/cloud/queue/list.js.map +1 -1
  78. package/dist/cmd/cloud/sandbox/execution/list.d.ts.map +1 -1
  79. package/dist/cmd/cloud/sandbox/execution/list.js +5 -3
  80. package/dist/cmd/cloud/sandbox/execution/list.js.map +1 -1
  81. package/dist/cmd/cloud/sandbox/list.d.ts.map +1 -1
  82. package/dist/cmd/cloud/sandbox/list.js +4 -1
  83. package/dist/cmd/cloud/sandbox/list.js.map +1 -1
  84. package/dist/cmd/cloud/sandbox/runtime/list.d.ts.map +1 -1
  85. package/dist/cmd/cloud/sandbox/runtime/list.js +4 -2
  86. package/dist/cmd/cloud/sandbox/runtime/list.js.map +1 -1
  87. package/dist/cmd/cloud/sandbox/snapshot/list.d.ts.map +1 -1
  88. package/dist/cmd/cloud/sandbox/snapshot/list.js +4 -2
  89. package/dist/cmd/cloud/sandbox/snapshot/list.js.map +1 -1
  90. package/dist/cmd/cloud/session/list.d.ts.map +1 -1
  91. package/dist/cmd/cloud/session/list.js +6 -1
  92. package/dist/cmd/cloud/session/list.js.map +1 -1
  93. package/dist/cmd/cloud/storage/list.d.ts.map +1 -1
  94. package/dist/cmd/cloud/storage/list.js +2 -1
  95. package/dist/cmd/cloud/storage/list.js.map +1 -1
  96. package/dist/cmd/cloud/stream/list.d.ts.map +1 -1
  97. package/dist/cmd/cloud/stream/list.js +5 -2
  98. package/dist/cmd/cloud/stream/list.js.map +1 -1
  99. package/dist/cmd/cloud/thread/list.d.ts.map +1 -1
  100. package/dist/cmd/cloud/thread/list.js +4 -1
  101. package/dist/cmd/cloud/thread/list.js.map +1 -1
  102. package/dist/cmd/cloud/vector/list-namespaces.d.ts.map +1 -1
  103. package/dist/cmd/cloud/vector/list-namespaces.js +5 -2
  104. package/dist/cmd/cloud/vector/list-namespaces.js.map +1 -1
  105. package/dist/cmd/cloud/vector/util.d.ts +1 -1
  106. package/dist/cmd/cloud/vector/util.d.ts.map +1 -1
  107. package/dist/cmd/cloud/vector/util.js +3 -2
  108. package/dist/cmd/cloud/vector/util.js.map +1 -1
  109. package/dist/cmd/dev/index.d.ts.map +1 -1
  110. package/dist/cmd/dev/index.js +2 -0
  111. package/dist/cmd/dev/index.js.map +1 -1
  112. package/dist/cmd/project/add/database.d.ts.map +1 -1
  113. package/dist/cmd/project/add/database.js +43 -3
  114. package/dist/cmd/project/add/database.js.map +1 -1
  115. package/dist/cmd/project/add/storage.d.ts.map +1 -1
  116. package/dist/cmd/project/add/storage.js +43 -3
  117. package/dist/cmd/project/add/storage.js.map +1 -1
  118. package/dist/cmd/upgrade/index.d.ts.map +1 -1
  119. package/dist/cmd/upgrade/index.js +25 -10
  120. package/dist/cmd/upgrade/index.js.map +1 -1
  121. package/dist/cmd/upgrade/npm-availability.d.ts +45 -12
  122. package/dist/cmd/upgrade/npm-availability.d.ts.map +1 -1
  123. package/dist/cmd/upgrade/npm-availability.js +73 -26
  124. package/dist/cmd/upgrade/npm-availability.js.map +1 -1
  125. package/dist/version-check.d.ts.map +1 -1
  126. package/dist/version-check.js +15 -2
  127. package/dist/version-check.js.map +1 -1
  128. package/package.json +6 -6
  129. package/src/cli.ts +41 -4
  130. package/src/cmd/ai/claude-code/constants.ts +26 -0
  131. package/src/cmd/ai/claude-code/index.ts +23 -0
  132. package/src/cmd/ai/claude-code/install.ts +181 -0
  133. package/src/cmd/ai/claude-code/uninstall.ts +122 -0
  134. package/src/cmd/ai/index.ts +6 -0
  135. package/src/cmd/build/vite/db-rewrite.ts +189 -0
  136. package/src/cmd/build/vite/registry-generator.ts +7 -0
  137. package/src/cmd/build/vite/server-bundler.ts +89 -3
  138. package/src/cmd/canary/index.ts +3 -1
  139. package/src/cmd/cloud/agent/list.ts +19 -4
  140. package/src/cmd/cloud/apikey/list.ts +4 -0
  141. package/src/cmd/cloud/db/list.ts +2 -1
  142. package/src/cmd/cloud/deployment/list.ts +16 -6
  143. package/src/cmd/cloud/deployment/remove.ts +2 -2
  144. package/src/cmd/cloud/deployment/rollback.ts +2 -2
  145. package/src/cmd/cloud/deployment/show.ts +2 -2
  146. package/src/cmd/cloud/deployment/undeploy.ts +2 -2
  147. package/src/cmd/cloud/env/list.ts +17 -4
  148. package/src/cmd/cloud/eval/list.ts +7 -1
  149. package/src/cmd/cloud/eval-run/list.ts +7 -1
  150. package/src/cmd/cloud/keyvalue/list-namespaces.ts +5 -2
  151. package/src/cmd/cloud/keyvalue/util.ts +12 -8
  152. package/src/cmd/cloud/machine/list.ts +5 -2
  153. package/src/cmd/cloud/queue/list.ts +3 -1
  154. package/src/cmd/cloud/sandbox/execution/list.ts +11 -3
  155. package/src/cmd/cloud/sandbox/list.ts +5 -1
  156. package/src/cmd/cloud/sandbox/runtime/list.ts +4 -2
  157. package/src/cmd/cloud/sandbox/snapshot/list.ts +4 -2
  158. package/src/cmd/cloud/session/list.ts +7 -1
  159. package/src/cmd/cloud/storage/list.ts +2 -1
  160. package/src/cmd/cloud/stream/list.ts +7 -2
  161. package/src/cmd/cloud/thread/list.ts +5 -1
  162. package/src/cmd/cloud/vector/list-namespaces.ts +5 -2
  163. package/src/cmd/cloud/vector/util.ts +12 -8
  164. package/src/cmd/dev/index.ts +2 -0
  165. package/src/cmd/project/add/database.ts +54 -3
  166. package/src/cmd/project/add/storage.ts +54 -3
  167. package/src/cmd/upgrade/index.ts +36 -11
  168. package/src/cmd/upgrade/npm-availability.ts +106 -36
  169. package/src/version-check.ts +22 -2
@@ -69,6 +69,7 @@ export const listSubcommand = createSubcommand({
69
69
  prefix: z.string().optional().describe('Path prefix to filter files'),
70
70
  }),
71
71
  options: z.object({
72
+ orgId: z.string().optional().describe('filter by organization id'),
72
73
  showCredentials: z
73
74
  .boolean()
74
75
  .optional()
@@ -94,7 +95,7 @@ export const listSubcommand = createSubcommand({
94
95
  message: 'Fetching storage',
95
96
  clearOnSuccess: true,
96
97
  callback: async () => {
97
- return listOrgResources(catalystClient, { type: 's3' });
98
+ return listOrgResources(catalystClient, { type: 's3', orgId: opts?.orgId });
98
99
  },
99
100
  });
100
101
 
@@ -74,8 +74,13 @@ export const listSubcommand = createCommand({
74
74
 
75
75
  async handler(ctx) {
76
76
  const { opts, options, apiClient, project } = ctx;
77
- // Use project context if available, or explicit flag
78
- const projectId = opts.projectId || project?.projectId;
77
+
78
+ if (opts?.orgId && opts?.projectId) {
79
+ tui.fatal('--org-id and --project-id are mutually exclusive. Use one or the other.');
80
+ }
81
+
82
+ // Use explicit projectId flag; only fall back to project context when --org-id is not set
83
+ const projectId = opts.projectId || (opts.orgId ? undefined : project?.projectId);
79
84
 
80
85
  // Parse metadata filter if provided
81
86
  let metadataFilter: Record<string, string> | undefined;
@@ -70,7 +70,11 @@ export const listSubcommand = createSubcommand({
70
70
  const { logger, auth, project, opts, options, config } = ctx;
71
71
  const catalystClient = await getGlobalCatalystAPIClient(logger, auth, config?.name);
72
72
 
73
- const projectId = opts.all ? undefined : opts.projectId || project?.projectId;
73
+ if (opts?.orgId && opts?.projectId) {
74
+ tui.fatal('--org-id and --project-id are mutually exclusive. Use one or the other.');
75
+ }
76
+
77
+ const projectId = opts.all || opts.orgId ? undefined : opts.projectId || project?.projectId;
74
78
  const orgId = opts.orgId;
75
79
 
76
80
  try {
@@ -19,14 +19,17 @@ export const listNamespacesSubcommand = createCommand({
19
19
  { command: getCommand('vector ns'), description: 'List namespaces (short alias)' },
20
20
  ],
21
21
  schema: {
22
+ options: z.object({
23
+ orgId: z.string().optional().describe('filter by organization id'),
24
+ }),
22
25
  response: NamespaceListResponseSchema,
23
26
  },
24
27
  webUrl: '/services/vector',
25
28
  idempotent: true,
26
29
 
27
30
  async handler(ctx) {
28
- const { options } = ctx;
29
- const storage = await createStorageAdapter(ctx);
31
+ const { options, opts } = ctx;
32
+ const storage = await createStorageAdapter(ctx, opts?.orgId);
30
33
  const namespaces = await storage.getNamespaces();
31
34
 
32
35
  if (!options.json) {
@@ -4,15 +4,19 @@ import type { AuthData, Config, GlobalOptions, ProjectConfig } from '../../../ty
4
4
  import { getCatalystUrl } from '../../../catalyst';
5
5
  import * as tui from '../../../tui';
6
6
 
7
- export function createStorageAdapter(ctx: {
8
- logger: Logger;
9
- auth: AuthData;
10
- region: string;
11
- project?: ProjectConfig;
12
- config: Config | null;
13
- options: GlobalOptions;
14
- }) {
7
+ export function createStorageAdapter(
8
+ ctx: {
9
+ logger: Logger;
10
+ auth: AuthData;
11
+ region: string;
12
+ project?: ProjectConfig;
13
+ config: Config | null;
14
+ options: GlobalOptions;
15
+ },
16
+ explicitOrgId?: string
17
+ ) {
15
18
  const orgId =
19
+ explicitOrgId ??
16
20
  ctx.project?.orgId ??
17
21
  ctx.options.orgId ??
18
22
  (process.env.AGENTUITY_CLOUD_ORG_ID || ctx.config?.preferences?.orgId);
@@ -1050,6 +1050,8 @@ export const command = createCommand({
1050
1050
  }
1051
1051
  process.env.PORT = String(opts.port);
1052
1052
  process.env.AGENTUITY_PORT = process.env.PORT;
1053
+ process.env.AGENTUITY_BASE_URL =
1054
+ process.env.AGENTUITY_BASE_URL || `http://localhost:${opts.port}`;
1053
1055
 
1054
1056
  if (project) {
1055
1057
  // Set environment variables for LLM provider patches
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { listResources } from '@agentuity/server';
2
+ import { listResources, projectEnvUpdate } from '@agentuity/server';
3
3
  import { createSubcommand } from '../../../types';
4
4
  import * as tui from '../../../tui';
5
5
  import { createPrompt } from '../../../tui';
@@ -7,7 +7,13 @@ import { getCatalystAPIClient } from '../../../config';
7
7
  import { getCommand } from '../../../command-prefix';
8
8
  import { isDryRunMode, outputDryRun } from '../../../explain';
9
9
  import { ErrorCode } from '../../../errors';
10
- import { addResourceEnvVars } from '../../../env-util';
10
+ import {
11
+ addResourceEnvVars,
12
+ findExistingEnvFile,
13
+ readEnvFile,
14
+ filterAgentuitySdkKeys,
15
+ splitEnvAndSecrets,
16
+ } from '../../../env-util';
11
17
 
12
18
  export const databaseSubcommand = createSubcommand({
13
19
  name: 'database',
@@ -42,7 +48,7 @@ export const databaseSubcommand = createSubcommand({
42
48
  },
43
49
 
44
50
  async handler(ctx) {
45
- const { logger, args, orgId, region, auth, options, projectDir } = ctx;
51
+ const { logger, args, orgId, region, auth, options, projectDir, project } = ctx;
46
52
 
47
53
  if (isDryRunMode(options)) {
48
54
  const message = args.name
@@ -131,6 +137,51 @@ export const databaseSubcommand = createSubcommand({
131
137
  tui.success(`Linked database: ${tui.bold(selectedDatabase.name)}`);
132
138
  tui.info('Environment variables written to .env');
133
139
  }
140
+
141
+ // Sync to cloud
142
+ const isHeadless = !process.stdin.isTTY || !process.stdout.isTTY;
143
+ let shouldSync = true;
144
+
145
+ if (!isHeadless && !options.json) {
146
+ shouldSync = await tui.confirm('Sync environment variables to cloud project?', true);
147
+ if (process.stdin.isTTY) {
148
+ process.stdin.pause();
149
+ }
150
+ }
151
+
152
+ if (shouldSync) {
153
+ try {
154
+ const envFilePath = await findExistingEnvFile(projectDir);
155
+ const localEnv = await readEnvFile(envFilePath);
156
+ const filteredEnv = filterAgentuitySdkKeys(localEnv);
157
+
158
+ if (Object.keys(filteredEnv).length > 0) {
159
+ const { env, secrets } = splitEnvAndSecrets(filteredEnv);
160
+ await tui.spinner({
161
+ message: 'Syncing environment variables to cloud',
162
+ clearOnSuccess: true,
163
+ callback: async () => {
164
+ await projectEnvUpdate(catalystClient, {
165
+ id: project.projectId,
166
+ env,
167
+ secrets,
168
+ });
169
+ },
170
+ });
171
+ if (!options.json) {
172
+ tui.success('Environment variables synced to cloud');
173
+ }
174
+ }
175
+ } catch (error) {
176
+ if (!options.json) {
177
+ tui.warning(
178
+ 'Failed to sync environment variables to cloud. You can sync later with: ' +
179
+ tui.bold(getCommand('cloud env push'))
180
+ );
181
+ }
182
+ logger.debug('Failed to sync env to cloud:', error);
183
+ }
184
+ }
134
185
  } else {
135
186
  if (!options.json) {
136
187
  tui.warning(`Database "${selectedDatabase.name}" has no environment variables to add`);
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { listResources } from '@agentuity/server';
2
+ import { listResources, projectEnvUpdate } from '@agentuity/server';
3
3
  import { createSubcommand } from '../../../types';
4
4
  import * as tui from '../../../tui';
5
5
  import { createPrompt } from '../../../tui';
@@ -7,7 +7,13 @@ import { getCatalystAPIClient } from '../../../config';
7
7
  import { getCommand } from '../../../command-prefix';
8
8
  import { isDryRunMode, outputDryRun } from '../../../explain';
9
9
  import { ErrorCode } from '../../../errors';
10
- import { addResourceEnvVars } from '../../../env-util';
10
+ import {
11
+ addResourceEnvVars,
12
+ findExistingEnvFile,
13
+ readEnvFile,
14
+ filterAgentuitySdkKeys,
15
+ splitEnvAndSecrets,
16
+ } from '../../../env-util';
11
17
 
12
18
  export const storageSubcommand = createSubcommand({
13
19
  name: 'storage',
@@ -42,7 +48,7 @@ export const storageSubcommand = createSubcommand({
42
48
  },
43
49
 
44
50
  async handler(ctx) {
45
- const { logger, args, orgId, region, auth, options, projectDir } = ctx;
51
+ const { logger, args, orgId, region, auth, options, projectDir, project } = ctx;
46
52
 
47
53
  if (isDryRunMode(options)) {
48
54
  const message = args.name
@@ -131,6 +137,51 @@ export const storageSubcommand = createSubcommand({
131
137
  tui.success(`Linked storage bucket: ${tui.bold(selectedBucket.bucket_name)}`);
132
138
  tui.info('Environment variables written to .env');
133
139
  }
140
+
141
+ // Sync to cloud
142
+ const isHeadless = !process.stdin.isTTY || !process.stdout.isTTY;
143
+ let shouldSync = true;
144
+
145
+ if (!isHeadless && !options.json) {
146
+ shouldSync = await tui.confirm('Sync environment variables to cloud project?', true);
147
+ if (process.stdin.isTTY) {
148
+ process.stdin.pause();
149
+ }
150
+ }
151
+
152
+ if (shouldSync) {
153
+ try {
154
+ const envFilePath = await findExistingEnvFile(projectDir);
155
+ const localEnv = await readEnvFile(envFilePath);
156
+ const filteredEnv = filterAgentuitySdkKeys(localEnv);
157
+
158
+ if (Object.keys(filteredEnv).length > 0) {
159
+ const { env, secrets } = splitEnvAndSecrets(filteredEnv);
160
+ await tui.spinner({
161
+ message: 'Syncing environment variables to cloud',
162
+ clearOnSuccess: true,
163
+ callback: async () => {
164
+ await projectEnvUpdate(catalystClient, {
165
+ id: project.projectId,
166
+ env,
167
+ secrets,
168
+ });
169
+ },
170
+ });
171
+ if (!options.json) {
172
+ tui.success('Environment variables synced to cloud');
173
+ }
174
+ }
175
+ } catch (error) {
176
+ if (!options.json) {
177
+ tui.warning(
178
+ 'Failed to sync environment variables to cloud. You can sync later with: ' +
179
+ tui.bold(getCommand('cloud env push'))
180
+ );
181
+ }
182
+ logger.debug('Failed to sync env to cloud:', error);
183
+ }
184
+ }
134
185
  } else {
135
186
  if (!options.json) {
136
187
  tui.warning(
@@ -5,6 +5,7 @@ import { z } from 'zod';
5
5
  import { ErrorCode, createError, exitWithError } from '../../errors';
6
6
  import * as tui from '../../tui';
7
7
  import { $ } from 'bun';
8
+ import { tmpdir } from 'node:os';
8
9
  import { getInstallationType, type InstallationType } from '../../utils/installation-type';
9
10
 
10
11
  const UpgradeOptionsSchema = z.object({
@@ -70,18 +71,35 @@ export async function fetchLatestVersion(): Promise<string> {
70
71
  }
71
72
 
72
73
  /**
73
- * Upgrade the CLI using bun global install
74
+ * Upgrade the CLI using bun global install.
75
+ * Retries on transient resolution errors caused by npm CDN propagation delays.
74
76
  */
75
- async function performBunUpgrade(version: string): Promise<void> {
77
+ async function performBunUpgrade(
78
+ version: string,
79
+ onRetry?: (attempt: number, delayMs: number) => void
80
+ ): Promise<void> {
76
81
  // Remove 'v' prefix for npm version
77
82
  const npmVersion = version.replace(/^v/, '');
78
83
 
79
- // Use bun to install the specific version globally
80
- const result = await $`bun add -g @agentuity/cli@${npmVersion}`.quiet().nothrow();
81
-
82
- if (result.exitCode !== 0) {
83
- const stderr = result.stderr.toString();
84
- throw new Error(`Failed to install @agentuity/cli@${npmVersion}: ${stderr}`);
84
+ const { installWithRetry } = await import('./npm-availability');
85
+
86
+ try {
87
+ await installWithRetry(
88
+ async () => {
89
+ // Use bun to install the specific version globally
90
+ // Run from tmpdir to avoid interference from any local package.json/node_modules
91
+ const result = await $`bun add -g @agentuity/cli@${npmVersion}`
92
+ .cwd(tmpdir())
93
+ .quiet()
94
+ .nothrow();
95
+ return { exitCode: result.exitCode, stderr: result.stderr };
96
+ },
97
+ { onRetry }
98
+ );
99
+ } catch (error) {
100
+ throw new Error(
101
+ `Failed to install @agentuity/cli@${npmVersion}: ${error instanceof Error ? error.message : String(error)}`
102
+ );
85
103
  }
86
104
  }
87
105
 
@@ -192,8 +210,9 @@ export const command = createCommand({
192
210
  callback: async () => {
193
211
  const { waitForNpmAvailability } = await import('./npm-availability');
194
212
  return await waitForNpmAvailability(latestVersion, {
195
- maxAttempts: 6,
196
- initialDelayMs: 2000,
213
+ maxAttempts: 10,
214
+ initialDelayMs: 5000,
215
+ maxDelayMs: 15000,
197
216
  });
198
217
  },
199
218
  });
@@ -242,8 +261,14 @@ export const command = createCommand({
242
261
 
243
262
  // Perform the upgrade using bun
244
263
  await tui.spinner({
264
+ type: 'logger',
245
265
  message: `Installing @agentuity/cli@${normalizedLatest}...`,
246
- callback: async () => await performBunUpgrade(latestVersion),
266
+ callback: async (log) =>
267
+ await performBunUpgrade(latestVersion, (attempt, delayMs) => {
268
+ log(
269
+ `Package not yet available on CDN, retrying in ${Math.round(delayMs / 1000)}s (attempt ${attempt})...`
270
+ );
271
+ }),
247
272
  });
248
273
 
249
274
  // Verify the upgrade
@@ -1,62 +1,51 @@
1
1
  /**
2
2
  * npm registry availability checking utilities.
3
- * Used to verify a version is available on npm before attempting upgrade.
3
+ * Used to verify a version is available via bun's resolver before attempting upgrade.
4
4
  */
5
5
 
6
- const NPM_REGISTRY_URL = 'https://registry.npmjs.org';
7
- const PACKAGE_NAME = '@agentuity/cli';
6
+ import { $ } from 'bun';
7
+ import { tmpdir } from 'node:os';
8
8
 
9
- /** Default timeout for quick checks (implicit version check) */
10
- const QUICK_CHECK_TIMEOUT_MS = 1000;
11
-
12
- /** Default timeout for explicit upgrade command */
13
- const EXPLICIT_CHECK_TIMEOUT_MS = 5000;
14
-
15
- export interface CheckNpmOptions {
16
- /** Timeout in milliseconds (default: 5000 for explicit, 1000 for quick) */
17
- timeoutMs?: number;
18
- }
9
+ const PACKAGE_SPEC = '@agentuity/cli';
19
10
 
20
11
  /**
21
- * Check if a specific version of @agentuity/cli is available on npm registry.
22
- * Uses the npm registry API directly for faster response than `npm view`.
12
+ * Check if a specific version of @agentuity/cli is resolvable by bun.
13
+ * Uses `bun info` to verify bun's own resolver/CDN can see the version,
14
+ * which avoids the race where npm registry returns 200 but bun's CDN
15
+ * has not yet propagated the version.
23
16
  *
24
17
  * @param version - Version to check (with or without 'v' prefix)
25
- * @param options - Optional configuration
26
18
  * @returns true if version is available, false otherwise
27
19
  */
28
- export async function isVersionAvailableOnNpm(
29
- version: string,
30
- options: CheckNpmOptions = {}
31
- ): Promise<boolean> {
32
- const { timeoutMs = EXPLICIT_CHECK_TIMEOUT_MS } = options;
20
+ export async function isVersionAvailableOnNpm(version: string): Promise<boolean> {
33
21
  const normalizedVersion = version.replace(/^v/, '');
34
- const url = `${NPM_REGISTRY_URL}/${encodeURIComponent(PACKAGE_NAME)}/${normalizedVersion}`;
35
-
36
22
  try {
37
- const response = await fetch(url, {
38
- method: 'HEAD', // Only need to check existence, not full metadata
39
- signal: AbortSignal.timeout(timeoutMs),
40
- headers: {
41
- Accept: 'application/json',
42
- },
43
- });
44
- return response.ok;
23
+ const result = await $`bun info ${PACKAGE_SPEC}@${normalizedVersion} --json`
24
+ .cwd(tmpdir())
25
+ .quiet()
26
+ .nothrow();
27
+ if (result.exitCode !== 0) {
28
+ return false;
29
+ }
30
+ const info = JSON.parse(result.stdout.toString());
31
+ if (info.error) {
32
+ return false;
33
+ }
34
+ return info.version === normalizedVersion;
45
35
  } catch {
46
- // Network error or timeout - assume not available
47
36
  return false;
48
37
  }
49
38
  }
50
39
 
51
40
  /**
52
- * Quick check if a version is available on npm with a short timeout.
53
- * Used for implicit version checks (auto-upgrade flow) to avoid blocking the user's command.
41
+ * Quick check if a version is available via bun's resolver.
42
+ * Used for implicit version checks (auto-upgrade flow).
54
43
  *
55
44
  * @param version - Version to check (with or without 'v' prefix)
56
- * @returns true if version is available, false if unavailable or timeout
45
+ * @returns true if version is available, false if unavailable or error
57
46
  */
58
47
  export async function isVersionAvailableOnNpmQuick(version: string): Promise<boolean> {
59
- return isVersionAvailableOnNpm(version, { timeoutMs: QUICK_CHECK_TIMEOUT_MS });
48
+ return isVersionAvailableOnNpm(version);
60
49
  }
61
50
 
62
51
  export interface WaitForNpmOptions {
@@ -103,3 +92,84 @@ export async function waitForNpmAvailability(
103
92
 
104
93
  return false;
105
94
  }
95
+
96
+ /**
97
+ * Patterns in bun's stderr that indicate a resolution/CDN propagation failure
98
+ * (as opposed to a permanent install error like permissions or disk space).
99
+ */
100
+ const RESOLUTION_ERROR_PATTERNS = [/failed to resolve/i, /no version matching/i];
101
+
102
+ /**
103
+ * Check whether a bun install failure is a transient resolution error
104
+ * caused by npm CDN propagation delays.
105
+ */
106
+ export function isResolutionError(stderr: string): boolean {
107
+ return RESOLUTION_ERROR_PATTERNS.some((pattern) => pattern.test(stderr));
108
+ }
109
+
110
+ export interface InstallWithRetryOptions {
111
+ /** Maximum number of attempts including the first (default: 7 → 1 initial + 6 retries) */
112
+ maxAttempts?: number;
113
+ /** Initial delay in ms before the first retry (default: 5000) */
114
+ initialDelayMs?: number;
115
+ /** Maximum delay cap in ms (default: 30000) */
116
+ maxDelayMs?: number;
117
+ /** Multiplier applied to the delay after each retry (default: 2) */
118
+ multiplier?: number;
119
+ /** Callback invoked before each retry with the attempt number and upcoming delay */
120
+ onRetry?: (attempt: number, delayMs: number) => void;
121
+ }
122
+
123
+ /**
124
+ * Run an install function and retry on transient resolution errors with
125
+ * exponential backoff. This covers the window (~2 min) where npm CDN nodes
126
+ * have not yet propagated a newly-published version.
127
+ *
128
+ * Total wait with defaults: 5 + 10 + 20 + 30 + 30 + 30 = 125 s ≈ 2 min
129
+ *
130
+ * @param installFn - Async function that performs the install and returns exitCode + stderr
131
+ * @param options - Retry configuration
132
+ * @returns The successful result (exitCode 0)
133
+ * @throws Error if all retries are exhausted or a non-resolution error occurs
134
+ */
135
+ export async function installWithRetry(
136
+ installFn: () => Promise<{ exitCode: number; stderr: Buffer }>,
137
+ options: InstallWithRetryOptions = {}
138
+ ): Promise<{ exitCode: number; stderr: Buffer }> {
139
+ const {
140
+ maxAttempts = 7,
141
+ initialDelayMs = 5000,
142
+ maxDelayMs = 30000,
143
+ multiplier = 2,
144
+ onRetry,
145
+ } = options;
146
+
147
+ let delay = initialDelayMs;
148
+
149
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
150
+ const result = await installFn();
151
+
152
+ if (result.exitCode === 0) {
153
+ return result;
154
+ }
155
+
156
+ const stderr = result.stderr.toString();
157
+
158
+ // Only retry on resolution/propagation errors — bail immediately for anything else
159
+ if (!isResolutionError(stderr)) {
160
+ throw new Error(stderr);
161
+ }
162
+
163
+ // Last attempt exhausted — throw
164
+ if (attempt === maxAttempts) {
165
+ throw new Error(stderr);
166
+ }
167
+
168
+ onRetry?.(attempt, delay);
169
+ await new Promise((resolve) => setTimeout(resolve, delay));
170
+ delay = Math.min(Math.round(delay * multiplier), maxDelayMs);
171
+ }
172
+
173
+ // Unreachable, but satisfies TypeScript
174
+ throw new Error('Install failed after retries');
175
+ }
@@ -5,6 +5,7 @@ import { getVersion, getCompareUrl, getReleaseUrl, toTag } from './version';
5
5
  import * as tui from './tui';
6
6
  import { saveConfig } from './config';
7
7
  import { $ } from 'bun';
8
+ import { tmpdir } from 'node:os';
8
9
  import { getExecutingAgent } from './agent-detection';
9
10
 
10
11
  const ONE_HOUR_MS = 60 * 60 * 1000;
@@ -171,8 +172,27 @@ async function performUpgrade(logger: Logger, targetVersion: string): Promise<vo
171
172
 
172
173
  logger.info('Upgrading to version %s...', npmVersion);
173
174
 
174
- // Use bun to install the specific version globally
175
- await $`bun add -g @agentuity/cli@${npmVersion}`.quiet();
175
+ // Use bun to install the specific version globally with retry for CDN propagation delays
176
+ // Run from tmpdir to avoid interference from any local package.json/node_modules
177
+ const { installWithRetry } = await import('./cmd/upgrade/npm-availability');
178
+ await installWithRetry(
179
+ async () => {
180
+ const result = await $`bun add -g @agentuity/cli@${npmVersion}`
181
+ .cwd(tmpdir())
182
+ .quiet()
183
+ .nothrow();
184
+ return { exitCode: result.exitCode, stderr: result.stderr };
185
+ },
186
+ {
187
+ onRetry: (attempt, delayMs) => {
188
+ logger.info(
189
+ 'Package not yet available on CDN, retrying in %ds (attempt %d)...',
190
+ Math.round(delayMs / 1000),
191
+ attempt
192
+ );
193
+ },
194
+ }
195
+ );
176
196
 
177
197
  // If we got here, the upgrade succeeded
178
198
  // Re-run the original command with the new binary