prpm 0.1.16 → 0.2.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/__tests__/e2e/test-helpers.js +3 -1
- package/dist/commands/install.js +53 -13
- package/dist/commands/list.js +10 -1
- package/dist/commands/login.js +17 -1
- package/dist/commands/playground.js +67 -0
- package/dist/commands/starred.js +147 -0
- package/dist/core/filesystem.js +5 -1
- package/dist/core/prompts.js +62 -0
- package/dist/core/telemetry.js +46 -0
- package/dist/core/user-config.js +2 -0
- package/dist/index.js +2 -0
- package/dist/types.js +3 -30
- package/package.json +5 -3
|
@@ -43,9 +43,11 @@ async function createMockPackage(testDir, name, type = 'cursor', version = '1.0.
|
|
|
43
43
|
name,
|
|
44
44
|
version,
|
|
45
45
|
description: `Test package ${name}`,
|
|
46
|
-
type,
|
|
46
|
+
format: type,
|
|
47
|
+
subtype: 'rule',
|
|
47
48
|
author: 'test-author',
|
|
48
49
|
tags: ['test', type],
|
|
50
|
+
files: ['prpm.json', '.cursorrules'],
|
|
49
51
|
};
|
|
50
52
|
const manifestPath = (0, path_1.join)(testDir, 'prpm.json');
|
|
51
53
|
await (0, promises_1.writeFile)(manifestPath, JSON.stringify(manifest, null, 2));
|
package/dist/commands/install.js
CHANGED
|
@@ -35,6 +35,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
return result;
|
|
36
36
|
};
|
|
37
37
|
})();
|
|
38
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
+
};
|
|
38
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
42
|
exports.handleInstall = handleInstall;
|
|
40
43
|
exports.installFromLockfile = installFromLockfile;
|
|
@@ -46,6 +49,8 @@ const filesystem_1 = require("../core/filesystem");
|
|
|
46
49
|
const telemetry_1 = require("../core/telemetry");
|
|
47
50
|
const tar = __importStar(require("tar"));
|
|
48
51
|
const errors_1 = require("../core/errors");
|
|
52
|
+
const prompts_1 = require("../core/prompts");
|
|
53
|
+
const path_1 = __importDefault(require("path"));
|
|
49
54
|
const lockfile_1 = require("../core/lockfile");
|
|
50
55
|
const cursor_config_1 = require("../core/cursor-config");
|
|
51
56
|
const claude_config_1 = require("../core/claude-config");
|
|
@@ -266,8 +271,7 @@ async function handleInstall(packageSpec, options) {
|
|
|
266
271
|
// Special handling for Claude packages: default to CLAUDE.md if it doesn't exist
|
|
267
272
|
// BUT only for packages that are generic rules (not skills, agents, or commands)
|
|
268
273
|
if (!options.as && pkg.format === 'claude' && pkg.subtype === 'rule') {
|
|
269
|
-
const
|
|
270
|
-
const claudeMdExists = await fileExists('CLAUDE.md');
|
|
274
|
+
const claudeMdExists = await (0, filesystem_1.fileExists)('CLAUDE.md');
|
|
271
275
|
if (!claudeMdExists) {
|
|
272
276
|
// CLAUDE.md doesn't exist, install as CLAUDE.md (recommended format for Claude Code)
|
|
273
277
|
format = 'claude-md';
|
|
@@ -309,6 +313,10 @@ async function handleInstall(packageSpec, options) {
|
|
|
309
313
|
const effectiveSubtype = options.subtype || pkg.subtype;
|
|
310
314
|
// Extract all files from tarball
|
|
311
315
|
const extractedFiles = await extractTarball(tarball, packageId);
|
|
316
|
+
const locationOverride = options.location?.trim();
|
|
317
|
+
if (locationOverride && effectiveFormat !== 'agents.md') {
|
|
318
|
+
console.log(` ⚠️ --location option currently only applies to Agents.md installs. Ignoring for ${effectiveFormat}.`);
|
|
319
|
+
}
|
|
312
320
|
// Track where files were saved for user feedback
|
|
313
321
|
let destPath;
|
|
314
322
|
let fileCount = 0;
|
|
@@ -333,7 +341,7 @@ async function handleInstall(packageSpec, options) {
|
|
|
333
341
|
const fileExtension = (effectiveFormat === 'cursor' && format === 'cursor') ? 'mdc' : 'md';
|
|
334
342
|
const packageName = (0, filesystem_1.stripAuthorNamespace)(packageId);
|
|
335
343
|
// For Claude skills, use SKILL.md filename in the package directory
|
|
336
|
-
// For agents.md,
|
|
344
|
+
// For agents.md, always install as AGENTS.md in the project root
|
|
337
345
|
// For Copilot, use official naming conventions
|
|
338
346
|
// For other formats, use package name as filename
|
|
339
347
|
if (effectiveFormat === 'claude' && effectiveSubtype === 'skill') {
|
|
@@ -344,7 +352,26 @@ async function handleInstall(packageSpec, options) {
|
|
|
344
352
|
destPath = `${destDir}/settings.json`;
|
|
345
353
|
}
|
|
346
354
|
else if (effectiveFormat === 'agents.md') {
|
|
347
|
-
|
|
355
|
+
let targetPath = 'AGENTS.md';
|
|
356
|
+
if (locationOverride) {
|
|
357
|
+
targetPath = path_1.default.join(locationOverride, 'AGENTS.override.md');
|
|
358
|
+
console.log(` 📁 Installing Agents.md package to custom location: ${targetPath}`);
|
|
359
|
+
}
|
|
360
|
+
destPath = targetPath;
|
|
361
|
+
if (await (0, filesystem_1.fileExists)(destPath)) {
|
|
362
|
+
if (options.force) {
|
|
363
|
+
console.log(` ⚠️ ${destPath} already exists - overwriting (forced).`);
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
console.log(` ⚠️ ${destPath} already exists.`);
|
|
367
|
+
const overwrite = await (0, prompts_1.promptYesNo)(` Overwrite existing ${destPath}? (y/N): `, ` ⚠️ Non-interactive terminal detected. Remove or rename ${destPath} to continue.`);
|
|
368
|
+
if (!overwrite) {
|
|
369
|
+
console.log(` 🚫 Skipping install to avoid overwriting ${destPath}`);
|
|
370
|
+
success = true;
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
348
375
|
}
|
|
349
376
|
else if (effectiveFormat === 'copilot') {
|
|
350
377
|
// Official GitHub Copilot naming conventions
|
|
@@ -386,7 +413,6 @@ async function handleInstall(packageSpec, options) {
|
|
|
386
413
|
// Special handling for Claude hooks - merge into settings.json
|
|
387
414
|
if (effectiveFormat === 'claude' && effectiveSubtype === 'hook') {
|
|
388
415
|
const { readFile } = await Promise.resolve().then(() => __importStar(require('fs/promises')));
|
|
389
|
-
const { fileExists } = await Promise.resolve().then(() => __importStar(require('../core/filesystem')));
|
|
390
416
|
// Parse the hook configuration from the downloaded file
|
|
391
417
|
let hookConfig;
|
|
392
418
|
try {
|
|
@@ -399,7 +425,7 @@ async function handleInstall(packageSpec, options) {
|
|
|
399
425
|
const hookId = `${packageId}@${actualVersion || version}`;
|
|
400
426
|
// Read existing settings.json if it exists
|
|
401
427
|
let existingSettings = { hooks: {} };
|
|
402
|
-
if (await fileExists(destPath)) {
|
|
428
|
+
if (await (0, filesystem_1.fileExists)(destPath)) {
|
|
403
429
|
try {
|
|
404
430
|
const existingContent = await readFile(destPath, 'utf-8');
|
|
405
431
|
existingSettings = JSON.parse(existingContent);
|
|
@@ -606,7 +632,6 @@ async function extractTarball(tarball, packageId) {
|
|
|
606
632
|
const zlib = await Promise.resolve().then(() => __importStar(require('zlib')));
|
|
607
633
|
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
608
634
|
const os = await Promise.resolve().then(() => __importStar(require('os')));
|
|
609
|
-
const path = await Promise.resolve().then(() => __importStar(require('path')));
|
|
610
635
|
return new Promise((resolve, reject) => {
|
|
611
636
|
// Decompress gzip first
|
|
612
637
|
zlib.gunzip(tarball, async (err, result) => {
|
|
@@ -626,10 +651,10 @@ async function extractTarball(tarball, packageId) {
|
|
|
626
651
|
return;
|
|
627
652
|
}
|
|
628
653
|
// Create temp directory for extraction
|
|
629
|
-
const tmpDir = await fs.promises.mkdtemp(
|
|
654
|
+
const tmpDir = await fs.promises.mkdtemp(path_1.default.join(os.tmpdir(), 'prpm-'));
|
|
630
655
|
try {
|
|
631
656
|
// Write tar data to temp file
|
|
632
|
-
const tarPath =
|
|
657
|
+
const tarPath = path_1.default.join(tmpDir, 'package.tar');
|
|
633
658
|
await fs.promises.writeFile(tarPath, result);
|
|
634
659
|
// Extract using tar library
|
|
635
660
|
await tar.extract({
|
|
@@ -642,9 +667,9 @@ async function extractTarball(tarball, packageId) {
|
|
|
642
667
|
const excludeFiles = ['package.tar', 'prpm.json', 'README.md', 'LICENSE', 'LICENSE.txt', 'LICENSE.md'];
|
|
643
668
|
for (const entry of extractedFiles) {
|
|
644
669
|
if (entry.isFile() && !excludeFiles.includes(entry.name)) {
|
|
645
|
-
const filePath =
|
|
670
|
+
const filePath = path_1.default.join(entry.path || tmpDir, entry.name);
|
|
646
671
|
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
647
|
-
const relativePath =
|
|
672
|
+
const relativePath = path_1.default.relative(tmpDir, filePath);
|
|
648
673
|
files.push({
|
|
649
674
|
name: relativePath,
|
|
650
675
|
content
|
|
@@ -716,12 +741,24 @@ async function installFromLockfile(options) {
|
|
|
716
741
|
? packageId.substring(0, packageId.lastIndexOf('@'))
|
|
717
742
|
: packageId;
|
|
718
743
|
console.log(` Installing ${packageId}...`);
|
|
744
|
+
let locationOverride = options.location;
|
|
745
|
+
if (!locationOverride && lockEntry.format === 'agents.md' && lockEntry.installedPath) {
|
|
746
|
+
const baseName = path_1.default.basename(lockEntry.installedPath);
|
|
747
|
+
if (baseName === 'AGENTS.override.md') {
|
|
748
|
+
locationOverride = path_1.default.dirname(lockEntry.installedPath);
|
|
749
|
+
}
|
|
750
|
+
else if (baseName !== 'AGENTS.md') {
|
|
751
|
+
// If the lockfile contains a non-standard filename, honor its directory
|
|
752
|
+
locationOverride = path_1.default.dirname(lockEntry.installedPath);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
719
755
|
await handleInstall(packageSpec, {
|
|
720
756
|
version: lockEntry.version,
|
|
721
757
|
as: options.as || lockEntry.format,
|
|
722
758
|
subtype: options.subtype || lockEntry.subtype,
|
|
723
759
|
frozenLockfile: options.frozenLockfile,
|
|
724
760
|
force: true, // Force reinstall when installing from lockfile
|
|
761
|
+
location: locationOverride,
|
|
725
762
|
});
|
|
726
763
|
successCount++;
|
|
727
764
|
}
|
|
@@ -761,6 +798,7 @@ function createInstallCommand() {
|
|
|
761
798
|
.option('--version <version>', 'Specific version to install')
|
|
762
799
|
.option('--as <format>', 'Convert and install in specific format (cursor, claude, continue, windsurf, copilot, kiro, agents.md, canonical)')
|
|
763
800
|
.option('--format <format>', 'Alias for --as')
|
|
801
|
+
.option('--location <path>', 'Custom location for installed files (currently supports Agents.md)')
|
|
764
802
|
.option('--subtype <subtype>', 'Specify subtype when converting (skill, agent, rule, etc.)')
|
|
765
803
|
.option('--frozen-lockfile', 'Fail if lock file needs to be updated (for CI)')
|
|
766
804
|
.action(async (packageSpec, options) => {
|
|
@@ -774,7 +812,8 @@ function createInstallCommand() {
|
|
|
774
812
|
await installFromLockfile({
|
|
775
813
|
as: convertTo,
|
|
776
814
|
subtype: options.subtype,
|
|
777
|
-
frozenLockfile: options.frozenLockfile
|
|
815
|
+
frozenLockfile: options.frozenLockfile,
|
|
816
|
+
location: options.location,
|
|
778
817
|
});
|
|
779
818
|
return;
|
|
780
819
|
}
|
|
@@ -782,7 +821,8 @@ function createInstallCommand() {
|
|
|
782
821
|
version: options.version,
|
|
783
822
|
as: convertTo,
|
|
784
823
|
subtype: options.subtype,
|
|
785
|
-
frozenLockfile: options.frozenLockfile
|
|
824
|
+
frozenLockfile: options.frozenLockfile,
|
|
825
|
+
location: options.location,
|
|
786
826
|
});
|
|
787
827
|
});
|
|
788
828
|
return command;
|
package/dist/commands/list.js
CHANGED
|
@@ -34,7 +34,7 @@ function getDestinationDir(type) {
|
|
|
34
34
|
case 'windsurf':
|
|
35
35
|
return '.windsurf/rules';
|
|
36
36
|
case 'agents.md':
|
|
37
|
-
return '.
|
|
37
|
+
return '.';
|
|
38
38
|
case 'generic':
|
|
39
39
|
return '.prompts';
|
|
40
40
|
case 'mcp':
|
|
@@ -56,6 +56,15 @@ function stripAuthorNamespace(packageId) {
|
|
|
56
56
|
async function findPackageLocation(id, format, subtype) {
|
|
57
57
|
if (!format)
|
|
58
58
|
return null;
|
|
59
|
+
if (format === 'agents.md') {
|
|
60
|
+
try {
|
|
61
|
+
await fs_1.promises.access('AGENTS.md');
|
|
62
|
+
return 'AGENTS.md';
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
59
68
|
const baseDir = getDestinationDir(format);
|
|
60
69
|
// Strip author namespace to get actual package name used in file system
|
|
61
70
|
const packageName = stripAuthorNamespace(id);
|
package/dist/commands/login.js
CHANGED
|
@@ -40,6 +40,7 @@ exports.handleLogin = handleLogin;
|
|
|
40
40
|
exports.createLoginCommand = createLoginCommand;
|
|
41
41
|
const commander_1 = require("commander");
|
|
42
42
|
const http_1 = require("http");
|
|
43
|
+
const jwt = __importStar(require("jsonwebtoken"));
|
|
43
44
|
const telemetry_1 = require("../core/telemetry");
|
|
44
45
|
const user_config_1 = require("../core/user-config");
|
|
45
46
|
const errors_1 = require("../core/errors");
|
|
@@ -257,11 +258,26 @@ async function handleLogin(options) {
|
|
|
257
258
|
// OAuth login
|
|
258
259
|
result = await loginWithOAuth(registryUrl);
|
|
259
260
|
}
|
|
260
|
-
//
|
|
261
|
+
// Extract user_id and email from JWT token
|
|
262
|
+
const decoded = jwt.decode(result.token);
|
|
263
|
+
if (!decoded) {
|
|
264
|
+
throw new Error('Failed to decode authentication token');
|
|
265
|
+
}
|
|
266
|
+
// Save token and user info to config
|
|
261
267
|
await (0, user_config_1.saveConfig)({
|
|
262
268
|
...config,
|
|
263
269
|
token: result.token,
|
|
264
270
|
username: result.username,
|
|
271
|
+
userId: decoded.user_id,
|
|
272
|
+
email: decoded.email,
|
|
273
|
+
});
|
|
274
|
+
// Identify user in PostHog with user properties
|
|
275
|
+
await telemetry_1.telemetry.identifyUser(decoded.user_id, {
|
|
276
|
+
username: result.username,
|
|
277
|
+
email: decoded.email,
|
|
278
|
+
cli_version: process.env.npm_package_version,
|
|
279
|
+
platform: process.platform,
|
|
280
|
+
first_login: new Date().toISOString(),
|
|
265
281
|
});
|
|
266
282
|
console.log('✅ Successfully logged in!\n');
|
|
267
283
|
console.log(` Username: ${result.username}`);
|
|
@@ -64,6 +64,67 @@ function prompt(rl, question) {
|
|
|
64
64
|
});
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Ask user for feedback on the result (subtle, non-intrusive)
|
|
69
|
+
*/
|
|
70
|
+
async function promptFeedback(sessionId) {
|
|
71
|
+
const rl = createReadline();
|
|
72
|
+
try {
|
|
73
|
+
console.log('\n💭 Was this result effective? (y/n, or press Enter to skip)');
|
|
74
|
+
const answer = await prompt(rl, ' ');
|
|
75
|
+
const normalized = answer.toLowerCase().trim();
|
|
76
|
+
if (normalized === 'y' || normalized === 'yes') {
|
|
77
|
+
// Optional comment
|
|
78
|
+
console.log('\n Any comments? (optional, press Enter to skip)');
|
|
79
|
+
const comment = await prompt(rl, ' ');
|
|
80
|
+
await submitFeedback(sessionId, true, comment.trim() || undefined);
|
|
81
|
+
if (comment.trim()) {
|
|
82
|
+
console.log(' ✓ Feedback submitted with comment\n');
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
console.log(' ✓ Feedback submitted\n');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (normalized === 'n' || normalized === 'no') {
|
|
89
|
+
// Optional comment
|
|
90
|
+
console.log('\n Any comments? (optional, press Enter to skip)');
|
|
91
|
+
const comment = await prompt(rl, ' ');
|
|
92
|
+
await submitFeedback(sessionId, false, comment.trim() || undefined);
|
|
93
|
+
if (comment.trim()) {
|
|
94
|
+
console.log(' ✓ Feedback submitted with comment\n');
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
console.log(' ✓ Feedback submitted\n');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// If empty or anything else, silently skip
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
// Silently fail - feedback is optional
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
rl.close();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Submit feedback to the API
|
|
111
|
+
*/
|
|
112
|
+
async function submitFeedback(sessionId, isEffective, comment) {
|
|
113
|
+
try {
|
|
114
|
+
const response = await apiCall('/api/v1/playground/feedback', 'POST', {
|
|
115
|
+
session_id: sessionId,
|
|
116
|
+
is_effective: isEffective,
|
|
117
|
+
comment: comment || undefined,
|
|
118
|
+
});
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
// Silently fail - don't interrupt user flow
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
// Silently fail - feedback is optional
|
|
126
|
+
}
|
|
127
|
+
}
|
|
67
128
|
/**
|
|
68
129
|
* Make authenticated API call to registry
|
|
69
130
|
*/
|
|
@@ -203,6 +264,8 @@ async function runInteractive(packageName, options) {
|
|
|
203
264
|
sessionId = result.session_id;
|
|
204
265
|
turnCount++;
|
|
205
266
|
displayResponse(result, true);
|
|
267
|
+
// Prompt for feedback in interactive mode (subtle, can skip)
|
|
268
|
+
await promptFeedback(result.session_id);
|
|
206
269
|
}
|
|
207
270
|
catch (error) {
|
|
208
271
|
console.error(`\n❌ Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -389,12 +452,16 @@ async function runSingle(packageName, input, options) {
|
|
|
389
452
|
console.log(` Total tokens: ${resultWithPackage.tokens_used + resultWithoutPackage.tokens_used}`);
|
|
390
453
|
console.log(` Total credits: ${resultWithPackage.credits_spent + resultWithoutPackage.credits_spent}`);
|
|
391
454
|
console.log(` Credits remaining: ${resultWithoutPackage.credits_remaining}`);
|
|
455
|
+
// Prompt for feedback on the with-package result
|
|
456
|
+
await promptFeedback(resultWithPackage.session_id);
|
|
392
457
|
}
|
|
393
458
|
else {
|
|
394
459
|
// Single mode: run with package only
|
|
395
460
|
console.log('\n⏳ Processing...');
|
|
396
461
|
const result = await runPlayground(packageName, input, options);
|
|
397
462
|
displayResponse(result, true);
|
|
463
|
+
// Prompt for feedback
|
|
464
|
+
await promptFeedback(result.session_id);
|
|
398
465
|
}
|
|
399
466
|
console.log(`\n💡 Tips:`);
|
|
400
467
|
console.log(` - Use --interactive for multi-turn conversation`);
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Starred command implementation - List user's starred packages and collections
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.handleStarred = handleStarred;
|
|
7
|
+
exports.createStarredCommand = createStarredCommand;
|
|
8
|
+
const commander_1 = require("commander");
|
|
9
|
+
const registry_client_1 = require("@pr-pm/registry-client");
|
|
10
|
+
const user_config_1 = require("../core/user-config");
|
|
11
|
+
const telemetry_1 = require("../core/telemetry");
|
|
12
|
+
const errors_1 = require("../core/errors");
|
|
13
|
+
async function handleStarred(options) {
|
|
14
|
+
let success = false;
|
|
15
|
+
try {
|
|
16
|
+
const config = await (0, user_config_1.getConfig)();
|
|
17
|
+
const token = config.token;
|
|
18
|
+
if (!token) {
|
|
19
|
+
throw new errors_1.CLIError('You must be logged in to view starred items. Run `prpm login` first.');
|
|
20
|
+
}
|
|
21
|
+
const registryUrl = config.registryUrl || process.env.PRPM_REGISTRY_URL || 'https://registry.prpm.dev';
|
|
22
|
+
const client = (0, registry_client_1.getRegistryClient)({ registryUrl, token });
|
|
23
|
+
// Determine what to show (both by default)
|
|
24
|
+
const showPackages = options.packages || (!options.packages && !options.collections);
|
|
25
|
+
const showCollections = options.collections || (!options.packages && !options.collections);
|
|
26
|
+
const limit = options.limit || 100;
|
|
27
|
+
// Fetch starred packages
|
|
28
|
+
let packages = [];
|
|
29
|
+
if (showPackages) {
|
|
30
|
+
try {
|
|
31
|
+
const response = await fetch(`${registryUrl}/api/v1/packages/starred?limit=${limit}`, {
|
|
32
|
+
headers: {
|
|
33
|
+
Authorization: `Bearer ${token}`,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
throw new Error(`Failed to fetch starred packages: ${response.statusText}`);
|
|
38
|
+
}
|
|
39
|
+
const data = await response.json();
|
|
40
|
+
packages = data.packages || [];
|
|
41
|
+
// Filter by format if specified
|
|
42
|
+
if (options.format) {
|
|
43
|
+
packages = packages.filter((pkg) => pkg.format === options.format);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.error('Failed to fetch starred packages:', error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Fetch starred collections
|
|
51
|
+
let collections = [];
|
|
52
|
+
if (showCollections) {
|
|
53
|
+
try {
|
|
54
|
+
const response = await fetch(`${registryUrl}/api/v1/collections/starred?limit=${limit}`, {
|
|
55
|
+
headers: {
|
|
56
|
+
Authorization: `Bearer ${token}`,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
throw new Error(`Failed to fetch starred collections: ${response.statusText}`);
|
|
61
|
+
}
|
|
62
|
+
const data = await response.json();
|
|
63
|
+
collections = data.collections || [];
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
console.error('Failed to fetch starred collections:', error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Display results
|
|
70
|
+
if (packages.length === 0 && collections.length === 0) {
|
|
71
|
+
console.log('\nNo starred items found.');
|
|
72
|
+
if (options.format) {
|
|
73
|
+
console.log(`Try removing the --format filter to see all starred packages.`);
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
console.log('');
|
|
78
|
+
// Display packages
|
|
79
|
+
if (packages.length > 0) {
|
|
80
|
+
console.log(`📦 Starred Packages (${packages.length}):`);
|
|
81
|
+
console.log('');
|
|
82
|
+
for (const pkg of packages) {
|
|
83
|
+
const formatBadge = `[${pkg.format || 'generic'}]`.padEnd(12);
|
|
84
|
+
const stars = `⭐ ${pkg.stars || 0}`.padEnd(8);
|
|
85
|
+
const downloads = `⬇️ ${(pkg.total_downloads || 0).toLocaleString()}`;
|
|
86
|
+
console.log(` ${formatBadge} ${pkg.name}`);
|
|
87
|
+
console.log(` ${stars} ${downloads}`);
|
|
88
|
+
if (pkg.description) {
|
|
89
|
+
const desc = pkg.description.length > 80
|
|
90
|
+
? pkg.description.substring(0, 77) + '...'
|
|
91
|
+
: pkg.description;
|
|
92
|
+
console.log(` ${desc}`);
|
|
93
|
+
}
|
|
94
|
+
console.log('');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Display collections
|
|
98
|
+
if (collections.length > 0) {
|
|
99
|
+
console.log(`📚 Starred Collections (${collections.length}):`);
|
|
100
|
+
console.log('');
|
|
101
|
+
for (const collection of collections) {
|
|
102
|
+
const stars = `⭐ ${collection.stars || 0}`.padEnd(8);
|
|
103
|
+
const packages = `📦 ${collection.package_count || 0} packages`;
|
|
104
|
+
console.log(` ${collection.scope}/${collection.name_slug}`);
|
|
105
|
+
console.log(` ${stars} ${packages}`);
|
|
106
|
+
if (collection.description) {
|
|
107
|
+
const desc = collection.description.length > 80
|
|
108
|
+
? collection.description.substring(0, 77) + '...'
|
|
109
|
+
: collection.description;
|
|
110
|
+
console.log(` ${desc}`);
|
|
111
|
+
}
|
|
112
|
+
console.log('');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
console.log(`\nTotal: ${packages.length + collections.length} starred items`);
|
|
116
|
+
console.log('');
|
|
117
|
+
success = true;
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
if (error instanceof errors_1.CLIError) {
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
throw new errors_1.CLIError(`Failed to fetch starred items: ${error instanceof Error ? error.message : String(error)}`);
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
await telemetry_1.telemetry.track({
|
|
127
|
+
command: 'starred',
|
|
128
|
+
success,
|
|
129
|
+
data: {
|
|
130
|
+
showPackages: options.packages,
|
|
131
|
+
showCollections: options.collections,
|
|
132
|
+
format: options.format,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function createStarredCommand() {
|
|
138
|
+
const command = new commander_1.Command('starred');
|
|
139
|
+
command
|
|
140
|
+
.description('List your starred packages and collections')
|
|
141
|
+
.option('--packages', 'Show only starred packages')
|
|
142
|
+
.option('--collections', 'Show only starred collections')
|
|
143
|
+
.option('--format <format>', 'Filter packages by format (cursor, claude, continue, windsurf, etc.)')
|
|
144
|
+
.option('--limit <number>', 'Maximum number of items to fetch (default: 100)', (val) => parseInt(val, 10))
|
|
145
|
+
.action(handleStarred);
|
|
146
|
+
return command;
|
|
147
|
+
}
|
package/dist/core/filesystem.js
CHANGED
|
@@ -73,7 +73,7 @@ function getDestinationDir(format, subtype, name) {
|
|
|
73
73
|
return '.kiro/hooks';
|
|
74
74
|
return '.kiro/steering';
|
|
75
75
|
case 'agents.md':
|
|
76
|
-
return '.
|
|
76
|
+
return '.';
|
|
77
77
|
case 'generic':
|
|
78
78
|
return '.prompts';
|
|
79
79
|
case 'mcp':
|
|
@@ -153,6 +153,10 @@ async function directoryExists(dirPath) {
|
|
|
153
153
|
* Returns the format if a matching directory is found, or null if none found
|
|
154
154
|
*/
|
|
155
155
|
async function autoDetectFormat() {
|
|
156
|
+
// Agents.md installs live at project root
|
|
157
|
+
if (await fileExists('AGENTS.md')) {
|
|
158
|
+
return 'agents.md';
|
|
159
|
+
}
|
|
156
160
|
const formatDirs = [
|
|
157
161
|
{ format: 'cursor', dir: '.cursor' },
|
|
158
162
|
{ format: 'claude', dir: '.claude' },
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.promptYesNo = promptYesNo;
|
|
37
|
+
const readline = __importStar(require("readline/promises"));
|
|
38
|
+
/**
|
|
39
|
+
* Prompt the user for a yes/no confirmation.
|
|
40
|
+
* Returns false automatically when running in a non-interactive environment.
|
|
41
|
+
*/
|
|
42
|
+
async function promptYesNo(question, nonInteractiveWarning) {
|
|
43
|
+
const stdinIsTTY = process.stdin.isTTY;
|
|
44
|
+
const stdoutIsTTY = process.stdout.isTTY;
|
|
45
|
+
if (stdinIsTTY === false || stdoutIsTTY === false) {
|
|
46
|
+
if (nonInteractiveWarning) {
|
|
47
|
+
console.log(nonInteractiveWarning);
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const rl = readline.createInterface({
|
|
52
|
+
input: process.stdin,
|
|
53
|
+
output: process.stdout,
|
|
54
|
+
});
|
|
55
|
+
try {
|
|
56
|
+
const answer = (await rl.question(question)).trim().toLowerCase();
|
|
57
|
+
return answer === 'y' || answer === 'yes';
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
rl.close();
|
|
61
|
+
}
|
|
62
|
+
}
|
package/dist/core/telemetry.js
CHANGED
|
@@ -67,6 +67,21 @@ class Telemetry {
|
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Load userId from user config and update telemetry config
|
|
72
|
+
*/
|
|
73
|
+
async loadUserIdFromConfig() {
|
|
74
|
+
try {
|
|
75
|
+
const userConfig = await (0, user_config_1.getConfig)();
|
|
76
|
+
if (userConfig.userId && userConfig.userId !== this.config.userId) {
|
|
77
|
+
this.config.userId = userConfig.userId;
|
|
78
|
+
await this.saveConfig();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Silently fail - telemetry shouldn't break the CLI
|
|
83
|
+
}
|
|
84
|
+
}
|
|
70
85
|
generateSessionId() {
|
|
71
86
|
return Math.random().toString(36).substring(2, 15) +
|
|
72
87
|
Math.random().toString(36).substring(2, 15);
|
|
@@ -88,6 +103,8 @@ class Telemetry {
|
|
|
88
103
|
return;
|
|
89
104
|
if (!this.config.enabled)
|
|
90
105
|
return;
|
|
106
|
+
// Load userId from user config before tracking
|
|
107
|
+
await this.loadUserIdFromConfig();
|
|
91
108
|
const fullEvent = {
|
|
92
109
|
...event,
|
|
93
110
|
timestamp: new Date().toISOString(),
|
|
@@ -170,6 +187,35 @@ class Telemetry {
|
|
|
170
187
|
}
|
|
171
188
|
}
|
|
172
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Identify user in PostHog with user properties
|
|
192
|
+
* Called after successful login to set user attributes
|
|
193
|
+
*/
|
|
194
|
+
async identifyUser(userId, traits) {
|
|
195
|
+
if (!this.posthog || !this.config.enabled)
|
|
196
|
+
return;
|
|
197
|
+
try {
|
|
198
|
+
// Update local config with userId
|
|
199
|
+
this.config.userId = userId;
|
|
200
|
+
await this.saveConfig();
|
|
201
|
+
// Send $identify event to PostHog
|
|
202
|
+
this.posthog.identify({
|
|
203
|
+
distinctId: userId,
|
|
204
|
+
properties: traits,
|
|
205
|
+
});
|
|
206
|
+
// Also capture the $identify event explicitly
|
|
207
|
+
this.posthog.capture({
|
|
208
|
+
distinctId: userId,
|
|
209
|
+
event: '$identify',
|
|
210
|
+
properties: traits,
|
|
211
|
+
});
|
|
212
|
+
// Flush immediately to ensure identification happens
|
|
213
|
+
await this.posthog.flush();
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
// Silently fail - telemetry shouldn't break the CLI
|
|
217
|
+
}
|
|
218
|
+
}
|
|
173
219
|
// Send to PostHog
|
|
174
220
|
async sendToPostHog(event) {
|
|
175
221
|
if (!this.posthog)
|
package/dist/core/user-config.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@ const playground_1 = require("./commands/playground");
|
|
|
31
31
|
const credits_1 = require("./commands/credits");
|
|
32
32
|
const subscribe_1 = require("./commands/subscribe");
|
|
33
33
|
const buy_credits_1 = require("./commands/buy-credits");
|
|
34
|
+
const starred_1 = require("./commands/starred");
|
|
34
35
|
const telemetry_2 = require("./core/telemetry");
|
|
35
36
|
const errors_1 = require("./core/errors");
|
|
36
37
|
// Read version from package.json
|
|
@@ -62,6 +63,7 @@ program.addCommand((0, publish_1.createPublishCommand)());
|
|
|
62
63
|
program.addCommand((0, login_1.createLoginCommand)());
|
|
63
64
|
program.addCommand((0, whoami_1.createWhoamiCommand)());
|
|
64
65
|
program.addCommand((0, collections_1.createCollectionsCommand)());
|
|
66
|
+
program.addCommand((0, starred_1.createStarredCommand)());
|
|
65
67
|
program.addCommand((0, outdated_1.createOutdatedCommand)());
|
|
66
68
|
program.addCommand((0, update_1.createUpdateCommand)());
|
|
67
69
|
program.addCommand((0, upgrade_1.createUpgradeCommand)());
|
package/dist/types.js
CHANGED
|
@@ -4,33 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.SUBTYPES = exports.FORMATS = void 0;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*/
|
|
11
|
-
exports.FORMATS = [
|
|
12
|
-
'cursor',
|
|
13
|
-
'claude',
|
|
14
|
-
'continue',
|
|
15
|
-
'windsurf',
|
|
16
|
-
'copilot',
|
|
17
|
-
'kiro',
|
|
18
|
-
'agents.md',
|
|
19
|
-
'generic',
|
|
20
|
-
'mcp',
|
|
21
|
-
];
|
|
22
|
-
/**
|
|
23
|
-
* Available subtypes as a constant array
|
|
24
|
-
* Useful for CLI prompts, validation, etc.
|
|
25
|
-
*/
|
|
26
|
-
exports.SUBTYPES = [
|
|
27
|
-
'rule',
|
|
28
|
-
'agent',
|
|
29
|
-
'skill',
|
|
30
|
-
'slash-command',
|
|
31
|
-
'prompt',
|
|
32
|
-
'collection',
|
|
33
|
-
'chatmode',
|
|
34
|
-
'tool',
|
|
35
|
-
'hook',
|
|
36
|
-
];
|
|
7
|
+
var types_1 = require("@pr-pm/types");
|
|
8
|
+
Object.defineProperty(exports, "FORMATS", { enumerable: true, get: function () { return types_1.FORMATS; } });
|
|
9
|
+
Object.defineProperty(exports, "SUBTYPES", { enumerable: true, get: function () { return types_1.SUBTYPES; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prpm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Prompt Package Manager CLI - Install and manage prompt-based files",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -45,16 +45,18 @@
|
|
|
45
45
|
"license": "MIT",
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@octokit/rest": "^22.0.0",
|
|
48
|
-
"@pr-pm/registry-client": "^1.3.
|
|
49
|
-
"@pr-pm/types": "^0.2.
|
|
48
|
+
"@pr-pm/registry-client": "^1.3.16",
|
|
49
|
+
"@pr-pm/types": "^0.2.17",
|
|
50
50
|
"ajv": "^8.17.1",
|
|
51
51
|
"ajv-formats": "^3.0.1",
|
|
52
52
|
"commander": "^11.1.0",
|
|
53
|
+
"jsonwebtoken": "^9.0.2",
|
|
53
54
|
"posthog-node": "^5.10.0",
|
|
54
55
|
"tar": "^6.2.1"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|
|
57
58
|
"@types/jest": "^29.5.8",
|
|
59
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
58
60
|
"@types/node": "^20.10.0",
|
|
59
61
|
"@types/tar": "^6.1.13",
|
|
60
62
|
"jest": "^29.7.0",
|