@agentuity/cli 0.1.28 → 0.1.29

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 (73) hide show
  1. package/dist/banner.js +2 -2
  2. package/dist/banner.js.map +1 -1
  3. package/dist/cmd/auth/signup.js +1 -1
  4. package/dist/cmd/auth/signup.js.map +1 -1
  5. package/dist/cmd/canary/index.d.ts.map +1 -1
  6. package/dist/cmd/canary/index.js +62 -127
  7. package/dist/cmd/canary/index.js.map +1 -1
  8. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  9. package/dist/cmd/cloud/deploy.js +1 -8
  10. package/dist/cmd/cloud/deploy.js.map +1 -1
  11. package/dist/cmd/dev/api.d.ts +3 -1
  12. package/dist/cmd/dev/api.d.ts.map +1 -1
  13. package/dist/cmd/dev/api.js +16 -2
  14. package/dist/cmd/dev/api.js.map +1 -1
  15. package/dist/cmd/dev/index.d.ts.map +1 -1
  16. package/dist/cmd/dev/index.js +16 -3
  17. package/dist/cmd/dev/index.js.map +1 -1
  18. package/dist/cmd/index.d.ts.map +1 -1
  19. package/dist/cmd/index.js +0 -6
  20. package/dist/cmd/index.js.map +1 -1
  21. package/dist/cmd/setup/index.d.ts.map +1 -1
  22. package/dist/cmd/setup/index.js +7 -9
  23. package/dist/cmd/setup/index.js.map +1 -1
  24. package/dist/cmd/upgrade/index.d.ts +7 -24
  25. package/dist/cmd/upgrade/index.d.ts.map +1 -1
  26. package/dist/cmd/upgrade/index.js +78 -263
  27. package/dist/cmd/upgrade/index.js.map +1 -1
  28. package/dist/command-prefix.d.ts +1 -1
  29. package/dist/command-prefix.d.ts.map +1 -1
  30. package/dist/command-prefix.js +4 -14
  31. package/dist/command-prefix.js.map +1 -1
  32. package/dist/download.d.ts.map +1 -1
  33. package/dist/download.js +2 -0
  34. package/dist/download.js.map +1 -1
  35. package/dist/errors.d.ts +2 -1
  36. package/dist/errors.d.ts.map +1 -1
  37. package/dist/errors.js +3 -0
  38. package/dist/errors.js.map +1 -1
  39. package/dist/legacy-check.d.ts.map +1 -1
  40. package/dist/legacy-check.js +2 -2
  41. package/dist/legacy-check.js.map +1 -1
  42. package/dist/types.d.ts +1 -0
  43. package/dist/types.d.ts.map +1 -1
  44. package/dist/types.js +1 -0
  45. package/dist/types.js.map +1 -1
  46. package/dist/utils/installation-type.d.ts +25 -0
  47. package/dist/utils/installation-type.d.ts.map +1 -0
  48. package/dist/utils/installation-type.js +43 -0
  49. package/dist/utils/installation-type.js.map +1 -0
  50. package/dist/version-check.d.ts.map +1 -1
  51. package/dist/version-check.js +16 -13
  52. package/dist/version-check.js.map +1 -1
  53. package/dist/version.js +1 -1
  54. package/dist/version.js.map +1 -1
  55. package/package.json +6 -6
  56. package/src/api-errors.md +2 -2
  57. package/src/banner.ts +2 -2
  58. package/src/cmd/auth/signup.ts +1 -1
  59. package/src/cmd/canary/index.ts +69 -144
  60. package/src/cmd/cloud/deploy.ts +1 -12
  61. package/src/cmd/dev/api.ts +19 -3
  62. package/src/cmd/dev/index.ts +23 -3
  63. package/src/cmd/index.ts +0 -7
  64. package/src/cmd/setup/index.ts +8 -9
  65. package/src/cmd/upgrade/index.ts +84 -302
  66. package/src/command-prefix.ts +4 -18
  67. package/src/download.ts +3 -0
  68. package/src/errors.ts +4 -0
  69. package/src/legacy-check.ts +2 -4
  70. package/src/types.ts +1 -0
  71. package/src/utils/installation-type.ts +51 -0
  72. package/src/version-check.ts +18 -13
  73. package/src/version.ts +1 -1
@@ -4,41 +4,8 @@ import { getCommand } from '../../command-prefix';
4
4
  import { z } from 'zod';
