C1 of design/client-architecture-split.md. There were four overlapping, none-complete CLIs:
the Linux shell's rich headless verbs, the Windows shell's near-none (it could not start a
stream at all), the session's own, and punktfunk-probe's diagnostics. This is the one a
script or a plugin should reach for, and it is a FRONT-END, not the brain — policy stays in
`pf-client-core` and the GUI shells keep calling it in-process. Shelling out for connects
would have traded duplicated code for a duplicated IPC protocol.
punktfunk pair | hosts list/add/forget | wake | library | launch | open
reachable | speed-test | profiles list | reset
Because it runs the same plan builder and the same wake machine as a card click, `launch`
and `open` WAKE A SLEEPING HOST — which the Linux shell's exec-style `--connect` never did;
it fired a packet at best and dialled into the void. Host references resolve through the
shared `resolve_host`, so `punktfunk launch desk` and `punktfunk://connect/desk` cannot
disagree about which box "desk" is.
Exit codes extend the session's contract so a consumer can branch without parsing prose:
0 ok, 2 connect, 3 trust, 4 renderer, 5 nothing matched what you named, 6 refused because
it needs a person. That last one is why `pair` and `reset` check for a terminal before
prompting — a CLI that blocks a CI job on a hidden question is a hang, not a UX.
Two deliberate non-features. `speed-test` measures and prints but does NOT apply: which
layer a bitrate belongs in is a decision the GUI makes with the user (global vs the bound
profile), and a CLI silently rewriting a profile is exactly the surprise that rule exists
to prevent. And `open` refuses an unknown host rather than pairing it — a URL may never
pair or trust on its own, at any surface.
`library` keeps TSV as its default output because that is what Decky's consumer parses;
`--json` is the door for tools, and the Playnite importer shells to exactly that. The
existing shell flags are untouched: they are a frozen compat contract until Decky migrates.
Verified on .21 against hand-authored stores: hosts list (TSV and JSON, with each host's
resolved profile and pins), profiles list, a launch honouring the binding ("Game"), a
one-off overriding it ("Work"), the same through `open` with `profile=`, and the refusal
codes (6 for an unknown host, 5 for `punktfunk://pair/...`).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>