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.
@@ -49,29 +49,93 @@ 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 or array of packages
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');
64
+ let prpmJsonExists = false;
65
+ let prpmJsonError = null;
61
66
  try {
62
67
  const content = await (0, promises_1.readFile)(prpmJsonPath, 'utf-8');
63
- const manifest = JSON.parse(content);
68
+ prpmJsonExists = true;
69
+ // Try to parse JSON
70
+ let manifest;
71
+ try {
72
+ manifest = JSON.parse(content);
73
+ }
74
+ catch (parseError) {
75
+ // JSON parse error - provide specific error message
76
+ const error = parseError;
77
+ throw new Error(`Invalid JSON in prpm.json:\n\n` +
78
+ `${error.message}\n\n` +
79
+ `Please check your prpm.json file for syntax errors:\n` +
80
+ ` - Missing or extra commas\n` +
81
+ ` - Unclosed quotes or brackets\n` +
82
+ ` - Invalid JSON syntax\n\n` +
83
+ `You can validate your JSON at https://jsonlint.com/`);
84
+ }
85
+ // Check if this is a multi-package manifest
86
+ if ('packages' in manifest && Array.isArray(manifest.packages)) {
87
+ const multiManifest = manifest;
88
+ // Validate each package in the array
89
+ const validatedManifests = multiManifest.packages.map((pkg, idx) => {
90
+ // Inherit top-level fields if not specified in package - using explicit undefined checks
91
+ const packageWithDefaults = {
92
+ name: pkg.name,
93
+ version: pkg.version,
94
+ description: pkg.description,
95
+ format: pkg.format,
96
+ files: pkg.files,
97
+ author: pkg.author !== undefined ? pkg.author : multiManifest.author,
98
+ license: pkg.license !== undefined ? pkg.license : multiManifest.license,
99
+ repository: pkg.repository !== undefined ? pkg.repository : multiManifest.repository,
100
+ homepage: pkg.homepage !== undefined ? pkg.homepage : multiManifest.homepage,
101
+ documentation: pkg.documentation !== undefined ? pkg.documentation : multiManifest.documentation,
102
+ organization: pkg.organization !== undefined ? pkg.organization : multiManifest.organization,
103
+ private: pkg.private !== undefined ? pkg.private : multiManifest.private,
104
+ tags: pkg.tags !== undefined ? pkg.tags : multiManifest.tags,
105
+ keywords: pkg.keywords !== undefined ? pkg.keywords : multiManifest.keywords,
106
+ subtype: pkg.subtype,
107
+ dependencies: pkg.dependencies,
108
+ peerDependencies: pkg.peerDependencies,
109
+ engines: pkg.engines,
110
+ main: pkg.main,
111
+ };
112
+ // Debug: Log inheritance only if DEBUG env var is set
113
+ if (process.env.DEBUG) {
114
+ console.log(`\nšŸ” Package ${pkg.name} inheritance:`);
115
+ console.log(` - Package-level private: ${pkg.private}`);
116
+ console.log(` - Top-level private: ${multiManifest.private}`);
117
+ console.log(` - Inherited private: ${packageWithDefaults.private}`);
118
+ console.log('');
119
+ }
120
+ return validateManifest(packageWithDefaults);
121
+ });
122
+ return { manifests: validatedManifests, source: 'prpm.json (multi-package)' };
123
+ }
124
+ // Single package manifest
64
125
  const validated = validateManifest(manifest);
65
- return { manifest: validated, source: 'prpm.json' };
126
+ return { manifests: [validated], source: 'prpm.json' };
66
127
  }
67
128
  catch (error) {
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') ||
129
+ // Store error for later
130
+ prpmJsonError = error;
131
+ // If it's a validation or parsing error, throw it immediately (don't try marketplace.json)
132
+ if (prpmJsonExists && error instanceof Error && (error.message.includes('Invalid JSON') ||
133
+ error.message.includes('Manifest validation failed') ||
70
134
  error.message.includes('Claude skill') ||
71
135
  error.message.includes('SKILL.md'))) {
72
136
  throw error;
73
137
  }
74
- // Otherwise, prpm.json not found or invalid JSON, try marketplace.json
138
+ // Otherwise, prpm.json not found or other error, try marketplace.json
75
139
  }
76
140
  // Try .claude/marketplace.json (Claude format)
77
141
  const marketplaceJsonPath = (0, path_1.join)(process.cwd(), '.claude', 'marketplace.json');