5
5
  import { ErrorCode, createError, exitWithError } from '../../errors';
6
6
  import * as tui from '../../tui';
7
- import { downloadWithProgress } from '../../download';
8
7
  import { $ } from 'bun';
9
- import { join, dirname } from 'node:path';
10
- import { tmpdir } from 'node:os';
11
- import { randomUUID } from 'node:crypto';
12
- import { access, constants } from 'node:fs/promises';
13
- import { StructuredError } from '@agentuity/core';
14
-
15
- export const PermissionError = StructuredError('PermissionError')<{
16
- binaryPath: string;
17
- reason: string;
18
- }>();
19
-
20
- async function checkWritePermission(binaryPath: string): Promise<void> {
21
- try {
22
- await access(binaryPath, constants.W_OK);
23
- } catch {
24
- throw new PermissionError({
25
- binaryPath,
26
- reason: `Cannot write to ${binaryPath}. You may need to run with elevated permissions (e.g., sudo) or reinstall to a user-writable location.`,
27
- message: `Permission denied: Cannot write to ${binaryPath}`,
28
- });
29
- }
30
-
31
- const parentDir = dirname(binaryPath);
32
- try {
33
- await access(parentDir, constants.W_OK);
34
- } catch {
35
- throw new PermissionError({
36
- binaryPath,
37
- reason: `Cannot write to directory ${parentDir}. You may need to run with elevated permissions (e.g., sudo) or reinstall to a user-writable location.`,
38
- message: `Permission denied: Cannot write to directory ${parentDir}`,
39
- });
40
- }
41
- }
8
+ import { getInstallationType, type InstallationType } from '../../utils/installation-type';
42
9
 
