cli4ai 1.1.5 → 1.2.1

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 (113) hide show
  1. package/README.md +39 -0
  2. package/dist/bin.d.ts +6 -0
  3. package/dist/bin.js +105 -0
  4. package/dist/cli.d.ts +5 -0
  5. package/dist/cli.js +335 -0
  6. package/dist/commands/add.d.ts +11 -0
  7. package/dist/commands/add.js +459 -0
  8. package/dist/commands/browse.d.ts +4 -0
  9. package/dist/commands/browse.js +379 -0
  10. package/dist/commands/config.d.ts +10 -0
  11. package/dist/commands/config.js +121 -0
  12. package/dist/commands/info.d.ts +9 -0
  13. package/dist/commands/info.js +122 -0
  14. package/dist/commands/init.d.ts +10 -0
  15. package/dist/commands/init.js +458 -0
  16. package/dist/commands/list.d.ts +10 -0
  17. package/dist/commands/list.js +76 -0
  18. package/dist/commands/mcp-config.d.ts +10 -0
  19. package/dist/commands/mcp-config.js +49 -0
  20. package/dist/commands/remotes.d.ts +22 -0
  21. package/dist/commands/remotes.js +196 -0
  22. package/dist/commands/remove.d.ts +8 -0
  23. package/dist/commands/remove.js +61 -0
  24. package/dist/commands/routines.d.ts +29 -0
  25. package/dist/commands/routines.js +363 -0
  26. package/dist/commands/run.d.ts +12 -0
  27. package/dist/commands/run.js +104 -0
  28. package/dist/commands/scheduler.d.ts +27 -0
  29. package/dist/commands/scheduler.js +350 -0
  30. package/dist/commands/search.d.ts +9 -0
  31. package/dist/commands/search.js +159 -0
  32. package/dist/commands/secrets.d.ts +28 -0
  33. package/dist/commands/secrets.js +236 -0
  34. package/dist/commands/serve.d.ts +13 -0
  35. package/dist/commands/serve.js +49 -0
  36. package/dist/commands/start.d.ts +8 -0
  37. package/dist/commands/start.js +27 -0
  38. package/dist/commands/update.d.ts +17 -0
  39. package/dist/commands/update.js +210 -0
  40. package/dist/core/config.d.ts +91 -0
  41. package/dist/core/config.js +738 -0
  42. package/dist/core/execute.d.ts +51 -0
  43. package/dist/core/execute.js +475 -0
  44. package/dist/core/link.d.ts +39 -0
  45. package/dist/core/link.js +214 -0
  46. package/dist/core/lockfile.d.ts +63 -0
  47. package/dist/core/lockfile.js +140 -0
  48. package/dist/core/manifest.d.ts +96 -0
  49. package/dist/core/manifest.js +224 -0
  50. package/dist/core/registry.d.ts +74 -0
  51. package/dist/core/registry.js +116 -0
  52. package/dist/core/remote-client.d.ts +98 -0
  53. package/dist/core/remote-client.js +252 -0
  54. package/dist/core/remotes.d.ts +88 -0
  55. package/dist/core/remotes.js +206 -0
  56. package/dist/core/routine-engine.d.ts +124 -0
  57. package/dist/core/routine-engine.js +699 -0
  58. package/dist/core/routines.d.ts +36 -0
  59. package/dist/core/routines.js +132 -0
  60. package/dist/core/scheduler-daemon.d.ts +10 -0
  61. package/dist/core/scheduler-daemon.js +77 -0
  62. package/dist/core/scheduler.d.ts +131 -0
  63. package/dist/core/scheduler.js +492 -0
  64. package/dist/core/secrets.d.ts +48 -0
  65. package/dist/core/secrets.js +384 -0
  66. package/dist/lib/cli.d.ts +84 -0
  67. package/dist/lib/cli.js +216 -0
  68. package/dist/mcp/adapter.d.ts +35 -0
  69. package/dist/mcp/adapter.js +94 -0
  70. package/dist/mcp/config-gen.d.ts +31 -0
  71. package/dist/mcp/config-gen.js +75 -0
  72. package/dist/mcp/server.d.ts +41 -0
  73. package/dist/mcp/server.js +296 -0
  74. package/dist/server/service.d.ts +85 -0
  75. package/dist/server/service.js +304 -0
  76. package/package.json +6 -3
  77. package/src/bin.ts +0 -118
  78. package/src/cli.ts +0 -409
  79. package/src/commands/add.ts +0 -562
  80. package/src/commands/browse.ts +0 -449
  81. package/src/commands/config.ts +0 -154
  82. package/src/commands/info.ts +0 -102
  83. package/src/commands/init.ts +0 -514
  84. package/src/commands/list.ts +0 -72
  85. package/src/commands/mcp-config.ts +0 -69
  86. package/src/commands/remotes.ts +0 -253
  87. package/src/commands/remove.ts +0 -78
  88. package/src/commands/routines.ts +0 -427
  89. package/src/commands/run.ts +0 -127
  90. package/src/commands/scheduler.ts +0 -438
  91. package/src/commands/search.ts +0 -148
  92. package/src/commands/secrets.ts +0 -292
  93. package/src/commands/serve.ts +0 -66
  94. package/src/commands/start.ts +0 -40
  95. package/src/commands/update.ts +0 -252
  96. package/src/core/config.ts +0 -845
  97. package/src/core/execute.ts +0 -569
  98. package/src/core/link.ts +0 -246
  99. package/src/core/lockfile.ts +0 -187
  100. package/src/core/manifest.ts +0 -327
  101. package/src/core/registry.ts +0 -165
  102. package/src/core/remote-client.ts +0 -419
  103. package/src/core/remotes.ts +0 -268
  104. package/src/core/routine-engine.ts +0 -895
  105. package/src/core/routines.ts +0 -171
  106. package/src/core/scheduler-daemon.ts +0 -94
  107. package/src/core/scheduler.ts +0 -606
  108. package/src/core/secrets.ts +0 -430
  109. package/src/lib/cli.ts +0 -261
  110. package/src/mcp/adapter.ts +0 -131
  111. package/src/mcp/config-gen.ts +0 -106
  112. package/src/mcp/server.ts +0 -365
  113. package/src/server/service.ts +0 -434
