peaks-cli 1.3.4 → 1.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/src/cli/commands/hook-handle.d.ts +2 -2
  2. package/dist/src/cli/commands/hook-handle.js +5 -10
  3. package/dist/src/cli/commands/hooks-commands.js +44 -29
  4. package/dist/src/cli/commands/project-commands.js +7 -1
  5. package/dist/src/cli/commands/workspace-commands.js +1 -2
  6. package/dist/src/cli/program.js +3 -4
  7. package/dist/src/services/dashboard/project-dashboard-service.d.ts +0 -7
  8. package/dist/src/services/dashboard/project-dashboard-service.js +1 -8
  9. package/dist/src/services/dispatch/sub-agent-dispatcher.d.ts +45 -40
  10. package/dist/src/services/dispatch/sub-agent-dispatcher.js +25 -20
  11. package/dist/src/services/ide/adapters/claude-code-adapter.js +0 -3
  12. package/dist/src/services/ide/adapters/trae-adapter.js +2 -17
  13. package/dist/src/services/ide/ide-types.d.ts +1 -18
  14. package/dist/src/services/progress/progress-service.d.ts +23 -103
  15. package/dist/src/services/progress/progress-service.js +24 -137
  16. package/dist/src/services/scan/file-size-scan.d.ts +4 -0
  17. package/dist/src/services/scan/file-size-scan.js +32 -3
  18. package/dist/src/services/skills/hooks-settings-service.d.ts +57 -5
  19. package/dist/src/services/skills/hooks-settings-service.js +153 -28
  20. package/dist/src/shared/incrementing-number.d.ts +0 -8
  21. package/dist/src/shared/incrementing-number.js +11 -1
  22. package/dist/src/shared/version.d.ts +1 -1
  23. package/dist/src/shared/version.js +1 -1
  24. package/package.json +1 -1
  25. package/skills/peaks-prd/SKILL.md +16 -16
  26. package/skills/peaks-prd/references/workflow.md +4 -4
  27. package/skills/peaks-qa/SKILL.md +25 -32
  28. package/skills/peaks-qa/references/qa-fanout-contract.md +6 -6
  29. package/skills/peaks-qa/references/regression-gates.md +1 -1
  30. package/skills/peaks-rd/SKILL.md +8 -21
  31. package/skills/peaks-rd/references/{openspec-mcp-cli.md → openspec-cli.md} +11 -14
  32. package/skills/peaks-solo/SKILL.md +1 -1
  33. package/skills/peaks-solo/references/a2a-artifact-mapping.md +1 -1
  34. package/skills/peaks-solo/references/browser-workflow.md +49 -38
  35. package/skills/peaks-solo/references/external-skill-invocation.md +9 -7
  36. package/skills/peaks-solo/references/{openspec-mcp-workflow.md → openspec-workflow.md} +5 -20
  37. package/skills/peaks-solo/references/runbook.md +21 -21
  38. package/skills/peaks-solo/references/sub-agent-dispatch.md +16 -35
  39. package/skills/peaks-solo/references/swarm-dispatch-contract.md +9 -9
  40. package/skills/peaks-ui/SKILL.md +22 -24
  41. package/skills/peaks-ui/references/workflow.md +2 -2
  42. package/dist/src/cli/commands/mcp-commands.d.ts +0 -3
  43. package/dist/src/cli/commands/mcp-commands.js +0 -144
  44. package/dist/src/cli/commands/progress-close-kill.d.ts +0 -51
  45. package/dist/src/cli/commands/progress-close-kill.js +0 -152
  46. package/dist/src/cli/commands/progress-commands.d.ts +0 -3
  47. package/dist/src/cli/commands/progress-commands.js +0 -379
  48. package/dist/src/cli/commands/progress-start-spawn.d.ts +0 -59
  49. package/dist/src/cli/commands/progress-start-spawn.js +0 -140
  50. package/dist/src/cli/commands/progress-watch-render.d.ts +0 -80
  51. package/dist/src/cli/commands/progress-watch-render.js +0 -308
  52. package/dist/src/services/mcp/mcp-apply-service.d.ts +0 -31
  53. package/dist/src/services/mcp/mcp-apply-service.js +0 -112
  54. package/dist/src/services/mcp/mcp-call-service.d.ts +0 -17
  55. package/dist/src/services/mcp/mcp-call-service.js +0 -34
  56. package/dist/src/services/mcp/mcp-client-service.d.ts +0 -14
  57. package/dist/src/services/mcp/mcp-client-service.js +0 -49
  58. package/dist/src/services/mcp/mcp-install-registry.d.ts +0 -11
  59. package/dist/src/services/mcp/mcp-install-registry.js +0 -38
  60. package/dist/src/services/mcp/mcp-plan-service.d.ts +0 -29
  61. package/dist/src/services/mcp/mcp-plan-service.js +0 -109
  62. package/dist/src/services/mcp/mcp-protocol.d.ts +0 -24
  63. package/dist/src/services/mcp/mcp-protocol.js +0 -41
  64. package/dist/src/services/mcp/mcp-scan-service.d.ts +0 -8
  65. package/dist/src/services/mcp/mcp-scan-service.js +0 -214
  66. package/dist/src/services/mcp/mcp-stdio-transport.d.ts +0 -10
  67. package/dist/src/services/mcp/mcp-stdio-transport.js +0 -50
  68. package/dist/src/services/mcp/mcp-types.d.ts +0 -31
  69. package/dist/src/services/mcp/mcp-types.js +0 -1