43
10
  const UpgradeOptionsSchema = z.object({
44
11
  force: z.boolean().optional().describe('Force upgrade even if version is the same'),
@@ -52,68 +19,17 @@ const UpgradeResponseSchema = z.object({
52
19
  });
53
20
 
54
21
  /**
55
- * Check if running from a compiled executable (not via bun/bunx)
56
- * @internal Exported for testing
22
+ * Get the installation type - re-exported for backward compatibility
23
+ * @deprecated Use getInstallationType() from '../../utils/installation-type' instead
57
24
  */
58
- export function isRunningFromExecutable(): boolean {
59
- const scriptPath = process.argv[1] || '';
60
-
61
- // Check if running from compiled binary (uses Bun's virtual filesystem)
62
- // When compiled with `bun build --compile`, the script path is in the virtual /$bunfs/root/ directory
63
- // Note: process.argv[0] is the executable path (e.g., /usr/local/bin/agentuity), not 'bun'
64
- if (scriptPath.startsWith('/$bunfs/root/')) {
65
- return true;
66
- }
67
-
68
- // If running via bun/bunx (from node_modules or .ts files), it's not an executable
69
- if (Bun.main.includes('/node_modules/') || Bun.main.includes('.ts')) {
70
- return false;
71
- }
72
-
73
- // Check if in a bin directory but not in node_modules (globally installed)
74
- const normalized = Bun.main;
75
- const isGlobal =
76
- normalized.includes('/bin/') &&
77
- !normalized.includes('/node_modules/') &&
78
- !normalized.includes('/packages/cli/bin');
79
-
80
- return isGlobal;
81
- }
25
+ export { getInstallationType, type InstallationType };
82
26
 
83
27
  /**
84
- * Get the OS and architecture for downloading the binary
85
- * @internal Exported for testing
28
+ * Check if running from a global installation
29
+ * This replaces the old isRunningFromExecutable() function
86
30
  */
87
- export function getPlatformInfo(): { os: string; arch: string } {
88
- const platform = process.platform;
89
- const arch = process.arch;
90
-
91
- let os: string;
92
- let archStr: string;
93
-
94
- switch (platform) {
95
- case 'darwin':
96
- os = 'darwin';
97
- break;
98
- case 'linux':
99
- os = 'linux';
100
- break;
101
- default:
102
- throw new Error(`Unsupported platform: ${platform}`);
103
- }
104
-
105
- switch (arch) {
106
- case 'x64':
107
- archStr = 'x64';
108
- break;
109
- case 'arm64':
110
- archStr = 'arm64';
111
- break;
112
- default:
113
- throw new Error(`Unsupported architecture: ${arch}`);
114
- }
115
-
116
- return { os, arch: archStr };
31
+ export function isGlobalInstall(): boolean {
32
+ return getInstallationType() === 'global';
117
33
  }
118
34
 
119
35
  /**
@@ -121,11 +37,19 @@ export function getPlatformInfo(): { os: string; arch: string } {
121
37
  * @internal Exported for testing
122
38
  */
123
39
  export async function fetchLatestVersion(): Promise<string> {
40
+ const currentVersion = getVersion();
124
41
  const response = await fetch('https://agentuity.sh/release/sdk/version', {
125
- signal: AbortSignal.timeout(10000), // 10 second timeout
42
+ signal: AbortSignal.timeout(10_000), // 10 second timeout
43
+ headers: {
44
+ 'User-Agent': `Agentuity CLI/${currentVersion}`,
45
+ },
126
46
  });
127
47
  if (!response.ok) {
128
- throw new Error(`Failed to fetch version: ${response.statusText}`);
48
+ const body = await response.text();
49
+ if (response.status === 426) {
50
+ tui.fatal(body, ErrorCode.UPGRADE_REQUIRED);
51
+ }
52
+ throw new Error(`Failed to fetch version: ${body}`);
129
53
  }
130
54
 
131
55
  const version = await response.text();
@@ -146,157 +70,47 @@ export async function fetchLatestVersion(): Promise<string> {
146
70
  }
147
71
 
148
72
  /**
149
- * Download the binary for the specified version
73
+ * Upgrade the CLI using bun global install
150
74
  */
151
- async function downloadBinary(
152
- version: string,
153
- platform: { os: string; arch: string }
154
- ): Promise<string> {
155
- const { os, arch } = platform;
156
- const url = `https://agentuity.sh/release/sdk/${version}/${os}/${arch}`;
157
-
158
- const tmpDir = tmpdir();
159
- const tmpFile = join(tmpDir, `agentuity-${randomUUID()}`);
160
- const gzFile = `${tmpFile}.gz`;
161
-
162
- const stream = await downloadWithProgress({
163
- url,
164
- message: `Downloading version ${version}...`,
165
- });
75
+ async function performBunUpgrade(version: string): Promise<void> {
76
+ // Remove 'v' prefix for npm version
77
+ const npmVersion = version.replace(/^v/, '');
166
78
 
167
- // Write to temp file
168
- const writer = Bun.file(gzFile).writer();
169
- for await (const chunk of stream) {
170
- writer.write(chunk);
171
- }
172
- await writer.end();
79
+ // Use bun to install the specific version globally
80
+ const result = await $`bun add -g @agentuity/cli@${npmVersion}`.quiet().nothrow();
173
81
 
174
- // Verify file was downloaded
175
- if (!(await Bun.file(gzFile).exists())) {
176
- throw new Error('Download failed - file not created');
82
+ if (result.exitCode !== 0) {
83
+ const stderr = result.stderr.toString();
84
+ throw new Error(`Failed to install @agentuity/cli@${npmVersion}: ${stderr}`);
177
85
  }
178
-
179
- // Decompress using gunzip
180
- try {
181
- await $`gunzip ${gzFile}`.quiet();
182
- } catch (error) {
183
- if (await Bun.file(gzFile).exists()) {
184
- await $`rm ${gzFile}`.quiet();
185
- }
186
- throw new Error(
187
- `Decompression failed: ${error instanceof Error ? error.message : 'Unknown error'}`
188
- );
189
- }
190
-
191
- // Verify decompressed file exists
192
- if (!(await Bun.file(tmpFile).exists())) {
193
- throw new Error('Decompression failed - file not found');
194
- }
195
-
196
- // Verify it's a valid binary
197
- const fileType = await $`file ${tmpFile}`.text();
198
- if (!fileType.match(/(executable|ELF|Mach-O|PE32)/i)) {
199
- throw new Error('Downloaded file is not a valid executable');
200
- }
201
-
202
- // Make executable
203
- await $`chmod 755 ${tmpFile}`.quiet();
204
-
205
- return tmpFile;
206
86
  }
207
87
 
208
88
  /**
209
- * Validate the downloaded binary by running version command
89
+ * Verify the upgrade was successful by checking the installed version
210
90
  */
211
- async function validateBinary(binaryPath: string, expectedVersion: string): Promise<void> {
212
- try {
213
- // Use spawn to capture both stdout and stderr
214
- const proc = Bun.spawn([binaryPath, 'version'], {
215
- stdout: 'pipe',
216
- stderr: 'pipe',
217
- });
218
-
219
- const [stdout, stderr] = await Promise.all([
220
- new Response(proc.stdout).text(),
221
- new Response(proc.stderr).text(),
222
- ]);
223
-
224
- const exitCode = await proc.exited;
225
-
226
- if (exitCode !== 0) {
227
- const errorDetails = [];
228
- if (stdout.trim()) errorDetails.push(`stdout: ${stdout.trim()}`);
229
- if (stderr.trim()) errorDetails.push(`stderr: ${stderr.trim()}`);
230
- const details = errorDetails.length > 0 ? `\n${errorDetails.join('\n')}` : '';
231
- throw new Error(`Failed with exit code ${exitCode}${details}`);
232
- }
233
-
234
- const actualVersion = stdout.trim();
91
+ async function verifyUpgrade(expectedVersion: string): Promise<void> {
92
+ // Run agentuity version to check the installed version
93
+ const result = await $`agentuity version`.quiet().nothrow();
235
94
 
236
- // Normalize versions for comparison (remove 'v' prefix)
237
- const normalizedExpected = expectedVersion.replace(/^v/, '');
238
- const normalizedActual = actualVersion.replace(/^v/, '');
239
-
240
- if (normalizedActual !== normalizedExpected) {
241
- throw new Error(`Version mismatch: expected ${expectedVersion}, got ${actualVersion}`);
242
- }
243
- } catch (error) {
244
- if (error instanceof Error) {
245
- throw new Error(`Binary validation failed: ${error.message}`);
246
- }
247
- throw new Error('Binary validation failed');
95
+ if (result.exitCode !== 0) {
96
+ throw new Error('Failed to verify upgrade - could not run agentuity version');
248
97
  }
249
- }
250
98
 
251
- /**
252
- * Replace the current binary with the new one
253
- * Uses platform-specific safe replacement strategies
254
- */
255
- async function replaceBinary(newBinaryPath: string, currentBinaryPath: string): Promise<void> {
256
- const platform = process.platform;
99
+ const installedVersion = result.stdout.toString().trim();
100
+ const normalizedExpected = expectedVersion.replace(/^v/, '');
101
+ const normalizedInstalled = installedVersion.replace(/^v/, '');
257
102
 
258
- if (platform === 'darwin' || platform === 'linux') {
259
- // Unix: Use atomic move via temp file
260
- const backupPath = `${currentBinaryPath}.backup`;
261
- const tempPath = `${currentBinaryPath}.new`;
262
-
263
- try {
264
- // Copy new binary to temp location next to current binary
265
- await $`cp ${newBinaryPath} ${tempPath}`.quiet();
266
- await $`chmod 755 ${tempPath}`.quiet();
267
-
268
- // Backup current binary
269
- if (await Bun.file(currentBinaryPath).exists()) {
270
- await $`cp ${currentBinaryPath} ${backupPath}`.quiet();
271
- }
272
-
273
- // Atomic rename
274
- await $`mv ${tempPath} ${currentBinaryPath}`.quiet();
275
-
276
- // Clean up backup after successful replacement
277
- if (await Bun.file(backupPath).exists()) {
278
- await $`rm ${backupPath}`.quiet();
279
- }
280
- } catch (error) {
281
- // Try to restore backup if replacement failed
282
- if (await Bun.file(backupPath).exists()) {
283
- await $`mv ${backupPath} ${currentBinaryPath}`.quiet();
284
- }
285
- // Clean up temp file if it exists
286
- if (await Bun.file(tempPath).exists()) {
287
- await $`rm ${tempPath}`.quiet();
288
- }
289
- throw error;
290
- }
291
- } else {
292
- throw new Error(`Unsupported platform for binary replacement: ${platform}`);
103
+ if (normalizedInstalled !== normalizedExpected) {
104
+ throw new Error(
105
+ `Version mismatch after upgrade: expected ${normalizedExpected}, got ${normalizedInstalled}`
106
+ );
293
107
  }
294
108
  }
295
109
 
296
110
  export const command = createCommand({
297
111
  name: 'upgrade',
298
112
  description: 'Upgrade the CLI to the latest version',
299
- executable: true,
113
+ hidden: false, // Always visible, but handler checks installation type
300
114
  skipUpgradeCheck: true,
301
115
  tags: ['update'],
302
116
  examples: [
@@ -318,9 +132,35 @@ export const command = createCommand({
318
132
  const { logger, options } = ctx;
319
133
  const { force } = ctx.opts;
320
134
 
135
+ const installationType = getInstallationType();
321
136
  const currentVersion = getVersion();
322
- // Use process.execPath to get the actual file path (Bun.main is virtual for compiled binaries)
323
- const currentBinaryPath = process.execPath;
137
+
138
+ // Check if we can upgrade based on installation type
139
+ if (installationType === 'source') {
140
+ tui.error('Upgrade is not available when running from source.');
141
+ tui.info('You are running the CLI from source code (development mode).');
142
+ tui.info('Use git to update the source code instead.');
143
+ return {
144
+ upgraded: false,
145
+ from: currentVersion,
146
+ to: currentVersion,
147
+ message: 'Cannot upgrade: running from source',
148
+ };
149
+ }
150
+
151
+ if (installationType === 'local') {
152
+ tui.error('Upgrade is not available for local project installations.');
153
+ tui.info('The CLI is installed as a project dependency.');
154
+ tui.newline();
155
+ tui.info('To upgrade, update your package.json or run:');
156
+ tui.info(` ${tui.muted('bun add @agentuity/cli@latest')}`);
157
+ return {
158
+ upgraded: false,
159
+ from: currentVersion,
160
+ to: currentVersion,
161
+ message: 'Cannot upgrade: local project installation',
162
+ };
163
+ }
324
164
 
325
165
  try {
326
166
  // Fetch latest version
@@ -359,39 +199,6 @@ export const command = createCommand({
359
199
  tui.newline();
360
200
  }
361
201
 
362
- // Check write permissions before prompting - fail early with helpful message
363
- try {
364
- await checkWritePermission(currentBinaryPath);
365
- } catch (error) {
366
- if (error instanceof PermissionError) {
367
- tui.error('Unable to upgrade: permission denied');
368
- tui.newline();
369
- tui.warning(`The CLI binary at ${tui.bold(error.binaryPath)} is not writable.`);
370
- tui.newline();
371
- if (process.env.AGENTUITY_RUNTIME) {
372
- console.log('You cannot self-upgrade the agentuity cli in the cloud runtime.');
373
- console.log('The runtime will automatically update the cli and other software');
374
- console.log('within a day or so. If you need assistance, please contact us');
375
- console.log('at support@agentuity.com.');
376
- } else {
377
- console.log('To fix this, you can either:');
378
- console.log(
379
- ` 1. Run with elevated permissions: ${tui.muted('sudo agentuity upgrade')}`
380
- );
381
- console.log(` 2. Reinstall to a user-writable location`);
382
- }
383
- tui.newline();
384
- exitWithError(
385
- createError(ErrorCode.PERMISSION_DENIED, 'Upgrade failed: permission denied', {
386
- path: error.binaryPath,
387
- }),
388
- logger,
389
- options.errorFormat
390
- );
391
- }
392
- throw error;
393
- }
394
-
395
202
  // Confirm upgrade
396
203
  if (!force) {
397
204
  const shouldUpgrade = await tui.confirm('Do you want to upgrade?', true);
@@ -408,39 +215,30 @@ export const command = createCommand({
408
215
  }
409
216
  }
410
217
 
411
- // Get platform info
412
- const platform = getPlatformInfo();
413
-
414
- // Download binary
415
- const tmpBinaryPath = await tui.spinner({
416
- type: 'progress',
417
- message: 'Downloading...',
418
- callback: async () => await downloadBinary(latestVersion, platform),
419
- });
420
-
421
- // Validate binary
218
+ // Perform the upgrade using bun
422
219
  await tui.spinner({
423
- message: 'Validating binary...',
424
- callback: async () => await validateBinary(tmpBinaryPath, latestVersion),
220
+ message: `Installing @agentuity/cli@${normalizedLatest}...`,
221
+ callback: async () => await performBunUpgrade(latestVersion),
425
222
  });
426
223
 
427
- // Replace binary
224
+ // Verify the upgrade
428
225
  await tui.spinner({
429
- message: 'Installing...',
430
- callback: async () => await replaceBinary(tmpBinaryPath, currentBinaryPath),
226
+ message: 'Verifying installation...',
227
+ callback: async () => await verifyUpgrade(latestVersion),
431
228
  });
432
229
 
433
- // Clean up temp file
434
- if (await Bun.file(tmpBinaryPath).exists()) {
435
- await $`rm ${tmpBinaryPath}`.quiet();
436
- }
437
-
438
230
  const message =
439
231
  normalizedCurrent === normalizedLatest
440
- ? `Successfully upgraded to ${normalizedLatest}`
232
+ ? `Successfully reinstalled ${normalizedLatest}`
441
233
  : `Successfully upgraded from ${normalizedCurrent} to ${normalizedLatest}`;
442
234
  tui.success(message);
443
235
 
236
+ // Hint about PATH if needed
237
+ tui.newline();
238
+ tui.info(
239
+ `${tui.muted('If the new version is not detected, restart your terminal or run:')} source ~/.bashrc`
240
+ );
241
+
444
242
  return {
445
243
  upgraded: true,
446
244
  from: currentVersion,
@@ -448,27 +246,11 @@ export const command = createCommand({
448
246
  message,
449
247
  };
450
248
  } catch (error) {
451
- let errorDetails: Record<string, unknown> = {
249
+ const errorDetails: Record<string, unknown> = {
452
250
  error: error instanceof Error ? error.message : 'Unknown error',
251
+ installationType,
453
252
  };
454
253
 
455
- if (error instanceof Error && error.message.includes('Binary validation failed')) {
456
- const match = error.message.match(
457
- /Failed with exit code (\d+)\n(stdout: .+\n)?(stderr: .+)?/s
458
- );
459
- if (match) {
460
- const exitCode = match[1];
461
- const stdout = match[2]?.replace('stdout: ', '').trim();
462
- const stderr = match[3]?.replace('stderr: ', '').trim();
463
-
464
- errorDetails = {
465
- validation_exit_code: exitCode,
466
- ...(stdout && { validation_stdout: stdout }),
467
- ...(stderr && { validation_stderr: stderr }),
468
- };
469
- }
470
- }
471
-
472
254
  exitWithError(
473
255
  createError(ErrorCode.INTERNAL_ERROR, 'Upgrade failed', errorDetails),
474
256
  logger,
@@ -1,34 +1,20 @@
1
- import path from 'node:path';
2
1
  import { getPackageName } from './version';
2
+ import { getInstallationType } from './utils/installation-type';
3
3
 
4
4
  let cachedPrefix: string | null = null;
5
5
 
6
6
  /**
7
7
  * Detects how the CLI is being invoked and returns the appropriate command prefix.
8
- * Returns "agentuity" if installed globally, or "bunx @agentuity/cli" if running via bunx.
8
+ * Returns "agentuity" if installed globally, or "bunx @agentuity/cli" if running locally.
9
9
  */
10
10
  export function getCommandPrefix(): string {
11
11
  if (cachedPrefix) {
12
12
  return cachedPrefix;
13
13
  }
14
14
 
15
- // Check if running from a globally installed package
16
- // When installed globally, the process.argv[1] will be in a bin directory
17
- const scriptPath = process.argv[1] || '';
18
- const normalized = path.normalize(scriptPath);
15
+ const installationType = getInstallationType();
19
16
 
20
- const isCompiledBinary =
21
- process.argv[0] === 'bun' && scriptPath.startsWith('/$bunfs/root/agentuity-');
22
-
23
- // If we have AGENTUITY_CLI_VERSION set we are running from compiled binary OR
24
- // If the script is in node_modules/.bin or a global bin directory, it's likely global
25
- const isGlobal =
26
- isCompiledBinary ||
27
- (normalized.includes(`${path.sep}bin${path.sep}`) &&
28
- !normalized.includes(`${path.sep}node_modules${path.sep}`) &&
29
- !normalized.includes(path.join('packages', 'cli', 'bin')));
30
-
31
- if (isGlobal) {
17
+ if (installationType === 'global') {
32
18
  cachedPrefix = 'agentuity';
33
19
  } else {
34
20
  // Running locally via bunx or from source
package/src/download.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Transform, Readable } from 'node:stream';
2
2
  import * as tui from './tui';
3
3
  import { APIError } from '@agentuity/server';
4
+ import { getVersion } from './version';
4
5
 
5
6
  export interface DownloadOptions {
6
7
  url: string;
@@ -18,6 +19,8 @@ export async function downloadWithProgress(
18
19
  ): Promise<NodeJS.ReadableStream> {
19
20
  const { url, headers = {}, onProgress } = options;
20
21
 
22
+ headers['User-Agent'] = `Agentuity CLI/${getVersion()}`;
23
+
21
24
  const response = await fetch(url, { headers });
22
25
  if (!response.ok) {
23
26
  throw new APIError({
package/src/errors.ts CHANGED
@@ -79,6 +79,9 @@ export enum ErrorCode {
79
79
 
80
80
  // Security errors
81
81
  MALWARE_DETECTED = 'MALWARE_DETECTED',
82
+
83
+ // Upgrade of the software is required to continue
84
+ UPGRADE_REQUIRED = 'UPGRADE_REQUIRED',
82
85
  }
83
86
 
84
87
  /**
@@ -152,6 +155,7 @@ export function getExitCode(errorCode: ErrorCode): ExitCode {
152
155
  case ErrorCode.RUNTIME_ERROR:
153
156
  case ErrorCode.INTERNAL_ERROR:
154
157
  case ErrorCode.NOT_IMPLEMENTED:
158
+ case ErrorCode.UPGRADE_REQUIRED:
155
159
  default:
156
160
  return ExitCode.GENERAL_ERROR;
157
161
  }
@@ -127,12 +127,10 @@ export async function checkLegacyCLI(): Promise<void> {
127
127
  }
128
128
 
129
129
  console.log(' ' + tui.bold('After removal, install the new CLI:'));
130
- tui.bullet('curl -sSL https://v1.agentuity.sh | sh');
130
+ tui.bullet('curl -sSL https://agentuity.sh | sh');
131
131
  tui.newline();
132
132
 
133
- console.log(
134
- ` Learn more: ${tui.link('https://preview.agentuity.dev/v1/Reference/migration-guide')}`
135
- );
133
+ console.log(` Learn more: ${tui.link('https://agentuity.dev/Reference/migration-guide')}`);
136
134
  tui.newline();
137
135
 
138
136
  process.exit(1);
package/src/types.ts CHANGED
@@ -24,6 +24,7 @@ export const ConfigSchema = zod.object({
24
24
  devmode: zod
25
25
  .object({
26
26
  hostname: zod.string().optional().describe('Development mode hostname'),
27
+ privateKey: zod.string().optional().describe('Development mode private key (base64-encoded PEM)'),
27
28
  })
28
29
  .optional()
29
30
  .describe('Development mode configuration'),
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Detects how the CLI was installed and is being run
3
+ */
4
+
5
+ export type InstallationType = 'global' | 'local' | 'source';
6
+
7
+ /**
8
+ * Determines the installation type based on how the CLI is being executed
9
+ *
10
+ * @returns 'global' - Installed globally via `bun add -g @agentuity/cli`
11
+ * @returns 'local' - Running from project's node_modules (bunx or direct)
12
+ * @returns 'source' - Running from source code (development)
13
+ */
14
+ export function getInstallationType(): InstallationType {
15
+ // Normalize path to POSIX separators for cross-platform compatibility
16
+ const mainPath = Bun.main.replace(/\\/g, '/');
17
+
18
+ // Global install: ~/.bun/install/global/node_modules/@agentuity/cli/...
19
+ if (mainPath.includes('/.bun/install/global/')) {
20
+ return 'global';
21
+ }
22
+
23
+ // Local project install: ./node_modules/@agentuity/cli/...
24
+ if (mainPath.includes('/node_modules/@agentuity/cli/')) {
25
+ return 'local';
26
+ }
27
+
28
+ // Source/development: packages/cli/bin/cli.ts or similar
29
+ return 'source';
30
+ }
31
+
32
+ /**
33
+ * Check if running from a global installation
34
+ */
35
+ export function isGlobalInstall(): boolean {
36
+ return getInstallationType() === 'global';
37
+ }
38
+
39
+ /**
40
+ * Check if running from a local project installation
41
+ */
42
+ export function isLocalInstall(): boolean {
43
+ return getInstallationType() === 'local';
44
+ }
45
+
46
+ /**
47
+ * Check if running from source (development mode)
48
+ */
49
+ export function isSourceInstall(): boolean {
50
+ return getInstallationType() === 'source';
51
+ }