@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.
Files changed (88) hide show
  1. package/bin/cli.ts +7 -0
  2. package/dist/bun-path.d.ts.map +1 -1
  3. package/dist/bun-path.js +1 -3
  4. package/dist/bun-path.js.map +1 -1
  5. package/dist/cmd/ai/index.d.ts.map +1 -1
  6. package/dist/cmd/ai/index.js +1 -0
  7. package/dist/cmd/ai/index.js.map +1 -1
  8. package/dist/cmd/build/ast.d.ts.map +1 -1
  9. package/dist/cmd/build/ast.js +5 -0
  10. package/dist/cmd/build/ast.js.map +1 -1
  11. package/dist/cmd/build/bundler.d.ts.map +1 -1
  12. package/dist/cmd/build/bundler.js +10 -0
  13. package/dist/cmd/build/bundler.js.map +1 -1
  14. package/dist/cmd/build/patch/_util.js +6 -6
  15. package/dist/cmd/build/patch/_util.js.map +1 -1
  16. package/dist/cmd/build/patch/llm.js +1 -1
  17. package/dist/cmd/build/patch/llm.js.map +1 -1
  18. package/dist/cmd/build/plugin.d.ts.map +1 -1
  19. package/dist/cmd/build/plugin.js +21 -14
  20. package/dist/cmd/build/plugin.js.map +1 -1
  21. package/dist/cmd/build/route-discovery.d.ts +8 -4
  22. package/dist/cmd/build/route-discovery.d.ts.map +1 -1
  23. package/dist/cmd/build/route-discovery.js +10 -5
  24. package/dist/cmd/build/route-discovery.js.map +1 -1
  25. package/dist/cmd/cloud/scp/download.js +3 -3
  26. package/dist/cmd/cloud/scp/download.js.map +1 -1
  27. package/dist/cmd/cloud/scp/upload.js +3 -3
  28. package/dist/cmd/cloud/scp/upload.js.map +1 -1
  29. package/dist/cmd/cloud/ssh.js +3 -3
  30. package/dist/cmd/cloud/ssh.js.map +1 -1
  31. package/dist/cmd/dev/index.d.ts.map +1 -1
  32. package/dist/cmd/dev/index.js +5 -0
  33. package/dist/cmd/dev/index.js.map +1 -1
  34. package/dist/cmd/index.d.ts.map +1 -1
  35. package/dist/cmd/index.js +7 -0
  36. package/dist/cmd/index.js.map +1 -1
  37. package/dist/cmd/profile/create.d.ts.map +1 -1
  38. package/dist/cmd/profile/create.js +1 -0
  39. package/dist/cmd/profile/create.js.map +1 -1
  40. package/dist/cmd/upgrade/index.d.ts +20 -0
  41. package/dist/cmd/upgrade/index.d.ts.map +1 -0
  42. package/dist/cmd/upgrade/index.js +307 -0
  43. package/dist/cmd/upgrade/index.js.map +1 -0
  44. package/dist/cmd/version/index.d.ts.map +1 -1
  45. package/dist/cmd/version/index.js +1 -0
  46. package/dist/cmd/version/index.js.map +1 -1
  47. package/dist/config.d.ts +1 -1
  48. package/dist/config.d.ts.map +1 -1
  49. package/dist/config.js +12 -94
  50. package/dist/config.js.map +1 -1
  51. package/dist/tui.d.ts.map +1 -1
  52. package/dist/tui.js +16 -0
  53. package/dist/tui.js.map +1 -1
  54. package/dist/types.d.ts +7 -0
  55. package/dist/types.d.ts.map +1 -1
  56. package/dist/types.js.map +1 -1
  57. package/dist/utils/dependency-checker.d.ts +20 -0
  58. package/dist/utils/dependency-checker.d.ts.map +1 -0
  59. package/dist/utils/dependency-checker.js +161 -0
  60. package/dist/utils/dependency-checker.js.map +1 -0
  61. package/dist/version-check.d.ts +13 -0
  62. package/dist/version-check.d.ts.map +1 -0
  63. package/dist/version-check.js +177 -0
  64. package/dist/version-check.js.map +1 -0
  65. package/package.json +3 -3
  66. package/src/bun-path.ts +1 -3
  67. package/src/cmd/ai/index.ts +1 -0
  68. package/src/cmd/build/ast.ts +7 -0
  69. package/src/cmd/build/bundler.ts +12 -0
  70. package/src/cmd/build/patch/_util.ts +6 -6
  71. package/src/cmd/build/patch/llm.ts +1 -1
  72. package/src/cmd/build/plugin.ts +23 -16
  73. package/src/cmd/build/route-discovery.ts +10 -5
  74. package/src/cmd/cloud/scp/download.ts +3 -3
  75. package/src/cmd/cloud/scp/upload.ts +3 -3
  76. package/src/cmd/cloud/ssh.ts +3 -3
  77. package/src/cmd/dev/index.ts +11 -0
  78. package/src/cmd/index.ts +8 -0
  79. package/src/cmd/profile/create.ts +1 -0
  80. package/src/cmd/project/download.ts +1 -1
  81. package/src/cmd/upgrade/index.ts +365 -0
  82. package/src/cmd/version/index.ts +1 -0
  83. package/src/config.ts +12 -121
  84. package/src/git-helper.ts +4 -4
  85. package/src/tui.ts +19 -0
  86. package/src/types.ts +7 -0
  87. package/src/utils/dependency-checker.ts +207 -0
  88. package/src/version-check.ts +234 -0
