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
package/dist/commands/install.js
DELETED
|
@@ -1,789 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Install command - Install packages from registry
|
|
4
|
-
*/
|
|
5
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
-
if (k2 === undefined) k2 = k;
|
|
7
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
-
}
|
|
11
|
-
Object.defineProperty(o, k2, desc);
|
|
12
|
-
}) : (function(o, m, k, k2) {
|
|
13
|
-
if (k2 === undefined) k2 = k;
|
|
14
|
-
o[k2] = m[k];
|
|
15
|
-
}));
|
|
16
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
-
}) : function(o, v) {
|
|
19
|
-
o["default"] = v;
|
|
20
|
-
});
|
|
21
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
-
var ownKeys = function(o) {
|
|
23
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
-
var ar = [];
|
|
25
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
-
return ar;
|
|
27
|
-
};
|
|
28
|
-
return ownKeys(o);
|
|
29
|
-
};
|
|
30
|
-
return function (mod) {
|
|
31
|
-
if (mod && mod.__esModule) return mod;
|
|
32
|
-
var result = {};
|
|
33
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
-
__setModuleDefault(result, mod);
|
|
35
|
-
return result;
|
|
36
|
-
};
|
|
37
|
-
})();
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.handleInstall = handleInstall;
|
|
40
|
-
exports.installFromLockfile = installFromLockfile;
|
|
41
|
-
exports.createInstallCommand = createInstallCommand;
|
|
42
|
-
const commander_1 = require("commander");
|
|
43
|
-
const registry_client_1 = require("@pr-pm/registry-client");
|
|
44
|
-
const user_config_1 = require("../core/user-config");
|
|
45
|
-
const filesystem_1 = require("../core/filesystem");
|
|
46
|
-
const telemetry_1 = require("../core/telemetry");
|
|
47
|
-
const tar = __importStar(require("tar"));
|
|
48
|
-
const errors_1 = require("../core/errors");
|
|
49
|
-
const lockfile_1 = require("../core/lockfile");
|
|
50
|
-
const cursor_config_1 = require("../core/cursor-config");
|
|
51
|
-
const claude_config_1 = require("../core/claude-config");
|
|
52
|
-
/**
|
|
53
|
-
* Get icon for package format and subtype
|
|
54
|
-
*/
|
|
55
|
-
function getPackageIcon(format, subtype) {
|
|
56
|
-
// Subtype icons take precedence
|
|
57
|
-
const subtypeIcons = {
|
|
58
|
-
'skill': 'š',
|
|
59
|
-
'agent': 'š¤',
|
|
60
|
-
'slash-command': 'ā”',
|
|
61
|
-
'rule': 'š',
|
|
62
|
-
'prompt': 'š¬',
|
|
63
|
-
'collection': 'š¦',
|
|
64
|
-
'chatmode': 'š¬',
|
|
65
|
-
'tool': 'š§',
|
|
66
|
-
'hook': 'šŖ',
|
|
67
|
-
};
|
|
68
|
-
// Format-specific icons for rules/defaults
|
|
69
|
-
const formatIcons = {
|
|
70
|
-
'claude': 'š¤',
|
|
71
|
-
'cursor': 'š',
|
|
72
|
-
'windsurf': 'š',
|
|
73
|
-
'continue': 'ā”ļø',
|
|
74
|
-
'copilot': 'āļø',
|
|
75
|
-
'kiro': 'šÆ',
|
|
76
|
-
'mcp': 'š',
|
|
77
|
-
'agents.md': 'š',
|
|
78
|
-
'generic': 'š¦',
|
|
79
|
-
};
|
|
80
|
-
return subtypeIcons[subtype] || formatIcons[format] || 'š¦';
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Get human-readable label for package format and subtype
|
|
84
|
-
*/
|
|
85
|
-
function getPackageLabel(format, subtype) {
|
|
86
|
-
const formatLabels = {
|
|
87
|
-
'claude': 'Claude',
|
|
88
|
-
'cursor': 'Cursor',
|
|
89
|
-
'windsurf': 'Windsurf',
|
|
90
|
-
'continue': 'Continue',
|
|
91
|
-
'copilot': 'GitHub Copilot',
|
|
92
|
-
'kiro': 'Kiro',
|
|
93
|
-
'mcp': 'MCP',
|
|
94
|
-
'agents.md': 'Agents.md',
|
|
95
|
-
'generic': '',
|
|
96
|
-
};
|
|
97
|
-
const subtypeLabels = {
|
|
98
|
-
'skill': 'Skill',
|
|
99
|
-
'agent': 'Agent',
|
|
100
|
-
'slash-command': 'Slash Command',
|
|
101
|
-
'rule': 'Rule',
|
|
102
|
-
'prompt': 'Prompt',
|
|
103
|
-
'collection': 'Collection',
|
|
104
|
-
'chatmode': 'Chat Mode',
|
|
105
|
-
'tool': 'Tool',
|
|
106
|
-
'hook': 'Hook',
|
|
107
|
-
};
|
|
108
|
-
const formatLabel = formatLabels[format];
|
|
109
|
-
const subtypeLabel = subtypeLabels[subtype];
|
|
110
|
-
if (format === 'generic') {
|
|
111
|
-
return subtypeLabel;
|
|
112
|
-
}
|
|
113
|
-
return `${formatLabel} ${subtypeLabel}`;
|
|
114
|
-
}
|
|
115
|
-
async function handleInstall(packageSpec, options) {
|
|
116
|
-
const startTime = Date.now();
|
|
117
|
-
let success = false;
|
|
118
|
-
let error;
|
|
119
|
-
try {
|
|
120
|
-
// Check if this is explicitly a collection install (collections/name)
|
|
121
|
-
if (packageSpec.startsWith('collections/')) {
|
|
122
|
-
const collectionId = packageSpec.replace('collections/', '');
|
|
123
|
-
console.log(`š„ Installing ${collectionId}@latest...`);
|
|
124
|
-
const { handleCollectionInstall } = await Promise.resolve().then(() => __importStar(require('./collections.js')));
|
|
125
|
-
return await handleCollectionInstall(collectionId, {
|
|
126
|
-
format: options.as,
|
|
127
|
-
skipOptional: false,
|
|
128
|
-
dryRun: false,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
// Parse package spec (e.g., "react-rules" or "react-rules@1.2.0" or "@pr-pm/pkg@1.0.0")
|
|
132
|
-
// For scoped packages (@scope/name), the first @ is part of the package name
|
|
133
|
-
let packageId;
|
|
134
|
-
let specVersion;
|
|
135
|
-
if (packageSpec.startsWith('@')) {
|
|
136
|
-
// Scoped package: @scope/name or @scope/name@version
|
|
137
|
-
const match = packageSpec.match(/^(@[^/]+\/[^@]+)(?:@(.+))?$/);
|
|
138
|
-
if (!match) {
|
|
139
|
-
throw new Error('Invalid package spec format. Use: @scope/package or @scope/package@version');
|
|
140
|
-
}
|
|
141
|
-
packageId = match[1];
|
|
142
|
-
specVersion = match[2];
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
// Unscoped package: name or name@version
|
|
146
|
-
const parts = packageSpec.split('@');
|
|
147
|
-
packageId = parts[0];
|
|
148
|
-
specVersion = parts[1];
|
|
149
|
-
}
|
|
150
|
-
// Read existing lock file
|
|
151
|
-
const lockfile = await (0, lockfile_1.readLockfile)();
|
|
152
|
-
const lockedVersion = (0, lockfile_1.getLockedVersion)(lockfile, packageId);
|
|
153
|
-
// Determine version to install
|
|
154
|
-
let version;
|
|
155
|
-
if (options.frozenLockfile) {
|
|
156
|
-
// Frozen lockfile mode - must use exact locked version
|
|
157
|
-
if (!lockedVersion) {
|
|
158
|
-
throw new Error(`Package ${packageId} not found in lock file. Run without --frozen-lockfile to update.`);
|
|
159
|
-
}
|
|
160
|
-
version = lockedVersion;
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
// Normal mode - use specified version or locked version or latest
|
|
164
|
-
version = options.version || specVersion || lockedVersion || 'latest';
|
|
165
|
-
}
|
|
166
|
-
// Check if package is already installed (skip if --force option is set)
|
|
167
|
-
if (!options.force && lockfile && lockfile.packages[packageId]) {
|
|
168
|
-
const installedPkg = lockfile.packages[packageId];
|
|
169
|
-
const requestedVersion = options.version || specVersion;
|
|
170
|
-
// If no specific version requested, or same version requested
|
|
171
|
-
if (!requestedVersion || requestedVersion === 'latest' || requestedVersion === installedPkg.version) {
|
|
172
|
-
console.log(`\n⨠Package already installed!`);
|
|
173
|
-
console.log(` š¦ ${packageId}@${installedPkg.version}`);
|
|
174
|
-
console.log(` š Format: ${installedPkg.format || 'unknown'} | Subtype: ${installedPkg.subtype || 'unknown'}`);
|
|
175
|
-
console.log(`\nš” To reinstall or upgrade:`);
|
|
176
|
-
console.log(` prpm upgrade ${packageId} # Upgrade to latest version`);
|
|
177
|
-
console.log(` prpm uninstall ${packageId} # Uninstall first, then install`);
|
|
178
|
-
success = true;
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
else if (requestedVersion !== installedPkg.version) {
|
|
182
|
-
// Different version requested - allow upgrade/downgrade
|
|
183
|
-
console.log(`š¦ Upgrading ${packageId}: ${installedPkg.version} ā ${requestedVersion}`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
console.log(`š„ Installing ${packageId}@${version}...`);
|
|
187
|
-
const config = await (0, user_config_1.getConfig)();
|
|
188
|
-
const client = (0, registry_client_1.getRegistryClient)(config);
|
|
189
|
-
// Check if this is a collection first (by trying to fetch it)
|
|
190
|
-
// Collections can be: name, scope/name, or @scope/name
|
|
191
|
-
let isCollection = false;
|
|
192
|
-
try {
|
|
193
|
-
// Try to parse as collection
|
|
194
|
-
let scope;
|
|
195
|
-
let name_slug;
|
|
196
|
-
const matchWithScope = packageId.match(/^@?([^/]+)\/([^/@]+)$/);
|
|
197
|
-
if (matchWithScope) {
|
|
198
|
-
[, scope, name_slug] = matchWithScope;
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
// No scope, assume 'collection' scope
|
|
202
|
-
scope = 'collection';
|
|
203
|
-
name_slug = packageId;
|
|
204
|
-
}
|
|
205
|
-
// Try to fetch as collection
|
|
206
|
-
await client.getCollection(scope, name_slug, version === 'latest' ? undefined : version);
|
|
207
|
-
isCollection = true;
|
|
208
|
-
// If successful, delegate to collection install handler
|
|
209
|
-
const { handleCollectionInstall } = await Promise.resolve().then(() => __importStar(require('./collections.js')));
|
|
210
|
-
return await handleCollectionInstall(packageId, {
|
|
211
|
-
format: options.as,
|
|
212
|
-
skipOptional: false,
|
|
213
|
-
dryRun: false,
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
catch (err) {
|
|
217
|
-
// Not a collection, continue with package install
|
|
218
|
-
isCollection = false;
|
|
219
|
-
}
|
|
220
|
-
// Get package info
|
|
221
|
-
const pkg = await client.getPackage(packageId);
|
|
222
|
-
const typeIcon = getPackageIcon(pkg.format, pkg.subtype);
|
|
223
|
-
const typeLabel = getPackageLabel(pkg.format, pkg.subtype);
|
|
224
|
-
console.log(` ${pkg.name} ${pkg.official ? 'š
' : ''}`);
|
|
225
|
-
console.log(` ${pkg.description || 'No description'}`);
|
|
226
|
-
console.log(` ${typeIcon} Type: ${typeLabel}`);
|
|
227
|
-
// Check if this is a Claude hook and show informational message
|
|
228
|
-
if (pkg.format === 'claude' && pkg.subtype === 'hook') {
|
|
229
|
-
// Only show detailed warning if not part of a collection (to avoid spam)
|
|
230
|
-
if (!options.fromCollection) {
|
|
231
|
-
console.log(`\nš Installing Claude Hook`);
|
|
232
|
-
console.log(` ā ļø Note: Hooks execute shell commands automatically.`);
|
|
233
|
-
console.log(` š Review the hook configuration in .claude/settings.json after installation.`);
|
|
234
|
-
console.log();
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
// Brief message for collection installs
|
|
238
|
-
console.log(` šŖ Hook (merges into .claude/settings.json)`);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
// Determine format preference with priority order:
|
|
242
|
-
// 1. CLI --as flag (highest priority)
|
|
243
|
-
// 2. defaultFormat from .prpmrc config
|
|
244
|
-
// 3. Auto-detection based on existing directories
|
|
245
|
-
// 4. Package native format (fallback)
|
|
246
|
-
let format = options.as;
|
|
247
|
-
if (!format) {
|
|
248
|
-
// Check for config default format
|
|
249
|
-
if (config.defaultFormat) {
|
|
250
|
-
format = config.defaultFormat;
|
|
251
|
-
console.log(` āļø Using default format from config: ${format}`);
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
// Auto-detect format based on existing directories
|
|
255
|
-
const detectedFormat = await (0, filesystem_1.autoDetectFormat)();
|
|
256
|
-
if (detectedFormat) {
|
|
257
|
-
format = detectedFormat;
|
|
258
|
-
console.log(` š Auto-detected ${format} format (found .${format}/ directory)`);
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
// No config or detection, use package's native format
|
|
262
|
-
format = pkg.format;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
// Special handling for Claude packages: default to CLAUDE.md if it doesn't exist
|
|
267
|
-
// BUT only for packages that are generic rules (not skills, agents, or commands)
|
|
268
|
-
if (!options.as && pkg.format === 'claude' && pkg.subtype === 'rule') {
|
|
269
|
-
const { fileExists } = await Promise.resolve().then(() => __importStar(require('../core/filesystem')));
|
|
270
|
-
const claudeMdExists = await fileExists('CLAUDE.md');
|
|
271
|
-
if (!claudeMdExists) {
|
|
272
|
-
// CLAUDE.md doesn't exist, install as CLAUDE.md (recommended format for Claude Code)
|
|
273
|
-
format = 'claude-md';
|
|
274
|
-
console.log(` š” Installing as CLAUDE.md (recommended for Claude Code)`);
|
|
275
|
-
console.log(` To install as skill instead, use: prpm install ${packageId} --as claude`);
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
// CLAUDE.md already exists, install as skill to avoid overwriting
|
|
279
|
-
console.log(` ā¹ļø CLAUDE.md already exists, installing as skill in .claude/skills/`);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
if (options.as && format !== 'canonical') {
|
|
283
|
-
console.log(` š Converting to ${format} format...`);
|
|
284
|
-
}
|
|
285
|
-
// Determine version to install
|
|
286
|
-
let tarballUrl;
|
|
287
|
-
let actualVersion;
|
|
288
|
-
if (version === 'latest') {
|
|
289
|
-
if (!pkg.latest_version) {
|
|
290
|
-
throw new Error('No versions available for this package');
|
|
291
|
-
}
|
|
292
|
-
tarballUrl = pkg.latest_version.tarball_url;
|
|
293
|
-
actualVersion = pkg.latest_version.version;
|
|
294
|
-
console.log(` š¦ Installing version ${pkg.latest_version.version}`);
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
const versionInfo = await client.getPackageVersion(packageId, version);
|
|
298
|
-
tarballUrl = versionInfo.tarball_url;
|
|
299
|
-
actualVersion = version;
|
|
300
|
-
console.log(` š¦ Installing version ${version}`);
|
|
301
|
-
}
|
|
302
|
-
// Download package in requested format
|
|
303
|
-
console.log(` ā¬ļø Downloading...`);
|
|
304
|
-
const tarball = await client.downloadPackage(tarballUrl, { format });
|
|
305
|
-
// Extract tarball and save files
|
|
306
|
-
console.log(` š Extracting...`);
|
|
307
|
-
// Determine effective format and subtype (from conversion or package native format)
|
|
308
|
-
const effectiveFormat = format || pkg.format;
|
|
309
|
-
const effectiveSubtype = options.subtype || pkg.subtype;
|
|
310
|
-
// Extract all files from tarball
|
|
311
|
-
const extractedFiles = await extractTarball(tarball, packageId);
|
|
312
|
-
// Track where files were saved for user feedback
|
|
313
|
-
let destPath;
|
|
314
|
-
let fileCount = 0;
|
|
315
|
-
let hookMetadata = undefined;
|
|
316
|
-
// Special handling for CLAUDE.md format (goes in project root)
|
|
317
|
-
if (format === 'claude-md') {
|
|
318
|
-
if (extractedFiles.length !== 1) {
|
|
319
|
-
throw new Error('CLAUDE.md format only supports single-file packages');
|
|
320
|
-
}
|
|
321
|
-
let mainFile = extractedFiles[0].content;
|
|
322
|
-
destPath = 'CLAUDE.md';
|
|
323
|
-
await (0, filesystem_1.saveFile)(destPath, mainFile);
|
|
324
|
-
fileCount = 1;
|
|
325
|
-
}
|
|
326
|
-
// Check if this is a multi-file package
|
|
327
|
-
else if (extractedFiles.length === 1) {
|
|
328
|
-
const destDir = (0, filesystem_1.getDestinationDir)(effectiveFormat, effectiveSubtype, pkg.name);
|
|
329
|
-
// Single file package
|
|
330
|
-
let mainFile = extractedFiles[0].content;
|
|
331
|
-
// Determine file extension based on effective format
|
|
332
|
-
// Cursor rules use .mdc, but slash commands and other files use .md
|
|
333
|
-
const fileExtension = (effectiveFormat === 'cursor' && format === 'cursor') ? 'mdc' : 'md';
|
|
334
|
-
const packageName = (0, filesystem_1.stripAuthorNamespace)(packageId);
|
|
335
|
-
// For Claude skills, use SKILL.md filename in the package directory
|
|
336
|
-
// For agents.md, use package-name/AGENTS.md directory structure
|
|
337
|
-
// For Copilot, use official naming conventions
|
|
338
|
-
// For other formats, use package name as filename
|
|
339
|
-
if (effectiveFormat === 'claude' && effectiveSubtype === 'skill') {
|
|
340
|
-
destPath = `${destDir}/SKILL.md`;
|
|
341
|
-
}
|
|
342
|
-
else if (effectiveFormat === 'claude' && effectiveSubtype === 'hook') {
|
|
343
|
-
// Claude hooks are merged into settings.json
|
|
344
|
-
destPath = `${destDir}/settings.json`;
|
|
345
|
-
}
|
|
346
|
-
else if (effectiveFormat === 'agents.md') {
|
|
347
|
-
destPath = `${destDir}/${packageName}/AGENTS.md`;
|
|
348
|
-
}
|
|
349
|
-
else if (effectiveFormat === 'copilot') {
|
|
350
|
-
// Official GitHub Copilot naming conventions
|
|
351
|
-
if (effectiveSubtype === 'chatmode') {
|
|
352
|
-
// Chat modes: .github/chatmodes/NAME.chatmode.md
|
|
353
|
-
destPath = `${destDir}/${packageName}.chatmode.md`;
|
|
354
|
-
}
|
|
355
|
-
else {
|
|
356
|
-
// Path-specific instructions: .github/instructions/NAME.instructions.md
|
|
357
|
-
destPath = `${destDir}/${packageName}.instructions.md`;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
else if (effectiveFormat === 'kiro' && effectiveSubtype === 'hook') {
|
|
361
|
-
// Kiro hooks use .kiro.hook extension (JSON files)
|
|
362
|
-
destPath = `${destDir}/${packageName}.kiro.hook`;
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
365
|
-
destPath = `${destDir}/${packageName}.${fileExtension}`;
|
|
366
|
-
}
|
|
367
|
-
// Handle cursor format - add header if missing for .mdc files
|
|
368
|
-
if (format === 'cursor' && effectiveFormat === 'cursor') {
|
|
369
|
-
if (!(0, cursor_config_1.hasMDCHeader)(mainFile)) {
|
|
370
|
-
console.log(` ā ļø Adding missing MDC header...`);
|
|
371
|
-
mainFile = (0, cursor_config_1.addMDCHeader)(mainFile, pkg.description);
|
|
372
|
-
}
|
|
373
|
-
// Apply cursor config if available
|
|
374
|
-
if (config.cursor) {
|
|
375
|
-
console.log(` āļø Applying cursor config...`);
|
|
376
|
-
mainFile = (0, cursor_config_1.applyCursorConfig)(mainFile, config.cursor);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
// Apply Claude config if downloading in Claude format
|
|
380
|
-
if (format === 'claude' && (0, claude_config_1.hasClaudeHeader)(mainFile)) {
|
|
381
|
-
if (config.claude) {
|
|
382
|
-
console.log(` āļø Applying Claude agent config...`);
|
|
383
|
-
mainFile = (0, claude_config_1.applyClaudeConfig)(mainFile, config.claude);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
// Special handling for Claude hooks - merge into settings.json
|
|
387
|
-
if (effectiveFormat === 'claude' && effectiveSubtype === 'hook') {
|
|
388
|
-
const { readFile } = await Promise.resolve().then(() => __importStar(require('fs/promises')));
|
|
389
|
-
const { fileExists } = await Promise.resolve().then(() => __importStar(require('../core/filesystem')));
|
|
390
|
-
// Parse the hook configuration from the downloaded file
|
|
391
|
-
let hookConfig;
|
|
392
|
-
try {
|
|
393
|
-
hookConfig = JSON.parse(mainFile);
|
|
394
|
-
}
|
|
395
|
-
catch (err) {
|
|
396
|
-
throw new Error(`Invalid hook configuration: ${err}. Hook file must be valid JSON.`);
|
|
397
|
-
}
|
|
398
|
-
// Generate unique hook ID for this installation
|
|
399
|
-
const hookId = `${packageId}@${actualVersion || version}`;
|
|
400
|
-
// Read existing settings.json if it exists
|
|
401
|
-
let existingSettings = { hooks: {} };
|
|
402
|
-
if (await fileExists(destPath)) {
|
|
403
|
-
try {
|
|
404
|
-
const existingContent = await readFile(destPath, 'utf-8');
|
|
405
|
-
existingSettings = JSON.parse(existingContent);
|
|
406
|
-
if (!existingSettings.hooks) {
|
|
407
|
-
existingSettings.hooks = {};
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
catch (err) {
|
|
411
|
-
console.log(` ā ļø Warning: Could not parse existing settings.json, creating new one.`);
|
|
412
|
-
existingSettings = { hooks: {} };
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
// Track which events this hook adds to
|
|
416
|
-
const events = [];
|
|
417
|
-
// Merge the new hook configuration
|
|
418
|
-
// Assume the downloaded file contains a hooks object
|
|
419
|
-
if (hookConfig.hooks) {
|
|
420
|
-
for (const [event, eventHooks] of Object.entries(hookConfig.hooks)) {
|
|
421
|
-
if (!existingSettings.hooks[event]) {
|
|
422
|
-
existingSettings.hooks[event] = [];
|
|
423
|
-
}
|
|
424
|
-
// Add hook ID to each hook config for tracking
|
|
425
|
-
const hooksWithId = eventHooks.map(hook => ({
|
|
426
|
-
...hook,
|
|
427
|
-
__prpm_hook_id: hookId, // Internal tracking ID
|
|
428
|
-
}));
|
|
429
|
-
// Add new hooks to the event
|
|
430
|
-
existingSettings.hooks[event] = [
|
|
431
|
-
...existingSettings.hooks[event],
|
|
432
|
-
...hooksWithId
|
|
433
|
-
];
|
|
434
|
-
events.push(event);
|
|
435
|
-
}
|
|
436
|
-
console.log(` ā Merged hook configuration into settings.json`);
|
|
437
|
-
// Store metadata for lockfile
|
|
438
|
-
hookMetadata = { events, hookId };
|
|
439
|
-
}
|
|
440
|
-
mainFile = JSON.stringify(existingSettings, null, 2);
|
|
441
|
-
}
|
|
442
|
-
await (0, filesystem_1.saveFile)(destPath, mainFile);
|
|
443
|
-
fileCount = 1;
|
|
444
|
-
}
|
|
445
|
-
else {
|
|
446
|
-
const destDir = (0, filesystem_1.getDestinationDir)(effectiveFormat, effectiveSubtype, pkg.name);
|
|
447
|
-
// Multi-file package - create directory for package
|
|
448
|
-
// For Claude skills, destDir already includes package name, so use it directly
|
|
449
|
-
// For Cursor rules converted from Claude skills, use flat structure
|
|
450
|
-
const packageName = (0, filesystem_1.stripAuthorNamespace)(packageId);
|
|
451
|
-
const isCursorConversion = (effectiveFormat === 'cursor' && pkg.format === 'claude' && pkg.subtype === 'skill');
|
|
452
|
-
const packageDir = (effectiveFormat === 'claude' && effectiveSubtype === 'skill')
|
|
453
|
-
? destDir
|
|
454
|
-
: isCursorConversion
|
|
455
|
-
? destDir // Cursor uses flat structure
|
|
456
|
-
: `${destDir}/${packageName}`;
|
|
457
|
-
destPath = packageDir;
|
|
458
|
-
console.log(` š Multi-file package - creating directory: ${packageDir}`);
|
|
459
|
-
// For Claude skills, verify SKILL.md exists
|
|
460
|
-
if (effectiveFormat === 'claude' && effectiveSubtype === 'skill') {
|
|
461
|
-
const skillMdIndex = extractedFiles.findIndex(f => f.name === 'SKILL.md' || f.name.endsWith('/SKILL.md'));
|
|
462
|
-
if (skillMdIndex === -1) {
|
|
463
|
-
// SKILL.md not found, look for common variations and auto-rename
|
|
464
|
-
const skillFileIndex = extractedFiles.findIndex(f => f.name.toLowerCase().endsWith('skill.md') ||
|
|
465
|
-
(f.name.endsWith('.md') && extractedFiles.length === 1) // Single .md file
|
|
466
|
-
);
|
|
467
|
-
if (skillFileIndex !== -1) {
|
|
468
|
-
const oldName = extractedFiles[skillFileIndex].name;
|
|
469
|
-
const basePath = oldName.substring(0, oldName.lastIndexOf('/') + 1);
|
|
470
|
-
const newName = basePath + 'SKILL.md';
|
|
471
|
-
console.log(` ā ļø Auto-fixing skill filename: ${oldName} ā ${newName}`);
|
|
472
|
-
console.log(` (Claude skills must be named SKILL.md per official documentation)`);
|
|
473
|
-
extractedFiles[skillFileIndex].name = newName;
|
|
474
|
-
}
|
|
475
|
-
else {
|
|
476
|
-
throw new Error('Claude skills must contain a SKILL.md file. ' +
|
|
477
|
-
'According to Claude documentation, skills must have a file named SKILL.md in their directory. ' +
|
|
478
|
-
'No suitable file found to rename. Please update the package to follow this requirement.');
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
// Track JSON files for @reference insertion in Cursor conversion
|
|
483
|
-
const jsonFiles = [];
|
|
484
|
-
for (const file of extractedFiles) {
|
|
485
|
-
// Strip the tarball's root directory prefix to preserve subdirectories
|
|
486
|
-
// Example: ".claude/skills/agent-builder/docs/examples.md" ā "docs/examples.md"
|
|
487
|
-
// ".claude/skills/agent-builder/SKILL.md" ā "SKILL.md"
|
|
488
|
-
// Find the common prefix (the package's root directory in the tarball)
|
|
489
|
-
const pathParts = file.name.split('/');
|
|
490
|
-
// For Claude skills, the tarball structure is typically: .claude/skills/package-name/...
|
|
491
|
-
// We want to strip everything up to and including the package-name directory
|
|
492
|
-
let relativeFileName = file.name;
|
|
493
|
-
// Find the skills directory index
|
|
494
|
-
const skillsDirIndex = pathParts.indexOf('skills');
|
|
495
|
-
if (skillsDirIndex !== -1 && pathParts.length > skillsDirIndex + 2) {
|
|
496
|
-
// Skip: .claude/skills/package-name/ and keep the rest
|
|
497
|
-
relativeFileName = pathParts.slice(skillsDirIndex + 2).join('/');
|
|
498
|
-
}
|
|
499
|
-
else if (pathParts.length > 1) {
|
|
500
|
-
// Fallback: just take the filename (last part)
|
|
501
|
-
relativeFileName = pathParts[pathParts.length - 1];
|
|
502
|
-
}
|
|
503
|
-
let fileContent = file.content;
|
|
504
|
-
let fileName = relativeFileName;
|
|
505
|
-
// Handle Cursor conversion from Claude skill
|
|
506
|
-
if (isCursorConversion) {
|
|
507
|
-
// Convert SKILL.md to .mdc
|
|
508
|
-
if (fileName === 'SKILL.md' || fileName.endsWith('/SKILL.md')) {
|
|
509
|
-
fileName = `${packageName}.mdc`;
|
|
510
|
-
// Add MDC header if missing
|
|
511
|
-
if (!(0, cursor_config_1.hasMDCHeader)(fileContent)) {
|
|
512
|
-
console.log(` ā ļø Adding MDC header to converted skill...`);
|
|
513
|
-
fileContent = (0, cursor_config_1.addMDCHeader)(fileContent, pkg.description);
|
|
514
|
-
}
|
|
515
|
-
// Apply cursor config if available
|
|
516
|
-
if (config.cursor) {
|
|
517
|
-
console.log(` āļø Applying cursor config...`);
|
|
518
|
-
fileContent = (0, cursor_config_1.applyCursorConfig)(fileContent, config.cursor);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
// Track JSON files for @reference
|
|
522
|
-
else if (fileName.endsWith('.json')) {
|
|
523
|
-
// Flatten structure - remove subdirectories
|
|
524
|
-
const jsonFileName = fileName.split('/').pop() || fileName;
|
|
525
|
-
fileName = jsonFileName;
|
|
526
|
-
jsonFiles.push(jsonFileName);
|
|
527
|
-
}
|
|
528
|
-
// For other files (docs, etc), flatten the structure
|
|
529
|
-
else {
|
|
530
|
-
fileName = fileName.split('/').pop() || fileName;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
const filePath = `${packageDir}/${fileName}`;
|
|
534
|
-
await (0, filesystem_1.saveFile)(filePath, fileContent);
|
|
535
|
-
fileCount++;
|
|
536
|
-
}
|
|
537
|
-
// Add @references to .mdc file for JSON files
|
|
538
|
-
if (isCursorConversion && jsonFiles.length > 0) {
|
|
539
|
-
const mdcFile = `${packageDir}/${packageName}.mdc`;
|
|
540
|
-
const { readFile } = await Promise.resolve().then(() => __importStar(require('fs/promises')));
|
|
541
|
-
let mdcContent = await readFile(mdcFile, 'utf-8');
|
|
542
|
-
// Find the end of frontmatter (if exists)
|
|
543
|
-
const frontmatterMatch = mdcContent.match(/^---\n[\s\S]*?\n---\n/);
|
|
544
|
-
if (frontmatterMatch) {
|
|
545
|
-
const frontmatterEnd = frontmatterMatch[0].length;
|
|
546
|
-
const beforeFrontmatter = mdcContent.slice(0, frontmatterEnd);
|
|
547
|
-
const afterFrontmatter = mdcContent.slice(frontmatterEnd);
|
|
548
|
-
// Add @references right after frontmatter
|
|
549
|
-
const references = jsonFiles.map(f => `@${f}`).join('\n');
|
|
550
|
-
mdcContent = `${beforeFrontmatter}\n${references}\n${afterFrontmatter}`;
|
|
551
|
-
await (0, filesystem_1.saveFile)(mdcFile, mdcContent);
|
|
552
|
-
console.log(` ā Added ${jsonFiles.length} @reference(s) to ${packageName}.mdc`);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
// Update or create lock file
|
|
557
|
-
const updatedLockfile = lockfile || (0, lockfile_1.createLockfile)();
|
|
558
|
-
(0, lockfile_1.addToLockfile)(updatedLockfile, packageId, {
|
|
559
|
-
version: actualVersion || version,
|
|
560
|
-
tarballUrl,
|
|
561
|
-
format: pkg.format, // Preserve original package format
|
|
562
|
-
subtype: pkg.subtype, // Preserve original package subtype
|
|
563
|
-
installedPath: destPath,
|
|
564
|
-
fromCollection: options.fromCollection,
|
|
565
|
-
hookMetadata, // Track hook installation metadata for uninstall
|
|
566
|
-
});
|
|
567
|
-
(0, lockfile_1.setPackageIntegrity)(updatedLockfile, packageId, tarball);
|
|
568
|
-
await (0, lockfile_1.writeLockfile)(updatedLockfile);
|
|
569
|
-
// Update lockfile (already done above via addToLockfile + writeLockfile)
|
|
570
|
-
// No need to call addPackage again as it would be redundant
|
|
571
|
-
// Track download analytics
|
|
572
|
-
await client.trackDownload(packageId, {
|
|
573
|
-
version: actualVersion || version,
|
|
574
|
-
client: 'cli',
|
|
575
|
-
format,
|
|
576
|
-
});
|
|
577
|
-
// Display the incremented download count
|
|
578
|
-
const newDownloadCount = pkg.total_downloads + 1;
|
|
579
|
-
console.log(`\nā
Successfully installed ${packageId}`);
|
|
580
|
-
console.log(` š Saved to: ${destPath}`);
|
|
581
|
-
console.log(` š Lock file updated`);
|
|
582
|
-
console.log(`\nš” This package has been downloaded ${newDownloadCount.toLocaleString()} times`);
|
|
583
|
-
success = true;
|
|
584
|
-
}
|
|
585
|
-
catch (err) {
|
|
586
|
-
error = err instanceof Error ? err.message : String(err);
|
|
587
|
-
throw new errors_1.CLIError(`\nā Installation failed: ${error}\n\nš” Tips:\n - Check package name: prpm search <query>\n - Get package info: prpm info <package>`, 1);
|
|
588
|
-
}
|
|
589
|
-
finally {
|
|
590
|
-
await telemetry_1.telemetry.track({
|
|
591
|
-
command: 'install',
|
|
592
|
-
success,
|
|
593
|
-
error,
|
|
594
|
-
duration: Date.now() - startTime,
|
|
595
|
-
data: {
|
|
596
|
-
packageId: packageSpec ? packageSpec.split('@')[0] : 'lockfile',
|
|
597
|
-
version: options.version || 'latest',
|
|
598
|
-
convertTo: options.as,
|
|
599
|
-
},
|
|
600
|
-
});
|
|
601
|
-
await telemetry_1.telemetry.shutdown();
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
async function extractTarball(tarball, packageId) {
|
|
605
|
-
const files = [];
|
|
606
|
-
const zlib = await Promise.resolve().then(() => __importStar(require('zlib')));
|
|
607
|
-
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
608
|
-
const os = await Promise.resolve().then(() => __importStar(require('os')));
|
|
609
|
-
const path = await Promise.resolve().then(() => __importStar(require('path')));
|
|
610
|
-
return new Promise((resolve, reject) => {
|
|
611
|
-
// Decompress gzip first
|
|
612
|
-
zlib.gunzip(tarball, async (err, result) => {
|
|
613
|
-
if (err) {
|
|
614
|
-
reject(err);
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
// Check if this is a tar archive by looking for tar header
|
|
618
|
-
const isTar = result.length > 257 && result.toString('utf-8', 257, 262) === 'ustar';
|
|
619
|
-
if (!isTar) {
|
|
620
|
-
// Not a tar archive, treat as single gzipped file
|
|
621
|
-
files.push({
|
|
622
|
-
name: `${packageId}.md`,
|
|
623
|
-
content: result.toString('utf-8')
|
|
624
|
-
});
|
|
625
|
-
resolve(files);
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
|
-
// Create temp directory for extraction
|
|
629
|
-
const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'prpm-'));
|
|
630
|
-
try {
|
|
631
|
-
// Write tar data to temp file
|
|
632
|
-
const tarPath = path.join(tmpDir, 'package.tar');
|
|
633
|
-
await fs.promises.writeFile(tarPath, result);
|
|
634
|
-
// Extract using tar library
|
|
635
|
-
await tar.extract({
|
|
636
|
-
file: tarPath,
|
|
637
|
-
cwd: tmpDir,
|
|
638
|
-
});
|
|
639
|
-
// Read all extracted files
|
|
640
|
-
const extractedFiles = await fs.promises.readdir(tmpDir, { withFileTypes: true, recursive: true });
|
|
641
|
-
// Files to exclude from package content (metadata files)
|
|
642
|
-
const excludeFiles = ['package.tar', 'prpm.json', 'README.md', 'LICENSE', 'LICENSE.txt', 'LICENSE.md'];
|
|
643
|
-
for (const entry of extractedFiles) {
|
|
644
|
-
if (entry.isFile() && !excludeFiles.includes(entry.name)) {
|
|
645
|
-
const filePath = path.join(entry.path || tmpDir, entry.name);
|
|
646
|
-
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
647
|
-
const relativePath = path.relative(tmpDir, filePath);
|
|
648
|
-
files.push({
|
|
649
|
-
name: relativePath,
|
|
650
|
-
content
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
if (files.length === 0) {
|
|
655
|
-
// No files found, fall back to single file
|
|
656
|
-
files.push({
|
|
657
|
-
name: `${packageId}.md`,
|
|
658
|
-
content: result.toString('utf-8')
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
// Cleanup
|
|
662
|
-
await fs.promises.rm(tmpDir, { recursive: true, force: true });
|
|
663
|
-
resolve(files);
|
|
664
|
-
}
|
|
665
|
-
catch (tarErr) {
|
|
666
|
-
// Cleanup and fall back to single file
|
|
667
|
-
await fs.promises.rm(tmpDir, { recursive: true, force: true }).catch(() => { });
|
|
668
|
-
files.push({
|
|
669
|
-
name: `${packageId}.md`,
|
|
670
|
-
content: result.toString('utf-8')
|
|
671
|
-
});
|
|
672
|
-
resolve(files);
|
|
673
|
-
}
|
|
674
|
-
});
|
|
675
|
-
});
|
|
676
|
-
}
|
|
677
|
-
/**
|
|
678
|
-
* Detect project format from existing directories
|
|
679
|
-
*/
|
|
680
|
-
function detectProjectFormat() {
|
|
681
|
-
const fs = require('fs');
|
|
682
|
-
if (fs.existsSync('.cursor/rules') || fs.existsSync('.cursor'))
|
|
683
|
-
return 'cursor';
|
|
684
|
-
if (fs.existsSync('.claude/agents') || fs.existsSync('.claude'))
|
|
685
|
-
return 'claude';
|
|
686
|
-
if (fs.existsSync('.continue'))
|
|
687
|
-
return 'continue';
|
|
688
|
-
if (fs.existsSync('.windsurf'))
|
|
689
|
-
return 'windsurf';
|
|
690
|
-
return null;
|
|
691
|
-
}
|
|
692
|
-
/**
|
|
693
|
-
* Install all packages from prpm.lock
|
|
694
|
-
*/
|
|
695
|
-
async function installFromLockfile(options) {
|
|
696
|
-
try {
|
|
697
|
-
// Read lockfile
|
|
698
|
-
const lockfile = await (0, lockfile_1.readLockfile)();
|
|
699
|
-
if (!lockfile) {
|
|
700
|
-
throw new errors_1.CLIError('ā No prpm.lock file found\n\nš” Run "prpm install <package>" first to create a lockfile, or initialize a new project with "prpm init"', 1);
|
|
701
|
-
}
|
|
702
|
-
const packageIds = Object.keys(lockfile.packages);
|
|
703
|
-
if (packageIds.length === 0) {
|
|
704
|
-
console.log('ā
No packages to install (prpm.lock is empty)');
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
console.log(`š¦ Installing ${packageIds.length} package${packageIds.length === 1 ? '' : 's'} from prpm.lock...\n`);
|
|
708
|
-
let successCount = 0;
|
|
709
|
-
let failCount = 0;
|
|
710
|
-
// Install each package from lockfile
|
|
711
|
-
for (const packageId of packageIds) {
|
|
712
|
-
const lockEntry = lockfile.packages[packageId];
|
|
713
|
-
try {
|
|
714
|
-
// Extract package spec (strip version if present in packageId)
|
|
715
|
-
const packageSpec = packageId.includes('@') && !packageId.startsWith('@')
|
|
716
|
-
? packageId.substring(0, packageId.lastIndexOf('@'))
|
|
717
|
-
: packageId;
|
|
718
|
-
console.log(` Installing ${packageId}...`);
|
|
719
|
-
await handleInstall(packageSpec, {
|
|
720
|
-
version: lockEntry.version,
|
|
721
|
-
as: options.as || lockEntry.format,
|
|
722
|
-
subtype: options.subtype || lockEntry.subtype,
|
|
723
|
-
frozenLockfile: options.frozenLockfile,
|
|
724
|
-
force: true, // Force reinstall when installing from lockfile
|
|
725
|
-
});
|
|
726
|
-
successCount++;
|
|
727
|
-
}
|
|
728
|
-
catch (error) {
|
|
729
|
-
// Check if this is a success exit (CLIError with exitCode 0)
|
|
730
|
-
if (error instanceof errors_1.CLIError && error.exitCode === 0) {
|
|
731
|
-
successCount++;
|
|
732
|
-
}
|
|
733
|
-
else {
|
|
734
|
-
failCount++;
|
|
735
|
-
console.error(` ā Failed to install ${packageId}:`);
|
|
736
|
-
console.error(` Type: ${error?.constructor?.name}`);
|
|
737
|
-
console.error(` Message: ${error instanceof Error ? error.message : String(error)}`);
|
|
738
|
-
if (error instanceof errors_1.CLIError) {
|
|
739
|
-
console.error(` ExitCode: ${error.exitCode}`);
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
console.log(`\nā
Installed ${successCount}/${packageIds.length} packages`);
|
|
745
|
-
if (failCount > 0) {
|
|
746
|
-
throw new errors_1.CLIError(`ā ${failCount} package${failCount === 1 ? '' : 's'} failed to install`, 1);
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
catch (error) {
|
|
750
|
-
if (error instanceof errors_1.CLIError) {
|
|
751
|
-
throw error;
|
|
752
|
-
}
|
|
753
|
-
throw new errors_1.CLIError(`ā Failed to install from lockfile: ${error}`, 1);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
function createInstallCommand() {
|
|
757
|
-
const command = new commander_1.Command('install');
|
|
758
|
-
command
|
|
759
|
-
.description('Install a package from the registry, or install all packages from prpm.lock if no package specified')
|
|
760
|
-
.argument('[package]', 'Package to install (e.g., react-rules or react-rules@1.2.0). If omitted, installs all packages from prpm.lock')
|
|
761
|
-
.option('--version <version>', 'Specific version to install')
|
|
762
|
-
.option('--as <format>', 'Convert and install in specific format (cursor, claude, continue, windsurf, copilot, kiro, agents.md, canonical)')
|
|
763
|
-
.option('--format <format>', 'Alias for --as')
|
|
764
|
-
.option('--subtype <subtype>', 'Specify subtype when converting (skill, agent, rule, etc.)')
|
|
765
|
-
.option('--frozen-lockfile', 'Fail if lock file needs to be updated (for CI)')
|
|
766
|
-
.action(async (packageSpec, options) => {
|
|
767
|
-
// Support both --as and --format (format is alias for as)
|
|
768
|
-
const convertTo = options.format || options.as;
|
|
769
|
-
if (convertTo && !['cursor', 'claude', 'continue', 'windsurf', 'copilot', 'kiro', 'agents.md', 'canonical'].includes(convertTo)) {
|
|
770
|
-
throw new errors_1.CLIError('ā Format must be one of: cursor, claude, continue, windsurf, copilot, kiro, agents.md, canonical\n\nš” Examples:\n prpm install my-package --as cursor # Convert to Cursor format\n prpm install my-package --format claude # Convert to Claude format\n prpm install my-package --format kiro # Convert to Kiro format\n prpm install my-package --format agents.md # Convert to Agents.md format\n prpm install my-package # Install in native format', 1);
|
|
771
|
-
}
|
|
772
|
-
// If no package specified, install from lockfile
|
|
773
|
-
if (!packageSpec) {
|
|
774
|
-
await installFromLockfile({
|
|
775
|
-
as: convertTo,
|
|
776
|
-
subtype: options.subtype,
|
|
777
|
-
frozenLockfile: options.frozenLockfile
|
|
778
|
-
});
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
await handleInstall(packageSpec, {
|
|
782
|
-
version: options.version,
|
|
783
|
-
as: convertTo,
|
|
784
|
-
subtype: options.subtype,
|
|
785
|
-
frozenLockfile: options.frozenLockfile
|
|
786
|
-
});
|
|
787
|
-
});
|
|
788
|
-
return command;
|
|
789
|
-
}
|