cognitive-modules-cli 2.2.1 → 2.2.7

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 (101) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +35 -29
  4. package/dist/cli.js +519 -23
  5. package/dist/commands/add.d.ts +33 -14
  6. package/dist/commands/add.js +383 -16
  7. package/dist/commands/compose.js +60 -23
  8. package/dist/commands/index.d.ts +4 -0
  9. package/dist/commands/index.js +4 -0
  10. package/dist/commands/init.js +23 -1
  11. package/dist/commands/migrate.d.ts +30 -0
  12. package/dist/commands/migrate.js +650 -0
  13. package/dist/commands/pipe.d.ts +1 -0
  14. package/dist/commands/pipe.js +31 -11
  15. package/dist/commands/remove.js +33 -2
  16. package/dist/commands/run.d.ts +2 -0
  17. package/dist/commands/run.js +61 -28
  18. package/dist/commands/search.d.ts +28 -0
  19. package/dist/commands/search.js +143 -0
  20. package/dist/commands/test.d.ts +65 -0
  21. package/dist/commands/test.js +454 -0
  22. package/dist/commands/update.d.ts +1 -0
  23. package/dist/commands/update.js +106 -14
  24. package/dist/commands/validate.d.ts +36 -0
  25. package/dist/commands/validate.js +97 -0
  26. package/dist/errors/index.d.ts +225 -0
  27. package/dist/errors/index.js +420 -0
  28. package/dist/mcp/server.js +84 -79
  29. package/dist/modules/composition.js +97 -32
  30. package/dist/modules/loader.js +4 -2
  31. package/dist/modules/runner.d.ts +72 -5
  32. package/dist/modules/runner.js +306 -59
  33. package/dist/modules/subagent.d.ts +6 -1
  34. package/dist/modules/subagent.js +18 -13
  35. package/dist/modules/validator.js +14 -6
  36. package/dist/providers/anthropic.d.ts +15 -0
  37. package/dist/providers/anthropic.js +147 -5
  38. package/dist/providers/base.d.ts +11 -0
  39. package/dist/providers/base.js +18 -0
  40. package/dist/providers/gemini.d.ts +15 -0
  41. package/dist/providers/gemini.js +122 -5
  42. package/dist/providers/ollama.d.ts +15 -0
  43. package/dist/providers/ollama.js +111 -3
  44. package/dist/providers/openai.d.ts +11 -0
  45. package/dist/providers/openai.js +133 -0
  46. package/dist/registry/client.d.ts +212 -0
  47. package/dist/registry/client.js +359 -0
  48. package/dist/registry/index.d.ts +4 -0
  49. package/dist/registry/index.js +4 -0
  50. package/dist/registry/tar.d.ts +8 -0
  51. package/dist/registry/tar.js +353 -0
  52. package/dist/server/http.js +301 -45
  53. package/dist/server/index.d.ts +2 -0
  54. package/dist/server/index.js +1 -0
  55. package/dist/server/sse.d.ts +13 -0
  56. package/dist/server/sse.js +22 -0
  57. package/dist/types.d.ts +32 -1
  58. package/dist/types.js +4 -1
  59. package/dist/version.d.ts +1 -0
  60. package/dist/version.js +4 -0
  61. package/package.json +31 -7
  62. package/dist/modules/composition.test.d.ts +0 -11
  63. package/dist/modules/composition.test.js +0 -450
  64. package/dist/modules/policy.test.d.ts +0 -10
  65. package/dist/modules/policy.test.js +0 -369
  66. package/src/cli.ts +0 -471
  67. package/src/commands/add.ts +0 -315
  68. package/src/commands/compose.ts +0 -185
  69. package/src/commands/index.ts +0 -13
  70. package/src/commands/init.ts +0 -94
  71. package/src/commands/list.ts +0 -33
  72. package/src/commands/pipe.ts +0 -76
  73. package/src/commands/remove.ts +0 -57
  74. package/src/commands/run.ts +0 -80
  75. package/src/commands/update.ts +0 -130
  76. package/src/commands/versions.ts +0 -79
  77. package/src/index.ts +0 -90
  78. package/src/mcp/index.ts +0 -5
  79. package/src/mcp/server.ts +0 -403
  80. package/src/modules/composition.test.ts +0 -558
  81. package/src/modules/composition.ts +0 -1674
  82. package/src/modules/index.ts +0 -9
  83. package/src/modules/loader.ts +0 -508
  84. package/src/modules/policy.test.ts +0 -455
  85. package/src/modules/runner.ts +0 -1983
  86. package/src/modules/subagent.ts +0 -277
  87. package/src/modules/validator.ts +0 -700
  88. package/src/providers/anthropic.ts +0 -89
  89. package/src/providers/base.ts +0 -29
  90. package/src/providers/deepseek.ts +0 -83
  91. package/src/providers/gemini.ts +0 -117
  92. package/src/providers/index.ts +0 -78
  93. package/src/providers/minimax.ts +0 -81
  94. package/src/providers/moonshot.ts +0 -82
  95. package/src/providers/ollama.ts +0 -83
  96. package/src/providers/openai.ts +0 -84
  97. package/src/providers/qwen.ts +0 -82
  98. package/src/server/http.ts +0 -316
  99. package/src/server/index.ts +0 -6
  100. package/src/types.ts +0 -599
  101. package/tsconfig.json +0 -17
