punktfunk
Recipe card from the charly-check plugin (Commands — runtime CLI verbs).
punktfunk — streaming-host check verb
Section titled “punktfunk — streaming-host check verb”Overview
Section titled “Overview”punktfunk: drives a running punktfunk
host through its management REST API. It both probes a host and manages
one, served out-of-process over go-plugin gRPC by
opencharly/plugin-punktfunk.
Install the host itself with /charly-punktfunk:punktfunk-host.
Why it is not the http: verb
Section titled “Why it is not the http: verb”http: already does URL + status + body/header matchers. This verb exists for
what http: structurally cannot do:
- Token discovery. The bearer token is generated per host at first start and
lives at
~/.config/punktfunk/mgmt-tokeninside the venue. An authoredhttp:step cannot read a per-deploy secret out of a container or guest. (The file is an env-file line,PUNKTFUNK_MGMT_TOKEN=…, not a bare token — sending the raw line yields a clean 401.) - Domain semantics. Methods map to endpoints and return real verdicts, with
json_path:to assert one field instead of pattern-matching a JSON blob. events. A bounded SSE subscription is the only way to assert a transition; polling/api/v1/hostobserves a state, never that a stream started.
The request runs INSIDE the venue
Section titled “The request runs INSIDE the venue”punktfunk binds the management API to loopback by design (the web console is the
network-facing surface). A host-vantage request cannot reach a loopback-bound
listener in a container, and the two ways to make it reachable — republishing
the port or a socat relay — would weaken exactly that posture. So the verb
issues its request in-venue over the reverse channel, which also puts it on the
same side as the token. curl must exist in the venue; its absence is
reported as such.
Methods
Section titled “Methods”| Group | Methods |
|---|---|
| host / health | health status diagnostics diagnostics-refresh compositors gpus plugins hooks |
| native pairing | pair-status pair-arm pair-disarm pending approve deny pin |
| paired clients | clients rename access unpair |
| library | library scanners scanner-toggle |
| virtual displays | display-state display-monitors display-settings display-release |
| lifecycle | actions action-invoke end-game |
| stream | events |
Client methods — the headless punktfunk CLI
Section titled “Client methods — the headless punktfunk CLI”The groups above are the HOST management API. These are the CLIENT half, served
by the headless punktfunk CLI that every Linux client package ships, so a
fleet bed can drive both ends of a stream in charly verbs instead of shelling
out to command:.
| Group | Methods |
|---|---|
| saved hosts | hosts-list (probe:) hosts-add hosts-forget |
| enrolment | pair (pin:) |
| streaming | launch (game:) open (auto_approve:) speed-test |
| client-side reads | client-library reachable profiles-list |
| maintenance | wake client-reset |
They take host: — the documented <host-ref>, a saved name or host[:port] —
and run inside the venue where punktfunk-client is installed. client_bin:
overrides the binary for the Flatpak build, which is not on PATH.
The PIN travels on stdin, never argv. Upstream documents --pin <value> as
visible in the process list and --pin - as the scripting form; pair uses the
latter. Omitting pin: selects the console-approval flow instead, where the
client requests and a punktfunk: approve step on the host completes it — which
is the flow an automated fleet bed wants, because no secret crosses the member
boundary.
Exit codes are mapped, not collapsed. The CLI documents 0 success,
2 connection failed, 3 trust rejected (re-pair needed), 4 renderer startup
failed, 5 no match for the reference, 6 interactive action required. Each
becomes a distinct verdict message, so a bed reports which thing broke rather
than that something did. 6 is the one to watch in automation: it means the CLI
wanted a human, i.e. the unattended contract is broken.
health is the only unauthenticated route, so it works before a token exists —
which makes it the right first probe on a fresh host.
Mutating methods are refused in a check: step. pair-arm, approve,
deny, unpair, rename, access, scanner-toggle, display-release,
action-invoke, end-game, pin and diagnostics-refresh change host state
and must be authored as run: steps, so a probe can never silently unpair a
device or reboot a machine.
Input fields
Section titled “Input fields”| Field | Meaning |
|---|---|
method |
the operation (also the scalar-sugar primary: punktfunk: status) |
port |
management API port (default 47990) |
token / token_file |
override the bearer; default is ~/.config/punktfunk/mgmt-token in the venue |
verify_tls |
require a verifiable chain. Default off — punktfunk serves a self-signed cert. Named this way (not insecure) so the ZERO VALUE is the correct default, which a bool could not express otherwise. |
fingerprint / pending_id / action_id / provider_id |
the target of a per-entity method |
name / access / pin / enabled |
payloads for rename / access / pin / scanner-toggle |
kinds / count / event_timeout |
the events subscription window |
json_path |
dotted path into the response; stdout becomes that value |
Scalar shorthand works — but the plugin candy must be in scan range
Section titled “Scalar shorthand works — but the plugin candy must be in scan range”punktfunk: status # scalar sugarpunktfunk: # map form — identical meaning method: statusBoth parse. The sugar is enabled by this candy’s plugin.primary: {punktfunk: method}, which charly reads in a PRE-CONNECT prescan — before any plugin is
connected — so the declaration has to be reachable at parse time.
That is the one real requirement: the plugin candy must be composed into the
box that authors the step. charly reads primary: off candies in scan range,
so a bed that authors punktfunk: status without composing this candy fails with
plugin verb "punktfunk" takes a MAP input(it declares no primary field for the scalar shorthand)and that failure is not local to the offending step — it blocks the parse for every bed sharing the project closure.
Historical note. Until 2026-08-30 the same error appeared even when the candy
WAS composed, whenever it arrived by @github ref rather than being discovered
locally: the remote-ref prescan leg was gated behind a local discover: block.
That was opencharly/charly#471, fixed by charly#473 — verified by an A/B on the
identical fixture, which fails on v2026.242.0649 and passes on the fix. Do not
reintroduce the map-form workaround this file used to prescribe.
Examples
Section titled “Examples”# Liveness, before any token exists.- check: the host's management API reports itself live punktfunk: method: health stdout: [{contains: '"status":"ok"'}] eventually: 90s retry_interval: 5s context: [runtime]
# Assert ONE field instead of matching a blob.- check: the host reports ok punktfunk: method: health json_path: status stdout: [{equals: ok}] context: [runtime]
# Managing is a run: step, never a check:.- run: arm native pairing so a device can enrol punktfunk: method: pair-arm
# Wait for a TRANSITION rather than polling a state.- check: a stream actually started punktfunk: method: events kinds: "stream.started" count: 1 event_timeout: 60s context: [runtime]Cross-References
Section titled “Cross-References”/charly-punktfunk:punktfunk-host— the candy that installs the host/charly-check:check— the check orchestrator, beds and the R10 sequence/charly-internals:plugin— the out-of-process plugin model this verb follows