cli4ai 1.2.0 → 1.2.2

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 +464 -0
  8. package/dist/commands/browse.d.ts +4 -0
  9. package/dist/commands/browse.js +382 -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 +125 -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 +162 -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,327 +0,0 @@
1
- /**
2
- * cli4ai.json manifest validation and parsing
3
- */
4
-
5
- import { readFileSync, existsSync } from 'fs';
6
- import { resolve, dirname, basename } from 'path';
7
- import { outputError } from '../lib/cli.js';
8
-
9
- // ═══════════════════════════════════════════════════════════════════════════
10
- // ERRORS
11
- // ═══════════════════════════════════════════════════════════════════════════
12
-
13
- export class ManifestValidationError extends Error {
14
- constructor(
15
- message: string,
16
- public details?: Record<string, unknown>
17
- ) {
18
- super(message);
19
- this.name = 'ManifestValidationError';
20
- }
21
- }
22
-
23
- // ═══════════════════════════════════════════════════════════════════════════
24
- // TYPES
25
- // ═══════════════════════════════════════════════════════════════════════════
26
-
27
- export interface CommandArg {
28
- name: string;
29
- description?: string;
30
- required?: boolean;
31
- type?: 'string' | 'number' | 'boolean';
32
- default?: string | number | boolean;
33
- }
34
-
35
- export interface CommandOption {
36
- name: string;
37
- short?: string;
38
- description?: string;
39
- type?: 'string' | 'number' | 'boolean';
40
- default?: string | number | boolean;
41
- }
42
-
43
- export interface CommandDef {
44
- description: string;
45
- args?: CommandArg[];
46
- options?: CommandOption[];
47
- }
48
-
49
- export interface EnvDef {
50
- required?: boolean;
51
- description?: string;
52
- default?: string;
53
- secret?: boolean;
54
- }
55
-
56
- export interface McpConfig {
57
- enabled?: boolean;
58
- transport?: 'stdio' | 'http';
59
- port?: number;
60
- }
61
-
62
- export interface Manifest {
63
- // Required fields
64
- name: string;
65
- version: string;
66
- entry: string;
67
-
68
- // Optional metadata
69
- description?: string;
70
- author?: string;
71
- license?: string;
72
- repository?: string;
73
- homepage?: string;
74
- keywords?: string[];
75
-
76
- // Runtime (node is default, bun kept for backwards compatibility)
77
- runtime?: 'node' | 'bun';
78
-
79
- // Commands (for MCP generation)
80
- commands?: Record<string, CommandDef>;
81
-
82
- // Environment variables
83
- env?: Record<string, EnvDef>;
84
-
85
- // Dependencies
86
- dependencies?: Record<string, string>;
87
- peerDependencies?: Record<string, string>;
88
-
89
- // MCP configuration
90
- mcp?: McpConfig;
91
-
92
- // Scripts
93
- scripts?: Record<string, string>;
94
-
95
- // Files to include when publishing
96
- files?: string[];
97
- }
98
-
99
- // ═══════════════════════════════════════════════════════════════════════════
100
- // VALIDATION
101
- // ═══════════════════════════════════════════════════════════════════════════
102
-
103
- const NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
104
- const VERSION_PATTERN = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/;
105
-
106
- export function validateManifest(manifest: unknown, source?: string): Manifest {
107
- if (!manifest || typeof manifest !== 'object') {
108
- throw new ManifestValidationError('Manifest must be an object', { source });
109
- }
110
-
111
- const m = manifest as Record<string, unknown>;
112
-
113
- // Required: name
114
- if (typeof m.name !== 'string' || !NAME_PATTERN.test(m.name)) {
115
- throw new ManifestValidationError('Invalid or missing "name" (lowercase letters, numbers, hyphens)', {
116
- source,
117
- got: m.name
118
- });
119
- }
120
-
121
- // Required: version
122
- if (typeof m.version !== 'string' || !VERSION_PATTERN.test(m.version)) {
123
- throw new ManifestValidationError('Invalid or missing "version" (semver format: x.y.z)', {
124
- source,
125
- got: m.version
126
- });
127
- }
128
-
129
- // Required: entry
130
- if (typeof m.entry !== 'string' || m.entry.length === 0) {
131
- throw new ManifestValidationError('Invalid or missing "entry" (path to main file)', {
132
- source,
133
- got: m.entry
134
- });
135
- }
136
-
137
- // SECURITY: Validate entry is a relative path that stays within package directory
138
- if (m.entry.startsWith('/') || m.entry.startsWith('\\') || m.entry.includes('..')) {
139
- throw new ManifestValidationError(
140
- 'Invalid "entry" - must be a relative path without ".." (security: path traversal)',
141
- { source, got: m.entry }
142
- );
143
- }
144
-
145
- // Optional: runtime (deno not supported)
146
- if (m.runtime !== undefined && !['bun', 'node'].includes(m.runtime as string)) {
147
- throw new ManifestValidationError('Invalid "runtime" (must be bun or node)', {
148
- source,
149
- got: m.runtime
150
- });
151
- }
152
-
153
- // Optional: commands
154
- if (m.commands !== undefined) {
155
- if (typeof m.commands !== 'object' || m.commands === null) {
156
- throw new ManifestValidationError('Invalid "commands" (must be an object)', { source });
157
- }
158
- for (const [cmdName, cmdDef] of Object.entries(m.commands as object)) {
159
- if (typeof cmdDef !== 'object' || cmdDef === null) {
160
- throw new ManifestValidationError(`Invalid command definition for "${cmdName}"`, { source });
161
- }
162
- const def = cmdDef as Record<string, unknown>;
163
- if (typeof def.description !== 'string') {
164
- throw new ManifestValidationError(`Command "${cmdName}" missing description`, { source });
165
- }
166
- }
167
- }
168
-
169
- return manifest as Manifest;
170
- }
171
-
172
- // ═══════════════════════════════════════════════════════════════════════════
173
- // LOADING
174
- // ═══════════════════════════════════════════════════════════════════════════
175
-
176
- export const MANIFEST_FILENAME = 'cli4ai.json';
177
-
178
- export class ManifestNotFoundError extends Error {
179
- constructor(public path: string) {
180
- super(`No ${MANIFEST_FILENAME} found at ${path}`);
181
- this.name = 'ManifestNotFoundError';
182
- }
183
- }
184
-
185
- export class ManifestParseError extends Error {
186
- constructor(public path: string, message: string) {
187
- super(message);
188
- this.name = 'ManifestParseError';
189
- }
190
- }
191
-
192
- /**
193
- * Load and validate cli4ai.json from a directory (throws on error)
194
- * Use this for programmatic/testable usage
195
- */
196
- export function loadManifestOrThrow(dir: string): Manifest {
197
- const manifestPath = resolve(dir, MANIFEST_FILENAME);
198
-
199
- if (!existsSync(manifestPath)) {
200
- throw new ManifestNotFoundError(manifestPath);
201
- }
202
-
203
- let content: string;
204
- try {
205
- content = readFileSync(manifestPath, 'utf-8');
206
- } catch (err) {
207
- throw new ManifestParseError(manifestPath, `Failed to read: ${err instanceof Error ? err.message : String(err)}`);
208
- }
209
-
210
- let data: unknown;
211
- try {
212
- data = JSON.parse(content);
213
- } catch {
214
- throw new ManifestParseError(manifestPath, 'Invalid JSON');
215
- }
216
-
217
- return validateManifest(data, manifestPath);
218
- }
219
-
220
- /**
221
- * Load manifest from package.json (fallback for npm packages)
222
- */
223
- export function loadFromPackageJson(dir: string): Manifest | null {
224
- const pkgJsonPath = resolve(dir, 'package.json');
225
- if (!existsSync(pkgJsonPath)) return null;
226
-
227
- try {
228
- const content = readFileSync(pkgJsonPath, 'utf-8');
229
- const pkg = JSON.parse(content);
230
-
231
- // Extract name without @cli4ai/ scope
232
- const name = pkg.name?.replace('@cli4ai/', '') || basename(dir);
233
-
234
- return {
235
- name,
236
- version: pkg.version || '1.0.0',
237
- entry: pkg.main || 'run.ts',
238
- description: pkg.description,
239
- author: pkg.author,
240
- license: pkg.license,
241
- runtime: 'node',
242
- keywords: pkg.keywords
243
- };
244
- } catch {
245
- return null;
246
- }
247
- }
248
-
249
- /**
250
- * Load and validate cli4ai.json from a directory
251
- * Falls back to package.json for npm packages
252
- * Exits with error on failure (for CLI usage)
253
- */
254
- export function loadManifest(dir: string): Manifest {
255
- // Try cli4ai.json first
256
- try {
257
- return loadManifestOrThrow(dir);
258
- } catch (err) {
259
- if (err instanceof ManifestNotFoundError) {
260
- // Fallback to package.json
261
- const fromPkgJson = loadFromPackageJson(dir);
262
- if (fromPkgJson) return fromPkgJson;
263
-
264
- outputError('NOT_FOUND', `No ${MANIFEST_FILENAME} found`, {
265
- path: err.path,
266
- hint: 'Run "cli4ai init" to create one'
267
- });
268
- }
269
- if (err instanceof ManifestParseError) {
270
- outputError('PARSE_ERROR', err.message, { path: err.path });
271
- }
272
- if (err instanceof ManifestValidationError) {
273
- outputError('MANIFEST_ERROR', err.message, err.details);
274
- }
275
- throw err;
276
- }
277
- }
278
-
279
- /**
280
- * Try to load manifest, return null if not found
281
- */
282
- export function tryLoadManifest(dir: string): Manifest | null {
283
- const manifestPath = resolve(dir, MANIFEST_FILENAME);
284
- if (!existsSync(manifestPath)) return null;
285
-
286
- try {
287
- const content = readFileSync(manifestPath, 'utf-8');
288
- const data = JSON.parse(content);
289
- return validateManifest(data, manifestPath);
290
- } catch {
291
- return null;
292
- }
293
- }
294
-
295
- /**
296
- * Find manifest by walking up directory tree
297
- */
298
- export function findManifest(startDir?: string): { manifest: Manifest; dir: string } | null {
299
- let dir = startDir || process.cwd();
300
-
301
- for (let i = 0; i < 10; i++) {
302
- const manifest = tryLoadManifest(dir);
303
- if (manifest) {
304
- return { manifest, dir };
305
- }
306
-
307
- const parent = dirname(dir);
308
- if (parent === dir) break;
309
- dir = parent;
310
- }
311
-
312
- return null;
313
- }
314
-
315
- /**
316
- * Create a minimal manifest
317
- */
318
- export function createManifest(name: string, options: Partial<Manifest> = {}): Manifest {
319
- return {
320
- name: name.toLowerCase().replace(/[^a-z0-9-]/g, '-'),
321
- version: '1.0.0',
322
- entry: 'run.ts',
323
- runtime: 'node',
324
- description: options.description || `${name} tool`,
325
- ...options
326
- };
327
- }
@@ -1,165 +0,0 @@
1
- /**
2
- * Package Registry
3
- *
4
- * Fetches and caches package metadata from cli4ai.com registry.
5
- * Provides integrity hashes for verification.
6
- */
7
-
8
- import { createHash } from 'crypto';
9
- import { readdirSync, readFileSync, existsSync } from 'fs';
10
- import { join } from 'path';
11
-
12
- const REGISTRY_URL = 'https://cli4ai.com/registry/packages.json';
13
- const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
14
-
15
- interface RegistryPackage {
16
- name: string;
17
- version: string;
18
- description: string;
19
- integrity: string;
20
- readme: string;
21
- commands: Record<string, { description: string; args?: Array<{ name: string; required: boolean }> }>;
22
- dependencies: Record<string, string>;
23
- env: Record<string, { required: boolean; description: string }>;
24
- category: string;
25
- keywords: string[];
26
- author: string;
27
- license: string;
28
- runtime: string;
29
- mcp: boolean;
30
- repository: string;
31
- homepage: string;
32
- npm: string;
33
- updatedAt: string;
34
- }
35
-
36
- interface Registry {
37
- version: number;
38
- generatedAt: string;
39
- packages: RegistryPackage[];
40
- }
41
-
42
- // In-memory cache
43
- let cachedRegistry: Registry | null = null;
44
- let cacheTimestamp = 0;
45
-
46
- /**
47
- * Fetch registry from cli4ai.com
48
- */
49
- export async function fetchRegistry(): Promise<Registry | null> {
50
- // Return cached if fresh
51
- if (cachedRegistry && Date.now() - cacheTimestamp < CACHE_TTL) {
52
- return cachedRegistry;
53
- }
54
-
55
- try {
56
- const response = await fetch(REGISTRY_URL, {
57
- headers: { 'Accept': 'application/json' },
58
- signal: AbortSignal.timeout(10000), // 10 second timeout
59
- });
60
-
61
- if (!response.ok) {
62
- return null;
63
- }
64
-
65
- const registry = await response.json() as Registry;
66
- cachedRegistry = registry;
67
- cacheTimestamp = Date.now();
68
- return registry;
69
- } catch {
70
- // Return cached even if stale, or null if no cache
71
- return cachedRegistry;
72
- }
73
- }
74
-
75
- /**
76
- * Get package info from registry
77
- */
78
- export async function getRegistryPackage(name: string): Promise<RegistryPackage | null> {
79
- const registry = await fetchRegistry();
80
- if (!registry) return null;
81
-
82
- return registry.packages.find(p => p.name === name) || null;
83
- }
84
-
85
- /**
86
- * Get integrity hash for a package from registry
87
- */
88
- export async function getRegistryIntegrity(name: string): Promise<string | null> {
89
- const pkg = await getRegistryPackage(name);
90
- return pkg?.integrity || null;
91
- }
92
-
93
- /**
94
- * Get all package names from registry
95
- */
96
- export async function getRegistryPackageNames(): Promise<string[]> {
97
- const registry = await fetchRegistry();
98
- if (!registry) return [];
99
- return registry.packages.map(p => p.name);
100
- }
101
-
102
- /**
103
- * Compute SHA-512 integrity hash for a directory
104
- * Same algorithm as used by generate-registry.ts
105
- */
106
- export function computeDirectoryIntegrity(dirPath: string): string {
107
- const hash = createHash('sha512');
108
-
109
- function processDir(dir: string) {
110
- const entries = readdirSync(dir, { withFileTypes: true }).sort((a, b) =>
111
- a.name.localeCompare(b.name)
112
- );
113
-
114
- for (const entry of entries) {
115
- const fullPath = join(dir, entry.name);
116
-
117
- // Skip node_modules and hidden files
118
- if (entry.name === 'node_modules' || entry.name.startsWith('.')) {
119
- continue;
120
- }
121
-
122
- if (entry.isDirectory()) {
123
- processDir(fullPath);
124
- } else if (entry.isFile()) {
125
- // Include relative path in hash for structure integrity
126
- const relativePath = fullPath.slice(dirPath.length + 1);
127
- hash.update(relativePath);
128
- hash.update(readFileSync(fullPath));
129
- }
130
- }
131
- }
132
-
133
- processDir(dirPath);
134
- return `sha512-${hash.digest('base64')}`;
135
- }
136
-
137
- /**
138
- * Verify package integrity against registry
139
- */
140
- export async function verifyPackageIntegrity(
141
- packageName: string,
142
- packagePath: string
143
- ): Promise<{ valid: boolean; expected: string | null; actual: string }> {
144
- const expected = await getRegistryIntegrity(packageName);
145
- const actual = computeDirectoryIntegrity(packagePath);
146
-
147
- if (!expected) {
148
- // Package not in registry, can't verify
149
- return { valid: true, expected: null, actual };
150
- }
151
-
152
- return {
153
- valid: expected === actual,
154
- expected,
155
- actual,
156
- };
157
- }
158
-
159
- /**
160
- * Clear the registry cache
161
- */
162
- export function clearRegistryCache(): void {
163
- cachedRegistry = null;
164
- cacheTimestamp = 0;
165
- }