prpm 0.1.12 → 0.1.14

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.
@@ -42,6 +42,8 @@ const commander_1 = require("commander");
42
42
  const user_config_1 = require("../core/user-config");
43
43
  const telemetry_1 = require("../core/telemetry");
44
44
  const readline = __importStar(require("readline"));
45
+ const fs = __importStar(require("fs"));
46
+ const path = __importStar(require("path"));
45
47
  const errors_1 = require("../core/errors");
46
48
  /**
47
49
  * Create a readline interface for user input
@@ -94,21 +96,20 @@ async function resolvePackageId(packageName) {
94
96
  if (uuidRegex.test(packageName)) {
95
97
  return packageName;
96
98
  }
97
- // Search for the package by name
99
+ // Look up package by name directly
98
100
  const config = await (0, user_config_1.getConfig)();
99
101
  const baseUrl = (config.registryUrl || "https://registry.prpm.dev").replace(/\/$/, '');
100
- const response = await fetch(`${baseUrl}/api/v1/search?q=${encodeURIComponent(packageName)}&limit=10`);
102
+ // URL encode the package name to handle scoped packages like @user/package
103
+ const encodedName = encodeURIComponent(packageName);
104
+ const response = await fetch(`${baseUrl}/api/v1/packages/${encodedName}`);
101
105
  if (!response.ok) {
102
- throw new Error(`Failed to search for package: ${response.statusText}`);
106
+ if (response.status === 404) {
107
+ throw new Error(`Package not found: ${packageName}`);
108
+ }
109
+ throw new Error(`Failed to fetch package: ${response.statusText}`);
103
110
  }
104
111
  const data = await response.json();
105
- // Find exact match
106
- const exactMatch = data.packages.find(pkg => pkg.name === packageName);
107
- if (exactMatch) {
108
- return exactMatch.id;
109
- }
110
- // If no exact match, throw error
111
- throw new Error(`Package not found: ${packageName}`);
112
+ return data.id;
112
113
  }
113
114
  /**
114
115
  * Execute a playground run
@@ -126,6 +127,28 @@ async function runPlayground(packageName, input, options, sessionId) {
126
127
  });
127
128
  return response.json();
128
129
  }
130
+ /**
131
+ * Execute a custom prompt playground run
132
+ */
133
+ async function runCustomPrompt(customPrompt, input, options, sessionId) {
134
+ const response = await apiCall('/api/v1/custom-prompt/run', 'POST', {
135
+ custom_prompt: customPrompt,
136
+ input,
137
+ model: options.model || 'sonnet',
138
+ session_id: sessionId,
139
+ });
140
+ return response.json();
141
+ }
142
+ /**
143
+ * Read custom prompt from file
144
+ */
145
+ function readPromptFile(filePath) {
146
+ const absolutePath = path.resolve(filePath);
147
+ if (!fs.existsSync(absolutePath)) {
148
+ throw new Error(`Prompt file not found: ${filePath}`);
149
+ }
150
+ return fs.readFileSync(absolutePath, 'utf-8');
151
+ }
129
152
  /**
130
153
  * Format and display playground response
131
154
  */
@@ -200,6 +223,139 @@ async function runInteractive(packageName, options) {
200
223
  rl.close();
201
224
  }
202
225
  }
