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,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli4ai Remote Service
|
|
3
|
+
*
|
|
4
|
+
* HTTP server that exposes cli4ai functionality for remote execution.
|
|
5
|
+
* Run with `cli4ai serve` to start the service.
|
|
6
|
+
*/
|
|
7
|
+
import { createServer } from 'http';
|
|
8
|
+
import { hostname } from 'os';
|
|
9
|
+
import { log } from '../lib/cli.js';
|
|
10
|
+
import { executeTool, ExecuteToolError } from '../core/execute.js';
|
|
11
|
+
import { findPackage, getGlobalPackages, getLocalPackages } from '../core/config.js';
|
|
12
|
+
import { loadManifest } from '../core/manifest.js';
|
|
13
|
+
import { loadRoutineDefinition, runRoutine } from '../core/routine-engine.js';
|
|
14
|
+
import { resolveRoutine } from '../core/routines.js';
|
|
15
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
16
|
+
// HELPERS
|
|
17
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
18
|
+
function parseBody(req) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const chunks = [];
|
|
21
|
+
req.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
|
22
|
+
req.on('error', reject);
|
|
23
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function sendJson(res, status, data) {
|
|
27
|
+
const body = JSON.stringify(data);
|
|
28
|
+
res.writeHead(status, {
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
'Content-Length': Buffer.byteLength(body)
|
|
31
|
+
});
|
|
32
|
+
res.end(body);
|
|
33
|
+
}
|
|
34
|
+
function sendError(res, status, code, message, details) {
|
|
35
|
+
sendJson(res, status, { error: { code, message, details } });
|
|
36
|
+
}
|
|
37
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
38
|
+
// REQUEST HANDLERS
|
|
39
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
40
|
+
async function handleHealth(config, startTime) {
|
|
41
|
+
return {
|
|
42
|
+
status: 'ok',
|
|
43
|
+
hostname: hostname(),
|
|
44
|
+
version: '1.0.0',
|
|
45
|
+
uptime: Math.floor((Date.now() - startTime) / 1000)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
async function handleListPackages(config) {
|
|
49
|
+
const localPkgs = getLocalPackages(config.cwd);
|
|
50
|
+
const globalPkgs = getGlobalPackages();
|
|
51
|
+
const packages = [
|
|
52
|
+
...localPkgs.map(p => ({ name: p.name, version: p.version, path: p.path, source: p.source })),
|
|
53
|
+
...globalPkgs.map(p => ({ name: p.name, version: p.version, path: p.path, source: p.source }))
|
|
54
|
+
];
|
|
55
|
+
// Deduplicate by name (local takes precedence)
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
const unique = packages.filter(p => {
|
|
58
|
+
if (seen.has(p.name))
|
|
59
|
+
return false;
|
|
60
|
+
seen.add(p.name);
|
|
61
|
+
return true;
|
|
62
|
+
});
|
|
63
|
+
return { packages: unique };
|
|
64
|
+
}
|
|
65
|
+
async function handlePackageInfo(config, packageName) {
|
|
66
|
+
const pkg = findPackage(packageName, config.cwd);
|
|
67
|
+
if (!pkg)
|
|
68
|
+
return null;
|
|
69
|
+
const manifest = loadManifest(pkg.path);
|
|
70
|
+
const commands = {};
|
|
71
|
+
if (manifest.commands) {
|
|
72
|
+
for (const [name, cmd] of Object.entries(manifest.commands)) {
|
|
73
|
+
commands[name] = { description: cmd.description };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
name: manifest.name,
|
|
78
|
+
version: manifest.version,
|
|
79
|
+
description: manifest.description,
|
|
80
|
+
commands
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function handleRunTool(config, request) {
|
|
84
|
+
const startTime = Date.now();
|
|
85
|
+
// Validate scope
|
|
86
|
+
const scope = request.scope ?? 'full';
|
|
87
|
+
const allowedScopes = config.allowedScopes ?? ['read', 'write', 'full'];
|
|
88
|
+
if (!allowedScopes.includes(scope)) {
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
exitCode: 1,
|
|
92
|
+
durationMs: Date.now() - startTime,
|
|
93
|
+
error: {
|
|
94
|
+
code: 'FORBIDDEN',
|
|
95
|
+
message: `Scope "${scope}" is not allowed on this remote`,
|
|
96
|
+
details: { allowedScopes }
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const result = await executeTool({
|
|
102
|
+
packageName: request.package,
|
|
103
|
+
command: request.command,
|
|
104
|
+
args: request.args ?? [],
|
|
105
|
+
cwd: config.cwd,
|
|
106
|
+
env: request.env,
|
|
107
|
+
stdin: request.stdin,
|
|
108
|
+
capture: 'pipe',
|
|
109
|
+
timeoutMs: request.timeout,
|
|
110
|
+
scope,
|
|
111
|
+
teeStderr: false
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
success: result.exitCode === 0,
|
|
115
|
+
exitCode: result.exitCode,
|
|
116
|
+
stdout: result.stdout,
|
|
117
|
+
stderr: result.stderr,
|
|
118
|
+
durationMs: result.durationMs
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
const durationMs = Date.now() - startTime;
|
|
123
|
+
if (err instanceof ExecuteToolError) {
|
|
124
|
+
return {
|
|
125
|
+
success: false,
|
|
126
|
+
exitCode: 1,
|
|
127
|
+
durationMs,
|
|
128
|
+
error: {
|
|
129
|
+
code: err.code,
|
|
130
|
+
message: err.message,
|
|
131
|
+
details: err.details
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
success: false,
|
|
137
|
+
exitCode: 1,
|
|
138
|
+
durationMs,
|
|
139
|
+
error: {
|
|
140
|
+
code: 'API_ERROR',
|
|
141
|
+
message: err instanceof Error ? err.message : String(err)
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async function handleRunRoutine(config, request) {
|
|
147
|
+
const resolved = resolveRoutine(request.routine, config.cwd);
|
|
148
|
+
if (!resolved)
|
|
149
|
+
return null;
|
|
150
|
+
const def = loadRoutineDefinition(resolved.path);
|
|
151
|
+
const result = await runRoutine(def, request.vars ?? {}, config.cwd);
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
155
|
+
// MAIN SERVER
|
|
156
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
157
|
+
export function createService(config) {
|
|
158
|
+
const startTime = Date.now();
|
|
159
|
+
const server = createServer(async (req, res) => {
|
|
160
|
+
const method = req.method ?? 'GET';
|
|
161
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
|
|
162
|
+
const path = url.pathname;
|
|
163
|
+
// CORS headers for cross-origin requests
|
|
164
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
165
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
166
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key');
|
|
167
|
+
// Handle preflight
|
|
168
|
+
if (method === 'OPTIONS') {
|
|
169
|
+
res.writeHead(204);
|
|
170
|
+
res.end();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
// Authentication check
|
|
174
|
+
if (config.apiKey) {
|
|
175
|
+
const providedKey = req.headers['x-api-key'] ||
|
|
176
|
+
req.headers['authorization']?.replace(/^Bearer\s+/i, '');
|
|
177
|
+
if (providedKey !== config.apiKey) {
|
|
178
|
+
sendError(res, 401, 'UNAUTHORIZED', 'Invalid or missing API key');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
// Route: GET /health
|
|
184
|
+
if (method === 'GET' && path === '/health') {
|
|
185
|
+
const data = await handleHealth(config, startTime);
|
|
186
|
+
sendJson(res, 200, data);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
// Route: GET /packages
|
|
190
|
+
if (method === 'GET' && path === '/packages') {
|
|
191
|
+
const data = await handleListPackages(config);
|
|
192
|
+
sendJson(res, 200, data);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
// Route: GET /packages/:name
|
|
196
|
+
if (method === 'GET' && path.startsWith('/packages/')) {
|
|
197
|
+
const packageName = path.slice('/packages/'.length);
|
|
198
|
+
const data = await handlePackageInfo(config, packageName);
|
|
199
|
+
if (!data) {
|
|
200
|
+
sendError(res, 404, 'NOT_FOUND', `Package not found: ${packageName}`);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
sendJson(res, 200, data);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// Route: POST /run
|
|
207
|
+
if (method === 'POST' && path === '/run') {
|
|
208
|
+
const body = await parseBody(req);
|
|
209
|
+
let request;
|
|
210
|
+
try {
|
|
211
|
+
request = JSON.parse(body);
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
sendError(res, 400, 'PARSE_ERROR', 'Invalid JSON body');
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (!request.package || typeof request.package !== 'string') {
|
|
218
|
+
sendError(res, 400, 'INVALID_INPUT', 'Missing required field: package');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const data = await handleRunTool(config, request);
|
|
222
|
+
sendJson(res, data.success ? 200 : 500, data);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
// Route: POST /routines/:name/run
|
|
226
|
+
if (method === 'POST' && path.match(/^\/routines\/[^/]+\/run$/)) {
|
|
227
|
+
const routineName = path.split('/')[2];
|
|
228
|
+
const body = await parseBody(req);
|
|
229
|
+
let request;
|
|
230
|
+
try {
|
|
231
|
+
request = body ? JSON.parse(body) : {};
|
|
232
|
+
request.routine = routineName;
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
sendError(res, 400, 'PARSE_ERROR', 'Invalid JSON body');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const data = await handleRunRoutine(config, request);
|
|
239
|
+
if (!data) {
|
|
240
|
+
sendError(res, 404, 'NOT_FOUND', `Routine not found: ${routineName}`);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
sendJson(res, data.status === 'success' ? 200 : 500, data);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// 404 for unknown routes
|
|
247
|
+
sendError(res, 404, 'NOT_FOUND', `Unknown route: ${method} ${path}`);
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
console.error('Request error:', err);
|
|
251
|
+
sendError(res, 500, 'API_ERROR', err instanceof Error ? err.message : String(err));
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
return server;
|
|
255
|
+
}
|
|
256
|
+
export async function startService(options = {}) {
|
|
257
|
+
const config = {
|
|
258
|
+
port: options.port ?? 4100,
|
|
259
|
+
host: options.host ?? '0.0.0.0',
|
|
260
|
+
apiKey: options.apiKey,
|
|
261
|
+
cwd: options.cwd ?? process.cwd(),
|
|
262
|
+
allowedScopes: options.allowedScopes
|
|
263
|
+
};
|
|
264
|
+
const server = createService(config);
|
|
265
|
+
return new Promise((resolve, reject) => {
|
|
266
|
+
server.on('error', (err) => {
|
|
267
|
+
if (err.code === 'EADDRINUSE') {
|
|
268
|
+
reject(new Error(`Port ${config.port} is already in use`));
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
reject(err);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
server.listen(config.port, config.host, () => {
|
|
275
|
+
log(`cli4ai service running on http://${config.host}:${config.port}`);
|
|
276
|
+
log(`Hostname: ${hostname()}`);
|
|
277
|
+
log(`Working directory: ${config.cwd}`);
|
|
278
|
+
if (config.apiKey) {
|
|
279
|
+
log(`Authentication: API key required`);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
log(`Authentication: None (not recommended for production)`);
|
|
283
|
+
}
|
|
284
|
+
log('');
|
|
285
|
+
log('Endpoints:');
|
|
286
|
+
log(' GET /health - Service health check');
|
|
287
|
+
log(' GET /packages - List available packages');
|
|
288
|
+
log(' GET /packages/:name - Get package info');
|
|
289
|
+
log(' POST /run - Execute a tool');
|
|
290
|
+
log(' POST /routines/:name/run - Run a routine');
|
|
291
|
+
log('');
|
|
292
|
+
log('Press Ctrl+C to stop');
|
|
293
|
+
});
|
|
294
|
+
// Handle graceful shutdown
|
|
295
|
+
const shutdown = () => {
|
|
296
|
+
log('\nShutting down...');
|
|
297
|
+
server.close(() => {
|
|
298
|
+
resolve();
|
|
299
|
+
});
|
|
300
|
+
};
|
|
301
|
+
process.on('SIGINT', shutdown);
|
|
302
|
+
process.on('SIGTERM', shutdown);
|
|
303
|
+
});
|
|
304
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cli4ai",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "The package manager for AI CLI tools - cli4ai.com",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"cli4ai": "./
|
|
7
|
+
"cli4ai": "./dist/bin.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"dev": "npx tsx src/bin.ts",
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"prepublishOnly": "npm run build",
|
|
11
13
|
"typecheck": "tsc --noEmit",
|
|
12
14
|
"test": "vitest run",
|
|
13
15
|
"test:watch": "vitest",
|
|
@@ -42,7 +44,8 @@
|
|
|
42
44
|
"cli4ai"
|
|
43
45
|
],
|
|
44
46
|
"files": [
|
|
45
|
-
"
|
|
47
|
+
"dist/**/*.js",
|
|
48
|
+
"dist/**/*.d.ts",
|
|
46
49
|
"templates/**/*"
|
|
47
50
|
],
|
|
48
51
|
"publishConfig": {
|
package/src/bin.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env npx tsx
|
|
2
|
-
/**
|
|
3
|
-
* cli4ai - The package manager for AI CLI tools
|
|
4
|
-
* cli4ai.com
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { createProgram } from './cli.js';
|
|
8
|
-
import { VERSION } from './lib/cli.js';
|
|
9
|
-
import { getNpmGlobalPackages } from './core/config.js';
|
|
10
|
-
|
|
11
|
-
// ANSI codes
|
|
12
|
-
const RESET = '\x1B[0m';
|
|
13
|
-
const BOLD = '\x1B[1m';
|
|
14
|
-
const DIM = '\x1B[2m';
|
|
15
|
-
const CYAN = '\x1B[36m';
|
|
16
|
-
const WHITE = '\x1B[37m';
|
|
17
|
-
const GREEN = '\x1B[32m';
|
|
18
|
-
const YELLOW = '\x1B[33m';
|
|
19
|
-
const MAGENTA = '\x1B[35m';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Sleep helper
|
|
23
|
-
*/
|
|
24
|
-
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Robot eating dots animation
|
|
28
|
-
*/
|
|
29
|
-
async function showAnimation(): Promise<void> {
|
|
30
|
-
const width = 35;
|
|
31
|
-
const robot = ['[•_•]', '[•_•]', '[°_°]', '[•_•]'];
|
|
32
|
-
const food = '·';
|
|
33
|
-
|
|
34
|
-
for (let pos = 0; pos <= width; pos++) {
|
|
35
|
-
process.stderr.write('\r\x1B[K');
|
|
36
|
-
const frame = robot[pos % robot.length];
|
|
37
|
-
const trail = ' '.repeat(pos) + food.repeat(width - pos);
|
|
38
|
-
process.stderr.write(` ${CYAN}${frame}${RESET}${DIM}${trail}${RESET}`);
|
|
39
|
-
await sleep(20);
|
|
40
|
-
}
|
|
41
|
-
process.stderr.write('\r\x1B[K');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Animate the robot face (blinking)
|
|
46
|
-
*/
|
|
47
|
-
async function animateRobotFace(): Promise<void> {
|
|
48
|
-
const faces = ['[•_•]', '[•_•]', '[-_-]', '[•_•]', '[•_•]', '[°_°]', '[•_•]'];
|
|
49
|
-
const line = ` ${BOLD}${CYAN}cli4ai${RESET} ${DIM}─${RESET} ${WHITE}cli4ai.com${RESET}`;
|
|
50
|
-
|
|
51
|
-
for (const face of faces) {
|
|
52
|
-
process.stderr.write(`\r ${CYAN}${face}${RESET}${line}`);
|
|
53
|
-
await sleep(120);
|
|
54
|
-
}
|
|
55
|
-
console.error('');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async function showBanner(): Promise<void> {
|
|
59
|
-
if (!process.stderr.isTTY) return;
|
|
60
|
-
|
|
61
|
-
console.error('');
|
|
62
|
-
|
|
63
|
-
// Fun robot eating animation
|
|
64
|
-
await showAnimation();
|
|
65
|
-
|
|
66
|
-
// Animated robot branding (blinking)
|
|
67
|
-
await animateRobotFace();
|
|
68
|
-
|
|
69
|
-
console.error(` ${DIM}The package manager for AI CLI tools${RESET}`);
|
|
70
|
-
console.error(` ${DIM}v${VERSION}${RESET}`);
|
|
71
|
-
console.error('');
|
|
72
|
-
console.error(` ${BOLD}Commands${RESET}`);
|
|
73
|
-
console.error(` ${DIM}${'─'.repeat(40)}${RESET}`);
|
|
74
|
-
console.error(` ${GREEN}browse${RESET} ${DIM}Browse & install packages${RESET}`);
|
|
75
|
-
console.error(` ${GREEN}run${RESET} ${CYAN}<pkg> <cmd>${RESET} ${DIM}Run a tool command${RESET}`);
|
|
76
|
-
console.error(` ${GREEN}ls${RESET} ${DIM}List installed packages${RESET}`);
|
|
77
|
-
console.error(` ${GREEN}update${RESET} ${DIM}Update all packages${RESET}`);
|
|
78
|
-
console.error('');
|
|
79
|
-
console.error(` ${DIM}Run${RESET} ${WHITE}cli4ai --help${RESET} ${DIM}for all commands${RESET}`);
|
|
80
|
-
console.error('');
|
|
81
|
-
|
|
82
|
-
// Check for updates in background (non-blocking)
|
|
83
|
-
checkUpdatesInBackground();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Check for updates without blocking - spawns detached process
|
|
88
|
-
*/
|
|
89
|
-
function checkUpdatesInBackground(): void {
|
|
90
|
-
// Quick local check only - don't hit network
|
|
91
|
-
try {
|
|
92
|
-
const packages = getNpmGlobalPackages();
|
|
93
|
-
if (packages.length > 0) {
|
|
94
|
-
// Spawn a background process to check updates and cache results
|
|
95
|
-
const { spawn } = require('child_process');
|
|
96
|
-
const child = spawn('sh', ['-c', 'timeout 3 npm view cli4ai version 2>/dev/null > /tmp/.cli4ai-update-check 2>&1 &'], {
|
|
97
|
-
detached: true,
|
|
98
|
-
stdio: ['ignore', 'ignore', 'ignore']
|
|
99
|
-
});
|
|
100
|
-
child.unref();
|
|
101
|
-
}
|
|
102
|
-
} catch {
|
|
103
|
-
// Ignore errors - this is best effort
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Check if running without command (just `cli4ai`)
|
|
108
|
-
const args = process.argv.slice(2);
|
|
109
|
-
const hasCommand = args.length > 0 && !args[0].startsWith('-');
|
|
110
|
-
const isHelp = args.includes('--help') || args.includes('-h');
|
|
111
|
-
const isVersion = args.includes('--version') || args.includes('-v');
|
|
112
|
-
|
|
113
|
-
if (!hasCommand && !isHelp && !isVersion) {
|
|
114
|
-
showBanner().then(() => process.exit(0));
|
|
115
|
-
} else {
|
|
116
|
-
const program = createProgram();
|
|
117
|
-
program.parse(process.argv);
|
|
118
|
-
}
|