cognitive-modules-cli 2.2.0 → 2.2.5

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 (94) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +35 -29
  4. package/dist/cli.js +572 -28
  5. package/dist/commands/add.d.ts +33 -14
  6. package/dist/commands/add.js +222 -13
  7. package/dist/commands/compose.d.ts +31 -0
  8. package/dist/commands/compose.js +185 -0
  9. package/dist/commands/index.d.ts +5 -0
  10. package/dist/commands/index.js +5 -0
  11. package/dist/commands/init.js +23 -1
  12. package/dist/commands/migrate.d.ts +30 -0
  13. package/dist/commands/migrate.js +650 -0
  14. package/dist/commands/pipe.d.ts +1 -0
  15. package/dist/commands/pipe.js +31 -11
  16. package/dist/commands/remove.js +33 -2
  17. package/dist/commands/run.d.ts +1 -0
  18. package/dist/commands/run.js +37 -27
  19. package/dist/commands/search.d.ts +28 -0
  20. package/dist/commands/search.js +143 -0
  21. package/dist/commands/test.d.ts +65 -0
  22. package/dist/commands/test.js +454 -0
  23. package/dist/commands/update.d.ts +1 -0
  24. package/dist/commands/update.js +106 -14
  25. package/dist/commands/validate.d.ts +36 -0
  26. package/dist/commands/validate.js +97 -0
  27. package/dist/errors/index.d.ts +218 -0
  28. package/dist/errors/index.js +412 -0
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.js +5 -1
  31. package/dist/mcp/server.js +84 -79
  32. package/dist/modules/composition.d.ts +251 -0
  33. package/dist/modules/composition.js +1330 -0
  34. package/dist/modules/index.d.ts +2 -0
  35. package/dist/modules/index.js +2 -0
  36. package/dist/modules/loader.d.ts +22 -2
  37. package/dist/modules/loader.js +171 -6
  38. package/dist/modules/runner.d.ts +422 -1
  39. package/dist/modules/runner.js +1472 -71
  40. package/dist/modules/subagent.d.ts +6 -1
  41. package/dist/modules/subagent.js +20 -13
  42. package/dist/modules/validator.d.ts +28 -0
  43. package/dist/modules/validator.js +637 -0
  44. package/dist/providers/anthropic.d.ts +15 -0
  45. package/dist/providers/anthropic.js +147 -5
  46. package/dist/providers/base.d.ts +11 -0
  47. package/dist/providers/base.js +18 -0
  48. package/dist/providers/gemini.d.ts +15 -0
  49. package/dist/providers/gemini.js +122 -5
  50. package/dist/providers/ollama.d.ts +15 -0
  51. package/dist/providers/ollama.js +111 -3
  52. package/dist/providers/openai.d.ts +11 -0
  53. package/dist/providers/openai.js +133 -0
  54. package/dist/registry/client.d.ts +204 -0
  55. package/dist/registry/client.js +356 -0
  56. package/dist/registry/index.d.ts +4 -0
  57. package/dist/registry/index.js +4 -0
  58. package/dist/server/http.js +173 -42
  59. package/dist/types.d.ts +123 -8
  60. package/dist/types.js +4 -1
  61. package/dist/version.d.ts +1 -0
  62. package/dist/version.js +4 -0
  63. package/package.json +32 -7
  64. package/src/cli.ts +0 -410
  65. package/src/commands/add.ts +0 -315
  66. package/src/commands/index.ts +0 -12
  67. package/src/commands/init.ts +0 -94
  68. package/src/commands/list.ts +0 -33
  69. package/src/commands/pipe.ts +0 -76
  70. package/src/commands/remove.ts +0 -57
  71. package/src/commands/run.ts +0 -80
  72. package/src/commands/update.ts +0 -130
  73. package/src/commands/versions.ts +0 -79
  74. package/src/index.ts +0 -55
  75. package/src/mcp/index.ts +0 -5
  76. package/src/mcp/server.ts +0 -403
  77. package/src/modules/index.ts +0 -7
  78. package/src/modules/loader.ts +0 -318
  79. package/src/modules/runner.ts +0 -495
  80. package/src/modules/subagent.ts +0 -275
  81. package/src/providers/anthropic.ts +0 -89
  82. package/src/providers/base.ts +0 -29
  83. package/src/providers/deepseek.ts +0 -83
  84. package/src/providers/gemini.ts +0 -117
  85. package/src/providers/index.ts +0 -78
  86. package/src/providers/minimax.ts +0 -81
  87. package/src/providers/moonshot.ts +0 -82
  88. package/src/providers/ollama.ts +0 -83
  89. package/src/providers/openai.ts +0 -84
  90. package/src/providers/qwen.ts +0 -82
  91. package/src/server/http.ts +0 -316
  92. package/src/server/index.ts +0 -6
  93. package/src/types.ts +0 -495
  94. package/tsconfig.json +0 -17
