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,242 +0,0 @@
1
- "use strict";
2
- /**
3
- * File system operations for managing prompt files
4
- */
5
- var __importDefault = (this && this.__importDefault) || function (mod) {
6
- return (mod && mod.__esModule) ? mod : { "default": mod };
7
- };
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.getDestinationDir = getDestinationDir;
10
- exports.ensureDirectoryExists = ensureDirectoryExists;
11
- exports.saveFile = saveFile;
12
- exports.deleteFile = deleteFile;
13
- exports.fileExists = fileExists;
14
- exports.directoryExists = directoryExists;
15
- exports.autoDetectFormat = autoDetectFormat;
16
- exports.generateId = generateId;
17
- exports.stripAuthorNamespace = stripAuthorNamespace;
18
- exports.getInstalledFilePath = getInstalledFilePath;
19
- exports.getInstalledFilePaths = getInstalledFilePaths;
20
- const fs_1 = require("fs");
21
- const path_1 = __importDefault(require("path"));
22
- /**
23
- * Get the destination directory for a package based on format and subtype
24
- * @param format - Package format (cursor, claude, etc.)
25
- * @param subtype - Package subtype (skill, agent, rule, etc.)
26
- * @param name - Package name (optional, only needed for Claude skills which create subdirectories)
27
- */
28
- function getDestinationDir(format, subtype, name) {
29
- // Strip author namespace from package name to avoid nested directories
30
- const packageName = stripAuthorNamespace(name);
31
- switch (format) {
32
- case 'cursor':
33
- if (subtype === 'agent')
34
- return '.cursor/agents';
35
- if (subtype === 'slash-command')
36
- return '.cursor/commands';
37
- return '.cursor/rules';
38
- case 'claude':
39
- // Only create subdirectory for skills if name is provided
40
- if (subtype === 'skill' && packageName)
41
- return `.claude/skills/${packageName}`;
42
- if (subtype === 'skill')
43
- return '.claude/skills';
44
- if (subtype === 'slash-command')
45
- return '.claude/commands';
46
- if (subtype === 'agent')
47
- return '.claude/agents';
48
- // Hooks are configured in settings.json, return .claude directory
49
- if (subtype === 'hook')
50
- return '.claude';
51
- return '.claude/agents'; // Default for claude
52
- case 'continue':
53
- // Continue has separate directories for prompts (slash commands) and rules
54
- if (subtype === 'rule')
55
- return '.continue/rules';
56
- return '.continue/prompts';
57
- case 'windsurf':
58
- return '.windsurf/rules';
59
- case 'copilot':
60
- // Copilot has different locations based on subtype:
61
- // - Repository-wide instructions: .github/copilot-instructions.md
62
- // - Path-specific instructions: .github/instructions/*.instructions.md
63
- // - Chat modes: .github/chatmodes/*.chatmode.md
64
- if (subtype === 'chatmode')
65
- return '.github/chatmodes';
66
- // Default to path-specific instructions directory
67
- return '.github/instructions';
68
- case 'kiro':
69
- // Kiro has different locations based on subtype:
70
- // - Steering files: .kiro/steering/*.md
71
- // - Hooks: .kiro/hooks/*.kiro.hook (JSON files)
72
- if (subtype === 'hook')
73
- return '.kiro/hooks';
74
- return '.kiro/steering';
75
- case 'agents.md':
76
- return '.agents';
77
- case 'generic':
78
- return '.prompts';
79
- case 'mcp':
80
- return '.mcp/tools';
81
- default:
82
- throw new Error(`Unknown format: ${format}`);
83
- }
84
- }
85
- /**
86
- * Ensure directory exists, creating it if necessary
87
- */
88
- async function ensureDirectoryExists(dirPath) {
89
- try {
90
- await fs_1.promises.mkdir(dirPath, { recursive: true });
91
- }
92
- catch (error) {
93
- throw new Error(`Failed to create directory ${dirPath}: ${error}`);
94
- }
95
- }
96
- /**
97
- * Save content to a file
98
- */
99
- async function saveFile(filePath, content) {
100
- try {
101
- // Ensure parent directory exists
102
- const dir = path_1.default.dirname(filePath);
103
- await ensureDirectoryExists(dir);
104
- // Write file
105
- await fs_1.promises.writeFile(filePath, content, 'utf-8');
106
- }
107
- catch (error) {
108
- throw new Error(`Failed to save file ${filePath}: ${error}`);
109
- }
110
- }
111
- /**
112
- * Delete a file
113
- */
114
- async function deleteFile(filePath) {
115
- try {
116
- await fs_1.promises.unlink(filePath);
117
- }
118
- catch (error) {
119
- const err = error;
120
- if (err.code === 'ENOENT') {
121
- // File doesn't exist, that's fine
122
- return;
123
- }
124
- throw new Error(`Failed to delete file ${filePath}: ${error}`);
125
- }
126
- }
127
- /**
128
- * Check if a file exists
129
- */
130
- async function fileExists(filePath) {
131
- try {
132
- await fs_1.promises.access(filePath);
133
- return true;
134
- }
135
- catch {
136
- return false;
137
- }
138
- }
139
- /**
140
- * Check if a directory exists
141
- */
142
- async function directoryExists(dirPath) {
143
- try {
144
- const stats = await fs_1.promises.stat(dirPath);
145
- return stats.isDirectory();
146
- }
147
- catch {
148
- return false;
149
- }
150
- }
151
- /**
152
- * Auto-detect the format based on existing directories in the current project
153
- * Returns the format if a matching directory is found, or null if none found
154
- */
155
- async function autoDetectFormat() {
156
- const formatDirs = [
157
- { format: 'cursor', dir: '.cursor' },
158
- { format: 'claude', dir: '.claude' },
159
- { format: 'continue', dir: '.continue' },
160
- { format: 'windsurf', dir: '.windsurf' },
161
- { format: 'copilot', dir: '.github/instructions' },
162
- { format: 'kiro', dir: '.kiro' },
163
- { format: 'agents.md', dir: '.agents' },
164
- ];
165
- for (const { format, dir } of formatDirs) {
166
- if (await directoryExists(dir)) {
167
- return format;
168
- }
169
- }
170
- return null;
171
- }
172
- /**
173
- * Generate a unique ID from filename
174
- */
175
- function generateId(filename) {
176
- // Remove extension and convert to kebab-case
177
- const name = filename.replace(/\.[^/.]+$/, '');
178
- return name
179
- .toLowerCase()
180
- .replace(/[^a-z0-9]+/g, '-')
181
- .replace(/^-+|-+$/g, '');
182
- }
183
- /**
184
- * Strip author namespace from package ID and return just the package name
185
- * @example
186
- * stripAuthorNamespace('@community/git-workflow-manager') // 'git-workflow-manager'
187
- * stripAuthorNamespace('community/git-workflow-manager') // 'git-workflow-manager'
188
- * stripAuthorNamespace('@wshobson/commands/agent-orchestration/improve-agent') // 'improve-agent'
189
- * stripAuthorNamespace('git-workflow-manager') // 'git-workflow-manager'
190
- */
191
- function stripAuthorNamespace(packageId) {
192
- // Handle undefined or empty string
193
- if (!packageId) {
194
- return '';
195
- }
196
- // Split by '/' and get the last segment (the actual package name)
197
- const parts = packageId.split('/');
198
- return parts[parts.length - 1];
199
- }
200
- /**
201
- * Get the expected installed file path for a package
202
- * This matches the logic used by the install command to determine where files are placed
203
- *
204
- * @param packageName - Full package name (e.g., '@prpm/typescript-rules')
205
- * @param format - Package format
206
- * @param subtype - Package subtype
207
- * @param fileName - Optional specific file name (defaults to main file)
208
- * @returns Path where the file will be installed relative to working directory
209
- */
210
- function getInstalledFilePath(packageName, format, subtype, fileName) {
211
- const destDir = getDestinationDir(format, subtype, packageName);
212
- const packageBaseName = stripAuthorNamespace(packageName);
213
- // If a specific file name is provided, use it
214
- if (fileName) {
215
- return path_1.default.join(destDir, fileName);
216
- }
217
- // Claude skills always use SKILL.md
218
- if (format === 'claude' && subtype === 'skill') {
219
- return path_1.default.join(destDir, 'SKILL.md');
220
- }
221
- // agents.md uses package-name/AGENTS.md structure
222
- if (format === 'agents.md') {
223
- return path_1.default.join(destDir, packageBaseName, 'AGENTS.md');
224
- }
225
- // Determine file extension
226
- const fileExtension = format === 'cursor' ? 'mdc' : 'md';
227
- // For other formats, use package name as filename
228
- return path_1.default.join(destDir, `${packageBaseName}.${fileExtension}`);
229
- }
230
- /**
231
- * Get all expected installed file paths for a multi-file package
232
- *
233
- * @param packageName - Full package name
234
- * @param format - Package format
235
- * @param subtype - Package subtype
236
- * @param fileNames - Array of file names in the package
237
- * @returns Array of paths where files will be installed
238
- */
239
- function getInstalledFilePaths(packageName, format, subtype, fileNames) {
240
- const destDir = getDestinationDir(format, subtype, packageName);
241
- return fileNames.map(fileName => path_1.default.join(destDir, fileName));
242
- }
@@ -1,292 +0,0 @@
1
- "use strict";
2
- /**
3
- * Lock file management for reproducible installations
4
- * prpm.lock format similar to package-lock.json
5
- */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.readLockfile = readLockfile;
8
- exports.writeLockfile = writeLockfile;
9
- exports.createLockfile = createLockfile;
10
- exports.addToLockfile = addToLockfile;
11
- exports.setPackageIntegrity = setPackageIntegrity;
12
- exports.verifyPackageIntegrity = verifyPackageIntegrity;
13
- exports.getLockedVersion = getLockedVersion;
14
- exports.isLockfileOutOfSync = isLockfileOutOfSync;
15
- exports.mergeLockfiles = mergeLockfiles;
16
- exports.pruneLockfile = pruneLockfile;
17
- exports.addPackage = addPackage;
18
- exports.removePackage = removePackage;
19
- exports.listPackages = listPackages;
20
- exports.getPackage = getPackage;
21
- exports.addCollectionToLockfile = addCollectionToLockfile;
22
- exports.getCollectionFromLockfile = getCollectionFromLockfile;
23
- exports.removeCollectionFromLockfile = removeCollectionFromLockfile;
24
- exports.listCollectionsFromLockfile = listCollectionsFromLockfile;
25
- const fs_1 = require("fs");
26
- const path_1 = require("path");
27
- const crypto_1 = require("crypto");
28
- const LOCKFILE_NAME = 'prpm.lock';
29
- const LOCKFILE_VERSION = 1;
30
- /**
31
- * Read lock file from current directory
32
- */
33
- async function readLockfile(cwd = process.cwd()) {
34
- try {
35
- const lockfilePath = (0, path_1.join)(cwd, LOCKFILE_NAME);
36
- const content = await fs_1.promises.readFile(lockfilePath, 'utf-8');
37
- return JSON.parse(content);
38
- }
39
- catch (error) {
40
- if (error.code === 'ENOENT') {
41
- return null; // Lock file doesn't exist
42
- }
43
- throw new Error(`Failed to read lock file: ${error}`);
44
- }
45
- }
46
- /**
47
- * Write lock file to current directory
48
- */
49
- async function writeLockfile(lockfile, cwd = process.cwd()) {
50
- try {
51
- const lockfilePath = (0, path_1.join)(cwd, LOCKFILE_NAME);
52
- const content = JSON.stringify(lockfile, null, 2);
53
- await fs_1.promises.writeFile(lockfilePath, content, 'utf-8');
54
- }
55
- catch (error) {
56
- throw new Error(`Failed to write lock file: ${error}`);
57
- }
58
- }
59
- /**
60
- * Create new lock file
61
- */
62
- function createLockfile() {
63
- return {
64
- version: '1.0.0',
65
- lockfileVersion: LOCKFILE_VERSION,
66
- packages: {},
67
- generated: new Date().toISOString(),
68
- };
69
- }
70
- /**
71
- * Add package to lock file
72
- */
73
- function addToLockfile(lockfile, packageId, packageInfo) {
74
- lockfile.packages[packageId] = {
75
- version: packageInfo.version,
76
- resolved: packageInfo.tarballUrl,
77
- integrity: '', // Will be set after download
78
- dependencies: packageInfo.dependencies,
79
- format: packageInfo.format,
80
- subtype: packageInfo.subtype,
81
- installedPath: packageInfo.installedPath,
82
- fromCollection: packageInfo.fromCollection,
83
- hookMetadata: packageInfo.hookMetadata,
84
- };
85
- lockfile.generated = new Date().toISOString();
86
- }
87
- /**
88
- * Update package integrity hash after download
89
- */
90
- function setPackageIntegrity(lockfile, packageId, tarballBuffer) {
91
- if (!lockfile.packages[packageId]) {
92
- throw new Error(`Package ${packageId} not found in lock file`);
93
- }
94
- const hash = (0, crypto_1.createHash)('sha256').update(tarballBuffer).digest('hex');
95
- lockfile.packages[packageId].integrity = `sha256-${hash}`;
96
- }
97
- /**
98
- * Verify package integrity
99
- */
100
- function verifyPackageIntegrity(lockfile, packageId, tarballBuffer) {
101
- const pkg = lockfile.packages[packageId];
102
- if (!pkg || !pkg.integrity) {
103
- return false;
104
- }
105
- const hash = (0, crypto_1.createHash)('sha256').update(tarballBuffer).digest('hex');
106
- const expectedHash = pkg.integrity.replace('sha256-', '');
107
- return hash === expectedHash;
108
- }
109
- /**
110
- * Get locked version for a package
111
- */
112
- function getLockedVersion(lockfile, packageId) {
113
- if (!lockfile || !lockfile.packages[packageId]) {
114
- return null;
115
- }
116
- return lockfile.packages[packageId].version;
117
- }
118
- /**
119
- * Check if lock file is out of sync with dependencies
120
- */
121
- function isLockfileOutOfSync(lockfile, requiredPackages) {
122
- if (!lockfile) {
123
- return true;
124
- }
125
- // Check if all required packages are in lock file
126
- for (const [pkgId, version] of Object.entries(requiredPackages)) {
127
- const locked = lockfile.packages[pkgId];
128
- if (!locked || locked.version !== version) {
129
- return true;
130
- }
131
- }
132
- return false;
133
- }
134
- /**
135
- * Merge lock files (for conflict resolution)
136
- */
137
- function mergeLockfiles(base, incoming) {
138
- const merged = createLockfile();
139
- // Merge packages from both lock files
140
- const allPackages = new Set([
141
- ...Object.keys(base.packages),
142
- ...Object.keys(incoming.packages),
143
- ]);
144
- for (const pkgId of allPackages) {
145
- const basePkg = base.packages[pkgId];
146
- const incomingPkg = incoming.packages[pkgId];
147
- if (!basePkg) {
148
- merged.packages[pkgId] = incomingPkg;
149
- }
150
- else if (!incomingPkg) {
151
- merged.packages[pkgId] = basePkg;
152
- }
153
- else {
154
- // Both exist - prefer newer version
155
- const baseVersion = basePkg.version;
156
- const incomingVersion = incomingPkg.version;
157
- merged.packages[pkgId] = compareVersions(baseVersion, incomingVersion) >= 0
158
- ? basePkg
159
- : incomingPkg;
160
- }
161
- }
162
- return merged;
163
- }
164
- /**
165
- * Simple semver comparison (returns 1 if a > b, -1 if a < b, 0 if equal)
166
- */
167
- function compareVersions(a, b) {
168
- const aParts = a.split('.').map(Number);
169
- const bParts = b.split('.').map(Number);
170
- for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
171
- const aVal = aParts[i] || 0;
172
- const bVal = bParts[i] || 0;
173
- if (aVal > bVal)
174
- return 1;
175
- if (aVal < bVal)
176
- return -1;
177
- }
178
- return 0;
179
- }
180
- /**
181
- * Prune unused packages from lock file
182
- */
183
- function pruneLockfile(lockfile, requiredPackages) {
184
- const pruned = { ...lockfile };
185
- pruned.packages = {};
186
- for (const pkgId of requiredPackages) {
187
- if (lockfile.packages[pkgId]) {
188
- pruned.packages[pkgId] = lockfile.packages[pkgId];
189
- }
190
- }
191
- pruned.generated = new Date().toISOString();
192
- return pruned;
193
- }
194
- /**
195
- * Add package to lock file (convenience wrapper)
196
- */
197
- async function addPackage(packageInfo) {
198
- const lockfile = (await readLockfile()) || createLockfile();
199
- addToLockfile(lockfile, packageInfo.id, {
200
- version: packageInfo.version,
201
- tarballUrl: packageInfo.tarballUrl,
202
- dependencies: packageInfo.dependencies,
203
- format: packageInfo.format,
204
- subtype: packageInfo.subtype,
205
- installedPath: packageInfo.installedPath,
206
- });
207
- await writeLockfile(lockfile);
208
- }
209
- /**
210
- * Remove package from lock file
211
- */
212
- async function removePackage(packageId) {
213
- const lockfile = await readLockfile();
214
- if (!lockfile || !lockfile.packages[packageId]) {
215
- return null;
216
- }
217
- const removed = lockfile.packages[packageId];
218
- delete lockfile.packages[packageId];
219
- lockfile.generated = new Date().toISOString();
220
- await writeLockfile(lockfile);
221
- return removed;
222
- }
223
- /**
224
- * List all packages in lock file
225
- */
226
- async function listPackages() {
227
- const lockfile = await readLockfile();
228
- if (!lockfile) {
229
- return [];
230
- }
231
- return Object.entries(lockfile.packages).map(([id, pkg]) => ({
232
- id,
233
- ...pkg,
234
- }));
235
- }
236
- /**
237
- * Get a specific package from lock file
238
- */
239
- async function getPackage(packageId) {
240
- const lockfile = await readLockfile();
241
- if (!lockfile || !lockfile.packages[packageId]) {
242
- return null;
243
- }
244
- return lockfile.packages[packageId];
245
- }
246
- /**
247
- * Add or update collection in lock file
248
- */
249
- function addCollectionToLockfile(lockfile, collectionKey, collectionInfo) {
250
- if (!lockfile.collections) {
251
- lockfile.collections = {};
252
- }
253
- lockfile.collections[collectionKey] = {
254
- scope: collectionInfo.scope,
255
- name_slug: collectionInfo.name_slug,
256
- version: collectionInfo.version,
257
- installedAt: new Date().toISOString(),
258
- packages: collectionInfo.packages,
259
- };
260
- lockfile.generated = new Date().toISOString();
261
- }
262
- /**
263
- * Get collection from lock file
264
- */
265
- function getCollectionFromLockfile(lockfile, collectionKey) {
266
- if (!lockfile || !lockfile.collections) {
267
- return null;
268
- }
269
- return lockfile.collections[collectionKey] || null;
270
- }
271
- /**
272
- * Remove collection from lock file
273
- */
274
- function removeCollectionFromLockfile(lockfile, collectionKey) {
275
- if (!lockfile.collections) {
276
- return;
277
- }
278
- delete lockfile.collections[collectionKey];
279
- lockfile.generated = new Date().toISOString();
280
- }
281
- /**
282
- * List all collections in lock file
283
- */
284
- function listCollectionsFromLockfile(lockfile) {
285
- if (!lockfile || !lockfile.collections) {
286
- return [];
287
- }
288
- return Object.entries(lockfile.collections).map(([key, collection]) => ({
289
- key,
290
- ...collection,
291
- }));
292
- }