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.
@@ -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:3000',
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));
@@ -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 FORMATS = [
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
- const content = templates[file] || `# ${file}\n\nAdd your content here.\n`;
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
- config.files = FORMAT_EXAMPLES.cursor.files;
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
- console.log('\nAvailable subtypes: rule, agent, skill, slash-command, prompt, workflow, tool, template, collection, chatmode');
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
- formatExamples.files.forEach((f, idx) => console.log(` ${idx + 1}. ${f}`));
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 = formatExamples.files;
532
+ config.files = exampleFiles;
554
533
  }
555
534
  else {
556
- const filesInput = await prompt(rl, 'Files (comma-separated)', formatExamples.files.join(', '));
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
  }
@@ -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
- destPath = `${destDir}/${packageName}.${fileExtension}`;
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 = `${destDir}/${packageName}`;
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);
@@ -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>/<id>.ext
61
+ // Try direct file: <dir>/<packageName>.ext
53
62
  for (const ext of extensions) {
54
- const directPath = path_1.default.join(baseDir, `${id}${ext}`);
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>/<id>/SKILL.md or <dir>/<id>/AGENT.md
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, id, 'SKILL.md');
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, id, 'AGENT.md');
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;
@@ -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 - default to production
144
- const webappUrl = registryUrl.includes('localhost')
145
- ? registryUrl.replace(':3000', ':5173') // Local: localhost:3000 → localhost:5173
146
- : 'https://prpm.dev'; // Production: always use prpm.dev
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`);