@unbrained/pm-cli 2026.5.11 → 2026.5.14

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 (171) hide show
  1. package/AGENTS.md +3 -116
  2. package/CHANGELOG.md +18 -0
  3. package/PRD.md +18 -39
  4. package/README.md +8 -5
  5. package/dist/cli/commander-usage.js +27 -0
  6. package/dist/cli/commander-usage.js.map +1 -1
  7. package/dist/cli/commands/activity.js +19 -4
  8. package/dist/cli/commands/activity.js.map +1 -1
  9. package/dist/cli/commands/calendar.js +5 -2
  10. package/dist/cli/commands/calendar.js.map +1 -1
  11. package/dist/cli/commands/contracts.js +63 -19
  12. package/dist/cli/commands/contracts.js.map +1 -1
  13. package/dist/cli/commands/create.js +58 -3
  14. package/dist/cli/commands/create.js.map +1 -1
  15. package/dist/cli/commands/extension.d.ts +14 -3
  16. package/dist/cli/commands/extension.js +481 -95
  17. package/dist/cli/commands/extension.js.map +1 -1
  18. package/dist/cli/commands/index.d.ts +1 -8
  19. package/dist/cli/commands/index.js +1 -8
  20. package/dist/cli/commands/index.js.map +1 -1
  21. package/dist/cli/commands/reindex.d.ts +8 -0
  22. package/dist/cli/commands/reindex.js +96 -23
  23. package/dist/cli/commands/reindex.js.map +1 -1
  24. package/dist/cli/commands/search.js +51 -25
  25. package/dist/cli/commands/search.js.map +1 -1
  26. package/dist/cli/commands/test.js +14 -6
  27. package/dist/cli/commands/test.js.map +1 -1
  28. package/dist/cli/commands/upgrade.d.ts +63 -0
  29. package/dist/cli/commands/upgrade.js +260 -0
  30. package/dist/cli/commands/upgrade.js.map +1 -0
  31. package/dist/cli/guide-topics.js +18 -16
  32. package/dist/cli/guide-topics.js.map +1 -1
  33. package/dist/cli/help-content.js +57 -18
  34. package/dist/cli/help-content.js.map +1 -1
  35. package/dist/cli/main.js +73 -7
  36. package/dist/cli/main.js.map +1 -1
  37. package/dist/cli/register-list-query.js +24 -142
  38. package/dist/cli/register-list-query.js.map +1 -1
  39. package/dist/cli/register-mutation.js +49 -257
  40. package/dist/cli/register-mutation.js.map +1 -1
  41. package/dist/cli/register-operations.js +29 -198
  42. package/dist/cli/register-operations.js.map +1 -1
  43. package/dist/cli/register-setup.js +181 -204
  44. package/dist/cli/register-setup.js.map +1 -1
  45. package/dist/cli/registration-helpers.d.ts +2 -2
  46. package/dist/cli/registration-helpers.js +1 -19
  47. package/dist/cli/registration-helpers.js.map +1 -1
  48. package/dist/core/extensions/loader.js +7 -1
  49. package/dist/core/extensions/loader.js.map +1 -1
  50. package/dist/core/packages/manifest.d.ts +38 -0
  51. package/dist/core/packages/manifest.js +221 -0
  52. package/dist/core/packages/manifest.js.map +1 -0
  53. package/dist/core/search/embedding-batches.d.ts +13 -1
  54. package/dist/core/search/embedding-batches.js +19 -1
  55. package/dist/core/search/embedding-batches.js.map +1 -1
  56. package/dist/core/store/front-matter-cache.d.ts +8 -1
  57. package/dist/core/store/front-matter-cache.js +20 -11
  58. package/dist/core/store/front-matter-cache.js.map +1 -1
  59. package/dist/mcp/server.d.ts +8 -0
  60. package/dist/mcp/server.js +100 -43
  61. package/dist/mcp/server.js.map +1 -1
  62. package/dist/sdk/cli-contracts/commander-mutation-options.d.ts +7 -0
  63. package/dist/sdk/cli-contracts/commander-mutation-options.js +477 -0
  64. package/dist/sdk/cli-contracts/commander-mutation-options.js.map +1 -0
  65. package/dist/sdk/cli-contracts/commander-types.d.ts +21 -0
  66. package/dist/sdk/cli-contracts/commander-types.js +92 -0
  67. package/dist/sdk/cli-contracts/commander-types.js.map +1 -0
  68. package/dist/sdk/cli-contracts.d.ts +22 -32
  69. package/dist/sdk/cli-contracts.js +155 -296
  70. package/dist/sdk/cli-contracts.js.map +1 -1
  71. package/dist/sdk/index.d.ts +2 -0
  72. package/dist/sdk/index.js +2 -0
  73. package/dist/sdk/index.js.map +1 -1
  74. package/dist/sdk/runtime.d.ts +29 -0
  75. package/dist/sdk/runtime.js +28 -0
  76. package/dist/sdk/runtime.js.map +1 -0
  77. package/docs/ARCHITECTURE.md +1 -1
  78. package/docs/COMMANDS.md +17 -1
  79. package/docs/EXTENSIONS.md +169 -61
  80. package/docs/QUICKSTART.md +11 -2
  81. package/docs/README.md +4 -6
  82. package/docs/RELEASING.md +4 -2
  83. package/docs/SDK.md +79 -438
  84. package/package.json +6 -23
  85. package/packages/pm-beads/README.md +10 -0
  86. package/packages/pm-beads/extensions/beads/index.js +113 -0
  87. package/{.agents/pm/extensions/beads/index.js → packages/pm-beads/extensions/beads/index.ts} +42 -20
  88. package/{.agents/pm → packages/pm-beads}/extensions/beads/runtime.js +2 -17
  89. package/{.agents/pm → packages/pm-beads}/extensions/beads/runtime.ts +41 -18
  90. package/packages/pm-beads/package.json +50 -0
  91. package/packages/pm-calendar/README.md +13 -0
  92. package/packages/pm-calendar/extensions/calendar/index.js +56 -0
  93. package/packages/pm-calendar/extensions/calendar/index.ts +62 -0
  94. package/packages/pm-calendar/extensions/calendar/manifest.json +7 -0
  95. package/packages/pm-calendar/extensions/calendar/runtime.js +95 -0
  96. package/packages/pm-calendar/extensions/calendar/runtime.ts +104 -0
  97. package/packages/pm-calendar/package.json +51 -0
  98. package/packages/pm-governance-audit/README.md +23 -0
  99. package/packages/pm-governance-audit/extensions/governance-audit/index.js +117 -0
  100. package/packages/pm-governance-audit/extensions/governance-audit/index.ts +118 -0
  101. package/packages/pm-governance-audit/extensions/governance-audit/manifest.json +7 -0
  102. package/packages/pm-governance-audit/extensions/governance-audit/runtime.js +159 -0
  103. package/packages/pm-governance-audit/extensions/governance-audit/runtime.ts +176 -0
  104. package/packages/pm-governance-audit/package.json +52 -0
  105. package/packages/pm-guide-shell/README.md +23 -0
  106. package/packages/pm-guide-shell/extensions/guide-shell/index.js +76 -0
  107. package/packages/pm-guide-shell/extensions/guide-shell/index.ts +81 -0
  108. package/packages/pm-guide-shell/extensions/guide-shell/manifest.json +7 -0
  109. package/packages/pm-guide-shell/extensions/guide-shell/runtime.js +263 -0
  110. package/packages/pm-guide-shell/extensions/guide-shell/runtime.ts +327 -0
  111. package/packages/pm-guide-shell/package.json +52 -0
  112. package/packages/pm-linked-test-adapters/README.md +24 -0
  113. package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/index.js +101 -0
  114. package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/index.ts +102 -0
  115. package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/manifest.json +7 -0
  116. package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/runtime.js +142 -0
  117. package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/runtime.ts +173 -0
  118. package/packages/pm-linked-test-adapters/package.json +53 -0
  119. package/packages/pm-search-advanced/README.md +27 -0
  120. package/packages/pm-search-advanced/extensions/search-advanced/index.js +93 -0
  121. package/packages/pm-search-advanced/extensions/search-advanced/index.ts +94 -0
  122. package/packages/pm-search-advanced/extensions/search-advanced/manifest.json +7 -0
  123. package/packages/pm-search-advanced/extensions/search-advanced/runtime.js +120 -0
  124. package/packages/pm-search-advanced/extensions/search-advanced/runtime.ts +144 -0
  125. package/packages/pm-search-advanced/package.json +54 -0
  126. package/packages/pm-templates/README.md +20 -0
  127. package/packages/pm-templates/extensions/templates/index.js +101 -0
  128. package/packages/pm-templates/extensions/templates/index.ts +109 -0
  129. package/packages/pm-templates/extensions/templates/manifest.json +7 -0
  130. package/packages/pm-templates/extensions/templates/runtime.js +226 -0
  131. package/packages/pm-templates/extensions/templates/runtime.ts +283 -0
  132. package/packages/pm-templates/package.json +50 -0
  133. package/packages/pm-todos/README.md +11 -0
  134. package/packages/pm-todos/extensions/todos/index.js +130 -0
  135. package/{.agents/pm/extensions/todos/index.js → packages/pm-todos/extensions/todos/index.ts} +47 -23
  136. package/{.agents/pm → packages/pm-todos}/extensions/todos/runtime.js +3 -18
  137. package/{.agents/pm → packages/pm-todos}/extensions/todos/runtime.ts +42 -20
  138. package/packages/pm-todos/package.json +51 -0
  139. package/plugins/pm-cli-claude/README.md +1 -2
  140. package/plugins/pm-cli-claude/hooks/session-start.mjs +4 -55
  141. package/plugins/pm-cli-claude/scripts/pm-mcp-server.mjs +4 -2
  142. package/plugins/pm-cli-codex/scripts/pm-mcp-server.mjs +4 -2
  143. package/.agents/pm/extensions/.managed-extensions.json +0 -42
  144. package/.agents/skills/HARNESS_COMPATIBILITY.md +0 -45
  145. package/.agents/skills/README.md +0 -21
  146. package/.agents/skills/pm-developer/SKILL.md +0 -73
  147. package/.agents/skills/pm-developer/references/COMMAND_PLAYBOOK.md +0 -48
  148. package/.agents/skills/pm-developer/references/PROMPTS.md +0 -17
  149. package/.agents/skills/pm-extensions/SKILL.md +0 -57
  150. package/.agents/skills/pm-extensions/references/LIFECYCLE.md +0 -40
  151. package/.agents/skills/pm-extensions/references/TROUBLESHOOTING.md +0 -25
  152. package/.agents/skills/pm-sdk/SKILL.md +0 -50
  153. package/.agents/skills/pm-sdk/references/INTEGRATION_CHECKLIST.md +0 -31
  154. package/.agents/skills/pm-sdk/references/PROMPTS.md +0 -13
  155. package/.agents/skills/pm-user/SKILL.md +0 -59
  156. package/.agents/skills/pm-user/references/PROMPTS.md +0 -17
  157. package/.agents/skills/pm-user/references/WORKFLOWS.md +0 -35
  158. package/.pi/README.md +0 -35
  159. package/.pi/agents/pm-triage-agent.md +0 -19
  160. package/.pi/agents/pm-verification-agent.md +0 -21
  161. package/.pi/chains/pm-native-delivery.chain.md +0 -11
  162. package/.pi/extensions/pm-cli/index.js +0 -387
  163. package/.pi/prompts/pm-workflow.md +0 -5
  164. package/.pi/skills/pm-native/SKILL.md +0 -44
  165. package/.pi/skills/pm-release/SKILL.md +0 -35
  166. package/dist/pi/native.d.ts +0 -5
  167. package/dist/pi/native.js +0 -236
  168. package/dist/pi/native.js.map +0 -1
  169. package/docs/PI_PACKAGE.md +0 -141
  170. /package/{.agents/pm → packages/pm-beads}/extensions/beads/manifest.json +0 -0
  171. /package/{.agents/pm → packages/pm-todos}/extensions/todos/manifest.json +0 -0
