premanmcp 1.1.18 → 1.1.20

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # `premanmcp` 1.0
1
+ # PreMan CLI and MCP bridge
2
2
 
3
3
  `premanmcp` is the compatibility client for agents that can start only a local
4
4
  stdio MCP process. It forwards MCP initialization, discovery, notifications,
@@ -18,6 +18,284 @@ four-tool PreMan conversation protocol:
18
18
 
19
19
  PreMan can add private agent capabilities without publishing a new npm package.
20
20
 
21
+ ## Installed desktop and delegated QA
22
+
23
+ This source tree adds durable delegated tasks to the modern CLI. The npm package
24
+ version alone is not evidence that this feature has been published. Desktop
25
+ packages record the exact CLI Git revision, dirty state and content hashes in
26
+ `resources/cli/bundle-manifest.json`; `preman doctor --json` reports that provenance
27
+ when present. Release builds require clean, pinned backend and dashboard sources.
28
+
29
+ On a desktop build containing these resources, sign-in provisions a revocable
30
+ machine API key and an executable at `~/.local/bin/preman` (Windows uses
31
+ `preman.cmd`). The launcher uses the signed app's embedded Electron Node runtime,
32
+ so a separate Node or npm installation is unnecessary. It retains the app's
33
+ absolute path; moving the app requires opening it again to refresh the launcher.
34
+ Add `~/.local/bin` to PATH if it is not already there. Detected Codex, Claude Code
35
+ and Cursor installations get MCP configuration and marked instructions pointing
36
+ to that exact executable. Existing unrelated configuration is preserved and
37
+ conflicts are reported. Start a new harness session after registration.
38
+
39
+ To register an agent explicitly, run the installed executable with
40
+ `preman connect --agent codex --no-guide` (or `claude-code` / `cursor`).
41
+ Connect writes the config and redeems the account pairing through its existing
42
+ heartbeat. Interactive connection verification initializes the configured MCP
43
+ bridge and discovers the four public tools; it does not invoke a tool, start a
44
+ coding agent, or run QA. `--no-self-test` skips that transport check, and
45
+ `--no-wait` writes the config and redeems pairing without waiting or self-testing.
46
+ After restarting the coding agent, use `preman doctor --json` and
47
+ `preman capabilities --json` to inspect readiness and available QA modes.
48
+
49
+ Other coding harnesses can invoke the executable as a normal CLI, or configure
50
+ standard MCP stdio with the absolute launcher path and `args: ["start"]`. The
51
+ generic MCP descriptor is available from `preman local status` in
52
+ `integrations.generic_mcp`. The machine credential is read from the shared,
53
+ backend/account-bound private credential store; it is not copied into harness
54
+ configuration. The bridge passes through server tools/resources and MCP Apps
55
+ metadata rather than maintaining a separate QA tool catalogue. A harness with
56
+ MCP Apps support can render the shared interactive task card; a CLI caller can
57
+ attach the PNG preview described below.
58
+
59
+ ```bash
60
+ preman capabilities --json
61
+ preman doctor --json --workspace WORKSPACE_ID
62
+ preman doctor --json --workspace WORKSPACE_ID --mode endpoint \
63
+ --target http://127.0.0.1:8000 --repo /absolute/repository --executor-kind local
64
+ ```
65
+
66
+ `connection_ready` means authentication, backend and runtime are connected.
67
+ `target_ready` is null until a target/source is supplied; it never implies a QA
68
+ pass. Capabilities use the server's existing modes, resources and action policies.
69
+ Selection retains workspace → collection → folder → request scope. Supply saved
70
+ IDs in a `--context-json` file, for example:
71
+
72
+ ```json
73
+ {
74
+ "mode": "endpoint",
75
+ "intent": "test",
76
+ "resources": {"collection_id": "COLLECTION_ID", "folder_id": "FOLDER_ID", "request_ids": ["REQUEST_ID"]},
77
+ "caller": {"name": "codex", "session_id": "CALLER_SESSION_ID"}
78
+ }
79
+ ```
80
+
81
+ The durable task interface defaults to one JSON object per response. `watch`
82
+ emits JSONL snapshots, ordered events and a resume checkpoint. Errors are JSON on
83
+ stdout, with a stable code and retryability; no machine command logs in, opens a
84
+ browser or silently answers an approval. Human `chat -p` remains available.
85
+ Nested commands such as `preman task start --help` and `preman patch apply --help` show scoped help without authentication, network calls, or execution.
86
+
87
+ Prepare a complete source handoff before starting a task:
88
+
89
+ ```bash
90
+ preman task prepare -p "Run the saved checks; repair the implementation if they fail." \
91
+ --workspace WORKSPACE_ID --context-json scope.json --repo /absolute/repository \
92
+ --target http://127.0.0.1:8000 --executor-kind local --repair patch \
93
+ --output /tmp/preman-prepared.json --json
94
+ preman task start --prepared /tmp/preman-prepared.json --json
95
+ ```
96
+
97
+ Preparation captures/uploads the requested source and validates its workspace and
98
+ context without starting a task or calling a model. `status: "ready"` validates
99
+ the handoff, not runtime qualification or a passing test. `needs_input` lists
100
+ missing fields and corrective actions. For MCP, call `preman_chat` with the exact
101
+ `mcp.arguments` from that JSON, including the snapshot, workspace, repair policy
102
+ and idempotency key. Plain MCP chat remains conversational. CLI `--prepared`
103
+ rejects context overrides; prepare again when the intended scope changes.
104
+ `PREMAN_WORKSPACE_ID` supplies the workspace when `--workspace` is absent.
105
+ On macOS, source operations fall back to the selected Xcode Git executable when
106
+ the system launcher cannot write its cache inside an agent sandbox. An explicit
107
+ `PREMAN_GIT_BIN` remains authoritative.
108
+ Direct task starts also reject missing source context before creating any work;
109
+ CLI errors preserve `error.issues` and `error.workspace_choices`. Correct the
110
+ input and retry. Existing tasks retain immutable source context; use a linked
111
+ retest for the applied tree.
112
+
113
+ ```bash
114
+ preman task start -p "Check the saved requests against this source; report failures." \
115
+ --workspace WORKSPACE_ID --context-json scope.json --repo /absolute/repository \
116
+ --target http://127.0.0.1:8000 --executor-kind local --repair patch \
117
+ --idempotency-key caller-operation-001
118
+ preman task get TASK_ID
119
+ preman task wait TASK_ID --execute-local --timeout 600 --check
120
+ preman task watch TASK_ID --execute-local --after 0 --timeout 600
121
+ preman task reply TASK_ID --request REQUEST_ID --answers-json answers.json
122
+ preman task message TASK_ID -p "Use the failure evidence to repair the implementation and return a patch."
123
+ preman task cancel TASK_ID
124
+ preman task result TASK_ID --check
125
+ ```
126
+
127
+ Retain the task ID and event cursor. Retry a mutation with the same idempotency
128
+ key and payload. Required input and exact action proposals return attention
129
+ states; only use `preman action confirm PROPOSAL_ID` after the human explicitly
130
+ approved that proposal's displayed impact. Cancellation is acknowledged only
131
+ after the owned work stops. Ctrl-C stops the CLI's wait, stops its local active
132
+ operation, and preserves the durable task; it does not silently cancel the task.
133
+ An interrupted claimed local operation is reported as incomplete, not replayed.
134
+
135
+ Exit codes: normal successful requests are 0. With `--check`, 0 requires a
136
+ verified pass bound to the task's exact source snapshot or explicit target build;
137
+ 1 means failed QA; 2 means unverified, inconclusive or attention required; 3 means
138
+ still running. Client/API errors are 4 and interrupted waits are 130. A completed
139
+ chat turn, submitted job, successful patch application or missing source identity
140
+ cannot produce a verified `--check` pass.
141
+
142
+ ### Missing QA capabilities
143
+
144
+ Delegated tasks report genuine unsupported capabilities in `feature_requests`.
145
+ Each persisted receipt has `capture_status: "saved"`, a stable request ID,
146
+ summary, review status and exact web/native links. The Feature requests page in
147
+ Workbench shows the workspace's reports. Developers use the separately
148
+ authenticated review surface to triage them. Repeated reads do not create
149
+ reports, and repeated reports of the same task/capability reuse the receipt.
150
+
151
+ Setup, authentication, permission, budget and transient failures remain task
152
+ blockers. They do not automatically become product feature requests. Saving
153
+ feedback does not resolve missing coverage or establish a verified QA pass.
154
+ An `unavailable` capture has no saved request ID or link; callers must report
155
+ that failure rather than claim the feedback reached developers.
156
+
157
+ The Preman agent can report a missing capability through its private
158
+ `report_capability_gap` action. CLI and MCP callers continue the existing task
159
+ with the missing capability if more context is needed; the four public MCP
160
+ tool names remain unchanged. Display the returned receipt and link alongside
161
+ the coverage gap. See [the feedback contract](../docs/DELEGATION_FEATURE_FEEDBACK.md)
162
+ for storage, access and recovery behavior.
163
+
164
+ ### Local execution and repair
165
+
166
+ `--repo` captures dirty and untracked eligible source into an immutable local
167
+ snapshot and uploads the bounded snapshot to the owned workspace: at most 500
168
+ files, 2 MiB each, 8 MiB total. Symlinks, generated paths, credential paths and
169
+ detected private keys are excluded. The returned manifest names exclusions.
170
+ Review it with `preman snapshot create --repo /absolute/repository` before upload
171
+ when needed. Source is not placed in ordinary chat context.
172
+
173
+ An explicitly selected local executor needs `task wait/watch --execute-local`.
174
+ Without a source snapshot, localhost requests use bounded, same-origin HTTP on
175
+ that device and cannot claim a source-specific pass. With a source snapshot,
176
+ the CLI boots the immutable source in a distinct Docker container and replays
177
+ the frozen saved checks against that container, not the caller's original server.
178
+ The source manifest and execution-plan digest are bound to the run lease.
179
+
180
+ Local repairs use the same native PreMan agent loop. Its bounded file operations
181
+ edit a private candidate snapshot; the coding harness is not redispatched as a
182
+ second repair agent. Original test/config files are frozen. Dependencies, suite
183
+ commands and service replay run only in a local Docker engine with a preinstalled
184
+ qualified image. Test/boot containers have no network, dropped capabilities,
185
+ resource limits and no host credentials; dependency installation has network
186
+ access inside its restricted container. No host-shell fallback or implicit image
187
+ pull is used. Missing Docker, images, checks or target permissions remain explicit
188
+ readiness/verification gaps. `--repair patch` permits returning a patch, never
189
+ applying it to the caller's checkout.
190
+
191
+ ```bash
192
+ preman artifacts get TASK_ID PATCH_ARTIFACT_ID --output /absolute/repair.json
193
+ preman patch apply --file /absolute/repair.json --repo /absolute/repository --check-only
194
+ preman patch apply --file /absolute/repair.json --repo /absolute/repository
195
+ preman task retest TASK_ID --repo /absolute/repository --idempotency-key caller-retest-001
196
+ preman task wait NEW_TASK_ID --execute-local --timeout 600 --check
197
+ ```
198
+
199
+ Patch application validates the original snapshot, exact changed-file preimages
200
+ and modes, paths, digest and postimages before editing. Unrelated caller edits
201
+ are preserved; changed preimages are rejected. Binary patches are unsupported.
202
+ Retest creates a new task/run against the newly captured source and the original
203
+ saved checks. It does not inherit the old verdict or verify merely by applying.
204
+ `artifact fetch` remains an alias for `artifacts get`.
205
+
206
+ For local AppQA, select `mode: "app_qa"`, `executor: {"kind":"local"}` and an
207
+ explicit supported target in context. Task creation checks target readiness and
208
+ registers the existing desktop controller before the server creates its run.
209
+ `wait/watch --execute-local` starts that assigned worker. Web targets without an
210
+ explicit existing-browser selection use the bundled isolated browser. Native app,
211
+ existing-browser and simulator targets require their platform qualification and
212
+ OS permissions; no permissions are requested silently. Closing the app window preserves supported
213
+ background execution; quitting the app stops it. A fully restarted app must prepare
214
+ a new worker/task rather than reusing a revoked worker lease. `preman local status`
215
+ and `preman local app-qa capabilities` distinguish those states.
216
+
217
+ Local web UI Flows use the installed desktop's bundled Electron browser and the
218
+ same Flow step engine as the backend worker. `task wait --execute-local` starts
219
+ only the owned run assigned to this device; no source checkout, separate worker,
220
+ browser download or Node installation is required. The browser uses a disposable
221
+ profile, blocks requests outside the selected origin, and captures each step.
222
+ The CLI also starts child Flow/AppQA runs created by repair verification.
223
+
224
+ Web AppQA uses that isolated browser beneath the existing AppQA observation,
225
+ action, lease and evidence controller. Native macOS apps, explicitly selected
226
+ existing browser sessions and iOS simulators retain their permission and platform
227
+ qualification gates. `doctor --mode flow --executor-kind local --target-kind web
228
+ --target http://127.0.0.1:3000` reports installed runtime readiness.
229
+
230
+ A source-bound repair can keep a private candidate service alive while the real
231
+ saved Flow, AppQA, endpoint and agent checks replay. All source commands run in a
232
+ bounded local container; the runtime verifies the immutable source, execution
233
+ plan and actual image ID. Private headers protect its loopback proxy and never
234
+ appear in public task evidence. Requests are bounded HTTP; WebSockets and streaming
235
+ are not supported by this local candidate proxy. No replay is assigned the
236
+ original caller's localhost service as candidate evidence.
237
+ For ASSERT source callables, the native message/history contract runs only inside
238
+ the selected source container. Provider-dependent code needs explicitly configured
239
+ isolated prerequisites; no caller provider secret is copied into the container.
240
+ Sandbox agent evaluations build the fixed root Dockerfile from the immutable
241
+ snapshot, using cached base images and no build network, mounts, secrets or remote
242
+ ADD. They require an existing paired evaluation runner connected on the same
243
+ device and Docker daemon; that runner consumes the normal agent_eval_v1 case jobs.
244
+ The CLI watch loop does not impersonate that evaluation runner. Missing Dockerfile,
245
+ base image or paired runner is an explicit prerequisite.
246
+
247
+ Closing the desktop window leaves its local executor available. A full process
248
+ restart preserves task history, but an interrupted AppQA worker is not rebound to
249
+ an uncertain browser session. `local_worker_session_lost` requires confirmed
250
+ cleanup followed by `preman task retest <task-id>`; it never replays browser input
251
+ from an expired worker lease.
252
+
253
+ ### Evidence, exact links and previews
254
+
255
+ Every task includes `links.native_url` and `links.web_url` for its exact page.
256
+ The native format is `preman://open?source=agent&route=<encoded-workbench-route>`;
257
+ use the web URL when the desktop cannot open. Do not replace a task/run link with
258
+ the app home page. Protected artifacts require the caller's normal authentication:
259
+
260
+ ```bash
261
+ preman artifacts get TASK_ID ARTIFACT_ID --output /absolute/evidence.png
262
+ preman task preview TASK_ID --output /absolute/task-card.png
263
+ preman task result TASK_ID --preview --output /absolute/task-card.png
264
+ ```
265
+
266
+ The installed desktop renders previews from the same bundled MCP task card using
267
+ its sandboxed offscreen Chromium. It works with the main window closed while the
268
+ desktop process remains alive. The returned absolute `local_path`, media type and
269
+ SHA256 let a coding harness attach the preview inline. It requires no Chromium
270
+ download. Explicit local execution or preview commands can wake the installed
271
+ app in the background; readiness inspection never launches it or opens login.
272
+ Remote artifacts retain their normal authentication requirements.
273
+
274
+ ### Upgrade and removal
275
+
276
+ Upgrade the desktop normally, then open it once to refresh its launcher and
277
+ managed harness registrations. A release bundles a pinned CLI plus the pinned
278
+ dashboard task renderer, and packaging fails if those resources or hashes are
279
+ missing. Release smoke tests execute the packaged Electron binary with Node
280
+ removed from PATH, then initialize MCP and discover tools/resources. The Electron
281
+ RunAsNode fuse must stay enabled for this launcher.
282
+
283
+ `preman harness status` reports managed registrations. Run `preman harness install`
284
+ after installing another coding harness; it preserves unrelated configuration and
285
+ enables the PreMan server for Cursor CLI. Start a fresh harness session afterward.
286
+
287
+ ```bash
288
+ preman harness remove --disconnect --remove-launcher
289
+ ```
290
+
291
+ This stops local desktop executors, revokes the connected machine key, removes
292
+ unchanged managed MCP entries and instruction blocks, and removes the marked
293
+ launcher. User-modified entries remain intact and are reported. If backend
294
+ revocation cannot be confirmed, removal stops with an actionable error. Then
295
+ remove the PreMan app normally. Private snapshots/repair evidence under `~/.preman`
296
+ remain available until explicitly removed. Desktop sign-out also stops local
297
+ work, revokes its machine key and removes only that account's credential profile.
298
+
21
299
  ## Installing
