prpm 0.0.10 → 0.0.12
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/__tests__/e2e/test-helpers.js +1 -1
- package/dist/commands/init.js +17 -40
- package/dist/commands/install.js +30 -11
- package/dist/commands/list.js +14 -5
- package/dist/commands/login.js +15 -4
- package/dist/commands/publish.js +312 -58
- package/dist/commands/search.js +3 -7
- package/dist/commands/uninstall.js +59 -21
- package/dist/core/filesystem.js +13 -1
- package/dist/core/marketplace-converter.js +28 -7
- package/dist/core/registry-client.js +31 -4
- package/dist/core/user-config.js +88 -22
- 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 +88 -0
|
@@ -77,7 +77,7 @@ async function createMockCollection(testDir, id, packages) {
|
|
|
77
77
|
async function createMockConfig(configPath, options) {
|
|
78
78
|
const config = {
|
|
79
79
|
token: options.token || 'test-token-123',
|
|
80
|
-
registryUrl: options.registryUrl || 'http://localhost:
|
|
80
|
+
registryUrl: options.registryUrl || 'http://localhost:3111',
|
|
81
81
|
};
|
|
82
82
|
await (0, promises_1.mkdir)((0, path_1.join)(configPath, '..'), { recursive: true });
|
|
83
83
|
await (0, promises_1.writeFile)(configPath, JSON.stringify(config, null, 2));
|
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
|
@@ -40,6 +40,13 @@ function getDestinationDir(type) {
|
|
|
40
40
|
return '.prompts';
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Strip author namespace from package ID
|
|
45
|
+
*/
|
|
46
|
+
function stripAuthorNamespace(packageId) {
|
|
47
|
+
const parts = packageId.split('/');
|
|
48
|
+
return parts[parts.length - 1];
|
|
49
|
+
}
|
|
43
50
|
/**
|
|
44
51
|
* Find the actual file location for a package
|
|
45
52
|
*/
|
|
@@ -47,11 +54,13 @@ async function findPackageLocation(id, format, subtype) {
|
|
|
47
54
|
if (!format)
|
|
48
55
|
return null;
|
|
49
56
|
const baseDir = getDestinationDir(format);
|
|
57
|
+
// Strip author namespace to get actual package name used in file system
|
|
58
|
+
const packageName = stripAuthorNamespace(id);
|
|
50
59
|
// Try different file extensions based on format
|
|
51
60
|
const extensions = format === 'cursor' ? ['.mdc', '.md'] : ['.md'];
|
|
52
|
-
// Try direct file: <dir>/<
|
|
61
|
+
// Try direct file: <dir>/<packageName>.ext
|
|
53
62
|
for (const ext of extensions) {
|
|
54
|
-
const directPath = path_1.default.join(baseDir, `${
|
|
63
|
+
const directPath = path_1.default.join(baseDir, `${packageName}${ext}`);
|
|
55
64
|
try {
|
|
56
65
|
await fs_1.promises.access(directPath);
|
|
57
66
|
return directPath;
|
|
@@ -60,9 +69,9 @@ async function findPackageLocation(id, format, subtype) {
|
|
|
60
69
|
// File doesn't exist, continue
|
|
61
70
|
}
|
|
62
71
|
}
|
|
63
|
-
// Try subdirectory: <dir>/<
|
|
72
|
+
// Try subdirectory: <dir>/<packageName>/SKILL.md or <dir>/<packageName>/AGENT.md
|
|
64
73
|
if (subtype === 'skill') {
|
|
65
|
-
const skillPath = path_1.default.join(baseDir,
|
|
74
|
+
const skillPath = path_1.default.join(baseDir, packageName, 'SKILL.md');
|
|
66
75
|
try {
|
|
67
76
|
await fs_1.promises.access(skillPath);
|
|
68
77
|
return skillPath;
|
|
@@ -72,7 +81,7 @@ async function findPackageLocation(id, format, subtype) {
|
|
|
72
81
|
}
|
|
73
82
|
}
|
|
74
83
|
if (subtype === 'agent' || format === 'claude') {
|
|
75
|
-
const agentPath = path_1.default.join(baseDir,
|
|
84
|
+
const agentPath = path_1.default.join(baseDir, packageName, 'AGENT.md');
|
|
76
85
|
try {
|
|
77
86
|
await fs_1.promises.access(agentPath);
|
|
78
87
|
return agentPath;
|
package/dist/commands/login.js
CHANGED
|
@@ -140,10 +140,21 @@ async function loginWithOAuth(registryUrl) {
|
|
|
140
140
|
}
|
|
141
141
|
// Create the CLI auth URL with session token, callback, and userId
|
|
142
142
|
const callbackUrl = 'http://localhost:8765/callback';
|
|
143
|
-
// Determine webapp URL
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
:
|
|
143
|
+
// Determine webapp URL based on registry URL
|
|
144
|
+
let webappUrl;
|
|
145
|
+
if (registryUrl.includes('localhost') || registryUrl.includes('127.0.0.1')) {
|
|
146
|
+
// Local development: registry on port 3111, webapp on port 5173
|
|
147
|
+
webappUrl = registryUrl.replace(':3111', ':5173');
|
|
148
|
+
}
|
|
149
|
+
else if (registryUrl.includes('registry.prpm.dev')) {
|
|
150
|
+
// Production: always use prpm.dev webapp
|
|
151
|
+
webappUrl = 'https://prpm.dev';
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// Custom registry: assume webapp is on same host without port
|
|
155
|
+
const url = new URL(registryUrl);
|
|
156
|
+
webappUrl = `${url.protocol}//${url.hostname}`;
|
|
157
|
+
}
|
|
147
158
|
const authUrl = `${webappUrl}/cli-auth?sessionToken=${encodeURIComponent(connectSessionToken)}&cliCallback=${encodeURIComponent(callbackUrl)}&userId=${encodeURIComponent(userId)}`;
|
|
148
159
|
console.log(` Please open this link in your browser to authenticate:`);
|
|
149
160
|
console.log(` ${authUrl}\n`);
|