prpm 0.0.9 ā 0.0.11
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/commands/init.js +17 -40
- package/dist/commands/install.js +30 -11
- package/dist/commands/list.js +28 -18
- package/dist/commands/publish.js +211 -52
- package/dist/commands/search.js +3 -7
- package/dist/commands/uninstall.js +59 -21
- package/dist/core/filesystem.js +2 -2
- package/dist/core/marketplace-converter.js +28 -7
- package/dist/core/registry-client.js +31 -4
- package/dist/types/registry.js +7 -0
- package/dist/types.js +30 -0
- package/dist/utils/license-extractor.js +122 -0
- package/dist/utils/multi-package.js +117 -0
- package/dist/utils/parallel-publisher.js +144 -0
- package/dist/utils/snippet-extractor.js +70 -0
- package/package.json +3 -3
- package/schemas/prpm-manifest.schema.json +30 -0
package/dist/commands/init.js
CHANGED
|
@@ -44,29 +44,7 @@ const path_1 = require("path");
|
|
|
44
44
|
const fs_1 = require("fs");
|
|
45
45
|
const readline = __importStar(require("readline/promises"));
|
|
46
46
|
const process_1 = require("process");
|
|
47
|
-
const
|
|
48
|
-
'cursor',
|
|
49
|
-
'claude',
|
|
50
|
-
'continue',
|
|
51
|
-
'windsurf',
|
|
52
|
-
'copilot',
|
|
53
|
-
'kiro',
|
|
54
|
-
'agents.md',
|
|
55
|
-
'generic',
|
|
56
|
-
'mcp',
|
|
57
|
-
];
|
|
58
|
-
const SUBTYPES = [
|
|
59
|
-
'rule',
|
|
60
|
-
'agent',
|
|
61
|
-
'skill',
|
|
62
|
-
'slash-command',
|
|
63
|
-
'prompt',
|
|
64
|
-
'workflow',
|
|
65
|
-
'tool',
|
|
66
|
-
'template',
|
|
67
|
-
'collection',
|
|
68
|
-
'chatmode',
|
|
69
|
-
];
|
|
47
|
+
const types_1 = require("../types");
|
|
70
48
|
const FORMAT_EXAMPLES = {
|
|
71
49
|
cursor: {
|
|
72
50
|
description: 'Cursor AI coding rules',
|
|
@@ -427,7 +405,7 @@ function getDefaultAuthor() {
|
|
|
427
405
|
/**
|
|
428
406
|
* Create example files based on format
|
|
429
407
|
*/
|
|
430
|
-
async function createExampleFiles(format, files) {
|
|
408
|
+
async function createExampleFiles(format, files, packageName) {
|
|
431
409
|
const templates = EXAMPLE_TEMPLATES[format] || {};
|
|
432
410
|
for (const file of files) {
|
|
433
411
|
const filePath = (0, path_1.join)(process.cwd(), file);
|
|
@@ -442,7 +420,9 @@ async function createExampleFiles(format, files) {
|
|
|
442
420
|
continue;
|
|
443
421
|
}
|
|
444
422
|
// Use template or create empty file
|
|
445
|
-
|
|
423
|
+
let content = templates[file] || `# ${file}\n\nAdd your content here.\n`;
|
|
424
|
+
// Replace 'example-skill' placeholder with actual package name in template content
|
|
425
|
+
content = content.replace(/example-skill/g, packageName);
|
|
446
426
|
await (0, promises_1.writeFile)(filePath, content, 'utf-8');
|
|
447
427
|
console.log(` Created ${file}`);
|
|
448
428
|
}
|
|
@@ -505,7 +485,8 @@ async function initPackage(options) {
|
|
|
505
485
|
config.author = getDefaultAuthor() || 'Your Name';
|
|
506
486
|
config.license = 'MIT';
|
|
507
487
|
config.tags = [];
|
|
508
|
-
|
|
488
|
+
// Replace 'example-skill' with actual package name in file paths
|
|
489
|
+
config.files = FORMAT_EXAMPLES.cursor.files.map(f => f.replace(/example-skill/g, config.name || 'example-skill'));
|
|
509
490
|
}
|
|
510
491
|
else {
|
|
511
492
|
// Interactive prompts
|
|
@@ -521,13 +502,9 @@ async function initPackage(options) {
|
|
|
521
502
|
// Description
|
|
522
503
|
config.description = await prompt(rl, 'Description', 'A PRPM package for AI coding assistants');
|
|
523
504
|
// Format
|
|
524
|
-
config.format = await select(rl, 'Select package format:', FORMATS, 'cursor');
|
|
505
|
+
config.format = await select(rl, 'Select package format:', types_1.FORMATS, 'cursor');
|
|
525
506
|
// Subtype
|
|
526
|
-
|
|
527
|
-
const includeSubtype = await prompt(rl, 'Specify subtype? (y/N)', 'n');
|
|
528
|
-
if (includeSubtype.toLowerCase() === 'y') {
|
|
529
|
-
config.subtype = await select(rl, 'Select package subtype:', SUBTYPES, 'rule');
|
|
530
|
-
}
|
|
507
|
+
config.subtype = await select(rl, 'Select package subtype:', types_1.SUBTYPES, 'rule');
|
|
531
508
|
// Author
|
|
532
509
|
const defaultAuthor = getDefaultAuthor();
|
|
533
510
|
config.author = await prompt(rl, 'Author', defaultAuthor || 'Your Name');
|
|
@@ -546,14 +523,16 @@ async function initPackage(options) {
|
|
|
546
523
|
.filter(Boolean);
|
|
547
524
|
// Files - use examples based on format
|
|
548
525
|
const formatExamples = FORMAT_EXAMPLES[config.format];
|
|
526
|
+
// Replace 'example-skill' with actual package name in file paths
|
|
527
|
+
const exampleFiles = formatExamples.files.map(f => f.replace(/example-skill/g, config.name || 'example-skill'));
|
|
549
528
|
console.log(`\nExample files for ${config.format}:`);
|
|
550
|
-
|
|
529
|
+
exampleFiles.forEach((f, idx) => console.log(` ${idx + 1}. ${f}`));
|
|
551
530
|
const useExamples = await prompt(rl, '\nUse example file structure? (Y/n)', 'y');
|
|
552
531
|
if (useExamples.toLowerCase() !== 'n') {
|
|
553
|
-
config.files =
|
|
532
|
+
config.files = exampleFiles;
|
|
554
533
|
}
|
|
555
534
|
else {
|
|
556
|
-
const filesInput = await prompt(rl, 'Files (comma-separated)',
|
|
535
|
+
const filesInput = await prompt(rl, 'Files (comma-separated)', exampleFiles.join(', '));
|
|
557
536
|
config.files = filesInput
|
|
558
537
|
.split(',')
|
|
559
538
|
.map(f => f.trim())
|
|
@@ -617,10 +596,8 @@ async function initPackage(options) {
|
|
|
617
596
|
version: config.version,
|
|
618
597
|
description: config.description,
|
|
619
598
|
format: config.format,
|
|
599
|
+
subtype: config.subtype || 'rule', // Default to 'rule' if not specified
|
|
620
600
|
};
|
|
621
|
-
if (config.subtype) {
|
|
622
|
-
manifest.subtype = config.subtype;
|
|
623
|
-
}
|
|
624
601
|
manifest.author = config.author;
|
|
625
602
|
manifest.license = config.license;
|
|
626
603
|
if (config.repository) {
|
|
@@ -634,9 +611,9 @@ async function initPackage(options) {
|
|
|
634
611
|
await (0, promises_1.writeFile)(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
|
|
635
612
|
console.log('\nā
Created prpm.json\n');
|
|
636
613
|
// Create example files
|
|
637
|
-
if (config.files && config.format) {
|
|
614
|
+
if (config.files && config.format && config.name) {
|
|
638
615
|
console.log('Creating example files...\n');
|
|
639
|
-
await createExampleFiles(config.format, config.files);
|
|
616
|
+
await createExampleFiles(config.format, config.files, config.name);
|
|
640
617
|
// Create README
|
|
641
618
|
await createReadme(config);
|
|
642
619
|
}
|
package/dist/commands/install.js
CHANGED
|
@@ -58,11 +58,9 @@ function getPackageIcon(format, subtype) {
|
|
|
58
58
|
'slash-command': 'ā”',
|
|
59
59
|
'rule': 'š',
|
|
60
60
|
'prompt': 'š¬',
|
|
61
|
-
'workflow': 'ā”',
|
|
62
|
-
'tool': 'š§',
|
|
63
|
-
'template': 'š',
|
|
64
61
|
'collection': 'š¦',
|
|
65
62
|
'chatmode': 'š¬',
|
|
63
|
+
'tool': 'š§',
|
|
66
64
|
};
|
|
67
65
|
// Format-specific icons for rules/defaults
|
|
68
66
|
const formatIcons = {
|
|
@@ -99,11 +97,9 @@ function getPackageLabel(format, subtype) {
|
|
|
99
97
|
'slash-command': 'Slash Command',
|
|
100
98
|
'rule': 'Rule',
|
|
101
99
|
'prompt': 'Prompt',
|
|
102
|
-
'workflow': 'Workflow',
|
|
103
|
-
'tool': 'Tool',
|
|
104
|
-
'template': 'Template',
|
|
105
100
|
'collection': 'Collection',
|
|
106
101
|
'chatmode': 'Chat Mode',
|
|
102
|
+
'tool': 'Tool',
|
|
107
103
|
};
|
|
108
104
|
const formatLabel = formatLabels[format];
|
|
109
105
|
const subtypeLabel = subtypeLabels[subtype];
|
|
@@ -117,6 +113,17 @@ async function handleInstall(packageSpec, options) {
|
|
|
117
113
|
let success = false;
|
|
118
114
|
let error;
|
|
119
115
|
try {
|
|
116
|
+
// Check if this is explicitly a collection install (collections/name)
|
|
117
|
+
if (packageSpec.startsWith('collections/')) {
|
|
118
|
+
const collectionId = packageSpec.replace('collections/', '');
|
|
119
|
+
console.log(`š„ Installing ${collectionId}@latest...`);
|
|
120
|
+
const { handleCollectionInstall } = await Promise.resolve().then(() => __importStar(require('./collections.js')));
|
|
121
|
+
return await handleCollectionInstall(collectionId, {
|
|
122
|
+
format: options.as,
|
|
123
|
+
skipOptional: false,
|
|
124
|
+
dryRun: false,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
120
127
|
// Parse package spec (e.g., "react-rules" or "react-rules@1.2.0" or "@pr-pm/pkg@1.0.0")
|
|
121
128
|
// For scoped packages (@scope/name), the first @ is part of the package name
|
|
122
129
|
let packageId;
|
|
@@ -255,7 +262,7 @@ async function handleInstall(packageSpec, options) {
|
|
|
255
262
|
console.log(` š Extracting...`);
|
|
256
263
|
// Determine effective format and subtype (from conversion or package native format)
|
|
257
264
|
const effectiveFormat = format || pkg.format;
|
|
258
|
-
const effectiveSubtype = pkg.subtype;
|
|
265
|
+
const effectiveSubtype = options.subtype || pkg.subtype;
|
|
259
266
|
// Extract all files from tarball
|
|
260
267
|
const extractedFiles = await extractTarball(tarball, packageId);
|
|
261
268
|
// Track where files were saved for user feedback
|
|
@@ -273,14 +280,21 @@ async function handleInstall(packageSpec, options) {
|
|
|
273
280
|
}
|
|
274
281
|
// Check if this is a multi-file package
|
|
275
282
|
else if (extractedFiles.length === 1) {
|
|
276
|
-
const destDir = (0, filesystem_1.getDestinationDir)(effectiveFormat, effectiveSubtype);
|
|
283
|
+
const destDir = (0, filesystem_1.getDestinationDir)(effectiveFormat, effectiveSubtype, pkg.name);
|
|
277
284
|
// Single file package
|
|
278
285
|
let mainFile = extractedFiles[0].content;
|
|
279
286
|
// Determine file extension based on effective format
|
|
280
287
|
// Cursor rules use .mdc, but slash commands and other files use .md
|
|
281
288
|
const fileExtension = (effectiveFormat === 'cursor' && format === 'cursor') ? 'mdc' : 'md';
|
|
282
289
|
const packageName = (0, filesystem_1.stripAuthorNamespace)(packageId);
|
|
283
|
-
|
|
290
|
+
// For Claude skills, use SKILL.md filename in the package directory
|
|
291
|
+
// For other formats, use package name as filename
|
|
292
|
+
if (effectiveFormat === 'claude' && effectiveSubtype === 'skill') {
|
|
293
|
+
destPath = `${destDir}/SKILL.md`;
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
destPath = `${destDir}/${packageName}.${fileExtension}`;
|
|
297
|
+
}
|
|
284
298
|
// Handle cursor format - add header if missing for .mdc files
|
|
285
299
|
if (format === 'cursor' && effectiveFormat === 'cursor') {
|
|
286
300
|
if (!(0, cursor_config_1.hasMDCHeader)(mainFile)) {
|
|
@@ -304,10 +318,13 @@ async function handleInstall(packageSpec, options) {
|
|
|
304
318
|
fileCount = 1;
|
|
305
319
|
}
|
|
306
320
|
else {
|
|
307
|
-
const destDir = (0, filesystem_1.getDestinationDir)(effectiveFormat, effectiveSubtype);
|
|
321
|
+
const destDir = (0, filesystem_1.getDestinationDir)(effectiveFormat, effectiveSubtype, pkg.name);
|
|
308
322
|
// Multi-file package - create directory for package
|
|
323
|
+
// For Claude skills, destDir already includes package name, so use it directly
|
|
309
324
|
const packageName = (0, filesystem_1.stripAuthorNamespace)(packageId);
|
|
310
|
-
const packageDir =
|
|
325
|
+
const packageDir = (effectiveFormat === 'claude' && effectiveSubtype === 'skill')
|
|
326
|
+
? destDir
|
|
327
|
+
: `${destDir}/${packageName}`;
|
|
311
328
|
destPath = packageDir;
|
|
312
329
|
console.log(` š Multi-file package - creating directory: ${packageDir}`);
|
|
313
330
|
// For Claude skills, auto-fix filename to SKILL.md if needed
|
|
@@ -483,6 +500,7 @@ function createInstallCommand() {
|
|
|
483
500
|
.option('--version <version>', 'Specific version to install')
|
|
484
501
|
.option('--as <format>', 'Convert and install in specific format (cursor, claude, continue, windsurf, canonical)')
|
|
485
502
|
.option('--format <format>', 'Alias for --as')
|
|
503
|
+
.option('--subtype <subtype>', 'Specify subtype when converting (skill, agent, rule, etc.)')
|
|
486
504
|
.option('--frozen-lockfile', 'Fail if lock file needs to be updated (for CI)')
|
|
487
505
|
.action(async (packageSpec, options) => {
|
|
488
506
|
// Support both --as and --format (format is alias for as)
|
|
@@ -498,6 +516,7 @@ function createInstallCommand() {
|
|
|
498
516
|
await handleInstall(packageSpec, {
|
|
499
517
|
version: options.version,
|
|
500
518
|
as: convertTo,
|
|
519
|
+
subtype: options.subtype,
|
|
501
520
|
frozenLockfile: options.frozenLockfile
|
|
502
521
|
});
|
|
503
522
|
process.exit(0);
|
package/dist/commands/list.js
CHANGED
|
@@ -43,21 +43,25 @@ function getDestinationDir(type) {
|
|
|
43
43
|
/**
|
|
44
44
|
* Find the actual file location for a package
|
|
45
45
|
*/
|
|
46
|
-
async function findPackageLocation(id,
|
|
47
|
-
if (!
|
|
46
|
+
async function findPackageLocation(id, format, subtype) {
|
|
47
|
+
if (!format)
|
|
48
48
|
return null;
|
|
49
|
-
const baseDir = getDestinationDir(
|
|
50
|
-
// Try
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
const baseDir = getDestinationDir(format);
|
|
50
|
+
// Try different file extensions based on format
|
|
51
|
+
const extensions = format === 'cursor' ? ['.mdc', '.md'] : ['.md'];
|
|
52
|
+
// Try direct file: <dir>/<id>.ext
|
|
53
|
+
for (const ext of extensions) {
|
|
54
|
+
const directPath = path_1.default.join(baseDir, `${id}${ext}`);
|
|
55
|
+
try {
|
|
56
|
+
await fs_1.promises.access(directPath);
|
|
57
|
+
return directPath;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// File doesn't exist, continue
|
|
61
|
+
}
|
|
58
62
|
}
|
|
59
63
|
// Try subdirectory: <dir>/<id>/SKILL.md or <dir>/<id>/AGENT.md
|
|
60
|
-
if (
|
|
64
|
+
if (subtype === 'skill') {
|
|
61
65
|
const skillPath = path_1.default.join(baseDir, id, 'SKILL.md');
|
|
62
66
|
try {
|
|
63
67
|
await fs_1.promises.access(skillPath);
|
|
@@ -67,7 +71,7 @@ async function findPackageLocation(id, type) {
|
|
|
67
71
|
// Not found
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
|
-
if (
|
|
74
|
+
if (subtype === 'agent' || format === 'claude') {
|
|
71
75
|
const agentPath = path_1.default.join(baseDir, id, 'AGENT.md');
|
|
72
76
|
try {
|
|
73
77
|
await fs_1.promises.access(agentPath);
|
|
@@ -92,13 +96,19 @@ async function displayPackages(packages) {
|
|
|
92
96
|
// Find file locations
|
|
93
97
|
const packagesWithLocations = await Promise.all(packages.map(async (pkg) => ({
|
|
94
98
|
...pkg,
|
|
95
|
-
location: await findPackageLocation(pkg.id,
|
|
99
|
+
location: await findPackageLocation(pkg.id, pkg.format, pkg.subtype)
|
|
96
100
|
})));
|
|
101
|
+
// Helper to format type display
|
|
102
|
+
const formatType = (format, subtype) => {
|
|
103
|
+
if (!format)
|
|
104
|
+
return '';
|
|
105
|
+
return subtype ? `${format}/${subtype}` : format;
|
|
106
|
+
};
|
|
97
107
|
// Calculate column widths
|
|
98
108
|
const idWidth = Math.max(8, ...packagesWithLocations.map(p => p.id.length));
|
|
99
109
|
const versionWidth = Math.max(7, ...packagesWithLocations.map(p => p.version.length));
|
|
100
|
-
const typeWidth = Math.max(6, ...packagesWithLocations.map(p => (
|
|
101
|
-
const locationWidth = Math.max(8, ...packagesWithLocations.map(p => (p.
|
|
110
|
+
const typeWidth = Math.max(6, ...packagesWithLocations.map(p => formatType(p.format, p.subtype).length));
|
|
111
|
+
const locationWidth = Math.max(8, ...packagesWithLocations.map(p => (p.installedPath || 'N/A').length));
|
|
102
112
|
// Header
|
|
103
113
|
const header = [
|
|
104
114
|
'ID'.padEnd(idWidth),
|
|
@@ -113,8 +123,8 @@ async function displayPackages(packages) {
|
|
|
113
123
|
const row = [
|
|
114
124
|
pkg.id.padEnd(idWidth),
|
|
115
125
|
pkg.version.padEnd(versionWidth),
|
|
116
|
-
(
|
|
117
|
-
(pkg.
|
|
126
|
+
formatType(pkg.format, pkg.subtype).padEnd(typeWidth),
|
|
127
|
+
(pkg.installedPath || 'N/A').padEnd(locationWidth)
|
|
118
128
|
].join(' | ');
|
|
119
129
|
console.log(row);
|
|
120
130
|
});
|
package/dist/commands/publish.js
CHANGED
|
@@ -49,20 +49,23 @@ const user_config_1 = require("../core/user-config");
|
|
|
49
49
|
const telemetry_1 = require("../core/telemetry");
|
|
50
50
|
const marketplace_converter_1 = require("../core/marketplace-converter");
|
|
51
51
|
const schema_validator_1 = require("../core/schema-validator");
|
|
52
|
+
const license_extractor_1 = require("../utils/license-extractor");
|
|
53
|
+
const snippet_extractor_1 = require("../utils/snippet-extractor");
|
|
52
54
|
/**
|
|
53
|
-
* Try to find and load
|
|
55
|
+
* Try to find and load manifest files
|
|
54
56
|
* Checks for:
|
|
55
|
-
* 1. prpm.json (native format)
|
|
56
|
-
* 2. .claude/marketplace.json (Claude format)
|
|
57
|
+
* 1. prpm.json (native format) - returns single manifest
|
|
58
|
+
* 2. .claude/marketplace.json (Claude format) - returns all plugins as separate manifests
|
|
59
|
+
* 3. .claude-plugin/marketplace.json (Claude format - alternative location) - returns all plugins
|
|
57
60
|
*/
|
|
58
|
-
async function
|
|
61
|
+
async function findAndLoadManifests() {
|
|
59
62
|
// Try prpm.json first (native format)
|
|
60
63
|
const prpmJsonPath = (0, path_1.join)(process.cwd(), 'prpm.json');
|
|
61
64
|
try {
|
|
62
65
|
const content = await (0, promises_1.readFile)(prpmJsonPath, 'utf-8');
|
|
63
66
|
const manifest = JSON.parse(content);
|
|
64
67
|
const validated = validateManifest(manifest);
|
|
65
|
-
return {
|
|
68
|
+
return { manifests: [validated], source: 'prpm.json' };
|
|
66
69
|
}
|
|
67
70
|
catch (error) {
|
|
68
71
|
// If it's a validation error, throw it immediately (don't try marketplace.json)
|
|
@@ -81,24 +84,52 @@ async function findAndLoadManifest() {
|
|
|
81
84
|
if (!(0, marketplace_converter_1.validateMarketplaceJson)(marketplaceData)) {
|
|
82
85
|
throw new Error('Invalid marketplace.json format');
|
|
83
86
|
}
|
|
84
|
-
// Convert marketplace.json to PRPM manifest
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
// Convert each plugin in marketplace.json to a separate PRPM manifest
|
|
88
|
+
const manifests = [];
|
|
89
|
+
for (let i = 0; i < marketplaceData.plugins.length; i++) {
|
|
90
|
+
const manifest = (0, marketplace_converter_1.marketplaceToManifest)(marketplaceData, i);
|
|
91
|
+
const validated = validateManifest(manifest);
|
|
92
|
+
manifests.push(validated);
|
|
93
|
+
}
|
|
94
|
+
return { manifests, source: '.claude/marketplace.json' };
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
// marketplace.json not found or invalid at .claude path, try .claude-plugin
|
|
98
|
+
}
|
|
99
|
+
// Try .claude-plugin/marketplace.json (alternative Claude format)
|
|
100
|
+
const marketplaceJsonPluginPath = (0, path_1.join)(process.cwd(), '.claude-plugin', 'marketplace.json');
|
|
101
|
+
try {
|
|
102
|
+
const content = await (0, promises_1.readFile)(marketplaceJsonPluginPath, 'utf-8');
|
|
103
|
+
const marketplaceData = JSON.parse(content);
|
|
104
|
+
if (!(0, marketplace_converter_1.validateMarketplaceJson)(marketplaceData)) {
|
|
105
|
+
throw new Error('Invalid marketplace.json format');
|
|
106
|
+
}
|
|
107
|
+
// Convert each plugin in marketplace.json to a separate PRPM manifest
|
|
108
|
+
const manifests = [];
|
|
109
|
+
for (let i = 0; i < marketplaceData.plugins.length; i++) {
|
|
110
|
+
const manifest = (0, marketplace_converter_1.marketplaceToManifest)(marketplaceData, i);
|
|
111
|
+
const validated = validateManifest(manifest);
|
|
112
|
+
manifests.push(validated);
|
|
113
|
+
}
|
|
114
|
+
return { manifests, source: '.claude-plugin/marketplace.json' };
|
|
89
115
|
}
|
|
90
116
|
catch (error) {
|
|
91
117
|
// marketplace.json not found or invalid
|
|
92
118
|
}
|
|
93
|
-
//
|
|
119
|
+
// No manifest file found
|
|
94
120
|
throw new Error('No manifest file found. Expected either:\n' +
|
|
95
121
|
' - prpm.json in the current directory, or\n' +
|
|
96
|
-
' - .claude/marketplace.json (Claude format)'
|
|
122
|
+
' - .claude/marketplace.json (Claude format), or\n' +
|
|
123
|
+
' - .claude-plugin/marketplace.json (Claude format)');
|
|
97
124
|
}
|
|
98
125
|
/**
|
|
99
126
|
* Validate package manifest
|
|
100
127
|
*/
|
|
101
128
|
function validateManifest(manifest) {
|
|
129
|
+
// Set default subtype to 'rule' if not provided
|
|
130
|
+
if (!manifest.subtype) {
|
|
131
|
+
manifest.subtype = 'rule';
|
|
132
|
+
}
|
|
102
133
|
// First, validate against JSON schema
|
|
103
134
|
const schemaValidation = (0, schema_validator_1.validateManifestSchema)(manifest);
|
|
104
135
|
if (!schemaValidation.valid) {
|
|
@@ -253,50 +284,178 @@ async function handlePublish(options) {
|
|
|
253
284
|
process.exit(1);
|
|
254
285
|
}
|
|
255
286
|
console.log('š¦ Publishing package...\n');
|
|
256
|
-
// Read and validate
|
|
257
|
-
console.log('š Validating package manifest...');
|
|
258
|
-
const {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
console.log(` Package: ${manifest.name}@${manifest.version}`);
|
|
263
|
-
console.log(` Format: ${manifest.format} | Subtype: ${manifest.subtype || 'rule (default)'}`);
|
|
264
|
-
console.log(` Description: ${manifest.description}`);
|
|
265
|
-
console.log('');
|
|
266
|
-
// Create tarball
|
|
267
|
-
console.log('š¦ Creating package tarball...');
|
|
268
|
-
const tarball = await createTarball(manifest);
|
|
269
|
-
// Display size in KB or MB depending on size
|
|
270
|
-
const sizeInBytes = tarball.length;
|
|
271
|
-
const sizeInKB = sizeInBytes / 1024;
|
|
272
|
-
const sizeInMB = sizeInBytes / (1024 * 1024);
|
|
273
|
-
let sizeDisplay;
|
|
274
|
-
if (sizeInMB >= 1) {
|
|
275
|
-
sizeDisplay = `${sizeInMB.toFixed(2)}MB`;
|
|
287
|
+
// Read and validate manifests
|
|
288
|
+
console.log('š Validating package manifest(s)...');
|
|
289
|
+
const { manifests, source } = await findAndLoadManifests();
|
|
290
|
+
if (manifests.length > 1) {
|
|
291
|
+
console.log(` Found ${manifests.length} plugins in ${source}`);
|
|
292
|
+
console.log(' Will publish each plugin separately\n');
|
|
276
293
|
}
|
|
277
|
-
|
|
278
|
-
|
|
294
|
+
// Get user info to check for organizations (once for all packages)
|
|
295
|
+
console.log('š Checking authentication...');
|
|
296
|
+
const client = (0, registry_client_1.getRegistryClient)(config);
|
|
297
|
+
let userInfo;
|
|
298
|
+
try {
|
|
299
|
+
userInfo = await client.whoami();
|
|
279
300
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (options.dryRun) {
|
|
283
|
-
console.log('ā
Dry run successful! Package is ready to publish.');
|
|
284
|
-
console.log(' Run without --dry-run to publish.');
|
|
285
|
-
success = true;
|
|
286
|
-
return;
|
|
301
|
+
catch (err) {
|
|
302
|
+
console.log(' Could not fetch user organizations, publishing as personal packages');
|
|
287
303
|
}
|
|
288
|
-
// Publish to registry
|
|
289
|
-
console.log('š Publishing to registry...');
|
|
290
|
-
const client = (0, registry_client_1.getRegistryClient)(config);
|
|
291
|
-
const result = await client.publish(manifest, tarball);
|
|
292
|
-
console.log('');
|
|
293
|
-
console.log('ā
Package published successfully!');
|
|
294
304
|
console.log('');
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
305
|
+
// Track published packages
|
|
306
|
+
const publishedPackages = [];
|
|
307
|
+
const failedPackages = [];
|
|
308
|
+
// Publish each manifest
|
|
309
|
+
for (let i = 0; i < manifests.length; i++) {
|
|
310
|
+
const manifest = manifests[i];
|
|
311
|
+
packageName = manifest.name;
|
|
312
|
+
version = manifest.version;
|
|
313
|
+
if (manifests.length > 1) {
|
|
314
|
+
console.log(`\n${'='.repeat(60)}`);
|
|
315
|
+
console.log(`š¦ Publishing plugin ${i + 1} of ${manifests.length}`);
|
|
316
|
+
console.log(`${'='.repeat(60)}\n`);
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
let selectedOrgId;
|
|
320
|
+
// Check if organization is specified in manifest
|
|
321
|
+
if (manifest.organization && userInfo) {
|
|
322
|
+
const orgFromManifest = userInfo.organizations?.find((org) => org.name === manifest.organization || org.id === manifest.organization);
|
|
323
|
+
if (!orgFromManifest) {
|
|
324
|
+
throw new Error(`Organization "${manifest.organization}" not found or you are not a member`);
|
|
325
|
+
}
|
|
326
|
+
// Check if user has publishing rights
|
|
327
|
+
if (!['owner', 'admin', 'maintainer'].includes(orgFromManifest.role)) {
|
|
328
|
+
throw new Error(`You do not have permission to publish to organization "${orgFromManifest.name}". ` +
|
|
329
|
+
`Your role: ${orgFromManifest.role}. Required: owner, admin, or maintainer`);
|
|
330
|
+
}
|
|
331
|
+
selectedOrgId = orgFromManifest.id;
|
|
332
|
+
}
|
|
333
|
+
console.log(` Source: ${source}`);
|
|
334
|
+
console.log(` Package: ${manifest.name}@${manifest.version}`);
|
|
335
|
+
console.log(` Format: ${manifest.format} | Subtype: ${manifest.subtype}`);
|
|
336
|
+
console.log(` Description: ${manifest.description}`);
|
|
337
|
+
if (selectedOrgId && userInfo) {
|
|
338
|
+
const selectedOrg = userInfo.organizations.find((org) => org.id === selectedOrgId);
|
|
339
|
+
console.log(` Publishing to: ${selectedOrg?.name || 'organization'}`);
|
|
340
|
+
}
|
|
341
|
+
console.log('');
|
|
342
|
+
// Extract license information
|
|
343
|
+
console.log('š Extracting license information...');
|
|
344
|
+
const licenseInfo = await (0, license_extractor_1.extractLicenseInfo)(manifest.repository);
|
|
345
|
+
// Update manifest with license information from LICENSE file if found
|
|
346
|
+
// Only set fields that aren't already manually specified in prpm.json
|
|
347
|
+
if (licenseInfo.type && !manifest.license) {
|
|
348
|
+
manifest.license = licenseInfo.type;
|
|
349
|
+
}
|
|
350
|
+
if (licenseInfo.text && !manifest.license_text) {
|
|
351
|
+
manifest.license_text = licenseInfo.text;
|
|
352
|
+
}
|
|
353
|
+
if (licenseInfo.url && !manifest.license_url) {
|
|
354
|
+
manifest.license_url = licenseInfo.url || undefined;
|
|
355
|
+
}
|
|
356
|
+
// Validate and warn about license (optional - will extract if present)
|
|
357
|
+
(0, license_extractor_1.validateLicenseInfo)(licenseInfo, manifest.name);
|
|
358
|
+
console.log('');
|
|
359
|
+
// Extract content snippet
|
|
360
|
+
console.log('š Extracting content snippet...');
|
|
361
|
+
const snippet = await (0, snippet_extractor_1.extractSnippet)(manifest);
|
|
362
|
+
if (snippet) {
|
|
363
|
+
manifest.snippet = snippet;
|
|
364
|
+
}
|
|
365
|
+
(0, snippet_extractor_1.validateSnippet)(snippet, manifest.name);
|
|
366
|
+
console.log('');
|
|
367
|
+
// Create tarball
|
|
368
|
+
console.log('š¦ Creating package tarball...');
|
|
369
|
+
const tarball = await createTarball(manifest);
|
|
370
|
+
// Display size in KB or MB depending on size
|
|
371
|
+
const sizeInBytes = tarball.length;
|
|
372
|
+
const sizeInKB = sizeInBytes / 1024;
|
|
373
|
+
const sizeInMB = sizeInBytes / (1024 * 1024);
|
|
374
|
+
let sizeDisplay;
|
|
375
|
+
if (sizeInMB >= 1) {
|
|
376
|
+
sizeDisplay = `${sizeInMB.toFixed(2)}MB`;
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
sizeDisplay = `${sizeInKB.toFixed(2)}KB`;
|
|
380
|
+
}
|
|
381
|
+
console.log(` Size: ${sizeDisplay}`);
|
|
382
|
+
console.log('');
|
|
383
|
+
if (options.dryRun) {
|
|
384
|
+
console.log('ā
Dry run successful! Package is ready to publish.');
|
|
385
|
+
publishedPackages.push({
|
|
386
|
+
name: manifest.name,
|
|
387
|
+
version: manifest.version,
|
|
388
|
+
url: ''
|
|
389
|
+
});
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
// Publish to registry
|
|
393
|
+
console.log('š Publishing to registry...');
|
|
394
|
+
const result = await client.publish(manifest, tarball, selectedOrgId ? { orgId: selectedOrgId } : undefined);
|
|
395
|
+
// Determine the webapp URL based on registry URL
|
|
396
|
+
let webappUrl;
|
|
397
|
+
const registryUrl = config.registryUrl || 'https://registry.prpm.dev';
|
|
398
|
+
if (registryUrl.includes('localhost') || registryUrl.includes('127.0.0.1')) {
|
|
399
|
+
// Local development - webapp is on port 5173
|
|
400
|
+
webappUrl = 'http://localhost:5173';
|
|
401
|
+
}
|
|
402
|
+
else if (registryUrl.includes('registry.prpm.dev')) {
|
|
403
|
+
// Production - webapp is on prpm.dev
|
|
404
|
+
webappUrl = 'https://prpm.dev';
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
// Default to registry URL for unknown environments
|
|
408
|
+
webappUrl = registryUrl;
|
|
409
|
+
}
|
|
410
|
+
const packageUrl = `${webappUrl}/packages/${encodeURIComponent(manifest.name)}`;
|
|
411
|
+
console.log('');
|
|
412
|
+
console.log('ā
Package published successfully!');
|
|
413
|
+
console.log('');
|
|
414
|
+
console.log(` Package: ${manifest.name}@${result.version}`);
|
|
415
|
+
console.log(` Install: prpm install ${manifest.name}`);
|
|
416
|
+
console.log('');
|
|
417
|
+
publishedPackages.push({
|
|
418
|
+
name: manifest.name,
|
|
419
|
+
version: result.version,
|
|
420
|
+
url: packageUrl
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
catch (err) {
|
|
424
|
+
const pkgError = err instanceof Error ? err.message : String(err);
|
|
425
|
+
console.error(`\nā Failed to publish ${manifest.name}: ${pkgError}\n`);
|
|
426
|
+
failedPackages.push({
|
|
427
|
+
name: manifest.name,
|
|
428
|
+
error: pkgError
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// Print summary if multiple packages
|
|
433
|
+
if (manifests.length > 1) {
|
|
434
|
+
console.log(`\n${'='.repeat(60)}`);
|
|
435
|
+
console.log(`š Publishing Summary`);
|
|
436
|
+
console.log(`${'='.repeat(60)}\n`);
|
|
437
|
+
if (publishedPackages.length > 0) {
|
|
438
|
+
console.log(`ā
Successfully published ${publishedPackages.length} package(s):`);
|
|
439
|
+
publishedPackages.forEach(pkg => {
|
|
440
|
+
console.log(` - ${pkg.name}@${pkg.version}`);
|
|
441
|
+
if (pkg.url) {
|
|
442
|
+
console.log(` ${pkg.url}`);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
console.log('');
|
|
446
|
+
}
|
|
447
|
+
if (failedPackages.length > 0) {
|
|
448
|
+
console.log(`ā Failed to publish ${failedPackages.length} package(s):`);
|
|
449
|
+
failedPackages.forEach(pkg => {
|
|
450
|
+
console.log(` - ${pkg.name}: ${pkg.error}`);
|
|
451
|
+
});
|
|
452
|
+
console.log('');
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
success = publishedPackages.length > 0;
|
|
456
|
+
if (failedPackages.length > 0 && publishedPackages.length === 0) {
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
300
459
|
}
|
|
301
460
|
catch (err) {
|
|
302
461
|
error = err instanceof Error ? err.message : String(err);
|
package/dist/commands/search.js
CHANGED
|
@@ -54,11 +54,9 @@ function getPackageIcon(format, subtype) {
|
|
|
54
54
|
'slash-command': 'ā”',
|
|
55
55
|
'rule': 'š',
|
|
56
56
|
'prompt': 'š¬',
|
|
57
|
-
'workflow': 'ā”',
|
|
58
|
-
'tool': 'š§',
|
|
59
|
-
'template': 'š',
|
|
60
57
|
'collection': 'š¦',
|
|
61
58
|
'chatmode': 'š¬',
|
|
59
|
+
'tool': 'š§',
|
|
62
60
|
};
|
|
63
61
|
// Format-specific icons for rules/defaults
|
|
64
62
|
const formatIcons = {
|
|
@@ -95,11 +93,9 @@ function getPackageLabel(format, subtype) {
|
|
|
95
93
|
'slash-command': 'Slash Command',
|
|
96
94
|
'rule': 'Rule',
|
|
97
95
|
'prompt': 'Prompt',
|
|
98
|
-
'workflow': 'Workflow',
|
|
99
|
-
'tool': 'Tool',
|
|
100
|
-
'template': 'Template',
|
|
101
96
|
'collection': 'Collection',
|
|
102
97
|
'chatmode': 'Chat Mode',
|
|
98
|
+
'tool': 'Tool',
|
|
103
99
|
};
|
|
104
100
|
const formatLabel = formatLabels[format];
|
|
105
101
|
const subtypeLabel = subtypeLabels[subtype];
|
|
@@ -409,7 +405,7 @@ function createSearchCommand() {
|
|
|
409
405
|
const limit = options.limit ? parseInt(options.limit, 10) : 20;
|
|
410
406
|
const page = options.page ? parseInt(options.page, 10) : 1;
|
|
411
407
|
const validFormats = ['cursor', 'claude', 'continue', 'windsurf', 'copilot', 'kiro', 'generic', 'mcp'];
|
|
412
|
-
const validSubtypes = ['rule', 'agent', 'skill', 'slash-command', 'prompt', '
|
|
408
|
+
const validSubtypes = ['rule', 'agent', 'skill', 'slash-command', 'prompt', 'collection', 'chatmode'];
|
|
413
409
|
if (options.format && !validFormats.includes(format)) {
|
|
414
410
|
console.error(`ā Format must be one of: ${validFormats.join(', ')}`);
|
|
415
411
|
process.exit(1);
|