22
300
 
23
301
  ```bash
@@ -32,6 +310,11 @@ stuck in. `--no-desktop` (or `PREMAN_NO_DESKTOP=1`) skips it, and it is skipped
32
310
  automatically off macOS, where there is no build. The git pre-push hook is the
33
311
  one thing still asked about, since it changes what happens on every push.
34
312
 
313
+ Hook repair preserves an existing `--read-only` verification policy. Background
314
+ repairs and command probes suppress further automatic repairs; help, version,
315
+ and MCP startup do not schedule repairs. `PREMAN_NO_HOOK_REPAIR=1` disables
316
+ background repair while leaving an explicit `preman hook repair` available.
317
+
35
318
  `preman install --cursor` writes Cursor's MCP config and nothing else, which is
36
319
  what plain `install` used to mean; `install --api-key …` and
37
320
  `install --project --backend …` are unchanged.
@@ -39,6 +322,44 @@ what plain `install` used to mean; `install --api-key …` and
39
322
  Nothing needs installing to try it: `npm exec -y premanmcp@latest -- <command>`
40
323
  runs any of these once, fetching the newest version each time.
41
324
 
325
+ ### Personal QA instructions
326
+
327
+ `npm exec -y premanmcp@latest -- onboard` also installs persistent instructions
328
+ telling detected coding agents to use the PreMan CLI agent for every QA task:
329
+ agent evaluations, compatibility across agents, useful user simulations,
330
+ full-stack API-to-frontend testing, backend testing, and frontend QA. The same
331
+ step runs through `setup` and the onboarding form of `install`.
332
+
333
+ | Coding agent | User instruction file |
334
+ | --- | --- |
335
+ | Codex | `~/.codex/AGENTS.md` (or under `CODEX_HOME`); also updates a nonempty `AGENTS.override.md` because it takes precedence |
336
+ | Claude Code | `~/.claude/CLAUDE.md` (or under `CLAUDE_CONFIG_DIR`) |
337
+ | Cursor | `~/.cursor/rules/preman-qa.mdc`, with `alwaysApply: true` |
338
+
339
+ These are user-level files, not repository instructions. Codex uses the global
340
+ `AGENTS.md` for its [personal instructions](https://learn.chatgpt.com/docs/personalize#add-custom-instructions).
341
+ Claude Code loads [user memory](https://code.claude.com/docs/en/memory) across
342
+ projects. Cursor's [local user rule files](https://prod.cursor.com/help/customization/rules#where-are-rules-stored)
343
+ apply on this machine; they do not change its account-synced User Rules setting.
344
+
345
+ Existing instructions are preserved outside a marked PreMan section. Rerunning
346
+ onboarding updates that section without duplicates. An existing Cursor file
347
+ that is not a PreMan-managed, always-on rule is preserved and reported as a
348
+ conflict. Errors for one file do not prevent setup for the other agents.
349
+ Onboarding prints each installed path and any manual follow-up needed. Start
350
+ a new coding-agent session afterward to load the instructions.
351
+
352
+ Use `--agent codex`, `--agent claude-code`, or `--agent cursor` to target just
353
+ one agent, even before it is installed. Use `--no-agent-instructions` to skip
354
+ this step. If no supported agents are detected, onboarding reports the skip.
355
+
356
+ The installed guidance supplies
357
+ `npm exec -y premanmcp@latest -- chat -p "<QA objective, target, and acceptance criteria>"`
358
+ and tells the coding agent to follow the actual run to completion, report
359
+ results and coverage gaps, explain unavailable targets, and respect the user's
360
+ scope and approvals. Installing instructions does not itself run QA or prove
361
+ that an agent followed them.
362
+
42
363
  ## The `preman` terminal session
43
364
 
44
365
  Installed globally, a bare `preman` opens an interactive session with the PreMan
@@ -83,12 +404,24 @@ account, backend, and workspace:
83
404
  /swarm stop <run-id>
84
405
  ```
