prpm 0.0.1

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.
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ /**
3
+ * Configuration management for .promptpm.json
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.readConfig = readConfig;
10
+ exports.writeConfig = writeConfig;
11
+ exports.addPackage = addPackage;
12
+ exports.removePackage = removePackage;
13
+ exports.getPackage = getPackage;
14
+ exports.listPackages = listPackages;
15
+ const fs_1 = require("fs");
16
+ const path_1 = __importDefault(require("path"));
17
+ const CONFIG_FILE = '.promptpm.json';
18
+ /**
19
+ * Read the configuration file from the current directory
20
+ */
21
+ async function readConfig() {
22
+ try {
23
+ const configPath = path_1.default.resolve(CONFIG_FILE);
24
+ const data = await fs_1.promises.readFile(configPath, 'utf-8');
25
+ return JSON.parse(data);
26
+ }
27
+ catch (error) {
28
+ // If file doesn't exist, return empty config
29
+ if (error.code === 'ENOENT') {
30
+ return { sources: [] };
31
+ }
32
+ throw new Error(`Failed to read config: ${error}`);
33
+ }
34
+ }
35
+ /**
36
+ * Write the configuration file to the current directory
37
+ */
38
+ async function writeConfig(config) {
39
+ try {
40
+ const configPath = path_1.default.resolve(CONFIG_FILE);
41
+ const data = JSON.stringify(config, null, 2);
42
+ await fs_1.promises.writeFile(configPath, data, 'utf-8');
43
+ }
44
+ catch (error) {
45
+ throw new Error(`Failed to write config: ${error}`);
46
+ }
47
+ }
48
+ /**
49
+ * Add a package to the configuration
50
+ */
51
+ async function addPackage(pkg) {
52
+ const config = await readConfig();
53
+ // Check if package with same ID already exists
54
+ const existingIndex = config.sources.findIndex(p => p.id === pkg.id);
55
+ if (existingIndex >= 0) {
56
+ // Update existing package
57
+ config.sources[existingIndex] = pkg;
58
+ }
59
+ else {
60
+ // Add new package
61
+ config.sources.push(pkg);
62
+ }
63
+ await writeConfig(config);
64
+ }
65
+ /**
66
+ * Remove a package from the configuration
67
+ */
68
+ async function removePackage(id) {
69
+ const config = await readConfig();
70
+ const index = config.sources.findIndex(p => p.id === id);
71
+ if (index === -1) {
72
+ return null;
73
+ }
74
+ const removed = config.sources.splice(index, 1)[0];
75
+ await writeConfig(config);
76
+ return removed;
77
+ }
78
+ /**
79
+ * Get a package by ID
80
+ */
81
+ async function getPackage(id) {
82
+ const config = await readConfig();
83
+ return config.sources.find(p => p.id === id) || null;
84
+ }
85
+ /**
86
+ * List all packages
87
+ */
88
+ async function listPackages() {
89
+ const config = await readConfig();
90
+ return config.sources;
91
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ /**
3
+ * HTTP file downloading functionality
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.downloadFile = downloadFile;
7
+ exports.extractFilename = extractFilename;
8
+ // Use Node.js built-in fetch (available in Node 18+)
9
+ /**
10
+ * Download a file from a URL
11
+ */
12
+ async function downloadFile(url) {
13
+ try {
14
+ // Validate URL format
15
+ if (!isValidUrl(url)) {
16
+ throw new Error('Invalid URL format');
17
+ }
18
+ const response = await fetch(url);
19
+ if (!response.ok) {
20
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
21
+ }
22
+ const content = await response.text();
23
+ return content;
24
+ }
25
+ catch (error) {
26
+ if (error instanceof Error) {
27
+ throw new Error(`Failed to download file: ${error.message}`);
28
+ }
29
+ throw new Error('Failed to download file: Unknown error');
30
+ }
31
+ }
32
+ /**
33
+ * Validate if URL is a valid raw GitHub URL
34
+ */
35
+ function isValidUrl(url) {
36
+ try {
37
+ const urlObj = new URL(url);
38
+ // For MVP, only support raw GitHub URLs
39
+ return (urlObj.protocol === 'https:' &&
40
+ (urlObj.hostname === 'raw.githubusercontent.com' ||
41
+ urlObj.hostname === 'github.com' && urlObj.pathname.includes('/raw/')));
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ /**
48
+ * Extract filename from URL
49
+ */
50
+ function extractFilename(url) {
51
+ try {
52
+ const urlObj = new URL(url);
53
+ const pathname = urlObj.pathname;
54
+ const filename = pathname.split('/').pop() || 'unknown';
55
+ // If no extension, assume it's a markdown file
56
+ if (!filename.includes('.')) {
57
+ return `${filename}.md`;
58
+ }
59
+ return filename;
60
+ }
61
+ catch {
62
+ return 'unknown.md';
63
+ }
64
+ }
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ /**
3
+ * File system operations for managing prompt files
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.getDestinationDir = getDestinationDir;
10
+ exports.ensureDirectoryExists = ensureDirectoryExists;
11
+ exports.saveFile = saveFile;
12
+ exports.deleteFile = deleteFile;
13
+ exports.fileExists = fileExists;
14
+ exports.generateId = generateId;
15
+ const fs_1 = require("fs");
16
+ const path_1 = __importDefault(require("path"));
17
+ /**
18
+ * Get the destination directory for a package type
19
+ */
20
+ function getDestinationDir(type) {
21
+ switch (type) {
22
+ case 'cursor':
23
+ return '.cursor/rules';
24
+ case 'claude':
25
+ return '.claude/agents';
26
+ default:
27
+ throw new Error(`Unknown package type: ${type}`);
28
+ }
29
+ }
30
+ /**
31
+ * Ensure directory exists, creating it if necessary
32
+ */
33
+ async function ensureDirectoryExists(dirPath) {
34
+ try {
35
+ await fs_1.promises.mkdir(dirPath, { recursive: true });
36
+ }
37
+ catch (error) {
38
+ throw new Error(`Failed to create directory ${dirPath}: ${error}`);
39
+ }
40
+ }
41
+ /**
42
+ * Save content to a file
43
+ */
44
+ async function saveFile(filePath, content) {
45
+ try {
46
+ // Ensure parent directory exists
47
+ const dir = path_1.default.dirname(filePath);
48
+ await ensureDirectoryExists(dir);
49
+ // Write file
50
+ await fs_1.promises.writeFile(filePath, content, 'utf-8');
51
+ }
52
+ catch (error) {
53
+ throw new Error(`Failed to save file ${filePath}: ${error}`);
54
+ }
55
+ }
56
+ /**
57
+ * Delete a file
58
+ */
59
+ async function deleteFile(filePath) {
60
+ try {
61
+ await fs_1.promises.unlink(filePath);
62
+ }
63
+ catch (error) {
64
+ const err = error;
65
+ if (err.code === 'ENOENT') {
66
+ // File doesn't exist, that's fine
67
+ return;
68
+ }
69
+ throw new Error(`Failed to delete file ${filePath}: ${error}`);
70
+ }
71
+ }
72
+ /**
73
+ * Check if a file exists
74
+ */
75
+ async function fileExists(filePath) {
76
+ try {
77
+ await fs_1.promises.access(filePath);
78
+ return true;
79
+ }
80
+ catch {
81
+ return false;
82
+ }
83
+ }
84
+ /**
85
+ * Generate a unique ID from filename
86
+ */
87
+ function generateId(filename) {
88
+ // Remove extension and convert to kebab-case
89
+ const name = filename.replace(/\.[^/.]+$/, '');
90
+ return name
91
+ .toLowerCase()
92
+ .replace(/[^a-z0-9]+/g, '-')
93
+ .replace(/^-+|-+$/g, '');
94
+ }
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ /**
3
+ * Lock file management for reproducible installations
4
+ * prpm.lock format similar to package-lock.json
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.readLockfile = readLockfile;
8
+ exports.writeLockfile = writeLockfile;
9
+ exports.createLockfile = createLockfile;
10
+ exports.addToLockfile = addToLockfile;
11
+ exports.setPackageIntegrity = setPackageIntegrity;
12
+ exports.verifyPackageIntegrity = verifyPackageIntegrity;
13
+ exports.getLockedVersion = getLockedVersion;
14
+ exports.isLockfileOutOfSync = isLockfileOutOfSync;
15
+ exports.mergeLockfiles = mergeLockfiles;
16
+ exports.pruneLockfile = pruneLockfile;
17
+ const fs_1 = require("fs");
18
+ const path_1 = require("path");
19
+ const crypto_1 = require("crypto");
20
+ const LOCKFILE_NAME = 'prpm.lock';
21
+ const LOCKFILE_VERSION = 1;
22
+ /**
23
+ * Read lock file from current directory
24
+ */
25
+ async function readLockfile(cwd = process.cwd()) {
26
+ try {
27
+ const lockfilePath = (0, path_1.join)(cwd, LOCKFILE_NAME);
28
+ const content = await fs_1.promises.readFile(lockfilePath, 'utf-8');
29
+ return JSON.parse(content);
30
+ }
31
+ catch (error) {
32
+ if (error.code === 'ENOENT') {
33
+ return null; // Lock file doesn't exist
34
+ }
35
+ throw new Error(`Failed to read lock file: ${error}`);
36
+ }
37
+ }
38
+ /**
39
+ * Write lock file to current directory
40
+ */
41
+ async function writeLockfile(lockfile, cwd = process.cwd()) {
42
+ try {
43
+ const lockfilePath = (0, path_1.join)(cwd, LOCKFILE_NAME);
44
+ const content = JSON.stringify(lockfile, null, 2);
45
+ await fs_1.promises.writeFile(lockfilePath, content, 'utf-8');
46
+ }
47
+ catch (error) {
48
+ throw new Error(`Failed to write lock file: ${error}`);
49
+ }
50
+ }
51
+ /**
52
+ * Create new lock file
53
+ */
54
+ function createLockfile() {
55
+ return {
56
+ version: '1.0.0',
57
+ lockfileVersion: LOCKFILE_VERSION,
58
+ packages: {},
59
+ generated: new Date().toISOString(),
60
+ };
61
+ }
62
+ /**
63
+ * Add package to lock file
64
+ */
65
+ function addToLockfile(lockfile, packageId, packageInfo) {
66
+ lockfile.packages[packageId] = {
67
+ version: packageInfo.version,
68
+ resolved: packageInfo.tarballUrl,
69
+ integrity: '', // Will be set after download
70
+ dependencies: packageInfo.dependencies,
71
+ type: packageInfo.type,
72
+ format: packageInfo.format,
73
+ };
74
+ lockfile.generated = new Date().toISOString();
75
+ }
76
+ /**
77
+ * Update package integrity hash after download
78
+ */
79
+ function setPackageIntegrity(lockfile, packageId, tarballBuffer) {
80
+ if (!lockfile.packages[packageId]) {
81
+ throw new Error(`Package ${packageId} not found in lock file`);
82
+ }
83
+ const hash = (0, crypto_1.createHash)('sha256').update(tarballBuffer).digest('hex');
84
+ lockfile.packages[packageId].integrity = `sha256-${hash}`;
85
+ }
86
+ /**
87
+ * Verify package integrity
88
+ */
89
+ function verifyPackageIntegrity(lockfile, packageId, tarballBuffer) {
90
+ const pkg = lockfile.packages[packageId];
91
+ if (!pkg || !pkg.integrity) {
92
+ return false;
93
+ }
94
+ const hash = (0, crypto_1.createHash)('sha256').update(tarballBuffer).digest('hex');
95
+ const expectedHash = pkg.integrity.replace('sha256-', '');
96
+ return hash === expectedHash;
97
+ }
98
+ /**
99
+ * Get locked version for a package
100
+ */
101
+ function getLockedVersion(lockfile, packageId) {
102
+ if (!lockfile || !lockfile.packages[packageId]) {
103
+ return null;
104
+ }
105
+ return lockfile.packages[packageId].version;
106
+ }
107
+ /**
108
+ * Check if lock file is out of sync with dependencies
109
+ */
110
+ function isLockfileOutOfSync(lockfile, requiredPackages) {
111
+ if (!lockfile) {
112
+ return true;
113
+ }
114
+ // Check if all required packages are in lock file
115
+ for (const [pkgId, version] of Object.entries(requiredPackages)) {
116
+ const locked = lockfile.packages[pkgId];
117
+ if (!locked || locked.version !== version) {
118
+ return true;
119
+ }
120
+ }
121
+ return false;
122
+ }
123
+ /**
124
+ * Merge lock files (for conflict resolution)
125
+ */
126
+ function mergeLockfiles(base, incoming) {
127
+ const merged = createLockfile();
128
+ // Merge packages from both lock files
129
+ const allPackages = new Set([
130
+ ...Object.keys(base.packages),
131
+ ...Object.keys(incoming.packages),
132
+ ]);
133
+ for (const pkgId of allPackages) {
134
+ const basePkg = base.packages[pkgId];
135
+ const incomingPkg = incoming.packages[pkgId];
136
+ if (!basePkg) {
137
+ merged.packages[pkgId] = incomingPkg;
138
+ }
139
+ else if (!incomingPkg) {
140
+ merged.packages[pkgId] = basePkg;
141
+ }
142
+ else {
143
+ // Both exist - prefer newer version
144
+ const baseVersion = basePkg.version;
145
+ const incomingVersion = incomingPkg.version;
146
+ merged.packages[pkgId] = compareVersions(baseVersion, incomingVersion) >= 0
147
+ ? basePkg
148
+ : incomingPkg;
149
+ }
150
+ }
151
+ return merged;
152
+ }
153
+ /**
154
+ * Simple semver comparison (returns 1 if a > b, -1 if a < b, 0 if equal)
155
+ */
156
+ function compareVersions(a, b) {
157
+ const aParts = a.split('.').map(Number);
158
+ const bParts = b.split('.').map(Number);
159
+ for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
160
+ const aVal = aParts[i] || 0;
161
+ const bVal = bParts[i] || 0;
162
+ if (aVal > bVal)
163
+ return 1;
164
+ if (aVal < bVal)
165
+ return -1;
166
+ }
167
+ return 0;
168
+ }
169
+ /**
170
+ * Prune unused packages from lock file
171
+ */
172
+ function pruneLockfile(lockfile, requiredPackages) {
173
+ const pruned = { ...lockfile };
174
+ pruned.packages = {};
175
+ for (const pkgId of requiredPackages) {
176
+ if (lockfile.packages[pkgId]) {
177
+ pruned.packages[pkgId] = lockfile.packages[pkgId];
178
+ }
179
+ }
180
+ pruned.generated = new Date().toISOString();
181
+ return pruned;
182
+ }
@@ -0,0 +1,265 @@
1
+ "use strict";
2
+ /**
3
+ * Registry API Client
4
+ * Handles all communication with the PRMP Registry
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.RegistryClient = void 0;
8
+ exports.getRegistryClient = getRegistryClient;
9
+ class RegistryClient {
10
+ constructor(config) {
11
+ this.baseUrl = config.url.replace(/\/$/, ''); // Remove trailing slash
12
+ this.token = config.token;
13
+ }
14
+ /**
15
+ * Search for packages in the registry
16
+ */
17
+ async search(query, options) {
18
+ const params = new URLSearchParams({ q: query });
19
+ if (options?.type)
20
+ params.append('type', options.type);
21
+ if (options?.tags)
22
+ options.tags.forEach(tag => params.append('tags', tag));
23
+ if (options?.limit)
24
+ params.append('limit', options.limit.toString());
25
+ if (options?.offset)
26
+ params.append('offset', options.offset.toString());
27
+ const response = await this.fetch(`/api/v1/search?${params}`);
28
+ return response.json();
29
+ }
30
+ /**
31
+ * Get package information
32
+ */
33
+ async getPackage(packageId) {
34
+ const response = await this.fetch(`/api/v1/packages/${packageId}`);
35
+ return response.json();
36
+ }
37
+ /**
38
+ * Get specific package version
39
+ */
40
+ async getPackageVersion(packageId, version) {
41
+ const response = await this.fetch(`/api/v1/packages/${packageId}/${version}`);
42
+ return response.json();
43
+ }
44
+ /**
45
+ * Get package dependencies
46
+ */
47
+ async getPackageDependencies(packageId, version) {
48
+ const versionPath = version ? `/${version}` : '';
49
+ const response = await this.fetch(`/api/v1/packages/${packageId}${versionPath}/dependencies`);
50
+ return response.json();
51
+ }
52
+ /**
53
+ * Get all versions for a package
54
+ */
55
+ async getPackageVersions(packageId) {
56
+ const response = await this.fetch(`/api/v1/packages/${packageId}/versions`);
57
+ return response.json();
58
+ }
59
+ /**
60
+ * Resolve dependency tree
61
+ */
62
+ async resolveDependencies(packageId, version) {
63
+ const params = new URLSearchParams();
64
+ if (version)
65
+ params.append('version', version);
66
+ const response = await this.fetch(`/api/v1/packages/${packageId}/resolve?${params}`);
67
+ return response.json();
68
+ }
69
+ /**
70
+ * Download package tarball
71
+ */
72
+ async downloadPackage(tarballUrl, options = {}) {
73
+ // If format is specified and tarballUrl is from registry, append format param
74
+ let url = tarballUrl;
75
+ if (options.format && tarballUrl.includes(this.baseUrl)) {
76
+ const urlObj = new URL(tarballUrl);
77
+ urlObj.searchParams.set('format', options.format);
78
+ url = urlObj.toString();
79
+ }
80
+ const response = await fetch(url);
81
+ if (!response.ok) {
82
+ throw new Error(`Failed to download package: ${response.statusText}`);
83
+ }
84
+ const arrayBuffer = await response.arrayBuffer();
85
+ return Buffer.from(arrayBuffer);
86
+ }
87
+ /**
88
+ * Get trending packages
89
+ */
90
+ async getTrending(type, limit = 20) {
91
+ const params = new URLSearchParams({ limit: limit.toString() });
92
+ if (type)
93
+ params.append('type', type);
94
+ const response = await this.fetch(`/api/v1/search/trending?${params}`);
95
+ const data = await response.json();
96
+ return data.packages;
97
+ }
98
+ /**
99
+ * Get featured packages
100
+ */
101
+ async getFeatured(type, limit = 20) {
102
+ const params = new URLSearchParams({ limit: limit.toString() });
103
+ if (type)
104
+ params.append('type', type);
105
+ const response = await this.fetch(`/api/v1/search/featured?${params}`);
106
+ const data = await response.json();
107
+ return data.packages;
108
+ }
109
+ /**
110
+ * Publish a package (requires authentication)
111
+ */
112
+ async publish(manifest, tarball) {
113
+ if (!this.token) {
114
+ throw new Error('Authentication required. Run `prpm login` first.');
115
+ }
116
+ const formData = new FormData();
117
+ formData.append('manifest', JSON.stringify(manifest));
118
+ formData.append('tarball', new Blob([tarball]), 'package.tar.gz');
119
+ const response = await this.fetch('/api/v1/packages', {
120
+ method: 'POST',
121
+ body: formData,
122
+ });
123
+ return response.json();
124
+ }
125
+ /**
126
+ * Login and get authentication token
127
+ */
128
+ async login() {
129
+ // This will open browser for GitHub OAuth
130
+ // For now, return placeholder - will implement OAuth flow
131
+ throw new Error('Login not yet implemented. Coming soon!');
132
+ }
133
+ /**
134
+ * Get current user info
135
+ */
136
+ async whoami() {
137
+ if (!this.token) {
138
+ throw new Error('Not authenticated. Run `prpm login` first.');
139
+ }
140
+ const response = await this.fetch('/api/v1/auth/me');
141
+ return response.json();
142
+ }
143
+ /**
144
+ * Get collections
145
+ */
146
+ async getCollections(options) {
147
+ const params = new URLSearchParams();
148
+ if (options?.category)
149
+ params.append('category', options.category);
150
+ if (options?.tag)
151
+ params.append('tag', options.tag);
152
+ if (options?.official)
153
+ params.append('official', 'true');
154
+ if (options?.scope)
155
+ params.append('scope', options.scope);
156
+ if (options?.limit)
157
+ params.append('limit', options.limit.toString());
158
+ if (options?.offset)
159
+ params.append('offset', options.offset.toString());
160
+ const response = await this.fetch(`/api/v1/collections?${params}`);
161
+ return response.json();
162
+ }
163
+ /**
164
+ * Get collection details
165
+ */
166
+ async getCollection(scope, id, version) {
167
+ const versionPath = version ? `/${version}` : '/1.0.0';
168
+ const response = await this.fetch(`/api/v1/collections/${scope}/${id}${versionPath}`);
169
+ return response.json();
170
+ }
171
+ /**
172
+ * Install collection (get installation plan)
173
+ */
174
+ async installCollection(options) {
175
+ const params = new URLSearchParams();
176
+ if (options.format)
177
+ params.append('format', options.format);
178
+ if (options.skipOptional)
179
+ params.append('skipOptional', 'true');
180
+ const versionPath = options.version ? `@${options.version}` : '';
181
+ const response = await this.fetch(`/api/v1/collections/${options.scope}/${options.id}${versionPath}/install?${params}`, { method: 'POST' });
182
+ return response.json();
183
+ }
184
+ /**
185
+ * Create a collection (requires authentication)
186
+ */
187
+ async createCollection(data) {
188
+ if (!this.token) {
189
+ throw new Error('Authentication required. Run `prpm login` first.');
190
+ }
191
+ const response = await this.fetch('/api/v1/collections', {
192
+ method: 'POST',
193
+ body: JSON.stringify(data),
194
+ });
195
+ return response.json();
196
+ }
197
+ /**
198
+ * Helper method for making authenticated requests with retry logic
199
+ */
200
+ async fetch(path, options = {}, retries = 3) {
201
+ const url = `${this.baseUrl}${path}`;
202
+ const headers = {
203
+ 'Content-Type': 'application/json',
204
+ ...options.headers,
205
+ };
206
+ if (this.token) {
207
+ headers['Authorization'] = `Bearer ${this.token}`;
208
+ }
209
+ let lastError = null;
210
+ for (let attempt = 0; attempt < retries; attempt++) {
211
+ try {
212
+ const response = await fetch(url, {
213
+ ...options,
214
+ headers,
215
+ });
216
+ // Handle rate limiting with retry
217
+ if (response.status === 429) {
218
+ const retryAfter = response.headers.get('Retry-After');
219
+ const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : Math.pow(2, attempt) * 1000;
220
+ if (attempt < retries - 1) {
221
+ await new Promise(resolve => setTimeout(resolve, waitTime));
222
+ continue;
223
+ }
224
+ }
225
+ // Handle server errors with retry
226
+ if (response.status >= 500 && response.status < 600 && attempt < retries - 1) {
227
+ const waitTime = Math.pow(2, attempt) * 1000;
228
+ await new Promise(resolve => setTimeout(resolve, waitTime));
229
+ continue;
230
+ }
231
+ if (!response.ok) {
232
+ const error = await response.json().catch(() => ({ error: response.statusText }));
233
+ throw new Error(error.error || error.message || `HTTP ${response.status}: ${response.statusText}`);
234
+ }
235
+ return response;
236
+ }
237
+ catch (error) {
238
+ lastError = error instanceof Error ? error : new Error(String(error));
239
+ // Network errors - retry with exponential backoff
240
+ if (attempt < retries - 1 && (lastError.message.includes('fetch failed') ||
241
+ lastError.message.includes('ECONNREFUSED') ||
242
+ lastError.message.includes('ETIMEDOUT'))) {
243
+ const waitTime = Math.pow(2, attempt) * 1000;
244
+ await new Promise(resolve => setTimeout(resolve, waitTime));
245
+ continue;
246
+ }
247
+ // If it's not a retryable error or we're out of retries, throw
248
+ if (attempt === retries - 1) {
249
+ throw lastError;
250
+ }
251
+ }
252
+ }
253
+ throw lastError || new Error('Request failed after retries');
254
+ }
255
+ }
256
+ exports.RegistryClient = RegistryClient;
257
+ /**
258
+ * Get registry client with configuration
259
+ */
260
+ function getRegistryClient(config) {
261
+ return new RegistryClient({
262
+ url: config.registryUrl || 'https://registry.prpm.dev',
263
+ token: config.token,
264
+ });
265
+ }