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.
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 -412
  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 -133
  83. package/src/commands/init.ts +0 -514
  84. package/src/commands/list.ts +0 -95
  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 -185
  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,434 +0,0 @@
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
-
8
- import { createServer, IncomingMessage, ServerResponse } from 'http';
9
- import { hostname } from 'os';
10
- import { log, output } from '../lib/cli.js';
11
- import { executeTool, ExecuteToolError, type ScopeLevel } from '../core/execute.js';
12
- import { findPackage, getGlobalPackages, getLocalPackages } from '../core/config.js';
13
- import { loadManifest } from '../core/manifest.js';
14
- import {
15
- loadRoutineDefinition,
16
- runRoutine,
17
- type RoutineRunSummary
18
- } from '../core/routine-engine.js';
19
- import { resolveRoutine } from '../core/routines.js';
20
-
21
- // ═══════════════════════════════════════════════════════════════════════════
22
- // TYPES
23
- // ═══════════════════════════════════════════════════════════════════════════
24
-
25
- export interface ServiceConfig {
26
- /** Port to listen on (default: 4100) */
27
- port: number;
28
- /** Host to bind to (default: 0.0.0.0) */
29
- host: string;
30
- /** API key for authentication (optional but recommended) */
31
- apiKey?: string;
32
- /** Working directory for command execution */
33
- cwd: string;
34
- /** Allowed scope levels (defaults to ['read', 'write', 'full']) */
35
- allowedScopes?: ScopeLevel[];
36
- }
37
-
38
- export interface RunToolRequest {
39
- /** Package name */
40
- package: string;
41
- /** Command within the package */
42
- command?: string;
43
- /** Arguments to pass */
44
- args?: string[];
45
- /** Environment variables */
46
- env?: Record<string, string>;
47
- /** Standard input to pass to the tool */
48
- stdin?: string;
49
- /** Timeout in milliseconds */
50
- timeout?: number;
51
- /** Scope level for execution */
52
- scope?: ScopeLevel;
53
- }
54
-
55
- export interface RunToolResponse {
56
- success: boolean;
57
- exitCode: number;
58
- stdout?: string;
59
- stderr?: string;
60
- durationMs: number;
61
- error?: {
62
- code: string;
63
- message: string;
64
- details?: Record<string, unknown>;
65
- };
66
- }
67
-
68
- export interface RunRoutineRequest {
69
- /** Routine name */
70
- routine: string;
71
- /** Variables to pass to the routine */
72
- vars?: Record<string, string>;
73
- }
74
-
75
- export interface ListPackagesResponse {
76
- packages: Array<{
77
- name: string;
78
- version: string;
79
- path: string;
80
- source: 'local' | 'registry';
81
- }>;
82
- }
83
-
84
- export interface PackageInfoResponse {
85
- name: string;
86
- version: string;
87
- description?: string;
88
- commands?: Record<string, { description: string }>;
89
- }
90
-
91
- export interface HealthResponse {
92
- status: 'ok';
93
- hostname: string;
94
- version: string;
95
- uptime: number;
96
- }
97
-
98
- // ═══════════════════════════════════════════════════════════════════════════
99
- // HELPERS
100
- // ═══════════════════════════════════════════════════════════════════════════
101
-
102
- function parseBody(req: IncomingMessage): Promise<string> {
103
- return new Promise((resolve, reject) => {
104
- const chunks: Buffer[] = [];
105
- req.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
106
- req.on('error', reject);
107
- req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
108
- });
109
- }
110
-
111
- function sendJson(res: ServerResponse, status: number, data: unknown): void {
112
- const body = JSON.stringify(data);
113
- res.writeHead(status, {
114
- 'Content-Type': 'application/json',
115
- 'Content-Length': Buffer.byteLength(body)
116
- });
117
- res.end(body);
118
- }
119
-
120
- function sendError(res: ServerResponse, status: number, code: string, message: string, details?: Record<string, unknown>): void {
121
- sendJson(res, status, { error: { code, message, details } });
122
- }
123
-
124
- // ═══════════════════════════════════════════════════════════════════════════
125
- // REQUEST HANDLERS
126
- // ═══════════════════════════════════════════════════════════════════════════
127
-
128
- async function handleHealth(config: ServiceConfig, startTime: number): Promise<HealthResponse> {
129
- return {
130
- status: 'ok',
131
- hostname: hostname(),
132
- version: '1.0.0',
133
- uptime: Math.floor((Date.now() - startTime) / 1000)
134
- };
135
- }
136
-
137
- async function handleListPackages(config: ServiceConfig): Promise<ListPackagesResponse> {
138
- const localPkgs = getLocalPackages(config.cwd);
139
- const globalPkgs = getGlobalPackages();
140
-
141
- const packages = [
142
- ...localPkgs.map(p => ({ name: p.name, version: p.version, path: p.path, source: p.source })),
143
- ...globalPkgs.map(p => ({ name: p.name, version: p.version, path: p.path, source: p.source }))
144
- ];
145
-
146
- // Deduplicate by name (local takes precedence)
147
- const seen = new Set<string>();
148
- const unique = packages.filter(p => {
149
- if (seen.has(p.name)) return false;
150
- seen.add(p.name);
151
- return true;
152
- });
153
-
154
- return { packages: unique };
155
- }
156
-
157
- async function handlePackageInfo(config: ServiceConfig, packageName: string): Promise<PackageInfoResponse | null> {
158
- const pkg = findPackage(packageName, config.cwd);
159
- if (!pkg) return null;
160
-
161
- const manifest = loadManifest(pkg.path);
162
-
163
- const commands: Record<string, { description: string }> = {};
164
- if (manifest.commands) {
165
- for (const [name, cmd] of Object.entries(manifest.commands)) {
166
- commands[name] = { description: cmd.description };
167
- }
168
- }
169
-
170
- return {
171
- name: manifest.name,
172
- version: manifest.version,
173
- description: manifest.description,
174
- commands
175
- };
176
- }
177
-
178
- async function handleRunTool(config: ServiceConfig, request: RunToolRequest): Promise<RunToolResponse> {
179
- const startTime = Date.now();
180
-
181
- // Validate scope
182
- const scope = request.scope ?? 'full';
183
- const allowedScopes = config.allowedScopes ?? ['read', 'write', 'full'];
184
- if (!allowedScopes.includes(scope)) {
185
- return {
186
- success: false,
187
- exitCode: 1,
188
- durationMs: Date.now() - startTime,
189
- error: {
190
- code: 'FORBIDDEN',
191
- message: `Scope "${scope}" is not allowed on this remote`,
192
- details: { allowedScopes }
193
- }
194
- };
195
- }
196
-
197
- try {
198
- const result = await executeTool({
199
- packageName: request.package,
200
- command: request.command,
201
- args: request.args ?? [],
202
- cwd: config.cwd,
203
- env: request.env,
204
- stdin: request.stdin,
205
- capture: 'pipe',
206
- timeoutMs: request.timeout,
207
- scope,
208
- teeStderr: false
209
- });
210
-
211
- return {
212
- success: result.exitCode === 0,
213
- exitCode: result.exitCode,
214
- stdout: result.stdout,
215
- stderr: result.stderr,
216
- durationMs: result.durationMs
217
- };
218
- } catch (err) {
219
- const durationMs = Date.now() - startTime;
220
-
221
- if (err instanceof ExecuteToolError) {
222
- return {
223
- success: false,
224
- exitCode: 1,
225
- durationMs,
226
- error: {
227
- code: err.code,
228
- message: err.message,
229
- details: err.details
230
- }
231
- };
232
- }
233
-
234
- return {
235
- success: false,
236
- exitCode: 1,
237
- durationMs,
238
- error: {
239
- code: 'API_ERROR',
240
- message: err instanceof Error ? err.message : String(err)
241
- }
242
- };
243
- }
244
- }
245
-
246
- async function handleRunRoutine(config: ServiceConfig, request: RunRoutineRequest): Promise<RoutineRunSummary | null> {
247
- const resolved = resolveRoutine(request.routine, config.cwd);
248
- if (!resolved) return null;
249
-
250
- const def = loadRoutineDefinition(resolved.path);
251
- const result = await runRoutine(def, request.vars ?? {}, config.cwd);
252
-
253
- return result;
254
- }
255
-
256
- // ═══════════════════════════════════════════════════════════════════════════
257
- // MAIN SERVER
258
- // ═══════════════════════════════════════════════════════════════════════════
259
-
260
- export function createService(config: ServiceConfig): ReturnType<typeof createServer> {
261
- const startTime = Date.now();
262
-
263
- const server = createServer(async (req, res) => {
264
- const method = req.method ?? 'GET';
265
- const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
266
- const path = url.pathname;
267
-
268
- // CORS headers for cross-origin requests
269
- res.setHeader('Access-Control-Allow-Origin', '*');
270
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
271
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key');
272
-
273
- // Handle preflight
274
- if (method === 'OPTIONS') {
275
- res.writeHead(204);
276
- res.end();
277
- return;
278
- }
279
-
280
- // Authentication check
281
- if (config.apiKey) {
282
- const providedKey = req.headers['x-api-key'] ||
283
- req.headers['authorization']?.replace(/^Bearer\s+/i, '');
284
-
285
- if (providedKey !== config.apiKey) {
286
- sendError(res, 401, 'UNAUTHORIZED', 'Invalid or missing API key');
287
- return;
288
- }
289
- }
290
-
291
- try {
292
- // Route: GET /health
293
- if (method === 'GET' && path === '/health') {
294
- const data = await handleHealth(config, startTime);
295
- sendJson(res, 200, data);
296
- return;
297
- }
298
-
299
- // Route: GET /packages
300
- if (method === 'GET' && path === '/packages') {
301
- const data = await handleListPackages(config);
302
- sendJson(res, 200, data);
303
- return;
304
- }
305
-
306
- // Route: GET /packages/:name
307
- if (method === 'GET' && path.startsWith('/packages/')) {
308
- const packageName = path.slice('/packages/'.length);
309
- const data = await handlePackageInfo(config, packageName);
310
- if (!data) {
311
- sendError(res, 404, 'NOT_FOUND', `Package not found: ${packageName}`);
312
- return;
313
- }
314
- sendJson(res, 200, data);
315
- return;
316
- }
317
-
318
- // Route: POST /run
319
- if (method === 'POST' && path === '/run') {
320
- const body = await parseBody(req);
321
- let request: RunToolRequest;
322
-
323
- try {
324
- request = JSON.parse(body);
325
- } catch {
326
- sendError(res, 400, 'PARSE_ERROR', 'Invalid JSON body');
327
- return;
328
- }
329
-
330
- if (!request.package || typeof request.package !== 'string') {
331
- sendError(res, 400, 'INVALID_INPUT', 'Missing required field: package');
332
- return;
333
- }
334
-
335
- const data = await handleRunTool(config, request);
336
- sendJson(res, data.success ? 200 : 500, data);
337
- return;
338
- }
339
-
340
- // Route: POST /routines/:name/run
341
- if (method === 'POST' && path.match(/^\/routines\/[^/]+\/run$/)) {
342
- const routineName = path.split('/')[2];
343
- const body = await parseBody(req);
344
- let request: RunRoutineRequest;
345
-
346
- try {
347
- request = body ? JSON.parse(body) : {};
348
- request.routine = routineName;
349
- } catch {
350
- sendError(res, 400, 'PARSE_ERROR', 'Invalid JSON body');
351
- return;
352
- }
353
-
354
- const data = await handleRunRoutine(config, request);
355
- if (!data) {
356
- sendError(res, 404, 'NOT_FOUND', `Routine not found: ${routineName}`);
357
- return;
358
- }
359
-
360
- sendJson(res, data.status === 'success' ? 200 : 500, data);
361
- return;
362
- }
363
-
364
- // 404 for unknown routes
365
- sendError(res, 404, 'NOT_FOUND', `Unknown route: ${method} ${path}`);
366
- } catch (err) {
367
- console.error('Request error:', err);
368
- sendError(res, 500, 'API_ERROR', err instanceof Error ? err.message : String(err));
369
- }
370
- });
371
-
372
- return server;
373
- }
374
-
375
- export interface StartServiceOptions {
376
- port?: number;
377
- host?: string;
378
- apiKey?: string;
379
- cwd?: string;
380
- allowedScopes?: ScopeLevel[];
381
- }
382
-
383
- export async function startService(options: StartServiceOptions = {}): Promise<void> {
384
- const config: ServiceConfig = {
385
- port: options.port ?? 4100,
386
- host: options.host ?? '0.0.0.0',
387
- apiKey: options.apiKey,
388
- cwd: options.cwd ?? process.cwd(),
389
- allowedScopes: options.allowedScopes
390
- };
391
-
392
- const server = createService(config);
393
-
394
- return new Promise((resolve, reject) => {
395
- server.on('error', (err: NodeJS.ErrnoException) => {
396
- if (err.code === 'EADDRINUSE') {
397
- reject(new Error(`Port ${config.port} is already in use`));
398
- } else {
399
- reject(err);
400
- }
401
- });
402
-
403
- server.listen(config.port, config.host, () => {
404
- log(`cli4ai service running on http://${config.host}:${config.port}`);
405
- log(`Hostname: ${hostname()}`);
406
- log(`Working directory: ${config.cwd}`);
407
- if (config.apiKey) {
408
- log(`Authentication: API key required`);
409
- } else {
410
- log(`Authentication: None (not recommended for production)`);
411
- }
412
- log('');
413
- log('Endpoints:');
414
- log(' GET /health - Service health check');
415
- log(' GET /packages - List available packages');
416
- log(' GET /packages/:name - Get package info');
417
- log(' POST /run - Execute a tool');
418
- log(' POST /routines/:name/run - Run a routine');
419
- log('');
420
- log('Press Ctrl+C to stop');
421
- });
422
-
423
- // Handle graceful shutdown
424
- const shutdown = () => {
425
- log('\nShutting down...');
426
- server.close(() => {
427
- resolve();
428
- });
429
- };
430
-
431
- process.on('SIGINT', shutdown);
432
- process.on('SIGTERM', shutdown);
433
- });
434
- }