@unbrained/pm-web 2026.7.27 → 2026.7.29
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/CHANGELOG.md +53 -0
- package/README.md +1 -2
- package/dist/app.js +2 -0
- package/dist/app.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/oidc.js.map +1 -1
- package/dist/routes/extensions.d.ts +16 -0
- package/dist/routes/extensions.js +214 -0
- package/dist/routes/extensions.js.map +1 -0
- package/dist/routes/oidc.d.ts +1 -1
- package/dist/routes/pm.js +125 -0
- package/dist/routes/pm.js.map +1 -1
- package/dist/services/mutation-event-watcher.d.ts +1 -1
- package/dist/services/package-catalog.d.ts +108 -0
- package/dist/services/package-catalog.js +256 -0
- package/dist/services/package-catalog.js.map +1 -0
- package/dist/services/pm-runner.d.ts +66 -1
- package/dist/services/pm-runner.js +83 -47
- package/dist/services/pm-runner.js.map +1 -1
- package/dist/services/project-watcher.d.ts +1 -1
- package/dist/services/realtime-bus.d.ts +1 -1
- package/manifest.json +1 -1
- package/package.json +24 -11
- package/public/index.html +4 -0
- package/public/src/api-types.js +17 -0
- package/public/src/api-types.js.map +1 -0
- package/public/src/api-types.ts +618 -0
- package/public/src/api.js +6 -3
- package/public/src/api.js.map +1 -1
- package/public/src/api.ts +30 -10
- package/public/src/app.js +11 -4
- package/public/src/app.js.map +1 -1
- package/public/src/app.ts +55 -12
- package/public/src/constants.js +1 -1
- package/public/src/constants.js.map +1 -1
- package/public/src/constants.ts +1 -1
- package/public/src/i18n/de.json +25 -1
- package/public/src/i18n/en.json +25 -1
- package/public/src/i18n/es.json +25 -1
- package/public/src/i18n/zh.json +26 -2
- package/public/src/i18n.ts +1 -1
- package/public/src/sw.ts +2 -0
- package/public/src/types.ts +11 -2
- package/public/src/views/activity.js +1 -1
- package/public/src/views/activity.js.map +1 -1
- package/public/src/views/activity.ts +5 -4
- package/public/src/views/calendar.js +1 -1
- package/public/src/views/calendar.js.map +1 -1
- package/public/src/views/calendar.ts +13 -11
- package/public/src/views/comments-audit.js.map +1 -1
- package/public/src/views/comments-audit.ts +7 -6
- package/public/src/views/config.js +1 -1
- package/public/src/views/config.js.map +1 -1
- package/public/src/views/config.ts +2 -2
- package/public/src/views/context.js +3 -3
- package/public/src/views/context.js.map +1 -1
- package/public/src/views/context.ts +10 -9
- package/public/src/views/create.js.map +1 -1
- package/public/src/views/create.ts +3 -2
- package/public/src/views/dedupe.js +5 -5
- package/public/src/views/dedupe.js.map +1 -1
- package/public/src/views/dedupe.ts +9 -8
- package/public/src/views/export.js.map +1 -1
- package/public/src/views/export.ts +2 -2
- package/public/src/views/github.js +5 -4
- package/public/src/views/github.js.map +1 -1
- package/public/src/views/github.ts +20 -18
- package/public/src/views/graph.js +7 -9
- package/public/src/views/graph.js.map +1 -1
- package/public/src/views/graph.ts +11 -13
- package/public/src/views/groups.js +1 -1
- package/public/src/views/groups.js.map +1 -1
- package/public/src/views/groups.ts +9 -8
- package/public/src/views/health.js +16 -2
- package/public/src/views/health.js.map +1 -1
- package/public/src/views/health.ts +26 -8
- package/public/src/views/items.js +18 -12
- package/public/src/views/items.js.map +1 -1
- package/public/src/views/items.ts +65 -35
- package/public/src/views/normalize.js.map +1 -1
- package/public/src/views/normalize.ts +5 -4
- package/public/src/views/packages.js +230 -0
- package/public/src/views/packages.js.map +1 -0
- package/public/src/views/packages.ts +255 -0
- package/public/src/views/plan.js +0 -2
- package/public/src/views/plan.js.map +1 -1
- package/public/src/views/plan.ts +5 -6
- package/public/src/views/projects.js.map +1 -1
- package/public/src/views/projects.ts +8 -7
- package/public/src/views/router.js +5 -0
- package/public/src/views/router.js.map +1 -1
- package/public/src/views/router.ts +3 -0
- package/public/src/views/search.js.map +1 -1
- package/public/src/views/search.ts +3 -2
- package/public/src/views/settings.js.map +1 -1
- package/public/src/views/settings.ts +8 -6
- package/public/src/views/shared.js.map +1 -1
- package/public/src/views/shared.ts +4 -3
- package/public/src/views/sharing.js.map +1 -1
- package/public/src/views/sharing.ts +5 -4
- package/public/src/views/stats.js +1 -1
- package/public/src/views/stats.js.map +1 -1
- package/public/src/views/stats.ts +10 -9
- package/public/src/views/templates.js.map +1 -1
- package/public/src/views/templates.ts +6 -5
- package/public/src/views/validate.js.map +1 -1
- package/public/src/views/validate.ts +6 -5
- package/public/sw.js +2 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The set of capabilities a pm package can declare, mirrored verbatim from
|
|
3
|
+
* each package's `manifest.json` `capabilities` array. The union is kept
|
|
4
|
+
* open-ended as a string array (not a closed enum) because the pm extension
|
|
5
|
+
* contract may add new capability kinds upstream; pinning a closed list here
|
|
6
|
+
* would reject a future package that uses a capability we have not enumerated.
|
|
7
|
+
*/
|
|
8
|
+
export type PackageCapability = string;
|
|
9
|
+
/**
|
|
10
|
+
* Catalog grouping: user-facing product extensions vs. authoring reference
|
|
11
|
+
* templates.
|
|
12
|
+
*
|
|
13
|
+
* - `"extension"` — a product extension a user installs for its functionality
|
|
14
|
+
* (e.g. pm-graph, pm-jira). This is the default and the only category the
|
|
15
|
+
* catalog originally carried.
|
|
16
|
+
* - `"template"` — a reference/scaffold extension that a user installs to
|
|
17
|
+
* learn the extension API. It registers real commands (it ships
|
|
18
|
+
* `manifest.json` + `dist/` and calls `registerCommand`), so installing it
|
|
19
|
+
* is a genuine operation, but its purpose is to be read and copied — it is
|
|
20
|
+
* the reference implementation covering every capability type. The UI
|
|
21
|
+
* badges template entries so a user can tell a learning scaffold from a
|
|
22
|
+
* product extension.
|
|
23
|
+
*/
|
|
24
|
+
export type PackageCategory = "extension" | "template";
|
|
25
|
+
/**
|
|
26
|
+
* Honest gating metadata so the UI can tell a user what they must configure
|
|
27
|
+
* before a package is useful. Both fields are OPTIONAL — most packages work
|
|
28
|
+
* with no external setup.
|
|
29
|
+
*
|
|
30
|
+
* - `requiresService`: a backing service the package talks to (e.g. Neo4j for
|
|
31
|
+
* pm-graph's sync). When set, the UI explains that the package's
|
|
32
|
+
* service-dependent features will not work until the service is reachable.
|
|
33
|
+
* `optional: true` means the package is still useful without the service
|
|
34
|
+
* (e.g. pm-graph's offline export/analyze work without Neo4j).
|
|
35
|
+
*
|
|
36
|
+
* - `requiresCredentials`: human-supplied secrets the package needs for its
|
|
37
|
+
* network-mutating commands (e.g. JIRA_API_TOKEN, LINEAR_API_KEY,
|
|
38
|
+
* PM_SLACK_WEBHOOK, GITHUB_TOKEN). Each entry names the env var(s) and a
|
|
39
|
+
* short human description. The UI must not pretend these are one-click; it
|
|
40
|
+
* surfaces the variables the user must set.
|
|
41
|
+
*/
|
|
42
|
+
export interface ServiceRequirement {
|
|
43
|
+
/** Display name of the backing service, e.g. "Neo4j". */
|
|
44
|
+
name: string;
|
|
45
|
+
/** True when the package is useful without the service (best-effort). */
|
|
46
|
+
optional?: boolean;
|
|
47
|
+
}
|
|
48
|
+
export interface CredentialRequirement {
|
|
49
|
+
/** Display label for what the credentials unlock, e.g. "Jira sync". */
|
|
50
|
+
label: string;
|
|
51
|
+
/** Environment variable(s) the package reads for the credentials. */
|
|
52
|
+
envVars: string[];
|
|
53
|
+
/**
|
|
54
|
+
* True when the package is partially usable without the credentials (e.g.
|
|
55
|
+
* `--dry-run` paths, unauthenticated reads). When false, the package is
|
|
56
|
+
* inert until the credentials are configured.
|
|
57
|
+
*/
|
|
58
|
+
optional?: boolean;
|
|
59
|
+
}
|
|
60
|
+
export interface PackageCatalogEntry {
|
|
61
|
+
/** Canonical npm package name, e.g. `pm-graph`. */
|
|
62
|
+
readonly name: string;
|
|
63
|
+
/** The install spec passed to `pm install`, always `npm:<name>`. */
|
|
64
|
+
readonly npmSpec: string;
|
|
65
|
+
/** Human-friendly title, e.g. `Graph`. */
|
|
66
|
+
readonly title: string;
|
|
67
|
+
/** One-line description, mirrored from the package's manifest/package.json. */
|
|
68
|
+
readonly description: string;
|
|
69
|
+
/** Capabilities declared in the package manifest. */
|
|
70
|
+
readonly capabilities: readonly PackageCapability[];
|
|
71
|
+
/**
|
|
72
|
+
* Whether this is a user-facing product extension (`"extension"`) or an
|
|
73
|
+
* authoring reference template (`"template"`). The UI badges template
|
|
74
|
+
* entries so a user can distinguish a learning scaffold from a product
|
|
75
|
+
* extension. See {@link PackageCategory}.
|
|
76
|
+
*/
|
|
77
|
+
readonly category: PackageCategory;
|
|
78
|
+
/** Backing service the package needs (optional). */
|
|
79
|
+
readonly requiresService?: ServiceRequirement;
|
|
80
|
+
/** Credentials the user must configure (optional). */
|
|
81
|
+
readonly requiresCredentials?: readonly CredentialRequirement[];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The catalog. Order is the display order in the UI. The list is exhaustive
|
|
85
|
+
* over every published pm package except `pm-web` itself (it is the host and
|
|
86
|
+
* cannot install itself). The {@link PackageCategory} field on each entry
|
|
87
|
+
* distinguishes user-facing product extensions from authoring reference
|
|
88
|
+
* templates; the two starter packages sort last so product extensions appear
|
|
89
|
+
* first.
|
|
90
|
+
*/
|
|
91
|
+
export declare const PACKAGE_CATALOG: readonly PackageCatalogEntry[];
|
|
92
|
+
/**
|
|
93
|
+
* Look up a catalog entry by package name. Returns the entry or `undefined`
|
|
94
|
+
* when the name is not in the catalog. This is the security-critical gate:
|
|
95
|
+
* route handlers MUST call this and reject with 400 on a miss BEFORE passing
|
|
96
|
+
* the name to any pm command, so a user-supplied string can never be
|
|
97
|
+
* interpolated into an install target.
|
|
98
|
+
*/
|
|
99
|
+
export declare function findCatalogEntry(name: string): PackageCatalogEntry | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Resolve a package name to its verified npm install spec
|
|
102
|
+
* (`npm:<name>`), or `null` when the name is not in the catalog. Route
|
|
103
|
+
* handlers use this to obtain the spawn argument without ever building an
|
|
104
|
+
* install target from a raw user string.
|
|
105
|
+
*/
|
|
106
|
+
export declare function resolveNpmSpec(name: string): string | null;
|
|
107
|
+
/** The immutable list of catalog package names, in display order. */
|
|
108
|
+
export declare function catalogNames(): readonly string[];
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// PACKAGE CATALOG — the typed, immutable list of every installable pm package
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// pm-web used to ship exactly one pm package — a vendored copy of pm-graph
|
|
6
|
+
// pinned to an old CLI — and offered no way to install any other. This
|
|
7
|
+
// catalog is the single source of truth for the per-project "Packages" view
|
|
8
|
+
// and the extensions routes: every package the UI can install is declared
|
|
9
|
+
// here, and every `:name` route parameter is validated against it BEFORE a
|
|
10
|
+
// pm command is ever spawned. A user-supplied name can never reach an install
|
|
11
|
+
// target — a catalog lookup miss must 400 before any process spawn.
|
|
12
|
+
//
|
|
13
|
+
// The catalog is exhaustive over every published pm package except `pm-web`
|
|
14
|
+
// itself (this package IS the host, so it cannot install itself). The
|
|
15
|
+
// `category` field distinguishes user-facing product extensions
|
|
16
|
+
// (`"extension"`) from authoring reference templates (`"template"`): the
|
|
17
|
+
// starters are real, published npm packages that ship `manifest.json` +
|
|
18
|
+
// `dist/` and call `registerCommand`, so installing them genuinely registers
|
|
19
|
+
// working commands — they exist to be read and copied as reference
|
|
20
|
+
// implementations covering every capability type, and the UI badges them so a
|
|
21
|
+
// user can tell a learning scaffold from a product extension.
|
|
22
|
+
//
|
|
23
|
+
// Truthfulness contract: the catalog mirrors each package's real
|
|
24
|
+
// `manifest.json` (name, capabilities) and README (gating). Do NOT invent
|
|
25
|
+
// capabilities or hide requirements here — the UI surfaces `requiresService`
|
|
26
|
+
// and `requiresCredentials` so users are not promised a one-click install for
|
|
27
|
+
// a package that needs a Neo4j instance or an API token. The
|
|
28
|
+
// `test/catalog.test.ts` suite asserts that every entry's declared capabilities
|
|
29
|
+
// match the on-disk `manifest.json` under fleet/<pkg>/manifest.json, so a
|
|
30
|
+
// drift between this catalog and the packages is a failing test, not a silent
|
|
31
|
+
// UI lie.
|
|
32
|
+
/**
|
|
33
|
+
* The catalog. Order is the display order in the UI. The list is exhaustive
|
|
34
|
+
* over every published pm package except `pm-web` itself (it is the host and
|
|
35
|
+
* cannot install itself). The {@link PackageCategory} field on each entry
|
|
36
|
+
* distinguishes user-facing product extensions from authoring reference
|
|
37
|
+
* templates; the two starter packages sort last so product extensions appear
|
|
38
|
+
* first.
|
|
39
|
+
*/
|
|
40
|
+
export const PACKAGE_CATALOG = [
|
|
41
|
+
{
|
|
42
|
+
name: "pm-graph",
|
|
43
|
+
npmSpec: "npm:pm-graph",
|
|
44
|
+
title: "Graph",
|
|
45
|
+
description: "Knowledge graph and dependency graph extension for pm CLI workspaces, with optional Neo4j sync.",
|
|
46
|
+
capabilities: ["commands", "importers", "services"],
|
|
47
|
+
category: "extension",
|
|
48
|
+
requiresService: { name: "Neo4j", optional: true },
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "pm-beads",
|
|
52
|
+
npmSpec: "npm:pm-beads",
|
|
53
|
+
title: "Beads",
|
|
54
|
+
description: "Beads JSONL importer/exporter. Import work items from the Beads JSONL format into pm and export pm items back to Beads JSONL, preserving ids and dependency edges.",
|
|
55
|
+
capabilities: ["commands", "schema", "importers"],
|
|
56
|
+
category: "extension",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "pm-brief",
|
|
60
|
+
npmSpec: "npm:pm-brief",
|
|
61
|
+
title: "Brief",
|
|
62
|
+
description: "Token-budgeted agent briefs and next-work plans for pm workspaces",
|
|
63
|
+
capabilities: ["commands", "renderers", "schema"],
|
|
64
|
+
category: "extension",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "pm-changelog",
|
|
68
|
+
npmSpec: "npm:pm-changelog",
|
|
69
|
+
title: "Changelog",
|
|
70
|
+
description: "Generate CHANGELOG.md release notes from pm-cli items",
|
|
71
|
+
capabilities: ["commands", "schema", "importers", "renderers"],
|
|
72
|
+
category: "extension",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "pm-context",
|
|
76
|
+
npmSpec: "npm:pm-context",
|
|
77
|
+
title: "Context",
|
|
78
|
+
description: "Generate deterministic pm context packs for agent handoffs, reviews, and status briefs",
|
|
79
|
+
capabilities: ["commands", "renderers", "schema"],
|
|
80
|
+
category: "extension",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "pm-csv",
|
|
84
|
+
npmSpec: "npm:pm-csv",
|
|
85
|
+
title: "CSV",
|
|
86
|
+
description: "CSV importer and exporter for pm-cli",
|
|
87
|
+
capabilities: ["commands", "importers", "schema"],
|
|
88
|
+
category: "extension",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "pm-gantt-chart",
|
|
92
|
+
npmSpec: "npm:pm-gantt-chart",
|
|
93
|
+
title: "Gantt Chart",
|
|
94
|
+
description: "ASCII Gantt chart renderer + multi-format exporter for pm-cli",
|
|
95
|
+
capabilities: ["commands", "schema", "importers", "preflight"],
|
|
96
|
+
category: "extension",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: "pm-github",
|
|
100
|
+
npmSpec: "npm:pm-github",
|
|
101
|
+
title: "GitHub",
|
|
102
|
+
description: "GitHub Issues + Projects v2 integration. Imports issues as pm items (`pm github import`), exports pm items as GitHub issues, syncs issue state, and bidirectionally syncs pm items with a GitHub Projects v2 board (`pm github project import/sync/list/fields`) — mapping pm status to the board Status column with idempotent, no-data-loss provenance.",
|
|
103
|
+
capabilities: ["commands", "importers", "schema", "hooks", "preflight", "search"],
|
|
104
|
+
category: "extension",
|
|
105
|
+
requiresCredentials: [
|
|
106
|
+
{
|
|
107
|
+
label: "GitHub token (for private repos, 5000 req/hr, and export/push)",
|
|
108
|
+
envVars: ["GITHUB_TOKEN", "GH_TOKEN"],
|
|
109
|
+
optional: true,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "pm-jira",
|
|
115
|
+
npmSpec: "npm:pm-jira",
|
|
116
|
+
title: "Jira",
|
|
117
|
+
description: "Jira issue sync for pm-cli",
|
|
118
|
+
capabilities: ["commands", "schema", "importers", "hooks", "preflight"],
|
|
119
|
+
category: "extension",
|
|
120
|
+
requiresCredentials: [
|
|
121
|
+
{
|
|
122
|
+
label: "Jira API credentials (for live sync, import, and export --push)",
|
|
123
|
+
envVars: ["JIRA_BASE_URL", "JIRA_EMAIL", "JIRA_API_TOKEN"],
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "pm-linear",
|
|
129
|
+
npmSpec: "npm:pm-linear",
|
|
130
|
+
title: "Linear",
|
|
131
|
+
description: "Linear.app issue sync for pm-cli",
|
|
132
|
+
capabilities: ["commands", "schema", "importers", "preflight"],
|
|
133
|
+
category: "extension",
|
|
134
|
+
requiresCredentials: [
|
|
135
|
+
{
|
|
136
|
+
label: "Linear API key (for live import/export and validate --check-network)",
|
|
137
|
+
envVars: ["LINEAR_API_KEY"],
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "pm-ops",
|
|
143
|
+
npmSpec: "npm:pm-ops",
|
|
144
|
+
title: "Ops",
|
|
145
|
+
description: "Multi-repo fleet operations for pm-cli",
|
|
146
|
+
capabilities: ["commands", "renderers", "schema", "parser"],
|
|
147
|
+
category: "extension",
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "pm-presets",
|
|
151
|
+
npmSpec: "npm:pm-presets",
|
|
152
|
+
title: "Presets",
|
|
153
|
+
description: "All 7 official pm-cli workspace presets in one package: bug-triage, indie-dev, open-source, software-sprint, startup-roadmap, kanban, agent-workflow",
|
|
154
|
+
capabilities: ["commands", "schema"],
|
|
155
|
+
category: "extension",
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "pm-slack",
|
|
159
|
+
npmSpec: "npm:pm-slack",
|
|
160
|
+
title: "Slack",
|
|
161
|
+
description: "Slack notifications for pm item lifecycle events",
|
|
162
|
+
capabilities: ["commands", "hooks", "schema", "preflight"],
|
|
163
|
+
category: "extension",
|
|
164
|
+
requiresCredentials: [
|
|
165
|
+
{
|
|
166
|
+
label: "Slack webhook (for posting notifications)",
|
|
167
|
+
envVars: ["PM_SLACK_WEBHOOK"],
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: "pm-slack-standup",
|
|
173
|
+
npmSpec: "npm:pm-slack-standup",
|
|
174
|
+
title: "Slack Standup",
|
|
175
|
+
description: "Post pm context as a Slack standup message",
|
|
176
|
+
capabilities: ["commands", "schema", "importers", "preflight", "services"],
|
|
177
|
+
category: "extension",
|
|
178
|
+
requiresCredentials: [
|
|
179
|
+
{
|
|
180
|
+
label: "Slack webhook (for posting the standup)",
|
|
181
|
+
envVars: ["PM_SLACK_WEBHOOK"],
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "pm-todos",
|
|
187
|
+
npmSpec: "npm:pm-todos",
|
|
188
|
+
title: "Todos",
|
|
189
|
+
description: "TODO round-trip. Import/export/sync markdown checkboxes, todo.txt, jsonl, checkbox, and pi coding-agent todo JSON as pm items.",
|
|
190
|
+
capabilities: ["commands", "schema", "importers", "preflight"],
|
|
191
|
+
category: "extension",
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: "pm-starter",
|
|
195
|
+
npmSpec: "npm:pm-starter",
|
|
196
|
+
title: "Starter",
|
|
197
|
+
description: "Complete starter/scaffold extension for pm-cli showing all capability types",
|
|
198
|
+
capabilities: [
|
|
199
|
+
"commands",
|
|
200
|
+
"renderers",
|
|
201
|
+
"hooks",
|
|
202
|
+
"schema",
|
|
203
|
+
"importers",
|
|
204
|
+
"search",
|
|
205
|
+
"parser",
|
|
206
|
+
"preflight",
|
|
207
|
+
"services",
|
|
208
|
+
],
|
|
209
|
+
category: "template",
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: "pm-ts-starter",
|
|
213
|
+
npmSpec: "npm:pm-ts-starter",
|
|
214
|
+
title: "TypeScript Starter",
|
|
215
|
+
description: "TypeScript reference extension for pm-cli covering all capability types",
|
|
216
|
+
capabilities: [
|
|
217
|
+
"commands",
|
|
218
|
+
"renderers",
|
|
219
|
+
"hooks",
|
|
220
|
+
"schema",
|
|
221
|
+
"importers",
|
|
222
|
+
"search",
|
|
223
|
+
"parser",
|
|
224
|
+
"preflight",
|
|
225
|
+
"services",
|
|
226
|
+
],
|
|
227
|
+
category: "template",
|
|
228
|
+
},
|
|
229
|
+
];
|
|
230
|
+
/** A frozen map keyed by package name for O(1) catalog lookup. */
|
|
231
|
+
const CATALOG_BY_NAME = new Map(PACKAGE_CATALOG.map((entry) => [entry.name, entry]));
|
|
232
|
+
/**
|
|
233
|
+
* Look up a catalog entry by package name. Returns the entry or `undefined`
|
|
234
|
+
* when the name is not in the catalog. This is the security-critical gate:
|
|
235
|
+
* route handlers MUST call this and reject with 400 on a miss BEFORE passing
|
|
236
|
+
* the name to any pm command, so a user-supplied string can never be
|
|
237
|
+
* interpolated into an install target.
|
|
238
|
+
*/
|
|
239
|
+
export function findCatalogEntry(name) {
|
|
240
|
+
return CATALOG_BY_NAME.get(name);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Resolve a package name to its verified npm install spec
|
|
244
|
+
* (`npm:<name>`), or `null` when the name is not in the catalog. Route
|
|
245
|
+
* handlers use this to obtain the spawn argument without ever building an
|
|
246
|
+
* install target from a raw user string.
|
|
247
|
+
*/
|
|
248
|
+
export function resolveNpmSpec(name) {
|
|
249
|
+
const entry = CATALOG_BY_NAME.get(name);
|
|
250
|
+
return entry ? entry.npmSpec : null;
|
|
251
|
+
}
|
|
252
|
+
/** The immutable list of catalog package names, in display order. */
|
|
253
|
+
export function catalogNames() {
|
|
254
|
+
return PACKAGE_CATALOG.map((entry) => entry.name);
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=package-catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-catalog.js","sourceRoot":"","sources":["../../src/services/package-catalog.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { PmClient, PmCliError, isPmCliExpectedError, EXIT_CODE, type GetItemAtResult } from "@unbrained/pm-cli/sdk";
|
|
2
2
|
export { PmCliError, isPmCliExpectedError, EXIT_CODE, type GetItemAtResult };
|
|
3
3
|
export declare class Semaphore {
|
|
4
|
-
private readonly limit;
|
|
5
4
|
private active;
|
|
6
5
|
private readonly waiting;
|
|
6
|
+
private readonly limit;
|
|
7
7
|
constructor(limit: number);
|
|
8
8
|
acquire(): Promise<() => void>;
|
|
9
9
|
}
|
|
@@ -49,6 +49,71 @@ export interface EnsureGraphExtensionResult {
|
|
|
49
49
|
active: boolean;
|
|
50
50
|
error?: string;
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* The per-extension state shape emitted by `pm extension --json`. Only the
|
|
54
|
+
* fields consumers read are typed; the command emits far more (triage, policy,
|
|
55
|
+
* diagnostics) which is deliberately dropped.
|
|
56
|
+
*/
|
|
57
|
+
export interface ExtensionState {
|
|
58
|
+
name: string;
|
|
59
|
+
version?: string;
|
|
60
|
+
active?: boolean;
|
|
61
|
+
enabled?: boolean;
|
|
62
|
+
runtime_active?: boolean;
|
|
63
|
+
activation_status?: string;
|
|
64
|
+
managed?: boolean;
|
|
65
|
+
source?: {
|
|
66
|
+
kind?: string;
|
|
67
|
+
input?: string;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Outcome of reading per-project extension state.
|
|
72
|
+
*
|
|
73
|
+
* `ok` distinguishes "the command ran and this is the state" from "the command
|
|
74
|
+
* failed, so we know nothing" — a distinction both previous copies of this
|
|
75
|
+
* parser collapsed, reporting a failed `pm extension --json` identically to a
|
|
76
|
+
* project with nothing installed.
|
|
77
|
+
*/
|
|
78
|
+
export interface ExtensionStatesResult {
|
|
79
|
+
ok: boolean;
|
|
80
|
+
states: Map<string, ExtensionState>;
|
|
81
|
+
error?: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Read the per-project extension state from `pm extension --json`, returning a
|
|
85
|
+
* map keyed by extension name. Used both by {@link ensureGraphExtension} and
|
|
86
|
+
* the extensions routes' catalog join.
|
|
87
|
+
*/
|
|
88
|
+
export declare function readProjectExtensionStates(projectDir: string): Promise<ExtensionStatesResult>;
|
|
89
|
+
/**
|
|
90
|
+
* Timeout for package installs, which resolve and download from the npm
|
|
91
|
+
* registry rather than only touching local state.
|
|
92
|
+
*
|
|
93
|
+
* The 30s default is sized for local commands and leaves too thin a margin
|
|
94
|
+
* here. Measured on this host: a warm-cache install is ~2s, and a cold-cache
|
|
95
|
+
* install of the heaviest catalog package (pm-graph, which pulls
|
|
96
|
+
* `neo4j-driver`) is ~10s. That is only a 3x margin on a fast connection,
|
|
97
|
+
* before accounting for a container sharing bandwidth or a slow registry —
|
|
98
|
+
* and the failure mode is a project create or package install that dies
|
|
99
|
+
* mid-download. A hung install still terminates, just later.
|
|
100
|
+
*/
|
|
101
|
+
export declare const INSTALL_COMMAND_TIMEOUT_MS = 180000;
|
|
102
|
+
/**
|
|
103
|
+
* Ensure the pm-graph package is installed and active for a project.
|
|
104
|
+
*
|
|
105
|
+
* This used to install a *vendored* copy of pm-graph from
|
|
106
|
+
* `extensions/pm-graph/` (a stale fork pinned to pm-cli `^2026.7.5`). The
|
|
107
|
+
* vendored fork is gone; pm-graph is now installed from npm through the same
|
|
108
|
+
* generic catalog path as every other pm package
|
|
109
|
+
* (src/services/package-catalog.ts). The npm spec is resolved from the catalog
|
|
110
|
+
* — never built from a user-supplied string — so the install target is always
|
|
111
|
+
* the verified `npm:pm-graph` constant.
|
|
112
|
+
*
|
|
113
|
+
* The graph routes in src/routes/pm.ts call this before `pm pm-graph export`,
|
|
114
|
+
* and {@link initProject} calls it on project creation, so the user-facing
|
|
115
|
+
* graph behaviour is unchanged.
|
|
116
|
+
*/
|
|
52
117
|
export declare function ensureGraphExtension(userId: string, slug: string): Promise<EnsureGraphExtensionResult>;
|
|
53
118
|
/**
|
|
54
119
|
* Return a cached {@link PmClient} for a workspace pm-root, creating one on
|
|
@@ -3,6 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { pool } from "../db.js";
|
|
5
5
|
import { getItemAt, PM_TOOL_PARAMETERS_SCHEMA, PmClient, PmCliError, isPmCliExpectedError, EXIT_CODE, } from "@unbrained/pm-cli/sdk";
|
|
6
|
+
import { resolveNpmSpec } from "./package-catalog.js";
|
|
6
7
|
// Re-exported so route handlers and tests can reference the verified projection
|
|
7
8
|
// shape and the typed error class without reaching into the SDK package map.
|
|
8
9
|
export { PmCliError, isPmCliExpectedError, EXIT_CODE };
|
|
@@ -13,9 +14,9 @@ function positiveInteger(value, fallback) {
|
|
|
13
14
|
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
14
15
|
}
|
|
15
16
|
export class Semaphore {
|
|
16
|
-
limit;
|
|
17
17
|
active = 0;
|
|
18
18
|
waiting = [];
|
|
19
|
+
limit;
|
|
19
20
|
constructor(limit) {
|
|
20
21
|
this.limit = limit;
|
|
21
22
|
}
|
|
@@ -50,8 +51,6 @@ const OLLAMA_BASE_URL = process.env.OLLAMA_BASE_URL ||
|
|
|
50
51
|
const OLLAMA_EMBEDDING_MODEL = process.env.PM_OLLAMA_MODEL ||
|
|
51
52
|
"qwen3-embedding:0.6b";
|
|
52
53
|
const OLLAMA_API_KEY = process.env.OLLAMA_API_KEY || "";
|
|
53
|
-
const PM_GRAPH_EXTENSION_PATH = process.env.PM_GRAPH_EXTENSION_PATH ||
|
|
54
|
-
path.join(process.cwd(), "extensions", "pm-graph");
|
|
55
54
|
export function getProjectDir(userId, slug) {
|
|
56
55
|
return path.join(projectsRoot(), userId, slug);
|
|
57
56
|
}
|
|
@@ -224,82 +223,102 @@ export function projectExists(userId, slug) {
|
|
|
224
223
|
const dir = getProjectDir(userId, slug);
|
|
225
224
|
return fs.existsSync(path.join(dir, ".agents", "pm", "settings.json"));
|
|
226
225
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Read the per-project extension state from `pm extension --json`, returning a
|
|
228
|
+
* map keyed by extension name. Used both by {@link ensureGraphExtension} and
|
|
229
|
+
* the extensions routes' catalog join.
|
|
230
|
+
*/
|
|
231
|
+
export async function readProjectExtensionStates(projectDir) {
|
|
232
|
+
const result = await runSerialized(projectDir, () => runProcess(projectDir, ["extension", "--json"], { timeoutMs: 15_000 }));
|
|
233
|
+
const states = new Map();
|
|
234
|
+
if (!result.ok || !result.stdout) {
|
|
235
|
+
return { ok: false, states, error: result.stderr || "pm extension --json failed" };
|
|
233
236
|
}
|
|
234
|
-
}
|
|
235
|
-
function bundledGraphExtensionManifest() {
|
|
236
|
-
const manifestPath = path.join(PM_GRAPH_EXTENSION_PATH, "manifest.json");
|
|
237
|
-
if (!fs.existsSync(manifestPath))
|
|
238
|
-
return null;
|
|
239
|
-
return readJsonFile(manifestPath);
|
|
240
|
-
}
|
|
241
|
-
function projectGraphExtensionManifest(projectDir) {
|
|
242
|
-
return readJsonFile(path.join(projectDir, ".agents", "pm", "extensions", "pm-graph", "manifest.json"));
|
|
243
|
-
}
|
|
244
|
-
async function graphExtensionIsActive(projectDir) {
|
|
245
|
-
const result = await runSerialized(projectDir, () => runProcess(projectDir, ["extension", "explore", "--project", "--json"], { timeoutMs: 15_000 }));
|
|
246
|
-
if (!result.ok || !result.stdout)
|
|
247
|
-
return false;
|
|
248
237
|
try {
|
|
249
238
|
const parsed = JSON.parse(result.stdout);
|
|
250
|
-
|
|
239
|
+
const extensions = parsed.details?.extensions;
|
|
240
|
+
if (!Array.isArray(extensions)) {
|
|
241
|
+
return { ok: false, states, error: "pm extension --json returned no extension list" };
|
|
242
|
+
}
|
|
243
|
+
for (const ext of extensions) {
|
|
244
|
+
if (ext && typeof ext.name === "string")
|
|
245
|
+
states.set(ext.name, ext);
|
|
246
|
+
}
|
|
251
247
|
}
|
|
252
248
|
catch {
|
|
253
|
-
return false;
|
|
249
|
+
return { ok: false, states, error: "pm extension --json returned malformed JSON" };
|
|
254
250
|
}
|
|
251
|
+
return { ok: true, states };
|
|
255
252
|
}
|
|
256
|
-
|
|
257
|
-
|
|
253
|
+
/**
|
|
254
|
+
* Timeout for package installs, which resolve and download from the npm
|
|
255
|
+
* registry rather than only touching local state.
|
|
256
|
+
*
|
|
257
|
+
* The 30s default is sized for local commands and leaves too thin a margin
|
|
258
|
+
* here. Measured on this host: a warm-cache install is ~2s, and a cold-cache
|
|
259
|
+
* install of the heaviest catalog package (pm-graph, which pulls
|
|
260
|
+
* `neo4j-driver`) is ~10s. That is only a 3x margin on a fast connection,
|
|
261
|
+
* before accounting for a container sharing bandwidth or a slow registry —
|
|
262
|
+
* and the failure mode is a project create or package install that dies
|
|
263
|
+
* mid-download. A hung install still terminates, just later.
|
|
264
|
+
*/
|
|
265
|
+
export const INSTALL_COMMAND_TIMEOUT_MS = 180_000;
|
|
266
|
+
async function runExtensionCommand(projectDir, args, timeoutMs) {
|
|
267
|
+
return runSerialized(projectDir, () => runProcess(projectDir, args, { timeoutMs }));
|
|
258
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Ensure the pm-graph package is installed and active for a project.
|
|
271
|
+
*
|
|
272
|
+
* This used to install a *vendored* copy of pm-graph from
|
|
273
|
+
* `extensions/pm-graph/` (a stale fork pinned to pm-cli `^2026.7.5`). The
|
|
274
|
+
* vendored fork is gone; pm-graph is now installed from npm through the same
|
|
275
|
+
* generic catalog path as every other pm package
|
|
276
|
+
* (src/services/package-catalog.ts). The npm spec is resolved from the catalog
|
|
277
|
+
* — never built from a user-supplied string — so the install target is always
|
|
278
|
+
* the verified `npm:pm-graph` constant.
|
|
279
|
+
*
|
|
280
|
+
* The graph routes in src/routes/pm.ts call this before `pm pm-graph export`,
|
|
281
|
+
* and {@link initProject} calls it on project creation, so the user-facing
|
|
282
|
+
* graph behaviour is unchanged.
|
|
283
|
+
*/
|
|
259
284
|
export async function ensureGraphExtension(userId, slug) {
|
|
260
285
|
const dir = getProjectDir(userId, slug);
|
|
261
|
-
|
|
262
|
-
|
|
286
|
+
// Resolve the verified npm install spec from the catalog — never a raw path.
|
|
287
|
+
const npmSpec = resolveNpmSpec("pm-graph");
|
|
288
|
+
if (!npmSpec) {
|
|
263
289
|
return {
|
|
264
290
|
ok: false,
|
|
265
291
|
installed: false,
|
|
266
292
|
active: false,
|
|
267
|
-
error:
|
|
293
|
+
error: "pm-graph is not present in the package catalog.",
|
|
268
294
|
};
|
|
269
295
|
}
|
|
270
|
-
const
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
296
|
+
const { states } = await readProjectExtensionStates(dir);
|
|
297
|
+
const graphState = states.get("pm-graph");
|
|
298
|
+
const installed = Boolean(graphState);
|
|
299
|
+
const active = Boolean(graphState?.active && graphState?.enabled);
|
|
300
|
+
if (!installed) {
|
|
301
|
+
const install = await runExtensionCommand(dir, ["install", npmSpec, "--project"], INSTALL_COMMAND_TIMEOUT_MS);
|
|
274
302
|
if (!install.ok) {
|
|
275
303
|
return {
|
|
276
304
|
ok: false,
|
|
277
|
-
installed:
|
|
305
|
+
installed: false,
|
|
278
306
|
active: false,
|
|
279
|
-
error: install.stderr || install.stdout || "Failed to install
|
|
307
|
+
error: install.stderr || install.stdout || "Failed to install pm-graph from npm.",
|
|
280
308
|
};
|
|
281
309
|
}
|
|
282
310
|
}
|
|
283
|
-
if (!
|
|
311
|
+
if (!active) {
|
|
284
312
|
const activate = await runExtensionCommand(dir, ["extension", "activate", "pm-graph", "--project"]);
|
|
285
313
|
if (!activate.ok) {
|
|
286
314
|
return {
|
|
287
315
|
ok: false,
|
|
288
316
|
installed: true,
|
|
289
317
|
active: false,
|
|
290
|
-
error: activate.stderr || activate.stdout || "Failed to activate
|
|
318
|
+
error: activate.stderr || activate.stdout || "Failed to activate pm-graph.",
|
|
291
319
|
};
|
|
292
320
|
}
|
|
293
321
|
}
|
|
294
|
-
const ping = await runExtensionCommand(dir, ["pm-graph", "ping", "--json"]);
|
|
295
|
-
if (!ping.ok) {
|
|
296
|
-
return {
|
|
297
|
-
ok: false,
|
|
298
|
-
installed: true,
|
|
299
|
-
active: false,
|
|
300
|
-
error: ping.stderr || ping.stdout || "Bundled pm-graph extension is installed but did not activate at runtime.",
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
322
|
return { ok: true, installed: true, active: true };
|
|
304
323
|
}
|
|
305
324
|
// ---------------------------------------------------------------------------
|
|
@@ -352,6 +371,16 @@ const SPAWN_FALLBACK_ACTIONS = new Set([
|
|
|
352
371
|
"test-runs",
|
|
353
372
|
// `pm templates list/show` is not a native SDK action.
|
|
354
373
|
"templates",
|
|
374
|
+
// Extension management (`pm install <source> --project`, `pm extension
|
|
375
|
+
// activate|deactivate|uninstall <name> --project`) takes a positional source
|
|
376
|
+
// or subcommand that `PmClient.run(action, {options})` drops — the SDK's
|
|
377
|
+
// `client.run("install", ...)` raises "requires extension source input" and
|
|
378
|
+
// `client.run("extension", ...)` only performs the default explore, ignoring
|
|
379
|
+
// activate/deactivate/uninstall. The per-project package catalog routes depend
|
|
380
|
+
// on these positionals, so install + extension stay on the spawn fallback (the
|
|
381
|
+
// verified `pm install npm:<pkg> --project` mechanism).
|
|
382
|
+
"install",
|
|
383
|
+
"extension",
|
|
355
384
|
// `PmClient.run("plan", {options:{subcommand,id}})` does not accept the plan id
|
|
356
385
|
// as an option key (only the typed convenience methods `planShow(id)` /
|
|
357
386
|
// `planAddStep(id,...)` do). Converting all ~17 plan routes to typed methods is
|
|
@@ -391,6 +420,13 @@ const POSITIONAL_KEYS = {
|
|
|
391
420
|
"pause-task": ["id"],
|
|
392
421
|
"close-task": ["id", "reason"],
|
|
393
422
|
history: ["id"],
|
|
423
|
+
// `pm schema [subcommand] [name]` — e.g. `schema add-type Spike`. Without this
|
|
424
|
+
// mapping both positionals are dropped and the SDK rejects the call with
|
|
425
|
+
// "Missing required argument: subcommand".
|
|
426
|
+
schema: ["subcommand", "name"],
|
|
427
|
+
// `pm history-repair [id]` — omitted only when `--all` is passed, which the
|
|
428
|
+
// per-item route never does.
|
|
429
|
+
"history-repair": ["id"],
|
|
394
430
|
config: ["scope", "configAction", "key", "value"],
|
|
395
431
|
};
|
|
396
432
|
const PM_CLIENT_CACHE_MAX = positiveInteger(process.env.PM_WEB_PM_CLIENT_CACHE_MAX, 256);
|