@agentuity/cli 0.0.86 → 0.0.87
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/bin/cli.ts +7 -0
- package/dist/bun-path.d.ts.map +1 -1
- package/dist/bun-path.js +1 -3
- package/dist/bun-path.js.map +1 -1
- package/dist/cmd/ai/index.d.ts.map +1 -1
- package/dist/cmd/ai/index.js +1 -0
- package/dist/cmd/ai/index.js.map +1 -1
- package/dist/cmd/build/ast.d.ts.map +1 -1
- package/dist/cmd/build/ast.js +5 -0
- package/dist/cmd/build/ast.js.map +1 -1
- package/dist/cmd/build/bundler.d.ts.map +1 -1
- package/dist/cmd/build/bundler.js +10 -0
- package/dist/cmd/build/bundler.js.map +1 -1
- package/dist/cmd/build/patch/_util.js +6 -6
- package/dist/cmd/build/patch/_util.js.map +1 -1
- package/dist/cmd/build/patch/llm.js +1 -1
- package/dist/cmd/build/patch/llm.js.map +1 -1
- package/dist/cmd/build/plugin.d.ts.map +1 -1
- package/dist/cmd/build/plugin.js +21 -14
- package/dist/cmd/build/plugin.js.map +1 -1
- package/dist/cmd/build/route-discovery.d.ts +8 -4
- package/dist/cmd/build/route-discovery.d.ts.map +1 -1
- package/dist/cmd/build/route-discovery.js +10 -5
- package/dist/cmd/build/route-discovery.js.map +1 -1
- package/dist/cmd/cloud/scp/download.js +3 -3
- package/dist/cmd/cloud/scp/download.js.map +1 -1
- package/dist/cmd/cloud/scp/upload.js +3 -3
- package/dist/cmd/cloud/scp/upload.js.map +1 -1
- package/dist/cmd/cloud/ssh.js +3 -3
- package/dist/cmd/cloud/ssh.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +5 -0
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/index.d.ts.map +1 -1
- package/dist/cmd/index.js +7 -0
- package/dist/cmd/index.js.map +1 -1
- package/dist/cmd/profile/create.d.ts.map +1 -1
- package/dist/cmd/profile/create.js +1 -0
- package/dist/cmd/profile/create.js.map +1 -1
- package/dist/cmd/upgrade/index.d.ts +20 -0
- package/dist/cmd/upgrade/index.d.ts.map +1 -0
- package/dist/cmd/upgrade/index.js +307 -0
- package/dist/cmd/upgrade/index.js.map +1 -0
- package/dist/cmd/version/index.d.ts.map +1 -1
- package/dist/cmd/version/index.js +1 -0
- package/dist/cmd/version/index.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +12 -94
- package/dist/config.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +16 -0
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/dependency-checker.d.ts +20 -0
- package/dist/utils/dependency-checker.d.ts.map +1 -0
- package/dist/utils/dependency-checker.js +161 -0
- package/dist/utils/dependency-checker.js.map +1 -0
- package/dist/version-check.d.ts +13 -0
- package/dist/version-check.d.ts.map +1 -0
- package/dist/version-check.js +177 -0
- package/dist/version-check.js.map +1 -0
- package/package.json +3 -3
- package/src/bun-path.ts +1 -3
- package/src/cmd/ai/index.ts +1 -0
- package/src/cmd/build/ast.ts +7 -0
- package/src/cmd/build/bundler.ts +12 -0
- package/src/cmd/build/patch/_util.ts +6 -6
- package/src/cmd/build/patch/llm.ts +1 -1
- package/src/cmd/build/plugin.ts +23 -16
- package/src/cmd/build/route-discovery.ts +10 -5
- package/src/cmd/cloud/scp/download.ts +3 -3
- package/src/cmd/cloud/scp/upload.ts +3 -3
- package/src/cmd/cloud/ssh.ts +3 -3
- package/src/cmd/dev/index.ts +11 -0
- package/src/cmd/index.ts +8 -0
- package/src/cmd/profile/create.ts +1 -0
- package/src/cmd/project/download.ts +1 -1
- package/src/cmd/upgrade/index.ts +365 -0
- package/src/cmd/version/index.ts +1 -0
- package/src/config.ts +12 -121
- package/src/git-helper.ts +4 -4
- package/src/tui.ts +19 -0
- package/src/types.ts +7 -0
- package/src/utils/dependency-checker.ts +207 -0
- package/src/version-check.ts +234 -0
package/src/cmd/cloud/ssh.ts
CHANGED
|
@@ -35,13 +35,13 @@ export const sshSubcommand = createSubcommand({
|
|
|
35
35
|
},
|
|
36
36
|
],
|
|
37
37
|
toplevel: true,
|
|
38
|
-
requires: { auth: true, apiClient: true },
|
|
38
|
+
requires: { auth: true, apiClient: true, region: true },
|
|
39
39
|
optional: { project: true },
|
|
40
40
|
prerequisites: ['cloud deploy'],
|
|
41
41
|
schema: { args, options },
|
|
42
42
|
|
|
43
43
|
async handler(ctx) {
|
|
44
|
-
const { apiClient, project, projectDir, args, config, opts } = ctx;
|
|
44
|
+
const { apiClient, project, projectDir, args, config, opts, region } = ctx;
|
|
45
45
|
|
|
46
46
|
let projectId = project?.projectId;
|
|
47
47
|
let identifier = args?.identifier;
|
|
@@ -56,7 +56,7 @@ export const sshSubcommand = createSubcommand({
|
|
|
56
56
|
projectId = await tui.showProjectList(apiClient, true);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
const hostname = getIONHost(config);
|
|
59
|
+
const hostname = getIONHost(config, region);
|
|
60
60
|
|
|
61
61
|
const cmd = ['ssh', `${identifier ?? projectId}@${hostname}`, command].filter(
|
|
62
62
|
Boolean
|
package/src/cmd/dev/index.ts
CHANGED
|
@@ -938,6 +938,17 @@ export const command = createCommand({
|
|
|
938
938
|
return;
|
|
939
939
|
}
|
|
940
940
|
|
|
941
|
+
// Ignore .git folder
|
|
942
|
+
if (changedFile && (changedFile === '.git' || changedFile.startsWith('.git/'))) {
|
|
943
|
+
logger.trace(
|
|
944
|
+
'File change ignored (.git folder): %s (event: %s, file: %s)',
|
|
945
|
+
watchDir,
|
|
946
|
+
eventType,
|
|
947
|
+
changedFile
|
|
948
|
+
);
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
|
|
941
952
|
// Ignore changes in .agentuity directory (build output)
|
|
942
953
|
// Check both relative path and normalized absolute path
|
|
943
954
|
const isInAgentuityDir =
|
package/src/cmd/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CommandDefinition } from '../types';
|
|
2
|
+
import { isRunningFromExecutable } from './upgrade';
|
|
2
3
|
|
|
3
4
|
// Use dynamic imports for bundler compatibility while maintaining lazy loading
|
|
4
5
|
export async function discoverCommands(): Promise<CommandDefinition[]> {
|
|
@@ -12,12 +13,19 @@ export async function discoverCommands(): Promise<CommandDefinition[]> {
|
|
|
12
13
|
import('./profile').then((m) => m.command),
|
|
13
14
|
import('./project').then((m) => m.command),
|
|
14
15
|
import('./repl').then((m) => m.command),
|
|
16
|
+
import('./upgrade').then((m) => m.command),
|
|
15
17
|
import('./version').then((m) => m.command),
|
|
16
18
|
]);
|
|
17
19
|
|
|
18
20
|
const commands: CommandDefinition[] = [];
|
|
21
|
+
const isExecutable = isRunningFromExecutable();
|
|
19
22
|
|
|
20
23
|
for (const cmd of commandModules) {
|
|
24
|
+
// Skip commands that require running from an executable when not in one
|
|
25
|
+
if (cmd.executable && !isExecutable) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
commands.push(cmd);
|
|
22
30
|
|
|
23
31
|
// Auto-create hidden top-level aliases for subcommands with toplevel: true
|
|
@@ -73,6 +73,7 @@ export const createCommand = createSubcommand({
|
|
|
73
73
|
if (name === 'local') {
|
|
74
74
|
// if we're creating a local profile, go ahead and fill it out for the dev to make it easier to get started
|
|
75
75
|
const localConfig = (await loadConfig(filename)) as Config;
|
|
76
|
+
localConfig.name = name;
|
|
76
77
|
localConfig.overrides = {
|
|
77
78
|
api_url: 'https://api.agentuity.io',
|
|
78
79
|
app_url: 'https://app.agentuity.io',
|
|
@@ -231,7 +231,7 @@ export async function setupProject(options: SetupOptions): Promise<void> {
|
|
|
231
231
|
// Check for real git (not macOS stub that triggers Xcode CLT popup)
|
|
232
232
|
const { isGitAvailable, getDefaultBranch } = await import('../../git-helper');
|
|
233
233
|
const gitAvailable = await isGitAvailable();
|
|
234
|
-
|
|
234
|
+
|
|
235
235
|
if (gitAvailable) {
|
|
236
236
|
// Get default branch from git config, fallback to 'main'
|
|
237
237
|
const defaultBranch = (await getDefaultBranch()) || 'main';
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { createCommand } from '../../types';
|
|
2
|
+
import { getVersion } from '../../version';
|
|
3
|
+
import { getCommand } from '../../command-prefix';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { ErrorCode, createError, exitWithError } from '../../errors';
|
|
6
|
+
import * as tui from '../../tui';
|
|
7
|
+
import { downloadWithProgress } from '../../download';
|
|
8
|
+
import { $ } from 'bun';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
import { randomUUID } from 'node:crypto';
|
|
12
|
+
|
|
13
|
+
const UpgradeOptionsSchema = z.object({
|
|
14
|
+
force: z.boolean().optional().describe('Force upgrade even if version is the same'),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const UpgradeResponseSchema = z.object({
|
|
18
|
+
upgraded: z.boolean().describe('Whether an upgrade was performed'),
|
|
19
|
+
from: z.string().describe('Version before upgrade'),
|
|
20
|
+
to: z.string().describe('Version after upgrade'),
|
|
21
|
+
message: z.string().describe('Status message'),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check if running from a compiled executable (not via bun/bunx)
|
|
26
|
+
* @internal Exported for testing
|
|
27
|
+
*/
|
|
28
|
+
export function isRunningFromExecutable(): boolean {
|
|
29
|
+
const scriptPath = process.argv[1] || '';
|
|
30
|
+
|
|
31
|
+
// Check if running from compiled binary (uses Bun's virtual filesystem)
|
|
32
|
+
// When compiled with `bun build --compile`, the path is in the virtual /$bunfs/root/ directory
|
|
33
|
+
const isCompiledBinary = process.argv[0] === 'bun' && scriptPath.startsWith('/$bunfs/root/');
|
|
34
|
+
|
|
35
|
+
if (isCompiledBinary) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// If running via bun/bunx (from node_modules or .ts files), it's not an executable
|
|
40
|
+
if (Bun.main.includes('/node_modules/') || Bun.main.includes('.ts')) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check if in a bin directory but not in node_modules (globally installed)
|
|
45
|
+
const normalized = Bun.main;
|
|
46
|
+
const isGlobal =
|
|
47
|
+
normalized.includes('/bin/') &&
|
|
48
|
+
!normalized.includes('/node_modules/') &&
|
|
49
|
+
!normalized.includes('/packages/cli/bin');
|
|
50
|
+
|
|
51
|
+
return isGlobal;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get the OS and architecture for downloading the binary
|
|
56
|
+
* @internal Exported for testing
|
|
57
|
+
*/
|
|
58
|
+
export function getPlatformInfo(): { os: string; arch: string } {
|
|
59
|
+
const platform = process.platform;
|
|
60
|
+
const arch = process.arch;
|
|
61
|
+
|
|
62
|
+
let os: string;
|
|
63
|
+
let archStr: string;
|
|
64
|
+
|
|
65
|
+
switch (platform) {
|
|
66
|
+
case 'darwin':
|
|
67
|
+
os = 'darwin';
|
|
68
|
+
break;
|
|
69
|
+
case 'linux':
|
|
70
|
+
os = 'linux';
|
|
71
|
+
break;
|
|
72
|
+
default:
|
|
73
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
switch (arch) {
|
|
77
|
+
case 'x64':
|
|
78
|
+
archStr = 'x64';
|
|
79
|
+
break;
|
|
80
|
+
case 'arm64':
|
|
81
|
+
archStr = 'arm64';
|
|
82
|
+
break;
|
|
83
|
+
default:
|
|
84
|
+
throw new Error(`Unsupported architecture: ${arch}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { os, arch: archStr };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Fetch the latest version from the API
|
|
92
|
+
* @internal Exported for testing
|
|
93
|
+
*/
|
|
94
|
+
export async function fetchLatestVersion(): Promise<string> {
|
|
95
|
+
const response = await fetch('https://agentuity.sh/release/sdk/version', {
|
|
96
|
+
signal: AbortSignal.timeout(10000), // 10 second timeout
|
|
97
|
+
});
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
throw new Error(`Failed to fetch version: ${response.statusText}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const version = await response.text();
|
|
103
|
+
const trimmedVersion = version.trim();
|
|
104
|
+
|
|
105
|
+
// Validate version format
|
|
106
|
+
if (
|
|
107
|
+
!/^v?[0-9]+\.[0-9]+\.[0-9]+/.test(trimmedVersion) ||
|
|
108
|
+
trimmedVersion.includes('message') ||
|
|
109
|
+
trimmedVersion.includes('error') ||
|
|
110
|
+
trimmedVersion.includes('<html>')
|
|
111
|
+
) {
|
|
112
|
+
throw new Error(`Invalid version format received: ${trimmedVersion}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Ensure version has 'v' prefix
|
|
116
|
+
return trimmedVersion.startsWith('v') ? trimmedVersion : `v${trimmedVersion}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Download the binary for the specified version
|
|
121
|
+
*/
|
|
122
|
+
async function downloadBinary(
|
|
123
|
+
version: string,
|
|
124
|
+
platform: { os: string; arch: string }
|
|
125
|
+
): Promise<string> {
|
|
126
|
+
const { os, arch } = platform;
|
|
127
|
+
const url = `https://agentuity.sh/release/sdk/${version}/${os}/${arch}`;
|
|
128
|
+
|
|
129
|
+
const tmpDir = tmpdir();
|
|
130
|
+
const tmpFile = join(tmpDir, `agentuity-${randomUUID()}`);
|
|
131
|
+
const gzFile = `${tmpFile}.gz`;
|
|
132
|
+
|
|
133
|
+
const stream = await downloadWithProgress({
|
|
134
|
+
url,
|
|
135
|
+
message: `Downloading version ${version}...`,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Write to temp file
|
|
139
|
+
const writer = Bun.file(gzFile).writer();
|
|
140
|
+
for await (const chunk of stream) {
|
|
141
|
+
writer.write(chunk);
|
|
142
|
+
}
|
|
143
|
+
await writer.end();
|
|
144
|
+
|
|
145
|
+
// Verify file was downloaded
|
|
146
|
+
if (!(await Bun.file(gzFile).exists())) {
|
|
147
|
+
throw new Error('Download failed - file not created');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Decompress using gunzip
|
|
151
|
+
try {
|
|
152
|
+
await $`gunzip ${gzFile}`.quiet();
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (await Bun.file(gzFile).exists()) {
|
|
155
|
+
await $`rm ${gzFile}`.quiet();
|
|
156
|
+
}
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Decompression failed: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Verify decompressed file exists
|
|
163
|
+
if (!(await Bun.file(tmpFile).exists())) {
|
|
164
|
+
throw new Error('Decompression failed - file not found');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Verify it's a valid binary
|
|
168
|
+
const fileType = await $`file ${tmpFile}`.text();
|
|
169
|
+
if (!fileType.match(/(executable|ELF|Mach-O|PE32)/i)) {
|
|
170
|
+
throw new Error('Downloaded file is not a valid executable');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Make executable
|
|
174
|
+
await $`chmod 755 ${tmpFile}`.quiet();
|
|
175
|
+
|
|
176
|
+
return tmpFile;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Validate the downloaded binary by running version command
|
|
181
|
+
*/
|
|
182
|
+
async function validateBinary(binaryPath: string, expectedVersion: string): Promise<void> {
|
|
183
|
+
try {
|
|
184
|
+
const result = await $`${binaryPath} version`.text();
|
|
185
|
+
const actualVersion = result.trim();
|
|
186
|
+
|
|
187
|
+
// Normalize versions for comparison (remove 'v' prefix)
|
|
188
|
+
const normalizedExpected = expectedVersion.replace(/^v/, '');
|
|
189
|
+
const normalizedActual = actualVersion.replace(/^v/, '');
|
|
190
|
+
|
|
191
|
+
if (normalizedActual !== normalizedExpected) {
|
|
192
|
+
throw new Error(`Version mismatch: expected ${expectedVersion}, got ${actualVersion}`);
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
if (error instanceof Error) {
|
|
196
|
+
throw new Error(`Binary validation failed: ${error.message}`);
|
|
197
|
+
}
|
|
198
|
+
throw new Error('Binary validation failed');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Replace the current binary with the new one
|
|
204
|
+
* Uses platform-specific safe replacement strategies
|
|
205
|
+
*/
|
|
206
|
+
async function replaceBinary(newBinaryPath: string, currentBinaryPath: string): Promise<void> {
|
|
207
|
+
const platform = process.platform;
|
|
208
|
+
|
|
209
|
+
if (platform === 'darwin' || platform === 'linux') {
|
|
210
|
+
// Unix: Use atomic move via temp file
|
|
211
|
+
const backupPath = `${currentBinaryPath}.backup`;
|
|
212
|
+
const tempPath = `${currentBinaryPath}.new`;
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
// Copy new binary to temp location next to current binary
|
|
216
|
+
await $`cp ${newBinaryPath} ${tempPath}`.quiet();
|
|
217
|
+
await $`chmod 755 ${tempPath}`.quiet();
|
|
218
|
+
|
|
219
|
+
// Backup current binary
|
|
220
|
+
if (await Bun.file(currentBinaryPath).exists()) {
|
|
221
|
+
await $`cp ${currentBinaryPath} ${backupPath}`.quiet();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Atomic rename
|
|
225
|
+
await $`mv ${tempPath} ${currentBinaryPath}`.quiet();
|
|
226
|
+
|
|
227
|
+
// Clean up backup after successful replacement
|
|
228
|
+
if (await Bun.file(backupPath).exists()) {
|
|
229
|
+
await $`rm ${backupPath}`.quiet();
|
|
230
|
+
}
|
|
231
|
+
} catch (error) {
|
|
232
|
+
// Try to restore backup if replacement failed
|
|
233
|
+
if (await Bun.file(backupPath).exists()) {
|
|
234
|
+
await $`mv ${backupPath} ${currentBinaryPath}`.quiet();
|
|
235
|
+
}
|
|
236
|
+
// Clean up temp file if it exists
|
|
237
|
+
if (await Bun.file(tempPath).exists()) {
|
|
238
|
+
await $`rm ${tempPath}`.quiet();
|
|
239
|
+
}
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
throw new Error(`Unsupported platform for binary replacement: ${platform}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export const command = createCommand({
|
|
248
|
+
name: 'upgrade',
|
|
249
|
+
description: 'Upgrade the CLI to the latest version',
|
|
250
|
+
executable: true,
|
|
251
|
+
skipUpgradeCheck: true,
|
|
252
|
+
tags: ['update'],
|
|
253
|
+
examples: [
|
|
254
|
+
{
|
|
255
|
+
command: getCommand('upgrade'),
|
|
256
|
+
description: 'Check for updates and prompt to upgrade',
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
command: getCommand('upgrade --force'),
|
|
260
|
+
description: 'Force upgrade even if already on latest version',
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
schema: {
|
|
264
|
+
options: UpgradeOptionsSchema,
|
|
265
|
+
response: UpgradeResponseSchema,
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
async handler(ctx) {
|
|
269
|
+
const { logger, options } = ctx;
|
|
270
|
+
const { force } = ctx.opts;
|
|
271
|
+
|
|
272
|
+
const currentVersion = getVersion();
|
|
273
|
+
// Use process.execPath to get the actual file path (Bun.main is virtual for compiled binaries)
|
|
274
|
+
const currentBinaryPath = process.execPath;
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
// Fetch latest version
|
|
278
|
+
const latestVersion = await tui.spinner({
|
|
279
|
+
message: 'Checking for updates...',
|
|
280
|
+
clearOnSuccess: true,
|
|
281
|
+
callback: async () => await fetchLatestVersion(),
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Compare versions
|
|
285
|
+
const normalizedCurrent = currentVersion.replace(/^v/, '');
|
|
286
|
+
const normalizedLatest = latestVersion.replace(/^v/, '');
|
|
287
|
+
|
|
288
|
+
if (normalizedCurrent === normalizedLatest && !force) {
|
|
289
|
+
const message = `Already on latest version ${currentVersion}`;
|
|
290
|
+
tui.success(message);
|
|
291
|
+
return {
|
|
292
|
+
upgraded: false,
|
|
293
|
+
from: currentVersion,
|
|
294
|
+
to: latestVersion,
|
|
295
|
+
message,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Confirm upgrade
|
|
300
|
+
if (!force) {
|
|
301
|
+
tui.info(`Current version: ${tui.muted(currentVersion)}`);
|
|
302
|
+
tui.info(`Latest version: ${tui.bold(latestVersion)}`);
|
|
303
|
+
tui.info('');
|
|
304
|
+
|
|
305
|
+
const shouldUpgrade = await tui.confirm('Do you want to upgrade?', true);
|
|
306
|
+
|
|
307
|
+
if (!shouldUpgrade) {
|
|
308
|
+
const message = 'Upgrade cancelled';
|
|
309
|
+
tui.info(message);
|
|
310
|
+
return {
|
|
311
|
+
upgraded: false,
|
|
312
|
+
from: currentVersion,
|
|
313
|
+
to: latestVersion,
|
|
314
|
+
message,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Get platform info
|
|
320
|
+
const platform = getPlatformInfo();
|
|
321
|
+
|
|
322
|
+
// Download binary
|
|
323
|
+
const tmpBinaryPath = await tui.spinner({
|
|
324
|
+
type: 'progress',
|
|
325
|
+
message: 'Downloading...',
|
|
326
|
+
callback: async () => await downloadBinary(latestVersion, platform),
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Validate binary
|
|
330
|
+
await tui.spinner({
|
|
331
|
+
message: 'Validating binary...',
|
|
332
|
+
callback: async () => await validateBinary(tmpBinaryPath, latestVersion),
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Replace binary
|
|
336
|
+
await tui.spinner({
|
|
337
|
+
message: 'Installing...',
|
|
338
|
+
callback: async () => await replaceBinary(tmpBinaryPath, currentBinaryPath),
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Clean up temp file
|
|
342
|
+
if (await Bun.file(tmpBinaryPath).exists()) {
|
|
343
|
+
await $`rm ${tmpBinaryPath}`.quiet();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const message = `Successfully upgraded from ${currentVersion} to ${latestVersion}`;
|
|
347
|
+
tui.success(message);
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
upgraded: true,
|
|
351
|
+
from: currentVersion,
|
|
352
|
+
to: latestVersion,
|
|
353
|
+
message,
|
|
354
|
+
};
|
|
355
|
+
} catch (error) {
|
|
356
|
+
exitWithError(
|
|
357
|
+
createError(ErrorCode.INTERNAL_ERROR, 'Upgrade failed', {
|
|
358
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
359
|
+
}),
|
|
360
|
+
logger,
|
|
361
|
+
options.errorFormat
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
});
|
package/src/cmd/version/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ const VersionResponseSchema = z.string().describe('CLI version number');
|
|
|
10
10
|
export const command = createCommand({
|
|
11
11
|
name: 'version',
|
|
12
12
|
description: 'Display version information',
|
|
13
|
+
skipUpgradeCheck: true,
|
|
13
14
|
tags: ['read-only', 'fast'],
|
|
14
15
|
examples: [
|
|
15
16
|
{ command: getCommand('version'), description: 'Show the CLI semantic version' },
|
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
|
|
640
|
-
'https://agentuity.dev/schema/cli/v1/agentuity.json',
|
|
641
|
-
|
|
642
|
-
|
|
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?.
|
|
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
|
-
|
|
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/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
|
|