prpm 0.0.10 ā 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/e2e/test-helpers.js +1 -1
- package/dist/commands/init.js +17 -40
- package/dist/commands/install.js +30 -11
- package/dist/commands/list.js +14 -5
- package/dist/commands/login.js +15 -4
- package/dist/commands/publish.js +312 -58
- package/dist/commands/search.js +3 -7
- package/dist/commands/uninstall.js +59 -21
- package/dist/core/filesystem.js +13 -1
- package/dist/core/marketplace-converter.js +28 -7
- package/dist/core/registry-client.js +31 -4
- package/dist/core/user-config.js +88 -22
- package/dist/types/registry.js +7 -0
- package/dist/types.js +30 -0
- package/dist/utils/license-extractor.js +122 -0
- package/dist/utils/multi-package.js +117 -0
- package/dist/utils/parallel-publisher.js +144 -0
- package/dist/utils/snippet-extractor.js +70 -0
- package/package.json +3 -3
- package/schemas/prpm-manifest.schema.json +88 -0
package/dist/commands/publish.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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 {
|
|
126
|
+
return { manifests: [validated], source: 'prpm.json' };
|
|
66
127
|
}
|
|
67
128
|
catch (error) {
|
|
68
|
-
//
|
|
69
|
-
|
|
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
|
|
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
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
//
|
|
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
|
|
257
|
-
console.log('š Validating package manifest...');
|
|
258
|
-
const {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
278
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
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)
|
|
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) => {
|
package/dist/commands/search.js
CHANGED
|
@@ -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', '
|
|
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
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
//
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
}
|
package/dist/core/filesystem.js
CHANGED
|
@@ -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
|
|
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
|
|
60
|
-
description
|
|
72
|
+
version,
|
|
73
|
+
description,
|
|
61
74
|
format,
|
|
62
75
|
subtype,
|
|
63
|
-
author
|
|
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
|
-
|
|
201
|
+
// owner can be either string or object with name property
|
|
202
|
+
if (!marketplace.owner) {
|
|
189
203
|
return false;
|
|
190
204
|
}
|
|
191
|
-
if (
|
|
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) {
|