prpm 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/__tests__/e2e/test-helpers.js +150 -0
- package/dist/commands/collections.js +248 -51
- package/dist/commands/index.js +110 -51
- package/dist/commands/info.js +8 -7
- package/dist/commands/install.js +270 -45
- package/dist/commands/list.js +91 -14
- package/dist/commands/login.js +86 -24
- package/dist/commands/outdated.js +3 -2
- package/dist/commands/publish.js +98 -41
- package/dist/commands/remove.js +43 -10
- package/dist/commands/schema.js +37 -0
- package/dist/commands/search.js +263 -38
- package/dist/commands/trending.js +4 -3
- package/dist/commands/uninstall.js +77 -0
- package/dist/commands/update.js +3 -2
- package/dist/commands/upgrade.js +3 -2
- package/dist/commands/whoami.js +25 -1
- package/dist/core/claude-config.js +91 -0
- package/dist/core/cursor-config.js +130 -0
- package/dist/core/filesystem.js +30 -0
- package/dist/core/lockfile.js +57 -0
- package/dist/core/marketplace-converter.js +198 -0
- package/dist/core/schema-validator.js +74 -0
- package/dist/core/telemetry.js +5 -0
- package/dist/index.js +6 -7
- package/dist/types/registry.js +5 -0
- package/package.json +15 -7
- package/dist/commands/add.js +0 -107
- package/dist/commands/deps.js +0 -92
- package/dist/core/config.js +0 -91
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Claude agent configuration utilities
|
|
4
|
+
* Handles applying user config to Claude agent YAML frontmatter
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.hasClaudeHeader = hasClaudeHeader;
|
|
8
|
+
exports.applyClaudeConfig = applyClaudeConfig;
|
|
9
|
+
exports.parseClaudeFrontmatter = parseClaudeFrontmatter;
|
|
10
|
+
/**
|
|
11
|
+
* Check if content has Claude agent YAML frontmatter
|
|
12
|
+
*/
|
|
13
|
+
function hasClaudeHeader(content) {
|
|
14
|
+
return content.startsWith('---\n') && content.includes('name:');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Apply user's Claude agent config to agent file
|
|
18
|
+
* Merges user config with existing frontmatter, with user config taking precedence
|
|
19
|
+
*/
|
|
20
|
+
function applyClaudeConfig(content, config) {
|
|
21
|
+
if (!hasClaudeHeader(content)) {
|
|
22
|
+
return content;
|
|
23
|
+
}
|
|
24
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
25
|
+
if (!match) {
|
|
26
|
+
return content;
|
|
27
|
+
}
|
|
28
|
+
const [, frontmatterText, body] = match;
|
|
29
|
+
// Parse existing frontmatter
|
|
30
|
+
const frontmatter = {};
|
|
31
|
+
frontmatterText.split('\n').forEach(line => {
|
|
32
|
+
const colonIndex = line.indexOf(':');
|
|
33
|
+
if (colonIndex > 0) {
|
|
34
|
+
const key = line.substring(0, colonIndex).trim();
|
|
35
|
+
const value = line.substring(colonIndex + 1).trim();
|
|
36
|
+
frontmatter[key] = value;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
// Apply user config overrides
|
|
40
|
+
if (config.tools !== undefined) {
|
|
41
|
+
frontmatter.tools = config.tools;
|
|
42
|
+
}
|
|
43
|
+
if (config.model !== undefined) {
|
|
44
|
+
frontmatter.model = config.model;
|
|
45
|
+
}
|
|
46
|
+
// Rebuild frontmatter
|
|
47
|
+
const lines = ['---'];
|
|
48
|
+
// Ensure required fields come first
|
|
49
|
+
if (frontmatter.name) {
|
|
50
|
+
lines.push(`name: ${frontmatter.name}`);
|
|
51
|
+
}
|
|
52
|
+
if (frontmatter.description) {
|
|
53
|
+
lines.push(`description: ${frontmatter.description}`);
|
|
54
|
+
}
|
|
55
|
+
// Add optional fields
|
|
56
|
+
const optionalFields = ['icon', 'tools', 'model'];
|
|
57
|
+
for (const field of optionalFields) {
|
|
58
|
+
if (frontmatter[field] && field !== 'name' && field !== 'description') {
|
|
59
|
+
lines.push(`${field}: ${frontmatter[field]}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Add any other fields that might exist
|
|
63
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
64
|
+
if (!['name', 'description', 'icon', 'tools', 'model'].includes(key)) {
|
|
65
|
+
lines.push(`${key}: ${value}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
lines.push('---');
|
|
69
|
+
return lines.join('\n') + '\n' + body;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Parse Claude agent frontmatter
|
|
73
|
+
*/
|
|
74
|
+
function parseClaudeFrontmatter(content) {
|
|
75
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
76
|
+
if (!match) {
|
|
77
|
+
return { frontmatter: {}, body: content };
|
|
78
|
+
}
|
|
79
|
+
const [, frontmatterText, body] = match;
|
|
80
|
+
// Simple YAML parsing (for basic key: value pairs)
|
|
81
|
+
const frontmatter = {};
|
|
82
|
+
frontmatterText.split('\n').forEach(line => {
|
|
83
|
+
const colonIndex = line.indexOf(':');
|
|
84
|
+
if (colonIndex > 0) {
|
|
85
|
+
const key = line.substring(0, colonIndex).trim();
|
|
86
|
+
const value = line.substring(colonIndex + 1).trim();
|
|
87
|
+
frontmatter[key] = value;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
return { frontmatter, body };
|
|
91
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cursor MDC header configuration utilities
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.applyCursorConfig = applyCursorConfig;
|
|
7
|
+
exports.hasMDCHeader = hasMDCHeader;
|
|
8
|
+
exports.addMDCHeader = addMDCHeader;
|
|
9
|
+
/**
|
|
10
|
+
* Apply cursor config to MDC header in content
|
|
11
|
+
* Replaces configurable fields in the YAML frontmatter
|
|
12
|
+
*/
|
|
13
|
+
function applyCursorConfig(content, config) {
|
|
14
|
+
// Check if content has MDC header (YAML frontmatter)
|
|
15
|
+
if (!content.startsWith('---')) {
|
|
16
|
+
return content;
|
|
17
|
+
}
|
|
18
|
+
const lines = content.split('\n');
|
|
19
|
+
const headerEndIndex = lines.findIndex((line, index) => index > 0 && line === '---');
|
|
20
|
+
if (headerEndIndex === -1) {
|
|
21
|
+
// Malformed header, return as-is
|
|
22
|
+
return content;
|
|
23
|
+
}
|
|
24
|
+
// Extract header lines (excluding the --- markers)
|
|
25
|
+
const headerLines = lines.slice(1, headerEndIndex);
|
|
26
|
+
const bodyLines = lines.slice(headerEndIndex + 1);
|
|
27
|
+
// Parse and update header
|
|
28
|
+
const updatedHeaderLines = [];
|
|
29
|
+
let i = 0;
|
|
30
|
+
while (i < headerLines.length) {
|
|
31
|
+
const line = headerLines[i];
|
|
32
|
+
// Check for fields that should be replaced by config
|
|
33
|
+
if (line.startsWith('version:') && config.version) {
|
|
34
|
+
updatedHeaderLines.push(`version: "${config.version}"`);
|
|
35
|
+
i++;
|
|
36
|
+
}
|
|
37
|
+
else if (line.startsWith('globs:') && config.globs) {
|
|
38
|
+
// Replace globs array
|
|
39
|
+
updatedHeaderLines.push('globs:');
|
|
40
|
+
config.globs.forEach((glob) => {
|
|
41
|
+
updatedHeaderLines.push(` - "${glob}"`);
|
|
42
|
+
});
|
|
43
|
+
// Skip existing globs in the original header
|
|
44
|
+
i++;
|
|
45
|
+
while (i < headerLines.length && headerLines[i].startsWith(' - ')) {
|
|
46
|
+
i++;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else if (line.startsWith('alwaysApply:') && config.alwaysApply !== undefined) {
|
|
50
|
+
updatedHeaderLines.push(`alwaysApply: ${config.alwaysApply}`);
|
|
51
|
+
i++;
|
|
52
|
+
}
|
|
53
|
+
else if (line.startsWith('author:') && config.author) {
|
|
54
|
+
// Replace existing author
|
|
55
|
+
updatedHeaderLines.push(`author: "${config.author}"`);
|
|
56
|
+
i++;
|
|
57
|
+
}
|
|
58
|
+
else if (line.startsWith('tags:') && config.tags) {
|
|
59
|
+
// Replace tags array
|
|
60
|
+
updatedHeaderLines.push('tags:');
|
|
61
|
+
config.tags.forEach((tag) => {
|
|
62
|
+
updatedHeaderLines.push(` - "${tag}"`);
|
|
63
|
+
});
|
|
64
|
+
// Skip existing tags in the original header
|
|
65
|
+
i++;
|
|
66
|
+
while (i < headerLines.length && headerLines[i].startsWith(' - ')) {
|
|
67
|
+
i++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// Keep existing line
|
|
72
|
+
updatedHeaderLines.push(line);
|
|
73
|
+
i++;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Add new fields if they don't exist
|
|
77
|
+
const hasAuthor = updatedHeaderLines.some(line => line.startsWith('author:'));
|
|
78
|
+
const hasTags = updatedHeaderLines.some(line => line.startsWith('tags:'));
|
|
79
|
+
if (config.author && !hasAuthor) {
|
|
80
|
+
updatedHeaderLines.push(`author: "${config.author}"`);
|
|
81
|
+
}
|
|
82
|
+
if (config.tags && !hasTags) {
|
|
83
|
+
updatedHeaderLines.push('tags:');
|
|
84
|
+
config.tags.forEach((tag) => {
|
|
85
|
+
updatedHeaderLines.push(` - "${tag}"`);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// Reconstruct content
|
|
89
|
+
return ['---', ...updatedHeaderLines, '---', ...bodyLines].join('\n');
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if content has valid MDC header (YAML frontmatter)
|
|
93
|
+
* A valid MDC header must:
|
|
94
|
+
* 1. Start with ---
|
|
95
|
+
* 2. Have a closing --- on its own line
|
|
96
|
+
* 3. Have at least a description field
|
|
97
|
+
*/
|
|
98
|
+
function hasMDCHeader(content) {
|
|
99
|
+
if (!content.startsWith('---\n')) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
const lines = content.split('\n');
|
|
103
|
+
const headerEndIndex = lines.findIndex((line, index) => index > 0 && line === '---');
|
|
104
|
+
// Must have closing ---
|
|
105
|
+
if (headerEndIndex === -1) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
// Extract header lines (excluding the --- markers)
|
|
109
|
+
const headerLines = lines.slice(1, headerEndIndex);
|
|
110
|
+
// Must have at least one valid YAML field (typically description)
|
|
111
|
+
const hasValidField = headerLines.some(line => {
|
|
112
|
+
const trimmed = line.trim();
|
|
113
|
+
return trimmed.length > 0 && trimmed.includes(':') && !trimmed.startsWith('#');
|
|
114
|
+
});
|
|
115
|
+
return hasValidField;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Add MDC header to content if missing
|
|
119
|
+
* Creates a basic YAML frontmatter with description
|
|
120
|
+
*/
|
|
121
|
+
function addMDCHeader(content, packageDescription) {
|
|
122
|
+
const description = packageDescription || 'Cursor rule for coding standards and best practices';
|
|
123
|
+
const header = [
|
|
124
|
+
'---',
|
|
125
|
+
`description: "${description}"`,
|
|
126
|
+
'---',
|
|
127
|
+
'',
|
|
128
|
+
].join('\n');
|
|
129
|
+
return header + content;
|
|
130
|
+
}
|
package/dist/core/filesystem.js
CHANGED
|
@@ -12,6 +12,7 @@ exports.saveFile = saveFile;
|
|
|
12
12
|
exports.deleteFile = deleteFile;
|
|
13
13
|
exports.fileExists = fileExists;
|
|
14
14
|
exports.generateId = generateId;
|
|
15
|
+
exports.stripAuthorNamespace = stripAuthorNamespace;
|
|
15
16
|
const fs_1 = require("fs");
|
|
16
17
|
const path_1 = __importDefault(require("path"));
|
|
17
18
|
/**
|
|
@@ -21,8 +22,24 @@ function getDestinationDir(type) {
|
|
|
21
22
|
switch (type) {
|
|
22
23
|
case 'cursor':
|
|
23
24
|
return '.cursor/rules';
|
|
25
|
+
case 'cursor-agent':
|
|
26
|
+
return '.cursor/agents';
|
|
27
|
+
case 'cursor-slash-command':
|
|
28
|
+
return '.cursor/commands';
|
|
24
29
|
case 'claude':
|
|
25
30
|
return '.claude/agents';
|
|
31
|
+
case 'claude-agent':
|
|
32
|
+
return '.claude/agents';
|
|
33
|
+
case 'claude-skill':
|
|
34
|
+
return '.claude/skills';
|
|
35
|
+
case 'claude-slash-command':
|
|
36
|
+
return '.claude/commands';
|
|
37
|
+
case 'continue':
|
|
38
|
+
return '.continue/rules';
|
|
39
|
+
case 'windsurf':
|
|
40
|
+
return '.windsurf/rules';
|
|
41
|
+
case 'generic':
|
|
42
|
+
return '.prompts';
|
|
26
43
|
default:
|
|
27
44
|
throw new Error(`Unknown package type: ${type}`);
|
|
28
45
|
}
|
|
@@ -92,3 +109,16 @@ function generateId(filename) {
|
|
|
92
109
|
.replace(/[^a-z0-9]+/g, '-')
|
|
93
110
|
.replace(/^-+|-+$/g, '');
|
|
94
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Strip author namespace from package ID and return just the package name
|
|
114
|
+
* @example
|
|
115
|
+
* stripAuthorNamespace('@community/git-workflow-manager') // 'git-workflow-manager'
|
|
116
|
+
* stripAuthorNamespace('community/git-workflow-manager') // 'git-workflow-manager'
|
|
117
|
+
* stripAuthorNamespace('@wshobson/commands/agent-orchestration/improve-agent') // 'improve-agent'
|
|
118
|
+
* stripAuthorNamespace('git-workflow-manager') // 'git-workflow-manager'
|
|
119
|
+
*/
|
|
120
|
+
function stripAuthorNamespace(packageId) {
|
|
121
|
+
// Split by '/' and get the last segment (the actual package name)
|
|
122
|
+
const parts = packageId.split('/');
|
|
123
|
+
return parts[parts.length - 1];
|
|
124
|
+
}
|
package/dist/core/lockfile.js
CHANGED
|
@@ -14,6 +14,10 @@ exports.getLockedVersion = getLockedVersion;
|
|
|
14
14
|
exports.isLockfileOutOfSync = isLockfileOutOfSync;
|
|
15
15
|
exports.mergeLockfiles = mergeLockfiles;
|
|
16
16
|
exports.pruneLockfile = pruneLockfile;
|
|
17
|
+
exports.addPackage = addPackage;
|
|
18
|
+
exports.removePackage = removePackage;
|
|
19
|
+
exports.listPackages = listPackages;
|
|
20
|
+
exports.getPackage = getPackage;
|
|
17
21
|
const fs_1 = require("fs");
|
|
18
22
|
const path_1 = require("path");
|
|
19
23
|
const crypto_1 = require("crypto");
|
|
@@ -70,6 +74,7 @@ function addToLockfile(lockfile, packageId, packageInfo) {
|
|
|
70
74
|
dependencies: packageInfo.dependencies,
|
|
71
75
|
type: packageInfo.type,
|
|
72
76
|
format: packageInfo.format,
|
|
77
|
+
installedPath: packageInfo.installedPath,
|
|
73
78
|
};
|
|
74
79
|
lockfile.generated = new Date().toISOString();
|
|
75
80
|
}
|
|
@@ -180,3 +185,55 @@ function pruneLockfile(lockfile, requiredPackages) {
|
|
|
180
185
|
pruned.generated = new Date().toISOString();
|
|
181
186
|
return pruned;
|
|
182
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Add package to lock file (convenience wrapper)
|
|
190
|
+
*/
|
|
191
|
+
async function addPackage(packageInfo) {
|
|
192
|
+
const lockfile = (await readLockfile()) || createLockfile();
|
|
193
|
+
addToLockfile(lockfile, packageInfo.id, {
|
|
194
|
+
version: packageInfo.version,
|
|
195
|
+
tarballUrl: packageInfo.tarballUrl,
|
|
196
|
+
dependencies: packageInfo.dependencies,
|
|
197
|
+
type: packageInfo.type,
|
|
198
|
+
format: packageInfo.format,
|
|
199
|
+
installedPath: packageInfo.installedPath,
|
|
200
|
+
});
|
|
201
|
+
await writeLockfile(lockfile);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Remove package from lock file
|
|
205
|
+
*/
|
|
206
|
+
async function removePackage(packageId) {
|
|
207
|
+
const lockfile = await readLockfile();
|
|
208
|
+
if (!lockfile || !lockfile.packages[packageId]) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
const removed = lockfile.packages[packageId];
|
|
212
|
+
delete lockfile.packages[packageId];
|
|
213
|
+
lockfile.generated = new Date().toISOString();
|
|
214
|
+
await writeLockfile(lockfile);
|
|
215
|
+
return removed;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* List all packages in lock file
|
|
219
|
+
*/
|
|
220
|
+
async function listPackages() {
|
|
221
|
+
const lockfile = await readLockfile();
|
|
222
|
+
if (!lockfile) {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
return Object.entries(lockfile.packages).map(([id, pkg]) => ({
|
|
226
|
+
id,
|
|
227
|
+
...pkg,
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get a specific package from lock file
|
|
232
|
+
*/
|
|
233
|
+
async function getPackage(packageId) {
|
|
234
|
+
const lockfile = await readLockfile();
|
|
235
|
+
if (!lockfile || !lockfile.packages[packageId]) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
return lockfile.packages[packageId];
|
|
239
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Converter for Claude marketplace.json format to PRPM manifest
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.marketplaceToManifest = marketplaceToManifest;
|
|
7
|
+
exports.validateMarketplaceJson = validateMarketplaceJson;
|
|
8
|
+
/**
|
|
9
|
+
* Convert marketplace.json to PRPM manifest format
|
|
10
|
+
*
|
|
11
|
+
* Strategy:
|
|
12
|
+
* - If multiple plugins exist, create a manifest for the first plugin (user can publish others separately)
|
|
13
|
+
* - If the plugin has agents/skills/commands, prefer those over the root plugin info
|
|
14
|
+
* - Map marketplace fields to PRPM manifest fields
|
|
15
|
+
*
|
|
16
|
+
* @param marketplace - The marketplace.json content
|
|
17
|
+
* @param pluginIndex - Which plugin to convert (default: 0, for the first plugin)
|
|
18
|
+
* @returns PRPM manifest
|
|
19
|
+
*/
|
|
20
|
+
function marketplaceToManifest(marketplace, pluginIndex = 0) {
|
|
21
|
+
if (!marketplace.plugins || marketplace.plugins.length === 0) {
|
|
22
|
+
throw new Error('marketplace.json must contain at least one plugin');
|
|
23
|
+
}
|
|
24
|
+
if (pluginIndex >= marketplace.plugins.length) {
|
|
25
|
+
throw new Error(`Plugin index ${pluginIndex} out of range. Found ${marketplace.plugins.length} plugins.`);
|
|
26
|
+
}
|
|
27
|
+
const plugin = marketplace.plugins[pluginIndex];
|
|
28
|
+
// Determine package type based on what the plugin contains
|
|
29
|
+
let type = 'claude';
|
|
30
|
+
if (plugin.agents && plugin.agents.length > 0) {
|
|
31
|
+
type = 'claude';
|
|
32
|
+
}
|
|
33
|
+
else if (plugin.skills && plugin.skills.length > 0) {
|
|
34
|
+
type = 'claude';
|
|
35
|
+
}
|
|
36
|
+
else if (plugin.commands && plugin.commands.length > 0) {
|
|
37
|
+
type = 'claude';
|
|
38
|
+
}
|
|
39
|
+
// Generate package name from plugin name
|
|
40
|
+
// Format: @owner/plugin-name
|
|
41
|
+
const packageName = generatePackageName(marketplace.owner, plugin.name);
|
|
42
|
+
// Collect all files that should be included
|
|
43
|
+
const files = collectFiles(plugin);
|
|
44
|
+
// Determine the main file
|
|
45
|
+
const main = determineMainFile(plugin);
|
|
46
|
+
// Collect keywords from both marketplace and plugin
|
|
47
|
+
const keywords = [
|
|
48
|
+
...(marketplace.keywords || []),
|
|
49
|
+
...(plugin.keywords || []),
|
|
50
|
+
].slice(0, 20); // Max 20 keywords
|
|
51
|
+
// Extract tags from keywords (first 10)
|
|
52
|
+
const tags = keywords.slice(0, 10);
|
|
53
|
+
const manifest = {
|
|
54
|
+
name: packageName,
|
|
55
|
+
version: plugin.version || marketplace.version || '1.0.0',
|
|
56
|
+
description: plugin.description || marketplace.description,
|
|
57
|
+
type,
|
|
58
|
+
author: plugin.author || marketplace.owner,
|
|
59
|
+
files,
|
|
60
|
+
tags,
|
|
61
|
+
keywords,
|
|
62
|
+
};
|
|
63
|
+
// Add optional fields if available
|
|
64
|
+
if (marketplace.githubUrl) {
|
|
65
|
+
manifest.repository = marketplace.githubUrl;
|
|
66
|
+
}
|
|
67
|
+
if (marketplace.websiteUrl) {
|
|
68
|
+
manifest.homepage = marketplace.websiteUrl;
|
|
69
|
+
}
|
|
70
|
+
if (plugin.category) {
|
|
71
|
+
manifest.category = plugin.category;
|
|
72
|
+
}
|
|
73
|
+
if (main) {
|
|
74
|
+
manifest.main = main;
|
|
75
|
+
}
|
|
76
|
+
return manifest;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Generate PRPM-compatible package name from owner and plugin name
|
|
80
|
+
*/
|
|
81
|
+
function generatePackageName(owner, pluginName) {
|
|
82
|
+
// Sanitize owner and plugin name
|
|
83
|
+
const sanitizedOwner = owner.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
84
|
+
const sanitizedName = pluginName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
85
|
+
// Remove leading/trailing hyphens
|
|
86
|
+
const cleanOwner = sanitizedOwner.replace(/^-+|-+$/g, '');
|
|
87
|
+
const cleanName = sanitizedName.replace(/^-+|-+$/g, '');
|
|
88
|
+
return `@${cleanOwner}/${cleanName}`;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Collect all files referenced in the plugin
|
|
92
|
+
*/
|
|
93
|
+
function collectFiles(plugin) {
|
|
94
|
+
const files = new Set();
|
|
95
|
+
// Add plugin source if it's a file path
|
|
96
|
+
if (plugin.source && !plugin.source.startsWith('http')) {
|
|
97
|
+
files.add(plugin.source);
|
|
98
|
+
}
|
|
99
|
+
// Add agent files
|
|
100
|
+
if (plugin.agents) {
|
|
101
|
+
for (const agent of plugin.agents) {
|
|
102
|
+
if (agent.source && !agent.source.startsWith('http')) {
|
|
103
|
+
files.add(agent.source);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Add skill files
|
|
108
|
+
if (plugin.skills) {
|
|
109
|
+
for (const skill of plugin.skills) {
|
|
110
|
+
if (skill.source && !skill.source.startsWith('http')) {
|
|
111
|
+
files.add(skill.source);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Add command files
|
|
116
|
+
if (plugin.commands) {
|
|
117
|
+
for (const command of plugin.commands) {
|
|
118
|
+
if (command.source && !command.source.startsWith('http')) {
|
|
119
|
+
files.add(command.source);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Add standard files if they're not already included
|
|
124
|
+
const standardFiles = ['README.md', 'LICENSE', '.claude/marketplace.json'];
|
|
125
|
+
for (const file of standardFiles) {
|
|
126
|
+
files.add(file);
|
|
127
|
+
}
|
|
128
|
+
return Array.from(files);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Determine the main entry file for the package
|
|
132
|
+
* Only set main if there's a single clear entry point
|
|
133
|
+
*/
|
|
134
|
+
function determineMainFile(plugin) {
|
|
135
|
+
const agentCount = plugin.agents?.length || 0;
|
|
136
|
+
const skillCount = plugin.skills?.length || 0;
|
|
137
|
+
const commandCount = plugin.commands?.length || 0;
|
|
138
|
+
// Only set main if there's exactly one item total
|
|
139
|
+
const totalCount = agentCount + skillCount + commandCount;
|
|
140
|
+
if (totalCount !== 1) {
|
|
141
|
+
// Multiple items or no items - no clear main file
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
// Single agent
|
|
145
|
+
if (agentCount === 1) {
|
|
146
|
+
const source = plugin.agents[0].source;
|
|
147
|
+
if (source && !source.startsWith('http')) {
|
|
148
|
+
return source;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Single skill
|
|
152
|
+
if (skillCount === 1) {
|
|
153
|
+
const source = plugin.skills[0].source;
|
|
154
|
+
if (source && !source.startsWith('http')) {
|
|
155
|
+
return source;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Single command
|
|
159
|
+
if (commandCount === 1) {
|
|
160
|
+
const source = plugin.commands[0].source;
|
|
161
|
+
if (source && !source.startsWith('http')) {
|
|
162
|
+
return source;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Otherwise, use plugin source if available
|
|
166
|
+
if (plugin.source && !plugin.source.startsWith('http')) {
|
|
167
|
+
return plugin.source;
|
|
168
|
+
}
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Validate marketplace.json structure
|
|
173
|
+
*/
|
|
174
|
+
function validateMarketplaceJson(data) {
|
|
175
|
+
if (!data || typeof data !== 'object') {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
const marketplace = data;
|
|
179
|
+
// Check required fields
|
|
180
|
+
if (!marketplace.name || typeof marketplace.name !== 'string') {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
if (!marketplace.owner || typeof marketplace.owner !== 'string') {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
if (!marketplace.description || typeof marketplace.description !== 'string') {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
if (!Array.isArray(marketplace.plugins) || marketplace.plugins.length === 0) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
// Validate first plugin has required fields
|
|
193
|
+
const plugin = marketplace.plugins[0];
|
|
194
|
+
if (!plugin.name || !plugin.description || !plugin.version) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* JSON Schema validation for PRPM manifests
|
|
4
|
+
*/
|
|
5
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.validateManifestSchema = validateManifestSchema;
|
|
10
|
+
exports.getManifestSchema = getManifestSchema;
|
|
11
|
+
const ajv_1 = __importDefault(require("ajv"));
|
|
12
|
+
const ajv_formats_1 = __importDefault(require("ajv-formats"));
|
|
13
|
+
const fs_1 = require("fs");
|
|
14
|
+
const path_1 = require("path");
|
|
15
|
+
// Load the JSON schema
|
|
16
|
+
const schemaPath = (0, path_1.join)(__dirname, '../../schemas/prpm-manifest.schema.json');
|
|
17
|
+
let schema;
|
|
18
|
+
try {
|
|
19
|
+
schema = JSON.parse((0, fs_1.readFileSync)(schemaPath, 'utf-8'));
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
// Schema file not found, validation will be skipped
|
|
23
|
+
console.warn('⚠️ Could not load manifest schema, skipping schema validation');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Validate manifest against JSON schema
|
|
27
|
+
*/
|
|
28
|
+
function validateManifestSchema(manifest) {
|
|
29
|
+
if (!schema) {
|
|
30
|
+
// Schema not loaded, skip validation
|
|
31
|
+
return { valid: true };
|
|
32
|
+
}
|
|
33
|
+
const ajv = new ajv_1.default({
|
|
34
|
+
allErrors: true,
|
|
35
|
+
verbose: true,
|
|
36
|
+
});
|
|
37
|
+
(0, ajv_formats_1.default)(ajv);
|
|
38
|
+
const validate = ajv.compile(schema);
|
|
39
|
+
const valid = validate(manifest);
|
|
40
|
+
if (!valid && validate.errors) {
|
|
41
|
+
const errors = validate.errors.map(err => {
|
|
42
|
+
const path = err.instancePath || 'manifest';
|
|
43
|
+
const message = err.message || 'validation failed';
|
|
44
|
+
// Format error messages to be more user-friendly
|
|
45
|
+
if (err.keyword === 'required') {
|
|
46
|
+
const missingProp = err.params.missingProperty;
|
|
47
|
+
return `Missing required field: ${missingProp}`;
|
|
48
|
+
}
|
|
49
|
+
if (err.keyword === 'pattern') {
|
|
50
|
+
return `${path}: ${message}. Value does not match required pattern.`;
|
|
51
|
+
}
|
|
52
|
+
if (err.keyword === 'enum') {
|
|
53
|
+
const allowedValues = err.params.allowedValues;
|
|
54
|
+
return `${path}: ${message}. Allowed values: ${allowedValues.join(', ')}`;
|
|
55
|
+
}
|
|
56
|
+
if (err.keyword === 'minLength' || err.keyword === 'maxLength') {
|
|
57
|
+
const limit = err.params.limit;
|
|
58
|
+
return `${path}: ${message} (${err.keyword}: ${limit})`;
|
|
59
|
+
}
|
|
60
|
+
if (err.keyword === 'oneOf') {
|
|
61
|
+
return `${path}: must match exactly one schema (check if files array uses either all strings or all objects, not mixed)`;
|
|
62
|
+
}
|
|
63
|
+
return `${path}: ${message}`;
|
|
64
|
+
});
|
|
65
|
+
return { valid: false, errors };
|
|
66
|
+
}
|
|
67
|
+
return { valid: true };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get the JSON schema (for documentation/export purposes)
|
|
71
|
+
*/
|
|
72
|
+
function getManifestSchema() {
|
|
73
|
+
return schema;
|
|
74
|
+
}
|
package/dist/core/telemetry.js
CHANGED
|
@@ -126,11 +126,16 @@ class Telemetry {
|
|
|
126
126
|
async shutdown() {
|
|
127
127
|
if (this.posthog) {
|
|
128
128
|
try {
|
|
129
|
+
// Flush any pending events before shutdown
|
|
130
|
+
await this.posthog.flush();
|
|
129
131
|
await this.posthog.shutdown();
|
|
130
132
|
}
|
|
131
133
|
catch (error) {
|
|
132
134
|
// Silently fail
|
|
133
135
|
}
|
|
136
|
+
finally {
|
|
137
|
+
this.posthog = null;
|
|
138
|
+
}
|
|
134
139
|
}
|
|
135
140
|
}
|
|
136
141
|
// Send to PostHog
|
package/dist/index.js
CHANGED
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
const commander_1 = require("commander");
|
|
8
|
-
const add_1 = require("./commands/add");
|
|
9
8
|
const list_1 = require("./commands/list");
|
|
10
|
-
const
|
|
9
|
+
const uninstall_1 = require("./commands/uninstall");
|
|
11
10
|
const index_1 = require("./commands/index");
|
|
12
11
|
const telemetry_1 = require("./commands/telemetry");
|
|
13
12
|
const search_1 = require("./commands/search");
|
|
@@ -18,16 +17,16 @@ const publish_1 = require("./commands/publish");
|
|
|
18
17
|
const login_1 = require("./commands/login");
|
|
19
18
|
const whoami_1 = require("./commands/whoami");
|
|
20
19
|
const collections_1 = require("./commands/collections");
|
|
21
|
-
const deps_1 = require("./commands/deps");
|
|
22
20
|
const outdated_1 = require("./commands/outdated");
|
|
23
21
|
const update_1 = require("./commands/update");
|
|
24
22
|
const upgrade_1 = require("./commands/upgrade");
|
|
23
|
+
const schema_1 = require("./commands/schema");
|
|
25
24
|
const telemetry_2 = require("./core/telemetry");
|
|
26
25
|
const program = new commander_1.Command();
|
|
27
26
|
program
|
|
28
27
|
.name('prpm')
|
|
29
28
|
.description('Prompt Package Manager - Install and manage prompt-based files')
|
|
30
|
-
.version('
|
|
29
|
+
.version('0.0.1');
|
|
31
30
|
// Registry commands (new)
|
|
32
31
|
program.addCommand((0, search_1.createSearchCommand)());
|
|
33
32
|
program.addCommand((0, install_1.createInstallCommand)());
|
|
@@ -37,16 +36,16 @@ program.addCommand((0, publish_1.createPublishCommand)());
|
|
|
37
36
|
program.addCommand((0, login_1.createLoginCommand)());
|
|
38
37
|
program.addCommand((0, whoami_1.createWhoamiCommand)());
|
|
39
38
|
program.addCommand((0, collections_1.createCollectionsCommand)());
|
|
40
|
-
program.addCommand((0, deps_1.createDepsCommand)());
|
|
41
39
|
program.addCommand((0, outdated_1.createOutdatedCommand)());
|
|
42
40
|
program.addCommand((0, update_1.createUpdateCommand)());
|
|
43
41
|
program.addCommand((0, upgrade_1.createUpgradeCommand)());
|
|
44
42
|
// Local file commands (existing)
|
|
45
|
-
program.addCommand((0, add_1.createAddCommand)());
|
|
46
43
|
program.addCommand((0, list_1.createListCommand)());
|
|
47
|
-
program.addCommand((0,
|
|
44
|
+
program.addCommand((0, uninstall_1.createUninstallCommand)());
|
|
48
45
|
program.addCommand((0, index_1.createIndexCommand)());
|
|
49
46
|
program.addCommand((0, telemetry_1.createTelemetryCommand)());
|
|
47
|
+
// Utility commands
|
|
48
|
+
program.addCommand((0, schema_1.createSchemaCommand)());
|
|
50
49
|
// Parse command line arguments
|
|
51
50
|
program.parse();
|
|
52
51
|
// Cleanup telemetry on exit
|