@unbrained/pm-web 2026.5.30 → 2026.6.2-1

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 CHANGED
@@ -1,6 +1,21 @@
1
1
  # Changelog
2
2
 
3
- ## 2026.5.30 - 2026-05-30
3
+ ## 2026.6.2-1 - 2026-06-02
4
+
5
+ ### Added
6
+
7
+ - Do NOT add the 'services' extension capability to pm-web ([pm-web-vyfp](https://github.com/unbraind/pm-web/blob/main/.agents/pm/decisions/pm-web-vyfp.toon))
8
+ - Deepen pm-web extension command surface \(status/stop/doctor\) + services-capability evaluation ([pm-web-7pxa](https://github.com/unbraind/pm-web/blob/main/.agents/pm/features/pm-web-7pxa.toon))
9
+ - Add /healthz version + unit tests + README docs ([pm-web-edyj](https://github.com/unbraind/pm-web/blob/main/.agents/pm/tasks/pm-web-edyj.toon))
10
+ - Introduce CommandError \(numeric exitCode\) for new handlers ([pm-web-9ycn](https://github.com/unbraind/pm-web/blob/main/.agents/pm/tasks/pm-web-9ycn.toon))
11
+
12
+ ### Other
13
+
14
+ - Implement 'pm web doctor' command ([pm-web-vyqb](https://github.com/unbraind/pm-web/blob/main/.agents/pm/tasks/pm-web-vyqb.toon))
15
+ - Implement 'pm web stop' command + pidfile-on-detach ([pm-web-odwq](https://github.com/unbraind/pm-web/blob/main/.agents/pm/tasks/pm-web-odwq.toon))
16
+ - Implement 'pm web status' command ([pm-web-8eqs](https://github.com/unbraind/pm-web/blob/main/.agents/pm/tasks/pm-web-8eqs.toon))
17
+
18
+ ## 2026.05.30 - 2026-05-30
4
19
 
5
20
  ### Fixed
6
21
 
package/README.md CHANGED
@@ -52,8 +52,55 @@ The package repository is at **github.com/unbraind/pm-web**.
52
52
 
53
53
  | Command | Description |
54
54
  |---|---|
55
- | `pm web` | Start the pm-web server |
55
+ | `pm web` | Start the pm-web server (foreground) |
56
56
  | `pm web --port 8080` | Start on a custom port |
57
+ | `pm web --detach` | Start the server in the background (tracked via a pidfile) |
58
+ | `pm web status` | Report whether a server is reachable (probes `/healthz`); `--json` supported |
59
+ | `pm web stop` | Stop a server previously started with `--detach`; `--json` supported |
60
+ | `pm web doctor` | Preflight diagnostics (Node, runtime deps, port, pm, workspace); `--json` supported |
61
+
62
+ #### `pm web status`
63
+
64
+ Probes `http://localhost:<port>/healthz` and reports `up`/`down`, the responding
65
+ port, and the server version. Never errors when the server is down — it returns a
66
+ structured `down` result. The port is resolved from `--port`, then `PORT`, then
67
+ the default `4000`.
68
+
69
+ ```bash
70
+ pm web status # human-readable
71
+ pm web status --port 8080 --json
72
+ ```
73
+
74
+ #### `pm web stop`
75
+
76
+ Stops a server started with `pm web --detach`. The detached PID is recorded in a
77
+ pidfile (under `PM_WEB_STATE_DIR` if set, otherwise the OS temp dir, keyed by
78
+ port). `pm web stop` reads the pidfile, sends `SIGTERM`, and clears the pidfile.
79
+ If nothing is running it reports `not_running` gracefully and cleans up any stale
80
+ pidfile.
81
+
82
+ ```bash
83
+ pm web stop # stops the server on the default port
84
+ pm web stop --port 8080 --json
85
+ ```
86
+
87
+ #### `pm web doctor`
88
+
89
+ Runs preflight checks before starting the server: Node version (>= 20), whether
90
+ runtime dependencies (express, etc.) are installed, whether the target port is
91
+ free, whether `pm` is on `PATH`, and whether the workspace is initialized.
92
+ Returns an overall `ok` boolean. The `port_available` check is informational (a
93
+ busy port may just be a server you already started) and does not gate `ok`.
94
+
95
+ ```bash
96
+ pm web doctor
97
+ pm web doctor --json
98
+ ```
99
+
100
+ > Note: the `services` extension capability is intentionally **not** declared.
101
+ > The pm SDK's `registerService` only overrides one of eight fixed core services
102
+ > (e.g. `output_format`), which would alter core output for unrelated commands;
103
+ > the server lifecycle is exposed safely through the commands above instead.
57
104
 
58
105
  ### Environment Variables
59
106
 
@@ -64,6 +111,7 @@ The package repository is at **github.com/unbraind/pm-web**.
64
111
  | `PM_WEB_SECRET_KEY` | Recommended | At-rest encryption key for saved GitHub PATs. Falls back to `JWT_SECRET`; use at least 32 characters |
65
112
  | `PM_WEB_BOOTSTRAP_ADMIN_EMAIL` | Recommended | Email of the user account to auto-promote to admin on schema init. Leave unset to skip auto-promotion (manage admins via the admin UI). |
66
113
  | `PORT` | No | Server port (default: 4000) |
114
+ | `PM_WEB_STATE_DIR` | No | Directory for the `--detach` pidfile used by `pm web stop` (default: OS temp dir) |
67
115
  | `NODE_ENV` | No | `production` enables caching |
68
116
  | `OLLAMA_BASE_URL` / `OLLAMA_HOST` | No | Local Ollama endpoint for semantic pm search |
69
117
  | `PM_OLLAMA_MODEL` | No | Embedding model for new projects, default `qwen3-embedding:0.6b` |
@@ -105,3 +153,19 @@ MIT
105
153
  ## Release Automation
106
154
 
107
155
  This package is release-ready for GitHub, npm, and Bun-compatible installs. CI runs type checking, build, production dependency audit, package packing, Bun install verification, and pm-changelog validation. The daily release workflow publishes only when commits exist after the latest release tag and uses pm-changelog to generate CHANGELOG.md and GitHub release notes.
156
+
157
+ ## New data endpoints (kanban board & search)
158
+
159
+ The pm data API now exposes board and search views, both driven by the
160
+ workspace's live `pm contracts` schema (so they reflect the installed pm CLI +
161
+ extensions):
162
+
163
+ - `GET /api/projects/:projectId/pm/board` — items grouped into kanban columns by
164
+ the workspace's runtime statuses (unlisted statuses fall into `(other)`).
165
+ - `GET /api/projects/:projectId/pm/search?q=<text>` — case-insensitive full-text
166
+ search over id, title, tags and body.
167
+ - `GET /api/projects/:projectId/pm/schema` — runtime types/statuses (existing).
168
+ - `GET /api/projects/:projectId/pm/graph` — dependency graph (existing).
169
+
170
+ The pure grouping/search helpers live in `src/board.ts` and are unit-tested
171
+ independently of the database.
package/dist/board.js ADDED
@@ -0,0 +1,41 @@
1
+ // Pure, dependency-free helpers for the kanban board and search endpoints.
2
+ // Kept in their own module (no db/neo4j imports) so they are unit-testable
3
+ // without booting the server or a database.
4
+ // Group items into board columns keyed by the workspace's actual statuses
5
+ // (from `pm contracts`), so the kanban board reflects whatever statuses the
6
+ // installed pm CLI + extensions define. Items with an unlisted status fall into
7
+ // a trailing "(other)" column so nothing is silently dropped.
8
+ export function boardColumns(items, statuses) {
9
+ const known = statuses.length > 0 ? statuses : ["open", "in_progress", "blocked", "closed"];
10
+ const columns = new Map();
11
+ for (const s of known)
12
+ columns.set(s, []);
13
+ const OTHER = "(other)";
14
+ for (const item of items) {
15
+ const s = item.status ?? "open";
16
+ if (columns.has(s))
17
+ columns.get(s).push(item);
18
+ else {
19
+ if (!columns.has(OTHER))
20
+ columns.set(OTHER, []);
21
+ columns.get(OTHER).push(item);
22
+ }
23
+ }
24
+ return [...columns.entries()].map(([status, list]) => ({ status, items: list }));
25
+ }
26
+ // Case-insensitive full-text filter over id, title, tags and body.
27
+ export function filterItemsByQuery(items, query) {
28
+ const q = query.trim().toLowerCase();
29
+ if (!q)
30
+ return items;
31
+ return items.filter((item) => {
32
+ const hay = [
33
+ item.id,
34
+ item.title ?? "",
35
+ (item.tags ?? []).join(" "),
36
+ item.body ?? "",
37
+ ].join(" ").toLowerCase();
38
+ return hay.includes(q);
39
+ });
40
+ }
41
+ //# sourceMappingURL=board.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board.js","sourceRoot":"","sources":["../src/board.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,2EAA2E;AAC3E,4CAA4C;AAU5C,0EAA0E;AAC1E,4EAA4E;AAC5E,gFAAgF;AAChF,8DAA8D;AAC9D,MAAM,UAAU,YAAY,CAC1B,KAAU,EACV,QAAkB;IAElB,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IAC5F,MAAM,OAAO,GAAG,IAAI,GAAG,EAAe,CAAC;IACvC,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,SAAS,CAAC;IACxB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC;QAChC,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aAC1C,CAAC;YACJ,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAChD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACnF,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,kBAAkB,CAAsB,KAAU,EAAE,KAAa;IAC/E,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrB,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QAC3B,MAAM,GAAG,GAAG;YACV,IAAI,CAAC,EAAE;YACP,IAAI,CAAC,KAAK,IAAI,EAAE;YAChB,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;YAC3B,IAAI,CAAC,IAAI,IAAI,EAAE;SAChB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QAC1B,OAAO,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/index.js CHANGED
@@ -2,16 +2,81 @@
2
2
  // This file registers the web server as a pm extension command.
3
3
  import { spawn, spawnSync } from "node:child_process";
4
4
  import fs from "node:fs";
5
+ import net from "node:net";
6
+ import os from "node:os";
5
7
  import path from "node:path";
6
8
  import { fileURLToPath } from "node:url";
7
9
  // Inline defineExtension helper (avoids runtime dependency on @unbrained/pm-cli/sdk)
8
10
  function defineExtension(m) { return m; }
11
+ // ---------------------------------------------------------------------------
12
+ // Error contract
13
+ // CommandError carries a numeric exitCode so the pm runtime surfaces a clean
14
+ // failure instead of double-invoking the handler on a plain thrown Error.
15
+ // 1 = generic, 2 = usage, 3 = not-found
16
+ // ---------------------------------------------------------------------------
17
+ class CommandError extends Error {
18
+ exitCode;
19
+ constructor(message, exitCode = 1) {
20
+ super(message);
21
+ this.name = "CommandError";
22
+ this.exitCode = exitCode;
23
+ }
24
+ }
9
25
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
26
  const packageRoot = path.resolve(__dirname, "..");
11
27
  let serverProcess = null;
12
- function ensureRuntimeDependencies() {
28
+ const DEFAULT_PORT = "4000";
29
+ // ---------------------------------------------------------------------------
30
+ // Pure helpers (unit-tested in test/)
31
+ // ---------------------------------------------------------------------------
32
+ /** Resolve the port from flag → PORT env → default 4000. */
33
+ export function resolvePort(options, env = process.env) {
34
+ const flag = options["port"];
35
+ if (flag !== undefined && flag !== null && String(flag).length > 0) {
36
+ return String(flag);
37
+ }
38
+ if (env["PORT"] && String(env["PORT"]).length > 0) {
39
+ return String(env["PORT"]);
40
+ }
41
+ return DEFAULT_PORT;
42
+ }
43
+ /**
44
+ * Resolve the pidfile path used to track a detached server.
45
+ * Uses PM_WEB_STATE_DIR when set, else the OS temp dir, keyed by port so
46
+ * multiple detached servers don't clobber each other's pidfile.
47
+ */
48
+ export function pidfilePath(port, env = process.env, tmpDir = os.tmpdir()) {
49
+ const baseDir = env["PM_WEB_STATE_DIR"] && String(env["PM_WEB_STATE_DIR"]).length > 0
50
+ ? String(env["PM_WEB_STATE_DIR"])
51
+ : tmpDir;
52
+ return path.join(baseDir, `pm-web-${String(port)}.pid`);
53
+ }
54
+ /** Shape a /healthz probe outcome into a stable status result object. */
55
+ export function shapeStatusResult(input) {
56
+ const portNum = Number(input.port);
57
+ const body = (input.body ?? null);
58
+ const version = body && typeof body["version"] === "string" ? body["version"] : null;
59
+ const result = {
60
+ status: input.reachable ? "up" : "down",
61
+ port: portNum,
62
+ reachable: input.reachable,
63
+ url: `http://localhost:${portNum}/healthz`,
64
+ version,
65
+ healthz: input.body ?? null,
66
+ };
67
+ if (input.error)
68
+ result.error = input.error;
69
+ return result;
70
+ }
71
+ // ---------------------------------------------------------------------------
72
+ // Runtime helpers
73
+ // ---------------------------------------------------------------------------
74
+ function runtimeDependenciesInstalled() {
13
75
  const expressPackage = path.join(packageRoot, "node_modules", "express", "package.json");
14
- if (fs.existsSync(expressPackage))
76
+ return fs.existsSync(expressPackage);
77
+ }
78
+ function ensureRuntimeDependencies() {
79
+ if (runtimeDependenciesInstalled())
15
80
  return;
16
81
  console.error("Installing pm-web runtime dependencies...");
17
82
  const install = spawnSync("npm", ["install", "--omit=dev"], {
@@ -22,7 +87,77 @@ function ensureRuntimeDependencies() {
22
87
  if (install.error)
23
88
  throw install.error;
24
89
  if (install.status !== 0) {
25
- throw new Error(`npm install --omit=dev failed with exit code ${install.status ?? "unknown"}`);
90
+ throw new CommandError(`npm install --omit=dev failed with exit code ${install.status ?? "unknown"}`);
91
+ }
92
+ }
93
+ /** HTTP GET /healthz with a short timeout. Never throws; returns a probe result. */
94
+ async function probeHealthz(port) {
95
+ const url = `http://localhost:${port}/healthz`;
96
+ const controller = new AbortController();
97
+ const timer = setTimeout(() => controller.abort(), 1500);
98
+ try {
99
+ const res = await fetch(url, { signal: controller.signal });
100
+ let body = null;
101
+ try {
102
+ body = await res.json();
103
+ }
104
+ catch {
105
+ body = null;
106
+ }
107
+ return { reachable: res.ok, body };
108
+ }
109
+ catch (err) {
110
+ return { reachable: false, error: err instanceof Error ? err.message : String(err) };
111
+ }
112
+ finally {
113
+ clearTimeout(timer);
114
+ }
115
+ }
116
+ /** Check whether a TCP port is free to bind locally. Never throws. */
117
+ function isPortFree(port) {
118
+ return new Promise((resolve) => {
119
+ const tester = net
120
+ .createServer()
121
+ .once("error", () => resolve(false))
122
+ .once("listening", () => {
123
+ tester.close(() => resolve(true));
124
+ })
125
+ .listen(port, "127.0.0.1");
126
+ });
127
+ }
128
+ function pmOnPath() {
129
+ const probe = spawnSync("pm", ["--version"], { stdio: "ignore" });
130
+ return !probe.error && probe.status === 0;
131
+ }
132
+ function workspaceInitialized(pmRoot) {
133
+ if (!pmRoot)
134
+ return false;
135
+ // A pm workspace has a settings.json plus item-type directories under the
136
+ // pm root (e.g. tasks/, features/). Match on the settings file or a known
137
+ // item-type dir so detection is robust across pm versions.
138
+ if (!fs.existsSync(pmRoot))
139
+ return false;
140
+ return (fs.existsSync(path.join(pmRoot, "settings.json")) ||
141
+ fs.existsSync(path.join(pmRoot, "schema")) ||
142
+ fs.existsSync(path.join(pmRoot, "tasks")));
143
+ }
144
+ function readPackageVersion() {
145
+ try {
146
+ const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
147
+ return pkg.version ?? "unknown";
148
+ }
149
+ catch {
150
+ return "unknown";
151
+ }
152
+ }
153
+ function processAlive(pid) {
154
+ try {
155
+ process.kill(pid, 0);
156
+ return true;
157
+ }
158
+ catch (err) {
159
+ // ESRCH = no such process; EPERM = exists but not ours (treat as alive)
160
+ return err.code === "EPERM";
26
161
  }
27
162
  }
28
163
  export default defineExtension({
@@ -30,7 +165,7 @@ export default defineExtension({
30
165
  version: "1.0.0",
31
166
  activate(api) {
32
167
  // -----------------------------------------------------------------------
33
- // Command: pm web [--port <port>]
168
+ // Command: pm web [--port <port>] [--detach]
34
169
  // -----------------------------------------------------------------------
35
170
  api.registerCommand({
36
171
  name: "web",
@@ -39,13 +174,14 @@ export default defineExtension({
39
174
  examples: [
40
175
  "pm web",
41
176
  "pm web --port 8080",
177
+ "pm web --detach",
42
178
  ],
43
179
  flags: [
44
180
  { long: "--port", value_name: "port", description: "Port to listen on (default: 4000 or PORT env var)" },
45
181
  { long: "--detach", description: "Run the server in the background" },
46
182
  ],
47
183
  async run(ctx) {
48
- const port = ctx.options["port"] ?? process.env["PORT"] ?? "4000";
184
+ const port = resolvePort(ctx.options);
49
185
  const detach = Boolean(ctx.options["detach"]);
50
186
  const serverPath = path.resolve(__dirname, "server.js");
51
187
  ensureRuntimeDependencies();
@@ -60,6 +196,15 @@ export default defineExtension({
60
196
  stdio: "ignore",
61
197
  });
62
198
  serverProcess.unref();
199
+ // Track the detached PID so `pm web stop` can terminate it.
200
+ if (serverProcess.pid) {
201
+ try {
202
+ fs.writeFileSync(pidfilePath(port), String(serverProcess.pid), "utf8");
203
+ }
204
+ catch (err) {
205
+ console.error(`Warning: could not write pidfile: ${err instanceof Error ? err.message : String(err)}`);
206
+ }
207
+ }
63
208
  console.error(`pm-web started on port ${port} (PID ${serverProcess.pid})`);
64
209
  return { status: "started", port: Number(port), pid: serverProcess.pid };
65
210
  }
@@ -77,12 +222,212 @@ export default defineExtension({
77
222
  resolve();
78
223
  return;
79
224
  }
80
- reject(new Error(`pm-web exited with code ${code ?? `signal ${signal}`}`));
225
+ reject(new CommandError(`pm-web exited with code ${code ?? `signal ${signal}`}`));
81
226
  });
82
227
  });
83
228
  return { status: "stopped", port: Number(port) };
84
229
  },
85
230
  });
231
+ // -----------------------------------------------------------------------
232
+ // Command: pm web status [--port <port>] [--json]
233
+ // -----------------------------------------------------------------------
234
+ api.registerCommand({
235
+ name: "web status",
236
+ description: "Report whether a pm-web server is reachable on the configured port.",
237
+ intent: "check pm-web server status",
238
+ examples: ["pm web status", "pm web status --port 8080 --json"],
239
+ flags: [
240
+ { long: "--port", value_name: "port", description: "Port to probe (default: 4000 or PORT env var)" },
241
+ { long: "--json", description: "Emit machine-readable JSON" },
242
+ ],
243
+ async run(ctx) {
244
+ const port = resolvePort(ctx.options);
245
+ const json = Boolean(ctx.options["json"]);
246
+ const probe = await probeHealthz(port);
247
+ const result = shapeStatusResult({
248
+ port,
249
+ reachable: probe.reachable,
250
+ body: probe.body,
251
+ error: probe.error,
252
+ });
253
+ if (json) {
254
+ console.log(JSON.stringify(result, null, 2));
255
+ }
256
+ else if (result.status === "up") {
257
+ console.log(`pm-web is UP on port ${result.port}` +
258
+ (result.version ? ` (version ${result.version})` : ""));
259
+ }
260
+ else {
261
+ console.log(`pm-web is DOWN on port ${result.port}`);
262
+ }
263
+ return result;
264
+ },
265
+ });
266
+ // -----------------------------------------------------------------------
267
+ // Command: pm web stop [--port <port>] [--json]
268
+ // -----------------------------------------------------------------------
269
+ api.registerCommand({
270
+ name: "web stop",
271
+ description: "Stop a pm-web server previously started with --detach.",
272
+ intent: "stop a detached pm-web server",
273
+ examples: ["pm web stop", "pm web stop --port 8080"],
274
+ flags: [
275
+ { long: "--port", value_name: "port", description: "Port of the detached server (default: 4000 or PORT env var)" },
276
+ { long: "--json", description: "Emit machine-readable JSON" },
277
+ ],
278
+ async run(ctx) {
279
+ const port = resolvePort(ctx.options);
280
+ const json = Boolean(ctx.options["json"]);
281
+ const pidfile = pidfilePath(port);
282
+ const emit = (result) => {
283
+ if (json) {
284
+ console.log(JSON.stringify(result, null, 2));
285
+ }
286
+ else {
287
+ console.log(String(result["message"]));
288
+ }
289
+ return result;
290
+ };
291
+ // Prefer the in-process handle if we started it this session.
292
+ let pid = null;
293
+ if (serverProcess?.pid) {
294
+ pid = serverProcess.pid;
295
+ }
296
+ else if (fs.existsSync(pidfile)) {
297
+ const raw = fs.readFileSync(pidfile, "utf8").trim();
298
+ const parsed = Number.parseInt(raw, 10);
299
+ if (Number.isInteger(parsed) && parsed > 0)
300
+ pid = parsed;
301
+ }
302
+ if (pid === null) {
303
+ return emit({
304
+ status: "not_running",
305
+ port: Number(port),
306
+ message: `pm-web is not running (no pidfile for port ${port}).`,
307
+ });
308
+ }
309
+ if (!processAlive(pid)) {
310
+ // Stale pidfile — clean it up and report gracefully.
311
+ if (fs.existsSync(pidfile))
312
+ fs.rmSync(pidfile, { force: true });
313
+ return emit({
314
+ status: "not_running",
315
+ port: Number(port),
316
+ pid,
317
+ message: `pm-web process (PID ${pid}) is not running; cleared stale pidfile.`,
318
+ });
319
+ }
320
+ try {
321
+ process.kill(pid, "SIGTERM");
322
+ }
323
+ catch (err) {
324
+ throw new CommandError(`Failed to stop pm-web (PID ${pid}): ${err instanceof Error ? err.message : String(err)}`);
325
+ }
326
+ if (fs.existsSync(pidfile))
327
+ fs.rmSync(pidfile, { force: true });
328
+ if (serverProcess?.pid === pid)
329
+ serverProcess = null;
330
+ return emit({
331
+ status: "stopped",
332
+ port: Number(port),
333
+ pid,
334
+ message: `Stopped pm-web (PID ${pid}) on port ${port}.`,
335
+ });
336
+ },
337
+ });
338
+ // -----------------------------------------------------------------------
339
+ // Command: pm web doctor [--port <port>] [--json]
340
+ // -----------------------------------------------------------------------
341
+ api.registerCommand({
342
+ name: "web doctor",
343
+ description: "Preflight diagnostics for running pm-web (Node, deps, port, pm, workspace).",
344
+ intent: "diagnose pm-web preflight readiness",
345
+ examples: ["pm web doctor", "pm web doctor --json"],
346
+ flags: [
347
+ { long: "--port", value_name: "port", description: "Port to check availability for (default: 4000 or PORT env var)" },
348
+ { long: "--json", description: "Emit machine-readable JSON" },
349
+ ],
350
+ async run(ctx) {
351
+ const port = resolvePort(ctx.options);
352
+ const json = Boolean(ctx.options["json"]);
353
+ const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
354
+ const depsInstalled = runtimeDependenciesInstalled();
355
+ const portFree = await isPortFree(Number(port));
356
+ const pmAvailable = pmOnPath();
357
+ const wsInit = workspaceInitialized(ctx.pm_root);
358
+ const checks = [
359
+ {
360
+ name: "node_version",
361
+ ok: nodeMajor >= 20,
362
+ detail: `Node ${process.versions.node} (requires >= 20)`,
363
+ },
364
+ {
365
+ name: "runtime_dependencies",
366
+ ok: depsInstalled,
367
+ detail: depsInstalled
368
+ ? "express and runtime deps installed"
369
+ : "runtime deps missing (will auto-install on `pm web`)",
370
+ },
371
+ {
372
+ name: "port_available",
373
+ ok: portFree,
374
+ detail: portFree
375
+ ? `port ${port} is free`
376
+ : `port ${port} is in use (a server may already be running)`,
377
+ },
378
+ {
379
+ name: "pm_on_path",
380
+ ok: pmAvailable,
381
+ detail: pmAvailable ? "pm CLI is on PATH" : "pm CLI not found on PATH",
382
+ },
383
+ {
384
+ name: "workspace_initialized",
385
+ ok: wsInit,
386
+ detail: wsInit
387
+ ? `pm workspace found at ${ctx.pm_root}`
388
+ : `no pm workspace at ${ctx.pm_root || "(unset)"} — run \`pm init\``,
389
+ },
390
+ ];
391
+ // port_available is informational (in-use can be a healthy running server),
392
+ // so it does not gate overall readiness.
393
+ const ok = checks
394
+ .filter((c) => c.name !== "port_available")
395
+ .every((c) => c.ok);
396
+ const result = {
397
+ ok,
398
+ version: readPackageVersion(),
399
+ node: process.versions.node,
400
+ port: Number(port),
401
+ checks,
402
+ };
403
+ if (json) {
404
+ console.log(JSON.stringify(result, null, 2));
405
+ }
406
+ else {
407
+ console.log(`pm-web doctor — overall: ${ok ? "OK" : "ISSUES"}`);
408
+ for (const c of checks) {
409
+ console.log(` [${c.ok ? "ok" : "!!"}] ${c.name}: ${c.detail}`);
410
+ }
411
+ }
412
+ return result;
413
+ },
414
+ });
415
+ // -----------------------------------------------------------------------
416
+ // services capability — intentionally NOT registered.
417
+ //
418
+ // The SDK's `registerService(name, override)` only accepts one of the eight
419
+ // fixed CORE service names (output_format, error_format, help_format,
420
+ // lock_acquire, lock_release, history_append, item_store_write,
421
+ // item_store_delete) and OVERRIDES that core service on every pm command.
422
+ // There is no API to register a new "pm-web lifecycle" service, and
423
+ // overriding a core service would alter/replace core output for unrelated
424
+ // commands. The pm-web server lifecycle is surfaced safely via the
425
+ // web/status/stop/doctor commands instead. The guarded block below
426
+ // documents the deliberate no-op; the "services" capability is therefore
427
+ // NOT declared in manifest.json.
428
+ if (typeof api.registerService === "function") {
429
+ // Deliberately register no service overrides — see comment above.
430
+ }
86
431
  },
87
432
  });
88
433
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/routes/pm.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Router } from "express";
2
2
  import { requireAuth } from "../middleware/auth.js";
3
3
  import { ensureGraphExtension, runPm } from "../services/pm-runner.js";
4
+ import { boardColumns, filterItemsByQuery } from "../board.js";
4
5
  import { verifyProjectAccess } from "./projects.js";
5
6
  import { addSSEClient, broadcastProjectEvent, setupSSEHeaders, broadcastPresence, updateClientView, getProjectPresence } from "../services/sse.js";
6
7
  import { v4 as uuidv4 } from "uuid";
@@ -391,6 +392,45 @@ router.get("/list-all", async (req, res) => {
391
392
  const result = runPm({ args, userId: project.ownerUserId, slug: project.slug, jsonOutput: true });
392
393
  res.json(result.ok ? (result.parsed || {}) : { error: result.stderr, items: [] });
393
394
  });
395
+ // GET /api/projects/:projectId/pm/board
396
+ // Kanban board: items grouped into columns by the workspace's runtime statuses
397
+ // (read live from `pm contracts`) so the board matches the installed CLI.
398
+ router.get("/board", async (req, res) => {
399
+ const project = await verifyProject(req.user.userId, req.params["projectId"]);
400
+ if (!project) {
401
+ res.status(404).json({ error: "Project not found" });
402
+ return;
403
+ }
404
+ const contracts = runPm({ args: ["contracts", "--json"], userId: project.ownerUserId, slug: project.slug, jsonOutput: true });
405
+ const rt = contracts.ok && contracts.parsed
406
+ ? contracts.parsed["runtime_schema"]
407
+ : undefined;
408
+ const statuses = Array.isArray(rt?.["statuses"]) ? rt["statuses"] : [];
409
+ const listed = runPm({ args: ["list-all", "--json"], userId: project.ownerUserId, slug: project.slug, jsonOutput: true });
410
+ if (!listed.ok) {
411
+ res.json({ error: listed.stderr, columns: [] });
412
+ return;
413
+ }
414
+ const items = itemsFromListAll(listed.parsed);
415
+ res.json({ columns: boardColumns(items, statuses), statuses, count: items.length });
416
+ });
417
+ // GET /api/projects/:projectId/pm/search?q=...
418
+ // Full-text search over id/title/tags/body via a single list-all read.
419
+ router.get("/search", async (req, res) => {
420
+ const project = await verifyProject(req.user.userId, req.params["projectId"]);
421
+ if (!project) {
422
+ res.status(404).json({ error: "Project not found" });
423
+ return;
424
+ }
425
+ const query = String(req.query["q"] ?? "");
426
+ const listed = runPm({ args: ["list-all", "--json", "--include-body"], userId: project.ownerUserId, slug: project.slug, jsonOutput: true });
427
+ if (!listed.ok) {
428
+ res.json({ error: listed.stderr, items: [] });
429
+ return;
430
+ }
431
+ const items = filterItemsByQuery(itemsFromListAll(listed.parsed), query);
432
+ res.json({ query, items, count: items.length });
433
+ });
394
434
  // POST /api/projects/:projectId/pm/create
395
435
  router.post("/create", async (req, res) => {
396
436
  const project = await verifyProject(req.user.userId, req.params["projectId"]);