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.
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +35 -29
- package/dist/cli.js +572 -28
- package/dist/commands/add.d.ts +33 -14
- package/dist/commands/add.js +222 -13
- package/dist/commands/compose.d.ts +31 -0
- package/dist/commands/compose.js +185 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.js +5 -0
- package/dist/commands/init.js +23 -1
- package/dist/commands/migrate.d.ts +30 -0
- package/dist/commands/migrate.js +650 -0
- package/dist/commands/pipe.d.ts +1 -0
- package/dist/commands/pipe.js +31 -11
- package/dist/commands/remove.js +33 -2
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +37 -27
- package/dist/commands/search.d.ts +28 -0
- package/dist/commands/search.js +143 -0
- package/dist/commands/test.d.ts +65 -0
- package/dist/commands/test.js +454 -0
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.js +106 -14
- package/dist/commands/validate.d.ts +36 -0
- package/dist/commands/validate.js +97 -0
- package/dist/errors/index.d.ts +218 -0
- package/dist/errors/index.js +412 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -1
- package/dist/mcp/server.js +84 -79
- package/dist/modules/composition.d.ts +251 -0
- package/dist/modules/composition.js +1330 -0
- package/dist/modules/index.d.ts +2 -0
- package/dist/modules/index.js +2 -0
- package/dist/modules/loader.d.ts +22 -2
- package/dist/modules/loader.js +171 -6
- package/dist/modules/runner.d.ts +422 -1
- package/dist/modules/runner.js +1472 -71
- package/dist/modules/subagent.d.ts +6 -1
- package/dist/modules/subagent.js +20 -13
- package/dist/modules/validator.d.ts +28 -0
- package/dist/modules/validator.js +637 -0
- package/dist/providers/anthropic.d.ts +15 -0
- package/dist/providers/anthropic.js +147 -5
- package/dist/providers/base.d.ts +11 -0
- package/dist/providers/base.js +18 -0
- package/dist/providers/gemini.d.ts +15 -0
- package/dist/providers/gemini.js +122 -5
- package/dist/providers/ollama.d.ts +15 -0
- package/dist/providers/ollama.js +111 -3
- package/dist/providers/openai.d.ts +11 -0
- package/dist/providers/openai.js +133 -0
- package/dist/registry/client.d.ts +204 -0
- package/dist/registry/client.js +356 -0
- package/dist/registry/index.d.ts +4 -0
- package/dist/registry/index.js +4 -0
- package/dist/server/http.js +173 -42
- package/dist/types.d.ts +123 -8
- package/dist/types.js +4 -1
- package/dist/version.d.ts +1 -0
- package/dist/version.js +4 -0
- package/package.json +32 -7
- package/src/cli.ts +0 -410
- package/src/commands/add.ts +0 -315
- package/src/commands/index.ts +0 -12
- package/src/commands/init.ts +0 -94
- package/src/commands/list.ts +0 -33
- package/src/commands/pipe.ts +0 -76
- package/src/commands/remove.ts +0 -57
- package/src/commands/run.ts +0 -80
- package/src/commands/update.ts +0 -130
- package/src/commands/versions.ts +0 -79
- package/src/index.ts +0 -55
- package/src/mcp/index.ts +0 -5
- package/src/mcp/server.ts +0 -403
- package/src/modules/index.ts +0 -7
- package/src/modules/loader.ts +0 -318
- package/src/modules/runner.ts +0 -495
- package/src/modules/subagent.ts +0 -275
- package/src/providers/anthropic.ts +0 -89
- package/src/providers/base.ts +0 -29
- package/src/providers/deepseek.ts +0 -83
- package/src/providers/gemini.ts +0 -117
- package/src/providers/index.ts +0 -78
- package/src/providers/minimax.ts +0 -81
- package/src/providers/moonshot.ts +0 -82
- package/src/providers/ollama.ts +0 -83
- package/src/providers/openai.ts +0 -84
- package/src/providers/qwen.ts +0 -82
- package/src/server/http.ts +0 -316
- package/src/server/index.ts +0 -6
- package/src/types.ts +0 -495
- package/tsconfig.json +0 -17
package/dist/commands/add.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
5
|
-
*
|
|
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
|
|
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 {};
|
package/dist/commands/add.js
CHANGED
|
@@ -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
|
-
*
|
|
5
|
-
*
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
212
|
-
|
|
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
|
+
}
|
package/dist/commands/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/commands/index.js
CHANGED
|
@@ -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';
|
package/dist/commands/init.js
CHANGED
|
@@ -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 =
|
|
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>;
|