@@ -1,8 +1,13 @@
1
1
  /**
2
- * Add command - Install modules from GitHub
2
+ * Add command - Install modules from GitHub or Registry
3
3
  *
4
- * cog add ziel-io/cognitive-modules -m code-simplifier
5
- * cog add https://github.com/org/repo --module name --tag v1.0.0
4
+ * From Registry:
5
+ * cog add code-reviewer
6
+ * cog add code-reviewer@2.0.0
7
+ *
8
+ * From GitHub:
9
+ * cog add ziel-io/cognitive-modules -m code-simplifier
10
+ * cog add https://github.com/org/repo --module name --tag v1.0.0
6
11
  */
7
12
  import type { CommandContext, CommandResult } from '../types.js';
8
13
  export interface AddOptions {
@@ -10,25 +15,39 @@ export interface AddOptions {
10
15
  name?: string;
11
16
  branch?: string;
12
17
  tag?: string;
18
+ registry?: string;
19
+ }
20
+ export interface InstallInfo {
21
+ source: string;
22
+ githubUrl: string;
23
+ modulePath?: string;
24
+ tag?: string;
25
+ branch?: string;
26
+ version?: string;
27
+ installedAt: string;
28
+ installedTime: string;
29
+ /** Registry module name if installed from registry */
30
+ registryModule?: string;
31
+ /** Registry URL if installed from a custom registry */
32
+ registryUrl?: string;
13
33
  }
14
34
  interface InstallManifest {
15
- [moduleName: string]: {
16
- source: string;
17
- githubUrl: string;
18
- modulePath?: string;
19
- tag?: string;
20
- branch?: string;
21
- version?: string;
22
- installedAt: string;
23
- installedTime: string;
24
- };
35
+ [moduleName: string]: InstallInfo;
25
36
  }
26
37
  /**
27
38
  * Get installation info for a module
28
39
  */
29
40
  export declare function getInstallInfo(moduleName: string): Promise<InstallManifest[string] | null>;
41
+ /**
42
+ * Add a module from the registry
43
+ */
44
+ export declare function addFromRegistry(moduleSpec: string, ctx: CommandContext, options?: AddOptions): Promise<CommandResult>;
30
45
  /**
31
46
  * Add a module from GitHub
32
47
  */
33
- export declare function add(url: string, ctx: CommandContext, options?: AddOptions): Promise<CommandResult>;
48
+ export declare function addFromGitHub(url: string, ctx: CommandContext, options?: AddOptions): Promise<CommandResult>;
49
+ /**
50
+ * Add a module from GitHub or Registry (auto-detect source)
51
+ */
52
+ export declare function add(source: string, ctx: CommandContext, options?: AddOptions): Promise<CommandResult>;
34
53
  export {};
@@ -1,15 +1,21 @@
1
1
  /**
2
- * Add command - Install modules from GitHub
2
+ * Add command - Install modules from GitHub or Registry
3
3
  *
4
- * cog add ziel-io/cognitive-modules -m code-simplifier
5
- * cog add https://github.com/org/repo --module name --tag v1.0.0
4
+ * From Registry:
5
+ * cog add code-reviewer
6
+ * cog add code-reviewer@2.0.0
7
+ *
8
+ * From GitHub:
9
+ * cog add ziel-io/cognitive-modules -m code-simplifier
10
+ * cog add https://github.com/org/repo --module name --tag v1.0.0
6
11
  */
7
12
  import { createWriteStream, existsSync, mkdirSync, rmSync, readdirSync, statSync, copyFileSync } from 'node:fs';
8
13
  import { writeFile, readFile, mkdir } from 'node:fs/promises';
9
14
  import { pipeline } from 'node:stream/promises';
10
15
  import { Readable } from 'node:stream';
11
- import { join, basename } from 'node:path';
16
+ import { join, basename, dirname, resolve, sep, isAbsolute } from 'node:path';
12
17
  import { homedir, tmpdir } from 'node:os';
18
+ import { RegistryClient } from '../registry/client.js';
13
19
  // Module storage paths
14
20
  const USER_MODULES_DIR = join(homedir(), '.cognitive', 'modules');
15
21
  const INSTALLED_MANIFEST = join(homedir(), '.cognitive', 'installed.json');
@@ -33,6 +39,33 @@ function parseGitHubUrl(url) {
33
39
  fullUrl: `https://github.com/${org}/${repo}`,
34
40
  };
35
41
  }
