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
@@ -0,0 +1,236 @@
1
+ /**
2
+ * cli4ai secrets - Manage secrets for CLI tools
3
+ */
4
+ import { createInterface } from 'readline';
5
+ import { resolve } from 'path';
6
+ import { existsSync, readFileSync } from 'fs';
7
+ import { homedir } from 'os';
8
+ import { output, outputError, log } from '../lib/cli.js';
9
+ import { getSecret, setSecret, deleteSecret, listSecretKeys, getSecretSource } from '../core/secrets.js';
10
+ import { findPackage } from '../core/config.js';
11
+ /**
12
+ * Expand ~ to home directory in paths
13
+ */
14
+ function expandTilde(path) {
15
+ if (path.startsWith('~/')) {
16
+ return resolve(homedir(), path.slice(2));
17
+ }
18
+ if (path === '~') {
19
+ return homedir();
20
+ }
21
+ return path;
22
+ }
23
+ /**
24
+ * Prompt for input (hidden for secrets)
25
+ */
26
+ async function prompt(message, hidden = false) {
27
+ const rl = createInterface({
28
+ input: process.stdin,
29
+ output: process.stderr
30
+ });
31
+ return new Promise((resolve) => {
32
+ if (hidden && process.stdin.isTTY) {
33
+ // Hide input for secrets
34
+ process.stderr.write(message);
35
+ let value = '';
36
+ process.stdin.setRawMode(true);
37
+ process.stdin.resume();
38
+ process.stdin.setEncoding('utf8');
39
+ const onData = (char) => {
40
+ if (char === '\n' || char === '\r') {
41
+ process.stdin.setRawMode(false);
42
+ process.stdin.removeListener('data', onData);
43
+ process.stderr.write('\n');
44
+ rl.close();
45
+ resolve(value);
46
+ }
47
+ else if (char === '\u0003') {
48
+ // Ctrl+C
49
+ process.exit(1);
50
+ }
51
+ else if (char === '\u007F') {
52
+ // Backspace
53
+ if (value.length > 0) {
54
+ value = value.slice(0, -1);
55
+ }
56
+ }
57
+ else {
58
+ value += char;
59
+ }
60
+ };
61
+ process.stdin.on('data', onData);
62
+ }
63
+ else {
64
+ rl.question(message, (answer) => {
65
+ rl.close();
66
+ resolve(answer);
67
+ });
68
+ }
69
+ });
70
+ }
71
+ /**
72
+ * cli4ai secrets set <key> [value]
73
+ */
74
+ export async function secretsSetCommand(key, value) {
75
+ if (!key) {
76
+ outputError('INVALID_INPUT', 'Secret key is required');
77
+ }
78
+ // Prompt for value if not provided
79
+ if (!value) {
80
+ value = await prompt(`Enter value for ${key}: `, true);
81
+ }
82
+ if (!value) {
83
+ outputError('INVALID_INPUT', 'Secret value cannot be empty');
84
+ }
85
+ // Expand ~ to home directory for paths
86
+ const expandedValue = expandTilde(value);
87
+ setSecret(key, expandedValue);
88
+ log(`✓ Secret '${key}' saved to vault`);
89
+ output({ key, status: 'saved' });
90
+ }
91
+ /**
92
+ * cli4ai secrets get <key>
93
+ */
94
+ export async function secretsGetCommand(key) {
95
+ if (!key) {
96
+ outputError('INVALID_INPUT', 'Secret key is required');
97
+ }
98
+ const value = getSecret(key);
99
+ const source = getSecretSource(key);
100
+ if (!value) {
101
+ outputError('NOT_FOUND', `Secret '${key}' not found`, {
102
+ hint: `Set it with: cli4ai secrets set ${key}`
103
+ });
104
+ }
105
+ // Output value to stdout (for piping)
106
+ console.log(value);
107
+ }
108
+ /**
109
+ * cli4ai secrets list
110
+ */
111
+ export async function secretsListCommand(options) {
112
+ const vaultKeys = listSecretKeys();
113
+ // If package specified, show status of required secrets
114
+ if (options.package) {
115
+ const pkg = findPackage(options.package, process.cwd());
116
+ if (!pkg) {
117
+ outputError('NOT_FOUND', `Package not found: ${options.package}`);
118
+ }
119
+ const cli4aiPath = resolve(pkg.path, 'cli4ai.json');
120
+ if (!existsSync(cli4aiPath)) {
121
+ outputError('NOT_FOUND', 'Package has no cli4ai.json');
122
+ }
123
+ const manifest = JSON.parse(readFileSync(cli4aiPath, 'utf-8'));
124
+ const envDefs = manifest.env || {};
125
+ log(`\nSecrets for ${options.package}:\n`);
126
+ const secrets = [];
127
+ for (const [key, def] of Object.entries(envDefs)) {
128
+ const source = getSecretSource(key);
129
+ secrets.push({
130
+ key,
131
+ required: def.required ?? false,
132
+ source,
133
+ description: def.description
134
+ });
135
+ const status = source === 'env' ? '✓ (env)' :
136
+ source === 'vault' ? '✓ (vault)' :
137
+ def.required ? '✗ missing' : '○ optional';
138
+ log(` ${status.padEnd(12)} ${key}`);
139
+ if (def.description) {
140
+ log(` ${def.description}`);
141
+ }
142
+ }
143
+ log('');
144
+ output({ package: options.package, secrets });
145
+ return;
146
+ }
147
+ // List all secrets in vault
148
+ if (vaultKeys.length === 0) {
149
+ log('No secrets stored in vault.\n');
150
+ log('Set a secret: cli4ai secrets set SLACK_BOT_TOKEN');
151
+ log('From env var: Secrets are also read from environment variables\n');
152
+ output({ secrets: [] });
153
+ return;
154
+ }
155
+ log('\nSecrets in vault:\n');
156
+ const secrets = vaultKeys.map(key => {
157
+ const source = getSecretSource(key);
158
+ const overridden = source === 'env';
159
+ log(` ${key}${overridden ? ' (overridden by env)' : ''}`);
160
+ return { key, overriddenByEnv: overridden };
161
+ });
162
+ log('\n');
163
+ output({ secrets });
164
+ }
165
+ /**
166
+ * cli4ai secrets delete <key>
167
+ */
168
+ export async function secretsDeleteCommand(key) {
169
+ if (!key) {
170
+ outputError('INVALID_INPUT', 'Secret key is required');
171
+ }
172
+ const deleted = deleteSecret(key);
173
+ if (!deleted) {
174
+ outputError('NOT_FOUND', `Secret '${key}' not found in vault`);
175
+ }
176
+ log(`✓ Secret '${key}' deleted from vault`);
177
+ output({ key, status: 'deleted' });
178
+ }
179
+ /**
180
+ * cli4ai secrets init [package]
181
+ * Interactive setup for a package's required secrets
182
+ */
183
+ export async function secretsInitCommand(packageName, options) {
184
+ const pkgName = packageName || options?.package;
185
+ if (!pkgName) {
186
+ outputError('INVALID_INPUT', 'Package name required', {
187
+ hint: 'Usage: cli4ai secrets init <package>'
188
+ });
189
+ }
190
+ const pkg = findPackage(pkgName, process.cwd());
191
+ if (!pkg) {
192
+ outputError('NOT_FOUND', `Package not found: ${pkgName}`);
193
+ }
194
+ const cli4aiPath = resolve(pkg.path, 'cli4ai.json');
195
+ if (!existsSync(cli4aiPath)) {
196
+ outputError('NOT_FOUND', 'Package has no cli4ai.json');
197
+ }
198
+ const manifest = JSON.parse(readFileSync(cli4aiPath, 'utf-8'));
199
+ const envDefs = manifest.env || {};
200
+ const required = Object.entries(envDefs)
201
+ .filter(([_, def]) => def.required)
202
+ .map(([key, def]) => ({ key, ...def }));
203
+ if (required.length === 0) {
204
+ log(`\n${pkgName} has no required secrets.\n`);
205
+ output({ package: pkgName, configured: [] });
206
+ return;
207
+ }
208
+ log(`\nConfiguring secrets for ${pkgName}:\n`);
209
+ const configured = [];
210
+ const skipped = [];
211
+ for (const { key, description } of required) {
212
+ const source = getSecretSource(key);
213
+ if (source !== 'missing') {
214
+ log(` ✓ ${key} (already set via ${source})`);
215
+ skipped.push(key);
216
+ continue;
217
+ }
218
+ if (description) {
219
+ log(`\n ${key}: ${description}`);
220
+ }
221
+ const value = await prompt(` Enter ${key}: `, true);
222
+ if (value) {
223
+ // Expand ~ to home directory for paths
224
+ const expandedValue = expandTilde(value);
225
+ setSecret(key, expandedValue);
226
+ log(` ✓ ${key} saved`);
227
+ configured.push(key);
228
+ }
229
+ else {
230
+ log(` ○ ${key} skipped`);
231
+ skipped.push(key);
232
+ }
233
+ }
234
+ log('\n');
235
+ output({ package: pkgName, configured, skipped });
236
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Serve command - Start cli4ai as a remote service.
3
+ *
4
+ * Allows other cli4ai instances to execute tools on this machine.
5
+ */
6
+ export interface ServeCommandOptions {
7
+ port?: string;
8
+ host?: string;
9
+ apiKey?: string;
10
+ scope?: string;
11
+ cwd?: string;
12
+ }
13
+ export declare function serveCommand(options: ServeCommandOptions): Promise<void>;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Serve command - Start cli4ai as a remote service.
3
+ *
4
+ * Allows other cli4ai instances to execute tools on this machine.
5
+ */
6
+ import { output, outputError } from '../lib/cli.js';
7
+ import { startService } from '../server/service.js';
8
+ export async function serveCommand(options) {
9
+ const serviceOptions = {
10
+ cwd: options.cwd ?? process.cwd()
11
+ };
12
+ // Parse port
13
+ if (options.port) {
14
+ const port = parseInt(options.port, 10);
15
+ if (isNaN(port) || port < 1 || port > 65535) {
16
+ outputError('INVALID_INPUT', 'Port must be a number between 1 and 65535');
17
+ }
18
+ serviceOptions.port = port;
19
+ }
20
+ // Parse host
21
+ if (options.host) {
22
+ serviceOptions.host = options.host;
23
+ }
24
+ // API key
25
+ if (options.apiKey) {
26
+ serviceOptions.apiKey = options.apiKey;
27
+ }
28
+ // Parse allowed scopes
29
+ if (options.scope) {
30
+ const scopes = options.scope.split(',').map(s => s.trim());
31
+ const validScopes = ['read', 'write', 'full'];
32
+ for (const scope of scopes) {
33
+ if (!validScopes.includes(scope)) {
34
+ outputError('INVALID_INPUT', `Invalid scope "${scope}". Must be one of: read, write, full`);
35
+ }
36
+ }
37
+ serviceOptions.allowedScopes = scopes;
38
+ }
39
+ try {
40
+ await startService(serviceOptions);
41
+ output({ status: 'stopped' });
42
+ }
43
+ catch (err) {
44
+ if (err instanceof Error) {
45
+ outputError('API_ERROR', err.message);
46
+ }
47
+ throw err;
48
+ }
49
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * cli4ai start - Start MCP server for a package
3
+ */
4
+ interface StartOptions {
5
+ stdio?: boolean;
6
+ }
7
+ export declare function startCommand(packageName: string, options: StartOptions): Promise<void>;
8
+ export {};
@@ -0,0 +1,27 @@
1
+ /**
2
+ * cli4ai start - Start MCP server for a package
3
+ */
4
+ import { outputError } from '../lib/cli.js';
5
+ import { findPackage } from '../core/config.js';
6
+ import { loadManifest } from '../core/manifest.js';
7
+ import { startMcpServer } from '../mcp/server.js';
8
+ export async function startCommand(packageName, options) {
9
+ const cwd = process.cwd();
10
+ // Find the package
11
+ const pkg = findPackage(packageName, cwd);
12
+ if (!pkg) {
13
+ outputError('NOT_FOUND', `Package not found: ${packageName}`, {
14
+ hint: 'Install the package first with "cli4ai add <package>"'
15
+ });
16
+ }
17
+ // Load manifest
18
+ const manifest = loadManifest(pkg.path);
19
+ // Check if MCP is enabled
20
+ if (!manifest.mcp?.enabled) {
21
+ outputError('INVALID_INPUT', `Package ${packageName} does not have MCP enabled`, {
22
+ hint: 'Add "mcp": { "enabled": true } to cli4ai.json'
23
+ });
24
+ }
25
+ // Start MCP server (stdio mode is default/only mode for now)
26
+ await startMcpServer(manifest, pkg.path);
27
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * cli4ai update - Update installed packages and cli4ai itself
3
+ */
4
+ interface UpdateOptions {
5
+ self?: boolean;
6
+ all?: boolean;
7
+ }
8
+ /**
9
+ * Check for cli4ai framework updates
10
+ */
11
+ export declare function checkForUpdates(): Promise<{
12
+ hasUpdate: boolean;
13
+ current: string;
14
+ latest: string;
15
+ }>;
16
+ export declare function updateCommand(options: UpdateOptions): Promise<void>;
17
+ export {};
@@ -0,0 +1,210 @@
1
+ /**
2
+ * cli4ai update - Update installed packages and cli4ai itself
3
+ */
4
+ import { spawn } from 'child_process';
5
+ import { log } from '../lib/cli.js';
6
+ import { getNpmGlobalPackages, getGlobalPackages } from '../core/config.js';
7
+ // ANSI colors
8
+ const RESET = '\x1B[0m';
9
+ const BOLD = '\x1B[1m';
10
+ const DIM = '\x1B[2m';
11
+ const CYAN = '\x1B[36m';
12
+ const GREEN = '\x1B[32m';
13
+ const YELLOW = '\x1B[33m';
14
+ /**
15
+ * Get latest version from npm registry API (fast!)
16
+ */
17
+ async function getLatestVersion(packageName) {
18
+ try {
19
+ // URL-encode the package name to handle scoped packages like @cli4ai/foo
20
+ // npm registry expects: @scope%2Fpackage (slash encoded, @ not encoded)
21
+ const encodedName = packageName.replace('/', '%2F');
22
+ const response = await fetch(`https://registry.npmjs.org/${encodedName}/latest`, {
23
+ headers: { 'Accept': 'application/json' }
24
+ });
25
+ if (!response.ok)
26
+ return null;
27
+ const data = await response.json();
28
+ return data.version;
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ }
34
+ /**
35
+ * Get latest versions for multiple packages in parallel
36
+ */
37
+ async function getLatestVersions(packageNames) {
38
+ const results = new Map();
39
+ const promises = packageNames.map(async (name) => {
40
+ const version = await getLatestVersion(name);
41
+ results.set(name, version);
42
+ });
43
+ await Promise.all(promises);
44
+ return results;
45
+ }
46
+ /**
47
+ * Update a single package using cli4ai add -g (reinstalls to ~/.cli4ai/packages/)
48
+ */
49
+ async function updateCli4aiPackage(packageName) {
50
+ return new Promise((resolve) => {
51
+ // Use cli4ai add -g to reinstall the package (same location as original install)
52
+ const proc = spawn('cli4ai', ['add', '-g', packageName, '-y'], {
53
+ stdio: 'pipe'
54
+ });
55
+ proc.on('close', (code) => {
56
+ resolve(code === 0);
57
+ });
58
+ proc.on('error', () => {
59
+ resolve(false);
60
+ });
61
+ });
62
+ }
63
+ /**
64
+ * Update cli4ai itself via npm
65
+ */
66
+ async function updateNpmPackage(packageName) {
67
+ return new Promise((resolve) => {
68
+ const proc = spawn('npm', ['install', '-g', `${packageName}@latest`], {
69
+ stdio: 'pipe'
70
+ });
71
+ proc.on('close', (code) => {
72
+ resolve(code === 0);
73
+ });
74
+ proc.on('error', () => {
75
+ resolve(false);
76
+ });
77
+ });
78
+ }
79
+ /**
80
+ * Check for cli4ai framework updates
81
+ */
82
+ export async function checkForUpdates() {
83
+ const { VERSION } = await import('../lib/cli.js');
84
+ const latest = await getLatestVersion('cli4ai');
85
+ if (!latest) {
86
+ return { hasUpdate: false, current: VERSION, latest: VERSION };
87
+ }
88
+ const hasUpdate = latest !== VERSION;
89
+ return { hasUpdate, current: VERSION, latest };
90
+ }
91
+ /**
92
+ * Get all updateable packages
93
+ */
94
+ function getUpdateablePackages() {
95
+ const packages = [];
96
+ const seen = new Set();
97
+ // Get packages from ~/.cli4ai/packages/ (where cli4ai add -g installs)
98
+ for (const pkg of getGlobalPackages()) {
99
+ if (!seen.has(pkg.name)) {
100
+ seen.add(pkg.name);
101
+ packages.push({
102
+ name: pkg.name,
103
+ npmName: `@cli4ai/${pkg.name}`,
104
+ version: pkg.version
105
+ });
106
+ }
107
+ }
108
+ // Also check npm global @cli4ai packages (for packages installed via npm directly)
109
+ for (const pkg of getNpmGlobalPackages()) {
110
+ if (!seen.has(pkg.name)) {
111
+ seen.add(pkg.name);
112
+ packages.push({
113
+ name: pkg.name,
114
+ npmName: `@cli4ai/${pkg.name}`,
115
+ version: pkg.version
116
+ });
117
+ }
118
+ }
119
+ return packages;
120
+ }
121
+ export async function updateCommand(options) {
122
+ const results = [];
123
+ // Update cli4ai itself first if --self or --all
124
+ if (options.self || options.all) {
125
+ log(`\n${BOLD}Checking cli4ai framework...${RESET}`);
126
+ const { hasUpdate, current, latest } = await checkForUpdates();
127
+ if (hasUpdate) {
128
+ log(` ${CYAN}↑${RESET} cli4ai ${DIM}${current}${RESET} → ${GREEN}${latest}${RESET}`);
129
+ const success = await updateNpmPackage('cli4ai');
130
+ if (success) {
131
+ log(` ${GREEN}✓${RESET} cli4ai updated\n`);
132
+ results.push({ name: 'cli4ai', from: current, to: latest, status: 'updated' });
133
+ }
134
+ else {
135
+ log(` ${YELLOW}✗${RESET} Failed to update cli4ai\n`);
136
+ results.push({ name: 'cli4ai', from: current, to: latest, status: 'failed' });
137
+ }
138
+ }
139
+ else {
140
+ log(` ${GREEN}✓${RESET} cli4ai is up to date (${current})\n`);
141
+ results.push({ name: 'cli4ai', from: current, to: current, status: 'current' });
142
+ }
143
+ if (options.self && !options.all) {
144
+ return;
145
+ }
146
+ }
147
+ // Get all installed packages
148
+ const packages = getUpdateablePackages();
149
+ if (packages.length === 0) {
150
+ log(`${DIM}No @cli4ai packages installed.${RESET}`);
151
+ log(`${DIM}Run ${RESET}cli4ai browse${DIM} to install packages.${RESET}\n`);
152
+ return;
153
+ }
154
+ log(`${BOLD}Checking ${packages.length} package${packages.length !== 1 ? 's' : ''} for updates...${RESET}\n`);
155
+ // Fetch all versions in parallel (fast!)
156
+ const npmNames = packages.map(p => p.npmName);
157
+ const latestVersions = await getLatestVersions(npmNames);
158
+ // Separate packages that need updates vs current
159
+ const toUpdate = [];
160
+ for (const pkg of packages) {
161
+ const latest = latestVersions.get(pkg.npmName);
162
+ if (!latest) {
163
+ log(` ${DIM}${pkg.name}${RESET} - ${YELLOW}could not check${RESET}`);
164
+ continue;
165
+ }
166
+ if (latest === pkg.version) {
167
+ log(` ${GREEN}✓${RESET} ${pkg.name} ${DIM}${pkg.version}${RESET}`);
168
+ results.push({ name: pkg.name, from: pkg.version, to: pkg.version, status: 'current' });
169
+ }
170
+ else {
171
+ log(` ${CYAN}↑${RESET} ${pkg.name} ${DIM}${pkg.version}${RESET} → ${GREEN}${latest}${RESET}`);
172
+ toUpdate.push({ name: pkg.name, npmName: pkg.npmName, from: pkg.version, to: latest });
173
+ }
174
+ }
175
+ // Update all packages in parallel
176
+ if (toUpdate.length > 0) {
177
+ log(`\n${BOLD}Updating ${toUpdate.length} package${toUpdate.length !== 1 ? 's' : ''}...${RESET}`);
178
+ const updateResults = [];
179
+ for (const pkg of toUpdate) {
180
+ // Use cli4ai add -g to update packages in ~/.cli4ai/packages/
181
+ const success = await updateCli4aiPackage(pkg.name);
182
+ updateResults.push({ ...pkg, success });
183
+ }
184
+ log('');
185
+ for (const result of updateResults) {
186
+ if (result.success) {
187
+ log(` ${GREEN}✓${RESET} ${result.name} updated`);
188
+ results.push({ name: result.name, from: result.from, to: result.to, status: 'updated' });
189
+ }
190
+ else {
191
+ log(` ${YELLOW}✗${RESET} ${result.name} failed`);
192
+ results.push({ name: result.name, from: result.from, to: result.to, status: 'failed' });
193
+ }
194
+ }
195
+ }
196
+ // Summary
197
+ const updated = results.filter(r => r.status === 'updated').length;
198
+ const failed = results.filter(r => r.status === 'failed').length;
199
+ log('');
200
+ if (updated > 0) {
201
+ log(`${GREEN}${updated} package${updated !== 1 ? 's' : ''} updated${RESET}`);
202
+ }
203
+ if (failed > 0) {
204
+ log(`${YELLOW}${failed} package${failed !== 1 ? 's' : ''} failed to update${RESET}`);
205
+ }
206
+ if (updated === 0 && failed === 0) {
207
+ log(`${GREEN}All packages are up to date${RESET}`);
208
+ }
209
+ log('');
210
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Global cli4ai configuration (~/.cli4ai/)
3
+ */
4
+ /**
5
+ * Check if a path is safe to use (not a malicious symlink)
6
+ */
7
+ export declare function isPathSafe(path: string): boolean;
8
+ export declare const CLI4AI_HOME: string;
9
+ export declare const CONFIG_FILE: string;
10
+ export declare const PACKAGES_DIR: string;
11
+ export declare const CACHE_DIR: string;
12
+ export declare const ROUTINES_DIR: string;
13
+ export declare const SCHEDULER_DIR: string;
14
+ export declare const CREDENTIALS_FILE: string;
15
+ export declare const LOCAL_DIR = ".cli4ai";
16
+ export declare const LOCAL_PACKAGES_DIR: string;
17
+ export declare const LOCAL_ROUTINES_DIR: string;
18
+ export interface Config {
19
+ registry: string;
20
+ localRegistries: string[];
21
+ defaultRuntime: 'node';
22
+ mcp: {
23
+ transport: 'stdio' | 'http';
24
+ port: number;
25
+ };
26
+ audit: {
27
+ /** Enable audit logging for MCP tool calls */
28
+ enabled: boolean;
29
+ };
30
+ telemetry: boolean;
31
+ }
32
+ export interface InstalledPackage {
33
+ name: string;
34
+ version: string;
35
+ path: string;
36
+ source: 'local' | 'registry';
37
+ installedAt: string;
38
+ }
39
+ export declare const DEFAULT_CONFIG: Config;
40
+ /**
41
+ * Ensure ~/.cli4ai directory exists with required subdirectories
42
+ */
43
+ export declare function ensureCli4aiHome(): void;
44
+ /**
45
+ * Ensure local .cli4ai directory exists
46
+ */
47
+ export declare function ensureLocalDir(projectDir: string): void;
48
+ /**
49
+ * Load global config, creating defaults if needed
50
+ */
51
+ export declare function loadConfig(): Config;
52
+ /**
53
+ * Save global config
54
+ */
55
+ export declare function saveConfig(config: Config): void;
56
+ /**
57
+ * Update config with a read-modify-write lock to avoid cross-process races.
58
+ */
59
+ export declare function updateConfig(mutator: (config: Config) => Config): Config;
60
+ /**
61
+ * Get a config value
62
+ */
63
+ export declare function getConfigValue<K extends keyof Config>(key: K): Config[K];
64
+ /**
65
+ * Set a config value
66
+ */
67
+ export declare function setConfigValue<K extends keyof Config>(key: K, value: Config[K]): void;
68
+ /**
69
+ * Add a local registry path
70
+ */
71
+ export declare function addLocalRegistry(path: string): void;
72
+ /**
73
+ * Remove a local registry path
74
+ */
75
+ export declare function removeLocalRegistry(path: string): void;
76
+ /**
77
+ * Get list of globally installed packages
78
+ */
79
+ export declare function getGlobalPackages(): InstalledPackage[];
80
+ /**
81
+ * Get list of locally installed packages (in project)
82
+ */
83
+ export declare function getLocalPackages(projectDir: string): InstalledPackage[];
84
+ /**
85
+ * Get all global @cli4ai packages (from both npm and bun)
86
+ */
87
+ export declare function getNpmGlobalPackages(): InstalledPackage[];
88
+ /**
89
+ * Find a package by name (checks local, global cli4ai, bun global, then npm global)
90
+ */
91
+ export declare function findPackage(name: string, projectDir?: string): InstalledPackage | null;