prpm 1.2.1 → 2.0.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/README.md +90 -862
- package/dist/index.js +23233 -65
- package/dist/schemas/agents-md.schema.json +24 -0
- package/dist/schemas/aider.schema.json +24 -0
- package/dist/schemas/canonical.schema.json +435 -0
- package/dist/schemas/claude-agent.schema.json +62 -0
- package/dist/schemas/claude-hook.schema.json +70 -0
- package/dist/schemas/claude-plugin.schema.json +122 -0
- package/dist/schemas/claude-skill.schema.json +51 -0
- package/dist/schemas/claude-slash-command.schema.json +77 -0
- package/dist/schemas/claude.schema.json +52 -0
- package/dist/schemas/continue.schema.json +98 -0
- package/dist/schemas/copilot.schema.json +76 -0
- package/dist/schemas/cursor-command.schema.json +27 -0
- package/dist/schemas/cursor-hooks.schema.json +59 -0
- package/dist/schemas/cursor.schema.json +74 -0
- package/dist/schemas/droid-hook.schema.json +103 -0
- package/dist/schemas/droid-skill.schema.json +46 -0
- package/dist/schemas/droid-slash-command.schema.json +53 -0
- package/dist/schemas/droid.schema.json +46 -0
- package/dist/schemas/format-registry.schema.json +99 -0
- package/dist/schemas/gemini-md.schema.json +24 -0
- package/dist/schemas/gemini.schema.json +30 -0
- package/dist/schemas/kiro-agent.schema.json +146 -0
- package/dist/schemas/kiro-hook.schema.json +120 -0
- package/dist/schemas/kiro-steering.schema.json +74 -0
- package/dist/schemas/mcp-server.schema.json +130 -0
- package/dist/schemas/opencode-slash-command.schema.json +60 -0
- package/dist/schemas/opencode.schema.json +111 -0
- package/dist/schemas/prpm-manifest.schema.json +733 -0
- package/dist/schemas/replit.schema.json +21 -0
- package/dist/schemas/ruler.schema.json +22 -0
- package/dist/schemas/trae.schema.json +24 -0
- package/dist/schemas/windsurf.schema.json +22 -0
- package/dist/schemas/zencoder.schema.json +51 -0
- package/package.json +20 -14
- package/schemas/prpm-manifest.schema.json +465 -39
- package/dist/__tests__/e2e/test-helpers.js +0 -150
- package/dist/commands/collections.js +0 -606
- package/dist/commands/index.js +0 -186
- package/dist/commands/info.js +0 -82
- package/dist/commands/install.js +0 -477
- package/dist/commands/list.js +0 -166
- package/dist/commands/login.js +0 -281
- package/dist/commands/outdated.js +0 -128
- package/dist/commands/popular.js +0 -27
- package/dist/commands/publish.js +0 -274
- package/dist/commands/schema.js +0 -37
- package/dist/commands/search.js +0 -404
- package/dist/commands/telemetry.js +0 -103
- package/dist/commands/trending.js +0 -76
- package/dist/commands/uninstall.js +0 -77
- package/dist/commands/update.js +0 -121
- package/dist/commands/upgrade.js +0 -121
- package/dist/commands/whoami.js +0 -75
- package/dist/core/claude-config.js +0 -91
- package/dist/core/cursor-config.js +0 -130
- package/dist/core/downloader.js +0 -64
- package/dist/core/filesystem.js +0 -124
- package/dist/core/lockfile.js +0 -239
- package/dist/core/marketplace-converter.js +0 -198
- package/dist/core/registry-client.js +0 -265
- package/dist/core/schema-validator.js +0 -74
- package/dist/core/telemetry.js +0 -175
- package/dist/core/user-config.js +0 -79
- package/dist/types/registry.js +0 -5
- package/dist/types.js +0 -5
package/dist/core/filesystem.js
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* File system operations for managing prompt files
|
|
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.getDestinationDir = getDestinationDir;
|
|
10
|
-
exports.ensureDirectoryExists = ensureDirectoryExists;
|
|
11
|
-
exports.saveFile = saveFile;
|
|
12
|
-
exports.deleteFile = deleteFile;
|
|
13
|
-
exports.fileExists = fileExists;
|
|
14
|
-
exports.generateId = generateId;
|
|
15
|
-
exports.stripAuthorNamespace = stripAuthorNamespace;
|
|
16
|
-
const fs_1 = require("fs");
|
|
17
|
-
const path_1 = __importDefault(require("path"));
|
|
18
|
-
/**
|
|
19
|
-
* Get the destination directory for a package type
|
|
20
|
-
*/
|
|
21
|
-
function getDestinationDir(type) {
|
|
22
|
-
switch (type) {
|
|
23
|
-
case 'cursor':
|
|
24
|
-
return '.cursor/rules';
|
|
25
|
-
case 'cursor-agent':
|
|
26
|
-
return '.cursor/agents';
|
|
27
|
-
case 'cursor-slash-command':
|
|
28
|
-
return '.cursor/commands';
|
|
29
|
-
case 'claude':
|
|
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';
|
|
43
|
-
default:
|
|
44
|
-
throw new Error(`Unknown package type: ${type}`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Ensure directory exists, creating it if necessary
|
|
49
|
-
*/
|
|
50
|
-
async function ensureDirectoryExists(dirPath) {
|
|
51
|
-
try {
|
|
52
|
-
await fs_1.promises.mkdir(dirPath, { recursive: true });
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
throw new Error(`Failed to create directory ${dirPath}: ${error}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Save content to a file
|
|
60
|
-
*/
|
|
61
|
-
async function saveFile(filePath, content) {
|
|
62
|
-
try {
|
|
63
|
-
// Ensure parent directory exists
|
|
64
|
-
const dir = path_1.default.dirname(filePath);
|
|
65
|
-
await ensureDirectoryExists(dir);
|
|
66
|
-
// Write file
|
|
67
|
-
await fs_1.promises.writeFile(filePath, content, 'utf-8');
|
|
68
|
-
}
|
|
69
|
-
catch (error) {
|
|
70
|
-
throw new Error(`Failed to save file ${filePath}: ${error}`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Delete a file
|
|
75
|
-
*/
|
|
76
|
-
async function deleteFile(filePath) {
|
|
77
|
-
try {
|
|
78
|
-
await fs_1.promises.unlink(filePath);
|
|
79
|
-
}
|
|
80
|
-
catch (error) {
|
|
81
|
-
const err = error;
|
|
82
|
-
if (err.code === 'ENOENT') {
|
|
83
|
-
// File doesn't exist, that's fine
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
throw new Error(`Failed to delete file ${filePath}: ${error}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Check if a file exists
|
|
91
|
-
*/
|
|
92
|
-
async function fileExists(filePath) {
|
|
93
|
-
try {
|
|
94
|
-
await fs_1.promises.access(filePath);
|
|
95
|
-
return true;
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Generate a unique ID from filename
|
|
103
|
-
*/
|
|
104
|
-
function generateId(filename) {
|
|
105
|
-
// Remove extension and convert to kebab-case
|
|
106
|
-
const name = filename.replace(/\.[^/.]+$/, '');
|
|
107
|
-
return name
|
|
108
|
-
.toLowerCase()
|
|
109
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
110
|
-
.replace(/^-+|-+$/g, '');
|
|
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
DELETED
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Lock file management for reproducible installations
|
|
4
|
-
* prpm.lock format similar to package-lock.json
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.readLockfile = readLockfile;
|
|
8
|
-
exports.writeLockfile = writeLockfile;
|
|
9
|
-
exports.createLockfile = createLockfile;
|
|
10
|
-
exports.addToLockfile = addToLockfile;
|
|
11
|
-
exports.setPackageIntegrity = setPackageIntegrity;
|
|
12
|
-
exports.verifyPackageIntegrity = verifyPackageIntegrity;
|
|
13
|
-
exports.getLockedVersion = getLockedVersion;
|
|
14
|
-
exports.isLockfileOutOfSync = isLockfileOutOfSync;
|
|
15
|
-
exports.mergeLockfiles = mergeLockfiles;
|
|
16
|
-
exports.pruneLockfile = pruneLockfile;
|
|
17
|
-
exports.addPackage = addPackage;
|
|
18
|
-
exports.removePackage = removePackage;
|
|
19
|
-
exports.listPackages = listPackages;
|
|
20
|
-
exports.getPackage = getPackage;
|
|
21
|
-
const fs_1 = require("fs");
|
|
22
|
-
const path_1 = require("path");
|
|
23
|
-
const crypto_1 = require("crypto");
|
|
24
|
-
const LOCKFILE_NAME = 'prpm.lock';
|
|
25
|
-
const LOCKFILE_VERSION = 1;
|
|
26
|
-
/**
|
|
27
|
-
* Read lock file from current directory
|
|
28
|
-
*/
|
|
29
|
-
async function readLockfile(cwd = process.cwd()) {
|
|
30
|
-
try {
|
|
31
|
-
const lockfilePath = (0, path_1.join)(cwd, LOCKFILE_NAME);
|
|
32
|
-
const content = await fs_1.promises.readFile(lockfilePath, 'utf-8');
|
|
33
|
-
return JSON.parse(content);
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
if (error.code === 'ENOENT') {
|
|
37
|
-
return null; // Lock file doesn't exist
|
|
38
|
-
}
|
|
39
|
-
throw new Error(`Failed to read lock file: ${error}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Write lock file to current directory
|
|
44
|
-
*/
|
|
45
|
-
async function writeLockfile(lockfile, cwd = process.cwd()) {
|
|
46
|
-
try {
|
|
47
|
-
const lockfilePath = (0, path_1.join)(cwd, LOCKFILE_NAME);
|
|
48
|
-
const content = JSON.stringify(lockfile, null, 2);
|
|
49
|
-
await fs_1.promises.writeFile(lockfilePath, content, 'utf-8');
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
throw new Error(`Failed to write lock file: ${error}`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Create new lock file
|
|
57
|
-
*/
|
|
58
|
-
function createLockfile() {
|
|
59
|
-
return {
|
|
60
|
-
version: '1.0.0',
|
|
61
|
-
lockfileVersion: LOCKFILE_VERSION,
|
|
62
|
-
packages: {},
|
|
63
|
-
generated: new Date().toISOString(),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Add package to lock file
|
|
68
|
-
*/
|
|
69
|
-
function addToLockfile(lockfile, packageId, packageInfo) {
|
|
70
|
-
lockfile.packages[packageId] = {
|
|
71
|
-
version: packageInfo.version,
|
|
72
|
-
resolved: packageInfo.tarballUrl,
|
|
73
|
-
integrity: '', // Will be set after download
|
|
74
|
-
dependencies: packageInfo.dependencies,
|
|
75
|
-
type: packageInfo.type,
|
|
76
|
-
format: packageInfo.format,
|
|
77
|
-
installedPath: packageInfo.installedPath,
|
|
78
|
-
};
|
|
79
|
-
lockfile.generated = new Date().toISOString();
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Update package integrity hash after download
|
|
83
|
-
*/
|
|
84
|
-
function setPackageIntegrity(lockfile, packageId, tarballBuffer) {
|
|
85
|
-
if (!lockfile.packages[packageId]) {
|
|
86
|
-
throw new Error(`Package ${packageId} not found in lock file`);
|
|
87
|
-
}
|
|
88
|
-
const hash = (0, crypto_1.createHash)('sha256').update(tarballBuffer).digest('hex');
|
|
89
|
-
lockfile.packages[packageId].integrity = `sha256-${hash}`;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Verify package integrity
|
|
93
|
-
*/
|
|
94
|
-
function verifyPackageIntegrity(lockfile, packageId, tarballBuffer) {
|
|
95
|
-
const pkg = lockfile.packages[packageId];
|
|
96
|
-
if (!pkg || !pkg.integrity) {
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
const hash = (0, crypto_1.createHash)('sha256').update(tarballBuffer).digest('hex');
|
|
100
|
-
const expectedHash = pkg.integrity.replace('sha256-', '');
|
|
101
|
-
return hash === expectedHash;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Get locked version for a package
|
|
105
|
-
*/
|
|
106
|
-
function getLockedVersion(lockfile, packageId) {
|
|
107
|
-
if (!lockfile || !lockfile.packages[packageId]) {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
return lockfile.packages[packageId].version;
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Check if lock file is out of sync with dependencies
|
|
114
|
-
*/
|
|
115
|
-
function isLockfileOutOfSync(lockfile, requiredPackages) {
|
|
116
|
-
if (!lockfile) {
|
|
117
|
-
return true;
|
|
118
|
-
}
|
|
119
|
-
// Check if all required packages are in lock file
|
|
120
|
-
for (const [pkgId, version] of Object.entries(requiredPackages)) {
|
|
121
|
-
const locked = lockfile.packages[pkgId];
|
|
122
|
-
if (!locked || locked.version !== version) {
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Merge lock files (for conflict resolution)
|
|
130
|
-
*/
|
|
131
|
-
function mergeLockfiles(base, incoming) {
|
|
132
|
-
const merged = createLockfile();
|
|
133
|
-
// Merge packages from both lock files
|
|
134
|
-
const allPackages = new Set([
|
|
135
|
-
...Object.keys(base.packages),
|
|
136
|
-
...Object.keys(incoming.packages),
|
|
137
|
-
]);
|
|
138
|
-
for (const pkgId of allPackages) {
|
|
139
|
-
const basePkg = base.packages[pkgId];
|
|
140
|
-
const incomingPkg = incoming.packages[pkgId];
|
|
141
|
-
if (!basePkg) {
|
|
142
|
-
merged.packages[pkgId] = incomingPkg;
|
|
143
|
-
}
|
|
144
|
-
else if (!incomingPkg) {
|
|
145
|
-
merged.packages[pkgId] = basePkg;
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
// Both exist - prefer newer version
|
|
149
|
-
const baseVersion = basePkg.version;
|
|
150
|
-
const incomingVersion = incomingPkg.version;
|
|
151
|
-
merged.packages[pkgId] = compareVersions(baseVersion, incomingVersion) >= 0
|
|
152
|
-
? basePkg
|
|
153
|
-
: incomingPkg;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return merged;
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Simple semver comparison (returns 1 if a > b, -1 if a < b, 0 if equal)
|
|
160
|
-
*/
|
|
161
|
-
function compareVersions(a, b) {
|
|
162
|
-
const aParts = a.split('.').map(Number);
|
|
163
|
-
const bParts = b.split('.').map(Number);
|
|
164
|
-
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
165
|
-
const aVal = aParts[i] || 0;
|
|
166
|
-
const bVal = bParts[i] || 0;
|
|
167
|
-
if (aVal > bVal)
|
|
168
|
-
return 1;
|
|
169
|
-
if (aVal < bVal)
|
|
170
|
-
return -1;
|
|
171
|
-
}
|
|
172
|
-
return 0;
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Prune unused packages from lock file
|
|
176
|
-
*/
|
|
177
|
-
function pruneLockfile(lockfile, requiredPackages) {
|
|
178
|
-
const pruned = { ...lockfile };
|
|
179
|
-
pruned.packages = {};
|
|
180
|
-
for (const pkgId of requiredPackages) {
|
|
181
|
-
if (lockfile.packages[pkgId]) {
|
|
182
|
-
pruned.packages[pkgId] = lockfile.packages[pkgId];
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
pruned.generated = new Date().toISOString();
|
|
186
|
-
return pruned;
|
|
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
|
-
}
|
|
@@ -1,198 +0,0 @@
|
|
|
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
|
-
}
|