@@ -16,7 +16,7 @@ Return a compact JSON envelope — do not write prose.
16
16
  ## Hard prohibitions
17
17
  - Do NOT call Skill(skill="..."). You are the role.
18
18
  - Do NOT call `peaks skill presence:set` — the main loop owns .peaks/.active-skill.json.
19
- If you need to record state, write to .peaks/<session-id>/system/sub-agent-<role>.json.
19
+ If you need to record state, write to .peaks/_runtime/<session-id>/system/sub-agent-<role>.json.
20
20
  - Do NOT commit, push, install hooks, or apply settings.json mutations.
21
21
  - Do NOT ask the user interactive questions. If you need clarification, return
22
22
  {"status":"blocked","blockedReason":"<text>"} and let the main loop handle it.
@@ -47,14 +47,14 @@ Steps:
47
47
  3. Read <project-scan-path> for component library / CSS framework.
48
48
  4. Run the prototype fidelity gate: Figma file? PRD visuals? Headed browser?
49
49
  5. Write TWO artefacts:
50
- - .peaks/<sid>/ui/design-draft.md
51
- - .peaks/<sid>/ui/requests/<rid>.md
50
+ - .peaks/_runtime/<sid>/ui/design-draft.md
51
+ - .peaks/_runtime/<sid>/ui/requests/<rid>.md
52
52
  6. Return:
53
53
  {
54
54
  "role": "ui",
55
55
  "rid": "<rid>",
56
56
  "status": "ok" | "blocked" | "skipped",
57
- "artefacts": [".peaks/<sid>/ui/design-draft.md", ".peaks/<sid>/ui/requests/<rid>.md"],
57
+ "artefacts": [".peaks/_runtime/<sid>/ui/design-draft.md", ".peaks/_runtime/<sid>/ui/requests/<rid>.md"],
58
58
  "warnings": [],
59
59
  "blockedReason": null
60
60
  }
@@ -80,15 +80,15 @@ Steps:
80
80
  into rd/project-scan.md.
81
81
  5. Read <existing-system-path> if archetype is legacy-*.
82
82
  6. Write the type-appropriate planning artefact:
83
- - feature | refactor → .peaks/<sid>/rd/tech-doc.md
84
- - bugfix → .peaks/<sid>/rd/bug-analysis.md
83
+ - feature | refactor → .peaks/_runtime/<sid>/rd/tech-doc.md
84
+ - bugfix → .peaks/_runtime/<sid>/rd/bug-analysis.md
85
85
  - config | docs | chore → no planning artefact required. Return skipped.
86
86
  7. Return:
87
87
  {
88
88
  "role": "rd-planning",
89
89
  "rid": "<rid>",
90
90
  "status": "ok" | "blocked" | "skipped",
91
- "artefacts": [".peaks/<sid>/rd/tech-doc.md"], // or [] when skipped
91
+ "artefacts": [".peaks/_runtime/<sid>/rd/tech-doc.md"], // or [] when skipped
92
92
  "warnings": [],
93
93
  "blockedReason": null
94
94
  }
@@ -106,14 +106,14 @@ Steps:
106
106
  2. peaks request show <rid> --role prd --project <repo> --json
107
107
  3. peaks request show <rid> --role rd --project <repo> --json
108
108
  4. Read <project-scan-path>.
109
- 5. Write .peaks/<sid>/qa/test-cases/<rid>.md with test cases linked to PRD
109
+ 5. Write .peaks/_runtime/<sid>/qa/test-cases/<rid>.md with test cases linked to PRD
110
110
  acceptance items (use **Acceptance:** A1, A2 style).
111
111
  6. Return:
112
112
  {
113
113
  "role": "qa-test-cases",
114
114
  "rid": "<rid>",
115
115
  "status": "ok" | "blocked" | "skipped",
116
- "artefacts": [".peaks/<sid>/qa/test-cases/<rid>.md"],
116
+ "artefacts": [".peaks/_runtime/<sid>/qa/test-cases/<rid>.md"],
117
117
  "warnings": [],
118
118
  "blockedReason": null
119
119
  }
@@ -13,7 +13,7 @@ UI's headed-browser work (visual inspection, regression seed capture, Figma / li
13
13
 
14
14
  ### Contract 1 — Inspection screenshots must land under .peaks/<sid>/qa/screenshots/
15
15
 
16
- Every Playwright screenshot tool call (via `peaks mcp call --capability playwright-mcp.browser-validation --tool browser_take_screenshot --args-json '<args>' --json`) **MUST** pass `filename` (in the args object) inside `.peaks/<session-id>/qa/screenshots/`, named after the inspection target (e.g. `home-after-cta.png`, `empty-state-v2.png`). Do not let Playwright fall back to the project root. After every batch, run:
16
+ Every Playwright screenshot tool call (the LLM invokes `browser_take_screenshot` directly when the Playwright MCP is present in its tool list) **MUST** pass `filename` (in the args object) inside `.peaks/<session-id>/qa/screenshots/`, named after the inspection target (e.g. `home-after-cta.png`, `empty-state-v2.png`). Do not let Playwright fall back to the project root. After every batch, run:
17
17
 
