prpm 0.0.10 → 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.
@@ -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);
@@ -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 a manifest file
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 findAndLoadManifest() {
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 { manifest: validated, source: 'prpm.json' };
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 manifest = (0, marketplace_converter_1.marketplaceToManifest)(marketplaceData);
86
- // Validate the converted manifest
87
- const validated = validateManifest(manifest);
88
- return { manifest: validated, source: '.claude/marketplace.json' };
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
- // Neither file found
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 manifest
257
- console.log('🔍 Validating package manifest...');
258
- const { manifest, source } = await findAndLoadManifest();
259
- packageName = manifest.name;
260
- version = manifest.version;
261
- console.log(` Source: ${source}`);
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
- else {
278
- sizeDisplay = `${sizeInKB.toFixed(2)}KB`;
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
- console.log(` Size: ${sizeDisplay}`);
281
- console.log('');
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
- console.log(` Package: ${manifest.name}@${result.version}`);
296
- console.log(` Install: prpm install ${manifest.name}`);
297
- console.log(` View: ${config.registryUrl}/packages/${result.package_id}`);
298
- console.log('');
299
- success = true;
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);
@@ -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', 'workflow', 'tool', 'template', 'collection'];
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);
@@ -24,31 +24,69 @@ async function handleUninstall(name) {
24
24
  // Get destination directory using format and subtype
25
25
  const format = pkg.format || 'generic';
26
26
  const subtype = pkg.subtype || 'rule';
27
- const destDir = (0, filesystem_1.getDestinationDir)(format, subtype);
28
- const fileExtension = pkg.format === 'cursor' ? 'mdc' : 'md';
29
- // Strip author namespace to get just the package name
30
27
  const packageName = (0, filesystem_1.stripAuthorNamespace)(name);
31
- // Try single file first
32
- const singleFilePath = `${destDir}/${packageName}.${fileExtension}`;
33
- if (await (0, filesystem_1.fileExists)(singleFilePath)) {
34
- // Single file package
35
- await (0, filesystem_1.deleteFile)(singleFilePath);
36
- console.log(` 🗑️ Deleted file: ${singleFilePath}`);
28
+ const destDir = (0, filesystem_1.getDestinationDir)(format, subtype, packageName);
29
+ const fileExtension = pkg.format === 'cursor' ? 'mdc' : 'md';
30
+ // For Claude skills, check the directory structure first (since they use SKILL.md)
31
+ if (format === 'claude' && subtype === 'skill') {
32
+ // Claude skills are in .claude/skills/${packageName}/ with SKILL.md file
33
+ const skillPath = `${destDir}/SKILL.md`;
34
+ if (await (0, filesystem_1.fileExists)(skillPath)) {
35
+ // Delete the SKILL.md file
36
+ await (0, filesystem_1.deleteFile)(skillPath);
37
+ console.log(` 🗑️ Deleted file: ${skillPath}`);
38
+ // If the directory is empty or only contains SKILL.md, delete the directory too
39
+ try {
40
+ const dirContents = await fs_1.promises.readdir(destDir);
41
+ if (dirContents.length === 0) {
42
+ await fs_1.promises.rmdir(destDir);
43
+ console.log(` 🗑️ Deleted empty directory: ${destDir}`);
44
+ }
45
+ }
46
+ catch (error) {
47
+ // Directory doesn't exist or can't be deleted, that's okay
48
+ }
49
+ }
50
+ else {
51
+ // Try the whole directory
52
+ try {
53
+ const stats = await fs_1.promises.stat(destDir);
54
+ if (stats.isDirectory()) {
55
+ await fs_1.promises.rm(destDir, { recursive: true, force: true });
56
+ console.log(` 🗑️ Deleted directory: ${destDir}`);
57
+ }
58
+ }
59
+ catch (error) {
60
+ const err = error;
61
+ if (err.code !== 'ENOENT') {
62
+ console.warn(` ⚠️ Could not delete package files: ${err.message}`);
63
+ }
64
+ }
65
+ }
37
66
  }
38
67
  else {
39
- // Try multi-file package directory
40
- const packageDir = `${destDir}/${packageName}`;
41
- try {
42
- const stats = await fs_1.promises.stat(packageDir);
43
- if (stats.isDirectory()) {
44
- await fs_1.promises.rm(packageDir, { recursive: true, force: true });
45
- console.log(` 🗑️ Deleted directory: ${packageDir}`);
46
- }
68
+ // For other formats, try single file first
69
+ const singleFilePath = `${destDir}/${packageName}.${fileExtension}`;
70
+ if (await (0, filesystem_1.fileExists)(singleFilePath)) {
71
+ // Single file package
72
+ await (0, filesystem_1.deleteFile)(singleFilePath);
73
+ console.log(` 🗑️ Deleted file: ${singleFilePath}`);
47
74
  }
48
- catch (error) {
49
- const err = error;
50
- if (err.code !== 'ENOENT') {
51
- console.warn(` ⚠️ Could not delete package files: ${err.message}`);
75
+ else {
76
+ // Try multi-file package directory
77
+ const packageDir = `${destDir}/${packageName}`;
78
+ try {
79
+ const stats = await fs_1.promises.stat(packageDir);
80
+ if (stats.isDirectory()) {
81
+ await fs_1.promises.rm(packageDir, { recursive: true, force: true });
82
+ console.log(` 🗑️ Deleted directory: ${packageDir}`);
83
+ }
84
+ }
85
+ catch (error) {
86
+ const err = error;
87
+ if (err.code !== 'ENOENT') {
88
+ console.warn(` ⚠️ Could not delete package files: ${err.message}`);
89
+ }
52
90
  }
53
91
  }
54
92
  }