42
+ function assertSafeModuleName(name) {
43
+ const trimmed = name.trim();
44
+ if (!trimmed) {
45
+ throw new Error('Invalid module name: empty');
46
+ }
47
+ if (trimmed.includes('..') || trimmed.includes('/') || trimmed.includes('\\')) {
48
+ throw new Error(`Invalid module name: ${name}`);
49
+ }
50
+ if (isAbsolute(trimmed)) {
51
+ throw new Error(`Invalid module name (absolute path not allowed): ${name}`);
52
+ }
53
+ return trimmed;
54
+ }
55
+ function resolveModuleTarget(moduleName) {
56
+ const safeName = assertSafeModuleName(moduleName);
57
+ const targetPath = resolve(USER_MODULES_DIR, safeName);
58
+ const root = resolve(USER_MODULES_DIR) + sep;
59
+ if (!targetPath.startsWith(root)) {
60
+ throw new Error(`Invalid module name (path traversal): ${moduleName}`);
61
+ }
62
+ return targetPath;
63
+ }
64
+ function isPathWithinRoot(rootDir, targetPath) {
65
+ const root = resolve(rootDir);
66
+ const target = resolve(targetPath);
67
+ return target === root || target.startsWith(root + sep);
68
+ }
36
69
  /**
37
70
  * Download and extract ZIP from GitHub
38
71
  */
@@ -56,6 +89,9 @@ async function downloadAndExtract(org, repo, ref, isTag) {
56
89
  await pipeline(Readable.fromWeb(response.body), fileStream);
57
90
  // Extract using built-in unzip (available on most systems)
58
91
  const { execSync } = await import('node:child_process');
92
+ // Validate ZIP entries to avoid path traversal (Zip Slip)
93
+ const listing = execSync(`unzip -Z1 "${zipPath}"`, { stdio: 'pipe' }).toString('utf-8');
94
+ assertSafeZipEntries(listing);
59
95
  execSync(`unzip -q "${zipPath}" -d "${tempDir}"`, { stdio: 'pipe' });
60
96
  // Find extracted directory
61
97
  const entries = readdirSync(tempDir).filter(e => e !== 'repo.zip' && statSync(join(tempDir, e)).isDirectory());
@@ -64,6 +100,22 @@ async function downloadAndExtract(org, repo, ref, isTag) {
64
100
  }
65
101
  return join(tempDir, entries[0]);
66
102
  }
103
+ /**
104
+ * Validate ZIP listing to prevent path traversal.
105
+ */
106
+ function assertSafeZipEntries(listing) {
107
+ const entries = listing.split(/\r?\n/).map(e => e.trim()).filter(Boolean);
108
+ for (const entry of entries) {
109
+ const normalized = entry.replace(/\\/g, '/');
110
+ if (normalized.startsWith('/') || /^[a-zA-Z]:\//.test(normalized)) {
111
+ throw new Error(`Unsafe ZIP entry (absolute path): ${entry}`);
112
+ }
113
+ const parts = normalized.split('/');
114
+ if (parts.includes('..')) {
115
+ throw new Error(`Unsafe ZIP entry (path traversal): ${entry}`);
116
+ }
117
+ }
118
+ }
67
119
  /**
68
120
  * Check if a directory is a valid module
69
121
  */