18
18
  ```bash
19
19
  ls .peaks/<sid>/qa/screenshots/*.png 2>&1
@@ -61,7 +61,7 @@ What the sub-agent **MUST** still do:
61
61
  - Do NOT call `Skill(skill="...")`.
62
62
  - Do NOT call `peaks skill presence:set` — Solo owns the active-skill file.
63
63
  - Do NOT modify application code. UI is design-direction only; the actual frontend code is written in the RD implementation phase.
64
- - Do NOT install MCP servers. If `peaks mcp list` shows playwright-mcp missing and the headed browser is required, return `{"status":"blocked","blockedReason":"playwright-mcp-unavailable"}` and let Solo escalate to the user.
64
+ - Do NOT install MCP servers. If the LLM tool list does not include the Playwright MCP and the headed browser is required, return `{"status":"blocked","blockedReason":"playwright-mcp-unavailable"}` and let Solo escalate to the user. (peaks-cli no longer manages MCP install — the user runs `claude mcp add playwright -- npx @playwright/mcp@latest` themselves in Claude Code, or the IDE-specific install command otherwise.)
65
65
  - Do NOT commit, push, install hooks, or apply settings.json mutations.
66
66
  - Do NOT ask the user interactive questions. If you need clarification, return `{"status":"blocked","blockedReason":"<text>"}`.
67
67
 
@@ -129,14 +129,13 @@ peaks request init --role ui --id <request-id> --project <repo> --apply --json
129
129
  peaks request show <request-id> --role prd --project <repo> --json # read linked PRD scope
130
130
 
131
131
  # 2. ensure Playwright MCP is available for the visible browser check
132
- peaks mcp list --json
133
- # if playwright-mcp.browser-validation is NOT in the list:
134
- peaks mcp plan --capability playwright-mcp.browser-validation --json
135
- peaks mcp apply --capability playwright-mcp.browser-validation --yes --json
136
- # if apply fails or user denies installation:
137
- # mark browser gate as blocked with reason "playwright-mcp-unavailable"
138
- # NEVER silently downgrade to screenshots-only, manual steps, or other tools
139
- # → NEVER route through chrome-devtools-mcp as a browser-launch substitute (it cannot launch)
132
+ # Slice #016: peaks-cli no longer manages MCP install. The LLM checks
133
+ # its own tool list for any Playwright MCP entry in the LLM tool list. If absent, the
134
+ # LLM tells the user the install command (`claude mcp add playwright
135
+ # -- npx @playwright/mcp@latest` in Claude Code) and reports the gate
136
+ # as blocked. Do NOT silently downgrade to screenshots-only, manual
137
+ # steps, or other tools. Do NOT route through chrome-devtools-mcp as a
138
+ # browser-launch substitute (it cannot launch a browser of its own).
140
139
 
141
140
  # 3. read project-scan for component library and CSS framework context
142
141
  # check .peaks/<session-id>/rd/project-scan.md (blocking if missing for existing projects)
@@ -151,8 +150,8 @@ peaks mcp apply --capability playwright-mcp.browser-validation --yes --json
151
150
  # See "Prototype fidelity gate" section for the full decision tree.
152
151
 
153
152
  # 5. drive the running page or prototype through Claude Code MCP tools
154
- # (these are not Peaks-Cli CLI commands; they are invoked by the host MCP runtime)
155
- # peaks mcp call --capability playwright-mcp.browser-validation --tool browser_navigate --args-json '{"url":"<url>"}' --json
153
+ # (the LLM invokes these directly from its tool list no peaks-cli envelope)
154
+ # browser_navigate --args '{"url":"<url>"}'
156
155
  # → URL (after allow-list check), launches headed browser
157
156
  #
158
157
  # LOGIN GATE (MANDATORY checkpoint):
@@ -162,19 +161,18 @@ peaks mcp apply --capability playwright-mcp.browser-validation --yes --json
162
161
  # If user does not confirm within reasonable time → pause and ask.
163
162
  # Only after user confirmation, continue to:
164
163
  #
165
- # peaks mcp call --capability playwright-mcp.browser-validation --tool browser_take_screenshot --args-json '{"filename":"<abs-path>"}' --json
164
+ # browser_take_screenshot --args '{"filename":"<abs-path>"}'
166
165
  # → visible-browser confirmation
167
- # peaks mcp call --capability playwright-mcp.browser-validation --tool browser_snapshot --args-json '{}' --json
166
+ # browser_snapshot --args '{}'
168
167
  # → accessibility tree for regression seeds
169
- # peaks mcp call --capability playwright-mcp.browser-validation --tool browser_console_messages --args-json '{}' --json
168
+ # browser_console_messages --args '{}'
170
169
  # → console errors
171
- # peaks mcp call --capability playwright-mcp.browser-validation --tool browser_network_requests --args-json '{}' --json
170
+ # browser_network_requests --args '{}'
172
171
  # → failed network
173
- # peaks mcp call --capability playwright-mcp.browser-validation --tool browser_close --args-json '{}' --json
172
+ # browser_close --args '{}'
174
173
  # → end the session cleanly
175
- # The skill body NEVER bakes in the `mcp__playwright__` prefix; the LLM's runtime
176
- # resolves the tool name from the registered server. The capability id
177
- # `playwright-mcp.browser-validation` is the contract; the registry is the source of truth.
174
+ # The skill body NEVER bakes in the the Playwright MCP tools prefix; the LLM's runtime
175
+ # resolves the tool name from the registered server.
178
176
 
179
177
  # 5. write design-draft artifact to .peaks/<session-id>/ui/design-draft.md
180
178
 