@@ -81,24 +145,52 @@ async function findAndLoadManifest() {
81
145
  if (!(0, marketplace_converter_1.validateMarketplaceJson)(marketplaceData)) {
82
146
  throw new Error('Invalid marketplace.json format');
83
147
  }
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' };
148
+ // Convert each plugin in marketplace.json to a separate PRPM manifest
149
+ const manifests = [];
150
+ for (let i = 0; i < marketplaceData.plugins.length; i++) {
151
+ const manifest = (0, marketplace_converter_1.marketplaceToManifest)(marketplaceData, i);
152
+ const validated = validateManifest(manifest);
153
+ manifests.push(validated);
154
+ }
155
+ return { manifests, source: '.claude/marketplace.json' };
156
+ }
157
+ catch (error) {
158
+ // marketplace.json not found or invalid at .claude path, try .claude-plugin
159
+ }
160
+ // Try .claude-plugin/marketplace.json (alternative Claude format)
161
+ const marketplaceJsonPluginPath = (0, path_1.join)(process.cwd(), '.claude-plugin', 'marketplace.json');
162
+ try {
163
+ const content = await (0, promises_1.readFile)(marketplaceJsonPluginPath, 'utf-8');
164
+ const marketplaceData = JSON.parse(content);
165
+ if (!(0, marketplace_converter_1.validateMarketplaceJson)(marketplaceData)) {
166
+ throw new Error('Invalid marketplace.json format');
167
+ }
168
+ // Convert each plugin in marketplace.json to a separate PRPM manifest
169
+ const manifests = [];
170
+ for (let i = 0; i < marketplaceData.plugins.length; i++) {
171
+ const manifest = (0, marketplace_converter_1.marketplaceToManifest)(marketplaceData, i);
172
+ const validated = validateManifest(manifest);
173
+ manifests.push(validated);
174
+ }
175
+ return { manifests, source: '.claude-plugin/marketplace.json' };
89
176
  }
90
177
  catch (error) {
91
178
  // marketplace.json not found or invalid
92
179
  }
93
- // Neither file found
180
+ // No manifest file found
94
181
  throw new Error('No manifest file found. Expected either:\n' +
95
182
  ' - prpm.json in the current directory, or\n' +
96
- ' - .claude/marketplace.json (Claude format)');
183
+ ' - .claude/marketplace.json (Claude format), or\n' +
184
+ ' - .claude-plugin/marketplace.json (Claude format)');
97
185
  }
98
186
  /**
99
187
  * Validate package manifest
100
188
  */