@@ -1,292 +0,0 @@
1
- /**
2
- * cli4ai secrets - Manage secrets for CLI tools
3
- */
4
-
5
- import { createInterface } from 'readline';
6
- import { resolve } from 'path';
7
- import { existsSync, readFileSync } from 'fs';
8
- import { homedir } from 'os';
9
- import { output, outputError, log } from '../lib/cli.js';
10
-
11
- import {
12
- getSecret,
13
- setSecret,
14
- deleteSecret,
15
- listSecretKeys,
16
- getSecretSource,
17
- hasSecret
18
- } from '../core/secrets.js';
19
- import { findPackage } from '../core/config.js';
20
-
21
- /**
22
- * Expand ~ to home directory in paths
23
- */
24
- function expandTilde(path: string): string {
25
- if (path.startsWith('~/')) {
26
- return resolve(homedir(), path.slice(2));
27
- }
28
- if (path === '~') {
29
- return homedir();
30
- }
31
- return path;
32
- }
33
-
34
- interface SecretsOptions {
35
- package?: string;
36
- }
37
-
38
- /**
39
- * Prompt for input (hidden for secrets)
40
- */
41
- async function prompt(message: string, hidden = false): Promise<string> {
42
- const rl = createInterface({
43
- input: process.stdin,
44
- output: process.stderr
45
- });
46
-
47
- return new Promise((resolve) => {
48
- if (hidden && process.stdin.isTTY) {
49
- // Hide input for secrets
50
- process.stderr.write(message);
51
- let value = '';
52
-
53
- process.stdin.setRawMode(true);
54
- process.stdin.resume();
55
- process.stdin.setEncoding('utf8');
56
-
57
- const onData = (char: string) => {
58
- if (char === '\n' || char === '\r') {
59
- process.stdin.setRawMode(false);
60
- process.stdin.removeListener('data', onData);
61
- process.stderr.write('\n');
62
- rl.close();
63
- resolve(value);
64
- } else if (char === '\u0003') {
65
- // Ctrl+C
66
- process.exit(1);
67
- } else if (char === '\u007F') {
68
- // Backspace
69
- if (value.length > 0) {
70
- value = value.slice(0, -1);
71
- }
72
- } else {
73
- value += char;
74
- }
75
- };
76
-
77
- process.stdin.on('data', onData);
78
- } else {
79
- rl.question(message, (answer) => {
80
- rl.close();
81
- resolve(answer);
82
- });
83
- }
84
- });
85
- }
86
-
87
- /**
88
- * cli4ai secrets set <key> [value]
89
- */
90
- export async function secretsSetCommand(key: string, value?: string): Promise<void> {
91
- if (!key) {
92
- outputError('INVALID_INPUT', 'Secret key is required');
93
- }
94
-
95
- // Prompt for value if not provided
96
- if (!value) {
97
- value = await prompt(`Enter value for ${key}: `, true);
98
- }
99
-
100
- if (!value) {
101
- outputError('INVALID_INPUT', 'Secret value cannot be empty');
102
- }
103
-
104
- // Expand ~ to home directory for paths
105
- const expandedValue = expandTilde(value);
106
- setSecret(key, expandedValue);
107
- log(`✓ Secret '${key}' saved to vault`);
108
-
109
- output({ key, status: 'saved' });
110
- }
111
-
112
- /**
113
- * cli4ai secrets get <key>
114
- */
115
- export async function secretsGetCommand(key: string): Promise<void> {
116
- if (!key) {
117
- outputError('INVALID_INPUT', 'Secret key is required');
118
- }
119
-
120
- const value = getSecret(key);
121
- const source = getSecretSource(key);
122
-
123
- if (!value) {
124
- outputError('NOT_FOUND', `Secret '${key}' not found`, {
125
- hint: `Set it with: cli4ai secrets set ${key}`
126
- });
127
- }
128
-
129
- // Output value to stdout (for piping)
130
- console.log(value);
131
- }
132
-
133
- /**
134
- * cli4ai secrets list
135
- */
136
- export async function secretsListCommand(options: SecretsOptions): Promise<void> {
137
- const vaultKeys = listSecretKeys();
138
-
139
- // If package specified, show status of required secrets
140
- if (options.package) {
141
- const pkg = findPackage(options.package, process.cwd());
142
- if (!pkg) {
143
- outputError('NOT_FOUND', `Package not found: ${options.package}`);
144
- }
145
-
146
- const cli4aiPath = resolve(pkg.path, 'cli4ai.json');
147
- if (!existsSync(cli4aiPath)) {
148
- outputError('NOT_FOUND', 'Package has no cli4ai.json');
149
- }
150
-
151
- const manifest = JSON.parse(readFileSync(cli4aiPath, 'utf-8'));
152
- const envDefs = manifest.env || {};
153
-
154
- log(`\nSecrets for ${options.package}:\n`);
155
-
156
- const secrets: Array<{ key: string; required: boolean; source: string; description?: string }> = [];
157
-
158
- for (const [key, def] of Object.entries(envDefs) as [string, { required?: boolean; description?: string }][]) {
159
- const source = getSecretSource(key);
160
- secrets.push({
161
- key,
162
- required: def.required ?? false,
163
- source,
164
- description: def.description
165
- });
166
-
167
- const status = source === 'env' ? '✓ (env)' :
168
- source === 'vault' ? '✓ (vault)' :
169
- def.required ? '✗ missing' : '○ optional';
170
-
171
- log(` ${status.padEnd(12)} ${key}`);
172
- if (def.description) {
173
- log(` ${def.description}`);
174
- }
175
- }
176
-
177
- log('');
178
- output({ package: options.package, secrets });
179
- return;
180
- }
181
-
182
- // List all secrets in vault
183
- if (vaultKeys.length === 0) {
184
- log('No secrets stored in vault.\n');
185
- log('Set a secret: cli4ai secrets set SLACK_BOT_TOKEN');
186
- log('From env var: Secrets are also read from environment variables\n');
187
- output({ secrets: [] });
188
- return;
189
- }
190
-
191
- log('\nSecrets in vault:\n');
192
-
193
- const secrets = vaultKeys.map(key => {
194
- const source = getSecretSource(key);
195
- const overridden = source === 'env';
196
- log(` ${key}${overridden ? ' (overridden by env)' : ''}`);
197
- return { key, overriddenByEnv: overridden };
198
- });
199
-
200
- log('\n');
201
- output({ secrets });
202
- }
203
-
204
- /**
205
- * cli4ai secrets delete <key>
206
- */
207
- export async function secretsDeleteCommand(key: string): Promise<void> {
208
- if (!key) {
209
- outputError('INVALID_INPUT', 'Secret key is required');
210
- }
211
-
212
- const deleted = deleteSecret(key);
213
-
214
- if (!deleted) {
215
- outputError('NOT_FOUND', `Secret '${key}' not found in vault`);
216
- }
217
-
218
- log(`✓ Secret '${key}' deleted from vault`);
219
- output({ key, status: 'deleted' });
220
- }
221
-
222
- /**
223
- * cli4ai secrets init [package]
224
- * Interactive setup for a package's required secrets
225
- */
226
- export async function secretsInitCommand(packageName?: string, options?: SecretsOptions): Promise<void> {
227
- const pkgName = packageName || options?.package;
228
-
229
- if (!pkgName) {
230
- outputError('INVALID_INPUT', 'Package name required', {
231
- hint: 'Usage: cli4ai secrets init <package>'
232
- });
233
- }
234
-
235
- const pkg = findPackage(pkgName, process.cwd());
236
- if (!pkg) {
237
- outputError('NOT_FOUND', `Package not found: ${pkgName}`);
238
- }
239
-
240
- const cli4aiPath = resolve(pkg.path, 'cli4ai.json');
241
- if (!existsSync(cli4aiPath)) {
242
- outputError('NOT_FOUND', 'Package has no cli4ai.json');
243
- }
244
-
245
- const manifest = JSON.parse(readFileSync(cli4aiPath, 'utf-8'));
246
- const envDefs = manifest.env || {};
247
-
248
- const required = Object.entries(envDefs)
249
- .filter(([_, def]) => (def as { required?: boolean }).required)
250
- .map(([key, def]) => ({ key, ...(def as { description?: string }) }));
251
-
252
- if (required.length === 0) {
253
- log(`\n${pkgName} has no required secrets.\n`);
254
- output({ package: pkgName, configured: [] });
255
- return;
256
- }
257
-
258
- log(`\nConfiguring secrets for ${pkgName}:\n`);
259
-
260
- const configured: string[] = [];
261
- const skipped: string[] = [];
262
-
263
- for (const { key, description } of required) {
264
- const source = getSecretSource(key);
265
-
266
- if (source !== 'missing') {
267
- log(` ✓ ${key} (already set via ${source})`);
268
- skipped.push(key);
269
- continue;
270
- }
271
-
272
- if (description) {
273
- log(`\n ${key}: ${description}`);
274
- }
275
-
276
- const value = await prompt(` Enter ${key}: `, true);
277
-
278
- if (value) {
279
- // Expand ~ to home directory for paths
280
- const expandedValue = expandTilde(value);
281
- setSecret(key, expandedValue);
282
- log(` ✓ ${key} saved`);
283
- configured.push(key);
284
- } else {
285
- log(` ○ ${key} skipped`);
286
- skipped.push(key);
287
- }
288
- }
289
-
290
- log('\n');
291
- output({ package: pkgName, configured, skipped });
292
- }
@@ -1,66 +0,0 @@
1
- /**
2
- * Serve command - Start cli4ai as a remote service.
3
- *
4
- * Allows other cli4ai instances to execute tools on this machine.
5
- */
6
-
7
- import { output, outputError, log } from '../lib/cli.js';
8
- import { startService, type StartServiceOptions } from '../server/service.js';
9
- import type { ScopeLevel } from '../core/execute.js';
10
-
11
- export interface ServeCommandOptions {
12
- port?: string;
13
- host?: string;
14
- apiKey?: string;
15
- scope?: string;
16
- cwd?: string;
17
- }
18
-
19
- export async function serveCommand(options: ServeCommandOptions): Promise<void> {
20
- const serviceOptions: StartServiceOptions = {
21
- cwd: options.cwd ?? process.cwd()
22
- };
23
-
24
- // Parse port
25
- if (options.port) {
26
- const port = parseInt(options.port, 10);
27
- if (isNaN(port) || port < 1 || port > 65535) {
28
- outputError('INVALID_INPUT', 'Port must be a number between 1 and 65535');
29
- }
30
- serviceOptions.port = port;
31
- }
32
-
33
- // Parse host
34
- if (options.host) {
35
- serviceOptions.host = options.host;
36
- }
37
-
38
- // API key
39
- if (options.apiKey) {
40
- serviceOptions.apiKey = options.apiKey;
41
- }
42
-
43
- // Parse allowed scopes
44
- if (options.scope) {
45
- const scopes = options.scope.split(',').map(s => s.trim()) as ScopeLevel[];
46
- const validScopes: ScopeLevel[] = ['read', 'write', 'full'];
47
-
48
- for (const scope of scopes) {
49
- if (!validScopes.includes(scope)) {
50
- outputError('INVALID_INPUT', `Invalid scope "${scope}". Must be one of: read, write, full`);
51
- }
52
- }
53
-
54
- serviceOptions.allowedScopes = scopes;
55
- }
56
-
57
- try {
58
- await startService(serviceOptions);
59
- output({ status: 'stopped' });
60
- } catch (err) {
61
- if (err instanceof Error) {
62
- outputError('API_ERROR', err.message);
63
- }
64
- throw err;
65
- }
66
- }
@@ -1,40 +0,0 @@
1
- /**
2
- * cli4ai start - Start MCP server for a package
3
- */
4
-
5
- import { outputError } from '../lib/cli.js';
6
- import { findPackage } from '../core/config.js';
7
- import { loadManifest } from '../core/manifest.js';
8
- import { startMcpServer } from '../mcp/server.js';
9
-
10
- interface StartOptions {
11
- stdio?: boolean;
12
- }
13
-
14
- export async function startCommand(
15
- packageName: string,
16
- options: StartOptions
17
- ): Promise<void> {
18
- const cwd = process.cwd();
19
-
20
- // Find the package
21
- const pkg = findPackage(packageName, cwd);
22
- if (!pkg) {
23
- outputError('NOT_FOUND', `Package not found: ${packageName}`, {
24
- hint: 'Install the package first with "cli4ai add <package>"'
25
- });
26
- }
27
-
28
- // Load manifest
29
- const manifest = loadManifest(pkg!.path);
30
-
31
- // Check if MCP is enabled
32
- if (!manifest.mcp?.enabled) {
33
- outputError('INVALID_INPUT', `Package ${packageName} does not have MCP enabled`, {
34
- hint: 'Add "mcp": { "enabled": true } to cli4ai.json'
35
- });
36
- }
37
-
38
- // Start MCP server (stdio mode is default/only mode for now)
39
- await startMcpServer(manifest, pkg!.path);
40
- }
@@ -1,252 +0,0 @@
1
- /**
2
- * cli4ai update - Update installed packages and cli4ai itself
3
- */
4
-
5
- import { spawn } from 'child_process';
6
- import { log, outputError } from '../lib/cli.js';
7
- import {
8
- getNpmGlobalPackages,
9
- getGlobalPackages,
10
- getLocalPackages
11
- } from '../core/config.js';
12
-
13
- interface UpdateOptions {
14
- self?: boolean;
15
- all?: boolean;
16
- }
17
-
18
- interface UpdateResult {
19
- name: string;
20
- from: string;
21
- to: string;
22
- status: 'updated' | 'current' | 'failed';
23
- }
24
-
25
- // ANSI colors
26
- const RESET = '\x1B[0m';
27
- const BOLD = '\x1B[1m';
28
- const DIM = '\x1B[2m';
29
- const CYAN = '\x1B[36m';
30
- const GREEN = '\x1B[32m';
31
- const YELLOW = '\x1B[33m';
32
-
33
- /**
34
- * Get latest version from npm registry API (fast!)
35
- */
36
- async function getLatestVersion(packageName: string): Promise<string | null> {
37
- try {
38
- // URL-encode the package name to handle scoped packages like @cli4ai/foo
39
- // npm registry expects: @scope%2Fpackage (slash encoded, @ not encoded)
40
- const encodedName = packageName.replace('/', '%2F');
41
- const response = await fetch(`https://registry.npmjs.org/${encodedName}/latest`, {
42
- headers: { 'Accept': 'application/json' }
43
- });
44
- if (!response.ok) return null;
45
- const data = await response.json() as { version: string };
46
- return data.version;
47
- } catch {
48
- return null;
49
- }
50
- }
51
-
52
- /**
53
- * Get latest versions for multiple packages in parallel
54
- */
55
- async function getLatestVersions(packageNames: string[]): Promise<Map<string, string | null>> {
56
- const results = new Map<string, string | null>();
57
- const promises = packageNames.map(async (name) => {
58
- const version = await getLatestVersion(name);
59
- results.set(name, version);
60
- });
61
- await Promise.all(promises);
62
- return results;
63
- }
64
-
65
- /**
66
- * Update a single package using cli4ai add -g (reinstalls to ~/.cli4ai/packages/)
67
- */
68
- async function updateCli4aiPackage(packageName: string): Promise<boolean> {
69
- return new Promise((resolve) => {
70
- // Use cli4ai add -g to reinstall the package (same location as original install)
71
- const proc = spawn('cli4ai', ['add', '-g', packageName, '-y'], {
72
- stdio: 'pipe'
73
- });
74
- proc.on('close', (code) => {
75
- resolve(code === 0);
76
- });
77
- proc.on('error', () => {
78
- resolve(false);
79
- });
80
- });
81
- }
82
-
83
- /**
84
- * Update cli4ai itself via npm
85
- */
86
- async function updateNpmPackage(packageName: string): Promise<boolean> {
87
- return new Promise((resolve) => {
88
- const proc = spawn('npm', ['install', '-g', `${packageName}@latest`], {
89
- stdio: 'pipe'
90
- });
91
- proc.on('close', (code) => {
92
- resolve(code === 0);
93
- });
94
- proc.on('error', () => {
95
- resolve(false);
96
- });
97
- });
98
- }
99
-
100
- /**
101
- * Check for cli4ai framework updates
102
- */
103
- export async function checkForUpdates(): Promise<{ hasUpdate: boolean; current: string; latest: string }> {
104
- const { VERSION } = await import('../lib/cli.js');
105
- const latest = await getLatestVersion('cli4ai');
106
-
107
- if (!latest) {
108
- return { hasUpdate: false, current: VERSION, latest: VERSION };
109
- }
110
-
111
- const hasUpdate = latest !== VERSION;
112
- return { hasUpdate, current: VERSION, latest };
113
- }
114
-
115
- /**
116
- * Get all updateable packages
117
- */
118
- function getUpdateablePackages(): Array<{ name: string; npmName: string; version: string }> {
119
- const packages: Array<{ name: string; npmName: string; version: string }> = [];
120
- const seen = new Set<string>();
121
-
122
- // Get packages from ~/.cli4ai/packages/ (where cli4ai add -g installs)
123
- for (const pkg of getGlobalPackages()) {
124
- if (!seen.has(pkg.name)) {
125
- seen.add(pkg.name);
126
- packages.push({
127
- name: pkg.name,
128
- npmName: `@cli4ai/${pkg.name}`,
129
- version: pkg.version
130
- });
131
- }
132
- }
133
-
134
- // Also check npm global @cli4ai packages (for packages installed via npm directly)
135
- for (const pkg of getNpmGlobalPackages()) {
136
- if (!seen.has(pkg.name)) {
137
- seen.add(pkg.name);
138
- packages.push({
139
- name: pkg.name,
140
- npmName: `@cli4ai/${pkg.name}`,
141
- version: pkg.version
142
- });
143
- }
144
- }
145
-
146
- return packages;
147
- }
148
-
149
- export async function updateCommand(options: UpdateOptions): Promise<void> {
150
- const results: UpdateResult[] = [];
151
-
152
- // Update cli4ai itself first if --self or --all
153
- if (options.self || options.all) {
154
- log(`\n${BOLD}Checking cli4ai framework...${RESET}`);
155
-
156
- const { hasUpdate, current, latest } = await checkForUpdates();
157
-
158
- if (hasUpdate) {
159
- log(` ${CYAN}↑${RESET} cli4ai ${DIM}${current}${RESET} → ${GREEN}${latest}${RESET}`);
160
- const success = await updateNpmPackage('cli4ai');
161
-
162
- if (success) {
163
- log(` ${GREEN}✓${RESET} cli4ai updated\n`);
164
- results.push({ name: 'cli4ai', from: current, to: latest, status: 'updated' });
165
- } else {
166
- log(` ${YELLOW}✗${RESET} Failed to update cli4ai\n`);
167
- results.push({ name: 'cli4ai', from: current, to: latest, status: 'failed' });
168
- }
169
- } else {
170
- log(` ${GREEN}✓${RESET} cli4ai is up to date (${current})\n`);
171
- results.push({ name: 'cli4ai', from: current, to: current, status: 'current' });
172
- }
173
-
174
- if (options.self && !options.all) {
175
- return;
176
- }
177
- }
178
-
179
- // Get all installed packages
180
- const packages = getUpdateablePackages();
181
-
182
- if (packages.length === 0) {
183
- log(`${DIM}No @cli4ai packages installed.${RESET}`);
184
- log(`${DIM}Run ${RESET}cli4ai browse${DIM} to install packages.${RESET}\n`);
185
- return;
186
- }
187
-
188
- log(`${BOLD}Checking ${packages.length} package${packages.length !== 1 ? 's' : ''} for updates...${RESET}\n`);
189
-
190
- // Fetch all versions in parallel (fast!)
191
- const npmNames = packages.map(p => p.npmName);
192
- const latestVersions = await getLatestVersions(npmNames);
193
-
194
- // Separate packages that need updates vs current
195
- const toUpdate: Array<{ name: string; npmName: string; from: string; to: string }> = [];
196
-
197
- for (const pkg of packages) {
198
- const latest = latestVersions.get(pkg.npmName);
199
-
200
- if (!latest) {
201
- log(` ${DIM}${pkg.name}${RESET} - ${YELLOW}could not check${RESET}`);
202
- continue;
203
- }
204
-
205
- if (latest === pkg.version) {
206
- log(` ${GREEN}✓${RESET} ${pkg.name} ${DIM}${pkg.version}${RESET}`);
207
- results.push({ name: pkg.name, from: pkg.version, to: pkg.version, status: 'current' });
208
- } else {
209
- log(` ${CYAN}↑${RESET} ${pkg.name} ${DIM}${pkg.version}${RESET} → ${GREEN}${latest}${RESET}`);
210
- toUpdate.push({ name: pkg.name, npmName: pkg.npmName, from: pkg.version, to: latest });
211
- }
212
- }
213
-
214
- // Update all packages in parallel
215
- if (toUpdate.length > 0) {
216
- log(`\n${BOLD}Updating ${toUpdate.length} package${toUpdate.length !== 1 ? 's' : ''}...${RESET}`);
217
-
218
- const updateResults: Array<(typeof toUpdate)[number] & { success: boolean }> = [];
219
- for (const pkg of toUpdate) {
220
- // Use cli4ai add -g to update packages in ~/.cli4ai/packages/
221
- const success = await updateCli4aiPackage(pkg.name);
222
- updateResults.push({ ...pkg, success });
223
- }
224
-
225
- log('');
226
- for (const result of updateResults) {
227
- if (result.success) {
228
- log(` ${GREEN}✓${RESET} ${result.name} updated`);
229
- results.push({ name: result.name, from: result.from, to: result.to, status: 'updated' });
230
- } else {
231
- log(` ${YELLOW}✗${RESET} ${result.name} failed`);
232
- results.push({ name: result.name, from: result.from, to: result.to, status: 'failed' });
233
- }
234
- }
235
- }
236
-
237
- // Summary
238
- const updated = results.filter(r => r.status === 'updated').length;
239
- const failed = results.filter(r => r.status === 'failed').length;
240
-
241
- log('');
242
- if (updated > 0) {
243
- log(`${GREEN}${updated} package${updated !== 1 ? 's' : ''} updated${RESET}`);
244
- }
245
- if (failed > 0) {
246
- log(`${YELLOW}${failed} package${failed !== 1 ? 's' : ''} failed to update${RESET}`);
247
- }
248
- if (updated === 0 && failed === 0) {
249
- log(`${GREEN}All packages are up to date${RESET}`);
250
- }
251
- log('');
252
- }