@@ -238,7 +236,7 @@ Use gstack as a concrete design-review workflow reference for the `Plan → Revi
238
236
  - map browser walkthrough concepts to UI regression seeds when runtime validation is approved;
239
237
  - keep accessibility, performance, and product-specific visual direction as Peaks-Cli UI acceptance inputs.
240
238
 
241
- For frontend work, especially full-auto mode, use Playwright MCP to inspect the running page or prototype before accepting the UI direction. The skill body never bakes in the `mcp__playwright__` prefix; it uses `peaks mcp call --capability playwright-mcp.browser-validation --tool <name> --args-json '<args>' --json` for every browser operation (browser_navigate / browser_snapshot / browser_take_screenshot / browser_console_messages / browser_network_requests / browser_close). Playwright MCP launches a headed browser on demand; if `peaks mcp list --json` does not include `playwright`, install it through `peaks mcp plan --capability playwright-mcp.browser-validation --json` then `peaks mcp apply --capability playwright-mcp.browser-validation --yes --json` before attempting to inspect. (Chrome DevTools MCP is a secondary surface that connects to an already-running Chrome via `--remote-debugging-port=9222`; it does NOT launch a browser on its own.) If login, CAPTCHA, SSO, or MFA appears, the visible browser is already open; wait for the user to complete login and explicitly confirm completion before continuing. Capture only sanitized visible regressions, weak hierarchy, generic template patterns, console errors, and interaction problems as UI feedback that should return to design/RD before handing off to QA; do not retain login URLs, cookies, headers, tokens, storage state, browser traces, or screenshots/logs containing PII or SSO/MFA material. Canonical browser workflow: `peaks-solo/references/browser-workflow.md`.
239
+ For frontend work, especially full-auto mode, use the Playwright MCP to inspect the running page or prototype before accepting the UI direction. The LLM checks its own tool list for any Playwright MCP entry in the LLM tool list; if present, it invokes the tools by name directly (browser_navigate / browser_snapshot / browser_take_screenshot / browser_console_messages / browser_network_requests / browser_close) — there is no peaks-cli envelope. Playwright MCP launches a headed browser on demand; if the tool list is empty, the user installs via `claude mcp add playwright -- npx @playwright/mcp@latest` (Claude Code) or the IDE's own MCP install path. (Chrome DevTools MCP is a secondary surface that connects to an already-running Chrome via `--remote-debugging-port=9222`; it does NOT launch a browser on its own.) If login, CAPTCHA, SSO, or MFA appears, the visible browser is already open; wait for the user to complete login and explicitly confirm completion before continuing. Capture only sanitized visible regressions, weak hierarchy, generic template patterns, console errors, and interaction problems as UI feedback that should return to design/RD before handing off to QA; do not retain login URLs, cookies, headers, tokens, storage state, browser traces, or screenshots/logs containing PII or SSO/MFA material. Canonical browser workflow: `peaks-solo/references/browser-workflow.md`.
242
240
 
243
241
  ## Prototype fidelity gate (MANDATORY — check BEFORE any design work)
244
242
 
@@ -248,7 +246,7 @@ For frontend work, especially full-auto mode, use Playwright MCP to inspect the
248
246
 
249
247
  Check these sources in order:
250
248
 
251
- 1. **Figma design file** — If the PRD links to a Figma file, use `peaks mcp call --capability figma-context-mcp.design-context --tool get_figma_data --args-json '{"fileKey":"<key>"}' --json` to fetch the design (the skill body never bakes in the `mcp__Figma_AI_Bridge__` prefix; the prefix is owned by the LLM runtime, and `FIGMA_API_KEY` must be set in the env before `peaks mcp apply` — the plan envelope's `envCheck.missing` field is the source of truth). The Figma data IS the design. Replicate layout, spacing, colors, typography, and component choices exactly as specified.
249
+ 1. **Figma design file** — If the PRD links to a Figma file, the LLM checks its own tool list for any the Figma MCP entry. If present, it invokes `get_figma_data` (or similar) directly with `{"fileKey":"<key>"}`; `FIGMA_API_KEY` must be set in the user's env for the MCP to authenticate. The skill body never bakes in the Figma MCP prefix; the LLM's runtime owns the namespace. The Figma data IS the design. Replicate layout, spacing, colors, typography, and component choices exactly as specified.
252
250
  2. **PRD document screenshots** — If the PRD source (Feishu/Lark doc) contains screenshots or mockups, those ARE the visual target. Check `.peaks/<id>/prd/source/` for saved screenshots.
253
251
  3. **PRD visual descriptions** — If the PRD explicitly describes layout, component placement, or visual behavior, those descriptions are constraints, not suggestions.
254
252
  4. **Existing application pages** — If modifying an existing app, the existing visual language (component library, spacing patterns, color usage) is the fidelity baseline. New pages must match existing conventions.
@@ -355,8 +353,8 @@ Use `peaks capabilities --source access-repo --json` and `peaks capabilities --s
355
353
 
356
354
  - In full-auto frontend mode, prefer the `awesome-design-md` + `taste-skill`/`design-taste-frontend` combination before shadcn/ui or generic component-library output (capability discovery must confirm availability first).
357
355
  - shadcn/ui, React Bits, awesome-design-md, taste-skill, and ui-ux-pro-max-skill are UI references; do not treat unreviewed generated UI as finished design.
