prpm 0.1.17 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +14257 -107
- package/package.json +11 -9
- package/dist/__tests__/e2e/test-helpers.js +0 -151
- package/dist/commands/buy-credits.js +0 -224
- package/dist/commands/catalog.js +0 -365
- package/dist/commands/collections.js +0 -655
- package/dist/commands/config.js +0 -161
- package/dist/commands/credits.js +0 -186
- package/dist/commands/index.js +0 -184
- package/dist/commands/info.js +0 -78
- package/dist/commands/init.js +0 -684
- package/dist/commands/install.js +0 -789
- package/dist/commands/list.js +0 -189
- package/dist/commands/login.js +0 -316
- package/dist/commands/outdated.js +0 -130
- package/dist/commands/playground.js +0 -570
- package/dist/commands/popular.js +0 -33
- package/dist/commands/publish.js +0 -803
- package/dist/commands/schema.js +0 -41
- package/dist/commands/search.js +0 -446
- package/dist/commands/subscribe.js +0 -211
- package/dist/commands/telemetry.js +0 -104
- package/dist/commands/trending.js +0 -86
- package/dist/commands/uninstall.js +0 -120
- package/dist/commands/update.js +0 -121
- package/dist/commands/upgrade.js +0 -121
- package/dist/commands/whoami.js +0 -83
- package/dist/core/claude-config.js +0 -91
- package/dist/core/cursor-config.js +0 -130
- package/dist/core/downloader.js +0 -64
- package/dist/core/errors.js +0 -29
- package/dist/core/filesystem.js +0 -242
- package/dist/core/lockfile.js +0 -292
- package/dist/core/marketplace-converter.js +0 -224
- package/dist/core/registry-client.js +0 -305
- package/dist/core/schema-validator.js +0 -74
- package/dist/core/telemetry.js +0 -253
- package/dist/core/user-config.js +0 -147
- package/dist/types/registry.js +0 -12
- package/dist/types.js +0 -36
- package/dist/utils/license-extractor.js +0 -122
- package/dist/utils/multi-package.js +0 -117
- package/dist/utils/parallel-publisher.js +0 -144
- package/dist/utils/script-executor.js +0 -72
- package/dist/utils/snippet-extractor.js +0 -77
- package/dist/utils/webapp-url.js +0 -44
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Converter for Claude marketplace.json format to PRPM manifest
|
|
4
|
-
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.marketplaceToManifest = marketplaceToManifest;
|
|
7
|
-
exports.validateMarketplaceJson = validateMarketplaceJson;
|
|
8
|
-
/**
|
|
9
|
-
* Convert marketplace.json to PRPM manifest format
|
|
10
|
-
*
|
|
11
|
-
* Strategy:
|
|
12
|
-
* - If multiple plugins exist, create a manifest for the first plugin (user can publish others separately)
|
|
13
|
-
* - If the plugin has agents/skills/commands, prefer those over the root plugin info
|
|
14
|
-
* - Map marketplace fields to PRPM manifest fields
|
|
15
|
-
*
|
|
16
|
-
* @param marketplace - The marketplace.json content
|
|
17
|
-
* @param pluginIndex - Which plugin to convert (default: 0, for the first plugin)
|
|
18
|
-
* @returns PRPM manifest
|
|
19
|
-
*/
|
|
20
|
-
function marketplaceToManifest(marketplace, pluginIndex = 0) {
|
|
21
|
-
if (!marketplace.plugins || marketplace.plugins.length === 0) {
|
|
22
|
-
throw new Error('marketplace.json must contain at least one plugin');
|
|
23
|
-
}
|
|
24
|
-
if (pluginIndex >= marketplace.plugins.length) {
|
|
25
|
-
throw new Error(`Plugin index ${pluginIndex} out of range. Found ${marketplace.plugins.length} plugins.`);
|
|
26
|
-
}
|
|
27
|
-
const plugin = marketplace.plugins[pluginIndex];
|
|
28
|
-
// Determine package format and subtype based on what the plugin contains
|
|
29
|
-
let format = 'claude';
|
|
30
|
-
let subtype = 'rule';
|
|
31
|
-
if (plugin.agents && plugin.agents.length > 0) {
|
|
32
|
-
format = 'claude';
|
|
33
|
-
subtype = 'agent';
|
|
34
|
-
}
|
|
35
|
-
else if (plugin.skills && plugin.skills.length > 0) {
|
|
36
|
-
format = 'claude';
|
|
37
|
-
subtype = 'skill';
|
|
38
|
-
}
|
|
39
|
-
else if (plugin.commands && plugin.commands.length > 0) {
|
|
40
|
-
format = 'claude';
|
|
41
|
-
subtype = 'slash-command';
|
|
42
|
-
}
|
|
43
|
-
// Generate package name from plugin name
|
|
44
|
-
// Format: @owner/plugin-name
|
|
45
|
-
const ownerName = typeof marketplace.owner === 'string' ? marketplace.owner : marketplace.owner.name;
|
|
46
|
-
const packageName = generatePackageName(ownerName, plugin.name);
|
|
47
|
-
// Collect all files that should be included
|
|
48
|
-
const files = collectFiles(plugin);
|
|
49
|
-
// Determine the main file
|
|
50
|
-
const main = determineMainFile(plugin);
|
|
51
|
-
// Collect keywords from both marketplace and plugin
|
|
52
|
-
const keywords = [
|
|
53
|
-
...(marketplace.keywords || []),
|
|
54
|
-
...(plugin.keywords || []),
|
|
55
|
-
].slice(0, 20); // Max 20 keywords
|
|
56
|
-
// Extract tags from keywords (first 10)
|
|
57
|
-
const tags = keywords.slice(0, 10);
|
|
58
|
-
// Get description from plugin, metadata, or root
|
|
59
|
-
const description = plugin.description ||
|
|
60
|
-
marketplace.metadata?.description ||
|
|
61
|
-
marketplace.description ||
|
|
62
|
-
'';
|
|
63
|
-
// Get version from plugin, metadata, or root
|
|
64
|
-
const version = plugin.version ||
|
|
65
|
-
marketplace.metadata?.version ||
|
|
66
|
-
marketplace.version ||
|
|
67
|
-
'1.0.0';
|
|
68
|
-
// Get author - prefer plugin.author, fallback to owner name
|
|
69
|
-
const author = plugin.author || ownerName;
|
|
70
|
-
const manifest = {
|
|
71
|
-
name: packageName,
|
|
72
|
-
version,
|
|
73
|
-
description,
|
|
74
|
-
format,
|
|
75
|
-
subtype,
|
|
76
|
-
author,
|
|
77
|
-
files,
|
|
78
|
-
tags,
|
|
79
|
-
keywords,
|
|
80
|
-
};
|
|
81
|
-
// Add optional fields if available
|
|
82
|
-
if (marketplace.githubUrl) {
|
|
83
|
-
manifest.repository = marketplace.githubUrl;
|
|
84
|
-
}
|
|
85
|
-
if (marketplace.websiteUrl) {
|
|
86
|
-
manifest.homepage = marketplace.websiteUrl;
|
|
87
|
-
}
|
|
88
|
-
if (plugin.category) {
|
|
89
|
-
manifest.category = plugin.category;
|
|
90
|
-
}
|
|
91
|
-
if (main) {
|
|
92
|
-
manifest.main = main;
|
|
93
|
-
}
|
|
94
|
-
return manifest;
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Generate PRPM-compatible package name from owner and plugin name
|
|
98
|
-
*/
|
|
99
|
-
function generatePackageName(owner, pluginName) {
|
|
100
|
-
// Sanitize owner and plugin name
|
|
101
|
-
const sanitizedOwner = owner.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
102
|
-
const sanitizedName = pluginName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
103
|
-
// Remove leading/trailing hyphens
|
|
104
|
-
const cleanOwner = sanitizedOwner.replace(/^-+|-+$/g, '');
|
|
105
|
-
const cleanName = sanitizedName.replace(/^-+|-+$/g, '');
|
|
106
|
-
return `@${cleanOwner}/${cleanName}`;
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Collect all files referenced in the plugin
|
|
110
|
-
*/
|
|
111
|
-
function collectFiles(plugin) {
|
|
112
|
-
const files = new Set();
|
|
113
|
-
// Add plugin source if it's a file path
|
|
114
|
-
if (plugin.source && !plugin.source.startsWith('http')) {
|
|
115
|
-
files.add(plugin.source);
|
|
116
|
-
}
|
|
117
|
-
// Add agent files
|
|
118
|
-
if (plugin.agents) {
|
|
119
|
-
for (const agent of plugin.agents) {
|
|
120
|
-
if (agent.source && !agent.source.startsWith('http')) {
|
|
121
|
-
files.add(agent.source);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
// Add skill files
|
|
126
|
-
if (plugin.skills) {
|
|
127
|
-
for (const skill of plugin.skills) {
|
|
128
|
-
if (skill.source && !skill.source.startsWith('http')) {
|
|
129
|
-
files.add(skill.source);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
// Add command files
|
|
134
|
-
if (plugin.commands) {
|
|
135
|
-
for (const command of plugin.commands) {
|
|
136
|
-
if (command.source && !command.source.startsWith('http')) {
|
|
137
|
-
files.add(command.source);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
// Add standard files if they're not already included
|
|
142
|
-
const standardFiles = ['README.md', 'LICENSE', '.claude/marketplace.json', '.claude-plugin/marketplace.json'];
|
|
143
|
-
for (const file of standardFiles) {
|
|
144
|
-
files.add(file);
|
|
145
|
-
}
|
|
146
|
-
return Array.from(files);
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Determine the main entry file for the package
|
|
150
|
-
* Only set main if there's a single clear entry point
|
|
151
|
-
*/
|
|
152
|
-
function determineMainFile(plugin) {
|
|
153
|
-
const agentCount = plugin.agents?.length || 0;
|
|
154
|
-
const skillCount = plugin.skills?.length || 0;
|
|
155
|
-
const commandCount = plugin.commands?.length || 0;
|
|
156
|
-
// Only set main if there's exactly one item total
|
|
157
|
-
const totalCount = agentCount + skillCount + commandCount;
|
|
158
|
-
if (totalCount !== 1) {
|
|
159
|
-
// Multiple items or no items - no clear main file
|
|
160
|
-
return undefined;
|
|
161
|
-
}
|
|
162
|
-
// Single agent
|
|
163
|
-
if (agentCount === 1) {
|
|
164
|
-
const source = plugin.agents[0].source;
|
|
165
|
-
if (source && !source.startsWith('http')) {
|
|
166
|
-
return source;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
// Single skill
|
|
170
|
-
if (skillCount === 1) {
|
|
171
|
-
const source = plugin.skills[0].source;
|
|
172
|
-
if (source && !source.startsWith('http')) {
|
|
173
|
-
return source;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
// Single command
|
|
177
|
-
if (commandCount === 1) {
|
|
178
|
-
const source = plugin.commands[0].source;
|
|
179
|
-
if (source && !source.startsWith('http')) {
|
|
180
|
-
return source;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// Otherwise, use plugin source if available
|
|
184
|
-
if (plugin.source && !plugin.source.startsWith('http')) {
|
|
185
|
-
return plugin.source;
|
|
186
|
-
}
|
|
187
|
-
return undefined;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Validate marketplace.json structure
|
|
191
|
-
*/
|
|
192
|
-
function validateMarketplaceJson(data) {
|
|
193
|
-
if (!data || typeof data !== 'object') {
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
const marketplace = data;
|
|
197
|
-
// Check required fields
|
|
198
|
-
if (!marketplace.name || typeof marketplace.name !== 'string') {
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
// owner can be either string or object with name property
|
|
202
|
-
if (!marketplace.owner) {
|
|
203
|
-
return false;
|
|
204
|
-
}
|
|
205
|
-
if (typeof marketplace.owner !== 'string' &&
|
|
206
|
-
(typeof marketplace.owner !== 'object' || !marketplace.owner.name)) {
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
// description can be at root or in metadata
|
|
210
|
-
const hasDescription = (marketplace.description && typeof marketplace.description === 'string') ||
|
|
211
|
-
(marketplace.metadata?.description && typeof marketplace.metadata.description === 'string');
|
|
212
|
-
if (!hasDescription) {
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
if (!Array.isArray(marketplace.plugins) || marketplace.plugins.length === 0) {
|
|
216
|
-
return false;
|
|
217
|
-
}
|
|
218
|
-
// Validate first plugin has required fields
|
|
219
|
-
const plugin = marketplace.plugins[0];
|
|
220
|
-
if (!plugin.name || !plugin.description || !plugin.version) {
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
return true;
|
|
224
|
-
}
|
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Registry API Client
|
|
4
|
-
* Handles all communication with the PRMP Registry
|
|
5
|
-
*/
|
|
6
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
-
};
|
|
9
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.RegistryClient = void 0;
|
|
11
|
-
exports.getRegistryClient = getRegistryClient;
|
|
12
|
-
const package_json_1 = __importDefault(require("../../package.json"));
|
|
13
|
-
// CLI version for User-Agent header (exempts from rate limiting)
|
|
14
|
-
const CLI_VERSION = package_json_1.default.version;
|
|
15
|
-
class RegistryClient {
|
|
16
|
-
constructor(config) {
|
|
17
|
-
this.baseUrl = config.url.replace(/\/$/, ''); // Remove trailing slash
|
|
18
|
-
this.token = config.token;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Search for packages in the registry
|
|
22
|
-
*/
|
|
23
|
-
async search(query, options) {
|
|
24
|
-
const params = new URLSearchParams({ q: query });
|
|
25
|
-
if (options?.format)
|
|
26
|
-
params.append('format', options.format);
|
|
27
|
-
if (options?.subtype)
|
|
28
|
-
params.append('subtype', options.subtype);
|
|
29
|
-
if (options?.tags)
|
|
30
|
-
options.tags.forEach(tag => params.append('tags', tag));
|
|
31
|
-
if (options?.limit)
|
|
32
|
-
params.append('limit', options.limit.toString());
|
|
33
|
-
if (options?.offset)
|
|
34
|
-
params.append('offset', options.offset.toString());
|
|
35
|
-
const response = await this.fetch(`/api/v1/search?${params}`);
|
|
36
|
-
return response.json();
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Get package information
|
|
40
|
-
*/
|
|
41
|
-
async getPackage(packageId) {
|
|
42
|
-
const response = await this.fetch(`/api/v1/packages/${packageId}`);
|
|
43
|
-
return response.json();
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Get specific package version
|
|
47
|
-
*/
|
|
48
|
-
async getPackageVersion(packageId, version) {
|
|
49
|
-
const response = await this.fetch(`/api/v1/packages/${packageId}/${version}`);
|
|
50
|
-
return response.json();
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Get package dependencies
|
|
54
|
-
*/
|
|
55
|
-
async getPackageDependencies(packageId, version) {
|
|
56
|
-
const versionPath = version ? `/${version}` : '';
|
|
57
|
-
const response = await this.fetch(`/api/v1/packages/${packageId}${versionPath}/dependencies`);
|
|
58
|
-
return response.json();
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Get all versions for a package
|
|
62
|
-
*/
|
|
63
|
-
async getPackageVersions(packageId) {
|
|
64
|
-
const response = await this.fetch(`/api/v1/packages/${packageId}/versions`);
|
|
65
|
-
return response.json();
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Resolve dependency tree
|
|
69
|
-
*/
|
|
70
|
-
async resolveDependencies(packageId, version) {
|
|
71
|
-
const params = new URLSearchParams();
|
|
72
|
-
if (version)
|
|
73
|
-
params.append('version', version);
|
|
74
|
-
const response = await this.fetch(`/api/v1/packages/${packageId}/resolve?${params}`);
|
|
75
|
-
return response.json();
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Download package tarball
|
|
79
|
-
*/
|
|
80
|
-
async downloadPackage(tarballUrl, options = {}) {
|
|
81
|
-
// If format is specified and tarballUrl is from registry, append format param
|
|
82
|
-
let url = tarballUrl;
|
|
83
|
-
if (options.format && tarballUrl.includes(this.baseUrl)) {
|
|
84
|
-
const urlObj = new URL(tarballUrl);
|
|
85
|
-
urlObj.searchParams.set('format', options.format);
|
|
86
|
-
url = urlObj.toString();
|
|
87
|
-
}
|
|
88
|
-
const response = await fetch(url);
|
|
89
|
-
if (!response.ok) {
|
|
90
|
-
throw new Error(`Failed to download package: ${response.statusText}`);
|
|
91
|
-
}
|
|
92
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
93
|
-
return Buffer.from(arrayBuffer);
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Get trending packages
|
|
97
|
-
*/
|
|
98
|
-
async getTrending(format, subtype, limit = 20) {
|
|
99
|
-
const params = new URLSearchParams({ limit: limit.toString() });
|
|
100
|
-
if (format)
|
|
101
|
-
params.append('format', format);
|
|
102
|
-
if (subtype)
|
|
103
|
-
params.append('subtype', subtype);
|
|
104
|
-
const response = await this.fetch(`/api/v1/search/trending?${params}`);
|
|
105
|
-
const data = await response.json();
|
|
106
|
-
return data.packages;
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Get featured packages
|
|
110
|
-
*/
|
|
111
|
-
async getFeatured(format, subtype, limit = 20) {
|
|
112
|
-
const params = new URLSearchParams({ limit: limit.toString() });
|
|
113
|
-
if (format)
|
|
114
|
-
params.append('format', format);
|
|
115
|
-
if (subtype)
|
|
116
|
-
params.append('subtype', subtype);
|
|
117
|
-
const response = await this.fetch(`/api/v1/search/featured?${params}`);
|
|
118
|
-
const data = await response.json();
|
|
119
|
-
return data.packages;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Publish a package (requires authentication)
|
|
123
|
-
*/
|
|
124
|
-
async publish(manifest, tarball, options) {
|
|
125
|
-
if (!this.token) {
|
|
126
|
-
throw new Error('Authentication required. Run `prpm login` first.');
|
|
127
|
-
}
|
|
128
|
-
const formData = new FormData();
|
|
129
|
-
formData.append('manifest', JSON.stringify(manifest));
|
|
130
|
-
formData.append('tarball', new Blob([tarball]), 'package.tar.gz');
|
|
131
|
-
// Add org_id if provided
|
|
132
|
-
if (options?.orgId) {
|
|
133
|
-
formData.append('org_id', options.orgId);
|
|
134
|
-
}
|
|
135
|
-
const response = await this.fetch('/api/v1/packages', {
|
|
136
|
-
method: 'POST',
|
|
137
|
-
body: formData,
|
|
138
|
-
});
|
|
139
|
-
return response.json();
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Login and get authentication token
|
|
143
|
-
*/
|
|
144
|
-
async login() {
|
|
145
|
-
// This will open browser for GitHub OAuth
|
|
146
|
-
// For now, return placeholder - will implement OAuth flow
|
|
147
|
-
throw new Error('Login not yet implemented. Coming soon!');
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Get current user info
|
|
151
|
-
*/
|
|
152
|
-
async whoami() {
|
|
153
|
-
if (!this.token) {
|
|
154
|
-
throw new Error('Not authenticated. Run `prpm login` first.');
|
|
155
|
-
}
|
|
156
|
-
const response = await this.fetch('/api/v1/auth/me');
|
|
157
|
-
return response.json();
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Get collections
|
|
161
|
-
*/
|
|
162
|
-
async getCollections(options) {
|
|
163
|
-
const params = new URLSearchParams();
|
|
164
|
-
if (options?.category)
|
|
165
|
-
params.append('category', options.category);
|
|
166
|
-
if (options?.tag)
|
|
167
|
-
params.append('tag', options.tag);
|
|
168
|
-
if (options?.official)
|
|
169
|
-
params.append('official', 'true');
|
|
170
|
-
if (options?.scope)
|
|
171
|
-
params.append('scope', options.scope);
|
|
172
|
-
if (options?.limit)
|
|
173
|
-
params.append('limit', options.limit.toString());
|
|
174
|
-
if (options?.offset)
|
|
175
|
-
params.append('offset', options.offset.toString());
|
|
176
|
-
const response = await this.fetch(`/api/v1/collections?${params}`);
|
|
177
|
-
return response.json();
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Get collection details
|
|
181
|
-
*/
|
|
182
|
-
async getCollection(scope, id, version) {
|
|
183
|
-
const versionPath = version ? `/${version}` : '/1.0.0';
|
|
184
|
-
const response = await this.fetch(`/api/v1/collections/${scope}/${id}${versionPath}`);
|
|
185
|
-
return response.json();
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Install collection (get installation plan)
|
|
189
|
-
*/
|
|
190
|
-
async installCollection(options) {
|
|
191
|
-
const params = new URLSearchParams();
|
|
192
|
-
if (options.format)
|
|
193
|
-
params.append('format', options.format);
|
|
194
|
-
if (options.skipOptional)
|
|
195
|
-
params.append('skipOptional', 'true');
|
|
196
|
-
const versionPath = options.version ? `@${options.version}` : '';
|
|
197
|
-
const response = await this.fetch(`/api/v1/collections/${options.scope}/${options.id}${versionPath}/install?${params}`, { method: 'POST' });
|
|
198
|
-
return response.json();
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Create a collection (requires authentication)
|
|
202
|
-
*/
|
|
203
|
-
async createCollection(data) {
|
|
204
|
-
if (!this.token) {
|
|
205
|
-
throw new Error('Authentication required. Run `prpm login` first.');
|
|
206
|
-
}
|
|
207
|
-
const response = await this.fetch('/api/v1/collections', {
|
|
208
|
-
method: 'POST',
|
|
209
|
-
body: JSON.stringify(data),
|
|
210
|
-
});
|
|
211
|
-
return response.json();
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Helper method for making authenticated requests with retry logic
|
|
215
|
-
*/
|
|
216
|
-
async fetch(path, options = {}, retries = 3) {
|
|
217
|
-
const url = `${this.baseUrl}${path}`;
|
|
218
|
-
// Debug logging
|
|
219
|
-
if (process.env.DEBUG || process.env.PRPM_DEBUG) {
|
|
220
|
-
console.error(`[DEBUG] Fetching: ${url}`);
|
|
221
|
-
console.error(`[DEBUG] Method: ${options.method || 'GET'}`);
|
|
222
|
-
console.error(`[DEBUG] Has token: ${!!this.token}`);
|
|
223
|
-
}
|
|
224
|
-
const headers = {
|
|
225
|
-
'Content-Type': 'application/json',
|
|
226
|
-
'User-Agent': `prpm-cli/${CLI_VERSION}`,
|
|
227
|
-
...options.headers,
|
|
228
|
-
};
|
|
229
|
-
if (this.token) {
|
|
230
|
-
headers['Authorization'] = `Bearer ${this.token}`;
|
|
231
|
-
}
|
|
232
|
-
let lastError = null;
|
|
233
|
-
for (let attempt = 0; attempt < retries; attempt++) {
|
|
234
|
-
try {
|
|
235
|
-
if (process.env.DEBUG || process.env.PRPM_DEBUG) {
|
|
236
|
-
console.error(`[DEBUG] Attempt ${attempt + 1}/${retries}`);
|
|
237
|
-
}
|
|
238
|
-
const response = await fetch(url, {
|
|
239
|
-
...options,
|
|
240
|
-
headers,
|
|
241
|
-
});
|
|
242
|
-
// Handle rate limiting with retry
|
|
243
|
-
if (response.status === 429) {
|
|
244
|
-
const retryAfter = response.headers.get('Retry-After');
|
|
245
|
-
const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : Math.pow(2, attempt) * 1000;
|
|
246
|
-
if (attempt < retries - 1) {
|
|
247
|
-
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
// Handle server errors with retry
|
|
252
|
-
if (response.status >= 500 && response.status < 600 && attempt < retries - 1) {
|
|
253
|
-
const waitTime = Math.pow(2, attempt) * 1000;
|
|
254
|
-
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
if (!response.ok) {
|
|
258
|
-
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
259
|
-
throw new Error(error.error || error.message || `HTTP ${response.status}: ${response.statusText}`);
|
|
260
|
-
}
|
|
261
|
-
return response;
|
|
262
|
-
}
|
|
263
|
-
catch (error) {
|
|
264
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
265
|
-
if (process.env.DEBUG || process.env.PRPM_DEBUG) {
|
|
266
|
-
console.error(`[DEBUG] Error on attempt ${attempt + 1}:`, lastError.message);
|
|
267
|
-
console.error(`[DEBUG] Error type:`, lastError.constructor.name);
|
|
268
|
-
console.error(`[DEBUG] Full error:`, lastError);
|
|
269
|
-
}
|
|
270
|
-
// Network errors - retry with exponential backoff
|
|
271
|
-
if (attempt < retries - 1 && (lastError.message.includes('fetch failed') ||
|
|
272
|
-
lastError.message.includes('ECONNREFUSED') ||
|
|
273
|
-
lastError.message.includes('ETIMEDOUT'))) {
|
|
274
|
-
const waitTime = Math.pow(2, attempt) * 1000;
|
|
275
|
-
if (process.env.DEBUG || process.env.PRPM_DEBUG) {
|
|
276
|
-
console.error(`[DEBUG] Retrying after ${waitTime}ms...`);
|
|
277
|
-
}
|
|
278
|
-
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
// If it's not a retryable error or we're out of retries, throw with more context
|
|
282
|
-
if (attempt === retries - 1) {
|
|
283
|
-
const enhancedError = new Error(`Failed to connect to registry at ${url}\n` +
|
|
284
|
-
`Original error: ${lastError.message}\n\n` +
|
|
285
|
-
`💡 Possible causes:\n` +
|
|
286
|
-
` - Registry server is not running\n` +
|
|
287
|
-
` - Network connection issue\n` +
|
|
288
|
-
` - Incorrect PRPM_REGISTRY_URL (currently: ${this.baseUrl})`);
|
|
289
|
-
throw enhancedError;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
throw lastError || new Error(`Request failed after ${retries} retries to ${url}`);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
exports.RegistryClient = RegistryClient;
|
|
297
|
-
/**
|
|
298
|
-
* Get registry client with configuration
|
|
299
|
-
*/
|
|
300
|
-
function getRegistryClient(config) {
|
|
301
|
-
return new RegistryClient({
|
|
302
|
-
url: config.registryUrl || 'https://registry.prpm.dev',
|
|
303
|
-
token: config.token,
|
|
304
|
-
});
|
|
305
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* JSON Schema validation for PRPM manifests
|
|
4
|
-
*/
|
|
5
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
-
};
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.validateManifestSchema = validateManifestSchema;
|
|
10
|
-
exports.getManifestSchema = getManifestSchema;
|
|
11
|
-
const ajv_1 = __importDefault(require("ajv"));
|
|
12
|
-
const ajv_formats_1 = __importDefault(require("ajv-formats"));
|
|
13
|
-
const fs_1 = require("fs");
|
|
14
|
-
const path_1 = require("path");
|
|
15
|
-
// Load the JSON schema
|
|
16
|
-
const schemaPath = (0, path_1.join)(__dirname, '../../schemas/prpm-manifest.schema.json');
|
|
17
|
-
let schema;
|
|
18
|
-
try {
|
|
19
|
-
schema = JSON.parse((0, fs_1.readFileSync)(schemaPath, 'utf-8'));
|
|
20
|
-
}
|
|
21
|
-
catch (error) {
|
|
22
|
-
// Schema file not found, validation will be skipped
|
|
23
|
-
console.warn('⚠️ Could not load manifest schema, skipping schema validation');
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Validate manifest against JSON schema
|
|
27
|
-
*/
|
|
28
|
-
function validateManifestSchema(manifest) {
|
|
29
|
-
if (!schema) {
|
|
30
|
-
// Schema not loaded, skip validation
|
|
31
|
-
return { valid: true };
|
|
32
|
-
}
|
|
33
|
-
const ajv = new ajv_1.default({
|
|
34
|
-
allErrors: true,
|
|
35
|
-
verbose: true,
|
|
36
|
-
});
|
|
37
|
-
(0, ajv_formats_1.default)(ajv);
|
|
38
|
-
const validate = ajv.compile(schema);
|
|
39
|
-
const valid = validate(manifest);
|
|
40
|
-
if (!valid && validate.errors) {
|
|
41
|
-
const errors = validate.errors.map(err => {
|
|
42
|
-
const path = err.instancePath || 'manifest';
|
|
43
|
-
const message = err.message || 'validation failed';
|
|
44
|
-
// Format error messages to be more user-friendly
|
|
45
|
-
if (err.keyword === 'required') {
|
|
46
|
-
const missingProp = err.params.missingProperty;
|
|
47
|
-
return `Missing required field: ${missingProp}`;
|
|
48
|
-
}
|
|
49
|
-
if (err.keyword === 'pattern') {
|
|
50
|
-
return `${path}: ${message}. Value does not match required pattern.`;
|
|
51
|
-
}
|
|
52
|
-
if (err.keyword === 'enum') {
|
|
53
|
-
const allowedValues = err.params.allowedValues;
|
|
54
|
-
return `${path}: ${message}. Allowed values: ${allowedValues.join(', ')}`;
|
|
55
|
-
}
|
|
56
|
-
if (err.keyword === 'minLength' || err.keyword === 'maxLength') {
|
|
57
|
-
const limit = err.params.limit;
|
|
58
|
-
return `${path}: ${message} (${err.keyword}: ${limit})`;
|
|
59
|
-
}
|
|
60
|
-
if (err.keyword === 'oneOf') {
|
|
61
|
-
return `${path}: must match exactly one schema (check if files array uses either all strings or all objects, not mixed)`;
|
|
62
|
-
}
|
|
63
|
-
return `${path}: ${message}`;
|
|
64
|
-
});
|
|
65
|
-
return { valid: false, errors };
|
|
66
|
-
}
|
|
67
|
-
return { valid: true };
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Get the JSON schema (for documentation/export purposes)
|
|
71
|
-
*/
|
|
72
|
-
function getManifestSchema() {
|
|
73
|
-
return schema;
|
|
74
|
-
}
|