@@ -2,11 +2,12 @@ import { execFile } from "node:child_process";
2
2
  import fs from "node:fs/promises";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
- import { fileURLToPath } from "node:url";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { promisify } from "node:util";
7
7
  import { activateExtensions, loadExtensions, nextExtensionReloadToken } from "../../core/extensions/index.js";
8
8
  import { EXTENSION_CAPABILITY_CONTRACT, KNOWN_EXTENSION_CAPABILITIES, parseLegacyExtensionCapabilityAliasWarning, parseUnknownExtensionCapabilityWarning, resolveExtensionRoots, } from "../../core/extensions/loader.js";
9
9
  import { pathExists } from "../../core/fs/fs-utils.js";
10
+ import { PM_PACKAGE_RESOURCE_KINDS, collectPackageExtensionDirectories, readPmPackageManifest, } from "../../core/packages/manifest.js";
10
11
  import { EXIT_CODE } from "../../core/shared/constants.js";
11
12
  import { PmCliError } from "../../core/shared/errors.js";
12
13
  import { nowIso } from "../../core/shared/time.js";
@@ -17,10 +18,17 @@ const DEFAULT_EXTENSION_PRIORITY = 100;
17
18
  const MANAGED_EXTENSION_STATE_FILENAME = ".managed-extensions.json";
18
19
  const MANAGED_EXTENSION_STATE_VERSION = 1;
19
20
  const PM_PACKAGE_ROOT_ENV = "PM_CLI_PACKAGE_ROOT";