358
- - Chrome DevTools MCP and Agent Browser can support runtime UI inspection only after the user approves the app target. Install or update those MCP servers through `peaks mcp plan --capability <id> --json` then `peaks mcp apply --capability <id> --yes --json` rather than hand-editing settings; invoke their tools through `peaks mcp call --capability <id> --tool <name> --args-json '{...}' --json`.
359
- - Figma Context MCP and Penpot require user-authorized design access and must not persist tokens or private design data in project artifacts. Same `peaks mcp plan / apply / call` installation and invocation path applies.
356
+ - Chrome DevTools MCP and Agent Browser can support runtime UI inspection only after the user approves the app target. (Slice #016: peaks-cli no longer auto-installs these; the user runs the IDE-native MCP install command themselves, and the LLM invokes the tool by name from its tool list when present.)
357
+ - Figma Context MCP and Penpot require user-authorized design access and must not persist tokens or private design data in project artifacts. Same rule: the LLM's tool list is the source of truth; peaks-cli is not in the install path.
360
358
  - Check license, accessibility, and performance before translating external visual references into Peaks-Cli UI constraints.
361
359
 
362
360
  ## Boundaries
@@ -11,7 +11,7 @@ Use this path before generating or accepting frontend UI:
11
11
  3. Produce a concrete visual direction, not vague “clean modern” language.
12
12
  4. Reject generic AI UI tells: centered stock hero, uniform card grids, default shadcn/library styling, purple-blue gradients, three equal feature cards, generic placeholder copy, and static-only happy states.
13
13
  5. Require meaningful loading, empty, error, hover, focus, active, and responsive states.
14
- 6. Use Playwright MCP on the running page or prototype to inspect real browser output (install via `peaks mcp plan/apply --capability playwright-mcp.browser-validation --yes` if not yet present; open with `mcp__playwright__browser_navigate` / `navigate_page`, capture with `take_snapshot` and `take_screenshot`); visible browser confirmation is mandatory, and login/CAPTCHA/SSO/MFA requires waiting for explicit user confirmation before continuing.
14
+ 6. Use Playwright MCP on the running page or prototype to inspect real browser output (the LLM checks its tool list for `mcp__playwright__*`; if absent, the user installs via `claude mcp add playwright -- npx @playwright/mcp@latest` for Claude Code, or the IDE-native MCP install command otherwise — peaks-cli no longer auto-installs as of slice #016; open with `browser_navigate` / `navigate_page`, capture with `browser_snapshot` and `browser_take_screenshot`); visible browser confirmation is mandatory, and login/CAPTCHA/SSO/MFA requires waiting for explicit user confirmation before continuing.
15
15
  7. If the browser view looks generic, visually weak, broken, inaccessible, or has console/runtime errors, return to design/RD and iterate before handing off to QA.
16
16
 
17
17
  ## Outputs
@@ -21,7 +21,7 @@ Use this path before generating or accepting frontend UI:
21
21
  - visual direction with references;
22
22
  - design dials and rejected generic patterns;
23
23
  - interaction constraints;
24
- - Playwright MCP browser observations when frontend output exists (`mcp__playwright__browser_snapshot`, `take_screenshot`, `list_console_messages`, `list_network_requests`);
24
+ - Playwright MCP browser observations when frontend output exists (`browser_snapshot`, `browser_take_screenshot`, `browser_console_messages`, `browser_network_requests`);
25
25
  - UI regression seeds;
26
26
  - accessibility notes;
27
27
  - taste report.
@@ -1,3 +0,0 @@
1
- import { Command } from 'commander';
2
- import { type ProgramIO } from '../cli-helpers.js';
3
- export declare function registerMcpCommands(program: Command, io: ProgramIO): void;
@@ -1,144 +0,0 @@
1
- import { InvalidArgumentError } from 'commander';
2
- import { readFile } from 'node:fs/promises';
3
- import { scanMcpServers } from '../../services/mcp/mcp-scan-service.js';
4
- import { planMcpInstall } from '../../services/mcp/mcp-plan-service.js';
5
- import { applyMcpInstall, rollbackMcpInstall } from '../../services/mcp/mcp-apply-service.js';
6
- import { callMcpTool } from '../../services/mcp/mcp-call-service.js';
7
- import { createStdioTransportFromSpec } from '../../services/mcp/mcp-stdio-transport.js';
8
- import { fail, ok } from '../../shared/result.js';
9
- import { addJsonOption, failUnsupportedNonDryRun, getErrorMessage, printResult } from '../cli-helpers.js';
10
- function parsePositiveInteger(value) {
11
- if (!/^\d+$/.test(value)) {
12
- throw new InvalidArgumentError('must be a positive integer');
13
- }
14
- const parsed = Number(value);
15
- if (!Number.isSafeInteger(parsed) || parsed < 1) {
16
- throw new InvalidArgumentError('must be a positive integer');
17
- }
18
- return parsed;
19
- }
20
- async function resolveCallArgs(options) {
21
- if (options.argsJson !== undefined && options.args !== undefined) {
22
- throw new Error('Pass either --args-json or --args, not both');
23
- }
24
- const raw = options.argsJson !== undefined
25
- ? options.argsJson
26
- : options.args !== undefined ? await readFile(options.args, 'utf8') : '{}';
27
- const parsed = JSON.parse(raw);
28
- if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
29
- throw new Error('MCP tool arguments must be a JSON object');
30
- }
31
- return parsed;
32
- }
33
- export function registerMcpCommands(program, io) {
34
- const mcp = program.command('mcp').description('Manage Claude Code MCP servers');
35
- addJsonOption(mcp
36
- .command('list')
37
- .alias('scan')
38
- .description('Scan Claude Code settings for configured MCP servers')
39
- .option('--project <path>', 'project root to also scan project-level .claude/settings.json')).action(async (options) => {
40
- try {
41
- const report = await scanMcpServers(options.project !== undefined ? { projectRoot: options.project } : {});
42
- printResult(io, ok('mcp.list', report), options.json);
43
- }
44
- catch (error) {
45
- printResult(io, fail('mcp.list', 'MCP_LIST_FAILED', getErrorMessage(error), {}, ['Check Claude settings path and permissions before retrying']), options.json);
46
- process.exitCode = 1;
47
- }
48
- });
49
- addJsonOption(mcp
50
- .command('plan')
51
- .description('Plan an MCP server install diff for a capability (dry-run only)')
52
- .requiredOption('--capability <id>', 'capability id from the MCP install registry')
53
- .option('--project <path>', 'project root for scoped scan')
54
- .option('--dry-run', 'preview the install diff (always true)', true)
55
- .option('--no-dry-run', 'unsupported: peaks mcp plan never writes settings')).action(async (options) => {
56
- if (options.dryRun === false) {
57
- failUnsupportedNonDryRun(io, 'mcp.plan', options.json);
58
- return;
59
- }
60
- try {
61
- const planOptions = options.project !== undefined ? { projectRoot: options.project } : {};
62
- const plan = await planMcpInstall(options.capability, planOptions);
63
- if (plan.action === 'unknown-capability') {
64
- printResult(io, fail('mcp.plan', 'MCP_UNKNOWN_CAPABILITY', `No MCP install spec registered for capability ${options.capability}`, plan, plan.nextActions), options.json);
65
- process.exitCode = 1;
66
- return;
67
- }
68
- printResult(io, ok('mcp.plan', plan, [], plan.nextActions), options.json);
69
- }
70
- catch (error) {
71
- printResult(io, fail('mcp.plan', 'MCP_PLAN_FAILED', getErrorMessage(error), { capabilityId: options.capability }, ['Check Claude settings path and the capability id before retrying']), options.json);
72
- process.exitCode = 1;
73
- }
74
- });
75
- addJsonOption(mcp
76
- .command('apply')
77
- .description('Apply an MCP server install for a capability (writes .claude/settings.json with backup)')
78
- .requiredOption('--capability <id>', 'capability id from the MCP install registry')
79
- .option('--yes', 'confirm the write — required for any real side effect')
80
- .option('--claim', 'take ownership of an existing non-peaks-managed server entry')
81
- .option('--project <path>', 'project root for scoped scan')).action(async (options) => {
82
- if (options.yes !== true) {
83
- printResult(io, fail('mcp.apply', 'MCP_APPLY_REQUIRES_YES', 'Refusing to apply without --yes', { capabilityId: options.capability }, ['Re-run with --yes to confirm the write']), options.json);
84
- process.exitCode = 1;
85
- return;
86
- }
87
- try {
88
- const applyOptions = {};
89
- if (options.project !== undefined) {
90
- applyOptions.projectRoot = options.project;
91
- }
92
- if (options.claim === true) {
93
- applyOptions.claim = true;
94
- }
95
- const result = await applyMcpInstall(options.capability, applyOptions);
96
- printResult(io, ok('mcp.apply', result), options.json);
97
- }
98
- catch (error) {
99
- printResult(io, fail('mcp.apply', 'MCP_APPLY_FAILED', getErrorMessage(error), { capabilityId: options.capability }, ['Check the plan first with peaks mcp plan, then re-run apply']), options.json);
100
- process.exitCode = 1;
101
- }
102
- });
103
- addJsonOption(mcp
104
- .command('rollback')
105
- .description('Restore Claude Code settings.json from a peaks-managed MCP backup file')
106
- .requiredOption('--backup <path>', 'path to a previously created backup settings.json')).action(async (options) => {
107
- try {
108
- const result = await rollbackMcpInstall({ backupPath: options.backup });
109
- printResult(io, ok('mcp.rollback', result), options.json);
110
- }
111
- catch (error) {
112
- printResult(io, fail('mcp.rollback', 'MCP_ROLLBACK_FAILED', getErrorMessage(error), { backupPath: options.backup }, ['Verify the backup path and rerun']), options.json);
113
- process.exitCode = 1;
114
- }
115
- });
116
- addJsonOption(mcp
117
- .command('call')
118
- .description('Invoke a tool on an installed MCP server via stdio (spawns the server, calls tools/call, closes)')
119
- .requiredOption('--capability <id>', 'capability id from the MCP install registry')
120
- .requiredOption('--tool <name>', 'MCP tool name to invoke')
121
- .option('--args <path>', 'path to a JSON file describing the tool arguments object')
122
- .option('--args-json <jsonString>', 'inline JSON object describing the tool arguments')
123
- .option('--timeout <ms>', 'per-request timeout in milliseconds', parsePositiveInteger)).action(async (options) => {
124
- try {
125
- const args = await resolveCallArgs(options);
126
- const factory = createStdioTransportFromSpec;
127
- const callOptions = {
128
- capabilityId: options.capability,
129
- toolName: options.tool,
130
- args,
131
- transportFactory: factory
132
- };
133
- if (options.timeout !== undefined) {
134
- callOptions.timeoutMs = Number(options.timeout);
135
- }
136
- const result = await callMcpTool(callOptions);
137
- printResult(io, ok('mcp.call', result), options.json);
138
- }
139
- catch (error) {
140
- printResult(io, fail('mcp.call', 'MCP_CALL_FAILED', getErrorMessage(error), { capabilityId: options.capability, toolName: options.tool }, ['Check the capability id, tool name, args JSON, and required env vars before retrying']), options.json);
141
- process.exitCode = 1;
142
- }
143
- });
144
- }
@@ -1,51 +0,0 @@
1
- /**
2
- * Best-effort close of a spawned `peaks progress watch`
3
- * window. Used by `peaks progress close` (manual escape
4
- * hatch) and by the watch-side auto-exit when the sub-agent
5
- * hits a terminal phase.
6
- *
7
- * The close is best-effort by design: we never throw from
8
- * individual signals. One failed close primitive is a UX
9
- * paper cut, not a correctness bug — the caller still clears
10
- * the spawn record after this returns.
11
- *
12
- * Cross-platform strategy:
13
- *
14
- * - macOS: pkill the watch process by command pattern
15
- * (matches the project path, so we never close the
16
- * wrong window), then send AppleScript to Terminal.app
17
- * to close the window by `custom title`. Terminal.app
18
- * is the dominant macOS terminal, and `custom title` is
19
- * the only stable identifier we can target from outside
20
- * the running shell.
21
- * - Linux: pkill the watch process, then try `wmctrl -c
22
- * peaks-cli-progress` to close the terminal window by
23
- * WM class (set in `progress start` for alacritty /
24
- * kitty; gnome-terminal / konsole / xfce4-terminal
25
- * close on their own when the child exits). wmctrl is
26
- * not always installed; we silently no-op on
27
- * "command not found" (exit 127) and surface other
28
- * errors as warnings.
29
- * - Windows: `taskkill /F /FI "WINDOWTITLE eq
30
- * peaks-cli:*"` to kill the cmd.exe wrapper. We use
31
- * the title prefix because the exact title includes the
32
- * `--reason` suffix which we do not know here.
33
- *
34
- * The kill is intentionally not a single primitive (e.g.
35
- * `process.kill(-pid, 'SIGTERM')` on the process group).
36
- * The launcher's PID is the spawn-time PID (osascript on
37
- * macOS, gnome-terminal on Linux), not the long-lived
38
- * watch process — and the long-lived process is the one we
39
- * actually need to terminate to make the terminal close.
40
- * Targeting by command pattern (pkill) + window title
41
- * (AppleScript / wmctrl / taskkill) is more reliable than
42
- * PID chasing across detached children.
43
- */
44
- import type { ProgressSpawnRecord } from '../../services/progress/progress-service.js';
45
- export type KillSpawnedTerminalResult = {
46
- /** Each signal that was successfully sent. */
47
- signals: string[];
48
- /** Soft failures (e.g. pkill matched no process, wmctrl missing). */
49
- warnings: string[];
50
- };
51
- export declare function killSpawnedTerminal(record: ProgressSpawnRecord, canonicalProjectRoot: string, currentPlatform: NodeJS.Platform): Promise<KillSpawnedTerminalResult>;
@@ -1,152 +0,0 @@
1
- /**
2
- * Best-effort close of a spawned `peaks progress watch`
3
- * window. Used by `peaks progress close` (manual escape
4
- * hatch) and by the watch-side auto-exit when the sub-agent
5
- * hits a terminal phase.
6
- *
7
- * The close is best-effort by design: we never throw from
8
- * individual signals. One failed close primitive is a UX
9
- * paper cut, not a correctness bug — the caller still clears
10
- * the spawn record after this returns.
11
- *
12
- * Cross-platform strategy:
13
- *
14
- * - macOS: pkill the watch process by command pattern
15
- * (matches the project path, so we never close the
16
- * wrong window), then send AppleScript to Terminal.app
17
- * to close the window by `custom title`. Terminal.app
18
- * is the dominant macOS terminal, and `custom title` is
19
- * the only stable identifier we can target from outside
20
- * the running shell.
21
- * - Linux: pkill the watch process, then try `wmctrl -c
22
- * peaks-cli-progress` to close the terminal window by
23
- * WM class (set in `progress start` for alacritty /
24
- * kitty; gnome-terminal / konsole / xfce4-terminal
25
- * close on their own when the child exits). wmctrl is
26
- * not always installed; we silently no-op on
27
- * "command not found" (exit 127) and surface other
28
- * errors as warnings.
29
- * - Windows: `taskkill /F /FI "WINDOWTITLE eq
30
- * peaks-cli:*"` to kill the cmd.exe wrapper. We use
31
- * the title prefix because the exact title includes the
32
- * `--reason` suffix which we do not know here.
33
- *
34
- * The kill is intentionally not a single primitive (e.g.
35
- * `process.kill(-pid, 'SIGTERM')` on the process group).
36
- * The launcher's PID is the spawn-time PID (osascript on
37
- * macOS, gnome-terminal on Linux), not the long-lived
38
- * watch process — and the long-lived process is the one we
39
- * actually need to terminate to make the terminal close.
40
- * Targeting by command pattern (pkill) + window title
41
- * (AppleScript / wmctrl / taskkill) is more reliable than
42
- * PID chasing across detached children.
43
- */
44
- import { execFile } from 'node:child_process';
45
- import { promisify } from 'node:util';
46
- import { getErrorMessage } from '../cli-helpers.js';
47
- const execFileAsync = promisify(execFile);
48
- export async function killSpawnedTerminal(record, canonicalProjectRoot, currentPlatform) {
49
- const signals = [];
50
- const warnings = [];
51
- // The watch command we spawned, escaped for use as a pkill
52
- // pattern. We anchor on `progress watch` (NOT `peaks progress
53
- // watch`) because the actual cmdline is `.../peaks.js progress
54
- // watch --project /path` — the literal substring
55
- // `peaks progress watch` does NOT appear in the cmdline
56
- // (there is a `.js` between `peaks` and `progress`).
57
- // Anchoring on the verb + the project path is specific
58
- // enough to not hit any user-owned `progress watch` process
59
- // for a different project.
60
- const watchPattern = `progress watch.*--project ${canonicalProjectRoot.replace(/[\\"\s]/g, '\\$&')}`;
61
- if (currentPlatform === 'darwin') {
62
- // pkill exit codes: 0 = matched & signalled, 1 = no processes
63
- // matched (silent miss), 2 = syntax error (warning), 3 = fatal
64
- // (warning). macOS pkill writes nothing to stderr on a clean
65
- // miss, so the exit code is the only signal we have.
66
- await trySignal('pkill', ['-f', watchPattern], signals, 'pkill-watch', warnings, /no.*process/i, new Set([1]));
67
- // AppleScript to close the Terminal.app window by
68
- // custom title. We use `every window whose custom title
69
- // is` so we only close the right tab. AppleScript returns
70
- // a non-zero exit when the window is already gone, the
71
- // app is not running, or the title does not match — all
72
- // of which are silent misses from the user's perspective
73
- // (the user-facing outcome is identical to the success
74
- // case: the window is no longer visible). Treat any
75
- // non-zero exit as silent.
76
- try {
77
- const escapedTitle = record.windowTitle.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
78
- await execFileAsync('osascript', [
79
- '-e',
80
- `tell application "Terminal" to close (every window whose custom title is "${escapedTitle}")`
81
- ]);
82
- signals.push('osascript-close-window');
83
- }
84
- catch {
85
- // Silent miss. See comment above.
86
- }
87
- }
88
- else if (currentPlatform === 'linux') {
89
- // Same pkill exit code semantics as macOS.
90
- await trySignal('pkill', ['-f', watchPattern], signals, 'pkill-watch', warnings, /no.*process/i, new Set([1]));
91
- // wmctrl by WM class (set in `progress start`). Missing
92
- // wmctrl is silent (exit 127) — most distros ship it but
93
- // headless / minimal installs do not.
94
- await trySignal('wmctrl', ['-c', 'peaks-cli-progress'], signals, 'wmctrl-close-class', warnings, /not found|No such file/i, new Set([127]));
95
- }
96
- else if (currentPlatform === 'win32') {
97
- // Title prefix is set in `progress start` to `peaks-cli:`.
98
- // We match the prefix because the full title includes
99
- // the `--reason` suffix which we do not know here.
100
- // taskkill exit codes: 0 = success, 1 = no tasks matched
101
- // (silent miss — the window is already gone), 128 = error.
102
- const titlePrefix = 'peaks-cli:';
103
- await trySignal('taskkill', ['/F', '/FI', `WINDOWTITLE eq ${titlePrefix}*`], signals, 'taskkill-window-title', warnings, /no.*task/i, new Set([1]));
104
- }
105
- else {
106
- warnings.push(`unsupported platform: ${currentPlatform}`);
107
- }
108
- return { signals, warnings };
109
- }
110
- /**
111
- * Run a single close primitive. If it throws AND either
112
- * (a) the error matches the "expected" stderr pattern
113
- * (e.g. "no process matched" for pkill, "command not
114
- * found" for wmctrl) — most platforms print this on
115
- * stderr; or
116
- * (b) the exit code is in `silentMissExitCodes` (pkill 1,
117
- * wmctrl 127, taskkill 1) — the primitive ran, found
118
- * nothing, and is not telling us via stderr,
119
- * we silently no-op — that is the success case for the
120
- * primitive. Other errors are appended to `warnings` for
121
- * the caller to surface. On a clean resolve, the named
122
- * signal is appended to `signals`.
123
- */
124
- async function trySignal(command, args, signals, signal, warnings, expectedFailurePattern, silentMissExitCodes) {
125
- try {
126
- await execFileAsync(command, args);
127
- }
128
- catch (error) {
129
- // execFile's error object exposes `code` as either a
130
- // numeric exit code (when the process ran) or a string
131
- // system code like 'ENOENT' (when the binary itself
132
- // is missing). Only numeric exit codes are candidates
133
- // for silent-miss.
134
- const execError = error;
135
- if (typeof execError.code === 'number' && silentMissExitCodes.has(execError.code)) {
136
- // Exit code says "ran, but found nothing to act on".
137
- // The user-facing outcome is identical to the success
138
- // case, so do not surface a warning.
139
- return;
140
- }
141
- const message = getErrorMessage(error);
142
- if (expectedFailurePattern.test(message)) {
143
- return;
144
- }
145
- warnings.push(`${command}: ${message}`);
146
- return;
147
- }
148
- // Reached only if execFile resolves (exit 0). All three
149
- // primitives exit non-zero on a miss, so a clean resolve
150
- // means the signal landed.
151
- signals.push(signal);
152
- }
@@ -1,3 +0,0 @@
1
- import { Command } from 'commander';
2
- import { type ProgramIO } from '../cli-helpers.js';
3
- export declare function registerProgressCommands(program: Command, io: ProgramIO): void;