85
406
 
86
- `/swarm help` lists every option. Add `--dry-run` to preview a launch without
87
- starting attempts. Ctrl+C while watching returns to the conversation and leaves
407
+ Launch follows automatically, showing queue and running counts, live tool
408
+ actions, and the final result. `/swarm help` lists every option. Add `--dry-run`
409
+ to preview a launch without starting attempts, or `--no-watch` to return
410
+ immediately. `--json` returns one JSON response. Ctrl+C while watching returns to the conversation and leaves
88
411
  the swarm running; `/swarm stop <run-id>` cancels it explicitly. The same commands
89
412
  work outside a session as `preman swarm list|launch|watch|stop|bank`. These run
90
413
  real agents against hosted MCPs; the app's sample swarms remain a separate demo.
91
414
 
415
+ Headless callers can choose the existing matrix settings with `--visibility
416
+ resident` (or `deferred`, or both comma-separated), repeat `--model <name>` to
417
+ compare explicit models, and set `--budget <USD>`. The matrix budget stops new
418
+ cells when priced spend plus in-flight reservations reaches the limit; it cannot
419
+ cap provider usage whose cost is unknown. Use a provider-enforced allowance for
420
+ a strict spending ceiling. `launch --json --no-watch`, `watch <id> --json`, and
421
+ `list --json` return result links. Watch JSON also includes the individual cells
422
+ and their evidence; process completion and tool-selection scores do not establish
423
+ a verified QA pass.
424
+
92
425
  A session takes a screen of its own, the way `vim` does: nothing above it is
