prpm 2.1.36 → 2.1.38

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.
Files changed (2) hide show
  1. package/dist/index.js +219 -61
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -102,7 +102,8 @@ function parseLockfileKey(key) {
102
102
  function addToLockfile(lockfile, packageId, packageInfo) {
103
103
  var _a;
104
104
  const snippetLocation = packageInfo.subtype === "snippet" ? (_a = packageInfo.snippetMetadata) == null ? void 0 : _a.targetPath : void 0;
105
- const lockfileKey = getLockfileKey(packageId, packageInfo.format, snippetLocation);
105
+ const locationKey = packageInfo.global ? "global" : snippetLocation;
106
+ const lockfileKey = getLockfileKey(packageId, packageInfo.format, locationKey);
106
107
  lockfile.packages[lockfileKey] = {
107
108
  version: packageInfo.version,
108
109
  resolved: packageInfo.tarballUrl,
@@ -111,6 +112,7 @@ function addToLockfile(lockfile, packageId, packageInfo) {
111
112
  dependencies: packageInfo.dependencies,
112
113
  format: packageInfo.format,
113
114
  subtype: packageInfo.subtype,
115
+ global: packageInfo.global,
114
116
  sourceFormat: packageInfo.sourceFormat,
115
117
  sourceSubtype: packageInfo.sourceSubtype,
116
118
  installedPath: packageInfo.installedPath,
@@ -140,13 +142,13 @@ function verifyPackageIntegrity(lockfile, packageId, tarballBuffer, format, loca
140
142
  const expectedHash = pkg.integrity.replace("sha256-", "");
141
143
  return hash === expectedHash;
142
144
  }
143
- function getLockedVersion(lockfile, packageId, format) {
145
+ function getLockedVersion(lockfile, packageId, format, location) {
144
146
  var _a;
145
147
  if (!lockfile) {
146
148
  return null;
147
149
  }
148
150
  if (format) {
149
- const lockfileKey = getLockfileKey(packageId, format);
151
+ const lockfileKey = getLockfileKey(packageId, format, location);
150
152
  return ((_a = lockfile.packages[lockfileKey]) == null ? void 0 : _a.version) || null;
151
153
  }
152
154
  if (lockfile.packages[packageId]) {
@@ -17641,6 +17643,36 @@ ${lines.join("\n")}
17641
17643
  ---
17642
17644
  ${body}`;
17643
17645
  }
17646
+ function replaceLeadingDirectory(filePath, from, to) {
17647
+ const normalizedPath = filePath.startsWith("./") ? filePath.slice(2) : filePath;
17648
+ if (normalizedPath === from) {
17649
+ return to;
17650
+ }
17651
+ if (normalizedPath.startsWith(`${from}/`)) {
17652
+ return import_path15.default.join(to, normalizedPath.slice(from.length + 1));
17653
+ }
17654
+ return filePath;
17655
+ }
17656
+ function getGlobalDestinationDir(format, destDir) {
17657
+ const homeDir = import_os5.default.homedir();
17658
+ if (format === "claude") {
17659
+ return replaceLeadingDirectory(destDir, ".claude", import_path15.default.join(homeDir, ".claude"));
17660
+ }
17661
+ if (format === "codex") {
17662
+ if (destDir === "." || destDir === "./") {
17663
+ return import_path15.default.join(homeDir, ".codex");
17664
+ }
17665
+ const codexPath = replaceLeadingDirectory(destDir, ".codex", import_path15.default.join(homeDir, ".codex"));
17666
+ if (codexPath !== destDir) {
17667
+ return codexPath;
17668
+ }
17669
+ return replaceLeadingDirectory(destDir, ".agents", import_path15.default.join(homeDir, ".agents"));
17670
+ }
17671
+ return destDir;
17672
+ }
17673
+ function getLockfileLocationKey(options) {
17674
+ return options.global ? "global" : void 0;
17675
+ }
17644
17676
  function getPackageLabel2(format, subtype) {
17645
17677
  const formatLabels = {
17646
17678
  "claude": "Claude",
@@ -17783,7 +17815,8 @@ async function handleInstall(packageSpec, options) {
17783
17815
  if (!targetFormat) {
17784
17816
  targetFormat = config.defaultFormat || await autoDetectFormat() || void 0;
17785
17817
  }
17786
- const lockedVersion = getLockedVersion(lockfile, packageId, targetFormat);
17818
+ const lockfileLocationKey = getLockfileLocationKey(options);
17819
+ const lockedVersion = getLockedVersion(lockfile, packageId, targetFormat, lockfileLocationKey);
17787
17820
  let version;
17788
17821
  if (options.frozenLockfile) {
17789
17822
  if (!lockedVersion) {
@@ -17804,7 +17837,7 @@ async function handleInstall(packageSpec, options) {
17804
17837
  matchedKey = snippetKey;
17805
17838
  }
17806
17839
  if (!installedPkg) {
17807
- const standardKey = getLockfileKey(packageId, targetFormat);
17840
+ const standardKey = getLockfileKey(packageId, targetFormat, lockfileLocationKey);
17808
17841
  if (lockfile.packages[standardKey]) {
17809
17842
  installedPkg = lockfile.packages[standardKey];
17810
17843
  matchedKey = standardKey;
@@ -17954,11 +17987,11 @@ async function handleInstall(packageSpec, options) {
17954
17987
  }
17955
17988
  console.log(` \u2B07\uFE0F Downloading...`);
17956
17989
  const tarball = await client.downloadPackage(tarballUrl);
17957
- const lockfileKeyForVerification = getLockfileKey(packageId, targetFormat);
17990
+ const lockfileKeyForVerification = getLockfileKey(packageId, targetFormat, lockfileLocationKey);
17958
17991
  const existingEntry = lockfile == null ? void 0 : lockfile.packages[lockfileKeyForVerification];
17959
17992
  if ((existingEntry == null ? void 0 : existingEntry.integrity) && existingEntry.version === actualVersion) {
17960
17993
  console.log(` \u{1F512} Verifying integrity...`);
17961
- const isValid = verifyPackageIntegrity(lockfile, packageId, tarball, targetFormat);
17994
+ const isValid = verifyPackageIntegrity(lockfile, packageId, tarball, targetFormat, lockfileLocationKey);
17962
17995
  if (!isValid) {
17963
17996
  throw new CLIError(
17964
17997
  `\u274C Integrity verification failed for ${packageId}
@@ -18164,6 +18197,7 @@ This could indicate:
18164
18197
  const isClaudePlugin = pkg.format === "claude" && pkg.subtype === "plugin";
18165
18198
  if (isClaudePlugin) {
18166
18199
  console.log(` \u{1F50C} Installing Claude Plugin...`);
18200
+ const claudeRoot = options.global ? import_path15.default.join(import_os5.default.homedir(), ".claude") : ".claude";
18167
18201
  const pluginJsonFile = extractedFiles.find(
18168
18202
  (f) => f.name === "plugin.json" || f.name === ".claude-plugin/plugin.json" || f.name.endsWith("/plugin.json")
18169
18203
  );
@@ -18180,14 +18214,15 @@ This could indicate:
18180
18214
  (f) => f.name.startsWith("agents/") && f.name.endsWith(".md")
18181
18215
  );
18182
18216
  if (agentFiles.length > 0) {
18183
- await import_promises4.default.mkdir(".claude/agents", { recursive: true });
18217
+ const agentDir = import_path15.default.join(claudeRoot, "agents");
18218
+ await import_promises4.default.mkdir(agentDir, { recursive: true });
18184
18219
  for (const file of agentFiles) {
18185
18220
  const filename = import_path15.default.basename(file.name);
18186
- const destFile = `.claude/agents/${filename}`;
18221
+ const destFile = import_path15.default.join(agentDir, filename);
18187
18222
  await saveFile(destFile, file.content);
18188
18223
  installedFiles.push(destFile);
18189
18224
  }
18190
- console.log(` \u2713 Installed ${agentFiles.length} agents to .claude/agents/`);
18225
+ console.log(` \u2713 Installed ${agentFiles.length} agents to ${agentDir}/`);
18191
18226
  }
18192
18227
  const skillFiles = extractedFiles.filter(
18193
18228
  (f) => f.name.startsWith("skills/") && (f.name.endsWith(".md") || f.name.includes("SKILL.md"))
@@ -18195,26 +18230,27 @@ This could indicate:
18195
18230
  if (skillFiles.length > 0) {
18196
18231
  for (const file of skillFiles) {
18197
18232
  const relativePath = file.name.replace(/^skills\//, "");
18198
- const destFile = `.claude/skills/${relativePath}`;
18233
+ const destFile = import_path15.default.join(claudeRoot, "skills", relativePath);
18199
18234
  const destFileDir = import_path15.default.dirname(destFile);
18200
18235
  await import_promises4.default.mkdir(destFileDir, { recursive: true });
18201
18236
  await saveFile(destFile, file.content);
18202
18237
  installedFiles.push(destFile);
18203
18238
  }
18204
- console.log(` \u2713 Installed ${skillFiles.length} skill files to .claude/skills/`);
18239
+ console.log(` \u2713 Installed ${skillFiles.length} skill files to ${import_path15.default.join(claudeRoot, "skills")}/`);
18205
18240
  }
18206
18241
  const commandFiles = extractedFiles.filter(
18207
18242
  (f) => f.name.startsWith("commands/") && f.name.endsWith(".md")
18208
18243
  );
18209
18244
  if (commandFiles.length > 0) {
18210
- await import_promises4.default.mkdir(".claude/commands", { recursive: true });
18245
+ const commandDir = import_path15.default.join(claudeRoot, "commands");
18246
+ await import_promises4.default.mkdir(commandDir, { recursive: true });
18211
18247
  for (const file of commandFiles) {
18212
18248
  const filename = import_path15.default.basename(file.name);
18213
- const destFile = `.claude/commands/${filename}`;
18249
+ const destFile = import_path15.default.join(commandDir, filename);
18214
18250
  await saveFile(destFile, file.content);
18215
18251
  installedFiles.push(destFile);
18216
18252
  }
18217
- console.log(` \u2713 Installed ${commandFiles.length} commands to .claude/commands/`);
18253
+ console.log(` \u2713 Installed ${commandFiles.length} commands to ${commandDir}/`);
18218
18254
  }
18219
18255
  if (pluginConfig.mcpServers && Object.keys(pluginConfig.mcpServers).length > 0) {
18220
18256
  const editor = options.editor || "claude";
@@ -18251,7 +18287,7 @@ This could indicate:
18251
18287
  files: installedFiles
18252
18288
  };
18253
18289
  }
18254
- destPath = ".claude/";
18290
+ destPath = `${claudeRoot}/`;
18255
18291
  fileCount = installedFiles.length;
18256
18292
  } else if (effectiveFormat === "mcp" && (effectiveSubtype === "server" || effectiveSubtype === "tool") || isMCPServerPackage && (pkg.subtype === "server" || pkg.subtype === "tool") && isMCPToEditor) {
18257
18293
  console.log(` \u{1F527} Installing MCP Server...`);
@@ -18341,11 +18377,18 @@ This could indicate:
18341
18377
  throw new Error("CLAUDE.md format only supports single-file packages");
18342
18378
  }
18343
18379
  let mainFile = extractedFiles[0].content;
18344
- destPath = "CLAUDE.md";
18380
+ destPath = options.global ? import_path15.default.join(import_os5.default.homedir(), ".claude", "CLAUDE.md") : "CLAUDE.md";
18345
18381
  await saveFile(destPath, mainFile);
18346
18382
  fileCount = 1;
18347
18383
  } else if (extractedFiles.length === 1) {
18348
18384
  destDir = getDestinationDir2(effectiveFormat, effectiveSubtype, pkg.name);
18385
+ if (options.global) {
18386
+ const globalDestDir = getGlobalDestinationDir(effectiveFormat, destDir);
18387
+ if (globalDestDir !== destDir) {
18388
+ destDir = globalDestDir;
18389
+ console.log(` \u{1F310} Installing globally to ${destDir}`);
18390
+ }
18391
+ }
18349
18392
  if (locationOverride && effectiveFormat === "cursor") {
18350
18393
  const relativeDestDir = destDir.startsWith("./") ? destDir.slice(2) : destDir;
18351
18394
  destDir = import_path15.default.join(locationOverride, relativeDestDir);
@@ -18369,7 +18412,11 @@ This could indicate:
18369
18412
  } else if (effectiveFormat === "agents.md" || effectiveFormat === "gemini.md" || effectiveFormat === "claude.md" || effectiveFormat === "codex") {
18370
18413
  if (effectiveSubtype === "skill") {
18371
18414
  destPath = `${destDir}/SKILL.md`;
18372
- console.log(` \u{1F4E6} Installing skill to ${destDir}/ for progressive disclosure`);
18415
+ if (effectiveFormat === "codex") {
18416
+ console.log(` \u{1F4E6} Installing Codex skill to ${destDir}/`);
18417
+ } else {
18418
+ console.log(` \u{1F4E6} Installing skill to ${destDir}/ for progressive disclosure`);
18419
+ }
18373
18420
  } else if (effectiveSubtype === "agent") {
18374
18421
  if (effectiveFormat === "codex") {
18375
18422
  destPath = `${destDir}/${packageName}.toml`;
@@ -18541,6 +18588,13 @@ This could indicate:
18541
18588
  } else {
18542
18589
  destDir = getDestinationDir2(effectiveFormat, effectiveSubtype, pkg.name);
18543
18590
  }
18591
+ if (options.global) {
18592
+ const globalDestDir = getGlobalDestinationDir(effectiveFormat, destDir);
18593
+ if (globalDestDir !== destDir) {
18594
+ destDir = globalDestDir;
18595
+ console.log(` \u{1F310} Installing globally to ${destDir}`);
18596
+ }
18597
+ }
18544
18598
  if (locationOverride && effectiveFormat === "cursor") {
18545
18599
  const relativeDestDir = destDir.startsWith("./") ? destDir.slice(2) : destDir;
18546
18600
  destDir = import_path15.default.join(locationOverride, relativeDestDir);
@@ -18704,11 +18758,13 @@ ${afterFrontmatter}`;
18704
18758
  progressiveDisclosure: progressiveDisclosureMetadata,
18705
18759
  pluginMetadata,
18706
18760
  // Track plugin installation metadata for uninstall
18707
- snippetMetadata
18761
+ snippetMetadata,
18708
18762
  // Track snippet installation metadata for uninstall
18763
+ global: options.global
18709
18764
  });
18710
18765
  const snippetTargetPath = effectiveSubtype === "snippet" ? snippetMetadata == null ? void 0 : snippetMetadata.targetPath : void 0;
18711
- setPackageIntegrity(updatedLockfile, packageId, tarball, effectiveFormat, snippetTargetPath);
18766
+ const integrityLocationKey = lockfileLocationKey || snippetTargetPath;
18767
+ setPackageIntegrity(updatedLockfile, packageId, tarball, effectiveFormat, integrityLocationKey);
18712
18768
  await writeLockfile(updatedLockfile);
18713
18769
  await client.trackDownload(packageId, {
18714
18770
  version: actualVersion || version,
@@ -18889,7 +18945,7 @@ async function collectExtractedFiles(rootDir, excludedNames, fs16) {
18889
18945
  return files;
18890
18946
  }
18891
18947
  async function installFromLockfile(options) {
18892
- var _a, _b;
18948
+ var _a, _b, _c, _d;
18893
18949
  try {
18894
18950
  const lockfile = await readLockfile();
18895
18951
  if (!lockfile) {
@@ -18906,7 +18962,7 @@ async function installFromLockfile(options) {
18906
18962
  let failCount = 0;
18907
18963
  for (const lockfileKey of packageIds) {
18908
18964
  const lockEntry = lockfile.packages[lockfileKey];
18909
- const { packageId, format } = parseLockfileKey(lockfileKey);
18965
+ const { packageId, format, location } = parseLockfileKey(lockfileKey);
18910
18966
  const displayName = format ? `${packageId} (${format})` : packageId;
18911
18967
  try {
18912
18968
  const packageSpec = packageId.includes("@") && !packageId.startsWith("@") ? packageId.substring(0, packageId.lastIndexOf("@")) : packageId;
@@ -18921,6 +18977,8 @@ async function installFromLockfile(options) {
18921
18977
  }
18922
18978
  }
18923
18979
  const manifestFile = (_a = lockEntry.progressiveDisclosure) == null ? void 0 : _a.manifestPath;
18980
+ const preservedGlobal = options.global ?? lockEntry.global ?? ((_b = lockEntry.pluginMetadata) == null ? void 0 : _b.mcpGlobal) ?? (location === "global" ? true : void 0);
18981
+ const preservedEditor = options.editor ?? ((_c = lockEntry.pluginMetadata) == null ? void 0 : _c.mcpEditor);
18924
18982
  await handleInstall(packageSpec, {
18925
18983
  version: lockEntry.version,
18926
18984
  as: options.as || lockEntry.format,
@@ -18931,8 +18989,10 @@ async function installFromLockfile(options) {
18931
18989
  location: locationOverride,
18932
18990
  manifestFile,
18933
18991
  hookMapping: options.hookMapping,
18934
- fromCollection: lockEntry.fromCollection
18992
+ fromCollection: lockEntry.fromCollection,
18935
18993
  // Preserve collection metadata
18994
+ global: preservedGlobal,
18995
+ editor: preservedEditor
18936
18996
  });
18937
18997
  successCount++;
18938
18998
  } catch (error) {
@@ -18941,7 +19001,7 @@ async function installFromLockfile(options) {
18941
19001
  } else {
18942
19002
  failCount++;
18943
19003
  console.error(` \u274C Failed to install ${displayName}:`);
18944
- console.error(` Type: ${(_b = error == null ? void 0 : error.constructor) == null ? void 0 : _b.name}`);
19004
+ console.error(` Type: ${(_d = error == null ? void 0 : error.constructor) == null ? void 0 : _d.name}`);
18945
19005
  console.error(` Message: ${error instanceof Error ? error.message : String(error)}`);
18946
19006
  if (error instanceof CLIError) {
18947
19007
  console.error(` ExitCode: ${error.exitCode}`);
@@ -18963,25 +19023,37 @@ async function installFromLockfile(options) {
18963
19023
  }
18964
19024
  function createInstallCommand() {
18965
19025
  const command = new import_commander12.Command("install");
18966
- command.description("Install a package from the registry, or install all packages from prpm.lock if no package specified").argument("[package]", "Package to install (e.g., react-rules or react-rules@1.2.0). If omitted, installs all packages from prpm.lock").option("--version <version>", "Specific version to install").option("--as <format>", `Convert and install in specific format (${import_types.FORMATS.join(", ")})`).option("--format <format>", "Alias for --as").option("--location <path>", "Custom location for installed files (Agents.md or nested Cursor rules)").option("--subtype <subtype>", "Specify subtype when converting (skill, agent, rule, etc.)").option("--hook-mapping <strategy>", "Hook mapping strategy: auto (default), strict, skip", "auto").option("--frozen-lockfile", "Fail if lock file needs to be updated (for CI)").option("-y, --yes", "Auto-confirm prompts (overwrite files without asking)").option("--no-append", "Skip adding skill to manifest file (skill files only)").option("--manifest-file <filename>", "Custom manifest filename for progressive disclosure").option("--eager", "Force skill/agent to always activate (not on-demand)").option("--lazy", "Use default on-demand activation (overrides package eager setting)").option("--tools <tools>", "Override Claude/Codex tool list for this install (comma- or space-separated)").option("--global", "Install MCP servers to global config (e.g., ~/.claude/settings.json, ~/.codex/config.toml, ~/.cursor/mcp.json, ~/.kiro/settings/mcp.json)").option("--editor <editor>", "[Deprecated: use --as] Target editor for MCP server installation").action(async (packageSpec, options) => {
19026
+ command.description("Install a package from the registry, or install all packages from prpm.lock if no package specified").argument("[package]", "Package to install (e.g., react-rules or react-rules@1.2.0). If omitted, installs all packages from prpm.lock").option("--version <version>", "Specific version to install").option("--as <format>", `Convert and install in specific format. Accepts a comma-separated list to install to multiple formats in one command (${import_types.FORMATS.join(", ")})`).option("--format <format>", "Alias for --as").option("--location <path>", "Custom location for installed files (Agents.md or nested Cursor rules)").option("--subtype <subtype>", "Specify subtype when converting (skill, agent, rule, etc.)").option("--hook-mapping <strategy>", "Hook mapping strategy: auto (default), strict, skip", "auto").option("--frozen-lockfile", "Fail if lock file needs to be updated (for CI)").option("-y, --yes", "Auto-confirm prompts (overwrite files without asking)").option("--no-append", "Skip adding skill to manifest file (skill files only)").option("--manifest-file <filename>", "Custom manifest filename for progressive disclosure").option("--eager", "Force skill/agent to always activate (not on-demand)").option("--lazy", "Use default on-demand activation (overrides package eager setting)").option("--tools <tools>", "Override Claude/Codex tool list for this install (comma- or space-separated)").option("--global", "Install to user-level/global locations where supported (e.g., ~/.claude/skills, ~/.agents/skills, ~/.codex/agents, or global MCP config)").option("--editor <editor>", "[Deprecated: use --as] Target editor for MCP server installation").action(async (packageSpec, options) => {
18967
19027
  const rawAs = options.format || options.as;
18968
19028
  const validFormats = import_types.FORMATS;
18969
- const isMCPEditorOnly = rawAs && !validFormats.includes(rawAs) && MCP_EDITORS.includes(rawAs);
18970
- const convertTo = isMCPEditorOnly ? void 0 : rawAs;
18971
- if (convertTo && !validFormats.includes(convertTo)) {
18972
- throw new CLIError(`\u274C Format must be one of: ${validFormats.join(", ")}
19029
+ const asTokens = rawAs ? Array.from(new Set(rawAs.split(",").map((s) => s.trim()).filter(Boolean))) : [];
19030
+ if (rawAs !== void 0 && asTokens.length === 0) {
19031
+ throw new CLIError(
19032
+ `\u274C --as requires at least one format. Got: "${rawAs}"
18973
19033
 
18974
19034
  \u{1F4A1} Examples:
18975
- prpm install my-package --as cursor # Convert to Cursor format
18976
- prpm install my-package --format claude # Convert to Claude format
18977
- prpm install my-package --format claude.md # Convert to Claude.md format
18978
- prpm install my-package --format kiro # Convert to Kiro format
18979
- prpm install my-package --format agents.md # Convert to Agents.md format
18980
- prpm install my-package --format gemini.md # Convert to Gemini format
18981
- prpm install my-mcp-server --as codex # Install MCP server to Codex
18982
- prpm install my-package # Install in native format`, 1);
18983
- }
18984
- const mcpEditor = options.editor || rawAs;
19035
+ prpm install my-package --as claude
19036
+ prpm install my-package --as claude,codex`,
19037
+ 1
19038
+ );
19039
+ }
19040
+ for (const token of asTokens) {
19041
+ const isFormat = validFormats.includes(token);
19042
+ const isMCPEditor = MCP_EDITORS.includes(token);
19043
+ if (!isFormat && !isMCPEditor) {
19044
+ throw new CLIError(`\u274C Format must be one of: ${validFormats.join(", ")}
19045
+
19046
+ \u{1F4A1} Examples:
19047
+ prpm install my-package --as cursor # Convert to Cursor format
19048
+ prpm install my-package --as claude,codex # Install to both Claude and Codex
19049
+ prpm install my-package --format claude.md # Convert to Claude.md format
19050
+ prpm install my-package --format kiro # Convert to Kiro format
19051
+ prpm install my-package --format agents.md # Convert to Agents.md format
19052
+ prpm install my-package --format gemini.md # Convert to Gemini format
19053
+ prpm install my-mcp-server --as codex # Install MCP server to Codex
19054
+ prpm install my-package # Install in native format`, 1);
19055
+ }
19056
+ }
18985
19057
  if (options.editor && !MCP_EDITORS.includes(options.editor)) {
18986
19058
  throw new CLIError(
18987
19059
  `Invalid MCP editor: ${options.editor}
@@ -19013,31 +19085,95 @@ Valid strategies: ${VALID_HOOK_MAPPING_STRATEGIES.join(", ")}`
19013
19085
  if (options.tools) {
19014
19086
  console.warn("\u26A0\uFE0F --tools is ignored when installing from prpm.lock (no package specified)");
19015
19087
  }
19088
+ if (asTokens.length > 1) {
19089
+ throw new CLIError(
19090
+ `\u274C Multi-format --as is not supported when installing from prpm.lock.
19091
+
19092
+ The lockfile already records the target format for each package. Run prpm install <package> --as ${asTokens.join(",")} for specific packages instead.`,
19093
+ 1
19094
+ );
19095
+ }
19096
+ const [singleAs] = asTokens;
19097
+ const isMCPEditorOnly = singleAs && !validFormats.includes(singleAs) && MCP_EDITORS.includes(singleAs);
19016
19098
  await installFromLockfile({
19017
- as: convertTo,
19099
+ as: isMCPEditorOnly ? void 0 : singleAs,
19018
19100
  subtype: options.subtype,
19019
19101
  frozenLockfile: options.frozenLockfile,
19020
19102
  location: options.location,
19021
- hookMapping: options.hookMapping
19103
+ hookMapping: options.hookMapping,
19104
+ global: options.global,
19105
+ editor: options.editor ?? (isMCPEditorOnly ? singleAs : void 0)
19022
19106
  });
19023
19107
  return;
19024
19108
  }
19025
19109
  const eager = options.eager ? true : options.lazy ? false : void 0;
19026
- await handleInstall(packageSpec, {
19027
- version: options.version,
19028
- as: convertTo,
19029
- subtype: options.subtype,
19030
- frozenLockfile: options.frozenLockfile,
19031
- force: options.yes,
19032
- location: options.location,
19033
- noAppend: options.noAppend,
19034
- manifestFile: options.manifestFile,
19035
- hookMapping: options.hookMapping,
19036
- eager,
19037
- tools: options.tools,
19038
- global: options.global,
19039
- editor: mcpEditor
19040
- });
19110
+ if (asTokens.length <= 1) {
19111
+ const [singleAs] = asTokens;
19112
+ const isMCPEditorOnly = singleAs && !validFormats.includes(singleAs) && MCP_EDITORS.includes(singleAs);
19113
+ const convertTo = isMCPEditorOnly ? void 0 : singleAs;
19114
+ const mcpEditor = options.editor || singleAs;
19115
+ await handleInstall(packageSpec, {
19116
+ version: options.version,
19117
+ as: convertTo,
19118
+ subtype: options.subtype,
19119
+ frozenLockfile: options.frozenLockfile,
19120
+ force: options.yes,
19121
+ location: options.location,
19122
+ noAppend: options.noAppend,
19123
+ manifestFile: options.manifestFile,
19124
+ hookMapping: options.hookMapping,
19125
+ eager,
19126
+ tools: options.tools,
19127
+ global: options.global,
19128
+ editor: mcpEditor
19129
+ });
19130
+ return;
19131
+ }
19132
+ console.log(`\u{1F4E6} Installing ${packageSpec} to ${asTokens.length} targets: ${asTokens.join(", ")}
19133
+ `);
19134
+ let successCount = 0;
19135
+ const failures = [];
19136
+ const isCollectionSpec = packageSpec.startsWith("collections/");
19137
+ for (const token of asTokens) {
19138
+ const isMCPEditorOnly = !validFormats.includes(token) && MCP_EDITORS.includes(token);
19139
+ const convertTo = isMCPEditorOnly ? void 0 : token;
19140
+ const mcpEditor = token;
19141
+ console.log(source_default.cyan(`
19142
+ \u2501\u2501 [${token}] \u2501\u2501`));
19143
+ try {
19144
+ await handleInstall(packageSpec, {
19145
+ version: options.version,
19146
+ as: convertTo,
19147
+ subtype: options.subtype,
19148
+ frozenLockfile: options.frozenLockfile,
19149
+ force: options.yes,
19150
+ location: options.location,
19151
+ noAppend: options.noAppend,
19152
+ manifestFile: options.manifestFile,
19153
+ hookMapping: options.hookMapping,
19154
+ eager,
19155
+ tools: options.tools,
19156
+ global: options.global,
19157
+ editor: options.editor || mcpEditor
19158
+ });
19159
+ successCount++;
19160
+ } catch (err) {
19161
+ if (err instanceof CLIError && err.exitCode === 0) {
19162
+ successCount++;
19163
+ continue;
19164
+ }
19165
+ const message = err instanceof Error ? err.message : String(err);
19166
+ failures.push({ target: token, error: message });
19167
+ console.error(source_default.red(`\u274C Failed to install ${packageSpec} as ${token}: ${message}`));
19168
+ }
19169
+ }
19170
+ console.log(`
19171
+ \u2705 Installed ${packageSpec} to ${successCount}/${asTokens.length} targets` + (isCollectionSpec ? " (collection)" : ""));
19172
+ if (failures.length > 0) {
19173
+ const detail = failures.map((f) => ` \u2022 ${f.target}: ${f.error}`).join("\n");
19174
+ throw new CLIError(`\u274C ${failures.length} target${failures.length === 1 ? "" : "s"} failed:
19175
+ ${detail}`, 1);
19176
+ }
19041
19177
  });
19042
19178
  return command;
19043
19179
  }
@@ -21000,7 +21136,14 @@ async function handleUninstall(name, options = {}) {
21000
21136
  if (requestedFormat) {
21001
21137
  const requestedKey = getLockfileKey(name, requestedFormat);
21002
21138
  if (!lockfile.packages[requestedKey]) {
21003
- if (lockfile.packages[name] && lockfile.packages[name].format === requestedFormat) {
21139
+ const matchingFormatKeys = matchingKeys.filter((key) => {
21140
+ const parsed = parseLockfileKey(key);
21141
+ const pkg = lockfile.packages[key];
21142
+ return parsed.format === requestedFormat || pkg.format === requestedFormat;
21143
+ });
21144
+ if (matchingFormatKeys.length > 0) {
21145
+ keysToUninstall = matchingFormatKeys;
21146
+ } else if (lockfile.packages[name] && lockfile.packages[name].format === requestedFormat) {
21004
21147
  keysToUninstall = [name];
21005
21148
  } else {
21006
21149
  throw new CLIError(`\u274C Package "${name}" with format "${requestedFormat}" not found`, 1);
@@ -24686,6 +24829,14 @@ init_lockfile();
24686
24829
  init_install();
24687
24830
  init_telemetry();
24688
24831
  init_errors();
24832
+ function getPreservedGlobal(pkg, location) {
24833
+ var _a;
24834
+ return pkg.global ?? ((_a = pkg.pluginMetadata) == null ? void 0 : _a.mcpGlobal) ?? (location === "global" ? true : void 0);
24835
+ }
24836
+ function getPreservedEditor(pkg) {
24837
+ var _a;
24838
+ return (_a = pkg.pluginMetadata) == null ? void 0 : _a.mcpEditor;
24839
+ }
24689
24840
  async function handleUpdate(packageName, options = {}) {
24690
24841
  const startTime = Date.now();
24691
24842
  let success = false;
@@ -24712,7 +24863,7 @@ async function handleUpdate(packageName, options = {}) {
24712
24863
  }
24713
24864
  console.log("\u{1F504} Checking for updates...\n");
24714
24865
  for (const pkg of packagesToUpdate) {
24715
- const { packageId, format: installedFormat } = parseLockfileKey(pkg.id);
24866
+ const { packageId, format: installedFormat, location } = parseLockfileKey(pkg.id);
24716
24867
  try {
24717
24868
  const registryPkg = await client.getPackage(packageId);
24718
24869
  if (!registryPkg.latest_version || !pkg.version) {
@@ -24738,9 +24889,16 @@ async function handleUpdate(packageName, options = {}) {
24738
24889
  \u{1F4E6} Updating ${packageId}: ${currentVersion} \u2192 ${latestVersion}`
24739
24890
  );
24740
24891
  const targetFormat = pkg.format || installedFormat;
24741
- await handleInstall(`${packageId}@${latestVersion}`, {
24742
- as: targetFormat
24743
- });
24892
+ const installOptions = { as: targetFormat };
24893
+ const preservedGlobal = getPreservedGlobal(pkg, location);
24894
+ const preservedEditor = getPreservedEditor(pkg);
24895
+ if (preservedGlobal !== void 0) {
24896
+ installOptions.global = preservedGlobal;
24897
+ }
24898
+ if (preservedEditor) {
24899
+ installOptions.editor = preservedEditor;
24900
+ }
24901
+ await handleInstall(`${packageId}@${latestVersion}`, installOptions);
24744
24902
  updatedCount++;
24745
24903
  } catch (err) {
24746
24904
  console.error(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prpm",
3
- "version": "2.1.36",
3
+ "version": "2.1.38",
4
4
  "description": "Prompt Package Manager CLI - Install and manage prompt-based files",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -45,9 +45,9 @@
45
45
  "license": "MIT",
46
46
  "dependencies": {
47
47
  "@octokit/rest": "^22.0.0",
48
- "@pr-pm/converters": "^2.1.37",
49
- "@pr-pm/registry-client": "^2.3.36",
50
- "@pr-pm/types": "^2.1.37",
48
+ "@pr-pm/converters": "^2.1.39",
49
+ "@pr-pm/registry-client": "^2.3.38",
50
+ "@pr-pm/types": "^2.1.39",
51
51
  "ajv": "^8.17.1",
52
52
  "ajv-formats": "^3.0.1",
53
53
  "chalk": "^5.6.2",