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.
- package/dist/src/cli/commands/hook-handle.d.ts +2 -2
- package/dist/src/cli/commands/hook-handle.js +5 -10
- package/dist/src/cli/commands/hooks-commands.js +44 -29
- package/dist/src/cli/commands/project-commands.js +7 -1
- package/dist/src/cli/commands/workspace-commands.js +1 -2
- package/dist/src/cli/program.js +3 -4
- package/dist/src/services/dashboard/project-dashboard-service.d.ts +0 -7
- package/dist/src/services/dashboard/project-dashboard-service.js +1 -8
- package/dist/src/services/dispatch/sub-agent-dispatcher.d.ts +45 -40
- package/dist/src/services/dispatch/sub-agent-dispatcher.js +25 -20
- package/dist/src/services/ide/adapters/claude-code-adapter.js +0 -3
- package/dist/src/services/ide/adapters/trae-adapter.js +2 -17
- package/dist/src/services/ide/ide-types.d.ts +1 -18
- package/dist/src/services/progress/progress-service.d.ts +23 -103
- package/dist/src/services/progress/progress-service.js +24 -137
- package/dist/src/services/scan/file-size-scan.d.ts +4 -0
- package/dist/src/services/scan/file-size-scan.js +32 -3
- package/dist/src/services/skills/hooks-settings-service.d.ts +57 -5
- package/dist/src/services/skills/hooks-settings-service.js +153 -28
- package/dist/src/shared/incrementing-number.d.ts +0 -8
- package/dist/src/shared/incrementing-number.js +11 -1
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +1 -1
- package/skills/peaks-prd/SKILL.md +16 -16
- package/skills/peaks-prd/references/workflow.md +4 -4
- package/skills/peaks-qa/SKILL.md +25 -32
- package/skills/peaks-qa/references/qa-fanout-contract.md +6 -6
- package/skills/peaks-qa/references/regression-gates.md +1 -1
- package/skills/peaks-rd/SKILL.md +8 -21
- package/skills/peaks-rd/references/{openspec-mcp-cli.md → openspec-cli.md} +11 -14
- package/skills/peaks-solo/SKILL.md +1 -1
- package/skills/peaks-solo/references/a2a-artifact-mapping.md +1 -1
- package/skills/peaks-solo/references/browser-workflow.md +49 -38
- package/skills/peaks-solo/references/external-skill-invocation.md +9 -7
- package/skills/peaks-solo/references/{openspec-mcp-workflow.md → openspec-workflow.md} +5 -20
- package/skills/peaks-solo/references/runbook.md +21 -21
- package/skills/peaks-solo/references/sub-agent-dispatch.md +16 -35
- package/skills/peaks-solo/references/swarm-dispatch-contract.md +9 -9
- package/skills/peaks-ui/SKILL.md +22 -24
- package/skills/peaks-ui/references/workflow.md +2 -2
- package/dist/src/cli/commands/mcp-commands.d.ts +0 -3
- package/dist/src/cli/commands/mcp-commands.js +0 -144
- package/dist/src/cli/commands/progress-close-kill.d.ts +0 -51
- package/dist/src/cli/commands/progress-close-kill.js +0 -152
- package/dist/src/cli/commands/progress-commands.d.ts +0 -3
- package/dist/src/cli/commands/progress-commands.js +0 -379
- package/dist/src/cli/commands/progress-start-spawn.d.ts +0 -59
- package/dist/src/cli/commands/progress-start-spawn.js +0 -140
- package/dist/src/cli/commands/progress-watch-render.d.ts +0 -80
- package/dist/src/cli/commands/progress-watch-render.js +0 -308
- package/dist/src/services/mcp/mcp-apply-service.d.ts +0 -31
- package/dist/src/services/mcp/mcp-apply-service.js +0 -112
- package/dist/src/services/mcp/mcp-call-service.d.ts +0 -17
- package/dist/src/services/mcp/mcp-call-service.js +0 -34
- package/dist/src/services/mcp/mcp-client-service.d.ts +0 -14
- package/dist/src/services/mcp/mcp-client-service.js +0 -49
- package/dist/src/services/mcp/mcp-install-registry.d.ts +0 -11
- package/dist/src/services/mcp/mcp-install-registry.js +0 -38
- package/dist/src/services/mcp/mcp-plan-service.d.ts +0 -29
- package/dist/src/services/mcp/mcp-plan-service.js +0 -109
- package/dist/src/services/mcp/mcp-protocol.d.ts +0 -24
- package/dist/src/services/mcp/mcp-protocol.js +0 -41
- package/dist/src/services/mcp/mcp-scan-service.d.ts +0 -8
- package/dist/src/services/mcp/mcp-scan-service.js +0 -214
- package/dist/src/services/mcp/mcp-stdio-transport.d.ts +0 -10
- package/dist/src/services/mcp/mcp-stdio-transport.js +0 -50
- package/dist/src/services/mcp/mcp-types.d.ts +0 -31
- 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
|
}
|
package/skills/peaks-ui/SKILL.md
CHANGED
|
@@ -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 (
|
|
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
|
|
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
|
|
133
|
-
#
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
#
|
|
137
|
-
#
|
|
138
|
-
#
|
|
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
|
-
# (
|
|
155
|
-
#
|
|
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
|
-
#
|
|
164
|
+
# browser_take_screenshot --args '{"filename":"<abs-path>"}'
|
|
166
165
|
# → visible-browser confirmation
|
|
167
|
-
#
|
|
166
|
+
# browser_snapshot --args '{}'
|
|
168
167
|
# → accessibility tree for regression seeds
|
|
169
|
-
#
|
|
168
|
+
# browser_console_messages --args '{}'
|
|
170
169
|
# → console errors
|
|
171
|
-
#
|
|
170
|
+
# browser_network_requests --args '{}'
|
|
172
171
|
# → failed network
|
|
173
|
-
#
|
|
172
|
+
# browser_close --args '{}'
|
|
174
173
|
# → end the session cleanly
|
|
175
|
-
# The skill body NEVER bakes in the
|
|
176
|
-
# resolves the tool name from the registered server.
|
|
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
|
|
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,
|
|
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.
|
|
359
|
-
- Figma Context MCP and Penpot require user-authorized design access and must not persist tokens or private design data in project artifacts. Same
|
|
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 (
|
|
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 (`
|
|
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,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
|
-
}
|