cli4ai 1.2.0 → 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.
- package/README.md +39 -0
- package/dist/bin.d.ts +6 -0
- package/dist/bin.js +105 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +335 -0
- package/dist/commands/add.d.ts +11 -0
- package/dist/commands/add.js +459 -0
- package/dist/commands/browse.d.ts +4 -0
- package/dist/commands/browse.js +379 -0
- package/dist/commands/config.d.ts +10 -0
- package/dist/commands/config.js +121 -0
- package/dist/commands/info.d.ts +9 -0
- package/dist/commands/info.js +122 -0
- package/dist/commands/init.d.ts +10 -0
- package/dist/commands/init.js +458 -0
- package/dist/commands/list.d.ts +10 -0
- package/dist/commands/list.js +76 -0
- package/dist/commands/mcp-config.d.ts +10 -0
- package/dist/commands/mcp-config.js +49 -0
- package/dist/commands/remotes.d.ts +22 -0
- package/dist/commands/remotes.js +196 -0
- package/dist/commands/remove.d.ts +8 -0
- package/dist/commands/remove.js +61 -0
- package/dist/commands/routines.d.ts +29 -0
- package/dist/commands/routines.js +363 -0
- package/dist/commands/run.d.ts +12 -0
- package/dist/commands/run.js +104 -0
- package/dist/commands/scheduler.d.ts +27 -0
- package/dist/commands/scheduler.js +350 -0
- package/dist/commands/search.d.ts +9 -0
- package/dist/commands/search.js +159 -0
- package/dist/commands/secrets.d.ts +28 -0
- package/dist/commands/secrets.js +236 -0
- package/dist/commands/serve.d.ts +13 -0
- package/dist/commands/serve.js +49 -0
- package/dist/commands/start.d.ts +8 -0
- package/dist/commands/start.js +27 -0
- package/dist/commands/update.d.ts +17 -0
- package/dist/commands/update.js +210 -0
- package/dist/core/config.d.ts +91 -0
- package/dist/core/config.js +738 -0
- package/dist/core/execute.d.ts +51 -0
- package/dist/core/execute.js +475 -0
- package/dist/core/link.d.ts +39 -0
- package/dist/core/link.js +214 -0
- package/dist/core/lockfile.d.ts +63 -0
- package/dist/core/lockfile.js +140 -0
- package/dist/core/manifest.d.ts +96 -0
- package/dist/core/manifest.js +224 -0
- package/dist/core/registry.d.ts +74 -0
- package/dist/core/registry.js +116 -0
- package/dist/core/remote-client.d.ts +98 -0
- package/dist/core/remote-client.js +252 -0
- package/dist/core/remotes.d.ts +88 -0
- package/dist/core/remotes.js +206 -0
- package/dist/core/routine-engine.d.ts +124 -0
- package/dist/core/routine-engine.js +699 -0
- package/dist/core/routines.d.ts +36 -0
- package/dist/core/routines.js +132 -0
- package/dist/core/scheduler-daemon.d.ts +10 -0
- package/dist/core/scheduler-daemon.js +77 -0
- package/dist/core/scheduler.d.ts +131 -0
- package/dist/core/scheduler.js +492 -0
- package/dist/core/secrets.d.ts +48 -0
- package/dist/core/secrets.js +384 -0
- package/dist/lib/cli.d.ts +84 -0
- package/dist/lib/cli.js +216 -0
- package/dist/mcp/adapter.d.ts +35 -0
- package/dist/mcp/adapter.js +94 -0
- package/dist/mcp/config-gen.d.ts +31 -0
- package/dist/mcp/config-gen.js +75 -0
- package/dist/mcp/server.d.ts +41 -0
- package/dist/mcp/server.js +296 -0
- package/dist/server/service.d.ts +85 -0
- package/dist/server/service.js +304 -0
- package/package.json +6 -3
- package/src/bin.ts +0 -118
- package/src/cli.ts +0 -412
- package/src/commands/add.ts +0 -562
- package/src/commands/browse.ts +0 -449
- package/src/commands/config.ts +0 -154
- package/src/commands/info.ts +0 -133
- package/src/commands/init.ts +0 -514
- package/src/commands/list.ts +0 -95
- package/src/commands/mcp-config.ts +0 -69
- package/src/commands/remotes.ts +0 -253
- package/src/commands/remove.ts +0 -78
- package/src/commands/routines.ts +0 -427
- package/src/commands/run.ts +0 -127
- package/src/commands/scheduler.ts +0 -438
- package/src/commands/search.ts +0 -185
- package/src/commands/secrets.ts +0 -292
- package/src/commands/serve.ts +0 -66
- package/src/commands/start.ts +0 -40
- package/src/commands/update.ts +0 -252
- package/src/core/config.ts +0 -845
- package/src/core/execute.ts +0 -569
- package/src/core/link.ts +0 -246
- package/src/core/lockfile.ts +0 -187
- package/src/core/manifest.ts +0 -327
- package/src/core/registry.ts +0 -165
- package/src/core/remote-client.ts +0 -419
- package/src/core/remotes.ts +0 -268
- package/src/core/routine-engine.ts +0 -895
- package/src/core/routines.ts +0 -171
- package/src/core/scheduler-daemon.ts +0 -94
- package/src/core/scheduler.ts +0 -606
- package/src/core/secrets.ts +0 -430
- package/src/lib/cli.ts +0 -261
- package/src/mcp/adapter.ts +0 -131
- package/src/mcp/config-gen.ts +0 -106
- package/src/mcp/server.ts +0 -365
- 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,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;
|