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.
@@ -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));
@@ -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 { fileExists } = await Promise.resolve().then(() => __importStar(require('../core/filesystem')));
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, use package-name/AGENTS.md directory structure
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
- destPath = `${destDir}/${packageName}/AGENTS.md`;
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(path.join(os.tmpdir(), 'prpm-'));
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 = path.join(tmpDir, 'package.tar');
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 = path.join(entry.path || tmpDir, entry.name);
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 = path.relative(tmpDir, filePath);
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;
@@ -34,7 +34,7 @@ function getDestinationDir(type) {
34
34
  case 'windsurf':
35
35
  return '.windsurf/rules';
36
36
  case 'agents.md':
37
- return '.agents';
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);
@@ -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
- // Save token to config
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
+ }
@@ -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 '.agents';
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
+ }
@@ -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)
@@ -134,6 +134,8 @@ async function clearAuth() {
134
134
  const config = await getConfig();
135
135
  delete config.token;
136
136
  delete config.username;
137
+ delete config.userId;
138
+ delete config.email;
137
139
  await saveConfig(config);
138
140
  }
139
141
  /**
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
- * Available formats as a constant array
9
- * Useful for CLI prompts, validation, etc.
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.1.16",
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.14",
49
- "@pr-pm/types": "^0.2.15",
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",