@cpmai/cli 0.2.0-beta.1 → 0.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +289 -262
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,107 +4,6 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import chalk5 from "chalk";
|
|
6
6
|
|
|
7
|
-
// src/adapters/base.ts
|
|
8
|
-
var PlatformAdapter = class {
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
// src/adapters/handlers/handler-registry.ts
|
|
12
|
-
var HandlerRegistry = class {
|
|
13
|
-
/**
|
|
14
|
-
* Internal storage for handlers.
|
|
15
|
-
* Maps package type strings to their handler instances.
|
|
16
|
-
* Using Map for O(1) lookup performance.
|
|
17
|
-
*/
|
|
18
|
-
handlers = /* @__PURE__ */ new Map();
|
|
19
|
-
/**
|
|
20
|
-
* Register a handler for a specific package type.
|
|
21
|
-
*
|
|
22
|
-
* When you create a new handler (like RulesHandler), you call this method
|
|
23
|
-
* to add it to the registry so it can be found later.
|
|
24
|
-
*
|
|
25
|
-
* @param handler - The handler instance to register. The handler's
|
|
26
|
-
* packageType property determines which type it handles.
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* ```typescript
|
|
30
|
-
* const rulesHandler = new RulesHandler();
|
|
31
|
-
* registry.register(rulesHandler);
|
|
32
|
-
* // Now "rules" type packages will use RulesHandler
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
35
|
-
register(handler) {
|
|
36
|
-
this.handlers.set(handler.packageType, handler);
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Get the handler for a specific package type.
|
|
40
|
-
*
|
|
41
|
-
* Use this when you need to install or uninstall a package.
|
|
42
|
-
* It returns the appropriate handler based on the package type.
|
|
43
|
-
*
|
|
44
|
-
* @param type - The package type to find a handler for (e.g., "rules", "skill", "mcp")
|
|
45
|
-
* @returns The handler that can process this package type
|
|
46
|
-
* @throws Error if no handler is registered for the given type
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* ```typescript
|
|
50
|
-
* const handler = registry.getHandler("skill");
|
|
51
|
-
* const files = await handler.install(manifest, context);
|
|
52
|
-
* ```
|
|
53
|
-
*/
|
|
54
|
-
getHandler(type) {
|
|
55
|
-
const handler = this.handlers.get(type);
|
|
56
|
-
if (!handler) {
|
|
57
|
-
throw new Error(`No handler registered for package type: ${type}`);
|
|
58
|
-
}
|
|
59
|
-
return handler;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Check if a handler exists for a specific package type.
|
|
63
|
-
*
|
|
64
|
-
* Useful when you want to check availability before attempting
|
|
65
|
-
* to get a handler, avoiding the need for try-catch blocks.
|
|
66
|
-
*
|
|
67
|
-
* @param type - The package type to check
|
|
68
|
-
* @returns true if a handler is registered, false otherwise
|
|
69
|
-
*
|
|
70
|
-
* @example
|
|
71
|
-
* ```typescript
|
|
72
|
-
* if (registry.hasHandler("mcp")) {
|
|
73
|
-
* const handler = registry.getHandler("mcp");
|
|
74
|
-
* // ... use handler
|
|
75
|
-
* } else {
|
|
76
|
-
* console.log("MCP packages not supported");
|
|
77
|
-
* }
|
|
78
|
-
* ```
|
|
79
|
-
*/
|
|
80
|
-
hasHandler(type) {
|
|
81
|
-
return this.handlers.has(type);
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Get a list of all registered package types.
|
|
85
|
-
*
|
|
86
|
-
* Useful for debugging, displaying supported types to users,
|
|
87
|
-
* or iterating over all available handlers.
|
|
88
|
-
*
|
|
89
|
-
* @returns Array of package type strings that have registered handlers
|
|
90
|
-
*
|
|
91
|
-
* @example
|
|
92
|
-
* ```typescript
|
|
93
|
-
* const types = registry.getRegisteredTypes();
|
|
94
|
-
* console.log("Supported types:", types.join(", "));
|
|
95
|
-
* // Output: "Supported types: rules, skill, mcp"
|
|
96
|
-
* ```
|
|
97
|
-
*/
|
|
98
|
-
getRegisteredTypes() {
|
|
99
|
-
return Array.from(this.handlers.keys());
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
var handlerRegistry = new HandlerRegistry();
|
|
103
|
-
|
|
104
|
-
// src/adapters/handlers/rules-handler.ts
|
|
105
|
-
import fs2 from "fs-extra";
|
|
106
|
-
import path6 from "path";
|
|
107
|
-
|
|
108
7
|
// src/constants.ts
|
|
109
8
|
var TIMEOUTS = {
|
|
110
9
|
MANIFEST_FETCH: 5e3,
|
|
@@ -171,15 +70,17 @@ var ALLOWED_MCP_COMMANDS = [
|
|
|
171
70
|
var BLOCKED_MCP_ARG_PATTERNS = [
|
|
172
71
|
/--eval/i,
|
|
173
72
|
// Node.js eval flag
|
|
174
|
-
/-e
|
|
175
|
-
// Short eval flag with space
|
|
176
|
-
|
|
177
|
-
//
|
|
73
|
+
/-e(?:\s|$)/,
|
|
74
|
+
// Short eval flag (with space or at end)
|
|
75
|
+
/^-e\S/,
|
|
76
|
+
// Concatenated eval flag (e.g., -eCODE)
|
|
77
|
+
/-c(?:\s|$)/,
|
|
78
|
+
// Command flag (with space or at end)
|
|
178
79
|
/\bcurl\b/i,
|
|
179
80
|
// curl command (data exfiltration)
|
|
180
81
|
/\bwget\b/i,
|
|
181
82
|
// wget command (data exfiltration)
|
|
182
|
-
/\brm
|
|
83
|
+
/\brm(?:\s|$)/i,
|
|
183
84
|
// rm command (file deletion)
|
|
184
85
|
/\bsudo\b/i,
|
|
185
86
|
// sudo command (privilege escalation)
|
|
@@ -187,8 +88,44 @@ var BLOCKED_MCP_ARG_PATTERNS = [
|
|
|
187
88
|
// chmod command (permission changes)
|
|
188
89
|
/\bchown\b/i,
|
|
189
90
|
// chown command (ownership changes)
|
|
190
|
-
/[|;&`$]
|
|
91
|
+
/[|;&`$]/,
|
|
191
92
|
// Shell metacharacters (command chaining/injection)
|
|
93
|
+
/--inspect/i,
|
|
94
|
+
// Node.js debugger (remote code execution)
|
|
95
|
+
/--allow-all/i,
|
|
96
|
+
// Deno sandbox bypass
|
|
97
|
+
/--allow-run/i,
|
|
98
|
+
// Deno run permission
|
|
99
|
+
/--allow-write/i,
|
|
100
|
+
// Deno write permission
|
|
101
|
+
/--allow-net/i,
|
|
102
|
+
// Deno net permission
|
|
103
|
+
/^https?:\/\//i
|
|
104
|
+
// Remote URLs as standalone args (script loading)
|
|
105
|
+
];
|
|
106
|
+
var BLOCKED_MCP_ENV_KEYS = [
|
|
107
|
+
"PATH",
|
|
108
|
+
"LD_PRELOAD",
|
|
109
|
+
"LD_LIBRARY_PATH",
|
|
110
|
+
"DYLD_INSERT_LIBRARIES",
|
|
111
|
+
"DYLD_LIBRARY_PATH",
|
|
112
|
+
"NODE_OPTIONS",
|
|
113
|
+
"NODE_PATH",
|
|
114
|
+
"PYTHONPATH",
|
|
115
|
+
"PYTHONSTARTUP",
|
|
116
|
+
"PYTHONHOME",
|
|
117
|
+
"RUBYOPT",
|
|
118
|
+
"PERL5OPT",
|
|
119
|
+
"BASH_ENV",
|
|
120
|
+
"ENV",
|
|
121
|
+
"CDPATH",
|
|
122
|
+
"HOME",
|
|
123
|
+
"USERPROFILE",
|
|
124
|
+
"NPM_CONFIG_REGISTRY",
|
|
125
|
+
"NPM_CONFIG_PREFIX",
|
|
126
|
+
"NPM_CONFIG_GLOBALCONFIG",
|
|
127
|
+
"DENO_DIR",
|
|
128
|
+
"BUN_INSTALL"
|
|
192
129
|
];
|
|
193
130
|
|
|
194
131
|
// src/types.ts
|
|
@@ -210,15 +147,15 @@ function isSkillManifest(manifest) {
|
|
|
210
147
|
function isMcpManifest(manifest) {
|
|
211
148
|
return manifest.type === "mcp";
|
|
212
149
|
}
|
|
213
|
-
function getTypeFromPath(
|
|
214
|
-
if (
|
|
215
|
-
if (
|
|
216
|
-
if (
|
|
217
|
-
if (
|
|
218
|
-
if (
|
|
219
|
-
if (
|
|
220
|
-
if (
|
|
221
|
-
if (
|
|
150
|
+
function getTypeFromPath(path14) {
|
|
151
|
+
if (path14.startsWith("skills/")) return "skill";
|
|
152
|
+
if (path14.startsWith("rules/")) return "rules";
|
|
153
|
+
if (path14.startsWith("mcp/")) return "mcp";
|
|
154
|
+
if (path14.startsWith("agents/")) return "agent";
|
|
155
|
+
if (path14.startsWith("hooks/")) return "hook";
|
|
156
|
+
if (path14.startsWith("workflows/")) return "workflow";
|
|
157
|
+
if (path14.startsWith("templates/")) return "template";
|
|
158
|
+
if (path14.startsWith("bundles/")) return "bundle";
|
|
222
159
|
return null;
|
|
223
160
|
}
|
|
224
161
|
function resolvePackageType(pkg) {
|
|
@@ -230,6 +167,107 @@ function resolvePackageType(pkg) {
|
|
|
230
167
|
throw new Error(`Cannot determine type for package: ${pkg.name}`);
|
|
231
168
|
}
|
|
232
169
|
|
|
170
|
+
// src/adapters/base.ts
|
|
171
|
+
var PlatformAdapter = class {
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// src/adapters/handlers/handler-registry.ts
|
|
175
|
+
var HandlerRegistry = class {
|
|
176
|
+
/**
|
|
177
|
+
* Internal storage for handlers.
|
|
178
|
+
* Maps package type strings to their handler instances.
|
|
179
|
+
* Using Map for O(1) lookup performance.
|
|
180
|
+
*/
|
|
181
|
+
handlers = /* @__PURE__ */ new Map();
|
|
182
|
+
/**
|
|
183
|
+
* Register a handler for a specific package type.
|
|
184
|
+
*
|
|
185
|
+
* When you create a new handler (like RulesHandler), you call this method
|
|
186
|
+
* to add it to the registry so it can be found later.
|
|
187
|
+
*
|
|
188
|
+
* @param handler - The handler instance to register. The handler's
|
|
189
|
+
* packageType property determines which type it handles.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* const rulesHandler = new RulesHandler();
|
|
194
|
+
* registry.register(rulesHandler);
|
|
195
|
+
* // Now "rules" type packages will use RulesHandler
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
register(handler) {
|
|
199
|
+
this.handlers.set(handler.packageType, handler);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Get the handler for a specific package type.
|
|
203
|
+
*
|
|
204
|
+
* Use this when you need to install or uninstall a package.
|
|
205
|
+
* It returns the appropriate handler based on the package type.
|
|
206
|
+
*
|
|
207
|
+
* @param type - The package type to find a handler for (e.g., "rules", "skill", "mcp")
|
|
208
|
+
* @returns The handler that can process this package type
|
|
209
|
+
* @throws Error if no handler is registered for the given type
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```typescript
|
|
213
|
+
* const handler = registry.getHandler("skill");
|
|
214
|
+
* const files = await handler.install(manifest, context);
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
getHandler(type) {
|
|
218
|
+
const handler = this.handlers.get(type);
|
|
219
|
+
if (!handler) {
|
|
220
|
+
throw new Error(`No handler registered for package type: ${type}`);
|
|
221
|
+
}
|
|
222
|
+
return handler;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Check if a handler exists for a specific package type.
|
|
226
|
+
*
|
|
227
|
+
* Useful when you want to check availability before attempting
|
|
228
|
+
* to get a handler, avoiding the need for try-catch blocks.
|
|
229
|
+
*
|
|
230
|
+
* @param type - The package type to check
|
|
231
|
+
* @returns true if a handler is registered, false otherwise
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```typescript
|
|
235
|
+
* if (registry.hasHandler("mcp")) {
|
|
236
|
+
* const handler = registry.getHandler("mcp");
|
|
237
|
+
* // ... use handler
|
|
238
|
+
* } else {
|
|
239
|
+
* console.log("MCP packages not supported");
|
|
240
|
+
* }
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
hasHandler(type) {
|
|
244
|
+
return this.handlers.has(type);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get a list of all registered package types.
|
|
248
|
+
*
|
|
249
|
+
* Useful for debugging, displaying supported types to users,
|
|
250
|
+
* or iterating over all available handlers.
|
|
251
|
+
*
|
|
252
|
+
* @returns Array of package type strings that have registered handlers
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```typescript
|
|
256
|
+
* const types = registry.getRegisteredTypes();
|
|
257
|
+
* console.log("Supported types:", types.join(", "));
|
|
258
|
+
* // Output: "Supported types: rules, skill, mcp"
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
getRegisteredTypes() {
|
|
262
|
+
return Array.from(this.handlers.keys());
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
var handlerRegistry = new HandlerRegistry();
|
|
266
|
+
|
|
267
|
+
// src/adapters/handlers/rules-handler.ts
|
|
268
|
+
import fs2 from "fs-extra";
|
|
269
|
+
import path5 from "path";
|
|
270
|
+
|
|
233
271
|
// src/utils/platform.ts
|
|
234
272
|
import path2 from "path";
|
|
235
273
|
|
|
@@ -331,14 +369,22 @@ var logger = {
|
|
|
331
369
|
};
|
|
332
370
|
|
|
333
371
|
// src/security/mcp-validator.ts
|
|
334
|
-
import path3 from "path";
|
|
335
372
|
function isAllowedCommand(command) {
|
|
336
|
-
|
|
373
|
+
if (command.includes("/") || command.includes("\\")) {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
337
376
|
return ALLOWED_MCP_COMMANDS.includes(
|
|
338
|
-
|
|
377
|
+
command
|
|
339
378
|
);
|
|
340
379
|
}
|
|
341
380
|
function containsBlockedPattern(args) {
|
|
381
|
+
for (const arg of args) {
|
|
382
|
+
for (const pattern of BLOCKED_MCP_ARG_PATTERNS) {
|
|
383
|
+
if (pattern.test(arg)) {
|
|
384
|
+
return pattern;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
342
388
|
const argsString = args.join(" ");
|
|
343
389
|
for (const pattern of BLOCKED_MCP_ARG_PATTERNS) {
|
|
344
390
|
if (pattern.test(argsString)) {
|
|
@@ -347,15 +393,23 @@ function containsBlockedPattern(args) {
|
|
|
347
393
|
}
|
|
348
394
|
return null;
|
|
349
395
|
}
|
|
396
|
+
function containsBlockedEnvKey(env) {
|
|
397
|
+
const blockedSet = new Set(BLOCKED_MCP_ENV_KEYS.map((k) => k.toUpperCase()));
|
|
398
|
+
for (const key of Object.keys(env)) {
|
|
399
|
+
if (blockedSet.has(key.toUpperCase())) {
|
|
400
|
+
return key;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
350
405
|
function validateMcpConfig(mcp) {
|
|
351
406
|
if (!mcp?.command) {
|
|
352
407
|
return { valid: false, error: "MCP command is required" };
|
|
353
408
|
}
|
|
354
|
-
const baseCommand = path3.basename(mcp.command);
|
|
355
409
|
if (!isAllowedCommand(mcp.command)) {
|
|
356
410
|
return {
|
|
357
411
|
valid: false,
|
|
358
|
-
error: `MCP command '${
|
|
412
|
+
error: `MCP command '${mcp.command}' is not allowed. Allowed: ${ALLOWED_MCP_COMMANDS.join(", ")}`
|
|
359
413
|
};
|
|
360
414
|
}
|
|
361
415
|
if (mcp.args) {
|
|
@@ -367,16 +421,25 @@ function validateMcpConfig(mcp) {
|
|
|
367
421
|
};
|
|
368
422
|
}
|
|
369
423
|
}
|
|
424
|
+
if (mcp.env) {
|
|
425
|
+
const blockedKey = containsBlockedEnvKey(mcp.env);
|
|
426
|
+
if (blockedKey) {
|
|
427
|
+
return {
|
|
428
|
+
valid: false,
|
|
429
|
+
error: `MCP environment variable '${blockedKey}' is not allowed. It could be used to bypass command security restrictions.`
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
370
433
|
return { valid: true };
|
|
371
434
|
}
|
|
372
435
|
|
|
373
436
|
// src/security/file-sanitizer.ts
|
|
374
|
-
import
|
|
437
|
+
import path3 from "path";
|
|
375
438
|
function sanitizeFileName(fileName) {
|
|
376
439
|
if (!fileName || typeof fileName !== "string") {
|
|
377
440
|
return { valid: false, error: "File name cannot be empty", sanitized: "" };
|
|
378
441
|
}
|
|
379
|
-
const baseName =
|
|
442
|
+
const baseName = path3.basename(fileName);
|
|
380
443
|
if (baseName.includes("\0")) {
|
|
381
444
|
return {
|
|
382
445
|
valid: false,
|
|
@@ -421,12 +484,12 @@ function sanitizeFolderName(name) {
|
|
|
421
484
|
if (!sanitized || sanitized.startsWith(".")) {
|
|
422
485
|
throw new Error(`Invalid package name: ${name}`);
|
|
423
486
|
}
|
|
424
|
-
const normalized =
|
|
487
|
+
const normalized = path3.normalize(sanitized);
|
|
425
488
|
if (normalized !== sanitized || normalized.includes("..")) {
|
|
426
489
|
throw new Error(`Invalid package name (path traversal detected): ${name}`);
|
|
427
490
|
}
|
|
428
|
-
const testPath =
|
|
429
|
-
const resolved =
|
|
491
|
+
const testPath = path3.join("/test", sanitized);
|
|
492
|
+
const resolved = path3.resolve(testPath);
|
|
430
493
|
if (!resolved.startsWith("/test/")) {
|
|
431
494
|
throw new Error(`Invalid package name (path traversal detected): ${name}`);
|
|
432
495
|
}
|
|
@@ -434,11 +497,11 @@ function sanitizeFolderName(name) {
|
|
|
434
497
|
}
|
|
435
498
|
|
|
436
499
|
// src/security/path-validator.ts
|
|
437
|
-
import
|
|
500
|
+
import path4 from "path";
|
|
438
501
|
function isPathWithinDirectory(filePath, directory) {
|
|
439
|
-
const resolvedPath =
|
|
440
|
-
const resolvedDir =
|
|
441
|
-
return resolvedPath.startsWith(resolvedDir +
|
|
502
|
+
const resolvedPath = path4.resolve(filePath);
|
|
503
|
+
const resolvedDir = path4.resolve(directory);
|
|
504
|
+
return resolvedPath.startsWith(resolvedDir + path4.sep) || resolvedPath === resolvedDir;
|
|
442
505
|
}
|
|
443
506
|
|
|
444
507
|
// src/adapters/handlers/rules-handler.ts
|
|
@@ -453,7 +516,7 @@ async function writePackageMetadata(packageDir, manifest) {
|
|
|
453
516
|
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
454
517
|
// ISO timestamp for when it was installed
|
|
455
518
|
};
|
|
456
|
-
const metadataPath =
|
|
519
|
+
const metadataPath = path5.join(packageDir, ".cpm.json");
|
|
457
520
|
try {
|
|
458
521
|
await fs2.writeJson(metadataPath, metadata, { spaces: 2 });
|
|
459
522
|
} catch (error) {
|
|
@@ -486,7 +549,7 @@ var RulesHandler = class {
|
|
|
486
549
|
const filesWritten = [];
|
|
487
550
|
const rulesBaseDir = getRulesPath("claude-code");
|
|
488
551
|
const folderName = sanitizeFolderName(manifest.name);
|
|
489
|
-
const rulesDir =
|
|
552
|
+
const rulesDir = path5.join(rulesBaseDir, folderName);
|
|
490
553
|
await fs2.ensureDir(rulesDir);
|
|
491
554
|
if (context.packagePath && await fs2.pathExists(context.packagePath)) {
|
|
492
555
|
const files = await fs2.readdir(context.packagePath);
|
|
@@ -500,8 +563,8 @@ var RulesHandler = class {
|
|
|
500
563
|
logger.warn(`Skipping unsafe file: ${file} (${validation.error})`);
|
|
501
564
|
continue;
|
|
502
565
|
}
|
|
503
|
-
const srcPath =
|
|
504
|
-
const destPath =
|
|
566
|
+
const srcPath = path5.join(context.packagePath, file);
|
|
567
|
+
const destPath = path5.join(rulesDir, validation.sanitized);
|
|
505
568
|
if (!isPathWithinDirectory(destPath, rulesDir)) {
|
|
506
569
|
logger.warn(`Blocked path traversal attempt: ${file}`);
|
|
507
570
|
continue;
|
|
@@ -516,7 +579,7 @@ var RulesHandler = class {
|
|
|
516
579
|
}
|
|
517
580
|
const rulesContent = this.getRulesContent(manifest);
|
|
518
581
|
if (!rulesContent) return filesWritten;
|
|
519
|
-
const rulesPath =
|
|
582
|
+
const rulesPath = path5.join(rulesDir, "RULES.md");
|
|
520
583
|
const content = `# ${manifest.name}
|
|
521
584
|
|
|
522
585
|
${manifest.description}
|
|
@@ -542,7 +605,7 @@ ${rulesContent.trim()}
|
|
|
542
605
|
const filesRemoved = [];
|
|
543
606
|
const folderName = sanitizeFolderName(packageName);
|
|
544
607
|
const rulesBaseDir = getRulesPath("claude-code");
|
|
545
|
-
const rulesPath =
|
|
608
|
+
const rulesPath = path5.join(rulesBaseDir, folderName);
|
|
546
609
|
if (await fs2.pathExists(rulesPath)) {
|
|
547
610
|
await fs2.remove(rulesPath);
|
|
548
611
|
filesRemoved.push(rulesPath);
|
|
@@ -569,7 +632,7 @@ ${rulesContent.trim()}
|
|
|
569
632
|
|
|
570
633
|
// src/adapters/handlers/skill-handler.ts
|
|
571
634
|
import fs3 from "fs-extra";
|
|
572
|
-
import
|
|
635
|
+
import path6 from "path";
|
|
573
636
|
async function writePackageMetadata2(packageDir, manifest) {
|
|
574
637
|
const metadata = {
|
|
575
638
|
name: manifest.name,
|
|
@@ -581,7 +644,7 @@ async function writePackageMetadata2(packageDir, manifest) {
|
|
|
581
644
|
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
582
645
|
// ISO timestamp for when it was installed
|
|
583
646
|
};
|
|
584
|
-
const metadataPath =
|
|
647
|
+
const metadataPath = path6.join(packageDir, ".cpm.json");
|
|
585
648
|
try {
|
|
586
649
|
await fs3.writeJson(metadataPath, metadata, { spaces: 2 });
|
|
587
650
|
} catch (error) {
|
|
@@ -633,7 +696,7 @@ var SkillHandler = class {
|
|
|
633
696
|
const filesWritten = [];
|
|
634
697
|
const skillsDir = getSkillsPath();
|
|
635
698
|
const folderName = sanitizeFolderName(manifest.name);
|
|
636
|
-
const skillDir =
|
|
699
|
+
const skillDir = path6.join(skillsDir, folderName);
|
|
637
700
|
await fs3.ensureDir(skillDir);
|
|
638
701
|
if (context.packagePath && await fs3.pathExists(context.packagePath)) {
|
|
639
702
|
const files = await fs3.readdir(context.packagePath);
|
|
@@ -647,8 +710,8 @@ var SkillHandler = class {
|
|
|
647
710
|
logger.warn(`Skipping unsafe file: ${file} (${validation.error})`);
|
|
648
711
|
continue;
|
|
649
712
|
}
|
|
650
|
-
const srcPath =
|
|
651
|
-
const destPath =
|
|
713
|
+
const srcPath = path6.join(context.packagePath, file);
|
|
714
|
+
const destPath = path6.join(skillDir, validation.sanitized);
|
|
652
715
|
if (!isPathWithinDirectory(destPath, skillDir)) {
|
|
653
716
|
logger.warn(`Blocked path traversal attempt: ${file}`);
|
|
654
717
|
continue;
|
|
@@ -663,7 +726,7 @@ var SkillHandler = class {
|
|
|
663
726
|
}
|
|
664
727
|
if (isSkillManifest(manifest)) {
|
|
665
728
|
const skillContent = formatSkillMd(manifest);
|
|
666
|
-
const skillPath =
|
|
729
|
+
const skillPath = path6.join(skillDir, "SKILL.md");
|
|
667
730
|
await fs3.writeFile(skillPath, skillContent, "utf-8");
|
|
668
731
|
filesWritten.push(skillPath);
|
|
669
732
|
const metadataPath = await writePackageMetadata2(skillDir, manifest);
|
|
@@ -671,7 +734,7 @@ var SkillHandler = class {
|
|
|
671
734
|
} else {
|
|
672
735
|
const content = this.getUniversalContent(manifest);
|
|
673
736
|
if (content) {
|
|
674
|
-
const skillPath =
|
|
737
|
+
const skillPath = path6.join(skillDir, "SKILL.md");
|
|
675
738
|
const skillContent = `# ${manifest.name}
|
|
676
739
|
|
|
677
740
|
${manifest.description}
|
|
@@ -699,7 +762,7 @@ ${content.trim()}
|
|
|
699
762
|
const filesRemoved = [];
|
|
700
763
|
const folderName = sanitizeFolderName(packageName);
|
|
701
764
|
const skillsDir = getSkillsPath();
|
|
702
|
-
const skillPath =
|
|
765
|
+
const skillPath = path6.join(skillsDir, folderName);
|
|
703
766
|
if (await fs3.pathExists(skillPath)) {
|
|
704
767
|
await fs3.remove(skillPath);
|
|
705
768
|
filesRemoved.push(skillPath);
|
|
@@ -725,7 +788,7 @@ ${content.trim()}
|
|
|
725
788
|
|
|
726
789
|
// src/adapters/handlers/mcp-handler.ts
|
|
727
790
|
import fs4 from "fs-extra";
|
|
728
|
-
import
|
|
791
|
+
import path7 from "path";
|
|
729
792
|
var McpHandler = class {
|
|
730
793
|
/**
|
|
731
794
|
* Identifies this handler as handling "mcp" type packages.
|
|
@@ -756,13 +819,21 @@ var McpHandler = class {
|
|
|
756
819
|
throw new Error(`MCP security validation failed: ${mcpValidation.error}`);
|
|
757
820
|
}
|
|
758
821
|
const claudeHome = getClaudeHome();
|
|
759
|
-
const mcpConfigPath =
|
|
822
|
+
const mcpConfigPath = path7.join(path7.dirname(claudeHome), ".claude.json");
|
|
760
823
|
let existingConfig = {};
|
|
761
824
|
if (await fs4.pathExists(mcpConfigPath)) {
|
|
762
825
|
try {
|
|
763
826
|
existingConfig = await fs4.readJson(mcpConfigPath);
|
|
764
827
|
} catch {
|
|
765
|
-
|
|
828
|
+
const backupPath = `${mcpConfigPath}.backup.${Date.now()}`;
|
|
829
|
+
try {
|
|
830
|
+
await fs4.copy(mcpConfigPath, backupPath);
|
|
831
|
+
logger.warn(
|
|
832
|
+
`Could not parse ${mcpConfigPath}, backup saved to ${backupPath}`
|
|
833
|
+
);
|
|
834
|
+
} catch {
|
|
835
|
+
logger.warn(`Could not parse ${mcpConfigPath}, creating new config`);
|
|
836
|
+
}
|
|
766
837
|
existingConfig = {};
|
|
767
838
|
}
|
|
768
839
|
}
|
|
@@ -802,7 +873,7 @@ var McpHandler = class {
|
|
|
802
873
|
const filesWritten = [];
|
|
803
874
|
const folderName = sanitizeFolderName(packageName);
|
|
804
875
|
const claudeHome = getClaudeHome();
|
|
805
|
-
const mcpConfigPath =
|
|
876
|
+
const mcpConfigPath = path7.join(path7.dirname(claudeHome), ".claude.json");
|
|
806
877
|
if (!await fs4.pathExists(mcpConfigPath)) {
|
|
807
878
|
return filesWritten;
|
|
808
879
|
}
|
|
@@ -838,7 +909,7 @@ initializeHandlers();
|
|
|
838
909
|
|
|
839
910
|
// src/adapters/claude-code.ts
|
|
840
911
|
import fs5 from "fs-extra";
|
|
841
|
-
import
|
|
912
|
+
import path8 from "path";
|
|
842
913
|
var ClaudeCodeAdapter = class extends PlatformAdapter {
|
|
843
914
|
platform = "claude-code";
|
|
844
915
|
displayName = "Claude Code";
|
|
@@ -865,23 +936,28 @@ var ClaudeCodeAdapter = class extends PlatformAdapter {
|
|
|
865
936
|
};
|
|
866
937
|
}
|
|
867
938
|
}
|
|
868
|
-
async uninstall(packageName,
|
|
939
|
+
async uninstall(packageName, projectPath) {
|
|
869
940
|
const filesWritten = [];
|
|
870
941
|
const folderName = sanitizeFolderName(packageName);
|
|
942
|
+
const context = { projectPath };
|
|
871
943
|
try {
|
|
872
944
|
const rulesBaseDir = getRulesPath("claude-code");
|
|
873
|
-
const rulesPath =
|
|
945
|
+
const rulesPath = path8.join(rulesBaseDir, folderName);
|
|
874
946
|
if (await fs5.pathExists(rulesPath)) {
|
|
875
947
|
await fs5.remove(rulesPath);
|
|
876
948
|
filesWritten.push(rulesPath);
|
|
877
949
|
}
|
|
878
950
|
const skillsDir = getSkillsPath();
|
|
879
|
-
const skillPath =
|
|
951
|
+
const skillPath = path8.join(skillsDir, folderName);
|
|
880
952
|
if (await fs5.pathExists(skillPath)) {
|
|
881
953
|
await fs5.remove(skillPath);
|
|
882
954
|
filesWritten.push(skillPath);
|
|
883
955
|
}
|
|
884
|
-
|
|
956
|
+
if (handlerRegistry.hasHandler("mcp")) {
|
|
957
|
+
const mcpHandler = handlerRegistry.getHandler("mcp");
|
|
958
|
+
const mcpFiles = await mcpHandler.uninstall(packageName, context);
|
|
959
|
+
filesWritten.push(...mcpFiles);
|
|
960
|
+
}
|
|
885
961
|
return {
|
|
886
962
|
success: true,
|
|
887
963
|
platform: "claude-code",
|
|
@@ -904,45 +980,23 @@ var ClaudeCodeAdapter = class extends PlatformAdapter {
|
|
|
904
980
|
return this.installFallback(manifest, context);
|
|
905
981
|
}
|
|
906
982
|
async installFallback(manifest, context) {
|
|
907
|
-
|
|
983
|
+
logger.warn(
|
|
984
|
+
`No handler registered for type "${manifest.type}", attempting content-based detection`
|
|
985
|
+
);
|
|
986
|
+
if (isSkillManifest(manifest)) {
|
|
908
987
|
const handler = handlerRegistry.getHandler("skill");
|
|
909
988
|
return handler.install(manifest, context);
|
|
910
989
|
}
|
|
911
|
-
if (
|
|
990
|
+
if (isMcpManifest(manifest)) {
|
|
912
991
|
const handler = handlerRegistry.getHandler("mcp");
|
|
913
992
|
return handler.install(manifest, context);
|
|
914
993
|
}
|
|
915
|
-
if (
|
|
994
|
+
if (isRulesManifest(manifest)) {
|
|
916
995
|
const handler = handlerRegistry.getHandler("rules");
|
|
917
996
|
return handler.install(manifest, context);
|
|
918
997
|
}
|
|
919
998
|
return [];
|
|
920
999
|
}
|
|
921
|
-
async removeMcpServer(serverName, filesWritten) {
|
|
922
|
-
const claudeHome = getClaudeHome();
|
|
923
|
-
const mcpConfigPath = path9.join(path9.dirname(claudeHome), ".claude.json");
|
|
924
|
-
if (!await fs5.pathExists(mcpConfigPath)) {
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
try {
|
|
928
|
-
const config = await fs5.readJson(mcpConfigPath);
|
|
929
|
-
const mcpServers = config.mcpServers;
|
|
930
|
-
if (!mcpServers || !mcpServers[serverName]) {
|
|
931
|
-
return;
|
|
932
|
-
}
|
|
933
|
-
const { [serverName]: _removed, ...remainingServers } = mcpServers;
|
|
934
|
-
const updatedConfig = {
|
|
935
|
-
...config,
|
|
936
|
-
mcpServers: remainingServers
|
|
937
|
-
};
|
|
938
|
-
await fs5.writeJson(mcpConfigPath, updatedConfig, { spaces: 2 });
|
|
939
|
-
filesWritten.push(mcpConfigPath);
|
|
940
|
-
} catch (error) {
|
|
941
|
-
logger.warn(
|
|
942
|
-
`Could not update MCP config: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
943
|
-
);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
1000
|
};
|
|
947
1001
|
|
|
948
1002
|
// src/adapters/index.ts
|
|
@@ -956,11 +1010,11 @@ function getAdapter(platform) {
|
|
|
956
1010
|
// src/utils/registry.ts
|
|
957
1011
|
import got from "got";
|
|
958
1012
|
import fs6 from "fs-extra";
|
|
959
|
-
import
|
|
1013
|
+
import path9 from "path";
|
|
960
1014
|
import os2 from "os";
|
|
961
1015
|
var DEFAULT_REGISTRY_URL = process.env.CPM_REGISTRY_URL || "https://raw.githubusercontent.com/cpmai-dev/packages/main/registry.json";
|
|
962
|
-
var CACHE_DIR =
|
|
963
|
-
var CACHE_FILE =
|
|
1016
|
+
var CACHE_DIR = path9.join(os2.homedir(), ".cpm", "cache");
|
|
1017
|
+
var CACHE_FILE = path9.join(CACHE_DIR, "registry.json");
|
|
964
1018
|
var comparators = {
|
|
965
1019
|
downloads: (a, b) => (b.downloads ?? 0) - (a.downloads ?? 0),
|
|
966
1020
|
stars: (a, b) => (b.stars ?? 0) - (a.stars ?? 0),
|
|
@@ -1101,7 +1155,7 @@ var registry = new Registry();
|
|
|
1101
1155
|
|
|
1102
1156
|
// src/utils/downloader.ts
|
|
1103
1157
|
import fs8 from "fs-extra";
|
|
1104
|
-
import
|
|
1158
|
+
import path11 from "path";
|
|
1105
1159
|
import os3 from "os";
|
|
1106
1160
|
|
|
1107
1161
|
// src/sources/manifest-resolver.ts
|
|
@@ -1195,76 +1249,45 @@ var ManifestResolver = class {
|
|
|
1195
1249
|
// src/sources/repository-source.ts
|
|
1196
1250
|
import got2 from "got";
|
|
1197
1251
|
import yaml from "yaml";
|
|
1252
|
+
var PACKAGES_REPO_BASE = "https://raw.githubusercontent.com/cpmai-dev/packages/main/packages";
|
|
1198
1253
|
var RepositorySource = class {
|
|
1199
|
-
/**
|
|
1200
|
-
* Name of this source for logging and debugging.
|
|
1201
|
-
*/
|
|
1202
1254
|
name = "repository";
|
|
1203
|
-
/**
|
|
1204
|
-
* Priority 1 - this is the first source tried.
|
|
1205
|
-
* Repository fetching is fast and provides complete manifests.
|
|
1206
|
-
*/
|
|
1207
1255
|
priority = 1;
|
|
1208
|
-
/**
|
|
1209
|
-
* Check if this source can fetch the given package.
|
|
1210
|
-
*
|
|
1211
|
-
* We can only fetch from GitHub repositories, so we check
|
|
1212
|
-
* if the package has a repository URL that includes "github.com".
|
|
1213
|
-
*
|
|
1214
|
-
* @param pkg - The registry package to check
|
|
1215
|
-
* @returns true if the package has a GitHub repository URL
|
|
1216
|
-
*
|
|
1217
|
-
* @example
|
|
1218
|
-
* ```typescript
|
|
1219
|
-
* // Package with GitHub repo
|
|
1220
|
-
* canFetch({ repository: "https://github.com/cpm/rules" }) // true
|
|
1221
|
-
*
|
|
1222
|
-
* // Package without repo
|
|
1223
|
-
* canFetch({ repository: undefined }) // false
|
|
1224
|
-
*
|
|
1225
|
-
* // Package with non-GitHub repo
|
|
1226
|
-
* canFetch({ repository: "https://gitlab.com/..." }) // false
|
|
1227
|
-
* ```
|
|
1228
|
-
*/
|
|
1229
1256
|
canFetch(pkg) {
|
|
1230
|
-
return !!pkg.repository?.includes("github.com");
|
|
1257
|
+
return !!pkg.path || !!pkg.repository?.includes("github.com");
|
|
1231
1258
|
}
|
|
1232
|
-
/**
|
|
1233
|
-
* Fetch the manifest from the package's GitHub repository.
|
|
1234
|
-
*
|
|
1235
|
-
* This method:
|
|
1236
|
-
* 1. Extracts the owner and repo name from the URL
|
|
1237
|
-
* 2. Constructs a raw.githubusercontent.com URL for cpm.yaml
|
|
1238
|
-
* 3. Fetches the file with a timeout
|
|
1239
|
-
* 4. Parses the YAML content
|
|
1240
|
-
*
|
|
1241
|
-
* @param pkg - The registry package to fetch
|
|
1242
|
-
* @param _context - Fetch context (not used by this source)
|
|
1243
|
-
* @returns The parsed manifest, or null if fetch fails
|
|
1244
|
-
*
|
|
1245
|
-
* @example
|
|
1246
|
-
* ```typescript
|
|
1247
|
-
* const manifest = await source.fetch({
|
|
1248
|
-
* name: "@cpm/typescript-rules",
|
|
1249
|
-
* repository: "https://github.com/cpm/typescript-rules",
|
|
1250
|
-
* // ... other fields
|
|
1251
|
-
* }, context);
|
|
1252
|
-
*
|
|
1253
|
-
* if (manifest) {
|
|
1254
|
-
* console.log("Fetched:", manifest.name, manifest.version);
|
|
1255
|
-
* }
|
|
1256
|
-
* ```
|
|
1257
|
-
*/
|
|
1258
1259
|
async fetch(pkg, _context) {
|
|
1259
|
-
if (
|
|
1260
|
+
if (pkg.path) {
|
|
1261
|
+
const manifest = await this.fetchFromPackagesRepo(pkg.path);
|
|
1262
|
+
if (manifest) return manifest;
|
|
1263
|
+
}
|
|
1264
|
+
if (pkg.repository) {
|
|
1265
|
+
return this.fetchFromStandaloneRepo(pkg.repository);
|
|
1266
|
+
}
|
|
1267
|
+
return null;
|
|
1268
|
+
}
|
|
1269
|
+
async fetchFromPackagesRepo(packagePath) {
|
|
1270
|
+
if (packagePath.includes("..") || packagePath.startsWith("/") || packagePath.includes("\\")) {
|
|
1271
|
+
return null;
|
|
1272
|
+
}
|
|
1273
|
+
const rawUrl = `${PACKAGES_REPO_BASE}/${packagePath}/cpm.yaml`;
|
|
1260
1274
|
try {
|
|
1261
|
-
const
|
|
1275
|
+
const response = await got2(rawUrl, {
|
|
1276
|
+
timeout: { request: TIMEOUTS.MANIFEST_FETCH }
|
|
1277
|
+
});
|
|
1278
|
+
return yaml.parse(response.body);
|
|
1279
|
+
} catch {
|
|
1280
|
+
return null;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
async fetchFromStandaloneRepo(repository) {
|
|
1284
|
+
try {
|
|
1285
|
+
const match = repository.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
1262
1286
|
if (!match) return null;
|
|
1263
1287
|
const [, owner, repo] = match;
|
|
1264
1288
|
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/main/cpm.yaml`;
|
|
1265
1289
|
const response = await got2(rawUrl, {
|
|
1266
1290
|
timeout: { request: TIMEOUTS.MANIFEST_FETCH }
|
|
1267
|
-
// 5 second timeout
|
|
1268
1291
|
});
|
|
1269
1292
|
return yaml.parse(response.body);
|
|
1270
1293
|
} catch {
|
|
@@ -1276,7 +1299,7 @@ var RepositorySource = class {
|
|
|
1276
1299
|
// src/sources/tarball-source.ts
|
|
1277
1300
|
import got3 from "got";
|
|
1278
1301
|
import fs7 from "fs-extra";
|
|
1279
|
-
import
|
|
1302
|
+
import path10 from "path";
|
|
1280
1303
|
import * as tar from "tar";
|
|
1281
1304
|
import yaml2 from "yaml";
|
|
1282
1305
|
var TarballSource = class {
|
|
@@ -1328,10 +1351,10 @@ var TarballSource = class {
|
|
|
1328
1351
|
responseType: "buffer"
|
|
1329
1352
|
// Get raw binary data
|
|
1330
1353
|
});
|
|
1331
|
-
const tarballPath =
|
|
1354
|
+
const tarballPath = path10.join(context.tempDir, "package.tar.gz");
|
|
1332
1355
|
await fs7.writeFile(tarballPath, response.body);
|
|
1333
1356
|
await this.extractTarball(tarballPath, context.tempDir);
|
|
1334
|
-
const manifestPath =
|
|
1357
|
+
const manifestPath = path10.join(context.tempDir, "cpm.yaml");
|
|
1335
1358
|
if (await fs7.pathExists(manifestPath)) {
|
|
1336
1359
|
const content = await fs7.readFile(manifestPath, "utf-8");
|
|
1337
1360
|
return yaml2.parse(content);
|
|
@@ -1356,7 +1379,7 @@ var TarballSource = class {
|
|
|
1356
1379
|
*/
|
|
1357
1380
|
async extractTarball(tarballPath, destDir) {
|
|
1358
1381
|
await fs7.ensureDir(destDir);
|
|
1359
|
-
const resolvedDestDir =
|
|
1382
|
+
const resolvedDestDir = path10.resolve(destDir);
|
|
1360
1383
|
await tar.extract({
|
|
1361
1384
|
file: tarballPath,
|
|
1362
1385
|
// The tarball file to extract
|
|
@@ -1366,8 +1389,8 @@ var TarballSource = class {
|
|
|
1366
1389
|
// Remove the top-level directory (e.g., "package-1.0.0/")
|
|
1367
1390
|
// Security filter: check each entry before extracting
|
|
1368
1391
|
filter: (entryPath) => {
|
|
1369
|
-
const resolvedPath =
|
|
1370
|
-
const isWithinDest = resolvedPath.startsWith(resolvedDestDir +
|
|
1392
|
+
const resolvedPath = path10.resolve(destDir, entryPath);
|
|
1393
|
+
const isWithinDest = resolvedPath.startsWith(resolvedDestDir + path10.sep) || resolvedPath === resolvedDestDir;
|
|
1371
1394
|
if (!isWithinDest) {
|
|
1372
1395
|
logger.warn(`Blocked path traversal in tarball: ${entryPath}`);
|
|
1373
1396
|
return false;
|
|
@@ -2048,11 +2071,11 @@ function createDefaultResolver() {
|
|
|
2048
2071
|
var defaultResolver = createDefaultResolver();
|
|
2049
2072
|
|
|
2050
2073
|
// src/utils/downloader.ts
|
|
2051
|
-
var TEMP_DIR =
|
|
2074
|
+
var TEMP_DIR = path11.join(os3.tmpdir(), "cpm-downloads");
|
|
2052
2075
|
async function downloadPackage(pkg) {
|
|
2053
2076
|
try {
|
|
2054
2077
|
await fs8.ensureDir(TEMP_DIR);
|
|
2055
|
-
const packageTempDir =
|
|
2078
|
+
const packageTempDir = path11.join(
|
|
2056
2079
|
TEMP_DIR,
|
|
2057
2080
|
`${pkg.name.replace(/[@/]/g, "_")}-${Date.now()}`
|
|
2058
2081
|
);
|
|
@@ -2198,7 +2221,7 @@ var SEMANTIC_COLORS = {
|
|
|
2198
2221
|
|
|
2199
2222
|
// src/commands/ui/formatters.ts
|
|
2200
2223
|
import chalk2 from "chalk";
|
|
2201
|
-
import
|
|
2224
|
+
import path12 from "path";
|
|
2202
2225
|
function formatNumber(num) {
|
|
2203
2226
|
if (num >= 1e6) {
|
|
2204
2227
|
return `${(num / 1e6).toFixed(1)}M`;
|
|
@@ -2209,7 +2232,7 @@ function formatNumber(num) {
|
|
|
2209
2232
|
return num.toString();
|
|
2210
2233
|
}
|
|
2211
2234
|
function formatPath(filePath) {
|
|
2212
|
-
const relativePath =
|
|
2235
|
+
const relativePath = path12.relative(process.cwd(), filePath);
|
|
2213
2236
|
if (relativePath.startsWith("..")) {
|
|
2214
2237
|
return filePath;
|
|
2215
2238
|
}
|
|
@@ -2271,8 +2294,12 @@ function formatUsageHints(manifest) {
|
|
|
2271
2294
|
if ("mcp" in manifest && manifest.mcp?.env) {
|
|
2272
2295
|
const envVars = Object.keys(manifest.mcp.env);
|
|
2273
2296
|
if (envVars.length > 0) {
|
|
2274
|
-
hints.push(
|
|
2275
|
-
|
|
2297
|
+
hints.push(
|
|
2298
|
+
chalk2.yellow(
|
|
2299
|
+
`
|
|
2300
|
+
Configure these environment variables in ~/.claude.json:`
|
|
2301
|
+
)
|
|
2302
|
+
);
|
|
2276
2303
|
for (const envVar of envVars) {
|
|
2277
2304
|
hints.push(chalk2.dim(` - ${envVar}`));
|
|
2278
2305
|
}
|
|
@@ -2540,10 +2567,10 @@ async function searchCommand(query, rawOptions) {
|
|
|
2540
2567
|
// src/commands/list.ts
|
|
2541
2568
|
import chalk4 from "chalk";
|
|
2542
2569
|
import fs9 from "fs-extra";
|
|
2543
|
-
import
|
|
2570
|
+
import path13 from "path";
|
|
2544
2571
|
import os4 from "os";
|
|
2545
2572
|
async function readPackageMetadata(packageDir) {
|
|
2546
|
-
const metadataPath =
|
|
2573
|
+
const metadataPath = path13.join(packageDir, ".cpm.json");
|
|
2547
2574
|
try {
|
|
2548
2575
|
if (await fs9.pathExists(metadataPath)) {
|
|
2549
2576
|
return await fs9.readJson(metadataPath);
|
|
@@ -2559,7 +2586,7 @@ async function scanDirectory(dir, type) {
|
|
|
2559
2586
|
}
|
|
2560
2587
|
const entries = await fs9.readdir(dir);
|
|
2561
2588
|
for (const entry of entries) {
|
|
2562
|
-
const entryPath =
|
|
2589
|
+
const entryPath = path13.join(dir, entry);
|
|
2563
2590
|
const stat = await fs9.stat(entryPath);
|
|
2564
2591
|
if (stat.isDirectory()) {
|
|
2565
2592
|
const metadata = await readPackageMetadata(entryPath);
|
|
@@ -2576,7 +2603,7 @@ async function scanDirectory(dir, type) {
|
|
|
2576
2603
|
}
|
|
2577
2604
|
async function scanMcpServers() {
|
|
2578
2605
|
const items = [];
|
|
2579
|
-
const configPath =
|
|
2606
|
+
const configPath = path13.join(os4.homedir(), ".claude.json");
|
|
2580
2607
|
if (!await fs9.pathExists(configPath)) {
|
|
2581
2608
|
return items;
|
|
2582
2609
|
}
|
|
@@ -2596,10 +2623,10 @@ async function scanMcpServers() {
|
|
|
2596
2623
|
return items;
|
|
2597
2624
|
}
|
|
2598
2625
|
async function scanInstalledPackages() {
|
|
2599
|
-
const claudeHome =
|
|
2626
|
+
const claudeHome = path13.join(os4.homedir(), ".claude");
|
|
2600
2627
|
const [rules, skills, mcp] = await Promise.all([
|
|
2601
|
-
scanDirectory(
|
|
2602
|
-
scanDirectory(
|
|
2628
|
+
scanDirectory(path13.join(claudeHome, "rules"), "rules"),
|
|
2629
|
+
scanDirectory(path13.join(claudeHome, "skills"), "skill"),
|
|
2603
2630
|
scanMcpServers()
|
|
2604
2631
|
]);
|
|
2605
2632
|
return [...rules, ...skills, ...mcp];
|