@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.
- package/dist/banner.js +2 -2
- package/dist/banner.js.map +1 -1
- package/dist/cmd/auth/signup.js +1 -1
- package/dist/cmd/auth/signup.js.map +1 -1
- package/dist/cmd/canary/index.d.ts.map +1 -1
- package/dist/cmd/canary/index.js +62 -127
- package/dist/cmd/canary/index.js.map +1 -1
- package/dist/cmd/cloud/deploy.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.js +1 -8
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/cmd/dev/api.d.ts +3 -1
- package/dist/cmd/dev/api.d.ts.map +1 -1
- package/dist/cmd/dev/api.js +16 -2
- package/dist/cmd/dev/api.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +16 -3
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/index.d.ts.map +1 -1
- package/dist/cmd/index.js +0 -6
- package/dist/cmd/index.js.map +1 -1
- package/dist/cmd/setup/index.d.ts.map +1 -1
- package/dist/cmd/setup/index.js +7 -9
- package/dist/cmd/setup/index.js.map +1 -1
- package/dist/cmd/upgrade/index.d.ts +7 -24
- package/dist/cmd/upgrade/index.d.ts.map +1 -1
- package/dist/cmd/upgrade/index.js +78 -263
- package/dist/cmd/upgrade/index.js.map +1 -1
- package/dist/command-prefix.d.ts +1 -1
- package/dist/command-prefix.d.ts.map +1 -1
- package/dist/command-prefix.js +4 -14
- package/dist/command-prefix.js.map +1 -1
- package/dist/download.d.ts.map +1 -1
- package/dist/download.js +2 -0
- package/dist/download.js.map +1 -1
- package/dist/errors.d.ts +2 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -0
- package/dist/errors.js.map +1 -1
- package/dist/legacy-check.d.ts.map +1 -1
- package/dist/legacy-check.js +2 -2
- package/dist/legacy-check.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/installation-type.d.ts +25 -0
- package/dist/utils/installation-type.d.ts.map +1 -0
- package/dist/utils/installation-type.js +43 -0
- package/dist/utils/installation-type.js.map +1 -0
- package/dist/version-check.d.ts.map +1 -1
- package/dist/version-check.js +16 -13
- package/dist/version-check.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +6 -6
- package/src/api-errors.md +2 -2
- package/src/banner.ts +2 -2
- package/src/cmd/auth/signup.ts +1 -1
- package/src/cmd/canary/index.ts +69 -144
- package/src/cmd/cloud/deploy.ts +1 -12
- package/src/cmd/dev/api.ts +19 -3
- package/src/cmd/dev/index.ts +23 -3
- package/src/cmd/index.ts +0 -7
- package/src/cmd/setup/index.ts +8 -9
- package/src/cmd/upgrade/index.ts +84 -302
- package/src/command-prefix.ts +4 -18
- package/src/download.ts +3 -0
- package/src/errors.ts +4 -0
- package/src/legacy-check.ts +2 -4
- package/src/types.ts +1 -0
- package/src/utils/installation-type.ts +51 -0
- package/src/version-check.ts +18 -13
- package/src/version.ts +1 -1
package/src/cmd/upgrade/index.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
*
|
|
56
|
-
* @
|
|
22
|
+
* Get the installation type - re-exported for backward compatibility
|
|
23
|
+
* @deprecated Use getInstallationType() from '../../utils/installation-type' instead
|
|
57
24
|
*/
|
|
58
|
-
export
|
|
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
|
-
*
|
|
85
|
-
*
|
|
28
|
+
* Check if running from a global installation
|
|
29
|
+
* This replaces the old isRunningFromExecutable() function
|
|
86
30
|
*/
|
|
87
|
-
export function
|
|
88
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
*
|
|
73
|
+
* Upgrade the CLI using bun global install
|
|
150
74
|
*/
|
|
151
|
-
async function
|
|
152
|
-
version
|
|
153
|
-
|
|
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
|
-
//
|
|
168
|
-
const
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
throw new Error(
|
|
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
|
-
*
|
|
89
|
+
* Verify the upgrade was successful by checking the installed version
|
|
210
90
|
*/
|
|
211
|
-
async function
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
237
|
-
|
|
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
|
-
|
|
253
|
-
|
|
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 (
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
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
|
-
|
|
323
|
-
|
|
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
|
-
//
|
|
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:
|
|
424
|
-
callback: async () => await
|
|
220
|
+
message: `Installing @agentuity/cli@${normalizedLatest}...`,
|
|
221
|
+
callback: async () => await performBunUpgrade(latestVersion),
|
|
425
222
|
});
|
|
426
223
|
|
|
427
|
-
//
|
|
224
|
+
// Verify the upgrade
|
|
428
225
|
await tui.spinner({
|
|
429
|
-
message: '
|
|
430
|
-
callback: async () => await
|
|
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
|
|
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
|
-
|
|
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,
|
package/src/command-prefix.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/legacy-check.ts
CHANGED
|
@@ -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://
|
|
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
|
+
}
|