fix(pi): repair the /mcpctl menu, skills target, and typecheck the extension
Some checks failed
CI/CD / lint (pull_request) Successful in 1m7s
CI/CD / test (pull_request) Successful in 1m23s
CI/CD / typecheck (pull_request) Successful in 2m49s
CI/CD / smoke (pull_request) Failing after 1m54s
CI/CD / build (pull_request) Successful in 4m21s
CI/CD / publish (pull_request) Has been skipped

The pi extension shipped in `src/pi-ext/` was covered by no tsconfig and no
eslint config, so nothing ever checked it against pi's API. Pointing tsc at
the published @earendil-works/pi-coding-agent types found the command surface
to be inert.

Fixes:

- `/mcpctl` did nothing. `ctx.ui.select` takes `string[]` and returns the
  chosen string; it was called with `{value,label}` objects, so the menu
  rendered five `[object Object]` rows and `choice === "status"` never
  matched any branch. Labels are now plain strings mapped back to actions.
- The headless branch returned a status string from a handler typed
  `Promise<void>`; pi drops it. Reports via notify instead.
- "Sync skills" omitted `--agent pi`, writing into ~/.claude/skills — in an
  integration whose stated purpose is to not depend on ~/.claude — and said
  so in its own success message. It also ran execSync with `stdio: "inherit"`,
  painting raw output over pi's TUI, and interpolated the project name into a
  shell string. Now execFile with `--agent pi` and captured output.
- Tool results typed `content[].type` as `string`; pi's AgentToolResult wants
  the `"text"` literal.
- `callTool` asserted `Promise<unknown>` to `ToolCallResult`.
- Sanitising MCP tool names to `[a-z0-9_]` can collide (`docs.search` vs
  `docs-search`). The colliding tool was silently never registered but still
  reported active, so its calls were forwarded to the first tool. Names are
  now disambiguated and tracked with the MCP tool they forward to.
- `registerWithPi` rewrote settings.json even when nothing changed. Since
  parsing strips `//` comments, a no-op run destroyed them.

Guards, so this class of bug can't return:

- `src/pi-ext/tsconfig.json` checks the extension against the real published
  pi types (dev dependency, not a shim — a shim drifting from the published
  API is the exact failure being guarded). Wired into `pnpm typecheck`.
- eslint now covers `src/pi-ext/*.ts` like every other source file.
- A test fails if the embedded copy in `config/pi-extension.ts` is stale;
  editing the sources without regenerating silently shipped old code.

Also: the branch added `config pi` without regenerating shell completions
(the committed-completions test was failing), and the doc advertised
`mcpctl pi sync-skills`, which does not exist. Both corrected, plus a note
on the session-token vs `mcpctl_pat_` bearer difference that would bite
against an authenticated `mcplocal serve`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017BMXdb2qZbPSh8Q7XpTyjB
This commit is contained in:
Michal
2026-08-08 17:30:42 +01:00
parent f1d84b0952
commit 739e679d1a
12 changed files with 1279 additions and 84 deletions

View File

@@ -81,8 +81,17 @@ tools are in the active set at any time.
Skills already sync in the Agent Skills `SKILL.md` format. `mcpctl config pi`
(when given a project) writes them under pi's own directory
`~/.pi/agent/skills/` and registers that directory in `~/.pi/agent/settings.json`
— no Claude involvement. `mcpctl pi sync-skills` (a thin wrapper) re-runs the
existing `skills` code path against the pi target directory.
— no Claude involvement. Re-sync later with:
```bash
mcpctl skills sync --agent pi --project <name>
```
`--agent pi` is what keeps skills out of `~/.claude/skills`; the default target
is still Claude Code. The `/mcpctl` menu's **sync skills** action runs exactly
this command. Sync state is tracked separately per agent
(`~/.mcpctl/skills-state-pi.json`), so pi and Claude never fight over the same
bookkeeping.
## Layout
@@ -95,3 +104,46 @@ src/pi-ext/
The extension imports only from pi-bundled packages
(`@earendil-works/pi-coding-agent`, `@earendil-works/pi-ai`, `typebox`), so it
loads standalone.
## Typechecking
The extension ships as *source*: it is embedded into the CLI
(`src/cli/src/config/pi-extension.ts`, generated by
`scripts/generate-pi-extension.ts`) and written verbatim into
`~/.pi/agent/extensions/mcpctl/`. The CLI's own build never compiles it, so
without a dedicated project nothing would check it against pi's API — which is
how a `ctx.ui.select()` call with the wrong option shape shipped in the first
place.
`src/pi-ext/tsconfig.json` closes that gap, checking against the **real**
published `@earendil-works/pi-coding-agent` types (a dev dependency, not a
hand-written shim — a shim that drifts from the published API is the exact
failure this guards against):
```bash
pnpm run typecheck # includes typecheck:pi-ext
```
After editing `src/pi-ext/*.ts`, regenerate the embedded copy or the CLI will
keep installing the old sources:
```bash
npx tsx scripts/generate-pi-extension.ts
```
A test (`tests/config/pi-extension-embed.test.ts`) fails if you forget.
## Authentication caveat
The extension sends the mcpd session token from `~/.mcpctl/credentials` as its
bearer. A locally running `mcpctl-local` daemon does not authenticate
`/projects/*`, so this works — the header is simply ignored.
`mcplocal serve` is different: it registers a token-auth preHandler that accepts
**only** `mcpctl_pat_` bearers (project mcptokens), and rejects a session token
with `401 Only mcpctl_pat_ bearers are accepted on this endpoint`. Pointing the
extension at an authenticated `mcplocal serve` therefore needs a project
mcptoken (`mcpctl create mcptoken <token-name> --project <name>`, printed once).
Wiring that token through to the extension is not yet implemented — the
`config prime-agent` path does the equivalent by storing it in
`~/.prime/agent/auth.json`.