101
189
  function validateManifest(manifest) {
190
+ // Set default subtype to 'rule' if not provided
191
+ if (!manifest.subtype) {
192
+ manifest.subtype = 'rule';
193
+ }
102
194
  // First, validate against JSON schema
103
195
  const schemaValidation = (0, schema_validator_1.validateManifestSchema)(manifest);
104
196
  if (!schemaValidation.valid) {
@@ -253,50 +345,212 @@ async function handlePublish(options) {
253
345
  process.exit(1);
254
346
  }
255
347
  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`;
348
+ // Read and validate manifests
349
+ console.log('šŸ” Validating package manifest(s)...');
350
+ const { manifests, source } = await findAndLoadManifests();
351
+ if (manifests.length > 1) {
352
+ console.log(` Found ${manifests.length} plugins in ${source}`);
353
+ console.log(' Will publish each plugin separately\n');
276
354
  }
277
- else {
278
- sizeDisplay = `${sizeInKB.toFixed(2)}KB`;
279
- }
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;
287
- }
288
- // Publish to registry
289
- console.log('šŸš€ Publishing to registry...');
355
+ // Get user info to check for organizations (once for all packages)
356
+ console.log('šŸ” Checking authentication...');
290
357
  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
- 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}`);
358
+ let userInfo;
359
+ try {
360
+ userInfo = await client.whoami();
361
+ }
362
+ catch (err) {
363
+ console.log(' Could not fetch user organizations, publishing as personal packages');
364
+ }
298
365
  console.log('');
299
- success = true;
366
+ // Track published packages
367
+ const publishedPackages = [];
368
+ const failedPackages = [];
369
+ // Publish each manifest
370
+ for (let i = 0; i < manifests.length; i++) {
371
+ const manifest = manifests[i];
372
+ packageName = manifest.name;
373
+ version = manifest.version;
374
+ if (manifests.length > 1) {
375
+ console.log(`\n${'='.repeat(60)}`);
376
+ console.log(`šŸ“¦ Publishing plugin ${i + 1} of ${manifests.length}`);
377
+ console.log(`${'='.repeat(60)}\n`);
378
+ }
379
+ try {
380
+ // Debug: Log access override logic only if DEBUG env var is set
381
+ if (process.env.DEBUG) {
382
+ console.log(`\nšŸ” Before access override:`);
383
+ console.log(` - manifest.private: ${manifest.private}`);
384
+ console.log(` - options.access: ${options.access}`);
385
+ }
386
+ // Determine access level:
387
+ // 1. If --access flag is provided, it overrides manifest setting
388
+ // 2. Otherwise, use manifest setting (defaults to false/public if not specified)
389
+ let isPrivate;
390
+ if (options.access !== undefined) {
391
+ // CLI flag explicitly provided - use it
392
+ isPrivate = options.access === 'private';
393
+ if (process.env.DEBUG) {
394
+ console.log(` - Using CLI flag override: ${options.access}`);
395
+ }
396
+ }
397
+ else {
398
+ // No CLI flag - use manifest setting
399
+ isPrivate = manifest.private || false;
400
+ if (process.env.DEBUG) {
401
+ console.log(` - Using manifest setting: ${isPrivate}`);
402
+ }
403
+ }
404
+ if (process.env.DEBUG) {
405
+ console.log(` - calculated isPrivate: ${isPrivate}`);
406
+ }
407
+ // Update manifest with final private setting
408
+ manifest.private = isPrivate;
409
+ if (process.env.DEBUG) {
410
+ console.log(` - final manifest.private: ${manifest.private}`);
411
+ console.log('');
412
+ }
413
+ let selectedOrgId;
414
+ // Check if organization is specified in manifest
415
+ if (manifest.organization && userInfo) {
416
+ const orgFromManifest = userInfo.organizations?.find((org) => org.name === manifest.organization || org.id === manifest.organization);
417
+ if (!orgFromManifest) {
418
+ throw new Error(`Organization "${manifest.organization}" not found or you are not a member`);
419
+ }
420
+ // Check if user has publishing rights
421
+ if (!['owner', 'admin', 'maintainer'].includes(orgFromManifest.role)) {
422
+ throw new Error(`You do not have permission to publish to organization "${orgFromManifest.name}". ` +
423
+ `Your role: ${orgFromManifest.role}. Required: owner, admin, or maintainer`);
424
+ }
425
+ selectedOrgId = orgFromManifest.id;
426
+ }
427
+ console.log(` Source: ${source}`);
428
+ console.log(` Package: ${manifest.name}@${manifest.version}`);
429
+ console.log(` Format: ${manifest.format} | Subtype: ${manifest.subtype}`);
430
+ console.log(` Description: ${manifest.description}`);
431
+ console.log(` Access: ${manifest.private ? 'private' : 'public'}`);
432
+ if (selectedOrgId && userInfo) {
433
+ const selectedOrg = userInfo.organizations.find((org) => org.id === selectedOrgId);
434
+ console.log(` Publishing to: ${selectedOrg?.name || 'organization'}`);
435
+ }
436
+ console.log('');
437
+ // Extract license information
438
+ console.log('šŸ“„ Extracting license information...');
439
+ const licenseInfo = await (0, license_extractor_1.extractLicenseInfo)(manifest.repository);
440
+ // Update manifest with license information from LICENSE file if found
441
+ // Only set fields that aren't already manually specified in prpm.json
442
+ if (licenseInfo.type && !manifest.license) {
443
+ manifest.license = licenseInfo.type;
444
+ }
445
+ if (licenseInfo.text && !manifest.license_text) {
446
+ manifest.license_text = licenseInfo.text;
447
+ }
448
+ if (licenseInfo.url && !manifest.license_url) {
449
+ manifest.license_url = licenseInfo.url || undefined;
450
+ }
451
+ // Validate and warn about license (optional - will extract if present)
452
+ (0, license_extractor_1.validateLicenseInfo)(licenseInfo, manifest.name);
453
+ console.log('');
454
+ // Extract content snippet
455
+ console.log('šŸ“ Extracting content snippet...');
456
+ const snippet = await (0, snippet_extractor_1.extractSnippet)(manifest);
457
+ if (snippet) {
458
+ manifest.snippet = snippet;
459
+ }
460
+ (0, snippet_extractor_1.validateSnippet)(snippet, manifest.name);
461
+ console.log('');
462
+ // Create tarball
463
+ console.log('šŸ“¦ Creating package tarball...');
464
+ const tarball = await createTarball(manifest);
465
+ // Display size in KB or MB depending on size
466
+ const sizeInBytes = tarball.length;
467
+ const sizeInKB = sizeInBytes / 1024;
468
+ const sizeInMB = sizeInBytes / (1024 * 1024);
469
+ let sizeDisplay;
470
+ if (sizeInMB >= 1) {
471
+ sizeDisplay = `${sizeInMB.toFixed(2)}MB`;
472
+ }
473
+ else {
474
+ sizeDisplay = `${sizeInKB.toFixed(2)}KB`;
475
+ }
476
+ console.log(` Size: ${sizeDisplay}`);
477
+ console.log('');
478
+ if (options.dryRun) {
479
+ console.log('āœ… Dry run successful! Package is ready to publish.');
480
+ publishedPackages.push({
481
+ name: manifest.name,
482
+ version: manifest.version,
483
+ url: ''
484
+ });
485
+ continue;
486
+ }
487
+ // Publish to registry
488
+ console.log('šŸš€ Publishing to registry...');
489
+ const result = await client.publish(manifest, tarball, selectedOrgId ? { orgId: selectedOrgId } : undefined);
490
+ // Determine the webapp URL based on registry URL
491
+ let webappUrl;
492
+ const registryUrl = config.registryUrl || 'https://registry.prpm.dev';
493
+ if (registryUrl.includes('localhost') || registryUrl.includes('127.0.0.1')) {
494
+ // Local development - webapp is on port 5173
495
+ webappUrl = 'http://localhost:5173';
496
+ }
497
+ else if (registryUrl.includes('registry.prpm.dev')) {
498
+ // Production - webapp is on prpm.dev
499
+ webappUrl = 'https://prpm.dev';
500
+ }
501
+ else {
502
+ // Default to registry URL for unknown environments
503
+ webappUrl = registryUrl;
504
+ }
505
+ const packageUrl = `${webappUrl}/packages/${encodeURIComponent(manifest.name)}`;
506
+ console.log('');
507
+ console.log('āœ… Package published successfully!');
508
+ console.log('');
509
+ console.log(` Package: ${manifest.name}@${result.version}`);
510
+ console.log(` Install: prpm install ${manifest.name}`);
511
+ console.log('');
512
+ publishedPackages.push({
513
+ name: manifest.name,
514
+ version: result.version,
515
+ url: packageUrl
516
+ });
517
+ }
518
+ catch (err) {
519
+ const pkgError = err instanceof Error ? err.message : String(err);
520
+ console.error(`\nāŒ Failed to publish ${manifest.name}: ${pkgError}\n`);
521
+ failedPackages.push({
522
+ name: manifest.name,
523
+ error: pkgError
524
+ });
525
+ }
526
+ }
527
+ // Print summary if multiple packages
528
+ if (manifests.length > 1) {
529
+ console.log(`\n${'='.repeat(60)}`);
530
+ console.log(`šŸ“Š Publishing Summary`);
531
+ console.log(`${'='.repeat(60)}\n`);
532
+ if (publishedPackages.length > 0) {
533
+ console.log(`āœ… Successfully published ${publishedPackages.length} package(s):`);
534
+ publishedPackages.forEach(pkg => {
535
+ console.log(` - ${pkg.name}@${pkg.version}`);
536
+ if (pkg.url) {
537
+ console.log(` ${pkg.url}`);
538
+ }
539
+ });
540
+ console.log('');
541
+ }
542
+ if (failedPackages.length > 0) {
543
+ console.log(`āŒ Failed to publish ${failedPackages.length} package(s):`);
544
+ failedPackages.forEach(pkg => {
545
+ console.log(` - ${pkg.name}: ${pkg.error}`);
546
+ });
547
+ console.log('');
548
+ }
549
+ }
550
+ success = publishedPackages.length > 0;
551
+ if (failedPackages.length > 0 && publishedPackages.length === 0) {
552
+ process.exit(1);
553
+ }
300
554
  }
