@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.
- package/AGENTS.md +3 -116
- package/CHANGELOG.md +18 -0
- package/PRD.md +18 -39
- package/README.md +8 -5
- package/dist/cli/commander-usage.js +27 -0
- package/dist/cli/commander-usage.js.map +1 -1
- package/dist/cli/commands/activity.js +19 -4
- package/dist/cli/commands/activity.js.map +1 -1
- package/dist/cli/commands/calendar.js +5 -2
- package/dist/cli/commands/calendar.js.map +1 -1
- package/dist/cli/commands/contracts.js +63 -19
- package/dist/cli/commands/contracts.js.map +1 -1
- package/dist/cli/commands/create.js +58 -3
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/commands/extension.d.ts +14 -3
- package/dist/cli/commands/extension.js +481 -95
- package/dist/cli/commands/extension.js.map +1 -1
- package/dist/cli/commands/index.d.ts +1 -8
- package/dist/cli/commands/index.js +1 -8
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/reindex.d.ts +8 -0
- package/dist/cli/commands/reindex.js +96 -23
- package/dist/cli/commands/reindex.js.map +1 -1
- package/dist/cli/commands/search.js +51 -25
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/commands/test.js +14 -6
- package/dist/cli/commands/test.js.map +1 -1
- package/dist/cli/commands/upgrade.d.ts +63 -0
- package/dist/cli/commands/upgrade.js +260 -0
- package/dist/cli/commands/upgrade.js.map +1 -0
- package/dist/cli/guide-topics.js +18 -16
- package/dist/cli/guide-topics.js.map +1 -1
- package/dist/cli/help-content.js +57 -18
- package/dist/cli/help-content.js.map +1 -1
- package/dist/cli/main.js +73 -7
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/register-list-query.js +24 -142
- package/dist/cli/register-list-query.js.map +1 -1
- package/dist/cli/register-mutation.js +49 -257
- package/dist/cli/register-mutation.js.map +1 -1
- package/dist/cli/register-operations.js +29 -198
- package/dist/cli/register-operations.js.map +1 -1
- package/dist/cli/register-setup.js +181 -204
- package/dist/cli/register-setup.js.map +1 -1
- package/dist/cli/registration-helpers.d.ts +2 -2
- package/dist/cli/registration-helpers.js +1 -19
- package/dist/cli/registration-helpers.js.map +1 -1
- package/dist/core/extensions/loader.js +7 -1
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/packages/manifest.d.ts +38 -0
- package/dist/core/packages/manifest.js +221 -0
- package/dist/core/packages/manifest.js.map +1 -0
- package/dist/core/search/embedding-batches.d.ts +13 -1
- package/dist/core/search/embedding-batches.js +19 -1
- package/dist/core/search/embedding-batches.js.map +1 -1
- package/dist/core/store/front-matter-cache.d.ts +8 -1
- package/dist/core/store/front-matter-cache.js +20 -11
- package/dist/core/store/front-matter-cache.js.map +1 -1
- package/dist/mcp/server.d.ts +8 -0
- package/dist/mcp/server.js +100 -43
- package/dist/mcp/server.js.map +1 -1
- package/dist/sdk/cli-contracts/commander-mutation-options.d.ts +7 -0
- package/dist/sdk/cli-contracts/commander-mutation-options.js +477 -0
- package/dist/sdk/cli-contracts/commander-mutation-options.js.map +1 -0
- package/dist/sdk/cli-contracts/commander-types.d.ts +21 -0
- package/dist/sdk/cli-contracts/commander-types.js +92 -0
- package/dist/sdk/cli-contracts/commander-types.js.map +1 -0
- package/dist/sdk/cli-contracts.d.ts +22 -32
- package/dist/sdk/cli-contracts.js +155 -296
- package/dist/sdk/cli-contracts.js.map +1 -1
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.js +2 -0
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/runtime.d.ts +29 -0
- package/dist/sdk/runtime.js +28 -0
- package/dist/sdk/runtime.js.map +1 -0
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/COMMANDS.md +17 -1
- package/docs/EXTENSIONS.md +169 -61
- package/docs/QUICKSTART.md +11 -2
- package/docs/README.md +4 -6
- package/docs/RELEASING.md +4 -2
- package/docs/SDK.md +79 -438
- package/package.json +6 -23
- package/packages/pm-beads/README.md +10 -0
- package/packages/pm-beads/extensions/beads/index.js +113 -0
- package/{.agents/pm/extensions/beads/index.js → packages/pm-beads/extensions/beads/index.ts} +42 -20
- package/{.agents/pm → packages/pm-beads}/extensions/beads/runtime.js +2 -17
- package/{.agents/pm → packages/pm-beads}/extensions/beads/runtime.ts +41 -18
- package/packages/pm-beads/package.json +50 -0
- package/packages/pm-calendar/README.md +13 -0
- package/packages/pm-calendar/extensions/calendar/index.js +56 -0
- package/packages/pm-calendar/extensions/calendar/index.ts +62 -0
- package/packages/pm-calendar/extensions/calendar/manifest.json +7 -0
- package/packages/pm-calendar/extensions/calendar/runtime.js +95 -0
- package/packages/pm-calendar/extensions/calendar/runtime.ts +104 -0
- package/packages/pm-calendar/package.json +51 -0
- package/packages/pm-governance-audit/README.md +23 -0
- package/packages/pm-governance-audit/extensions/governance-audit/index.js +117 -0
- package/packages/pm-governance-audit/extensions/governance-audit/index.ts +118 -0
- package/packages/pm-governance-audit/extensions/governance-audit/manifest.json +7 -0
- package/packages/pm-governance-audit/extensions/governance-audit/runtime.js +159 -0
- package/packages/pm-governance-audit/extensions/governance-audit/runtime.ts +176 -0
- package/packages/pm-governance-audit/package.json +52 -0
- package/packages/pm-guide-shell/README.md +23 -0
- package/packages/pm-guide-shell/extensions/guide-shell/index.js +76 -0
- package/packages/pm-guide-shell/extensions/guide-shell/index.ts +81 -0
- package/packages/pm-guide-shell/extensions/guide-shell/manifest.json +7 -0
- package/packages/pm-guide-shell/extensions/guide-shell/runtime.js +263 -0
- package/packages/pm-guide-shell/extensions/guide-shell/runtime.ts +327 -0
- package/packages/pm-guide-shell/package.json +52 -0
- package/packages/pm-linked-test-adapters/README.md +24 -0
- package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/index.js +101 -0
- package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/index.ts +102 -0
- package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/manifest.json +7 -0
- package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/runtime.js +142 -0
- package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/runtime.ts +173 -0
- package/packages/pm-linked-test-adapters/package.json +53 -0
- package/packages/pm-search-advanced/README.md +27 -0
- package/packages/pm-search-advanced/extensions/search-advanced/index.js +93 -0
- package/packages/pm-search-advanced/extensions/search-advanced/index.ts +94 -0
- package/packages/pm-search-advanced/extensions/search-advanced/manifest.json +7 -0
- package/packages/pm-search-advanced/extensions/search-advanced/runtime.js +120 -0
- package/packages/pm-search-advanced/extensions/search-advanced/runtime.ts +144 -0
- package/packages/pm-search-advanced/package.json +54 -0
- package/packages/pm-templates/README.md +20 -0
- package/packages/pm-templates/extensions/templates/index.js +101 -0
- package/packages/pm-templates/extensions/templates/index.ts +109 -0
- package/packages/pm-templates/extensions/templates/manifest.json +7 -0
- package/packages/pm-templates/extensions/templates/runtime.js +226 -0
- package/packages/pm-templates/extensions/templates/runtime.ts +283 -0
- package/packages/pm-templates/package.json +50 -0
- package/packages/pm-todos/README.md +11 -0
- package/packages/pm-todos/extensions/todos/index.js +130 -0
- package/{.agents/pm/extensions/todos/index.js → packages/pm-todos/extensions/todos/index.ts} +47 -23
- package/{.agents/pm → packages/pm-todos}/extensions/todos/runtime.js +3 -18
- package/{.agents/pm → packages/pm-todos}/extensions/todos/runtime.ts +42 -20
- package/packages/pm-todos/package.json +51 -0
- package/plugins/pm-cli-claude/README.md +1 -2
- package/plugins/pm-cli-claude/hooks/session-start.mjs +4 -55
- package/plugins/pm-cli-claude/scripts/pm-mcp-server.mjs +4 -2
- package/plugins/pm-cli-codex/scripts/pm-mcp-server.mjs +4 -2
- package/.agents/pm/extensions/.managed-extensions.json +0 -42
- package/.agents/skills/HARNESS_COMPATIBILITY.md +0 -45
- package/.agents/skills/README.md +0 -21
- package/.agents/skills/pm-developer/SKILL.md +0 -73
- package/.agents/skills/pm-developer/references/COMMAND_PLAYBOOK.md +0 -48
- package/.agents/skills/pm-developer/references/PROMPTS.md +0 -17
- package/.agents/skills/pm-extensions/SKILL.md +0 -57
- package/.agents/skills/pm-extensions/references/LIFECYCLE.md +0 -40
- package/.agents/skills/pm-extensions/references/TROUBLESHOOTING.md +0 -25
- package/.agents/skills/pm-sdk/SKILL.md +0 -50
- package/.agents/skills/pm-sdk/references/INTEGRATION_CHECKLIST.md +0 -31
- package/.agents/skills/pm-sdk/references/PROMPTS.md +0 -13
- package/.agents/skills/pm-user/SKILL.md +0 -59
- package/.agents/skills/pm-user/references/PROMPTS.md +0 -17
- package/.agents/skills/pm-user/references/WORKFLOWS.md +0 -35
- package/.pi/README.md +0 -35
- package/.pi/agents/pm-triage-agent.md +0 -19
- package/.pi/agents/pm-verification-agent.md +0 -21
- package/.pi/chains/pm-native-delivery.chain.md +0 -11
- package/.pi/extensions/pm-cli/index.js +0 -387
- package/.pi/prompts/pm-workflow.md +0 -5
- package/.pi/skills/pm-native/SKILL.md +0 -44
- package/.pi/skills/pm-release/SKILL.md +0 -35
- package/dist/pi/native.d.ts +0 -5
- package/dist/pi/native.js +0 -236
- package/dist/pi/native.js.map +0 -1
- package/docs/PI_PACKAGE.md +0 -141
- /package/{.agents/pm → packages/pm-beads}/extensions/beads/manifest.json +0 -0
- /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
|
|
21
|
-
beads:
|
|
22
|
-
|
|
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
|
|
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
|
|
42
|
-
if (await pathExists(path.join(
|
|
43
|
-
return
|
|
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
|
|
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
|
|
582
|
-
|
|
583
|
-
|
|
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
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
-
|
|
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
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
path.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
"
|
|
1005
|
-
"
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
"
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1496
|
+
`or use an explicit .mjs entry and rerun ${lifecycleFlagCommand(options, "doctor")} ${scopeFlag}.`);
|
|
1170
1497
|
}
|
|
1171
1498
|
if (updateCheckFailedTotal > 0) {
|
|
1172
|
-
remediation.push(`Run
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
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
|
-
? [
|
|
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
|
-
? [
|
|
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 === "
|
|
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:
|
|
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 =
|