226
+ /**
227
+ * Run interactive custom prompt session
228
+ */
229
+ async function runCustomInteractive(customPrompt, options) {
230
+ console.log('\nšŸŽ® Interactive Custom Prompt Mode');
231
+ console.log(` Model: ${options.model || 'sonnet'}`);
232
+ console.log(` Type 'exit' or 'quit' to end session\n`);
233
+ const rl = createReadline();
234
+ let sessionId;
235
+ let turnCount = 0;
236
+ try {
237
+ while (true) {
238
+ const input = await prompt(rl, `\nšŸ’¬ You: `);
239
+ if (input.trim().toLowerCase() === 'exit' || input.trim().toLowerCase() === 'quit') {
240
+ console.log('\nšŸ‘‹ Ending playground session. Goodbye!');
241
+ break;
242
+ }
243
+ if (!input.trim()) {
244
+ console.log('āŒ Please enter a message');
245
+ continue;
246
+ }
247
+ try {
248
+ console.log('\nā³ Processing...');
249
+ const result = await runCustomPrompt(customPrompt, input, options, sessionId);
250
+ // Store session ID for conversation continuity
251
+ sessionId = result.session_id;
252
+ turnCount++;
253
+ displayResponse(result, true);
254
+ }
255
+ catch (error) {
256
+ console.error(`\nāŒ Error: ${error instanceof Error ? error.message : String(error)}`);
257
+ if (error instanceof Error && error.message.includes('Insufficient credits')) {
258
+ console.log('\nšŸ’” Get more credits:');
259
+ console.log(' - Purchase credits: prpm buy-credits');
260
+ console.log(' - Subscribe to PRPM+: prpm subscribe');
261
+ console.log(' - Check balance: prpm credits');
262
+ break;
263
+ }
264
+ }
265
+ }
266
+ if (turnCount > 0) {
267
+ console.log(`\nšŸ“ Session summary: ${turnCount} turn(s)`);
268
+ }
269
+ }
270
+ finally {
271
+ rl.close();
272
+ }
273
+ }
274
+ /**
275
+ * Run baseline (no prompt) comparison
276
+ */
277
+ async function runBaseline(input, options) {
278
+ // Use a minimal package but with use_no_prompt flag to get raw model baseline
279
+ // We need any valid package ID because the API requires it, but the prompt will be ignored
280
+ // Let's use a well-known minimal package (we'll search for any package)
281
+ const config = await (0, user_config_1.getConfig)();
282
+ const baseUrl = (config.registryUrl || "https://registry.prpm.dev").replace(/\/$/, '');
283
+ // Get any package to use for baseline comparison (the prompt will be ignored anyway)
284
+ const searchResponse = await fetch(`${baseUrl}/api/v1/search?q=coding&limit=1`);
285
+ if (!searchResponse.ok) {
286
+ throw new Error('Failed to find a package for baseline comparison');
287
+ }
288
+ const searchData = await searchResponse.json();
289
+ if (!searchData.packages || searchData.packages.length === 0) {
290
+ throw new Error('No packages found for baseline comparison');
291
+ }
292
+ const dummyPackageId = searchData.packages[0].id;
293
+ // Call playground with use_no_prompt flag (this ignores the package prompt)
294
+ const response = await apiCall('/api/v1/playground/run', 'POST', {
295
+ package_id: dummyPackageId,
296
+ input,
297
+ model: options.model || 'sonnet',
298
+ use_no_prompt: true, // This makes it a raw baseline with no system prompt
299
+ });
300
+ return response.json();
301
+ }
302
+ /**
303
+ * Run single custom prompt query
304
+ */
305
+ async function runCustomSingle(customPrompt, input, options) {
306
+ console.log(`\nšŸŽ® Testing custom prompt`);
307
+ console.log(` Model: ${options.model || 'sonnet'}`);
308
+ console.log(` Credits: 2x normal cost (custom prompts)`);
309
+ if (options.compare) {
310
+ console.log(` Mode: Comparing custom prompt vs. no prompt (baseline)`);
311
+ }
312
+ try {
313
+ if (options.compare) {
314
+ // Comparison mode: run with custom prompt and without any prompt
315
+ console.log('\nā³ Processing comparison (2 requests)...');
316
+ // Run with custom prompt
317
+ const resultWithPrompt = await runCustomPrompt(customPrompt, input, options);
318
+ // Run baseline (no prompt at all)
319
+ const resultBaseline = await runBaseline(input, options);
320
+ // Display both results
321
+ console.log('\n' + '═'.repeat(60));
322
+ console.log('✨ WITH CUSTOM PROMPT');
323
+ console.log('═'.repeat(60));
324
+ displayResponse(resultWithPrompt, false);
325
+ console.log('\n' + '═'.repeat(60));
326
+ console.log('šŸ”µ WITHOUT PROMPT (BASELINE)');
327
+ console.log('═'.repeat(60));
328
+ displayResponse(resultBaseline, false);
329
+ // Combined stats
330
+ console.log(`\nšŸ“Š Combined Stats:`);
331
+ console.log(` Total tokens: ${resultWithPrompt.tokens_used + resultBaseline.tokens_used}`);
332
+ console.log(` Total credits: ${resultWithPrompt.credits_spent + resultBaseline.credits_spent}`);
333
+ console.log(` Credits remaining: ${resultBaseline.credits_remaining}`);
334
+ console.log(`\nšŸ’” Compare the responses to evaluate your custom prompt's effectiveness!`);
335
+ }
336
+ else {
337
+ // Single mode: run with custom prompt only
338
+ console.log('\nā³ Processing...');
339
+ const result = await runCustomPrompt(customPrompt, input, options);
340
+ displayResponse(result, true);
341
+ console.log(`\nšŸ’” Tips:`);
342
+ console.log(` - Use --interactive for multi-turn conversation`);
343
+ console.log(` - Use --compare to test against baseline (no prompt)`);
344
+ console.log(` - Use --prompt-file to iterate on a prompt file`);
345
+ console.log(` - Custom prompts cost 2x credits (no caching)`);
346
+ }
347
+ }
348
+ catch (error) {
349
+ console.error(`\nāŒ Error: ${error instanceof Error ? error.message : String(error)}`);
350
+ if (error instanceof Error && error.message.includes('Insufficient credits')) {
351
+ console.log('\nšŸ’” Get more credits:');
352
+ console.log(' - Purchase credits: prpm buy-credits');
353
+ console.log(' - Subscribe to PRPM+: prpm subscribe');
354
+ console.log(' - Check balance: prpm credits');
355
+ }
356
+ throw new errors_1.CLIError(`\nāŒ Error: ${error instanceof Error ? error.message : String(error)}`, 1);
357
+ }
358
+ }
203
359
  /**
204
360
  * Run single playground query
205
361
  */