package/src/types.ts CHANGED
@@ -89,6 +89,7 @@ export interface GlobalOptions {
89
89
  explain?: boolean;
90
90
  dryRun?: boolean;
91
91
  validate?: boolean;
92
+ skipVersionCheck?: boolean;
92
93
  }
93
94
 
94
95
  export interface PaginationInfo {
@@ -282,6 +283,8 @@ export function createCommand<
282
283
  banner?: true;
283
284
  aliases?: string[];
284
285
  hidden?: boolean;
286
+ executable?: boolean;
287
+ skipUpgradeCheck?: boolean;
285
288
  requires?: R;
286
289
  optional?: O;
287
290
  examples?: CommandExample[];
@@ -318,6 +321,8 @@ type CommandDefBase =
318
321
  description: string;
319
322
  aliases?: string[];
320
323
  banner?: boolean;
324
+ executable?: boolean;
325
+ skipUpgradeCheck?: boolean;
321
326
  examples?: CommandExample[];
322
327
  idempotent?: boolean;
323
328
  prerequisites?: string[];
@@ -332,6 +337,8 @@ type CommandDefBase =
332
337
  description: string;
333
338
  aliases?: string[];
334
339
  banner?: boolean;
340
+ executable?: boolean;
341
+ skipUpgradeCheck?: boolean;
335
342
  examples?: CommandExample[];
336
343
  idempotent?: boolean;
337
344
  prerequisites?: string[];
@@ -0,0 +1,207 @@
1
+ import { $ } from 'bun';
2
+ import { join } from 'node:path';
3
+ import { readFileSync, writeFileSync } from 'node:fs';
4
+ import { getVersion } from '../version';
5
+ import type { Logger } from '../types';
6
+
7
+ interface PackageJson {
8
+ dependencies?: Record<string, string>;
9
+ devDependencies?: Record<string, string>;
10
+ }
11
+
12
+ export interface UpgradeResult {
13
+ upgraded: string[];
14
+ skipped: string[];
15
+ failed: string[];
16
+ }
17
+
18
+ /**
19
+ * Checks if a version specifier should be upgraded
20
+ * @param specifier - The version specifier from package.json (e.g., "latest", "^1.0.0", "1.2.3")
21
+ * @returns true if the package should be upgraded
22
+ */
23
+ export function shouldUpgradeVersion(specifier: string): boolean {
24
+ // Always upgrade "latest" and "*"
25
+ if (specifier === 'latest' || specifier === '*') {
26
+ return true;
27
+ }
28
+
29
+ // Skip pinned versions (exact semver like "1.2.3")
30
+ if (/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/.test(specifier)) {
31
+ return false;
32
+ }
33
+
34
+ // Upgrade ranges (^1.0.0, ~1.0.0, >=1.0.0, etc.)
35
+ // Check if the specifier is a range pattern
36
+ if (/^[~^>=<]/.test(specifier)) {
37
+ return true;
38
+ }
39
+
40
+ // Default to not upgrading if we can't determine
41
+ return false;
42
+ }
43
+
44
+ /**
45
+ * Check and upgrade @agentuity/* dependencies to match CLI version
46
+ * @param projectDir - Root directory of the user's project
47
+ * @param logger - Logger instance
48
+ * @returns Result of the upgrade operation
49
+ */
50
+ export async function checkAndUpgradeDependencies(
51
+ projectDir: string,
52
+ logger: Logger
53
+ ): Promise<UpgradeResult> {
54
+ const result: UpgradeResult = {
55
+ upgraded: [],
56
+ skipped: [],
57
+ failed: [],
58
+ };
59
+
60
+ // Skip in CI/non-interactive environments
61
+ if (!process.stdin.isTTY) {
62
+ logger.debug('Skipping dependency check in non-interactive environment');
63
+ return result;
64
+ }
65
+
66
+ const packageJsonPath = join(projectDir, 'package.json');
67
+ const cliVersion = getVersion();
68
+
69
+ logger.debug('CLI version: %s', cliVersion);
70
+
71
+ // Read package.json
72
+ let packageJson: PackageJson;
73
+ try {
74
+ packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
75
+ } catch (error) {
76
+ logger.debug('Failed to read package.json: %s', error);
77
+ return result;
78
+ }
79
+
80
+ // Collect all @agentuity/* packages and their original specifiers
81
+ const allDeps = {
82
+ ...packageJson.dependencies,
83
+ ...packageJson.devDependencies,
84
+ };
85
+
86
+ const agentuitPackages = Object.entries(allDeps)
87
+ .filter(([name]) => name.startsWith('@agentuity/'))
88
+ .map(([name, specifier]) => ({ name, specifier }));
89
+
90
+ if (agentuitPackages.length === 0) {
91
+ logger.debug('No @agentuity/* packages found in package.json');
92
+ return result;
93
+ }
94
+
95
+ // Check which packages need upgrading
96
+ const packagesToUpgrade = agentuitPackages.filter(({ specifier }) =>
97
+ shouldUpgradeVersion(specifier)
98
+ );
99
+
100
+ if (packagesToUpgrade.length === 0) {
101
+ logger.debug('All @agentuity/* packages are pinned, skipping upgrade');
102
+ for (const pkg of agentuitPackages) {
103
+ result.skipped.push(pkg.name);
104
+ }
105
+ return result;
106
+ }
107
+
108
+ // Check if CLI version is different from installed packages
109
+ let needsUpgrade = false;
110
+ for (const { name } of packagesToUpgrade) {
111
+ try {
112
+ const installedPackageJson = JSON.parse(
113
+ readFileSync(join(projectDir, 'node_modules', name, 'package.json'), 'utf-8')
114
+ );
115
+ const installedVersion: string = installedPackageJson.version;
116
+ if (installedVersion !== cliVersion) {
117
+ logger.debug(
118
+ '%s: installed=%s, cli=%s (needs upgrade)',
119
+ name,
120
+ installedVersion,
121
+ cliVersion
122
+ );
123
+ needsUpgrade = true;
124
+ } else {
125
+ logger.debug('%s: already at correct version %s', name, installedVersion);
126
+ }
127
+ } catch {
128
+ // Package not installed or can't read version - needs upgrade
129
+ logger.debug('%s: not installed or unreadable, needs upgrade', name);
130
+ needsUpgrade = true;
131
+ }
132
+ }
133
+
134
+ if (!needsUpgrade) {
135
+ logger.debug('All @agentuity/* packages are already at CLI version');
136
+ for (const pkg of packagesToUpgrade) {
137
+ result.skipped.push(pkg.name);
138
+ }
139
+ return result;
140
+ }
141
+
142
+ // Upgrade packages
143
+ logger.debug('Upgrading %d @agentuity/* package(s) to %s', packagesToUpgrade.length, cliVersion);
144
+
145
+ for (const { name } of packagesToUpgrade) {
146
+ try {
147
+ logger.debug('Installing %s@%s', name, cliVersion);
148
+ const installResult = await $`bun add ${name}@${cliVersion}`
149
+ .cwd(projectDir)
150
+ .quiet()
151
+ .nothrow();
152
+
153
+ if (installResult.exitCode !== 0) {
154
+ logger.error(
155
+ 'Failed to install %s@%s: %s',
156
+ name,
157
+ cliVersion,
158
+ installResult.stderr.toString()
159
+ );
160
+ result.failed.push(name);
161
+ } else {
162
+ logger.debug('Successfully installed %s@%s', name, cliVersion);
163
+ result.upgraded.push(name);
164
+ }
165
+ } catch (_error) {
166
+ logger.error('Error installing %s: %s', name, _error);
167
+ result.failed.push(name);
168
+ }
169
+ }
170
+
171
+ // Restore original version specifiers in package.json
172
+ // (bun add replaces them with specific versions)
173
+ if (result.upgraded.length > 0) {
174
+ try {
175
+ const updatedPackageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
176
+ let modified = false;
177
+
178
+ for (const { name, specifier } of packagesToUpgrade) {
179
+ // Only restore if we successfully upgraded
180
+ if (!result.upgraded.includes(name)) {
181
+ continue;
182
+ }
183
+
184
+ // Check both dependencies and devDependencies
185
+ if (updatedPackageJson.dependencies?.[name]) {
186
+ updatedPackageJson.dependencies[name] = specifier;
187
+ modified = true;
188
+ logger.debug('Restored %s to "%s" in dependencies', name, specifier);
189
+ }
190
+ if (updatedPackageJson.devDependencies?.[name]) {
191
+ updatedPackageJson.devDependencies[name] = specifier;
192
+ modified = true;
193
+ logger.debug('Restored %s to "%s" in devDependencies', name, specifier);
194
+ }
195
+ }
196
+
197
+ if (modified) {
198
+ writeFileSync(packageJsonPath, JSON.stringify(updatedPackageJson, null, 2) + '\n');
199
+ logger.debug('Restored original version specifiers in package.json');
200
+ }
201
+ } catch (_error) {
202
+ logger.warn('Failed to restore version specifiers in package.json: %s', _error);
203
+ }
204
+ }
205
+
206
+ return result;
207
+ }
@@ -0,0 +1,234 @@
1
+ import type { Config, Logger, CommandDefinition } from './types';
2
+ import { isRunningFromExecutable, fetchLatestVersion } from './cmd/upgrade';
3
+ import { getVersion } from './version';
4
+ import * as tui from './tui';
5
+ import { saveConfig } from './config';
6
+ import { $ } from 'bun';
7
+
8
+ const ONE_HOUR_MS = 60 * 60 * 1000;
9
+
10
+ /**
11
+ * Check if we should skip the version check based on environment and config
12
+ */
13
+ function shouldSkipCheck(
14
+ config: Config | null,
15
+ options: {
16
+ json?: boolean;
17
+ quiet?: boolean;
18
+ validate?: boolean;
19
+ dryRun?: boolean;
20
+ skipVersionCheck?: boolean;
21
+ },
22
+ commandDef: CommandDefinition | undefined,
23
+ args: string[]
24
+ ): boolean {
25
+ // Skip if running via bun/bunx (not installed executable)
26
+ if (!isRunningFromExecutable()) {
27
+ return true;
28
+ }
29
+
30
+ // Skip if no TTY (CI, redirected output, etc.)
31
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
32
+ return true;
33
+ }
34
+
35
+ // Skip if any of these flags are set
36
+ if (options.json || options.quiet || options.validate || options.dryRun) {
37
+ return true;
38
+ }
39
+
40
+ // Skip if explicitly disabled via flag
41
+ if (options.skipVersionCheck) {
42
+ return true;
43
+ }
44
+
45
+ // Skip if explicitly disabled via environment variable
46
+ if (process.env.AGENTUITY_SKIP_VERSION_CHECK === '1') {
47
+ return true;
48
+ }
49
+
50
+ // Skip if explicitly disabled via config
51
+ if (config?.overrides?.skip_version_check) {
52
+ return true;
53
+ }
54
+
55
+ // Skip if development version (0.0.x or 'dev')
56
+ const currentVersion = getVersion();
57
+ if (currentVersion.startsWith('0.0.') || currentVersion === 'dev') {
58
+ return true;
59
+ }
60
+
61
+ // Skip if command explicitly opts out of upgrade check
62
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
+ if (commandDef && (commandDef as any).skipUpgradeCheck === true) {
64
+ return true;
65
+ }
66
+
67
+ // Skip for help commands
68
+ const helpFlags = ['--help', '-h', 'help'];
69
+ if (args.some((arg) => helpFlags.includes(arg))) {
70
+ return true;
71
+ }
72
+
73
+ return false;
74
+ }
75
+
76
+ /**
77
+ * Check if enough time has passed since last check (at least 1 hour)
78
+ */
79
+ function shouldCheckNow(config: Config | null): boolean {
80
+ const lastCheck = config?.preferences?.last_update_check;
81
+ if (!lastCheck) {
82
+ return true;
83
+ }
84
+
85
+ const now = Date.now();
86
+ const elapsed = now - lastCheck;
87
+ return elapsed >= ONE_HOUR_MS;
88
+ }
89
+
90
+ /**
91
+ * Prompt user to upgrade to a new version
92
+ * Returns true if user wants to upgrade, false otherwise
93
+ */
94
+ async function promptUpgrade(currentVersion: string, latestVersion: string): Promise<boolean> {
95
+ tui.newline();
96
+ tui.info(`${tui.bold('A new version of the CLI is available!')}`);
97
+ tui.info(`Current version: ${tui.muted(currentVersion)}`);
98
+ tui.info(`Latest version: ${tui.bold(latestVersion)}`);
99
+ tui.newline();
100
+
101
+ return await tui.confirm('Would you like to upgrade now?', true);
102
+ }
103
+
104
+ /**
105
+ * Update the last check timestamp in config
106
+ */
107
+ async function updateCheckTimestamp(config: Config | null, logger: Logger): Promise<void> {
108
+ if (!config) {
109
+ return;
110
+ }
111
+
112
+ const updatedConfig: Config = {
113
+ ...config,
114
+ preferences: {
115
+ ...config.preferences,
116
+ last_update_check: Date.now(),
117
+ },
118
+ };
119
+
120
+ try {
121
+ await saveConfig(updatedConfig);
122
+ } catch (error) {
123
+ // Non-fatal - log but continue
124
+ logger.debug('Failed to save config after version check: %s', error);
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Perform the upgrade and re-run the command
130
+ */
131
+ async function performUpgrade(logger: Logger): Promise<void> {
132
+ try {
133
+ // Run upgrade command (it will validate, download, install, etc.)
134
+ // Use process.execPath to get the actual binary path (not Bun.main which is virtual)
135
+ logger.info('Starting upgrade...');
136
+ await $`${process.execPath} upgrade`.quiet();
137
+
138
+ // If we got here, the upgrade succeeded
139
+ // Re-run the original command with the new binary
140
+ const args = process.argv.slice(2);
141
+ const newBinaryPath = process.execPath;
142
+
143
+ logger.info('Upgrade successful! Restarting with new version...');
144
+ tui.info('');
145
+
146
+ // Spawn new process with same arguments
147
+ const proc = Bun.spawn([newBinaryPath, ...args], {
148
+ stdin: 'inherit',
149
+ stdout: 'inherit',
150
+ stderr: 'inherit',
151
+ });
152
+
153
+ // Wait for the new process to complete
154
+ await proc.exited;
155
+
156
+ // Exit with the same exit code as the new process
157
+ process.exit(proc.exitCode ?? 0);
158
+ } catch (error) {
159
+ // Upgrade failed - log and continue with original command
160
+ logger.error('Upgrade failed: %s', error instanceof Error ? error.message : 'Unknown error');
161
+ tui.warning('Continuing with current version...');
162
+ tui.info('');
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Check for updates and optionally prompt to upgrade
168
+ * Should be called early in the CLI initialization, before commands execute
169
+ */
170
+ export async function checkForUpdates(
171
+ config: Config | null,
172
+ logger: Logger,
173
+ options: {
174
+ json?: boolean;
175
+ quiet?: boolean;
176
+ validate?: boolean;
177
+ dryRun?: boolean;
178
+ skipVersionCheck?: boolean;
179
+ },
180
+ commandDef: CommandDefinition | undefined,
181
+ args: string[]
182
+ ): Promise<void> {
183
+ // Determine if we should skip the check
184
+ if (shouldSkipCheck(config, options, commandDef, args)) {
185
+ logger.trace('Skipping version check (disabled or not applicable)');
186
+ return;
187
+ }
188
+
189
+ // Check if enough time has passed
190
+ if (!shouldCheckNow(config)) {
191
+ logger.trace('Skipping version check (checked recently)');
192
+ return;
193
+ }
194
+
195
+ // Perform the actual version check
196
+ logger.trace('Checking for updates...');
197
+
198
+ try {
199
+ const currentVersion = getVersion();
200
+ const latestVersion = await fetchLatestVersion();
201
+
202
+ // Update the timestamp since we successfully checked
203
+ await updateCheckTimestamp(config, logger);
204
+
205
+ // Compare versions
206
+ const normalizedCurrent = currentVersion.replace(/^v/, '');
207
+ const normalizedLatest = latestVersion.replace(/^v/, '');
208
+
209
+ if (normalizedCurrent === normalizedLatest) {
210
+ logger.trace('Already on latest version: %s', currentVersion);
211
+ return;
212
+ }
213
+
214
+ // New version available - prompt user
215
+ const shouldUpgrade = await promptUpgrade(currentVersion, latestVersion);
216
+
217
+ if (!shouldUpgrade) {
218
+ // User declined - just continue
219
+ tui.info('You can upgrade later by running: agentuity upgrade');
220
+ tui.newline();
221
+ return;
222
+ }
223
+
224
+ // User wants to upgrade - perform it
225
+ await performUpgrade(logger);
226
+ } catch (error) {
227
+ // Non-fatal - if we can't fetch the latest version (network error, timeout, etc.),
228
+ // just log at debug level and continue without interrupting the user's command
229
+ logger.debug(
230
+ 'Version check failed: %s',
231
+ error instanceof Error ? error.message : 'Unknown error'
232
+ );
233
+ }
234
+ }