93
426
  scrollable, and leaving hands your shell back exactly as it was with one line
94
427
  saying how to resume. Ctrl+C stops the current turn (PreMan keeps working on it)
package/bin/agent.js CHANGED
@@ -2086,7 +2086,7 @@ export async function runSwarm(rest, { args, token, workspaceId, say, paint, reg
2086
2086
  }
2087
2087
  await swarmCommand(argv, {
2088
2088
  sessionArgs: args, token, workspaceId, signal: controller.signal,
2089
- write: say, clearWatch: false,
2089
+ write: say,
2090
2090
  });
2091
2091
  say("\n");
2092
2092
  } catch (error) {
package/bin/cli.js CHANGED
@@ -51,6 +51,9 @@ import {
51
51
  } from "./desktop.js";
52
52
  import { ACCOUNT_HELP, doctorCommand, loginBrowser, logoutCommand, watchCommand } from "./account.js";
53
53
  import { AGENT_HELP, agentCommand } from "./agent.js";
54
+ import { TASK_HELP, taskHelpRequested, taskCommand, artifactCommand, capabilitiesCommand, machineDoctorCommand, snapshotCommand, patchCommand, actionCommand, localCommand } from "./tasks.js";
55
+ import { publicError } from "./task_client.js";
56
+ import { HARNESS_HELP, harnessCommand } from "./harness.js";
54
57
  import {
55
58
  CREDENTIALS_FILE,
56
59
  cliInvocation,
@@ -105,6 +108,8 @@ const leadingFlag = Boolean(args[0]) && args[0].startsWith("-");
105
108
  // Only the first two positions, because `run` and `dispatch` forward arguments
106
109
  // to a tool and one of those may legitimately be asking that tool for help.
107
110
  const wantsHelp = args.slice(0, 2).some((arg) => arg === "--help" || arg === "-h");
111
+ const wantsTaskHelp = ["task", "artifact", "artifacts", "snapshot", "patch", "action", "local", "capabilities", "doctor"].includes(args[0])
112
+ && taskHelpRequested(args.slice(1));
108
113
  // Leading position only, unlike help: `update --version 1.1.9` is how somebody
109
114
  // pins a version to install, so reading `--version` anywhere would answer that
110
115
  // with a number and never run the update. Asking a CLI its version is also the
@@ -142,6 +147,10 @@ function printHelp() {
142
147
  const usage = [
143
148
  ["", "Chat with the PreMan agent in this terminal"],
144
149
  ["chat [message] [--continue]", "The same session, named explicitly"],
150
+ ["task prepare|start|get|wait|watch|reply|cancel|retest|result", "Delegate durable QA with JSON results"],
151
+ ["capabilities --json | doctor --json", "Discover modes and machine readiness"],
152
+ ["artifacts get | snapshot create | patch apply", "Fetch evidence and exchange source/patches"],
153
+ ["harness status|install|remove", "Manage installed coding-agent integrations"],
145
154
  ["status", "Healthy / failing / recently fixed endpoints"],
146
155
  ["swarm list|launch|watch|stop|bank", "Send real coding agents to see if they pick your MCP tools"],
147
156
  ["verify [options]", "Test endpoints against your local app"],
@@ -180,7 +189,7 @@ function printHelp() {
180
189
 
181
190
  Usage:
182
191
  ${usageLines}
183
- ${AGENT_HELP}${INTEGRATIONS_HELP}${CONNECT_HELP}${DISPATCH_HELP}${STATUS_HELP}${SWARM_HELP}${VERIFY_HELP}${PROBE_HELP}${HOOK_HELP}${RUNNER_HELP}${EVAL_HELP}${TARGET_HELP}${ACCOUNT_HELP}${DESKTOP_HELP}${PREDICT_HELP}${ENDPOINTS_HELP}${TEST_HELP}${TESTS_HELP}
192
+ ${TASK_HELP}${HARNESS_HELP}${AGENT_HELP}${INTEGRATIONS_HELP}${CONNECT_HELP}${DISPATCH_HELP}${STATUS_HELP}${SWARM_HELP}${VERIFY_HELP}${PROBE_HELP}${HOOK_HELP}${RUNNER_HELP}${EVAL_HELP}${TARGET_HELP}${ACCOUNT_HELP}${DESKTOP_HELP}${PREDICT_HELP}${ENDPOINTS_HELP}${TEST_HELP}${TESTS_HELP}
184
193
  Login options:
185
194
  --email <email> Pre-fill the email prompt
186
195
  --backend <url> PreMan backend URL. Defaults to ${DEFAULT_BACKEND}
@@ -362,13 +371,36 @@ function startServer() {
362
371
  }
363
372
 
364
373
  async function main() {
374
+ // Scoped help must precede hook repair, credentials, source capture and any
375
+ // executor or network activity, including help on nested operations.
376
+ if (wantsTaskHelp) {
377
+ process.stdout.write(TASK_HELP);
378
+ return;
379
+ }
365
380
  // Running at all is the signal. `hook` and `verify` own the hook themselves,
366
381
  // and connect reinstalls it in the foreground, so a background rewrite there
367
- // would only race with the one the user is watching.
368
- if (!["hook", "verify", "connect"].includes(command)) scheduleHookRepair();
382
+ // would only race with the one the user is watching. Informational commands
383
+ // and MCP startup must remain free of background hook changes.
384
+ if (!["help", "version", "start", "hook", "verify", "connect", "task", "artifact", "artifacts", "snapshot", "patch", "action", "local", "capabilities", "harness"].includes(command) && !(command === "doctor" && commandArgs.includes("--json"))) scheduleHookRepair();
369
385
 
370
386
  if (command === "version") {
371
387
  process.stdout.write(`${packageVersion() || "unknown"}\n`);
388
+ } else if (command === "task") {
389
+ process.exitCode = await taskCommand(commandArgs);
390
+ } else if (command === "harness") {
391
+ process.exitCode = await harnessCommand(commandArgs);
392
+ } else if (["artifact", "artifacts"].includes(command)) {
393
+ process.exitCode = await artifactCommand(commandArgs);
394
+ } else if (command === "snapshot") {
395
+ process.exitCode = await snapshotCommand(commandArgs);
396
+ } else if (command === "patch") {
397
+ process.exitCode = await patchCommand(commandArgs);
398
+ } else if (command === "action") {
399
+ process.exitCode = await actionCommand(commandArgs);
400
+ } else if (command === "local") {
401
+ process.exitCode = await localCommand(commandArgs);
402
+ } else if (command === "capabilities") {
403
+ process.exitCode = await capabilitiesCommand(commandArgs);
372
404
  } else if (command === "agent" || command === "chat") {
373
405
  await agentCommand(commandArgs);
374
406
  } else if (command === "target") {
@@ -414,7 +446,8 @@ async function main() {
414
446
  } else if (command === "logout") {
415
447
  await logoutCommand();
416
448
  } else if (command === "doctor") {
417
- await doctorCommand(commandArgs);
449
+ if (commandArgs.includes("--json")) process.exitCode = await machineDoctorCommand(commandArgs);
450
+ else await doctorCommand(commandArgs);
418
451
  } else if (command === "watch") {
419
452
  await watchCommand(commandArgs);
420
453
  } else if (command === "install-desktop") {
@@ -530,6 +563,11 @@ async function main() {
530
563
  }
531
564
 
532
565
  main().catch((error) => {
566
+ if (["task", "artifact", "artifacts", "snapshot", "patch", "action", "local", "capabilities", "harness"].includes(command) || (command === "doctor" && commandArgs.includes("--json"))) {
567
+ process.stdout.write(`${JSON.stringify(publicError(error))}\n`);
568
+ process.exitCode = error?.exitCode || 4;
569
+ return;
570
+ }
533
571
  const message = error instanceof Error ? error.message : String(error);
534
572
  process.stderr.write(`[preman] ${message}\n`);
535
573
  process.exit(error?.exitCode || 1);
@@ -9,13 +9,14 @@
9
9
 
10
10
  import { spawn, spawnSync } from "node:child_process";
11
11
  import { existsSync } from "node:fs";
12
- import { callBackendJson, onPath } from "../shared.js";
12
+ import { callBackendJson, cliInvocation, onPath } from "../shared.js";
13
13
 
14
14
  import { findAgent } from "./agents.js";
15
15
  import { shellQuote } from "./configs.js";
16
16
 
17
17
  const MCP_CONNECT_SOURCES = new Set([
18
18
  "mcp",
19
+ // Historical pairing receipts remain valid; this is never invoked as a tool.
19
20
  "preman_status",
20
21
  "premanmcp",
21
22
  "agent",
@@ -102,10 +103,9 @@ export function pollTicker() {
102
103
  /**
103
104
  * How to ask each agent to make one PreMan call without opening its UI.
104
105
  *
105
- * The link is established by the agent's first call, so anything that reaches
106
- * `preman_status` finishes the connect. Claude Code needs the server on its
107
- * allow-list because print mode refuses un-allowed MCP tools rather than
108
- * prompting.
106
+ * Retained for callers that explicitly open an agent session. Connection
107
+ * verification itself uses MCP discovery and the terminal's pairing heartbeat,
108
+ * without starting an agent or a QA conversation.
109
109
  */
110
110
  export function headlessCheckIn(agent, serverName) {
111
111
  const prompt = checkInPrompt(serverName);
@@ -123,7 +123,8 @@ export function headlessCheckIn(agent, serverName) {
123
123
  }
124
124
 
125
125
  function checkInPrompt(serverName) {
126
- return `Call the ${serverName} MCP tool preman_status and report the result.`;
126
+ const cli = cliInvocation();
127
+ return `Run ${cli} doctor --json and ${cli} capabilities --json, then report PreMan readiness and any blockers. The configured MCP server is ${serverName}. Do not start QA or a chat merely to verify the connection.`;
127
128
  }
128
129
 
129
130
  /**
@@ -365,7 +366,7 @@ export function selfTestBudgetMs() {
365
366
  }
366
367
 
367
368
  /**
368
- * Run the config we just wrote, exactly as the agent will, and call one tool.
369
+ * Run the config we just wrote, exactly as the agent will, and discover tools.
369
370
  *
370
371
  * This is both the fastest way to finish the link and the only check that proves
371
372
  * the whole chain — launcher, package, key, backend — rather than proving the
@@ -374,7 +375,9 @@ export function selfTestBudgetMs() {
374
375
  * redirect the server to a backend the key was never issued for, and every
375
376
  * symptom of that points at the key.
376
377
  *
377
- * Speaks the two JSON-RPC calls an MCP client makes on startup. Never throws.
378
+ * Speaks the two JSON-RPC requests an MCP client makes on startup. No tool is
379
+ * called: discovery verifies the authenticated transport without model usage.
380
+ * Pairing is separately established by the terminal heartbeat. Never throws.
378
381
  */
379
382
  export async function mcpSelfTest(
380
383
  serverConfig,
@@ -468,24 +471,41 @@ export async function mcpSelfTest(
468
471
  continue;
469
472
  }
470
473
  if (message.id === 1) {
474
+ if (message.error || !message.result?.protocolVersion) {
475
+ finish({ ok: false, reason: message.error?.message || "the MCP server returned an invalid initialization response" });
476
+ continue;
477
+ }
471
478
  send({ jsonrpc: "2.0", method: "notifications/initialized" });
472
479
  send({
473
480
  jsonrpc: "2.0",
474
481
  id: 2,
475
- method: "tools/call",
476
- params: { name: "preman_status", arguments: {} },
482
+ method: "tools/list",
483
+ params: {},
477
484
  });
478
485
  continue;
479
486
  }
480
487
  if (message.id !== 2) continue;
481
- const text = message.result?.content?.[0]?.text;
482
- let status = {};
483
- try {
484
- status = text ? JSON.parse(text) : {};
485
- } catch {
486
- status = { raw: text };
488
+ if (message.error) {
489
+ finish({ ok: false, reason: message.error.message || "MCP tool discovery failed" });
490
+ continue;
487
491
  }
488
- finish({ ok: !message.error, status, reason: message.error?.message || "" });
492
+ const expected = ["preman_chat", "preman_confirm_action", "preman_get_turn", "preman_cancel_turn"].sort();
493
+ const names = Array.isArray(message.result?.tools)
494
+ ? message.result.tools.map((tool) => tool?.name).sort()
495
+ : [];
496
+ if (JSON.stringify(names) !== JSON.stringify(expected) || message.result?.nextCursor) {
497
+ finish({ ok: false, reason: "the MCP server did not expose the four supported PreMan tools; update PreMan and check the configured backend" });
498
+ continue;
499
+ }
500
+ finish({
501
+ ok: true,
502
+ status: {
503
+ authenticated: true,
504
+ backend_url: serverConfig.env?.PREMAN_BACKEND || "",
505
+ tools: names,
506
+ },
507
+ reason: "",
508
+ });
489
509
  }
490
510
  });
491
511
 
@@ -494,7 +514,7 @@ export async function mcpSelfTest(
494
514
  id: 1,
495
515
  method: "initialize",
496
516
  params: {
497
- protocolVersion: "2024-11-05",
517
+ protocolVersion: "2025-06-18",
498
518
  capabilities: {},
499
519
  clientInfo: { name: "preman-connect-selftest", version: "1" },
500
520
  },
@@ -40,9 +40,10 @@ function manualSteps() {
40
40
  }
41
41
 
42
42
  export function nextStepsBlock(agent) {
43
+ const cli = cliInvocation();
43
44
  return (
44
45
  "\nNext steps:\n" +
45
- ` 1. Restart ${agent.label}, then ask it: "run preman_status" to finish linking.\n` +
46
+ ` 1. Restart ${agent.label}, then ask it to run "${cli} doctor --json" and "${cli} capabilities --json" to check readiness.\n` +
46
47
  ` 2. ${cliInvocation()} runner start --background # let PreMan run work here\n` +
47
48
  ` 3. ${cliInvocation()} endpoints discover # map this repo's endpoints\n` +
48
49
  ` 4. ${cliInvocation()} hook install # test affected endpoints on git push\n`