prpm 0.0.11 → 0.0.12

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.
@@ -77,7 +77,7 @@ async function createMockCollection(testDir, id, packages) {
77
77
  async function createMockConfig(configPath, options) {
78
78
  const config = {
79
79
  token: options.token || 'test-token-123',
80
- registryUrl: options.registryUrl || 'http://localhost:3000',
80
+ registryUrl: options.registryUrl || 'http://localhost:3111',
81
81
  };
82
82
  await (0, promises_1.mkdir)((0, path_1.join)(configPath, '..'), { recursive: true });
83
83
  await (0, promises_1.writeFile)(configPath, JSON.stringify(config, null, 2));
@@ -40,6 +40,13 @@ function getDestinationDir(type) {
40
40
  return '.prompts';
41
41
  }
42
42
  }
43
+ /**
44
+ * Strip author namespace from package ID
45
+ */
46
+ function stripAuthorNamespace(packageId) {
47
+ const parts = packageId.split('/');
48
+ return parts[parts.length - 1];
49
+ }
43
50
  /**
44
51
  * Find the actual file location for a package
45
52
  */
@@ -47,11 +54,13 @@ async function findPackageLocation(id, format, subtype) {
47
54
  if (!format)
48
55
  return null;
49
56
  const baseDir = getDestinationDir(format);
57
+ // Strip author namespace to get actual package name used in file system
58
+ const packageName = stripAuthorNamespace(id);
50
59
  // Try different file extensions based on format
51
60
  const extensions = format === 'cursor' ? ['.mdc', '.md'] : ['.md'];
52
- // Try direct file: <dir>/<id>.ext
61
+ // Try direct file: <dir>/<packageName>.ext
53
62
  for (const ext of extensions) {
54
- const directPath = path_1.default.join(baseDir, `${id}${ext}`);
63
+ const directPath = path_1.default.join(baseDir, `${packageName}${ext}`);
55
64
  try {
56
65
  await fs_1.promises.access(directPath);
57
66
  return directPath;
@@ -60,9 +69,9 @@ async function findPackageLocation(id, format, subtype) {
60
69
  // File doesn't exist, continue
61
70
  }
62
71
  }
63
- // Try subdirectory: <dir>/<id>/SKILL.md or <dir>/<id>/AGENT.md
72
+ // Try subdirectory: <dir>/<packageName>/SKILL.md or <dir>/<packageName>/AGENT.md
64
73
  if (subtype === 'skill') {
65
- const skillPath = path_1.default.join(baseDir, id, 'SKILL.md');
74
+ const skillPath = path_1.default.join(baseDir, packageName, 'SKILL.md');
66
75
  try {
67
76
  await fs_1.promises.access(skillPath);
68
77
  return skillPath;
@@ -72,7 +81,7 @@ async function findPackageLocation(id, format, subtype) {
72
81
  }
73
82
  }
74
83
  if (subtype === 'agent' || format === 'claude') {
75
- const agentPath = path_1.default.join(baseDir, id, 'AGENT.md');
84
+ const agentPath = path_1.default.join(baseDir, packageName, 'AGENT.md');
76
85
  try {
77
86
  await fs_1.promises.access(agentPath);
78
87
  return agentPath;
@@ -140,10 +140,21 @@ async function loginWithOAuth(registryUrl) {
140
140
  }
141
141
  // Create the CLI auth URL with session token, callback, and userId
142
142
  const callbackUrl = 'http://localhost:8765/callback';
143
- // Determine webapp URL - default to production
144
- const webappUrl = registryUrl.includes('localhost')
145
- ? registryUrl.replace(':3000', ':5173') // Local: localhost:3000 → localhost:5173
146
- : 'https://prpm.dev'; // Production: always use prpm.dev
143
+ // Determine webapp URL based on registry URL
144
+ let webappUrl;
145
+ if (registryUrl.includes('localhost') || registryUrl.includes('127.0.0.1')) {
146
+ // Local development: registry on port 3111, webapp on port 5173
147
+ webappUrl = registryUrl.replace(':3111', ':5173');
148
+ }
149
+ else if (registryUrl.includes('registry.prpm.dev')) {
150
+ // Production: always use prpm.dev webapp
151
+ webappUrl = 'https://prpm.dev';
152
+ }
153
+ else {
154
+ // Custom registry: assume webapp is on same host without port
155
+ const url = new URL(registryUrl);
156
+ webappUrl = `${url.protocol}//${url.hostname}`;
157
+ }
147
158
  const authUrl = `${webappUrl}/cli-auth?sessionToken=${encodeURIComponent(connectSessionToken)}&cliCallback=${encodeURIComponent(callbackUrl)}&userId=${encodeURIComponent(userId)}`;
148
159
  console.log(` Please open this link in your browser to authenticate:`);
149
160
  console.log(` ${authUrl}\n`);
@@ -54,27 +54,88 @@ const snippet_extractor_1 = require("../utils/snippet-extractor");
54
54
  /**
55
55
  * Try to find and load manifest files
56
56
  * Checks for:
57
- * 1. prpm.json (native format) - returns single manifest
57
+ * 1. prpm.json (native format) - returns single manifest or array of packages
58
58
  * 2. .claude/marketplace.json (Claude format) - returns all plugins as separate manifests
59
59
  * 3. .claude-plugin/marketplace.json (Claude format - alternative location) - returns all plugins
60
60
  */
61
61
  async function findAndLoadManifests() {
62
62
  // Try prpm.json first (native format)
63
63
  const prpmJsonPath = (0, path_1.join)(process.cwd(), 'prpm.json');
64
+ let prpmJsonExists = false;
65
+ let prpmJsonError = null;
64
66
  try {
65
67
  const content = await (0, promises_1.readFile)(prpmJsonPath, 'utf-8');
66
- const manifest = JSON.parse(content);
68
+ prpmJsonExists = true;
69
+ // Try to parse JSON
70
+ let manifest;
71
+ try {
72
+ manifest = JSON.parse(content);
73
+ }
74
+ catch (parseError) {
75
+ // JSON parse error - provide specific error message
76
+ const error = parseError;
77
+ throw new Error(`Invalid JSON in prpm.json:\n\n` +
78
+ `${error.message}\n\n` +
79
+ `Please check your prpm.json file for syntax errors:\n` +
80
+ ` - Missing or extra commas\n` +
81
+ ` - Unclosed quotes or brackets\n` +
82
+ ` - Invalid JSON syntax\n\n` +
83
+ `You can validate your JSON at https://jsonlint.com/`);
84
+ }
85
+ // Check if this is a multi-package manifest
86
+ if ('packages' in manifest && Array.isArray(manifest.packages)) {
87
+ const multiManifest = manifest;
88
+ // Validate each package in the array
89
+ const validatedManifests = multiManifest.packages.map((pkg, idx) => {
90
+ // Inherit top-level fields if not specified in package - using explicit undefined checks
91
+ const packageWithDefaults = {
92
+ name: pkg.name,
93
+ version: pkg.version,
94
+ description: pkg.description,
95
+ format: pkg.format,
96
+ files: pkg.files,
97
+ author: pkg.author !== undefined ? pkg.author : multiManifest.author,
98
+ license: pkg.license !== undefined ? pkg.license : multiManifest.license,
99
+ repository: pkg.repository !== undefined ? pkg.repository : multiManifest.repository,
100
+ homepage: pkg.homepage !== undefined ? pkg.homepage : multiManifest.homepage,
101
+ documentation: pkg.documentation !== undefined ? pkg.documentation : multiManifest.documentation,
102
+ organization: pkg.organization !== undefined ? pkg.organization : multiManifest.organization,
103
+ private: pkg.private !== undefined ? pkg.private : multiManifest.private,
104
+ tags: pkg.tags !== undefined ? pkg.tags : multiManifest.tags,
105
+ keywords: pkg.keywords !== undefined ? pkg.keywords : multiManifest.keywords,
106
+ subtype: pkg.subtype,
107
+ dependencies: pkg.dependencies,
108
+ peerDependencies: pkg.peerDependencies,
109
+ engines: pkg.engines,
110
+ main: pkg.main,
111
+ };
112
+ // Debug: Log inheritance only if DEBUG env var is set
113
+ if (process.env.DEBUG) {
114
+ console.log(`\n🔍 Package ${pkg.name} inheritance:`);
115
+ console.log(` - Package-level private: ${pkg.private}`);
116
+ console.log(` - Top-level private: ${multiManifest.private}`);
117
+ console.log(` - Inherited private: ${packageWithDefaults.private}`);
118
+ console.log('');
119
+ }
120
+ return validateManifest(packageWithDefaults);
121
+ });
122
+ return { manifests: validatedManifests, source: 'prpm.json (multi-package)' };
123
+ }
124
+ // Single package manifest
67
125
  const validated = validateManifest(manifest);
68
126
  return { manifests: [validated], source: 'prpm.json' };
69
127
  }
70
128
  catch (error) {
71
- // If it's a validation error, throw it immediately (don't try marketplace.json)
72
- if (error instanceof Error && (error.message.includes('Manifest validation failed') ||
129
+ // Store error for later
130
+ prpmJsonError = error;
131
+ // If it's a validation or parsing error, throw it immediately (don't try marketplace.json)
132
+ if (prpmJsonExists && error instanceof Error && (error.message.includes('Invalid JSON') ||
133
+ error.message.includes('Manifest validation failed') ||
73
134
  error.message.includes('Claude skill') ||
74
135
  error.message.includes('SKILL.md'))) {
75
136
  throw error;
76
137
  }
77
- // Otherwise, prpm.json not found or invalid JSON, try marketplace.json
138
+ // Otherwise, prpm.json not found or other error, try marketplace.json
78
139
  }
79
140
  // Try .claude/marketplace.json (Claude format)
80
141
  const marketplaceJsonPath = (0, path_1.join)(process.cwd(), '.claude', 'marketplace.json');
@@ -316,6 +377,39 @@ async function handlePublish(options) {
316
377
  console.log(`${'='.repeat(60)}\n`);
317
378
  }
318
379
  try {
380
+ // Debug: Log access override logic only if DEBUG env var is set
381
+ if (process.env.DEBUG) {
382
+ console.log(`\n🔍 Before access override:`);
383
+ console.log(` - manifest.private: ${manifest.private}`);
384
+ console.log(` - options.access: ${options.access}`);
385
+ }
386
+ // Determine access level:
387
+ // 1. If --access flag is provided, it overrides manifest setting
388
+ // 2. Otherwise, use manifest setting (defaults to false/public if not specified)
389
+ let isPrivate;
390
+ if (options.access !== undefined) {
391
+ // CLI flag explicitly provided - use it
392
+ isPrivate = options.access === 'private';
393
+ if (process.env.DEBUG) {
394
+ console.log(` - Using CLI flag override: ${options.access}`);
395
+ }
396
+ }
397
+ else {
398
+ // No CLI flag - use manifest setting
399
+ isPrivate = manifest.private || false;
400
+ if (process.env.DEBUG) {
401
+ console.log(` - Using manifest setting: ${isPrivate}`);
402
+ }
403
+ }
404
+ if (process.env.DEBUG) {
405
+ console.log(` - calculated isPrivate: ${isPrivate}`);
406
+ }
407
+ // Update manifest with final private setting
408
+ manifest.private = isPrivate;
409
+ if (process.env.DEBUG) {
410
+ console.log(` - final manifest.private: ${manifest.private}`);
411
+ console.log('');
412
+ }
319
413
  let selectedOrgId;
320
414
  // Check if organization is specified in manifest
321
415
  if (manifest.organization && userInfo) {
@@ -334,6 +428,7 @@ async function handlePublish(options) {
334
428
  console.log(` Package: ${manifest.name}@${manifest.version}`);
335
429
  console.log(` Format: ${manifest.format} | Subtype: ${manifest.subtype}`);
336
430
  console.log(` Description: ${manifest.description}`);
431
+ console.log(` Access: ${manifest.private ? 'private' : 'public'}`);
337
432
  if (selectedOrgId && userInfo) {
338
433
  const selectedOrg = userInfo.organizations.find((org) => org.id === selectedOrgId);
339
434
  console.log(` Publishing to: ${selectedOrg?.name || 'organization'}`);
@@ -512,7 +607,7 @@ async function handlePublish(options) {
512
607
  function createPublishCommand() {
513
608
  return new commander_1.Command('publish')
514
609
  .description('Publish a package to the registry')
515
- .option('--access <type>', 'Package access (public or private)', 'public')
610
+ .option('--access <type>', 'Package access (public or private) - overrides manifest setting')
516
611
  .option('--tag <tag>', 'NPM-style tag (e.g., latest, beta)', 'latest')
517
612
  .option('--dry-run', 'Validate package without publishing')
518
613
  .action(async (options) => {
@@ -17,8 +17,13 @@ const fs_1 = require("fs");
17
17
  const path_1 = __importDefault(require("path"));
18
18
  /**
19
19
  * Get the destination directory for a package based on format and subtype
20
+ * @param format - Package format (cursor, claude, etc.)
21
+ * @param subtype - Package subtype (skill, agent, rule, etc.)
22
+ * @param name - Package name (optional, only needed for Claude skills which create subdirectories)
20
23
  */
21
24
  function getDestinationDir(format, subtype, name) {
25
+ // Strip author namespace from package name to avoid nested directories
26
+ const packageName = stripAuthorNamespace(name);
22
27
  switch (format) {
23
28
  case 'cursor':
24
29
  if (subtype === 'agent')
@@ -27,8 +32,11 @@ function getDestinationDir(format, subtype, name) {
27
32
  return '.cursor/commands';
28
33
  return '.cursor/rules';
29
34
  case 'claude':
35
+ // Only create subdirectory for skills if name is provided
36
+ if (subtype === 'skill' && packageName)
37
+ return `.claude/skills/${packageName}`;
30
38
  if (subtype === 'skill')
31
- return `.claude/skills/${name}`;
39
+ return '.claude/skills';
32
40
  if (subtype === 'slash-command')
33
41
  return '.claude/commands';
34
42
  if (subtype === 'agent')
@@ -124,6 +132,10 @@ function generateId(filename) {
124
132
  * stripAuthorNamespace('git-workflow-manager') // 'git-workflow-manager'
125
133
  */
126
134
  function stripAuthorNamespace(packageId) {
135
+ // Handle undefined or empty string
136
+ if (!packageId) {
137
+ return '';
138
+ }
127
139
  // Split by '/' and get the last segment (the actual package name)
128
140
  const parts = packageId.split('/');
129
141
  return parts[parts.length - 1];
@@ -1,58 +1,124 @@
1
1
  "use strict";
2
2
  /**
3
- * User configuration management for ~/.prpmrc
3
+ * User configuration management for ~/.prpmrc and .prpmrc
4
4
  * Stores global settings like registry URL and authentication token
5
+ * Supports both user-level (~/.prpmrc) and repository-level (.prpmrc) config
5
6
  */
6
7
  Object.defineProperty(exports, "__esModule", { value: true });
7
8
  exports.getConfig = getConfig;
8
9
  exports.saveConfig = saveConfig;
10
+ exports.saveRepoConfig = saveRepoConfig;
11
+ exports.getRepoConfig = getRepoConfig;
12
+ exports.getUserConfig = getUserConfig;
9
13
  exports.updateConfig = updateConfig;
10
14
  exports.clearAuth = clearAuth;
11
15
  exports.getRegistryUrl = getRegistryUrl;
12
16
  const fs_1 = require("fs");
13
17
  const path_1 = require("path");
14
18
  const os_1 = require("os");
15
- const CONFIG_FILE = (0, path_1.join)((0, os_1.homedir)(), '.prpmrc');
19
+ const USER_CONFIG_FILE = (0, path_1.join)((0, os_1.homedir)(), '.prpmrc');
20
+ const REPO_CONFIG_FILE = '.prpmrc';
16
21
  const DEFAULT_REGISTRY_URL = 'https://registry.prpm.dev';
17
22
  /**
18
- * Get user configuration
23
+ * Load configuration from a file
19
24
  */
20
- async function getConfig() {
25
+ async function loadConfigFile(filePath) {
21
26
  try {
22
- const data = await fs_1.promises.readFile(CONFIG_FILE, 'utf-8');
23
- const config = JSON.parse(data);
24
- // Allow environment variable to override registry URL
25
- if (process.env.PRPM_REGISTRY_URL) {
26
- config.registryUrl = process.env.PRPM_REGISTRY_URL;
27
- }
28
- else if (!config.registryUrl) {
29
- config.registryUrl = DEFAULT_REGISTRY_URL;
30
- }
31
- return config;
27
+ const data = await fs_1.promises.readFile(filePath, 'utf-8');
28
+ return JSON.parse(data);
32
29
  }
33
30
  catch (error) {
34
- // If file doesn't exist, return default config
35
31
  if (error.code === 'ENOENT') {
36
- return {
37
- registryUrl: process.env.PRPM_REGISTRY_URL || DEFAULT_REGISTRY_URL,
38
- telemetryEnabled: true,
39
- };
32
+ return null;
40
33
  }
41
- throw new Error(`Failed to read user config: ${error}`);
34
+ throw new Error(`Failed to read config from ${filePath}: ${error}`);
35
+ }
36
+ }
37
+ /**
38
+ * Get merged configuration from user and repository levels
39
+ * Priority: CLI flags > environment > repo config > user config > defaults
40
+ */
41
+ async function getConfig() {
42
+ // Load user-level config (~/.prpmrc)
43
+ const userConfig = await loadConfigFile(USER_CONFIG_FILE);
44
+ // Load repository-level config (./prpmrc)
45
+ const repoConfigPath = (0, path_1.join)(process.cwd(), REPO_CONFIG_FILE);
46
+ const repoConfig = await loadConfigFile(repoConfigPath);
47
+ // Merge configs (repo overrides user)
48
+ const config = {
49
+ ...userConfig,
50
+ ...repoConfig,
51
+ };
52
+ // Deep merge nested objects
53
+ if (userConfig?.cursor || repoConfig?.cursor) {
54
+ config.cursor = {
55
+ ...userConfig?.cursor,
56
+ ...repoConfig?.cursor,
57
+ };
58
+ }
59
+ if (userConfig?.claude || repoConfig?.claude) {
60
+ config.claude = {
61
+ ...userConfig?.claude,
62
+ ...repoConfig?.claude,
63
+ };
64
+ }
65
+ if (userConfig?.collections || repoConfig?.collections) {
66
+ config.collections = {
67
+ ...userConfig?.collections,
68
+ ...repoConfig?.collections,
69
+ };
70
+ }
71
+ // Allow environment variable to override registry URL
72
+ if (process.env.PRPM_REGISTRY_URL) {
73
+ config.registryUrl = process.env.PRPM_REGISTRY_URL;
42
74
  }
75
+ else if (!config.registryUrl) {
76
+ config.registryUrl = DEFAULT_REGISTRY_URL;
77
+ }
78
+ // Set defaults
79
+ if (config.telemetryEnabled === undefined) {
80
+ config.telemetryEnabled = true;
81
+ }
82
+ return config;
43
83
  }
44
84
  /**
45
- * Save user configuration
85
+ * Save user configuration to ~/.prpmrc
46
86
  */
47
87
  async function saveConfig(config) {
48
88
  try {
49
89
  const data = JSON.stringify(config, null, 2);
50
- await fs_1.promises.writeFile(CONFIG_FILE, data, 'utf-8');
90
+ await fs_1.promises.writeFile(USER_CONFIG_FILE, data, 'utf-8');
51
91
  }
52
92
  catch (error) {
53
93
  throw new Error(`Failed to save user config: ${error}`);
54
94
  }
55
95
  }
96
+ /**
97
+ * Save repository configuration to ./.prpmrc
98
+ */
99
+ async function saveRepoConfig(config) {
100
+ try {
101
+ const repoConfigPath = (0, path_1.join)(process.cwd(), REPO_CONFIG_FILE);
102
+ const data = JSON.stringify(config, null, 2);
103
+ await fs_1.promises.writeFile(repoConfigPath, data, 'utf-8');
104
+ }
105
+ catch (error) {
106
+ throw new Error(`Failed to save repository config: ${error}`);
107
+ }
108
+ }
109
+ /**
110
+ * Get repository-level configuration only
111
+ */
112
+ async function getRepoConfig() {
113
+ const repoConfigPath = (0, path_1.join)(process.cwd(), REPO_CONFIG_FILE);
114
+ return await loadConfigFile(repoConfigPath);
115
+ }
116
+ /**
117
+ * Get user-level configuration only
118
+ */
119
+ async function getUserConfig() {
120
+ return await loadConfigFile(USER_CONFIG_FILE);
121
+ }
56
122
  /**
57
123
  * Update specific config values
58
124
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prpm",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
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.2.5",
49
- "@pr-pm/types": "^0.1.5",
48
+ "@pr-pm/registry-client": "^1.2.6",
49
+ "@pr-pm/types": "^0.1.6",
50
50
  "ajv": "^8.17.1",
51
51
  "ajv-formats": "^3.0.1",
52
52
  "commander": "^11.1.0",
@@ -148,6 +148,11 @@
148
148
  "my-company"
149
149
  ]
150
150
  },
151
+ "private": {
152
+ "type": "boolean",
153
+ "description": "Whether the package is private. Private packages are only accessible to the owner/organization members. Defaults to false (public).",
154
+ "default": false
155
+ },
151
156
  "tags": {
152
157
  "type": "array",
153
158
  "description": "Package tags for categorization",
@@ -354,6 +359,14 @@
354
359
  "node": ">=18.0.0"
355
360
  }
356
361
  ]
362
+ },
363
+ "packages": {
364
+ "type": "array",
365
+ "description": "Array of packages to publish from a single manifest (multi-package publishing). Packages inherit top-level fields unless overridden.",
366
+ "items": {
367
+ "$ref": "#"
368
+ },
369
+ "minItems": 1
357
370
  }
358
371
  },
359
372
  "additionalProperties": false,
@@ -491,6 +504,51 @@
491
504
  "files": [
492
505
  ".windsurfrules"
493
506
  ]
507
+ },
508
+ {
509
+ "name": "@company/private-package",
510
+ "version": "1.0.0",
511
+ "description": "A private package only accessible to organization members",
512
+ "format": "claude",
513
+ "subtype": "skill",
514
+ "author": "Company Team",
515
+ "organization": "my-company",
516
+ "private": true,
517
+ "license": "Proprietary",
518
+ "files": [
519
+ "internal-skill.md",
520
+ "README.md"
521
+ ]
522
+ },
523
+ {
524
+ "name": "@username/multi-package-example",
525
+ "version": "1.0.0",
526
+ "description": "Multi-package manifest example",
527
+ "author": "Your Name",
528
+ "license": "MIT",
529
+ "repository": "https://github.com/username/multi-package",
530
+ "packages": [
531
+ {
532
+ "name": "@username/package-one",
533
+ "version": "1.0.0",
534
+ "description": "First package in the multi-package manifest",
535
+ "format": "claude",
536
+ "subtype": "skill",
537
+ "files": [
538
+ "package-one/SKILL.md"
539
+ ]
540
+ },
541
+ {
542
+ "name": "@username/package-two",
543
+ "version": "1.0.0",
544
+ "description": "Second package with different settings",
545
+ "format": "cursor",
546
+ "private": true,
547
+ "files": [
548
+ "package-two/.cursor/rules/main.mdc"
549
+ ]
550
+ }
551
+ ]
494
552
  }
495
553
  ]
496
554
  }