Skip to content

punktfunk

Recipe card from the charly-check plugin (Commands — runtime CLI verbs).

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.

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-token inside the venue. An authored http: 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/host observes a state, never that a stream started.

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.

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.

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 sugar
punktfunk: # map form — identical meaning
method: status

Both 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.

# 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]