@agentuity/cli 1.0.8 → 1.0.10
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/cmd/ai/claude-code/constants.d.ts +13 -0
- package/dist/cmd/ai/claude-code/constants.d.ts.map +1 -0
- package/dist/cmd/ai/claude-code/constants.js +16 -0
- package/dist/cmd/ai/claude-code/constants.js.map +1 -0
- package/dist/cmd/ai/claude-code/index.d.ts +3 -0
- package/dist/cmd/ai/claude-code/index.d.ts.map +1 -0
- package/dist/cmd/ai/claude-code/index.js +22 -0
- package/dist/cmd/ai/claude-code/index.js.map +1 -0
- package/dist/cmd/ai/claude-code/install.d.ts +3 -0
- package/dist/cmd/ai/claude-code/install.d.ts.map +1 -0
- package/dist/cmd/ai/claude-code/install.js +133 -0
- package/dist/cmd/ai/claude-code/install.js.map +1 -0
- package/dist/cmd/ai/claude-code/uninstall.d.ts +3 -0
- package/dist/cmd/ai/claude-code/uninstall.d.ts.map +1 -0
- package/dist/cmd/ai/claude-code/uninstall.js +105 -0
- package/dist/cmd/ai/claude-code/uninstall.js.map +1 -0
- package/dist/cmd/ai/index.d.ts.map +1 -1
- package/dist/cmd/ai/index.js +6 -0
- package/dist/cmd/ai/index.js.map +1 -1
- package/dist/cmd/build/entry-generator.d.ts.map +1 -1
- package/dist/cmd/build/entry-generator.js +1 -8
- package/dist/cmd/build/entry-generator.js.map +1 -1
- package/dist/cmd/build/vite/config-loader.d.ts +9 -0
- package/dist/cmd/build/vite/config-loader.d.ts.map +1 -1
- package/dist/cmd/build/vite/config-loader.js +30 -0
- package/dist/cmd/build/vite/config-loader.js.map +1 -1
- package/dist/cmd/build/vite/server-bundler.d.ts.map +1 -1
- package/dist/cmd/build/vite/server-bundler.js +22 -1
- package/dist/cmd/build/vite/server-bundler.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.js +19 -14
- package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
- package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-builder.js +12 -8
- package/dist/cmd/build/vite/vite-builder.js.map +1 -1
- package/dist/cmd/dev/download.d.ts.map +1 -1
- package/dist/cmd/dev/download.js +63 -53
- package/dist/cmd/dev/download.js.map +1 -1
- package/dist/cmd/upgrade/index.d.ts.map +1 -1
- package/dist/cmd/upgrade/index.js +24 -11
- package/dist/cmd/upgrade/index.js.map +1 -1
- package/dist/cmd/upgrade/npm-availability.d.ts +45 -12
- package/dist/cmd/upgrade/npm-availability.d.ts.map +1 -1
- package/dist/cmd/upgrade/npm-availability.js +73 -26
- package/dist/cmd/upgrade/npm-availability.js.map +1 -1
- package/dist/types.d.ts +4 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/version-check.d.ts.map +1 -1
- package/dist/version-check.js +13 -2
- package/dist/version-check.js.map +1 -1
- package/package.json +6 -7
- package/src/cmd/ai/claude-code/constants.ts +26 -0
- package/src/cmd/ai/claude-code/index.ts +23 -0
- package/src/cmd/ai/claude-code/install.ts +181 -0
- package/src/cmd/ai/claude-code/uninstall.ts +122 -0
- package/src/cmd/ai/index.ts +6 -0
- package/src/cmd/build/entry-generator.ts +1 -8
- package/src/cmd/build/vite/config-loader.ts +37 -0
- package/src/cmd/build/vite/server-bundler.ts +28 -1
- package/src/cmd/build/vite/vite-asset-server-config.ts +22 -13
- package/src/cmd/build/vite/vite-builder.ts +17 -8
- package/src/cmd/dev/download.ts +76 -78
- package/src/cmd/upgrade/index.ts +35 -12
- package/src/cmd/upgrade/npm-availability.ts +106 -36
- package/src/types.ts +4 -2
- package/src/version-check.ts +20 -2
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import { createSubcommand, type CommandContext } from '../../../types';
|
|
3
|
+
import * as tui from '../../../tui';
|
|
4
|
+
import { getCommand } from '../../../command-prefix';
|
|
5
|
+
import {
|
|
6
|
+
type ClaudeSettings,
|
|
7
|
+
CLAUDE_SETTINGS_FILE,
|
|
8
|
+
PLUGIN_INSTALL_DIR,
|
|
9
|
+
AGENTUITY_ALLOW_PERMISSIONS,
|
|
10
|
+
AGENTUITY_DENY_PERMISSIONS,
|
|
11
|
+
} from './constants';
|
|
12
|
+
|
|
13
|
+
export const uninstallSubcommand = createSubcommand({
|
|
14
|
+
name: 'uninstall',
|
|
15
|
+
description: 'Uninstall Agentuity Coder plugin for Claude Code',
|
|
16
|
+
tags: ['fast'],
|
|
17
|
+
examples: [
|
|
18
|
+
{
|
|
19
|
+
command: getCommand('ai claude-code uninstall'),
|
|
20
|
+
description: 'Uninstall Agentuity Coder plugin for Claude Code',
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
async handler(ctx: CommandContext) {
|
|
24
|
+
const { options } = ctx;
|
|
25
|
+
const jsonMode = !!options?.json;
|
|
26
|
+
|
|
27
|
+
if (!jsonMode) {
|
|
28
|
+
tui.newline();
|
|
29
|
+
tui.output(tui.bold('Uninstalling Agentuity Coder plugin for Claude Code'));
|
|
30
|
+
tui.newline();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let removedPlugin = false;
|
|
34
|
+
let removedPermissions = false;
|
|
35
|
+
|
|
36
|
+
if (await Bun.file(`${PLUGIN_INSTALL_DIR}/package.json`).exists()) {
|
|
37
|
+
try {
|
|
38
|
+
rmSync(PLUGIN_INSTALL_DIR, { recursive: true, force: true });
|
|
39
|
+
removedPlugin = true;
|
|
40
|
+
if (!jsonMode) {
|
|
41
|
+
tui.success('Removed plugin installation directory');
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (!jsonMode) {
|
|
45
|
+
tui.warning(`Failed to remove plugin directory: ${error}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
if (!jsonMode) {
|
|
50
|
+
tui.info('Plugin installation directory not found - nothing to remove');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (await Bun.file(CLAUDE_SETTINGS_FILE).exists()) {
|
|
55
|
+
try {
|
|
56
|
+
const content = readFileSync(CLAUDE_SETTINGS_FILE, 'utf-8');
|
|
57
|
+
const settings: ClaudeSettings = JSON.parse(content);
|
|
58
|
+
|
|
59
|
+
if (settings.permissions) {
|
|
60
|
+
const allPerms = [...AGENTUITY_ALLOW_PERMISSIONS, ...AGENTUITY_DENY_PERMISSIONS];
|
|
61
|
+
|
|
62
|
+
if (settings.permissions.allow) {
|
|
63
|
+
const originalAllowLen = settings.permissions.allow.length;
|
|
64
|
+
settings.permissions.allow = settings.permissions.allow.filter(
|
|
65
|
+
(p) => !allPerms.includes(p)
|
|
66
|
+
);
|
|
67
|
+
if (settings.permissions.allow.length < originalAllowLen) {
|
|
68
|
+
removedPermissions = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (settings.permissions.deny) {
|
|
73
|
+
const originalDenyLen = settings.permissions.deny.length;
|
|
74
|
+
settings.permissions.deny = settings.permissions.deny.filter(
|
|
75
|
+
(p) => !allPerms.includes(p)
|
|
76
|
+
);
|
|
77
|
+
if (settings.permissions.deny.length < originalDenyLen) {
|
|
78
|
+
removedPermissions = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (removedPermissions) {
|
|
83
|
+
writeFileSync(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2) + '\n');
|
|
84
|
+
if (!jsonMode) {
|
|
85
|
+
tui.success('Removed Agentuity permissions from Claude Code settings');
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
if (!jsonMode) {
|
|
89
|
+
tui.info('No Agentuity permissions found in Claude Code settings');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (!jsonMode) {
|
|
95
|
+
tui.warning(`Failed to update Claude Code settings: ${error}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
if (!jsonMode) {
|
|
100
|
+
tui.info('Claude Code settings file not found');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!jsonMode) {
|
|
105
|
+
tui.newline();
|
|
106
|
+
|
|
107
|
+
if (removedPlugin || removedPermissions) {
|
|
108
|
+
tui.output(tui.bold('Agentuity Coder plugin uninstalled successfully'));
|
|
109
|
+
} else {
|
|
110
|
+
tui.output(tui.bold('Agentuity Coder plugin was not installed'));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
tui.newline();
|
|
114
|
+
tui.info(`To reinstall, run: ${tui.bold(getCommand('ai claude-code install'))}`);
|
|
115
|
+
tui.newline();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { success: true, removedPlugin, removedPermissions };
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
export default uninstallSubcommand;
|
package/src/cmd/ai/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import capabilitiesCommand from './capabilities';
|
|
|
3
3
|
import promptCommand from './prompt';
|
|
4
4
|
import schemaCommand from './schema';
|
|
5
5
|
import opencodeCommand from './opencode';
|
|
6
|
+
import claudeCodeCommand from './claude-code';
|
|
6
7
|
import introSubcommand from './intro';
|
|
7
8
|
import detectSubcommand from './detect';
|
|
8
9
|
import { getCommand } from '../../command-prefix';
|
|
@@ -25,6 +26,10 @@ export const command = createCommand({
|
|
|
25
26
|
command: getCommand('ai opencode install'),
|
|
26
27
|
description: 'Install Agentuity Open Code plugin',
|
|
27
28
|
},
|
|
29
|
+
{
|
|
30
|
+
command: getCommand('ai claude-code install'),
|
|
31
|
+
description: 'Install Agentuity Coder plugin for Claude Code',
|
|
32
|
+
},
|
|
28
33
|
{
|
|
29
34
|
command: getCommand('ai capabilities show'),
|
|
30
35
|
description: 'Show CLI capabilities for AI agents',
|
|
@@ -38,6 +43,7 @@ export const command = createCommand({
|
|
|
38
43
|
detectSubcommand,
|
|
39
44
|
introSubcommand,
|
|
40
45
|
opencodeCommand,
|
|
46
|
+
claudeCodeCommand,
|
|
41
47
|
capabilitiesCommand,
|
|
42
48
|
promptCommand,
|
|
43
49
|
schemaCommand,
|
|
@@ -252,18 +252,11 @@ if (isDevelopment() && process.env.VITE_PORT) {
|
|
|
252
252
|
const url = new URL(c.req.url);
|
|
253
253
|
const queryString = url.search; // Includes the '?' prefix
|
|
254
254
|
|
|
255
|
-
// Get the requested WebSocket subprotocol (Vite uses 'vite-hmr')
|
|
256
|
-
const requestedProtocol = c.req.header('sec-websocket-protocol');
|
|
257
|
-
|
|
258
255
|
const success = server.upgrade(c.req.raw, {
|
|
259
256
|
data: { type: 'vite-hmr', queryString },
|
|
260
|
-
// Echo back the requested subprotocol so the browser accepts the connection
|
|
261
|
-
headers: requestedProtocol ? {
|
|
262
|
-
'Sec-WebSocket-Protocol': requestedProtocol,
|
|
263
|
-
} : undefined,
|
|
264
257
|
});
|
|
265
258
|
if (success) {
|
|
266
|
-
otel.logger.debug('[HMR Proxy] WebSocket upgrade successful
|
|
259
|
+
otel.logger.debug('[HMR Proxy] WebSocket upgrade successful');
|
|
267
260
|
return new Response(null);
|
|
268
261
|
}
|
|
269
262
|
otel.logger.error('[HMR Proxy] WebSocket upgrade returned false');
|
|
@@ -71,3 +71,40 @@ export function getWorkbenchConfig(
|
|
|
71
71
|
headers: workbench.headers ?? {},
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Known Vite framework plugin name prefixes.
|
|
77
|
+
* Each framework's Vite plugin registers one or more plugins whose names
|
|
78
|
+
* start with these prefixes. We match against these to detect whether the
|
|
79
|
+
* user has already configured a framework plugin in their agentuity.config.ts.
|
|
80
|
+
*/
|
|
81
|
+
const FRAMEWORK_PLUGIN_PREFIXES = [
|
|
82
|
+
'vite:react', // @vitejs/plugin-react (vite:react-babel, vite:react-refresh, …)
|
|
83
|
+
'vite:preact', // @preact/preset-vite
|
|
84
|
+
'vite-plugin-svelte', // @sveltejs/vite-plugin-svelte
|
|
85
|
+
'vite:vue', // @vitejs/plugin-vue (vite:vue, vite:vue-jsx)
|
|
86
|
+
'vite-plugin-solid', // vite-plugin-solid
|
|
87
|
+
'solid', // vite-plugin-solid also uses plain "solid"
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check if the user's plugins include any known UI-framework Vite plugin
|
|
92
|
+
* (React, Svelte, Vue, Solid, Preact, …).
|
|
93
|
+
*
|
|
94
|
+
* Detection is name-based: Vite plugins expose a `name` property and every
|
|
95
|
+
* major framework plugin uses a predictable prefix. This avoids dynamically
|
|
96
|
+
* importing every possible framework just to compare names.
|
|
97
|
+
*/
|
|
98
|
+
export function hasFrameworkPlugin(userPlugins: import('vite').PluginOption[]): boolean {
|
|
99
|
+
const flat = (userPlugins as unknown[]).flat(Infinity).filter(Boolean);
|
|
100
|
+
return flat.some(
|
|
101
|
+
(p: unknown) =>
|
|
102
|
+
p &&
|
|
103
|
+
typeof p === 'object' &&
|
|
104
|
+
'name' in p &&
|
|
105
|
+
typeof (p as { name: unknown }).name === 'string' &&
|
|
106
|
+
FRAMEWORK_PLUGIN_PREFIXES.some((prefix) =>
|
|
107
|
+
((p as { name: string }).name).startsWith(prefix)
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
@@ -316,10 +316,28 @@ export async function installExternalsAndBuild(options: ServerBundleOptions): Pr
|
|
|
316
316
|
},
|
|
317
317
|
};
|
|
318
318
|
|
|
319
|
+
// Detect files belonging to @agentuity/postgres or @agentuity/drizzle.
|
|
320
|
+
// Matches both published paths (node_modules/@agentuity/postgres/) and
|
|
321
|
+
// symlinked/monorepo paths (packages/postgres/dist/, packages/postgres/src/).
|
|
322
|
+
const isAgentuityPostgres = (filePath: string) =>
|
|
323
|
+
filePath.includes('/@agentuity/postgres/') ||
|
|
324
|
+
filePath.includes('\\@agentuity\\postgres\\') ||
|
|
325
|
+
filePath.includes('/packages/postgres/');
|
|
326
|
+
|
|
327
|
+
const isAgentuityDrizzle = (filePath: string) =>
|
|
328
|
+
filePath.includes('/@agentuity/drizzle/') ||
|
|
329
|
+
filePath.includes('\\@agentuity\\drizzle\\') ||
|
|
330
|
+
filePath.includes('/packages/drizzle/');
|
|
331
|
+
|
|
319
332
|
const dbRewritePlugin: BunPlugin = {
|
|
320
333
|
name: 'agentuity:db-rewrite',
|
|
321
334
|
setup(build) {
|
|
322
335
|
build.onResolve({ filter: /^drizzle-orm\/bun-sql$/ }, (args) => {
|
|
336
|
+
// Don't redirect if the importer is @agentuity/drizzle itself — that would create a cycle.
|
|
337
|
+
// Matches both published packages in node_modules and symlinked monorepo paths.
|
|
338
|
+
if (args.importer && isAgentuityDrizzle(args.importer)) {
|
|
339
|
+
return; // Let default resolution handle it
|
|
340
|
+
}
|
|
323
341
|
// Resolve to @agentuity/drizzle — the bundler will find it in node_modules
|
|
324
342
|
// and bundle it into .agentuity/app.js (NOT kept external).
|
|
325
343
|
const resolved = import.meta.resolveSync('@agentuity/drizzle', args.importer);
|
|
@@ -333,7 +351,16 @@ export async function installExternalsAndBuild(options: ServerBundleOptions): Pr
|
|
|
333
351
|
namespace: 'file',
|
|
334
352
|
},
|
|
335
353
|
async (args) => {
|
|
336
|
-
|
|
354
|
+
// Skip node_modules and the rewrite-target packages themselves.
|
|
355
|
+
// The symlink check is needed because symlinked packages (e.g. via
|
|
356
|
+
// workspace links) resolve to paths outside node_modules/ (like
|
|
357
|
+
// ../../sdk/packages/postgres/dist/) and would otherwise be rewritten,
|
|
358
|
+
// creating circular imports (postgres importing from itself).
|
|
359
|
+
if (
|
|
360
|
+
args.path.includes('/node_modules/') ||
|
|
361
|
+
isAgentuityPostgres(args.path) ||
|
|
362
|
+
isAgentuityDrizzle(args.path)
|
|
363
|
+
) {
|
|
337
364
|
return;
|
|
338
365
|
}
|
|
339
366
|
const contents = await Bun.file(args.path).text();
|
|
@@ -128,24 +128,33 @@ export async function generateAssetServerConfig(
|
|
|
128
128
|
'process.env.NODE_ENV': JSON.stringify('development'),
|
|
129
129
|
},
|
|
130
130
|
|
|
131
|
-
// Plugins: User plugins first (
|
|
131
|
+
// Plugins: User plugins first (includes framework plugin like React/Svelte/Vue), then browser env
|
|
132
132
|
// Try project's node_modules first, fall back to CLI's bundled version
|
|
133
133
|
plugins: await (async () => {
|
|
134
|
-
const projectRequire = createRequire(join(rootDir, 'package.json'));
|
|
135
|
-
let reactPluginPath = '@vitejs/plugin-react';
|
|
136
|
-
try {
|
|
137
|
-
reactPluginPath = projectRequire.resolve('@vitejs/plugin-react');
|
|
138
|
-
} catch {
|
|
139
|
-
// Project doesn't have @vitejs/plugin-react, use CLI's bundled version
|
|
140
|
-
}
|
|
141
|
-
const reactPlugin = (await import(reactPluginPath)).default();
|
|
142
134
|
const { browserEnvPlugin } = await import('./browser-env-plugin');
|
|
143
135
|
const { publicAssetPathPlugin } = await import('./public-asset-path-plugin');
|
|
136
|
+
const { hasFrameworkPlugin } = await import('./config-loader');
|
|
137
|
+
|
|
138
|
+
// Auto-add React plugin if no framework plugin is present (backwards compatibility)
|
|
139
|
+
const resolvedUserPlugins = [...userPlugins];
|
|
140
|
+
if (resolvedUserPlugins.length === 0 || !hasFrameworkPlugin(resolvedUserPlugins)) {
|
|
141
|
+
logger.debug(
|
|
142
|
+
'No framework plugin found in agentuity.config.ts plugins, adding React automatically for dev server'
|
|
143
|
+
);
|
|
144
|
+
const projectRequire = createRequire(join(rootDir, 'package.json'));
|
|
145
|
+
let reactPluginPath = '@vitejs/plugin-react';
|
|
146
|
+
try {
|
|
147
|
+
reactPluginPath = projectRequire.resolve('@vitejs/plugin-react');
|
|
148
|
+
} catch {
|
|
149
|
+
// Project doesn't have @vitejs/plugin-react, use CLI's bundled version
|
|
150
|
+
}
|
|
151
|
+
const reactModule = await import(reactPluginPath);
|
|
152
|
+
resolvedUserPlugins.unshift(reactModule.default());
|
|
153
|
+
}
|
|
154
|
+
|
|
144
155
|
return [
|
|
145
|
-
// User-defined plugins from agentuity.config.ts (
|
|
146
|
-
...
|
|
147
|
-
// React plugin for JSX/TSX transformation and Fast Refresh
|
|
148
|
-
reactPlugin,
|
|
156
|
+
// User-defined plugins from agentuity.config.ts (framework plugin + extras)
|
|
157
|
+
...resolvedUserPlugins,
|
|
149
158
|
// Browser env plugin to map process.env to import.meta.env
|
|
150
159
|
browserEnvPlugin(),
|
|
151
160
|
// Warn about incorrect public asset paths in dev mode
|
|
@@ -180,8 +180,24 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
|
|
|
180
180
|
|
|
181
181
|
// Load custom user plugins from agentuity.config.ts if it exists
|
|
182
182
|
const clientOutDir = join(rootDir, '.agentuity/client');
|
|
183
|
+
const { loadAgentuityConfig, hasFrameworkPlugin } = await import('./config-loader');
|
|
184
|
+
const userConfig = await loadAgentuityConfig(rootDir, logger);
|
|
185
|
+
const userPlugins = userConfig?.plugins || [];
|
|
186
|
+
|
|
187
|
+
// Auto-add React plugin if no framework plugin is present (backwards compatibility)
|
|
188
|
+
if (userPlugins.length === 0 || !hasFrameworkPlugin(userPlugins)) {
|
|
189
|
+
logger.debug(
|
|
190
|
+
'No framework plugin found in agentuity.config.ts plugins, adding React automatically'
|
|
191
|
+
);
|
|
192
|
+
userPlugins.unshift(react());
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (userPlugins.length > 0) {
|
|
196
|
+
logger.debug('Loaded %d custom plugin(s) from agentuity.config.ts', userPlugins.length);
|
|
197
|
+
}
|
|
198
|
+
|
|
183
199
|
const plugins = [
|
|
184
|
-
|
|
200
|
+
...userPlugins,
|
|
185
201
|
browserEnvPlugin(),
|
|
186
202
|
// Fix incorrect public asset paths and rewrite to CDN URLs
|
|
187
203
|
publicAssetPathPlugin({ cdnBaseUrl }),
|
|
@@ -189,13 +205,6 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
|
|
|
189
205
|
// Emit analytics beacon as hashed CDN asset (prod builds only)
|
|
190
206
|
beaconPlugin({ enabled: analyticsEnabled && !dev }),
|
|
191
207
|
];
|
|
192
|
-
const { loadAgentuityConfig } = await import('./config-loader');
|
|
193
|
-
const userConfig = await loadAgentuityConfig(rootDir, logger);
|
|
194
|
-
const userPlugins = userConfig?.plugins || [];
|
|
195
|
-
plugins.push(...userPlugins);
|
|
196
|
-
if (userPlugins.length > 0) {
|
|
197
|
-
logger.debug('Loaded %d custom plugin(s) from agentuity.config.ts', userPlugins.length);
|
|
198
|
-
}
|
|
199
208
|
|
|
200
209
|
// Merge custom define values from user config
|
|
201
210
|
const userDefine = userConfig?.define || {};
|
package/src/cmd/dev/download.ts
CHANGED
|
@@ -1,118 +1,116 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { tmpdir, platform } from 'node:os';
|
|
4
4
|
import { join, dirname } from 'node:path';
|
|
5
5
|
import * as tar from 'tar';
|
|
6
|
-
import {
|
|
6
|
+
import { StructuredError } from '@agentuity/core';
|
|
7
7
|
import { spinner } from '../../tui';
|
|
8
8
|
|
|
9
|
-
const user = 'agentuity';
|
|
10
|
-
const repo = 'gravity';
|
|
11
|
-
|
|
12
|
-
function filterRelease(release: { prerelease: boolean }) {
|
|
13
|
-
// Filter out prereleases.
|
|
14
|
-
return release.prerelease === false;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function filterAsset(asset: { name: string }): boolean {
|
|
18
|
-
// Filter out the release matching our os and architecture
|
|
19
|
-
let arch: string = process.arch;
|
|
20
|
-
if (arch === 'x64') {
|
|
21
|
-
arch = 'x86_64';
|
|
22
|
-
}
|
|
23
|
-
return asset.name.includes(arch) && asset.name.includes(platform());
|
|
24
|
-
}
|
|
25
|
-
|
|
26
9
|
interface GravityClient {
|
|
27
10
|
filename: string;
|
|
28
11
|
version: string;
|
|
29
12
|
}
|
|
30
13
|
|
|
14
|
+
const GravityVersionError = StructuredError('GravityVersionError')<{
|
|
15
|
+
status: number;
|
|
16
|
+
statusText: string;
|
|
17
|
+
}>();
|
|
18
|
+
const GravityDownloadError = StructuredError('GravityDownloadError')<{
|
|
19
|
+
status: number;
|
|
20
|
+
statusText: string;
|
|
21
|
+
}>();
|
|
22
|
+
const GravityExtractionError = StructuredError('GravityExtractionError')<{
|
|
23
|
+
path: string;
|
|
24
|
+
}>();
|
|
25
|
+
|
|
26
|
+
function getBaseURL(): string {
|
|
27
|
+
return process.env.AGENTUITY_SH_URL || 'https://agentuity.sh';
|
|
28
|
+
}
|
|
29
|
+
|
|
31
30
|
/**
|
|
32
31
|
*
|
|
33
32
|
* @returns full path to the downloaded file
|
|
34
33
|
*/
|
|
35
34
|
export async function download(gravityDir: string): Promise<GravityClient> {
|
|
36
|
-
const
|
|
35
|
+
const baseURL = getBaseURL();
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
// Step 1: Get the latest version from agentuity.sh
|
|
38
|
+
const tag = (await spinner({
|
|
39
39
|
message: 'Checking Agentuity Gravity',
|
|
40
40
|
callback: async () => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
)
|
|
41
|
+
const resp = await fetch(`${baseURL}/release/gravity/version`, {
|
|
42
|
+
signal: AbortSignal.timeout(10_000),
|
|
43
|
+
});
|
|
44
|
+
if (!resp.ok) {
|
|
45
|
+
throw new GravityVersionError({
|
|
46
|
+
status: resp.status,
|
|
47
|
+
statusText: resp.statusText,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const text = (await resp.text()).trim();
|
|
51
|
+
return text.startsWith('v') ? text : `v${text}`;
|
|
52
52
|
},
|
|
53
53
|
clearOnSuccess: true,
|
|
54
|
-
})) as
|
|
54
|
+
})) as string;
|
|
55
55
|
|
|
56
|
-
const
|
|
57
|
-
const version = versionTok[1] ?? 'unknown';
|
|
56
|
+
const version = tag.startsWith('v') ? tag.slice(1) : tag;
|
|
58
57
|
const releaseFilename = join(gravityDir, version, 'gravity');
|
|
59
|
-
const mustDownload = !existsSync(releaseFilename);
|
|
60
58
|
|
|
61
|
-
if
|
|
59
|
+
// Step 2: Check if already downloaded
|
|
60
|
+
if (await Bun.file(releaseFilename).exists()) {
|
|
62
61
|
return { filename: releaseFilename, version };
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
outputdir,
|
|
72
|
-
filterRelease,
|
|
73
|
-
filterAsset,
|
|
74
|
-
false,
|
|
75
|
-
true,
|
|
76
|
-
false,
|
|
77
|
-
''
|
|
78
|
-
)) as string[];
|
|
79
|
-
const file = res[0];
|
|
80
|
-
if (!file) {
|
|
81
|
-
throw new Error('No file downloaded from release');
|
|
82
|
-
}
|
|
83
|
-
return file;
|
|
84
|
-
},
|
|
85
|
-
clearOnSuccess: true,
|
|
86
|
-
});
|
|
64
|
+
// Step 3: Download the binary from agentuity.sh
|
|
65
|
+
const os = platform();
|
|
66
|
+
let arch: string = process.arch;
|
|
67
|
+
if (arch === 'x64') {
|
|
68
|
+
arch = 'x86_64';
|
|
69
|
+
}
|
|
87
70
|
|
|
88
|
-
|
|
71
|
+
const tmpFile = join(tmpdir(), `${randomUUID()}.tar.gz`);
|
|
72
|
+
|
|
73
|
+
try {
|
|
89
74
|
await spinner({
|
|
90
|
-
message:
|
|
75
|
+
message: `Downloading Gravity ${version}`,
|
|
91
76
|
callback: async () => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const downloadDir = dirname(releaseFilename);
|
|
95
|
-
if (!existsSync(downloadDir)) {
|
|
96
|
-
mkdirSync(downloadDir, { recursive: true });
|
|
97
|
-
}
|
|
98
|
-
input.on('finish', resolve);
|
|
99
|
-
input.on('end', resolve);
|
|
100
|
-
input.on('error', reject);
|
|
101
|
-
input.pipe(tar.x({ C: downloadDir, chmod: true }));
|
|
77
|
+
const resp = await fetch(`${baseURL}/release/gravity/${tag}/${os}/${arch}`, {
|
|
78
|
+
signal: AbortSignal.timeout(60_000),
|
|
102
79
|
});
|
|
80
|
+
if (!resp.ok) {
|
|
81
|
+
throw new GravityDownloadError({
|
|
82
|
+
status: resp.status,
|
|
83
|
+
statusText: resp.statusText,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const buffer = await resp.arrayBuffer();
|
|
87
|
+
writeFileSync(tmpFile, Buffer.from(buffer));
|
|
103
88
|
},
|
|
104
89
|
clearOnSuccess: true,
|
|
105
90
|
});
|
|
106
|
-
} else {
|
|
107
|
-
// TODO:
|
|
108
|
-
}
|
|
109
91
|
|
|
110
|
-
|
|
111
|
-
|
|
92
|
+
// Step 4: Extract the tarball
|
|
93
|
+
await spinner({
|
|
94
|
+
message: 'Extracting release',
|
|
95
|
+
callback: async () => {
|
|
96
|
+
const downloadDir = dirname(releaseFilename);
|
|
97
|
+
if (!(await Bun.file(downloadDir).exists())) {
|
|
98
|
+
mkdirSync(downloadDir, { recursive: true });
|
|
99
|
+
}
|
|
100
|
+
await tar.x({ file: tmpFile, cwd: downloadDir, chmod: true });
|
|
101
|
+
},
|
|
102
|
+
clearOnSuccess: true,
|
|
103
|
+
});
|
|
104
|
+
} finally {
|
|
105
|
+
// Clean up temp file regardless of success or failure
|
|
106
|
+
if (await Bun.file(tmpFile).exists()) {
|
|
107
|
+
rmSync(tmpFile);
|
|
108
|
+
}
|
|
112
109
|
}
|
|
113
110
|
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
// Step 5: Verify the binary was extracted
|
|
112
|
+
if (!(await Bun.file(releaseFilename).exists())) {
|
|
113
|
+
throw new GravityExtractionError({ path: releaseFilename });
|
|
116
114
|
}
|
|
117
115
|
|
|
118
116
|
return { filename: releaseFilename, version };
|
package/src/cmd/upgrade/index.ts
CHANGED
|
@@ -71,19 +71,35 @@ export async function fetchLatestVersion(): Promise<string> {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* 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.
|
|
75
76
|
*/
|
|
76
|
-
async function performBunUpgrade(
|
|
77
|
+
async function performBunUpgrade(
|
|
78
|
+
version: string,
|
|
79
|
+
onRetry?: (attempt: number, delayMs: number) => void
|
|
80
|
+
): Promise<void> {
|
|
77
81
|
// Remove 'v' prefix for npm version
|
|
78
82
|
const npmVersion = version.replace(/^v/, '');
|
|
79
83
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
+
);
|
|
87
103
|
}
|
|
88
104
|
}
|
|
89
105
|
|
|
@@ -194,8 +210,9 @@ export const command = createCommand({
|
|
|
194
210
|
callback: async () => {
|
|
195
211
|
const { waitForNpmAvailability } = await import('./npm-availability');
|
|
196
212
|
return await waitForNpmAvailability(latestVersion, {
|
|
197
|
-
maxAttempts:
|
|
198
|
-
initialDelayMs:
|
|
213
|
+
maxAttempts: 10,
|
|
214
|
+
initialDelayMs: 5000,
|
|
215
|
+
maxDelayMs: 15000,
|
|
199
216
|
});
|
|
200
217
|
},
|
|
201
218
|
});
|
|
@@ -244,8 +261,14 @@ export const command = createCommand({
|
|
|
244
261
|
|
|
245
262
|
// Perform the upgrade using bun
|
|
246
263
|
await tui.spinner({
|
|
264
|
+
type: 'logger',
|
|
247
265
|
message: `Installing @agentuity/cli@${normalizedLatest}...`,
|
|
248
|
-
callback: async () =>
|
|
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
|
+
}),
|
|
249
272
|
});
|
|
250
273
|
|
|
251
274
|
// Verify the upgrade
|