prpm 0.0.6 → 0.0.8

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.
@@ -48,60 +48,69 @@ const lockfile_1 = require("../core/lockfile");
48
48
  const cursor_config_1 = require("../core/cursor-config");
49
49
  const claude_config_1 = require("../core/claude-config");
50
50
  /**
51
- * Get icon for package type
51
+ * Get icon for package format and subtype
52
52
  */
53
- function getTypeIcon(type) {
54
- const icons = {
55
- 'claude-skill': 'šŸŽ“',
56
- 'claude-agent': 'šŸ¤–',
57
- 'claude-slash-command': '⚔',
53
+ function getPackageIcon(format, subtype) {
54
+ // Subtype icons take precedence
55
+ const subtypeIcons = {
56
+ 'skill': 'šŸŽ“',
57
+ 'agent': 'šŸ¤–',
58
+ 'slash-command': '⚔',
59
+ 'rule': 'šŸ“‹',
60
+ 'prompt': 'šŸ’¬',
61
+ 'workflow': '⚔',
62
+ 'tool': 'šŸ”§',
63
+ 'template': 'šŸ“„',
64
+ 'collection': 'šŸ“¦',
65
+ 'chatmode': 'šŸ’¬',
66
+ };
67
+ // Format-specific icons for rules/defaults
68
+ const formatIcons = {
58
69
  'claude': 'šŸ¤–',
59
70
  'cursor': 'šŸ“‹',
60
- 'cursor-agent': 'šŸ¤–',
61
- 'cursor-slash-command': '⚔',
62
71
  'windsurf': '🌊',
63
72
  'continue': 'āž”ļø',
73
+ 'copilot': 'āœˆļø',
74
+ 'kiro': 'šŸŽÆ',
64
75
  'mcp': 'šŸ”—',
76
+ 'agents.md': 'šŸ“',
65
77
  'generic': 'šŸ“¦',
66
- // Legacy mappings
67
- skill: 'šŸŽ“',
68
- agent: 'šŸ¤–',
69
- rule: 'šŸ“‹',
70
- plugin: 'šŸ”Œ',
71
- prompt: 'šŸ’¬',
72
- workflow: '⚔',
73
- tool: 'šŸ”§',
74
- template: 'šŸ“„',
75
78
  };
76
- return icons[type] || 'šŸ“¦';
79
+ return subtypeIcons[subtype] || formatIcons[format] || 'šŸ“¦';
77
80
  }
78
81
  /**
79
- * Get human-readable label for package type
82
+ * Get human-readable label for package format and subtype
80
83
  */
81
- function getTypeLabel(type) {
82
- const labels = {
83
- 'claude-skill': 'Claude Skill',
84
- 'claude-agent': 'Claude Agent',
85
- 'claude-slash-command': 'Claude Slash Command',
86
- 'claude': 'Claude Agent',
87
- 'cursor': 'Cursor Rule',
88
- 'cursor-agent': 'Cursor Agent',
89
- 'cursor-slash-command': 'Cursor Slash Command',
90
- 'windsurf': 'Windsurf Rule',
91
- 'continue': 'Continue Rule',
92
- 'mcp': 'MCP Server',
93
- 'generic': 'Package',
94
- // Legacy mappings
95
- skill: 'Skill',
96
- agent: 'Agent',
97
- rule: 'Rule',
98
- plugin: 'Plugin',
99
- prompt: 'Prompt',
100
- workflow: 'Workflow',
101
- tool: 'Tool',
102
- template: 'Template',
84
+ function getPackageLabel(format, subtype) {
85
+ const formatLabels = {
86
+ 'claude': 'Claude',
87
+ 'cursor': 'Cursor',
88
+ 'windsurf': 'Windsurf',
89
+ 'continue': 'Continue',
90
+ 'copilot': 'GitHub Copilot',
91
+ 'kiro': 'Kiro',
92
+ 'mcp': 'MCP',
93
+ 'agents.md': 'Agents.md',
94
+ 'generic': '',
95
+ };
96
+ const subtypeLabels = {
97
+ 'skill': 'Skill',
98
+ 'agent': 'Agent',
99
+ 'slash-command': 'Slash Command',
100
+ 'rule': 'Rule',
101
+ 'prompt': 'Prompt',
102
+ 'workflow': 'Workflow',
103
+ 'tool': 'Tool',
104
+ 'template': 'Template',
105
+ 'collection': 'Collection',
106
+ 'chatmode': 'Chat Mode',
103
107
  };
104
- return labels[type] || type;
108
+ const formatLabel = formatLabels[format];
109
+ const subtypeLabel = subtypeLabels[subtype];
110
+ if (format === 'generic') {
111
+ return subtypeLabel;
112
+ }
113
+ return `${formatLabel} ${subtypeLabel}`;
105
114
  }
106
115
  async function handleInstall(packageSpec, options) {
107
116
  const startTime = Date.now();
@@ -151,7 +160,7 @@ async function handleInstall(packageSpec, options) {
151
160
  if (!requestedVersion || requestedVersion === 'latest' || requestedVersion === installedPkg.version) {
152
161
  console.log(`\n✨ Package already installed!`);
153
162
  console.log(` šŸ“¦ ${packageId}@${installedPkg.version}`);
154
- console.log(` šŸ”„ Format: ${installedPkg.format || installedPkg.type || 'unknown'}`);
163
+ console.log(` šŸ”„ Format: ${installedPkg.format || 'unknown'} | Subtype: ${installedPkg.subtype || 'unknown'}`);
155
164
  console.log(`\nšŸ’” To reinstall or upgrade:`);
156
165
  console.log(` prpm upgrade ${packageId} # Upgrade to latest version`);
157
166
  console.log(` prpm uninstall ${packageId} # Uninstall first, then install`);
@@ -199,13 +208,29 @@ async function handleInstall(packageSpec, options) {
199
208
  }
200
209
  // Get package info
201
210
  const pkg = await client.getPackage(packageId);
202
- const typeIcon = getTypeIcon(pkg.type);
203
- const typeLabel = getTypeLabel(pkg.type);
211
+ const typeIcon = getPackageIcon(pkg.format, pkg.subtype);
212
+ const typeLabel = getPackageLabel(pkg.format, pkg.subtype);
204
213
  console.log(` ${pkg.name} ${pkg.official ? 'šŸ…' : ''}`);
205
214
  console.log(` ${pkg.description || 'No description'}`);
206
215
  console.log(` ${typeIcon} Type: ${typeLabel}`);
207
- // Determine format preference - use package type if no explicit conversion requested
208
- const format = options.as || pkg.type;
216
+ // Determine format preference
217
+ let format = options.as || pkg.format;
218
+ // Special handling for Claude packages: default to CLAUDE.md if it doesn't exist
219
+ // BUT only for packages that are generic rules (not skills, agents, or commands)
220
+ if (!options.as && pkg.format === 'claude' && pkg.subtype === 'rule') {
221
+ const { fileExists } = await Promise.resolve().then(() => __importStar(require('../core/filesystem.js')));
222
+ const claudeMdExists = await fileExists('CLAUDE.md');
223
+ if (!claudeMdExists) {
224
+ // CLAUDE.md doesn't exist, install as CLAUDE.md (recommended format for Claude Code)
225
+ format = 'claude-md';
226
+ console.log(` šŸ’” Installing as CLAUDE.md (recommended for Claude Code)`);
227
+ console.log(` To install as skill instead, use: prpm install ${packageId} --as claude`);
228
+ }
229
+ else {
230
+ // CLAUDE.md already exists, install as skill to avoid overwriting
231
+ console.log(` ā„¹ļø CLAUDE.md already exists, installing as skill in .claude/skills/`);
232
+ }
233
+ }
209
234
  if (options.as && format !== 'canonical') {
210
235
  console.log(` šŸ”„ Converting to ${format} format...`);
211
236
  }
@@ -228,58 +253,36 @@ async function handleInstall(packageSpec, options) {
228
253
  const tarball = await client.downloadPackage(tarballUrl, { format });
229
254
  // Extract tarball and save files
230
255
  console.log(` šŸ“‚ Extracting...`);
231
- // Determine effective type based on format and original package type
232
- let effectiveType;
233
- if (format === 'cursor') {
234
- // Map package types to cursor equivalents
235
- if (pkg.type === 'claude-slash-command' || pkg.type === 'cursor-slash-command') {
236
- effectiveType = 'cursor-slash-command';
237
- }
238
- else if (pkg.type === 'claude-agent' || pkg.type === 'cursor-agent') {
239
- effectiveType = 'cursor-agent';
240
- }
241
- else {
242
- effectiveType = 'cursor';
243
- }
244
- }
245
- else if (format === 'claude') {
246
- // Map package types to claude equivalents
247
- if (pkg.type === 'cursor-slash-command' || pkg.type === 'claude-slash-command') {
248
- effectiveType = 'claude-slash-command';
249
- }
250
- else if (pkg.type === 'cursor-agent' || pkg.type === 'claude-agent') {
251
- effectiveType = 'claude-agent';
252
- }
253
- else if (pkg.type === 'claude-skill') {
254
- effectiveType = 'claude-skill';
255
- }
256
- else {
257
- effectiveType = 'claude-agent';
258
- }
259
- }
260
- else if (format === 'continue' || format === 'windsurf') {
261
- effectiveType = format;
262
- }
263
- else {
264
- effectiveType = (options.type || pkg.type);
265
- }
266
- const destDir = (0, filesystem_1.getDestinationDir)(effectiveType);
256
+ // Determine effective format and subtype (from conversion or package native format)
257
+ const effectiveFormat = format || pkg.format;
258
+ const effectiveSubtype = pkg.subtype;
267
259
  // Extract all files from tarball
268
260
  const extractedFiles = await extractTarball(tarball, packageId);
269
261
  // Track where files were saved for user feedback
270
262
  let destPath;
271
263
  let fileCount = 0;
264
+ // Special handling for CLAUDE.md format (goes in project root)
265
+ if (format === 'claude-md') {
266
+ if (extractedFiles.length !== 1) {
267
+ throw new Error('CLAUDE.md format only supports single-file packages');
268
+ }
269
+ let mainFile = extractedFiles[0].content;
270
+ destPath = 'CLAUDE.md';
271
+ await (0, filesystem_1.saveFile)(destPath, mainFile);
272
+ fileCount = 1;
273
+ }
272
274
  // Check if this is a multi-file package
273
- if (extractedFiles.length === 1) {
275
+ else if (extractedFiles.length === 1) {
276
+ const destDir = (0, filesystem_1.getDestinationDir)(effectiveFormat, effectiveSubtype);
274
277
  // Single file package
275
278
  let mainFile = extractedFiles[0].content;
276
- // Determine file extension based on effective type
279
+ // Determine file extension based on effective format
277
280
  // Cursor rules use .mdc, but slash commands and other files use .md
278
- const fileExtension = (effectiveType === 'cursor' && format === 'cursor') ? 'mdc' : 'md';
281
+ const fileExtension = (effectiveFormat === 'cursor' && format === 'cursor') ? 'mdc' : 'md';
279
282
  const packageName = (0, filesystem_1.stripAuthorNamespace)(packageId);
280
283
  destPath = `${destDir}/${packageName}.${fileExtension}`;
281
284
  // Handle cursor format - add header if missing for .mdc files
282
- if (format === 'cursor' && effectiveType === 'cursor') {
285
+ if (format === 'cursor' && effectiveFormat === 'cursor') {
283
286
  if (!(0, cursor_config_1.hasMDCHeader)(mainFile)) {
284
287
  console.log(` āš ļø Adding missing MDC header...`);
285
288
  mainFile = (0, cursor_config_1.addMDCHeader)(mainFile, pkg.description);
@@ -301,11 +304,34 @@ async function handleInstall(packageSpec, options) {
301
304
  fileCount = 1;
302
305
  }
303
306
  else {
307
+ const destDir = (0, filesystem_1.getDestinationDir)(effectiveFormat, effectiveSubtype);
304
308
  // Multi-file package - create directory for package
305
309
  const packageName = (0, filesystem_1.stripAuthorNamespace)(packageId);
306
310
  const packageDir = `${destDir}/${packageName}`;
307
311
  destPath = packageDir;
308
312
  console.log(` šŸ“ Multi-file package - creating directory: ${packageDir}`);
313
+ // For Claude skills, auto-fix filename to SKILL.md if needed
314
+ if (effectiveFormat === 'claude' && effectiveSubtype === 'skill') {
315
+ const skillMdIndex = extractedFiles.findIndex(f => f.name === 'SKILL.md');
316
+ if (skillMdIndex === -1) {
317
+ // SKILL.md not found, look for common variations and auto-rename
318
+ const skillFileIndex = extractedFiles.findIndex(f => f.name.toLowerCase() === 'skill.md' ||
319
+ f.name === 'skill.md' ||
320
+ f.name.endsWith('.md') && extractedFiles.length === 1 // Single .md file
321
+ );
322
+ if (skillFileIndex !== -1) {
323
+ const oldName = extractedFiles[skillFileIndex].name;
324
+ console.log(` āš ļø Auto-fixing skill filename: ${oldName} → SKILL.md`);
325
+ console.log(` (Claude skills must be named SKILL.md per official documentation)`);
326
+ extractedFiles[skillFileIndex].name = 'SKILL.md';
327
+ }
328
+ else {
329
+ throw new Error('Claude skills must contain a SKILL.md file. ' +
330
+ 'According to Claude documentation, skills must have a file named SKILL.md in their directory. ' +
331
+ 'No suitable file found to rename. Please update the package to follow this requirement.');
332
+ }
333
+ }
334
+ }
309
335
  for (const file of extractedFiles) {
310
336
  const filePath = `${packageDir}/${file.name}`;
311
337
  await (0, filesystem_1.saveFile)(filePath, file.content);
@@ -318,8 +344,8 @@ async function handleInstall(packageSpec, options) {
318
344
  (0, lockfile_1.addToLockfile)(updatedLockfile, packageId, {
319
345
  version: actualVersion || version,
320
346
  tarballUrl,
321
- type: pkg.type,
322
- format,
347
+ format: pkg.format, // Preserve original package format
348
+ subtype: pkg.subtype, // Preserve original package subtype
323
349
  installedPath: destPath,
324
350
  });
325
351
  (0, lockfile_1.setPackageIntegrity)(updatedLockfile, packageId, tarball);
@@ -357,7 +383,7 @@ async function handleInstall(packageSpec, options) {
357
383
  data: {
358
384
  packageId: packageSpec.split('@')[0],
359
385
  version: options.version || 'latest',
360
- type: options.type,
386
+ convertTo: options.as,
361
387
  },
362
388
  });
363
389
  await telemetry_1.telemetry.shutdown();
@@ -455,23 +481,26 @@ function createInstallCommand() {
455
481
  .description('Install a package from the registry')
456
482
  .argument('<package>', 'Package to install (e.g., react-rules or react-rules@1.2.0)')
457
483
  .option('--version <version>', 'Specific version to install')
458
- .option('--type <type>', 'Override package type (cursor, claude, continue)')
459
- .option('--as <format>', 'Download in specific format (cursor, claude, continue, windsurf)')
484
+ .option('--as <format>', 'Convert and install in specific format (cursor, claude, continue, windsurf, canonical)')
485
+ .option('--format <format>', 'Alias for --as')
460
486
  .option('--frozen-lockfile', 'Fail if lock file needs to be updated (for CI)')
461
487
  .action(async (packageSpec, options) => {
462
- if (options.type && !['cursor', 'claude', 'continue', 'windsurf', 'generic'].includes(options.type)) {
463
- console.error('āŒ Type must be one of: cursor, claude, continue, windsurf, generic');
464
- process.exit(1);
465
- }
466
- if (options.as && !['cursor', 'claude', 'continue', 'windsurf', 'canonical'].includes(options.as)) {
488
+ // Support both --as and --format (format is alias for as)
489
+ const convertTo = options.format || options.as;
490
+ if (convertTo && !['cursor', 'claude', 'continue', 'windsurf', 'canonical'].includes(convertTo)) {
467
491
  console.error('āŒ Format must be one of: cursor, claude, continue, windsurf, canonical');
492
+ console.log('\nšŸ’” Examples:');
493
+ console.log(' prpm install my-package --as cursor # Convert to Cursor format');
494
+ console.log(' prpm install my-package --format claude # Convert to Claude format');
495
+ console.log(' prpm install my-package # Install in native format');
468
496
  process.exit(1);
469
497
  }
470
498
  await handleInstall(packageSpec, {
471
- type: options.type,
472
- as: options.as,
499
+ version: options.version,
500
+ as: convertTo,
473
501
  frozenLockfile: options.frozenLockfile
474
502
  });
503
+ process.exit(0);
475
504
  });
476
505
  return command;
477
506
  }
@@ -92,12 +92,12 @@ async function displayPackages(packages) {
92
92
  // Find file locations
93
93
  const packagesWithLocations = await Promise.all(packages.map(async (pkg) => ({
94
94
  ...pkg,
95
- location: await findPackageLocation(pkg.id, pkg.type)
95
+ location: await findPackageLocation(pkg.id, `${pkg.format}-${pkg.subtype}`)
96
96
  })));
97
97
  // Calculate column widths
98
98
  const idWidth = Math.max(8, ...packagesWithLocations.map(p => p.id.length));
99
99
  const versionWidth = Math.max(7, ...packagesWithLocations.map(p => p.version.length));
100
- const typeWidth = Math.max(6, ...packagesWithLocations.map(p => (p.type || '').length));
100
+ const typeWidth = Math.max(6, ...packagesWithLocations.map(p => (`${p.format || ''}-${p.subtype || ''}`).length));
101
101
  const locationWidth = Math.max(8, ...packagesWithLocations.map(p => (p.location || 'N/A').length));
102
102
  // Header
103
103
  const header = [
@@ -113,7 +113,7 @@ async function displayPackages(packages) {
113
113
  const row = [
114
114
  pkg.id.padEnd(idWidth),
115
115
  pkg.version.padEnd(versionWidth),
116
- (pkg.type || '').padEnd(typeWidth),
116
+ (`${pkg.format}-${pkg.subtype}` || '').padEnd(typeWidth),
117
117
  (pkg.location || 'N/A').padEnd(locationWidth)
118
118
  ].join(' | ');
119
119
  console.log(row);
@@ -161,6 +161,9 @@ function createListCommand() {
161
161
  const command = new commander_1.Command('list');
162
162
  command
163
163
  .description('List all installed prompt packages')
164
- .action(handleList);
164
+ .action(async () => {
165
+ await handleList();
166
+ process.exit(0);
167
+ });
165
168
  return command;
166
169
  }
@@ -140,7 +140,10 @@ 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
- const webappUrl = registryUrl.replace('registry', 'webapp').replace(':3000', ':5173');
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
144
147
  const authUrl = `${webappUrl}/cli-auth?sessionToken=${encodeURIComponent(connectSessionToken)}&cliCallback=${encodeURIComponent(callbackUrl)}&userId=${encodeURIComponent(userId)}`;
145
148
  console.log(` Please open this link in your browser to authenticate:`);
146
149
  console.log(` ${authUrl}\n`);
@@ -281,5 +284,8 @@ function createLoginCommand() {
281
284
  return new commander_1.Command('login')
282
285
  .description('Login to the PRMP registry')
283
286
  .option('--token <token>', 'Login with a personal access token')
284
- .action(handleLogin);
287
+ .action(async (options) => {
288
+ await handleLogin(options);
289
+ process.exit(0);
290
+ });
285
291
  }
@@ -124,5 +124,8 @@ function getUpdateType(current, latest) {
124
124
  function createOutdatedCommand() {
125
125
  return new commander_1.Command('outdated')
126
126
  .description('Check for package updates')
127
- .action(handleOutdated);
127
+ .action(async () => {
128
+ await handleOutdated();
129
+ process.exit(0);
130
+ });
128
131
  }
@@ -14,7 +14,10 @@ const trending_1 = require("./trending");
14
14
  async function handlePopular(options) {
15
15
  // Delegate to trending command
16
16
  console.log('šŸ“Š Popular Packages (All Time)\n');
17
- await (0, trending_1.handleTrending)({ type: options.type });
17
+ await (0, trending_1.handleTrending)({
18
+ format: options.format,
19
+ subtype: options.subtype
20
+ });
18
21
  }
19
22
  /**
20
23
  * Create the popular command
@@ -22,6 +25,10 @@ async function handlePopular(options) {
22
25
  function createPopularCommand() {
23
26
  return new commander_1.Command('popular')
24
27
  .description('Show popular packages (all time)')
25
- .option('-t, --type <type>', 'Filter by package type (cursor, claude, continue, windsurf)')
26
- .action(handlePopular);
28
+ .option('--format <format>', 'Filter by format (cursor, claude, continue, windsurf, copilot, kiro, generic)')
29
+ .option('--subtype <subtype>', 'Filter by subtype (rule, agent, skill, slash-command, prompt, workflow, tool, template, collection)')
30
+ .action(async (options) => {
31
+ await handlePopular(options);
32
+ process.exit(0);
33
+ });
27
34
  }
@@ -65,7 +65,13 @@ async function findAndLoadManifest() {
65
65
  return { manifest: validated, source: 'prpm.json' };
66
66
  }
67
67
  catch (error) {
68
- // prpm.json not found or invalid, try marketplace.json
68
+ // If it's a validation error, throw it immediately (don't try marketplace.json)
69
+ if (error instanceof Error && (error.message.includes('Manifest validation failed') ||
70
+ error.message.includes('Claude skill') ||
71
+ error.message.includes('SKILL.md'))) {
72
+ throw error;
73
+ }
74
+ // Otherwise, prpm.json not found or invalid JSON, try marketplace.json
69
75
  }
70
76
  // Try .claude/marketplace.json (Claude format)
71
77
  const marketplaceJsonPath = (0, path_1.join)(process.cwd(), '.claude', 'marketplace.json');
@@ -103,13 +109,55 @@ function validateManifest(manifest) {
103
109
  // Check if using enhanced format (file objects)
104
110
  const hasEnhancedFormat = manifest.files.some(f => typeof f === 'object');
105
111
  if (hasEnhancedFormat) {
106
- // Check if files have multiple distinct types
107
- const fileTypes = new Set(manifest.files
112
+ // Check if files have multiple distinct formats
113
+ const fileFormats = new Set(manifest.files
108
114
  .filter(f => typeof f === 'object')
109
- .map(f => f.type));
110
- // Only suggest "collection" if there are multiple distinct types
111
- if (fileTypes.size > 1 && manifest.type !== 'collection') {
112
- console.warn('āš ļø Package contains multiple file types. Consider setting type to "collection" for clarity.');
115
+ .map(f => f.format));
116
+ // Only suggest "collection" if there are multiple distinct formats
117
+ if (fileFormats.size > 1 && manifest.subtype !== 'collection') {
118
+ console.warn('āš ļø Package contains multiple file formats. Consider setting subtype to "collection" for clarity.');
119
+ }
120
+ }
121
+ // Enforce SKILL.md filename for Claude skills
122
+ if (manifest.format === 'claude' && manifest.subtype === 'skill') {
123
+ const filePaths = normalizeFilePaths(manifest.files);
124
+ const hasSkillMd = filePaths.some(path => path.endsWith('/SKILL.md') || path === 'SKILL.md');
125
+ if (!hasSkillMd) {
126
+ throw new Error('Claude skills must contain a SKILL.md file.\n' +
127
+ 'According to Claude documentation at https://docs.claude.com/en/docs/claude-code/skills,\n' +
128
+ 'skills must have a file named SKILL.md in their directory.\n' +
129
+ 'Please rename your skill file to SKILL.md (all caps) and update your prpm.json files array.');
130
+ }
131
+ // Validate skill name length (max 64 characters)
132
+ if (manifest.name.length > 64) {
133
+ throw new Error(`Claude skill name "${manifest.name}" exceeds 64 character limit (${manifest.name.length} characters).\n` +
134
+ 'According to Claude documentation, skill names must be max 64 characters.\n' +
135
+ 'Please shorten your package name.');
136
+ }
137
+ // Validate skill name format (lowercase, numbers, hyphens only)
138
+ if (!/^[a-z0-9-]+$/.test(manifest.name)) {
139
+ throw new Error(`Claude skill name "${manifest.name}" contains invalid characters.\n` +
140
+ 'According to Claude documentation, skill names must use lowercase letters, numbers, and hyphens only.\n' +
141
+ 'Please update your package name.');
142
+ }
143
+ // Validate description length (max 1024 characters)
144
+ if (manifest.description.length > 1024) {
145
+ throw new Error(`Claude skill description exceeds 1024 character limit (${manifest.description.length} characters).\n` +
146
+ 'According to Claude documentation, skill descriptions must be max 1024 characters.\n' +
147
+ 'Please shorten your description.');
148
+ }
149
+ // Warn if description is approaching the limit (80% = 819 chars)
150
+ if (manifest.description.length > 819) {
151
+ console.warn(`āš ļø Warning: Skill description is ${manifest.description.length}/1024 characters (${Math.round(manifest.description.length / 1024 * 100)}% of limit).\n` +
152
+ ' Consider keeping it concise for better discoverability.');
153
+ }
154
+ // Warn if description is too short (less than 100 chars)
155
+ if (manifest.description.length < 100) {
156
+ console.warn(`āš ļø Warning: Skill description is only ${manifest.description.length} characters.\n` +
157
+ ' Claude uses descriptions for skill discovery - consider adding more detail about:\n' +
158
+ ' - What the skill does\n' +
159
+ ' - When Claude should use it\n' +
160
+ ' - What problems it solves');
113
161
  }
114
162
  }
115
163
  return manifest;
@@ -212,14 +260,24 @@ async function handlePublish(options) {
212
260
  version = manifest.version;
213
261
  console.log(` Source: ${source}`);
214
262
  console.log(` Package: ${manifest.name}@${manifest.version}`);
215
- console.log(` Type: ${manifest.type}`);
263
+ console.log(` Format: ${manifest.format} | Subtype: ${manifest.subtype || 'rule (default)'}`);
216
264
  console.log(` Description: ${manifest.description}`);
217
265
  console.log('');
218
266
  // Create tarball
219
267
  console.log('šŸ“¦ Creating package tarball...');
220
268
  const tarball = await createTarball(manifest);
221
- const sizeMB = (tarball.length / (1024 * 1024)).toFixed(2);
222
- console.log(` Size: ${sizeMB}MB`);
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`;
276
+ }
277
+ else {
278
+ sizeDisplay = `${sizeInKB.toFixed(2)}KB`;
279
+ }
280
+ console.log(` Size: ${sizeDisplay}`);
223
281
  console.log('');
224
282
  if (options.dryRun) {
225
283
  console.log('āœ… Dry run successful! Package is ready to publish.');
@@ -243,6 +301,34 @@ async function handlePublish(options) {
243
301
  catch (err) {
244
302
  error = err instanceof Error ? err.message : String(err);
245
303
  console.error(`\nāŒ Failed to publish package: ${error}\n`);
304
+ // Provide helpful hints based on error type
305
+ if (error.includes('Manifest validation failed')) {
306
+ console.log('šŸ’” Common validation issues:');
307
+ console.log(' - Missing required fields (name, version, description, format)');
308
+ console.log(' - Invalid format or subtype values');
309
+ console.log(' - Description too short (min 10 chars) or too long (max 500 chars)');
310
+ console.log(' - Package name must be lowercase with hyphens only');
311
+ console.log('');
312
+ console.log('šŸ’” For Claude skills specifically:');
313
+ console.log(' - Add "subtype": "skill" to your prpm.json');
314
+ console.log(' - Ensure files include a SKILL.md file');
315
+ console.log(' - Package name must be max 64 characters');
316
+ console.log('');
317
+ console.log('šŸ’” View the schema: prpm schema');
318
+ console.log('');
319
+ }
320
+ else if (error.includes('SKILL.md')) {
321
+ console.log('šŸ’” Claude skills require:');
322
+ console.log(' - A file named SKILL.md (all caps) in your package');
323
+ console.log(' - "format": "claude" and "subtype": "skill" in prpm.json');
324
+ console.log('');
325
+ }
326
+ else if (error.includes('No manifest file found')) {
327
+ console.log('šŸ’” Create a manifest file:');
328
+ console.log(' - Run: prpm init');
329
+ console.log(' - Or create prpm.json manually');
330
+ console.log('');
331
+ }
246
332
  process.exit(1);
247
333
  }
248
334
  finally {
@@ -270,5 +356,8 @@ function createPublishCommand() {
270
356
  .option('--access <type>', 'Package access (public or private)', 'public')
271
357
  .option('--tag <tag>', 'NPM-style tag (e.g., latest, beta)', 'latest')
272
358
  .option('--dry-run', 'Validate package without publishing')
273
- .action(handlePublish);
359
+ .action(async (options) => {
360
+ await handlePublish(options);
361
+ process.exit(0);
362
+ });
274
363
  }
@@ -32,6 +32,9 @@ function createSchemaCommand() {
32
32
  const command = new commander_1.Command('schema');
33
33
  command
34
34
  .description('Display the PRPM manifest JSON schema')
35
- .action(handleSchema);
35
+ .action(async () => {
36
+ await handleSchema();
37
+ process.exit(0);
38
+ });
36
39
  return command;
37
40
  }