@@ -0,0 +1,359 @@
1
+ /**
2
+ * Registry Client - Fetch and manage modules from Cognitive Modules Registry
3
+ *
4
+ * Supports both v1 and v2 registry formats.
5
+ *
6
+ * Usage:
7
+ * const client = new RegistryClient();
8
+ * const modules = await client.listModules();
9
+ * const module = await client.getModule('code-reviewer');
10
+ */
11
+ import { existsSync, statSync } from 'node:fs';
12
+ import { writeFile, readFile, mkdir } from 'node:fs/promises';
13
+ import { join } from 'node:path';
14
+ import { homedir } from 'node:os';
15
+ import { createHash } from 'node:crypto';
16
+ // =============================================================================
17
+ // Constants
18
+ // =============================================================================
19
+ const DEFAULT_REGISTRY_URL = 'https://raw.githubusercontent.com/Cognary/cognitive/main/cognitive-registry.v2.json';
20
+ const CACHE_DIR = join(homedir(), '.cognitive', 'cache');
21
+ const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
22
+ const REGISTRY_FETCH_TIMEOUT_MS = 10_000; // 10s
23
+ const MAX_REGISTRY_BYTES = 1024 * 1024; // 1MB
24
+ // =============================================================================
25
+ // Registry Client
26
+ // =============================================================================
27
+ export class RegistryClient {
28
+ registryUrl;
29
+ cache = { data: null, timestamp: 0 };
30
+ constructor(registryUrl = DEFAULT_REGISTRY_URL) {
31
+ this.registryUrl = registryUrl;
32
+ }
33
+ async parseRegistryResponse(response) {
34
+ const contentLengthHeader = response.headers?.get('content-length');
35
+ if (contentLengthHeader) {
36
+ const contentLength = Number(contentLengthHeader);
37
+ if (!Number.isNaN(contentLength) && contentLength > MAX_REGISTRY_BYTES) {
38
+ throw new Error(`Registry payload too large: ${contentLength} bytes (max ${MAX_REGISTRY_BYTES})`);
39
+ }
40
+ }
41
+ if (response.body && typeof response.body.getReader === 'function') {
42
+ const reader = response.body.getReader();
43
+ const decoder = new TextDecoder('utf-8');
44
+ let buffer = '';
45
+ let totalBytes = 0;
46
+ try {
47
+ while (true) {
48
+ const { done, value } = await reader.read();
49
+ if (done)
50
+ break;
51
+ if (value) {
52
+ totalBytes += value.byteLength;
53
+ if (totalBytes > MAX_REGISTRY_BYTES) {
54
+ throw new Error(`Registry payload too large: ${totalBytes} bytes (max ${MAX_REGISTRY_BYTES})`);
55
+ }
56
+ buffer += decoder.decode(value, { stream: true });
57
+ }
58
+ }
59
+ buffer += decoder.decode();
60
+ }
61
+ finally {
62
+ reader.releaseLock();
63
+ }
64
+ try {
65
+ return JSON.parse(buffer);
66
+ }
67
+ catch (error) {
68
+ throw new Error(`Invalid registry JSON: ${error.message}`);
69
+ }
70
+ }
71
+ if (typeof response.text === 'function') {
72
+ const text = await response.text();
73
+ const byteLen = Buffer.byteLength(text, 'utf-8');
74
+ if (byteLen > MAX_REGISTRY_BYTES) {
75
+ throw new Error(`Registry payload too large: ${byteLen} bytes (max ${MAX_REGISTRY_BYTES})`);
76
+ }
77
+ try {
78
+ return JSON.parse(text);
79
+ }
80
+ catch (error) {
81
+ throw new Error(`Invalid registry JSON: ${error.message}`);
82
+ }
83
+ }
84
+ if (typeof response.json === 'function') {
85
+ return await response.json();
86
+ }
87
+ throw new Error('Failed to read registry response body');
88
+ }
89
+ /**
90
+ * Generate a unique cache filename based on registry URL
91
+ */
92
+ getCacheFileName() {
93
+ const hash = createHash('md5').update(this.registryUrl).digest('hex').slice(0, 8);
94
+ return `registry-${hash}.json`;
95
+ }
96
+ /**
97
+ * Fetch registry index (with caching)
98
+ */
99
+ async fetchRegistry(forceRefresh = false) {
100
+ const now = Date.now();
101
+ // Check memory cache
102
+ if (!forceRefresh && this.cache.data && (now - this.cache.timestamp) < CACHE_TTL_MS) {
103
+ return this.cache.data;
104
+ }
105
+ // Check file cache (unique per registry URL)
106
+ const cacheFile = join(CACHE_DIR, this.getCacheFileName());
107
+ if (!forceRefresh && existsSync(cacheFile)) {
108
+ try {
109
+ const stat = statSync(cacheFile);
110
+ if ((now - stat.mtimeMs) < CACHE_TTL_MS) {
111
+ const content = await readFile(cacheFile, 'utf-8');
112
+ const data = JSON.parse(content);
113
+ this.cache = { data, timestamp: now };
114
+ return data;
115
+ }
116
+ }
117
+ catch {
118
+ // Ignore cache read errors
119
+ }
120
+ }
121
+ // Fetch from network
122
+ const controller = new AbortController();
123
+ const timeout = setTimeout(() => controller.abort(), REGISTRY_FETCH_TIMEOUT_MS);
124
+ let data;
125
+ try {
126
+ const response = await fetch(this.registryUrl, {
127
+ headers: { 'User-Agent': 'cognitive-runtime/2.2' },
128
+ signal: controller.signal,
129
+ });
130
+ if (!response.ok) {
131
+ throw new Error(`Failed to fetch registry: ${response.status} ${response.statusText}`);
132
+ }
133
+ data = await this.parseRegistryResponse(response);
134
+ }
135
+ catch (error) {
136
+ if (error instanceof Error && error.name === 'AbortError') {
137
+ throw new Error(`Registry fetch timed out after ${REGISTRY_FETCH_TIMEOUT_MS}ms`);
138
+ }
139
+ throw error;
140
+ }
141
+ finally {
142
+ clearTimeout(timeout);
143
+ }
144
+ // Update cache
145
+ this.cache = { data, timestamp: now };
146
+ // Save to file cache
147
+ try {
148
+ await mkdir(CACHE_DIR, { recursive: true });
149
+ await writeFile(cacheFile, JSON.stringify(data, null, 2));
150
+ }
151
+ catch {
152
+ // Ignore cache write errors
153
+ }
154
+ return data;
155
+ }
156
+ /**
157
+ * Check if registry is v2 format
158
+ */
159
+ isV2Registry(registry) {
160
+ const firstModule = Object.values(registry.modules)[0];
161
+ return firstModule && 'identity' in firstModule;
162
+ }
163
+ /**
164
+ * Normalize module entry to unified format
165
+ */
166
+ normalizeModule(name, entry) {
167
+ if ('identity' in entry) {
168
+ // v2 format
169
+ const v2 = entry;
170
+ return {
171
+ name: v2.identity.name,
172
+ version: v2.identity.version,
173
+ description: v2.metadata.description,
174
+ author: v2.metadata.author,
175
+ source: v2.distribution.tarball,
176
+ tarball: v2.distribution.tarball,
177
+ checksum: v2.distribution.checksum,
178
+ keywords: v2.metadata.keywords || [],
179
+ tier: v2.metadata.tier,
180
+ namespace: v2.identity.namespace,
181
+ license: v2.metadata.license,
182
+ repository: v2.metadata.repository,
183
+ conformance_level: v2.quality?.conformance_level,
184
+ verified: v2.quality?.verified,
185
+ deprecated: v2.quality?.deprecated,
186
+ };
187
+ }
188
+ else {
189
+ // v1 format
190
+ const v1 = entry;
191
+ return {
192
+ name,
193
+ version: v1.version,
194
+ description: v1.description,
195
+ author: v1.author,
196
+ source: v1.source,
197
+ keywords: v1.tags || [],
198
+ };
199
+ }
200
+ }
201
+ /**
202
+ * List all modules in registry
203
+ */
204
+ async listModules() {
205
+ const registry = await this.fetchRegistry();
206
+ return Object.entries(registry.modules).map(([name, entry]) => this.normalizeModule(name, entry));
207
+ }
208
+ /**
209
+ * Get a specific module by name
210
+ */
211
+ async getModule(name) {
212
+ const registry = await this.fetchRegistry();
213
+ const entry = registry.modules[name];
214
+ if (!entry) {
215
+ return null;
216
+ }
217
+ return this.normalizeModule(name, entry);
218
+ }
219
+ /**
220
+ * Search modules by query
221
+ */
222
+ async search(query) {
223
+ const modules = await this.listModules();
224
+ // If query is empty, return all modules sorted by name
225
+ if (!query.trim()) {
226
+ return modules
227
+ .map(m => ({
228
+ name: m.name,
229
+ description: m.description,
230
+ version: m.version,
231
+ score: 1,
232
+ keywords: m.keywords,
233
+ }))
234
+ .sort((a, b) => a.name.localeCompare(b.name));
235
+ }
236
+ const queryLower = query.toLowerCase().trim();
237
+ const queryTerms = queryLower.split(/\s+/).filter(t => t.length > 0);
238
+ const results = [];
239
+ for (const module of modules) {
240
+ let score = 0;
241
+ // Name match (highest weight)
242
+ if (module.name.toLowerCase().includes(queryLower)) {
243
+ score += 10;
244
+ if (module.name.toLowerCase() === queryLower) {
245
+ score += 5;
246
+ }
247
+ }
248
+ // Description match
249
+ const descLower = module.description.toLowerCase();
250
+ for (const term of queryTerms) {
251
+ if (descLower.includes(term)) {
252
+ score += 3;
253
+ }
254
+ }
255
+ // Keyword match
256
+ for (const keyword of module.keywords) {
257
+ const keywordLower = keyword.toLowerCase();
258
+ for (const term of queryTerms) {
259
+ if (keywordLower.includes(term) || term.includes(keywordLower)) {
260
+ score += 2;
261
+ }
262
+ }
263
+ }
264
+ if (score > 0) {
265
+ results.push({
266
+ name: module.name,
267
+ description: module.description,
268
+ version: module.version,
269
+ score,
270
+ keywords: module.keywords,
271
+ });
272
+ }
273
+ }
274
+ // Sort by score descending
275
+ results.sort((a, b) => b.score - a.score);
276
+ return results;
277
+ }
278
+ /**
279
+ * Get categories
280
+ */
281
+ async getCategories() {
282
+ const registry = await this.fetchRegistry();
283
+ return registry.categories || {};
284
+ }
285
+ /**
286
+ * Parse GitHub source string
287
+ * Format: github:<owner>/<repo>[/<path>][@<ref>]
288
+ */
289
+ parseGitHubSource(source) {
290
+ if (!source.startsWith('github:')) {
291
+ return null;
292
+ }
293
+ const rest = source.slice('github:'.length);
294
+ // Split ref if present
295
+ const [pathPart, ref] = rest.split('@');
296
+ // Parse owner/repo/path
297
+ const parts = pathPart.split('/');
298
+ if (parts.length < 2) {
299
+ return null;
300
+ }
301
+ const org = parts[0];
302
+ const repo = parts[1];
303
+ const modulePath = parts.length > 2 ? parts.slice(2).join('/') : undefined;
304
+ return { org, repo, path: modulePath, ref };
305
+ }
306
+ /**
307
+ * Verify checksum of downloaded file
308
+ */
309
+ async verifyChecksum(filePath, expected) {
310
+ const [algo, expectedHash] = expected.split(':');
311
+ if (!algo || !expectedHash) {
312
+ throw new Error(`Invalid checksum format: ${expected}`);
313
+ }
314
+ const content = await readFile(filePath);
315
+ const actualHash = createHash(algo).update(content).digest('hex');
316
+ return actualHash === expectedHash;
317
+ }
318
+ /**
319
+ * Get the download URL for a module
320
+ */
321
+ async getDownloadUrl(moduleName) {
322
+ const module = await this.getModule(moduleName);
323
+ if (!module) {
324
+ throw new Error(`Module not found in registry: ${moduleName}`);
325
+ }
326
+ const source = module.source;
327
+ // Check if it's a GitHub source
328
+ const githubInfo = this.parseGitHubSource(source);
329
+ if (githubInfo) {
330
+ return {
331
+ url: `https://github.com/${githubInfo.org}/${githubInfo.repo}`,
332
+ isGitHub: true,
333
+ githubInfo,
334
+ };
335
+ }
336
+ // Check if it's a tarball URL
337
+ if (source.startsWith('http://') || source.startsWith('https://')) {
338
+ return {
339
+ url: source,
340
+ checksum: module.checksum,
341
+ isGitHub: false,
342
+ };
343
+ }
344
+ throw new Error(`Unknown source format: ${source}`);
345
+ }
346
+ }
347
+ // =============================================================================
348
+ // Exports
349
+ // =============================================================================
350
+ export const defaultRegistry = new RegistryClient();
351
+ export async function listRegistryModules() {
352
+ return defaultRegistry.listModules();
353
+ }
354
+ export async function getRegistryModule(name) {
355
+ return defaultRegistry.getModule(name);
356
+ }
357
+ export async function searchRegistry(query) {
358
+ return defaultRegistry.search(query);
359
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Registry Module - Re-export all registry functionality
3
+ */
4
+ export * from './client.js';
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Registry Module - Re-export all registry functionality
3
+ */
4
+ export * from './client.js';
@@ -0,0 +1,8 @@
1
+ export interface ExtractTarGzOptions {
2
+ maxFiles?: number;
3
+ maxTotalBytes?: number;
4
+ maxSingleFileBytes?: number;
5
+ maxTarBytes?: number;
6
+ }
7
+ export declare function extractTarGzFile(tarGzPath: string, destDir: string, options?: ExtractTarGzOptions): Promise<string[]>;
8
+ export declare function extractTarGzBuffer(gzBuffer: Buffer, destDir: string, options?: ExtractTarGzOptions): Promise<string[]>;