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.
Files changed (46) hide show
  1. package/dist/index.js +14257 -107
  2. package/package.json +11 -9
  3. package/dist/__tests__/e2e/test-helpers.js +0 -151
  4. package/dist/commands/buy-credits.js +0 -224
  5. package/dist/commands/catalog.js +0 -365
  6. package/dist/commands/collections.js +0 -655
  7. package/dist/commands/config.js +0 -161
  8. package/dist/commands/credits.js +0 -186
  9. package/dist/commands/index.js +0 -184
  10. package/dist/commands/info.js +0 -78
  11. package/dist/commands/init.js +0 -684
  12. package/dist/commands/install.js +0 -789
  13. package/dist/commands/list.js +0 -189
  14. package/dist/commands/login.js +0 -316
  15. package/dist/commands/outdated.js +0 -130
  16. package/dist/commands/playground.js +0 -570
  17. package/dist/commands/popular.js +0 -33
  18. package/dist/commands/publish.js +0 -803
  19. package/dist/commands/schema.js +0 -41
  20. package/dist/commands/search.js +0 -446
  21. package/dist/commands/subscribe.js +0 -211
  22. package/dist/commands/telemetry.js +0 -104
  23. package/dist/commands/trending.js +0 -86
  24. package/dist/commands/uninstall.js +0 -120
  25. package/dist/commands/update.js +0 -121
  26. package/dist/commands/upgrade.js +0 -121
  27. package/dist/commands/whoami.js +0 -83
  28. package/dist/core/claude-config.js +0 -91
  29. package/dist/core/cursor-config.js +0 -130
  30. package/dist/core/downloader.js +0 -64
  31. package/dist/core/errors.js +0 -29
  32. package/dist/core/filesystem.js +0 -242
  33. package/dist/core/lockfile.js +0 -292
  34. package/dist/core/marketplace-converter.js +0 -224
  35. package/dist/core/registry-client.js +0 -305
  36. package/dist/core/schema-validator.js +0 -74
  37. package/dist/core/telemetry.js +0 -253
  38. package/dist/core/user-config.js +0 -147
  39. package/dist/types/registry.js +0 -12
  40. package/dist/types.js +0 -36
  41. package/dist/utils/license-extractor.js +0 -122
  42. package/dist/utils/multi-package.js +0 -117
  43. package/dist/utils/parallel-publisher.js +0 -144
  44. package/dist/utils/script-executor.js +0 -72
  45. package/dist/utils/snippet-extractor.js +0 -77
  46. package/dist/utils/webapp-url.js +0 -44
@@ -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
- }