@@ -259,10 +415,11 @@ async function runSingle(packageName, input, options) {
259
415
  /**
260
416
  * Handle the playground command
261
417
  */
262
- async function handlePlayground(packageName, input, options) {
418
+ async function handlePlayground(options) {
263
419
  const startTime = Date.now();
264
420
  let success = false;
265
421
  let error;
422
+ let customPromptMode = false;
266
423
  try {
267
424
  // Validate authentication
268
425
  const config = await (0, user_config_1.getConfig)();
@@ -272,14 +429,50 @@ async function handlePlayground(packageName, input, options) {
272
429
  console.log(' prpm login');
273
430
  throw new errors_1.CLIError('āŒ Authentication required', 1);
274
431
  }
275
- // Interactive mode or single query
276
- if (options.interactive || !input) {
277
- // Interactive mode
278
- await runInteractive(packageName, options);
432
+ // Check for custom prompt mode
433
+ if (options.custom || options.promptFile) {
434
+ customPromptMode = true;
435
+ // Get custom prompt from option or file
436
+ let customPrompt;
437
+ if (options.promptFile) {
438
+ console.log(`šŸ“„ Loading prompt from: ${options.promptFile}`);
439
+ customPrompt = readPromptFile(options.promptFile);
440
+ console.log(`āœ… Loaded ${customPrompt.length} characters\n`);
441
+ }
442
+ else {
443
+ customPrompt = options.custom;
444
+ }
445
+ // Validate custom prompt length
446
+ if (customPrompt.length < 10) {
447
+ throw new Error('Custom prompt too short (minimum 10 characters)');
448
+ }
449
+ if (customPrompt.length > 50000) {
450
+ throw new Error('Custom prompt too long (maximum 50000 characters)');
451
+ }
452
+ // Interactive mode or single query
453
+ if (options.interactive || !options.input) {
454
+ // Interactive mode with custom prompt
455
+ await runCustomInteractive(customPrompt, options);
456
+ }
457
+ else {
458
+ // Single query mode with custom prompt
459
+ await runCustomSingle(customPrompt, options.input, options);
460
+ }
279
461
  }
280
462
  else {
281
- // Single query mode
282
- await runSingle(packageName, input, options);
463
+ // Regular package-based playground
464
+ if (!options.package) {
465
+ throw new Error('Either --package or --custom/--prompt-file is required');
466
+ }
467
+ // Interactive mode or single query
468
+ if (options.interactive || !options.input) {
469
+ // Interactive mode
470
+ await runInteractive(options.package, options);
471
+ }
472
+ else {
473
+ // Single query mode
474
+ await runSingle(options.package, options.input, options);
475
+ }
283
476
  }
284
477
  success = true;
285
478
  }
@@ -295,10 +488,11 @@ async function handlePlayground(packageName, input, options) {
295
488
  error,
296
489
  duration: Date.now() - startTime,
297
490
  data: {
298
- packageName,
491
+ packageName: customPromptMode ? 'custom-prompt' : (options.package || 'unknown'),
299
492
  model: options.model || 'sonnet',
300
493
  compare: options.compare || false,
301
494
  interactive: options.interactive || false,
495
+ customPrompt: customPromptMode,
302
496
  },
303
497
  });
304
498
  await telemetry_1.telemetry.shutdown();
@@ -310,30 +504,49 @@ async function handlePlayground(packageName, input, options) {
310
504
  function createPlaygroundCommand() {
311
505
  const command = new commander_1.Command('playground');
312
506
  command
313
- .description('Test a package with AI models in the playground')
314
- .argument('<package>', 'Package name to test')
315
- .argument('[input]', 'Input text to send to the model (omit for interactive mode)')
507
+ .description('Test a package or custom prompt with AI models in the playground')
508
+ .option('-p, --package <name>', 'Package name to test')
509
+ .option('--input <text>', 'Input text to send to the model (omit for interactive mode)')
316
510
  .option('-m, --model <model>', 'AI model to use (sonnet, opus, gpt-4o, gpt-4o-mini, gpt-4-turbo)', 'sonnet')
317
511
  .option('-c, --compare', 'Compare against no prompt (test raw model baseline)', false)
318
512
  .option('-i, --interactive', 'Start interactive multi-turn conversation mode', false)
319
513
  .option('-v, --version <version>', 'Specific package version to test')
514
+ .option('--custom <prompt>', 'Use a custom prompt string (verified authors only)')
515
+ .option('--prompt-file <file>', 'Load custom prompt from a file (verified authors only)')
320
516
  .addHelpText('after', `
321
517
  Examples:
322
- # Single query with default model (Sonnet)
323
- $ prpm playground @anthropic/code-reviewer "Review this code: console.log('hello')"
518
+ # Test a package (single query)
519
+ $ prpm playground --package @anthropic/code-reviewer --input "Review this code: console.log('hello')"
324
520
 
325
521
  # Interactive mode for multi-turn conversation
326
- $ prpm playground @anthropic/brainstorm-assistant --interactive
522
+ $ prpm playground --package @anthropic/brainstorm-assistant --interactive
327
523
 
328
524
  # Compare with and without the package prompt
329
- $ prpm playground @user/custom-prompt "Test input" --compare
525
+ $ prpm playground --package @user/custom-prompt --input "Test input" --compare
330
526
 
331
527
  # Use a different model
332
- $ prpm playground @user/prompt --model opus "Complex task requiring Opus"
333
- $ prpm playground @user/prompt --model gpt-4o "Test with GPT-4o"
528
+ $ prpm playground --package @user/prompt --model opus --input "Complex task requiring Opus"
529
+ $ prpm playground --package @user/prompt --model gpt-4o --input "Test with GPT-4o"
334
530
 
335
531
  # Test specific version
336
- $ prpm playground @user/prompt@1.2.0 "Test input"
532
+ $ prpm playground --package @user/prompt --version 1.2.0 --input "Test input"
533
+
534
+ # Use a custom prompt string (verified authors only)
535
+ $ prpm playground --custom "You are a helpful coding assistant" --input "Explain async/await"
536
+
537
+ # Load custom prompt from a file (verified authors only)
538
+ $ prpm playground --prompt-file ./my-prompt.txt --input "Test input"
539
+
540
+ # Compare custom prompt against baseline (no prompt)
541
+ $ prpm playground --custom "You are concise" --input "Explain recursion" --compare
542
+ $ prpm playground --prompt-file ./my-prompt.txt --input "Test" --compare
543
+
544
+ # Interactive mode with custom prompt from file
545
+ $ prpm playground --prompt-file ./my-prompt.txt --interactive
546
+
547
+ # Short flags for common usage
548
+ $ prpm playground -p @user/prompt --input "Test this"
549
+ $ prpm playground -p @user/prompt -i
337
550
 
338
551
  Available Models:
339
552
  sonnet - Claude 3.5 Sonnet (default, balanced performance)
@@ -342,10 +555,16 @@ Available Models:
342
555
  gpt-4o-mini - GPT-4o Mini (faster, cheaper)
343
556
  gpt-4-turbo - GPT-4 Turbo
344
557
 
558
+ Custom Prompts:
559
+ - Available to verified authors (link your GitHub account)
560
+ - Cost 2x normal credits (no prompt caching)
561
+ - Min 10 characters, max 50,000 characters
562
+ - Perfect for iterating on prompt files locally
563
+
345
564
  Note: Playground usage requires credits. Run 'prpm credits' to check balance.
346
565
  `)
347
- .action(async (packageName, input, options) => {
348
- await handlePlayground(packageName, input, options);
566
+ .action(async (options) => {
567
+ await handlePlayground(options);
349
568
  });
350
569
  return command;
351
570
  }
@@ -21,57 +21,41 @@ async function handleUninstall(name) {
21
21
  if (!pkg) {
22
22
  throw new errors_1.CLIError(`āŒ Package "${name}" not found`, 1);
23
23
  }
24
- // Get destination directory using format and subtype
25
- const format = pkg.format || 'generic';
26
- const subtype = pkg.subtype || 'rule';
24
+ // Get the installation path from lock file if available, otherwise calculate it
27
25
  const packageName = (0, filesystem_1.stripAuthorNamespace)(name);
28
- const destDir = (0, filesystem_1.getDestinationDir)(format, subtype, packageName);
29
- const fileExtension = pkg.format === 'cursor' ? 'mdc' : 'md';
30
- // For Claude skills, delete the entire directory (may contain multiple files)
31
- if (format === 'claude' && subtype === 'skill') {
32
- // Claude skills are in .claude/skills/${packageName}/ directory
33
- // Delete the entire directory (includes SKILL.md, EXAMPLES.md, docs/, etc.)
34
- try {
35
- const stats = await fs_1.promises.stat(destDir);
36
- if (stats.isDirectory()) {
37
- await fs_1.promises.rm(destDir, { recursive: true, force: true });
38
- console.log(` šŸ—‘ļø Deleted directory: ${destDir}`);
39
- }
26
+ let targetPath;
27
+ if (pkg.installedPath) {
28
+ // Use the exact path where it was installed (from lock file)
29
+ targetPath = pkg.installedPath;
30
+ console.log(` šŸ“ Using installation path from lock file: ${targetPath}`);
31
+ }
32
+ else {
33
+ // Fallback: warn user that installedPath is missing (shouldn't happen with recent installations)
34
+ console.warn(` āš ļø No installation path in lock file for ${name}`);
35
+ console.warn(` āš ļø This may indicate an old or corrupted lock file`);
36
+ throw new errors_1.CLIError(`Cannot uninstall ${name}: installation path unknown`, 1);
37
+ }
38
+ // Check if the target path is a directory or file and delete accordingly
39
+ try {
40
+ const stats = await fs_1.promises.stat(targetPath);
41
+ if (stats.isDirectory()) {
42
+ // Delete entire directory
43
+ await fs_1.promises.rm(targetPath, { recursive: true, force: true });
44
+ console.log(` šŸ—‘ļø Deleted directory: ${targetPath}`);
40
45
  }
41
- catch (error) {
42
- const err = error;
43
- if (err.code === 'ENOENT') {
44
- console.warn(` āš ļø Skill directory not found: ${destDir}`);
45
- }
46
- else {
47
- console.warn(` āš ļø Could not delete skill directory: ${err.message}`);
48
- }
46
+ else if (stats.isFile()) {
47
+ // Delete single file
48
+ await fs_1.promises.unlink(targetPath);
49
+ console.log(` šŸ—‘ļø Deleted file: ${targetPath}`);
49
50
  }
50
51
  }
51
- else {
52
- // For other formats, try single file first
53
- const singleFilePath = `${destDir}/${packageName}.${fileExtension}`;
54
- if (await (0, filesystem_1.fileExists)(singleFilePath)) {
55
- // Single file package
56
- await (0, filesystem_1.deleteFile)(singleFilePath);
57
- console.log(` šŸ—‘ļø Deleted file: ${singleFilePath}`);
52
+ catch (error) {
53
+ const err = error;
54
+ if (err.code === 'ENOENT') {
55
+ console.warn(` āš ļø File/directory not found: ${targetPath}`);
58
56
  }
59
57
  else {
60
- // Try multi-file package directory
61
- const packageDir = `${destDir}/${packageName}`;
62
- try {
63
- const stats = await fs_1.promises.stat(packageDir);
64
- if (stats.isDirectory()) {
65
- await fs_1.promises.rm(packageDir, { recursive: true, force: true });
66
- console.log(` šŸ—‘ļø Deleted directory: ${packageDir}`);
67
- }
68
- }
69
- catch (error) {
70
- const err = error;
71
- if (err.code !== 'ENOENT') {
72
- console.warn(` āš ļø Could not delete package files: ${err.message}`);
73
- }
74
- }
58
+ throw err;
75
59
  }
76
60
  }
77
61
  console.log(`āœ… Successfully uninstalled ${name}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prpm",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Prompt Package Manager CLI - Install and manage prompt-based files",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -45,8 +45,8 @@
45
45
  "license": "MIT",
46
46
  "dependencies": {
47
47
  "@octokit/rest": "^22.0.0",
48
- "@pr-pm/registry-client": "^1.3.10",
49
- "@pr-pm/types": "^0.2.11",
48
+ "@pr-pm/registry-client": "^1.3.12",
49
+ "@pr-pm/types": "^0.2.13",
50
50
  "ajv": "^8.17.1",
51
51
  "ajv-formats": "^3.0.1",
52
52
  "commander": "^11.1.0",