301
555
  catch (err) {
302
556
  error = err instanceof Error ? err.message : String(err);
@@ -353,7 +607,7 @@ async function handlePublish(options) {
353
607
  function createPublishCommand() {
354
608
  return new commander_1.Command('publish')
355
609
  .description('Publish a package to the registry')
356
- .option('--access <type>', 'Package access (public or private)', 'public')
610
+ .option('--access <type>', 'Package access (public or private) - overrides manifest setting')
357
611
  .option('--tag <tag>', 'NPM-style tag (e.g., latest, beta)', 'latest')
358
612
  .option('--dry-run', 'Validate package without publishing')
359
613
  .action(async (options) => {
@@ -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
  }
@@ -17,8 +17,13 @@ const fs_1 = require("fs");
17
17
  const path_1 = __importDefault(require("path"));
18
18
  /**
19
19
  * Get the destination directory for a package based on format and subtype
20
+ * @param format - Package format (cursor, claude, etc.)
21
+ * @param subtype - Package subtype (skill, agent, rule, etc.)
22
+ * @param name - Package name (optional, only needed for Claude skills which create subdirectories)
20
23
  */
21
- function getDestinationDir(format, subtype) {
24
+ function getDestinationDir(format, subtype, name) {
25
+ // Strip author namespace from package name to avoid nested directories
26
+ const packageName = stripAuthorNamespace(name);
22
27
  switch (format) {
23
28
  case 'cursor':
24
29
  if (subtype === 'agent')
@@ -27,6 +32,9 @@ function getDestinationDir(format, subtype) {
27
32
  return '.cursor/commands';
28
33
  return '.cursor/rules';
29
34
  case 'claude':
35
+ // Only create subdirectory for skills if name is provided
36
+ if (subtype === 'skill' && packageName)
37
+ return `.claude/skills/${packageName}`;
30
38
  if (subtype === 'skill')
31
39
  return '.claude/skills';
32
40
  if (subtype === 'slash-command')
@@ -124,6 +132,10 @@ function generateId(filename) {
124
132
  * stripAuthorNamespace('git-workflow-manager') // 'git-workflow-manager'
125
133
  */
126
134
  function stripAuthorNamespace(packageId) {
135
+ // Handle undefined or empty string
136
+ if (!packageId) {
137
+ return '';
138
+ }
127
139
  // Split by '/' and get the last segment (the actual package name)
128
140
  const parts = packageId.split('/');
129
141
  return parts[parts.length - 1];
@@ -42,7 +42,8 @@ function marketplaceToManifest(marketplace, pluginIndex = 0) {
42
42
  }
43
43
  // Generate package name from plugin name
44
44
  // Format: @owner/plugin-name
45
- const packageName = generatePackageName(marketplace.owner, plugin.name);
45
+ const ownerName = typeof marketplace.owner === 'string' ? marketplace.owner : marketplace.owner.name;
46
+ const packageName = generatePackageName(ownerName, plugin.name);
46
47
  // Collect all files that should be included
47
48
  const files = collectFiles(plugin);
48
49
  // Determine the main file
@@ -54,13 +55,25 @@ function marketplaceToManifest(marketplace, pluginIndex = 0) {
54
55
  ].slice(0, 20); // Max 20 keywords
55
56
  // Extract tags from keywords (first 10)
56
57
  const tags = keywords.slice(0, 10);
58
+ // Get description from plugin, metadata, or root
59
+ const description = plugin.description ||
60
+ marketplace.metadata?.description ||
61
+ marketplace.description ||
62
+ '';
63
+ // Get version from plugin, metadata, or root
64
+ const version = plugin.version ||
65
+ marketplace.metadata?.version ||
66
+ marketplace.version ||
67
+ '1.0.0';
68
+ // Get author - prefer plugin.author, fallback to owner name
69
+ const author = plugin.author || ownerName;
57
70
  const manifest = {
58
71
  name: packageName,
59
- version: plugin.version || marketplace.version || '1.0.0',
60
- description: plugin.description || marketplace.description,
72
+ version,
73
+ description,
61
74
  format,
62
75
  subtype,
63
- author: plugin.author || marketplace.owner,
76
+ author,
64
77
  files,
65
78
  tags,
66
79
  keywords,
@@ -126,7 +139,7 @@ function collectFiles(plugin) {
126
139
  }
127
140
  }
128
141
  // Add standard files if they're not already included
129
- const standardFiles = ['README.md', 'LICENSE', '.claude/marketplace.json'];
142
+ const standardFiles = ['README.md', 'LICENSE', '.claude/marketplace.json', '.claude-plugin/marketplace.json'];
130
143
  for (const file of standardFiles) {
131
144
  files.add(file);
132
145
  }
@@ -185,10 +198,18 @@ function validateMarketplaceJson(data) {
185
198
  if (!marketplace.name || typeof marketplace.name !== 'string') {
186
199
  return false;
187
200
  }
188
- if (!marketplace.owner || typeof marketplace.owner !== 'string') {
201
+ // owner can be either string or object with name property
202
+ if (!marketplace.owner) {
189
203
  return false;
190
204
  }
191
- if (!marketplace.description || typeof marketplace.description !== 'string') {
205
+ if (typeof marketplace.owner !== 'string' &&
206
+ (typeof marketplace.owner !== 'object' || !marketplace.owner.name)) {
207
+ return false;
208
+ }
209
+ // description can be at root or in metadata
210
+ const hasDescription = (marketplace.description && typeof marketplace.description === 'string') ||
211
+ (marketplace.metadata?.description && typeof marketplace.metadata.description === 'string');
212
+ if (!hasDescription) {
192
213
  return false;
193
214
  }
194
215
  if (!Array.isArray(marketplace.plugins) || marketplace.plugins.length === 0) {