prpm 0.1.17 → 1.0.0
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/index.js +14257 -107
- package/package.json +11 -9
- package/dist/__tests__/e2e/test-helpers.js +0 -151
- package/dist/commands/buy-credits.js +0 -224
- package/dist/commands/catalog.js +0 -365
- package/dist/commands/collections.js +0 -655
- package/dist/commands/config.js +0 -161
- package/dist/commands/credits.js +0 -186
- package/dist/commands/index.js +0 -184
- package/dist/commands/info.js +0 -78
- package/dist/commands/init.js +0 -684
- package/dist/commands/install.js +0 -789
- package/dist/commands/list.js +0 -189
- package/dist/commands/login.js +0 -316
- package/dist/commands/outdated.js +0 -130
- package/dist/commands/playground.js +0 -570
- package/dist/commands/popular.js +0 -33
- package/dist/commands/publish.js +0 -803
- package/dist/commands/schema.js +0 -41
- package/dist/commands/search.js +0 -446
- package/dist/commands/subscribe.js +0 -211
- package/dist/commands/telemetry.js +0 -104
- package/dist/commands/trending.js +0 -86
- package/dist/commands/uninstall.js +0 -120
- package/dist/commands/update.js +0 -121
- package/dist/commands/upgrade.js +0 -121
- package/dist/commands/whoami.js +0 -83
- package/dist/core/claude-config.js +0 -91
- package/dist/core/cursor-config.js +0 -130
- package/dist/core/downloader.js +0 -64
- package/dist/core/errors.js +0 -29
- package/dist/core/filesystem.js +0 -242
- package/dist/core/lockfile.js +0 -292
- package/dist/core/marketplace-converter.js +0 -224
- package/dist/core/registry-client.js +0 -305
- package/dist/core/schema-validator.js +0 -74
- package/dist/core/telemetry.js +0 -253
- package/dist/core/user-config.js +0 -147
- package/dist/types/registry.js +0 -12
- package/dist/types.js +0 -36
- package/dist/utils/license-extractor.js +0 -122
- package/dist/utils/multi-package.js +0 -117
- package/dist/utils/parallel-publisher.js +0 -144
- package/dist/utils/script-executor.js +0 -72
- package/dist/utils/snippet-extractor.js +0 -77
- package/dist/utils/webapp-url.js +0 -44
package/dist/commands/catalog.js
DELETED
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Catalog command - Discover and catalog existing packages
|
|
4
|
-
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.handleCatalog = handleCatalog;
|
|
7
|
-
exports.createCatalogCommand = createCatalogCommand;
|
|
8
|
-
const commander_1 = require("commander");
|
|
9
|
-
const promises_1 = require("fs/promises");
|
|
10
|
-
const path_1 = require("path");
|
|
11
|
-
const telemetry_1 = require("../core/telemetry");
|
|
12
|
-
const lockfile_1 = require("../core/lockfile");
|
|
13
|
-
const errors_1 = require("../core/errors");
|
|
14
|
-
/**
|
|
15
|
-
* Detect format and subtype from file path and content
|
|
16
|
-
*/
|
|
17
|
-
function detectPackageInfo(filePath, content) {
|
|
18
|
-
const fileName = (0, path_1.basename)(filePath);
|
|
19
|
-
const lowerFileName = fileName.toLowerCase();
|
|
20
|
-
// Claude skills - SKILL.md files
|
|
21
|
-
if (fileName === 'SKILL.md') {
|
|
22
|
-
const dirName = (0, path_1.basename)((0, path_1.join)(filePath, '..'));
|
|
23
|
-
return {
|
|
24
|
-
format: 'claude',
|
|
25
|
-
subtype: 'skill',
|
|
26
|
-
name: dirName.toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
// Claude agents
|
|
30
|
-
if (filePath.includes('.claude/agents') || filePath.includes('.claude-plugin/agents')) {
|
|
31
|
-
return {
|
|
32
|
-
format: 'claude',
|
|
33
|
-
subtype: 'agent',
|
|
34
|
-
name: fileName.replace(/\.(md|txt)$/, '').toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
// Cursor rules
|
|
38
|
-
if (filePath.includes('.cursor/rules') || lowerFileName.endsWith('.mdc')) {
|
|
39
|
-
return {
|
|
40
|
-
format: 'cursor',
|
|
41
|
-
subtype: 'rule',
|
|
42
|
-
name: fileName.replace(/\.(md|mdc|txt)$/, '').toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
// Windsurf rules
|
|
46
|
-
if (filePath.includes('.windsurf/rules')) {
|
|
47
|
-
return {
|
|
48
|
-
format: 'windsurf',
|
|
49
|
-
subtype: 'rule',
|
|
50
|
-
name: fileName.replace(/\.(md|txt)$/, '').toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
// Continue config
|
|
54
|
-
if (filePath.includes('.continue/prompts')) {
|
|
55
|
-
return {
|
|
56
|
-
format: 'continue',
|
|
57
|
-
subtype: 'prompt',
|
|
58
|
-
name: fileName.replace(/\.(md|txt)$/, '').toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
// Generic markdown files in root that look like prompts
|
|
62
|
-
if (lowerFileName.endsWith('.md') && content.length > 50) {
|
|
63
|
-
return {
|
|
64
|
-
format: 'generic',
|
|
65
|
-
subtype: 'prompt',
|
|
66
|
-
name: fileName.replace(/\.md$/, '').toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Recursively scan directories for packages
|
|
73
|
-
*/
|
|
74
|
-
async function scanDirectory(dirPath, baseDir, scanDir, maxDepth = 5, currentDepth = 0) {
|
|
75
|
-
if (currentDepth > maxDepth) {
|
|
76
|
-
return [];
|
|
77
|
-
}
|
|
78
|
-
const discovered = [];
|
|
79
|
-
try {
|
|
80
|
-
const entries = await (0, promises_1.readdir)(dirPath, { withFileTypes: true });
|
|
81
|
-
for (const entry of entries) {
|
|
82
|
-
const fullPath = (0, path_1.join)(dirPath, entry.name);
|
|
83
|
-
const relativePath = (0, path_1.relative)(baseDir, fullPath);
|
|
84
|
-
// Skip node_modules, .git, and other common dirs
|
|
85
|
-
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist' || entry.name === 'build') {
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
if (entry.isDirectory()) {
|
|
89
|
-
// Recursively scan subdirectories
|
|
90
|
-
const subDirPackages = await scanDirectory(fullPath, baseDir, scanDir, maxDepth, currentDepth + 1);
|
|
91
|
-
discovered.push(...subDirPackages);
|
|
92
|
-
}
|
|
93
|
-
else if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.mdc') || entry.name.endsWith('.txt'))) {
|
|
94
|
-
// Check if this is a package file
|
|
95
|
-
try {
|
|
96
|
-
const content = await (0, promises_1.readFile)(fullPath, 'utf-8');
|
|
97
|
-
const packageInfo = detectPackageInfo(fullPath, content);
|
|
98
|
-
if (packageInfo) {
|
|
99
|
-
discovered.push({
|
|
100
|
-
path: relativePath,
|
|
101
|
-
format: packageInfo.format,
|
|
102
|
-
subtype: packageInfo.subtype,
|
|
103
|
-
name: packageInfo.name,
|
|
104
|
-
files: [relativePath],
|
|
105
|
-
scanDir,
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
catch (err) {
|
|
110
|
-
// Skip files we can't read
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
catch (err) {
|
|
116
|
-
// Skip directories we can't read
|
|
117
|
-
}
|
|
118
|
-
return discovered;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Extract description from file content
|
|
122
|
-
* Tries multiple strategies:
|
|
123
|
-
* 1. YAML frontmatter (---\ndescription: ...\n---)
|
|
124
|
-
* 2. Markdown description field (description: ...)
|
|
125
|
-
* 3. First substantial paragraph after title
|
|
126
|
-
*/
|
|
127
|
-
function extractDescription(content) {
|
|
128
|
-
const lines = content.split('\n');
|
|
129
|
-
// Strategy 1: YAML frontmatter
|
|
130
|
-
if (lines[0]?.trim() === '---') {
|
|
131
|
-
let foundClosing = false;
|
|
132
|
-
let frontmatterLines = [];
|
|
133
|
-
for (let i = 1; i < lines.length && i < 50; i++) {
|
|
134
|
-
const line = lines[i];
|
|
135
|
-
if (line.trim() === '---') {
|
|
136
|
-
foundClosing = true;
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
frontmatterLines.push(line);
|
|
140
|
-
}
|
|
141
|
-
if (foundClosing && frontmatterLines.length > 0) {
|
|
142
|
-
// Parse YAML-like frontmatter
|
|
143
|
-
for (const line of frontmatterLines) {
|
|
144
|
-
const match = line.match(/^description:\s*(.+)$/i);
|
|
145
|
-
if (match) {
|
|
146
|
-
return match[1].trim().replace(/^["']|["']$/g, '').substring(0, 200);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
// Strategy 2: Look for "description:" field anywhere in first 20 lines
|
|
152
|
-
for (let i = 0; i < Math.min(lines.length, 20); i++) {
|
|
153
|
-
const line = lines[i];
|
|
154
|
-
const match = line.match(/^description:\s*(.+)$/i);
|
|
155
|
-
if (match) {
|
|
156
|
-
return match[1].trim().replace(/^["']|["']$/g, '').substring(0, 200);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
// Strategy 3: First substantial non-header paragraph
|
|
160
|
-
let foundTitle = false;
|
|
161
|
-
for (let i = 0; i < Math.min(lines.length, 30); i++) {
|
|
162
|
-
const line = lines[i].trim();
|
|
163
|
-
// Skip empty lines and YAML frontmatter
|
|
164
|
-
if (line === '' || line === '---') {
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
// Skip markdown headers
|
|
168
|
-
if (line.startsWith('#')) {
|
|
169
|
-
foundTitle = true;
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
// Found a substantial line after the title
|
|
173
|
-
if (foundTitle && line.length >= 20 && !line.startsWith('```')) {
|
|
174
|
-
return line.substring(0, 200);
|
|
175
|
-
}
|
|
176
|
-
// If no title found yet but line is substantial, use it
|
|
177
|
-
if (!foundTitle && line.length >= 30) {
|
|
178
|
-
return line.substring(0, 200);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Discover packages in specified directories
|
|
185
|
-
*/
|
|
186
|
-
async function handleCatalog(directories, options) {
|
|
187
|
-
const startTime = Date.now();
|
|
188
|
-
let success = false;
|
|
189
|
-
let error;
|
|
190
|
-
try {
|
|
191
|
-
console.log('🔍 Scanning for packages...\n');
|
|
192
|
-
const allDiscovered = [];
|
|
193
|
-
// Scan each directory
|
|
194
|
-
for (const dir of directories) {
|
|
195
|
-
console.log(` Scanning ${dir}...`);
|
|
196
|
-
try {
|
|
197
|
-
const dirStat = await (0, promises_1.stat)(dir);
|
|
198
|
-
if (!dirStat.isDirectory()) {
|
|
199
|
-
console.log(` ⚠️ Skipping ${dir} (not a directory)`);
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
const discovered = await scanDirectory(dir, dir, dir);
|
|
203
|
-
allDiscovered.push(...discovered);
|
|
204
|
-
console.log(` Found ${discovered.length} package(s) in ${dir}`);
|
|
205
|
-
}
|
|
206
|
-
catch (err) {
|
|
207
|
-
console.log(` ⚠️ Could not access ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
// Read lockfile to exclude packages installed from other users
|
|
211
|
-
const lockfile = await (0, lockfile_1.readLockfile)();
|
|
212
|
-
const installedPackageIds = new Set(lockfile ? Object.keys(lockfile.packages) : []);
|
|
213
|
-
// Filter out packages that are installed from the registry (not user-created)
|
|
214
|
-
const filteredDiscovered = allDiscovered.filter(pkg => {
|
|
215
|
-
// Check if this package exists in lockfile
|
|
216
|
-
// Package ID in lockfile could be: "package-name", "@author/package-name"
|
|
217
|
-
// Check both formats
|
|
218
|
-
const isInstalled = installedPackageIds.has(pkg.name) ||
|
|
219
|
-
Array.from(installedPackageIds).some(id => id.endsWith(`/${pkg.name}`));
|
|
220
|
-
if (isInstalled) {
|
|
221
|
-
console.log(` ⏩ Skipping ${pkg.name} (installed from registry, not user-created)`);
|
|
222
|
-
return false;
|
|
223
|
-
}
|
|
224
|
-
return true;
|
|
225
|
-
});
|
|
226
|
-
console.log(`\n✨ Discovered ${filteredDiscovered.length} package(s) total:\n`);
|
|
227
|
-
if (filteredDiscovered.length === 0) {
|
|
228
|
-
console.log('No user-created packages found. Try scanning different directories or check if all packages were installed from registry.');
|
|
229
|
-
success = true;
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
// Display discovered packages
|
|
233
|
-
const byFormat = new Map();
|
|
234
|
-
for (const pkg of filteredDiscovered) {
|
|
235
|
-
if (!byFormat.has(pkg.format)) {
|
|
236
|
-
byFormat.set(pkg.format, []);
|
|
237
|
-
}
|
|
238
|
-
byFormat.get(pkg.format).push(pkg);
|
|
239
|
-
}
|
|
240
|
-
for (const [format, packages] of byFormat.entries()) {
|
|
241
|
-
console.log(`📦 ${format} (${packages.length}):`);
|
|
242
|
-
for (const pkg of packages) {
|
|
243
|
-
console.log(` - ${pkg.name} (${pkg.subtype}): ${pkg.path}`);
|
|
244
|
-
}
|
|
245
|
-
console.log('');
|
|
246
|
-
}
|
|
247
|
-
if (options.dryRun) {
|
|
248
|
-
console.log('🔍 Dry run - would update prpm.json\n');
|
|
249
|
-
success = true;
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
// Load or create prpm.json
|
|
253
|
-
const prpmJsonPath = options.output || (0, path_1.join)(process.cwd(), 'prpm.json');
|
|
254
|
-
let manifest;
|
|
255
|
-
if (options.append) {
|
|
256
|
-
try {
|
|
257
|
-
const existingContent = await (0, promises_1.readFile)(prpmJsonPath, 'utf-8');
|
|
258
|
-
const existing = JSON.parse(existingContent);
|
|
259
|
-
// Check if it's a multi-package manifest
|
|
260
|
-
if ('packages' in existing && Array.isArray(existing.packages)) {
|
|
261
|
-
manifest = existing;
|
|
262
|
-
}
|
|
263
|
-
else {
|
|
264
|
-
// Convert single package to multi-package
|
|
265
|
-
manifest = {
|
|
266
|
-
name: 'multi-package',
|
|
267
|
-
version: '1.0.0',
|
|
268
|
-
packages: [existing],
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
catch (err) {
|
|
273
|
-
// File doesn't exist, create new
|
|
274
|
-
manifest = {
|
|
275
|
-
name: 'multi-package',
|
|
276
|
-
version: '1.0.0',
|
|
277
|
-
packages: [],
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
manifest = {
|
|
283
|
-
name: 'multi-package',
|
|
284
|
-
version: '1.0.0',
|
|
285
|
-
packages: [],
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
// Convert discovered packages to manifests
|
|
289
|
-
const existingNames = new Set(manifest.packages.map(p => p.name));
|
|
290
|
-
let addedCount = 0;
|
|
291
|
-
for (const discovered of filteredDiscovered) {
|
|
292
|
-
// Skip if already exists
|
|
293
|
-
if (existingNames.has(discovered.name)) {
|
|
294
|
-
console.log(` ⚠️ Skipping ${discovered.name} (already in prpm.json)`);
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
// Extract description from first file
|
|
298
|
-
let description = `${discovered.format} ${discovered.subtype}`;
|
|
299
|
-
try {
|
|
300
|
-
const firstFilePath = (0, path_1.join)(process.cwd(), discovered.scanDir, discovered.files[0]);
|
|
301
|
-
const content = await (0, promises_1.readFile)(firstFilePath, 'utf-8');
|
|
302
|
-
const extractedDesc = extractDescription(content);
|
|
303
|
-
if (extractedDesc) {
|
|
304
|
-
description = extractedDesc;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
catch (err) {
|
|
308
|
-
// Use default description
|
|
309
|
-
}
|
|
310
|
-
const packageManifest = {
|
|
311
|
-
name: discovered.name,
|
|
312
|
-
version: '1.0.0',
|
|
313
|
-
description,
|
|
314
|
-
author: '', // User should fill this in
|
|
315
|
-
format: discovered.format,
|
|
316
|
-
subtype: discovered.subtype,
|
|
317
|
-
files: discovered.files,
|
|
318
|
-
};
|
|
319
|
-
manifest.packages.push(packageManifest);
|
|
320
|
-
addedCount++;
|
|
321
|
-
}
|
|
322
|
-
// Write updated prpm.json
|
|
323
|
-
await (0, promises_1.writeFile)(prpmJsonPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
|
|
324
|
-
console.log(`\n✅ Updated ${prpmJsonPath}`);
|
|
325
|
-
console.log(` Added ${addedCount} new package(s)`);
|
|
326
|
-
console.log(` Total: ${manifest.packages.length} package(s)\n`);
|
|
327
|
-
console.log('💡 Next steps:');
|
|
328
|
-
console.log(' 1. Review and edit package metadata in prpm.json');
|
|
329
|
-
console.log(' 2. Add author, license, and other fields');
|
|
330
|
-
console.log(' 3. Run: prpm publish --dry-run to validate');
|
|
331
|
-
console.log(' 4. Run: prpm publish to publish packages\n');
|
|
332
|
-
success = true;
|
|
333
|
-
}
|
|
334
|
-
catch (err) {
|
|
335
|
-
error = err instanceof Error ? err.message : String(err);
|
|
336
|
-
console.error(`\n❌ Failed to catalog packages: ${error}\n`);
|
|
337
|
-
throw new errors_1.CLIError(`\n❌ Failed to catalog packages: ${error}`, 1);
|
|
338
|
-
}
|
|
339
|
-
finally {
|
|
340
|
-
await telemetry_1.telemetry.track({
|
|
341
|
-
command: 'catalog',
|
|
342
|
-
success,
|
|
343
|
-
error,
|
|
344
|
-
duration: Date.now() - startTime,
|
|
345
|
-
data: {
|
|
346
|
-
directories: directories.length,
|
|
347
|
-
},
|
|
348
|
-
});
|
|
349
|
-
await telemetry_1.telemetry.shutdown();
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Create the catalog command
|
|
354
|
-
*/
|
|
355
|
-
function createCatalogCommand() {
|
|
356
|
-
return new commander_1.Command('catalog')
|
|
357
|
-
.description('Discover and catalog existing packages from directories')
|
|
358
|
-
.argument('[directories...]', 'Directories to scan for packages (defaults to current directory)', ['.'])
|
|
359
|
-
.option('-o, --output <path>', 'Output path for prpm.json (default: ./prpm.json)')
|
|
360
|
-
.option('-a, --append', 'Append to existing prpm.json instead of overwriting')
|
|
361
|
-
.option('--dry-run', 'Show what would be cataloged without making changes')
|
|
362
|
-
.action(async (directories, options) => {
|
|
363
|
-
await handleCatalog(directories, options);
|
|
364
|
-
});
|
|
365
|
-
}
|