@@ -76,11 +128,15 @@ function isValidModule(path) {
76
128
  * Find module within repository
77
129
  */
78
130
  function findModuleInRepo(repoRoot, modulePath) {
79
- const possiblePaths = [
80
- join(repoRoot, modulePath),
81
- join(repoRoot, 'cognitive', 'modules', modulePath),
82
- join(repoRoot, 'modules', modulePath),
131
+ const candidatePaths = [
132
+ resolve(repoRoot, modulePath),
133
+ resolve(repoRoot, 'cognitive', 'modules', modulePath),
134
+ resolve(repoRoot, 'modules', modulePath),
83
135
  ];
136
+ const possiblePaths = candidatePaths.filter((p) => isPathWithinRoot(repoRoot, p));
137
+ if (possiblePaths.length === 0) {
138
+ throw new Error(`Invalid module path (outside repository root): ${modulePath}`);
139
+ }
84
140
  for (const p of possiblePaths) {
85
141
  if (existsSync(p) && isValidModule(p)) {
86
142
  return p;
@@ -157,10 +213,135 @@ export async function getInstallInfo(moduleName) {
157
213
  const manifest = JSON.parse(content);
158
214
  return manifest[moduleName] || null;
159
215
  }
216
+ /**
217
+ * Check if input looks like a GitHub URL or org/repo format
218
+ *
219
+ * GitHub formats:
220
+ * - https://github.com/org/repo
221
+ * - http://github.com/org/repo
222
+ * - github:org/repo
223
+ * - org/repo (shorthand, must be exactly two parts)
224
+ *
225
+ * NOT GitHub (registry module names):
226
+ * - code-reviewer
227
+ * - code-reviewer@1.0.0
228
+ * - my-module (no slash)
229
+ */
230
+ function isGitHubSource(input) {
231
+ // URL formats
232
+ if (input.startsWith('http://') || input.startsWith('https://')) {
233
+ return true;
234
+ }
235
+ // github: prefix format
236
+ if (input.startsWith('github:')) {
237
+ return true;
238
+ }
239
+ // Contains github.com
240
+ if (input.includes('github.com')) {
241
+ return true;
242
+ }
243
+ // Shorthand format: org/repo (exactly two parts, no @ version suffix)
244
+ // Must match pattern like: owner/repo or owner/repo but NOT module@version
245
+ const shorthandMatch = input.match(/^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)$/);
246
+ if (shorthandMatch) {
247
+ // Additional check: if it contains @, it's likely a scoped npm package or versioned module
248
+ // But GitHub shorthand shouldn't have @ in the middle
249
+ return !input.includes('@');
250
+ }
251
+ return false;
252
+ }
253
+ /**
254
+ * Parse module name with optional version: "module-name@1.0.0"
255
+ */
256
+ function parseModuleSpec(spec) {
257
+ const match = spec.match(/^([^@]+)(?:@(.+))?$/);
258
+ if (!match) {
259
+ return { name: spec };
260
+ }
261
+ return { name: match[1], version: match[2] };
262
+ }
263
+ /**
264
+ * Add a module from the registry
265
+ */
266
+ export async function addFromRegistry(moduleSpec, ctx, options = {}) {
267
+ const { name: moduleName, version: requestedVersion } = parseModuleSpec(moduleSpec);
268
+ const { name: customName, registry: registryUrl } = options;
269
+ try {
270
+ const client = new RegistryClient(registryUrl);
271
+ const moduleInfo = await client.getModule(moduleName);
272
+ if (!moduleInfo) {
273
+ return {
274
+ success: false,
275
+ error: `Module not found in registry: ${moduleName}\nUse 'cog search' to find available modules.`,
276
+ };
277
+ }
278
+ // Check if deprecated
279
+ if (moduleInfo.deprecated) {
280
+ console.error(`Warning: Module '${moduleName}' is deprecated.`);
281
+ }
282
+ // Get download info
283
+ const downloadInfo = await client.getDownloadUrl(moduleName);
284
+ if (downloadInfo.isGitHub && downloadInfo.githubInfo) {
285
+ const { org, repo, path, ref } = downloadInfo.githubInfo;
286
+ // Use addFromGitHub() directly (not add() to avoid recursion)
287
+ const result = await addFromGitHub(`${org}/${repo}`, ctx, {
288
+ module: path,
289
+ name: customName || moduleName,
290
+ tag: requestedVersion || ref,
291
+ branch: ref || 'main',
292
+ });
293
+ // If successful, update the install manifest to track registry info
294
+ if (result.success && result.data) {
295
+ const data = result.data;
296
+ const installName = data.name;
297
+ // Update manifest with registry info
298
+ const existingInfo = await getInstallInfo(installName);
299
+ if (existingInfo) {
300
+ await recordInstall(installName, {
301
+ ...existingInfo,
302
+ registryModule: moduleName,
303
+ registryUrl: registryUrl,
304
+ });
305
+ }
306
+ else {
307
+ // Fallback: create minimal install info if not found (shouldn't happen but safety first)
308
+ await recordInstall(installName, {
309
+ source: 'registry',
310
+ githubUrl: `${org}/${repo}`,
311
+ modulePath: path,
312
+ version: data.version,
313
+ installedAt: data.location || '',
314
+ installedTime: new Date().toISOString(),
315
+ registryModule: moduleName,
316
+ registryUrl: registryUrl,
317
+ });
318
+ }
319
+ // Update result to indicate registry source
320
+ result.data = {
321
+ ...result.data,
322
+ source: 'registry',
323
+ registryModule: moduleName,
324
+ };
325
+ }
326
+ return result;
327
+ }
328
+ // For tarball sources, download directly (future implementation)
329
+ return {
330
+ success: false,
331
+ error: `Tarball downloads not yet supported. Source: ${moduleInfo.source}`,
332
+ };
333
+ }
334
+ catch (error) {
335
+ return {
336
+ success: false,
337
+ error: error instanceof Error ? error.message : String(error),
338
+ };
339
+ }
340
+ }
160
341
  /**
161
342
  * Add a module from GitHub
162
343
  */
163
- export async function add(url, ctx, options = {}) {
344
+ export async function addFromGitHub(url, ctx, options = {}) {
164
345
  const { org, repo, fullUrl } = parseGitHubUrl(url);
165
346
  const { module: modulePath, name, branch = 'main', tag } = options;
166
347
  // Determine ref (tag takes priority)
@@ -184,11 +365,11 @@ export async function add(url, ctx, options = {}) {
184
365
  sourcePath = repoRoot;
185
366
  }
186
367
  // Determine module name
187
- moduleName = name || basename(sourcePath);
368
+ moduleName = name ? assertSafeModuleName(name) : basename(sourcePath);
188
369
  // Get version
189
370
  const version = await getModuleVersion(sourcePath);
190
371
  // Install to user modules dir
191
- const targetPath = join(USER_MODULES_DIR, moduleName);
372
+ const targetPath = resolveModuleTarget(moduleName);
192
373
  // Remove existing
193
374
  if (existsSync(targetPath)) {
194
375
  rmSync(targetPath, { recursive: true, force: true });
@@ -208,8 +389,10 @@ export async function add(url, ctx, options = {}) {
208
389
  installedTime: new Date().toISOString(),
209
390
  });
210
391
  // Cleanup temp directory
211
- const tempDir = repoRoot.split('/').slice(0, -1).join('/');
212
- rmSync(tempDir, { recursive: true, force: true });
392
+ const tempDir = dirname(repoRoot);
393
+ if (tempDir && tempDir !== '/' && tempDir !== '.' && tempDir !== USER_MODULES_DIR) {
394
+ rmSync(tempDir, { recursive: true, force: true });
395
+ }
213
396
  return {
214
397
  success: true,
215
398
  data: {
@@ -217,13 +400,39 @@ export async function add(url, ctx, options = {}) {
217
400
  name: moduleName,
218
401
  version,
219
402
  location: targetPath,
403
+ source: 'github',
220
404
  },
221
405
  };
222
406
  }
223
407
  catch (error) {
408
+ // Cleanup temp directory on error
409
+ if (repoRoot) {
410
+ try {
411
+ const tempDir = dirname(repoRoot);
412
+ if (tempDir && tempDir !== '/' && tempDir !== '.' && tempDir !== USER_MODULES_DIR) {
413
+ rmSync(tempDir, { recursive: true, force: true });
414
+ }
415
+ }
416
+ catch {
417
+ // Ignore cleanup errors
418
+ }
419
+ }
224
420
  return {
225
421
  success: false,
226
422
  error: error instanceof Error ? error.message : String(error),
227
423
  };
228
424
  }
229
425
  }
426
+ /**
427
+ * Add a module from GitHub or Registry (auto-detect source)
428
+ */
429
+ export async function add(source, ctx, options = {}) {
430
+ // Determine source type
431
+ if (isGitHubSource(source)) {
432
+ return addFromGitHub(source, ctx, options);
433
+ }
434
+ else {
435
+ // Treat as registry module name
436
+ return addFromRegistry(source, ctx, options);
437
+ }
438
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * cog compose - Execute a Composed Cognitive Module Workflow
3
+ *
4
+ * Supports all composition patterns:
5
+ * - Sequential: A → B → C
6
+ * - Parallel: A → [B, C, D] → Aggregate
7
+ * - Conditional: A → (condition) → B or C
8
+ * - Iterative: A → (check) → A → ... → Done
9
+ */
10
+ import type { CommandContext, CommandResult } from '../types.js';
11
+ export interface ComposeOptions {
12
+ /** Direct text input */
13
+ args?: string;
14
+ /** JSON input data */
15
+ input?: string;
16
+ /** Maximum composition depth */
17
+ maxDepth?: number;
18
+ /** Timeout in milliseconds */
19
+ timeout?: number;
20
+ /** Include execution trace */
21
+ trace?: boolean;
22
+ /** Pretty print output */
23
+ pretty?: boolean;
24
+ /** Verbose mode */
25
+ verbose?: boolean;
26
+ }
27
+ export declare function compose(moduleName: string, ctx: CommandContext, options?: ComposeOptions): Promise<CommandResult>;
28
+ /**
29
+ * Show composition info for a module
30
+ */
31
+ export declare function composeInfo(moduleName: string, ctx: CommandContext): Promise<CommandResult>;
@@ -0,0 +1,185 @@
1
+ /**
2
+ * cog compose - Execute a Composed Cognitive Module Workflow
3
+ *
4
+ * Supports all composition patterns:
5
+ * - Sequential: A → B → C
6
+ * - Parallel: A → [B, C, D] → Aggregate
7
+ * - Conditional: A → (condition) → B or C
8
+ * - Iterative: A → (check) → A → ... → Done
9
+ */
10
+ import { findModule, getDefaultSearchPaths, executeComposition } from '../modules/index.js';
11
+ import { ErrorCodes, attachContext, makeErrorEnvelope } from '../errors/index.js';
12
+ function looksLikeCode(str) {
13
+ const codeIndicators = [
14
+ /^(def|function|class|const|let|var|import|export|public|private)\s/,
15
+ /[{};()]/,
16
+ /=>/,
17
+ /\.(py|js|ts|go|rs|java|cpp|c|rb)$/,
18
+ ];
19
+ return codeIndicators.some(re => re.test(str));
20
+ }
21
+ export async function compose(moduleName, ctx, options = {}) {
22
+ const searchPaths = getDefaultSearchPaths(ctx.cwd);
23
+ // Find module
24
+ const module = await findModule(moduleName, searchPaths);
25
+ if (!module) {
26
+ const errorEnvelope = attachContext(makeErrorEnvelope({
27
+ code: ErrorCodes.MODULE_NOT_FOUND,
28
+ message: `Module not found: ${moduleName}\nSearch paths: ${searchPaths.join(', ')}`,
29
+ suggestion: "Use 'cog list' to see installed modules or 'cog search' to find modules in registry",
30
+ }), { module: moduleName, provider: ctx.provider.name });
31
+ return {
32
+ success: false,
33
+ error: errorEnvelope.error.message,
34
+ data: errorEnvelope,
35
+ };
36
+ }
37
+ try {
38
+ // Parse input if provided as JSON
39
+ let inputData = {};
40
+ if (options.input) {
41
+ try {
42
+ inputData = JSON.parse(options.input);
43
+ }
44
+ catch {
45
+ const errorEnvelope = attachContext(makeErrorEnvelope({
46
+ code: ErrorCodes.INVALID_INPUT,
47
+ message: `Invalid JSON input: ${options.input}`,
48
+ suggestion: 'Ensure input is valid JSON format',
49
+ }), { module: moduleName, provider: ctx.provider.name });
50
+ return {
51
+ success: false,
52
+ error: errorEnvelope.error.message,
53
+ data: errorEnvelope,
54
+ };
55
+ }
56
+ }
57
+ // Handle --args as text input
58
+ if (options.args) {
59
+ if (looksLikeCode(options.args)) {
60
+ inputData.code = options.args;
61
+ }
62
+ else {
63
+ inputData.query = options.args;
64
+ }
65
+ }
66
+ // Execute composition
67
+ const result = await executeComposition(moduleName, inputData, ctx.provider, {
68
+ cwd: ctx.cwd,
69
+ maxDepth: options.maxDepth,
70
+ timeoutMs: options.timeout
71
+ });
72
+ if (options.verbose) {
73
+ console.error('--- Composition Trace ---');
74
+ for (const entry of result.trace) {
75
+ const status = entry.success
76
+ ? (entry.skipped ? '⏭️ SKIPPED' : '✅ OK')
77
+ : '❌ FAILED';
78
+ console.error(`${status} ${entry.module} (${entry.durationMs}ms)`);
79
+ if (entry.reason) {
80
+ console.error(` Reason: ${entry.reason}`);
81
+ }
82
+ }
83
+ console.error(`--- Total: ${result.totalTimeMs}ms ---`);
84
+ }
85
+ if (!result.ok) {
86
+ const partialData = options.trace
87
+ ? { moduleResults: result.moduleResults, trace: result.trace }
88
+ : { moduleResults: result.moduleResults };
89
+ const errorEnvelope = attachContext(makeErrorEnvelope({
90
+ code: result.error?.code ?? ErrorCodes.INTERNAL_ERROR,
91
+ message: result.error?.message ?? 'Composition failed',
92
+ details: result.error?.module ? { module: result.error.module } : undefined,
93
+ partial_data: partialData,
94
+ }), { module: moduleName, provider: ctx.provider.name });
95
+ return {
96
+ success: false,
97
+ error: errorEnvelope.error.message,
98
+ data: errorEnvelope,
99
+ };
100
+ }
101
+ // Return result
102
+ if (options.trace) {
103
+ // Include full result with trace
104
+ return {
105
+ success: true,
106
+ data: {
107
+ ok: result.ok,
108
+ result: result.result,
109
+ moduleResults: result.moduleResults,
110
+ trace: result.trace,
111
+ totalTimeMs: result.totalTimeMs,
112
+ error: result.error
113
+ }
114
+ };
115
+ }
116
+ else if (options.pretty) {
117
+ return {
118
+ success: true,
119
+ data: result.result,
120
+ };
121
+ }
122
+ else {
123
+ // Keep compose output consistent with run/pipe: always return full v2.2 envelope
124
+ return {
125
+ success: true,
126
+ data: result.result,
127
+ };
128
+ }
129
+ }
130
+ catch (e) {
131
+ const errorEnvelope = attachContext(makeErrorEnvelope({
132
+ code: ErrorCodes.INTERNAL_ERROR,
133
+ message: e instanceof Error ? e.message : String(e),
134
+ recoverable: false,
135
+ }), { module: moduleName, provider: ctx.provider.name });
136
+ return {
137
+ success: false,
138
+ error: errorEnvelope.error.message,
139
+ data: errorEnvelope,
140
+ };
141
+ }
142
+ }
143
+ /**
144
+ * Show composition info for a module
145
+ */
146
+ export async function composeInfo(moduleName, ctx) {
147
+ const searchPaths = getDefaultSearchPaths(ctx.cwd);
148
+ const module = await findModule(moduleName, searchPaths);
149
+ if (!module) {
150
+ return {
151
+ success: false,
152
+ error: `Module not found: ${moduleName}`,
153
+ };
154
+ }
155
+ const composition = module.composition;
156
+ if (!composition) {
157
+ return {
158
+ success: true,
159
+ data: {
160
+ name: module.name,
161
+ hasComposition: false,
162
+ message: 'Module does not have composition configuration'
163
+ }
164
+ };
165
+ }
166
+ return {
167
+ success: true,
168
+ data: {
169
+ name: module.name,
170
+ hasComposition: true,
171
+ pattern: composition.pattern,
172
+ requires: composition.requires?.map(d => ({
173
+ name: d.name,
174
+ version: d.version,
175
+ optional: d.optional,
176
+ fallback: d.fallback
177
+ })),
178
+ dataflowSteps: composition.dataflow?.length ?? 0,
179
+ routingRules: composition.routing?.length ?? 0,
180
+ maxDepth: composition.max_depth,
181
+ timeoutMs: composition.timeout_ms,
182
+ iteration: composition.iteration
183
+ }
184
+ };
185
+ }
@@ -9,3 +9,8 @@ export * from './add.js';
9
9
  export * from './update.js';
10
10
  export * from './remove.js';
11
11
  export * from './versions.js';
12
+ export * from './compose.js';
13
+ export * from './validate.js';
14
+ export * from './migrate.js';
15
+ export * from './test.js';
16
+ export * from './search.js';
@@ -9,3 +9,8 @@ export * from './add.js';
9
9
  export * from './update.js';
10
10
  export * from './remove.js';
11
11
  export * from './versions.js';
12
+ export * from './compose.js';
13
+ export * from './validate.js';
14
+ export * from './migrate.js';
15
+ export * from './test.js';
16
+ export * from './search.js';
@@ -3,6 +3,28 @@
3
3
  */
4
4
  import * as fs from 'node:fs/promises';
5
5
  import * as path from 'node:path';
6
+ function assertSafeModuleName(name) {
7
+ const trimmed = name.trim();
8
+ if (!trimmed) {
9
+ throw new Error('Invalid module name: empty');
10
+ }
11
+ if (trimmed.includes('..') || trimmed.includes('/') || trimmed.includes('\\')) {
12
+ throw new Error(`Invalid module name: ${name}`);
13
+ }
14
+ if (path.isAbsolute(trimmed)) {
15
+ throw new Error(`Invalid module name (absolute path not allowed): ${name}`);
16
+ }
17
+ return trimmed;
18
+ }
19
+ function resolveModuleDir(rootDir, moduleName) {
20
+ const safeName = assertSafeModuleName(moduleName);
21
+ const targetPath = path.resolve(rootDir, safeName);
22
+ const root = path.resolve(rootDir) + path.sep;
23
+ if (!targetPath.startsWith(root)) {
24
+ throw new Error(`Invalid module name (path traversal): ${moduleName}`);
25
+ }
26
+ return targetPath;
27
+ }
6
28
  const EXAMPLE_MODULE_MD = `---
7
29
  name: my-module
8
30
  version: 1.0.0
@@ -46,7 +68,7 @@ export async function init(ctx, moduleName) {
46
68
  await fs.mkdir(cognitiveDir, { recursive: true });
47
69
  // If module name provided, create a new module
48
70
  if (moduleName) {
49
- const moduleDir = path.join(cognitiveDir, moduleName);
71
+ const moduleDir = resolveModuleDir(cognitiveDir, moduleName);
50
72
  await fs.mkdir(moduleDir, { recursive: true });
51
73
  await fs.writeFile(path.join(moduleDir, 'MODULE.md'), EXAMPLE_MODULE_MD.replace('my-module', moduleName));
52
74
  await fs.writeFile(path.join(moduleDir, 'schema.json'), EXAMPLE_SCHEMA_JSON);
@@ -0,0 +1,30 @@
1
+ /**
2
+ * cog migrate - Migrate modules to v2.2 format
3
+ *
4
+ * Aligns with Python CLI's `cog migrate` command.
5
+ * Performs migration from v0/v1/v2.1 to v2.2 format.
6
+ */
7
+ import type { CommandContext, CommandResult } from '../types.js';
8
+ export interface MigrateOptions {
9
+ /** Dry run - only show what would be done */
10
+ dryRun?: boolean;
11
+ /** Create backup before migration */
12
+ backup?: boolean;
13
+ /** Migrate all modules */
14
+ all?: boolean;
15
+ }
16
+ export interface MigrateResult {
17
+ moduleName: string;
18
+ modulePath: string;
19
+ success: boolean;
20
+ changes: string[];
21
+ warnings: string[];
22
+ }
23
+ /**
24
+ * Migrate a single module to v2.2 format.
25
+ */
26
+ export declare function migrate(nameOrPath: string, ctx: CommandContext, options?: MigrateOptions): Promise<CommandResult>;
27
+ /**
28
+ * Migrate all modules to v2.2 format.
29
+ */
30
+ export declare function migrateAll(ctx: CommandContext, options?: MigrateOptions): Promise<CommandResult>;