20
- const BUNDLED_EXTENSION_ALIASES = {
21
- beads: "beads",
22
- todos: "todos",
21
+ const LEGACY_BUNDLED_PACKAGE_ALIASES = {
22
+ beads: {
23
+ package_directory: "pm-beads",
24
+ legacy_extension_directory: "beads",
25
+ },
26
+ todos: {
27
+ package_directory: "pm-todos",
28
+ legacy_extension_directory: "todos",
29
+ },
23
30
  };
31
+ const BUNDLED_PACKAGE_INSTALL_ALL_TARGETS = new Set(["*", "all"]);
24
32
  function resolvePackageRootCandidates() {
25
33
  const candidates = [];
26
34
  const envRoot = process.env[PM_PACKAGE_ROOT_ENV];
@@ -33,18 +41,87 @@ function resolvePackageRootCandidates() {
33
41
  }
34
42
  async function resolveBundledExtensionAliasSource(input) {
35
43
  const normalized = input.trim().toLowerCase();
36
- const alias = BUNDLED_EXTENSION_ALIASES[normalized];
44
+ const packageRoot = await resolveBundledPackageRoot(normalized);
45
+ if (packageRoot) {
46
+ return packageRoot;
47
+ }
48
+ const alias = LEGACY_BUNDLED_PACKAGE_ALIASES[normalized];
37
49
  if (!alias) {
38
50
  return null;
39
51
  }
40
52
  for (const packageRoot of resolvePackageRootCandidates()) {
41
- const bundledPath = path.join(packageRoot, ".agents", "pm", "extensions", alias);
42
- if (await pathExists(path.join(bundledPath, "manifest.json"))) {
43
- return bundledPath;
53
+ const legacyExtensionPath = path.join(packageRoot, ".agents", "pm", "extensions", alias.legacy_extension_directory);
54
+ if (await pathExists(path.join(legacyExtensionPath, "manifest.json"))) {
55
+ return legacyExtensionPath;
44
56
  }
45
57
  }
46
58
  return null;
47
59
  }
60
+ function isBundledPackageInstallAllTarget(input) {
61
+ return BUNDLED_PACKAGE_INSTALL_ALL_TARGETS.has(input.trim().toLowerCase());
62
+ }
63
+ function derivePackageAlias(packageDirectory) {
64
+ return packageDirectory.replace(/^pm-/i, "").trim().toLowerCase();
65
+ }
66
+ async function collectBundledPackageEntries() {
67
+ const entriesByAlias = new Map();
68
+ for (const packageRoot of resolvePackageRootCandidates()) {
69
+ const packagesRoot = path.join(packageRoot, "packages");
70
+ if (!(await pathExists(packagesRoot))) {
71
+ continue;
72
+ }
73
+ const entries = await fs.readdir(packagesRoot, { withFileTypes: true });
74
+ for (const entry of entries) {
75
+ if (!entry.isDirectory() || !entry.name.startsWith("pm-")) {
76
+ continue;
77
+ }
78
+ const candidateRoot = path.join(packagesRoot, entry.name);
79
+ if (!(await pathExists(path.join(candidateRoot, "package.json")))) {
80
+ continue;
81
+ }
82
+ const manifest = await readPmPackageManifest(candidateRoot);
83
+ const aliases = manifest.aliases && manifest.aliases.length > 0
84
+ ? manifest.aliases
85
+ : [derivePackageAlias(entry.name)];
86
+ for (const alias of aliases) {
87
+ const normalizedAlias = alias.trim().toLowerCase();
88
+ if (normalizedAlias.length === 0 || entriesByAlias.has(normalizedAlias)) {
89
+ continue;
90
+ }
91
+ entriesByAlias.set(normalizedAlias, {
92
+ alias: normalizedAlias,
93
+ package_directory: entry.name,
94
+ package_root: candidateRoot,
95
+ });
96
+ }
97
+ }
98
+ }
99
+ for (const [alias, legacy] of Object.entries(LEGACY_BUNDLED_PACKAGE_ALIASES)) {
100
+ if (entriesByAlias.has(alias)) {
101
+ continue;
102
+ }
103
+ for (const packageRoot of resolvePackageRootCandidates()) {
104
+ const packagePath = path.join(packageRoot, "packages", legacy.package_directory);
105
+ if (await pathExists(path.join(packagePath, "package.json"))) {
106
+ entriesByAlias.set(alias, {
107
+ alias,
108
+ package_directory: legacy.package_directory,
109
+ package_root: packagePath,
110
+ });
111
+ break;
112
+ }
113
+ }
114
+ }
115
+ return [...entriesByAlias.values()].sort((left, right) => left.alias.localeCompare(right.alias));
116
+ }
117
+ async function listBundledPackageAliases() {
118
+ return (await collectBundledPackageEntries()).map((entry) => entry.alias);
119
+ }
120
+ async function resolveBundledPackageRoot(alias) {
121
+ const normalized = alias.trim().toLowerCase();
122
+ const entry = (await collectBundledPackageEntries()).find((candidate) => candidate.alias === normalized);
123
+ return entry?.package_root ?? null;
124
+ }
48
125
  function normalizeStringList(values) {
49
126
  return [...new Set(values.map((value) => value.trim()).filter((value) => value.length > 0))].sort((left, right) => left.localeCompare(right));
50
127
  }
@@ -273,7 +350,7 @@ function normalizeManagedState(raw) {
273
350
  continue;
274
351
  }
275
352
  const source = entry.source;
276
- if ((source.kind !== "local" && source.kind !== "github") ||
353
+ if ((source.kind !== "local" && source.kind !== "github" && source.kind !== "npm") ||
277
354
  typeof source.input !== "string" ||
278
355
  typeof source.location !== "string") {
279
356
  continue;
@@ -291,6 +368,8 @@ function normalizeManagedState(raw) {
291
368
  kind: source.kind,
292
369
  input: source.input,
293
370
  location: source.location,
371
+ package: typeof source.package === "string" ? source.package : undefined,
372
+ version: typeof source.version === "string" ? source.version : undefined,
294
373
  repository: typeof source.repository === "string" ? source.repository : undefined,
295
374
  owner: typeof source.owner === "string" ? source.owner : undefined,
296
375
  repo: typeof source.repo === "string" ? source.repo : undefined,
@@ -366,7 +445,11 @@ async function resolveBundledAliasManifestName(input) {
366
445
  return null;
367
446
  }
368
447
  try {
369
- const validated = await validateExtensionDirectory(bundledAliasSource);
448
+ const extensionDirectories = await collectPackageExtensionDirectories(bundledAliasSource);
449
+ if (extensionDirectories.length !== 1) {
450
+ return null;
451
+ }
452
+ const validated = await validateExtensionDirectory(extensionDirectories[0]);
370
453
  return validated.manifest.name;
371
454
  }
372
455
  catch {
@@ -453,6 +536,7 @@ function resolveAction(target, options) {
453
536
  options.manage ? "manage" : null,
454
537
  options.reload ? "reload" : null,
455
538
  options.doctor ? "doctor" : null,
539
+ options.catalog ? "catalog" : null,
456
540
  options.init ? "init" : null,
457
541
  options.scaffold ? "init" : null,
458
542
  options.adopt ? "adopt" : null,
@@ -467,10 +551,13 @@ function resolveAction(target, options) {
467
551
  if (typeof target === "string" && target.trim().toLowerCase() === "reload") {
468
552
  return "reload";
469
553
  }
554
+ if (typeof target === "string" && target.trim().toLowerCase() === "catalog") {
555
+ return "catalog";
556
+ }
470
557
  if (typeof target === "string" && (target.trim().toLowerCase() === "init" || target.trim().toLowerCase() === "scaffold")) {
471
558
  return "init";
472
559
  }
473
- throw new PmCliError("One action flag is required. Use one of: --install, --uninstall, --explore, --manage, --reload, --doctor, --init/--scaffold, --adopt, --adopt-all, --activate, --deactivate.", EXIT_CODE.USAGE);
560
+ throw new PmCliError("One action flag is required. Use one of: --install, --uninstall, --explore, --manage, --reload, --doctor, --catalog, --init/--scaffold, --adopt, --adopt-all, --activate, --deactivate.", EXIT_CODE.USAGE);
474
561
  }
475
562
  if (selected.length > 1) {
476
563
  throw new PmCliError("Extension action flags are mutually exclusive.", EXIT_CODE.USAGE);
@@ -485,6 +572,77 @@ function resolveScope(options) {
485
572
  }
486
573
  return global ? "global" : "project";
487
574
  }
575
+ async function buildBundledPackageCatalog(scope, global) {
576
+ const roots = resolveExtensionRoots(resolvePmRoot(process.cwd(), global.path), process.cwd());
577
+ const selectedRoot = scope === "global" ? roots.global : roots.project;
578
+ const managedStateRead = await readManagedExtensionState(selectedRoot);
579
+ const installedLocations = new Set(managedStateRead.state.entries
580
+ .filter((entry) => entry.scope === scope)
581
+ .map((entry) => path.resolve(entry.source.location)));
582
+ const packages = [];
583
+ for (const alias of await listBundledPackageAliases()) {
584
+ const packageRoot = await resolveBundledPackageRoot(alias);
585
+ const installScopeFlag = scope === "global" ? "--global" : "--project";
586
+ if (!packageRoot) {
587
+ packages.push({
588
+ alias,
589
+ bundled: true,
590
+ available: false,
591
+ installed: false,
592
+ install_target: alias,
593
+ install_command: `pm install ${alias} ${installScopeFlag}`,
594
+ });
595
+ continue;
596
+ }
597
+ const manifest = await readPmPackageManifest(packageRoot);
598
+ const repository = manifest.catalog?.links?.repository ?? manifest.package_repository_url;
599
+ const report = manifest.catalog?.links?.report ?? manifest.package_bugs_url;
600
+ const docs = manifest.catalog?.links?.docs ?? manifest.package_homepage;
601
+ const npm = manifest.catalog?.links?.npm ??
602
+ (manifest.package_name ? `https://www.npmjs.com/package/${encodeURIComponent(manifest.package_name)}` : undefined);
603
+ const metadataOnlyResources = Object.fromEntries(PM_PACKAGE_RESOURCE_KINDS
604
+ .filter((resourceKind) => resourceKind !== "extensions")
605
+ .map((resourceKind) => [resourceKind, manifest.resources[resourceKind] ?? []])
606
+ .filter(([, entries]) => Array.isArray(entries) && entries.length > 0));
607
+ packages.push({
608
+ alias,
609
+ bundled: true,
610
+ available: true,
611
+ installed: installedLocations.has(path.resolve(packageRoot)),
612
+ install_target: alias,
613
+ install_command: `pm install ${alias} ${installScopeFlag}`,
614
+ package_root: packageRoot,
615
+ package_name: manifest.package_name,
616
+ package_version: manifest.package_version,
617
+ description: manifest.catalog?.summary ?? manifest.package_description,
618
+ keywords: manifest.package_keywords ?? [],
619
+ resources: manifest.resources,
620
+ installable_resources: {
621
+ extensions: manifest.resources.extensions ?? [],
622
+ },
623
+ metadata_only_resources: metadataOnlyResources,
624
+ catalog: {
625
+ display_name: manifest.catalog?.display_name,
626
+ category: manifest.catalog?.category,
627
+ tags: manifest.catalog?.tags ?? manifest.package_keywords ?? [],
628
+ links: {
629
+ docs,
630
+ npm,
631
+ repository,
632
+ report,
633
+ },
634
+ media: manifest.catalog?.media,
635
+ },
636
+ });
637
+ }
638
+ return {
639
+ total: packages.length,
640
+ scope,
641
+ installable_resource_kinds: ["extensions"],
642
+ metadata_only_resource_kinds: PM_PACKAGE_RESOURCE_KINDS.filter((resourceKind) => resourceKind !== "extensions"),
643
+ packages,
644
+ };
645
+ }
488
646
  function parseGithubPathSpec(pathSpec, input, refOverride) {
489
647
  const segments = pathSpec
490
648
  .split("/")
@@ -527,6 +685,23 @@ export function parseExtensionInstallSource(input, options = {}) {
527
685
  throw new PmCliError("Extension source is required for --install.", EXIT_CODE.USAGE);
528
686
  }
529
687
  const refOverride = typeof options.ref === "string" && options.ref.trim().length > 0 ? options.ref.trim() : undefined;
688
+ if (normalizedInput.startsWith("npm:")) {
689
+ const spec = normalizedInput.slice("npm:".length).trim();
690
+ if (spec.length === 0) {
691
+ throw new PmCliError('npm package source must include a package spec after "npm:".', EXIT_CODE.USAGE);
692
+ }
693
+ if (options.forceGithub) {
694
+ throw new PmCliError('Options "--gh/--github" cannot be combined with npm: package sources.', EXIT_CODE.USAGE);
695
+ }
696
+ if (refOverride) {
697
+ throw new PmCliError('Option "--ref" cannot be combined with npm: package sources.', EXIT_CODE.USAGE);
698
+ }
699
+ return {
700
+ kind: "npm",
701
+ input: normalizedInput,
702
+ spec,
703
+ };
704
+ }
530
705
  const maybeGithubByUrl = (() => {
531
706
  try {
532
707
  const parsed = new URL(normalizedInput);
@@ -578,22 +753,123 @@ async function runGitCommand(args) {
578
753
  throw new PmCliError(`Git command failed: git ${args.join(" ")}\n${message}`, EXIT_CODE.GENERIC_FAILURE);
579
754
  }
580
755
  }
581
- async function listManifestDirectories(parentDirectory) {
582
- if (!(await pathExists(parentDirectory))) {
583
- return [];
756
+ async function runNpmCommand(args, cwd) {
757
+ try {
758
+ const result = await execFileAsync("npm", args, { cwd, encoding: "utf8" });
759
+ return (result.stdout ?? "").trim();
584
760
  }
585
- const entries = await fs.readdir(parentDirectory, { withFileTypes: true });
586
- const candidates = [];
587
- for (const entry of entries) {
588
- if (!entry.isDirectory()) {
589
- continue;
761
+ catch (error) {
762
+ const stderr = typeof error === "object" && error !== null && "stderr" in error ? String(error.stderr) : "";
763
+ const message = stderr.trim().length > 0 ? stderr.trim() : error instanceof Error ? error.message : String(error);
764
+ throw new PmCliError(`npm command failed: npm ${args.join(" ")}\n${message}`, EXIT_CODE.GENERIC_FAILURE);
765
+ }
766
+ }
767
+ async function resolveLocalNpmPackagePath(spec) {
768
+ if (path.isAbsolute(spec) || spec.startsWith(".") || spec.startsWith("..")) {
769
+ const absolutePath = path.resolve(process.cwd(), spec);
770
+ return (await pathExists(absolutePath)) ? absolutePath : null;
771
+ }
772
+ try {
773
+ const parsed = new URL(spec);
774
+ if (parsed.protocol === "file:") {
775
+ const absolutePath = fileURLToPath(parsed);
776
+ return (await pathExists(absolutePath)) ? absolutePath : null;
590
777
  }
591
- const directory = path.join(parentDirectory, entry.name);
592
- if (await pathExists(path.join(directory, "manifest.json"))) {
593
- candidates.push(directory);
778
+ }
779
+ catch {
780
+ // Registry package specs are not URLs.
781
+ }
782
+ return null;
783
+ }
784
+ async function resolveNpmPackSpec(spec) {
785
+ const localPath = await resolveLocalNpmPackagePath(spec);
786
+ if (localPath) {
787
+ return pathToFileURL(localPath).href;
788
+ }
789
+ if (/^[a-z][a-z0-9+.-]*:/i.test(spec)) {
790
+ return spec;
791
+ }
792
+ return spec;
793
+ }
794
+ function parsePackedNpmPackage(stdout, packDirectory) {
795
+ try {
796
+ const parsed = JSON.parse(stdout);
797
+ const first = Array.isArray(parsed) ? parsed[0] : undefined;
798
+ if (first && typeof first.filename === "string" && first.filename.trim().length > 0) {
799
+ return {
800
+ tarball: path.resolve(packDirectory, first.filename),
801
+ package: typeof first.name === "string" ? first.name : undefined,
802
+ version: typeof first.version === "string" ? first.version : undefined,
803
+ };
594
804
  }
595
805
  }
596
- return candidates.sort((left, right) => left.localeCompare(right));
806
+ catch {
807
+ // Fall back to the last stdout line for older npm output.
808
+ }
809
+ const lastLine = stdout
810
+ .split(/\r?\n/)
811
+ .map((line) => line.trim())
812
+ .filter((line) => line.length > 0)
813
+ .at(-1);
814
+ if (!lastLine) {
815
+ throw new PmCliError("npm pack did not report a tarball filename.", EXIT_CODE.GENERIC_FAILURE);
816
+ }
817
+ return {
818
+ tarball: path.resolve(packDirectory, lastLine),
819
+ };
820
+ }
821
+ async function resolveNpmSourceDirectory(source) {
822
+ const localPackageRoot = await resolveLocalNpmPackagePath(source.spec);
823
+ if (localPackageRoot) {
824
+ const packageJsonPath = path.join(localPackageRoot, "package.json");
825
+ const packageJson = (await pathExists(packageJsonPath))
826
+ ? JSON.parse(await fs.readFile(packageJsonPath, "utf8"))
827
+ : {};
828
+ return {
829
+ directory: await resolvePackageExtensionDirectory(localPackageRoot, source.input),
830
+ package: typeof packageJson.name === "string" ? packageJson.name : undefined,
831
+ version: typeof packageJson.version === "string" ? packageJson.version : undefined,
832
+ cleanup: async () => { },
833
+ };
834
+ }
835
+ const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "pm-npm-package-source-"));
836
+ const packDirectory = path.join(tempRoot, "pack");
837
+ const extractDirectory = path.join(tempRoot, "extract");
838
+ await fs.mkdir(packDirectory, { recursive: true });
839
+ await fs.mkdir(extractDirectory, { recursive: true });
840
+ try {
841
+ const packSpec = await resolveNpmPackSpec(source.spec);
842
+ const packStdout = await runNpmCommand(["pack", packSpec, "--json", "--pack-destination", packDirectory]);
843
+ const packed = parsePackedNpmPackage(packStdout, packDirectory);
844
+ await execFileAsync("tar", ["-xzf", packed.tarball, "-C", extractDirectory], { encoding: "utf8" });
845
+ const packageRoot = path.join(extractDirectory, "package");
846
+ const directory = await resolvePackageExtensionDirectory(packageRoot, source.input);
847
+ return {
848
+ directory,
849
+ package: packed.package,
850
+ version: packed.version,
851
+ cleanup: async () => {
852
+ await fs.rm(tempRoot, { recursive: true, force: true });
853
+ },
854
+ };
855
+ }
856
+ catch (error) {
857
+ await fs.rm(tempRoot, { recursive: true, force: true });
858
+ throw error;
859
+ }
860
+ }
861
+ async function resolvePackageExtensionDirectory(packageRoot, sourceLabel) {
862
+ const discovered = await collectPackageExtensionDirectories(packageRoot);
863
+ if (discovered.length === 1) {
864
+ return discovered[0];
865
+ }
866
+ if (discovered.length > 1) {
867
+ const choices = discovered
868
+ .map((entry) => path.relative(packageRoot, entry).replaceAll(path.sep, "/"))
869
+ .sort((left, right) => left.localeCompare(right));
870
+ throw new PmCliError(`Package source "${sourceLabel}" contains multiple extension manifests. Provide an explicit extension path. Candidates: ${choices.join(", ")}`, EXIT_CODE.USAGE);
871
+ }
872
+ throw new PmCliError(`Unable to locate a pm extension manifest in package source "${sourceLabel}". Package installs currently activate only extension resources, so add package.json pm.extensions or an extensions/ directory. Metadata-only resources like pm.docs/pm.examples are catalog metadata and do not activate commands.`, EXIT_CODE.USAGE);
597
873
  }
598
874
  async function resolveGithubSourceDirectory(cloneDirectory, source) {
599
875
  const candidatePaths = [];
@@ -612,25 +888,11 @@ async function resolveGithubSourceDirectory(cloneDirectory, source) {
612
888
  if (await pathExists(path.join(cloneDirectory, "manifest.json"))) {
613
889
  return { directory: cloneDirectory, resolved_subpath: "." };
614
890
  }
615
- const defaultRoots = [
616
- path.join(cloneDirectory, ".agents", "pm", "extensions"),
617
- path.join(cloneDirectory, ".custom", "pm-extensions"),
618
- path.join(cloneDirectory, ".custom", "pm-extension"),
619
- ];
620
- const discovered = (await Promise.all(defaultRoots.map((defaultRoot) => listManifestDirectories(defaultRoot)))).flat();
621
- if (discovered.length === 1) {
622
- return {
623
- directory: discovered[0],
624
- resolved_subpath: path.relative(cloneDirectory, discovered[0]).replaceAll(path.sep, "/"),
625
- };
626
- }
627
- if (discovered.length > 1) {
628
- const choices = discovered
629
- .map((entry) => path.relative(cloneDirectory, entry).replaceAll(path.sep, "/"))
630
- .sort((left, right) => left.localeCompare(right));
631
- throw new PmCliError(`GitHub source "${source.input}" contains multiple extension manifests. Provide an explicit path. Candidates: ${choices.join(", ")}`, EXIT_CODE.USAGE);
632
- }
633
- throw new PmCliError(`Unable to locate extension manifest in GitHub source "${source.input}". Provide an explicit extension path.`, EXIT_CODE.USAGE);
891
+ const discoveredDirectory = await resolvePackageExtensionDirectory(cloneDirectory, source.input);
892
+ return {
893
+ directory: discoveredDirectory,
894
+ resolved_subpath: path.relative(cloneDirectory, discoveredDirectory).replaceAll(path.sep, "/"),
895
+ };
634
896
  }
635
897
  async function resolveInstallSource(source) {
636
898
  if (source.kind === "local") {
@@ -641,9 +903,21 @@ async function resolveInstallSource(source) {
641
903
  if (!stats.isDirectory()) {
642
904
  throw new PmCliError(`Local extension source must be a directory: "${source.absolute_path}".`, EXIT_CODE.USAGE);
643
905
  }
906
+ const directory = await resolvePackageExtensionDirectory(source.absolute_path, source.input);
644
907
  return {
645
908
  source,
646
- directory: source.absolute_path,
909
+ directory,
910
+ };
911
+ }
912
+ if (source.kind === "npm") {
913
+ const resolved = await resolveNpmSourceDirectory(source);
914
+ return {
915
+ source,
916
+ directory: resolved.directory,
917
+ cleanup: resolved.cleanup,
918
+ resolved_subpath: path.relative(path.dirname(resolved.directory), resolved.directory).replaceAll(path.sep, "/"),
919
+ npm_package: resolved.package,
920
+ npm_version: resolved.version,
647
921
  };
648
922
  }
649
923
  const cloneDirectory = await fs.mkdtemp(path.join(os.tmpdir(), "pm-extension-source-"));
@@ -826,20 +1100,66 @@ function applyDoctorRuntimeActivationState(extensions, loadResult, activationRes
826
1100
  const loadedNames = new Set(loadResult.loaded.map((entry) => normalizeExtensionNameForMatch(entry.name)));
827
1101
  const loadFailedNames = new Set(loadResult.failed.map((entry) => normalizeExtensionNameForMatch(entry.name)));
828
1102
  const activationFailedNames = new Set(activationResult.failed.map((entry) => normalizeExtensionNameForMatch(entry.name)));
1103
+ const commandPathsByExtension = new Map();
1104
+ const actionPathsByExtension = new Map();
1105
+ const addCommandPath = (extensionName, commandPath) => {
1106
+ const normalizedName = normalizeExtensionNameForMatch(extensionName);
1107
+ const normalizedCommandPath = commandPath.trim();
1108
+ if (normalizedName.length === 0 || normalizedCommandPath.length === 0) {
1109
+ return;
1110
+ }
1111
+ const existing = commandPathsByExtension.get(normalizedName) ?? new Set();
1112
+ existing.add(normalizedCommandPath);
1113
+ commandPathsByExtension.set(normalizedName, existing);
1114
+ };
1115
+ const addActionPath = (extensionName, actionPath) => {
1116
+ const normalizedName = normalizeExtensionNameForMatch(extensionName);
1117
+ const normalizedActionPath = actionPath.trim();
1118
+ if (normalizedName.length === 0 || normalizedActionPath.length === 0) {
1119
+ return;
1120
+ }
1121
+ const existing = actionPathsByExtension.get(normalizedName) ?? new Set();
1122
+ existing.add(normalizedActionPath);
1123
+ actionPathsByExtension.set(normalizedName, existing);
1124
+ };
1125
+ for (const registration of activationResult.registrations.commands) {
1126
+ addCommandPath(registration.name, registration.command);
1127
+ addActionPath(registration.name, registration.action);
1128
+ }
1129
+ for (const handler of activationResult.commands.handlers) {
1130
+ addCommandPath(handler.name, handler.command);
1131
+ }
1132
+ for (const override of activationResult.commands.overrides) {
1133
+ addCommandPath(override.name, override.command);
1134
+ }
1135
+ const sortedPaths = (values) => {
1136
+ if (!values || values.size === 0) {
1137
+ return undefined;
1138
+ }
1139
+ return [...values].sort((left, right) => left.localeCompare(right));
1140
+ };
829
1141
  return extensions.map((entry) => {
1142
+ const normalizedName = normalizeExtensionNameForMatch(entry.name);
1143
+ const commandPaths = sortedPaths(commandPathsByExtension.get(normalizedName));
1144
+ const actionPaths = sortedPaths(actionPathsByExtension.get(normalizedName));
1145
+ const runtimeMetadata = {
1146
+ ...(commandPaths ? { command_paths: commandPaths } : {}),
1147
+ ...(actionPaths ? { action_paths: actionPaths } : {}),
1148
+ };
830
1149
  if (!entry.enabled) {
831
1150
  return {
832
1151
  ...entry,
833
1152
  runtime_active: false,
834
1153
  activation_status: "not_loaded",
1154
+ ...runtimeMetadata,
835
1155
  };
836
1156
  }
837
- const normalizedName = normalizeExtensionNameForMatch(entry.name);
838
1157
  if (loadFailedNames.has(normalizedName) || activationFailedNames.has(normalizedName)) {
839
1158
  return {
840
1159
  ...entry,
841
1160
  runtime_active: false,
842
1161
  activation_status: "failed",
1162
+ ...runtimeMetadata,
843
1163
  };
844
1164
  }
845
1165
  if (loadedNames.has(normalizedName)) {
@@ -847,12 +1167,14 @@ function applyDoctorRuntimeActivationState(extensions, loadResult, activationRes
847
1167
  ...entry,
848
1168
  runtime_active: true,
849
1169
  activation_status: "ok",
1170
+ ...runtimeMetadata,
850
1171
  };
851
1172
  }
852
1173
  return {
853
1174
  ...entry,
854
1175
  runtime_active: false,
855
1176
  activation_status: "not_loaded",
1177
+ ...runtimeMetadata,
856
1178
  };
857
1179
  });
858
1180
  }
@@ -987,13 +1309,16 @@ function requireTarget(target, action) {
987
1309
  const normalized = target?.trim();
988
1310
  if (!normalized) {
989
1311
  if (action === "init") {
990
- throw new PmCliError('Action "init" requires a scaffold target path (for example: pm extension --init ./my-extension or pm extension init ./my-extension).', EXIT_CODE.USAGE);
1312
+ throw new PmCliError('Action "init" requires a scaffold target path (for example: pm package init ./my-package or pm extension init ./my-extension).', EXIT_CODE.USAGE);
991
1313
  }
992
1314
  throw new PmCliError(`Action "${action}" requires an extension name or source target argument.`, EXIT_CODE.USAGE);
993
1315
  }
994
1316
  return normalized;
995
1317
  }
996
- function buildStarterExtensionScaffoldFiles(extensionName, commandName) {
1318
+ function lifecycleFlagCommand(options, action) {
1319
+ return options.vocabulary === "package" ? `pm package ${action}` : `pm extension --${action}`;
1320
+ }
1321
+ function buildStarterExtensionScaffoldFiles(extensionName, commandName, vocabulary) {
997
1322
  const manifest = `${JSON.stringify({
998
1323
  name: extensionName,
999
1324
  version: "0.1.0",
@@ -1001,26 +1326,28 @@ function buildStarterExtensionScaffoldFiles(extensionName, commandName) {
1001
1326
  capabilities: ["commands"],
1002
1327
  }, null, 2)}\n`;
1003
1328
  const entrypoint = [
1004
- "module.exports = {",
1005
- " activate(api) {",
1006
- " api.registerCommand({",
1007
- ` name: ${JSON.stringify(commandName)},`,
1008
- ' description: "Starter scaffold command. Replace with your own behavior.",',
1009
- " run: async (context) => ({",
1010
- " ok: true,",
1011
- ` source: ${JSON.stringify(extensionName)},`,
1012
- " command: context.command,",
1013
- ' message: "Starter extension scaffold is active.",',
1014
- " }),",
1015
- " });",
1016
- " },",
1329
+ "export function activate(api) {",
1330
+ " api.registerCommand({",
1331
+ ` name: ${JSON.stringify(commandName)},`,
1332
+ ' description: "Starter scaffold command. Replace with your own behavior.",',
1333
+ " run: async (context) => ({",
1334
+ " ok: true,",
1335
+ ` source: ${JSON.stringify(extensionName)},`,
1336
+ " command: context.command,",
1337
+ ' message: "Starter extension scaffold is active.",',
1338
+ " }),",
1339
+ " });",
1340
+ "}",
1341
+ "",
1342
+ "export default {",
1343
+ " activate,",
1017
1344
  "};",
1018
1345
  "",
1019
1346
  ].join("\n");
1020
1347
  const readme = [
1021
1348
  `# ${extensionName}`,
1022
1349
  "",
1023
- "Generated by `pm extension --init`.",
1350
+ `Generated by \`${vocabulary === "package" ? "pm package init" : "pm extension init"}\`.`,
1024
1351
  "",
1025
1352
  "## Included Files",
1026
1353
  "- `manifest.json`: extension metadata and capabilities.",
@@ -1028,13 +1355,13 @@ function buildStarterExtensionScaffoldFiles(extensionName, commandName) {
1028
1355
  "",
1029
1356
  "## Quick Start",
1030
1357
  "```bash",
1031
- "pm extension --install --project <scaffold-path>",
1358
+ `${vocabulary === "package" ? "pm install" : "pm extension --install"} --project <scaffold-path>`,
1032
1359
  `pm ${commandName}`,
1033
- "pm extension --doctor --project --detail summary",
1360
+ `${vocabulary === "package" ? "pm package doctor" : "pm extension --doctor"} --project --detail summary`,
1034
1361
  "```",
1035
1362
  "",
1036
1363
  "## Notes",
1037
- "- This scaffold uses CommonJS (`module.exports`) for zero-config runtime compatibility.",
1364
+ "- This scaffold uses ESM exports so it works in package scopes with `type: module`.",
1038
1365
  "- Update `manifest.json` capabilities and `index.js` command behavior as your extension evolves.",
1039
1366
  "",
1040
1367
  ].join("\n");
@@ -1044,12 +1371,12 @@ function buildStarterExtensionScaffoldFiles(extensionName, commandName) {
1044
1371
  "README.md": readme,
1045
1372
  };
1046
1373
  }
1047
- async function scaffoldExtensionProject(target) {
1374
+ async function scaffoldExtensionProject(target, vocabulary = "extension") {
1048
1375
  const normalizedTarget = target.trim();
1049
1376
  const targetPath = path.resolve(process.cwd(), normalizedTarget);
1050
1377
  const extensionName = normalizeManagedDirectoryName(path.basename(targetPath));
1051
1378
  const commandName = `${extensionName} ping`;
1052
- const scaffoldFiles = buildStarterExtensionScaffoldFiles(extensionName, commandName);
1379
+ const scaffoldFiles = buildStarterExtensionScaffoldFiles(extensionName, commandName, vocabulary);
1053
1380
  let createdDirectory = false;
1054
1381
  if (await pathExists(targetPath)) {
1055
1382
  const existingTargetStats = await fs.stat(targetPath);
@@ -1106,7 +1433,7 @@ function classifyDoctorLoadFailureWarnings(loadFailures) {
1106
1433
  }
1107
1434
  return [...new Set(warnings)].sort((left, right) => left.localeCompare(right));
1108
1435
  }
1109
- function buildExtensionTriageSummary(scope, warnings, extensions) {
1436
+ function buildExtensionTriageSummary(scope, warnings, extensions, options = {}) {
1110
1437
  const normalizedWarnings = [...new Set(warnings)].sort((left, right) => left.localeCompare(right));
1111
1438
  const managedTotal = extensions.filter((entry) => entry.managed).length;
1112
1439
  const enabledTotal = extensions.filter((entry) => entry.enabled).length;
@@ -1146,7 +1473,7 @@ function buildExtensionTriageSummary(scope, warnings, extensions) {
1146
1473
  const remediation = [];
1147
1474
  if (normalizedWarnings.length > 0) {
1148
1475
  if (normalizedWarnings.some((warning) => warning.startsWith("extension_manifest_"))) {
1149
- remediation.push(`Run pm extension --explore ${scopeFlag} to inspect discovered manifests and directories.`);
1476
+ remediation.push(`Run ${lifecycleFlagCommand(options, "explore")} ${scopeFlag} to inspect discovered manifests and directories.`);
1150
1477
  }
1151
1478
  if (normalizedWarnings.some((warning) => warning.startsWith("extension_capability_unknown:"))) {
1152
1479
  remediation.push(`Unknown extension capabilities detected. Allowed capabilities: ${KNOWN_EXTENSION_CAPABILITIES.join(", ")}. ` +
@@ -1162,14 +1489,14 @@ function buildExtensionTriageSummary(scope, warnings, extensions) {
1162
1489
  }
1163
1490
  if (normalizedWarnings.some((warning) => warning.startsWith("extension_load_failed_sdk_dependency_missing:"))) {
1164
1491
  remediation.push(`Detected extension load failures caused by missing SDK dependency resolution. ` +
1165
- `Ensure extension package dependencies include "@unbrained/pm-cli" and reinstall dependencies before running pm extension --doctor ${scopeFlag}.`);
1492
+ `Ensure extension package dependencies include "@unbrained/pm-cli" and reinstall dependencies before running ${lifecycleFlagCommand(options, "doctor")} ${scopeFlag}.`);
1166
1493
  }
1167
1494
  if (normalizedWarnings.some((warning) => warning.startsWith("extension_load_failed_module_mode_mismatch:"))) {
1168
1495
  remediation.push(`Detected extension module-mode mismatches. For ESM-based extension entries/imports, set package.json "type": "module" ` +
1169
- `or use an explicit .mjs entry and rerun pm extension --doctor ${scopeFlag}.`);
1496
+ `or use an explicit .mjs entry and rerun ${lifecycleFlagCommand(options, "doctor")} ${scopeFlag}.`);
1170
1497
  }
1171
1498
  if (updateCheckFailedTotal > 0) {
1172
- remediation.push(`Run pm extension --manage ${scopeFlag} after validating network and repository access.`);
1499
+ remediation.push(`Run ${lifecycleFlagCommand(options, "manage")} ${scopeFlag} after validating network and repository access.`);
1173
1500
  }
1174
1501
  if (normalizedWarnings.some((warning) => warning.startsWith("extension_manager_state_"))) {
1175
1502
  remediation.push(`Review and repair ${scope} managed extension state file if schema/read warnings persist.`);
@@ -1179,19 +1506,19 @@ function buildExtensionTriageSummary(scope, warnings, extensions) {
1179
1506
  }
1180
1507
  }
1181
1508
  if (updateHealthPartial) {
1182
- remediation.push(`Update-check coverage is partial because unmanaged extensions need adoption. Adopt existing installs via pm extension --manage ${scopeFlag} --fix-managed-state (or pm extension --adopt-all ${scopeFlag}, pm extension --adopt <name> ${scopeFlag}, or reinstall via pm extension --install ${scopeFlag} <source>).`);
1509
+ remediation.push(`Update-check coverage is partial because unmanaged extensions need adoption. Adopt existing installs via ${lifecycleFlagCommand(options, "manage")} ${scopeFlag} --fix-managed-state (or ${lifecycleFlagCommand(options, "adopt-all")} ${scopeFlag}, ${lifecycleFlagCommand(options, "adopt")} <name> ${scopeFlag}, or reinstall via ${lifecycleFlagCommand(options, "install")} ${scopeFlag} <source>).`);
1183
1510
  }
1184
1511
  else if (skippedUnmanagedTotal > 0) {
1185
- remediation.push(`Loaded unmanaged extensions are currently treated as informational. Use pm extension --manage ${scopeFlag} --fix-managed-state to adopt them for update checks.`);
1512
+ remediation.push(`Loaded unmanaged extensions are currently treated as informational. Use ${lifecycleFlagCommand(options, "manage")} ${scopeFlag} --fix-managed-state to adopt them for update checks.`);
1186
1513
  }
1187
1514
  if (skippedNonGithubTotal > 0) {
1188
1515
  remediation.push(`Non-GitHub managed extensions are skipped by update checks. Use doctor output for non-update diagnostics.`);
1189
1516
  }
1190
1517
  if (updateAvailableTotal > 0) {
1191
- remediation.push(`Update available managed extensions via pm extension --install ${scopeFlag} <source>.`);
1518
+ remediation.push(`Update available managed extensions via ${lifecycleFlagCommand(options, "install")} ${scopeFlag} <source>.`);
1192
1519
  }
1193
1520
  if (remediation.length === 0) {
1194
- remediation.push(`No immediate action required. Re-run pm extension --manage ${scopeFlag} after extension changes.`);
1521
+ remediation.push(`No immediate action required. Re-run ${lifecycleFlagCommand(options, "manage")} ${scopeFlag} after extension changes.`);
1195
1522
  }
1196
1523
  return {
1197
1524
  status: effectiveWarnings.length === 0 ? "ok" : "warn",
@@ -1343,6 +1670,9 @@ export async function runExtension(target, options, global) {
1343
1670
  if (action === "reload" && normalizedInput === "reload") {
1344
1671
  return undefined;
1345
1672
  }
1673
+ if (action === "catalog" && normalizedInput === "catalog") {
1674
+ return undefined;
1675
+ }
1346
1676
  const inferredInitAlias = action === "init" &&
1347
1677
  options.init !== true &&
1348
1678
  options.scaffold !== true &&
@@ -1374,7 +1704,7 @@ export async function runExtension(target, options, global) {
1374
1704
  throw new PmCliError('Action "init" does not accept --gh/--github/--ref options.', EXIT_CODE.USAGE);
1375
1705
  }
1376
1706
  const scaffoldTarget = requireTarget(normalizedTarget, action);
1377
- const scaffold = await scaffoldExtensionProject(scaffoldTarget);
1707
+ const scaffold = await scaffoldExtensionProject(scaffoldTarget, options.vocabulary ?? "extension");
1378
1708
  const quotedTargetPath = JSON.stringify(scaffold.target_path);
1379
1709
  return withResult({
1380
1710
  scaffolded: scaffold.created_directory || scaffold.files.some((entry) => entry.status === "created"),
@@ -1386,9 +1716,9 @@ export async function runExtension(target, options, global) {
1386
1716
  created_directory: scaffold.created_directory,
1387
1717
  files: scaffold.files,
1388
1718
  next_steps: [
1389
- `Install the scaffold: pm extension --install --project ${quotedTargetPath}`,
1719
+ `Install the scaffold: ${options.vocabulary === "package" ? "pm install --project" : "pm extension --install --project"} ${quotedTargetPath}`,
1390
1720
  `Smoke-test command path: pm ${scaffold.command_name}`,
1391
- "Run extension diagnostics: pm extension --doctor --project --detail summary",
1721
+ `Run diagnostics: ${options.vocabulary === "package" ? "pm package doctor" : "pm extension --doctor"} --project --detail summary`,
1392
1722
  ],
1393
1723
  });
1394
1724
  }
@@ -1440,9 +1770,45 @@ export async function runExtension(target, options, global) {
1440
1770
  }
1441
1771
  return withResult(details);
1442
1772
  }
1773
+ if (action === "catalog") {
1774
+ if (typeof normalizedTarget === "string" && normalizedTarget.length > 0 && normalizedTarget !== "catalog") {
1775
+ throw new PmCliError('Action "catalog" does not accept a package target.', EXIT_CODE.USAGE);
1776
+ }
1777
+ return withResult(await buildBundledPackageCatalog(scope, global));
1778
+ }
1443
1779
  if (action === "install") {
1444
1780
  const githubOption = resolveGithubOption(options);
1445
1781
  const explicitSourceInput = githubOption ?? requireTarget(normalizedTarget, action);
1782
+ if (typeof githubOption !== "string" && isBundledPackageInstallAllTarget(explicitSourceInput)) {
1783
+ if (typeof options.ref === "string" && options.ref.trim().length > 0) {
1784
+ throw new PmCliError('Action "install all" does not accept --ref.', EXIT_CODE.USAGE);
1785
+ }
1786
+ const aliases = await listBundledPackageAliases();
1787
+ const packages = [];
1788
+ for (const alias of aliases) {
1789
+ packages.push({
1790
+ alias,
1791
+ result: await runExtension(alias, { ...options, install: true }, global),
1792
+ });
1793
+ }
1794
+ for (const entry of packages) {
1795
+ warnings.push(...entry.result.warnings);
1796
+ }
1797
+ return withResult({
1798
+ installed_all: true,
1799
+ installed_count: packages.length,
1800
+ packages: packages.map((entry) => ({
1801
+ alias: entry.alias,
1802
+ ok: entry.result.ok,
1803
+ extension: entry.result.details.extension,
1804
+ source: entry.result.details.source,
1805
+ destination_path: entry.result.details.destination_path,
1806
+ activated: entry.result.details.activated,
1807
+ settings_changed: entry.result.details.settings_changed,
1808
+ warnings: entry.result.warnings,
1809
+ })),
1810
+ });
1811
+ }
1446
1812
  const bundledAliasSource = typeof githubOption === "string" ? null : await resolveBundledExtensionAliasSource(explicitSourceInput);
1447
1813
  const sourceInput = bundledAliasSource ?? explicitSourceInput;
1448
1814
  const installSource = parseExtensionInstallSource(sourceInput, {
@@ -1472,17 +1838,25 @@ export async function runExtension(target, options, global) {
1472
1838
  input: installSource.input,
1473
1839
  location: installSource.absolute_path,
1474
1840
  }
1475
- : {
1476
- kind: "github",
1477
- input: installSource.input,
1478
- location: resolvedSource.resolved_subpath ?? installSource.subpath ?? ".",
1479
- repository: installSource.repository,
1480
- owner: installSource.owner,
1481
- repo: installSource.repo,
1482
- ref: installSource.ref,
1483
- subpath: resolvedSource.resolved_subpath ?? installSource.subpath,
1484
- commit: resolvedSource.commit,
1485
- };
1841
+ : installSource.kind === "npm"
1842
+ ? {
1843
+ kind: "npm",
1844
+ input: installSource.input,
1845
+ location: resolvedSource.resolved_subpath ?? ".",
1846
+ package: resolvedSource.npm_package,
1847
+ version: resolvedSource.npm_version,
1848
+ }
1849
+ : {
1850
+ kind: "github",
1851
+ input: installSource.input,
1852
+ location: resolvedSource.resolved_subpath ?? installSource.subpath ?? ".",
1853
+ repository: installSource.repository,
1854
+ owner: installSource.owner,
1855
+ repo: installSource.repo,
1856
+ ref: installSource.ref,
1857
+ subpath: resolvedSource.resolved_subpath ?? installSource.subpath,
1858
+ commit: resolvedSource.commit,
1859
+ };
1486
1860
  const now = nowIso();
1487
1861
  const existingManagedEntry = managedStateRead.state.entries.find((entry) => normalizeExtensionNameForMatch(entry.name) === normalizeExtensionNameForMatch(validated.manifest.name));
1488
1862
  const managedState = upsertManagedEntry(managedStateRead.state, {
@@ -1539,7 +1913,7 @@ export async function runExtension(target, options, global) {
1539
1913
  const adoption = await adoptUnmanagedExtensions(resolvedRoots.selected_root, scope, installed.extensions, managedStateRead.state);
1540
1914
  const refreshedInstalled = await listInstalledExtensions(resolvedRoots.selected_root, scope, settings, adoption.state);
1541
1915
  warnings.push(...refreshedInstalled.warnings);
1542
- const triage = buildExtensionTriageSummary(scope, warnings, refreshedInstalled.extensions);
1916
+ const triage = buildExtensionTriageSummary(scope, warnings, refreshedInstalled.extensions, options);
1543
1917
  warnings.push(...triage.warnings);
1544
1918
  const adoptedDetails = adoption.adopted_entries.map((entry) => {
1545
1919
  const refreshedEntry = refreshedInstalled.extensions.find((candidate) => normalizeExtensionNameForMatch(candidate.name) === normalizeExtensionNameForMatch(entry.name) &&
@@ -1746,7 +2120,7 @@ export async function runExtension(target, options, global) {
1746
2120
  .filter((entry) => entry.update_check_status === "failed")
1747
2121
  .map((entry) => `extension_update_check_failed:${entry.name}`);
1748
2122
  warnings.push(...updateCheckWarnings);
1749
- const triage = buildExtensionTriageSummary(scope, warnings, runtimeInstalledExtensions);
2123
+ const triage = buildExtensionTriageSummary(scope, warnings, runtimeInstalledExtensions, options);
1750
2124
  warnings.push(...triage.warnings);
1751
2125
  const normalizedWarnings = [...triage.warnings];
1752
2126
  const policySummary = {
@@ -1776,10 +2150,18 @@ export async function runExtension(target, options, global) {
1776
2150
  ...new Set([
1777
2151
  ...triage.remediation,
1778
2152
  ...(loadResult.failed.length > 0
1779
- ? ["Run pm extension --explore --project and pm extension --explore --global to inspect load failures."]
2153
+ ? [
2154
+ options.vocabulary === "package"
2155
+ ? "Run pm package explore --project and pm package explore --global to inspect load failures."
2156
+ : "Run pm extension --explore --project and pm extension --explore --global to inspect load failures.",
2157
+ ]
1780
2158
  : []),
1781
2159
  ...(activationResult.failed.length > 0
1782
- ? ["Review activation failures in pm extension --doctor --detail deep output."]
2160
+ ? [
2161
+ options.vocabulary === "package"
2162
+ ? "Review activation failures in pm package doctor --detail deep output."
2163
+ : "Review activation failures in pm extension --doctor --detail deep output.",
2164
+ ]
1783
2165
  : []),
1784
2166
  ...(managedStateFix && managedStateFix.adopted_entries.length > 0
1785
2167
  ? [`Managed-state fix adopted ${managedStateFix.adopted_entries.length} extension(s).`]
@@ -1956,7 +2338,7 @@ export async function runExtension(target, options, global) {
1956
2338
  }
1957
2339
  let runtimeProbeSummary;
1958
2340
  let runtimeInstalledExtensions = refreshedInstalled.extensions;
1959
- if (action === "manage" && options.runtimeProbe === true) {
2341
+ if (action === "explore" || options.runtimeProbe === true) {
1960
2342
  const loadResult = await loadExtensions({
1961
2343
  pmRoot: resolvedRoots.pm_root,
1962
2344
  settings,
@@ -1973,6 +2355,7 @@ export async function runExtension(target, options, global) {
1973
2355
  runtimeProbeSummary = {
1974
2356
  requested: true,
1975
2357
  executed: true,
2358
+ reason: action === "explore" ? "explore_defaults_to_runtime_probe" : "runtime_probe_requested",
1976
2359
  load_failure_count: loadResult.failed.length,
1977
2360
  activation_failure_count: activationResult.failed.length,
1978
2361
  warning_count: [...new Set([...loadResult.warnings, ...activationResult.warnings])].length,
@@ -1981,11 +2364,11 @@ export async function runExtension(target, options, global) {
1981
2364
  }
1982
2365
  else if (action === "manage") {
1983
2366
  runtimeProbeSummary = {
1984
- requested: options.runtimeProbe === true,
2367
+ requested: false,
1985
2368
  executed: false,
1986
2369
  };
1987
2370
  }
1988
- const triage = buildExtensionTriageSummary(scope, warnings, runtimeInstalledExtensions);
2371
+ const triage = buildExtensionTriageSummary(scope, warnings, runtimeInstalledExtensions, options);
1989
2372
  warnings.push(...triage.warnings);
1990
2373
  const details = {
1991
2374
  total: runtimeInstalledExtensions.length,
@@ -1996,6 +2379,9 @@ export async function runExtension(target, options, global) {
1996
2379
  triage,
1997
2380
  policy: configuredPolicy,
1998
2381
  };
2382
+ if (action === "explore") {
2383
+ details.runtime_probe = runtimeProbeSummary;
2384
+ }
1999
2385
  if (action === "manage") {
2000
2386
  details.runtime_probe = runtimeProbeSummary;
2001
2387
  details.managed_state_fix =