Skip to content

jetkvm

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

JetKVM — IP-KVM control from a jetkvm: check verb

Section titled “JetKVM — IP-KVM control from a jetkvm: check verb”

jetkvm: is a DECLARATIVE check/control verb — authored as jetkvm: <method> inside a candy/box plan check:/run: step. It is NOT a host charly check subcommand: there is no charly check jetkvm. The verb’s implementation and its WebRTC/HID client dependency live in the out-of-tree candy/plugin-jetkvm plugin module; at check time the host dispatches jetkvm: through the provider registry to that out-of-process plugin, exactly like vnc:/adb:/cdp:.

It drives a JetKVM (https://jetkvm.com) with no browser, over the device’s own control plane: local login (POST /auth/login-local) → the signaling websocket (/webrtc/signaling/client) → the rpc JSON-RPC 2.0 data channel and the binary hidrpc input channel, with the H.264 video track decoded to a PNG by ffmpeg (required on the host).

The method name is the scalar value for a bare-method step (jetkvm: status), or the method: key of the jetkvm: map when the step carries jetkvm-exclusive fields (host:, x:, y:, text:, key:, artifact:, …) — those live INSIDE the jetkvm: map. Only the shared matchers (stdout:, stderr:, exit_status:) and context:/id:/timeout: stay siblings. All jetkvm: steps are deploy-context only (they need a reachable device), so author them with context: [runtime].

- check: the JetKVM is reachable and its control channel answers
context: [runtime]
jetkvm:
method: status
host: jk.example.ts.net
stdout:
- contains: "rpc: true"
- check: a fresh framebuffer is captured as a real PNG
context: [runtime]
jetkvm:
method: screenshot
host: jk.example.ts.net
artifact: /tmp/kvm.png
artifact_min_bytes: 20000

READ-ONLY BY DEFAULT — the device-safety gate

Section titled “READ-ONLY BY DEFAULT — the device-safety gate”

A JetKVM is a physical appliance wired into a real machine’s keyboard, video and power. Unlike a container it is NOT disposable, and a stray keystroke or power action has real consequences. So every mutating method requires allow_control: true; without it the step reports a documented skip naming the gate rather than acting. factory-reset and update are refused even WITH allow_control, because no unattended plan may wipe or reimage a device.

The classification is an allowlist, so a method added later is mutating-by-default and cannot touch a device until it is deliberately classified.

Read-only (no gate): status, screenshot, version, video-state, usb-state, atx-state, dc-state, virtual-media-state, storage-files, wol-devices, macros, keyboard-layout, timezones, cloud-state, network-state, network-settings, tailscale-status, update-status, devmode-state, ssh-key, tls-state, extensions, public-ip, diagnostics, check-media-url, usb-config.

Mutating (need allow_control: true): key, type, key-combo, macro, click, mouse, move, scroll, drag, power, dc-power, reboot, wol, virtual-media, usb-device, usb-emulation, set-settings, set-edid, set-video, set-display, set-audio, set-network, set-tailscale, set-devmode, set-ssh-key, set-tls, set-keyboard-layout, set-macros, set-jiggler, set-extension, set-wol-devices, set-log-level, renew-dhcp, rpc.

Never autonomous: factory-reset, update.

The rpc method is the raw escape hatch: author rpc_method: (the device JSON-RPC method) and optionally rpc_params: (a JSON object), so a method the plugin has not typed yet is reachable without a release. It is deliberately MUTATING — it can invoke ANY device method, so an ungated escape hatch would be a bypass of the safety gate — and it requires allow_control: true. Even then it refuses the irreversible reimaging methods (factoryReset, tryUpdate, tryUpdateComponents), matching the never-autonomous rule its typed counterparts obey.

Prefer password_secret: (a key in the verb:credential store, resolved peer-to-peer over the reverse channel) or the JETKVM_AUTH_TOKEN / JETKVM_PASSWORD environment variables. An auth_token: may also be authored directly when a session already exists. Never commit a device password to a charly.yml.

host accepts a bare hostname, host:port, or an explicit http(s):// URL; a bare value defaults to the device’s plaintext scheme. Set insecure: true to accept the self-signed certificate a JetKVM ships by default (needed for an https:// device; also required for the signaling websocket).

The device address is resolved in order: the authored host:, then the JETKVM_HOST environment variable, then the deploy venue’s address. Author no host: and set JETKVM_HOST to keep a device-specific hostname out of a committed plan — the same shape JETKVM_AUTH_TOKEN / JETKVM_PASSWORD already give credentials. No device hostname belongs in a repository.

Input methods: move/mouse are always a pure position move — an authored button: is validated but never sent. click/drag press the button, which defaults to left. x/y (and from_x/from_y) are absolute HID pointer coordinates in [0,32767], not desktop pixels; map a desktop pixel (px,py) on a W×H screen with px*32767/(W-1), py*32767/(H-1) (centre of 1920×1080 ≈ 16384,16384). key and key-combo resolve over the common USB HID Keyboard/Keypad usages — letters az, digits 09, F1F12, the navigation/editing keys (Enter, Escape, Tab, arrows, Home, End, PageUp, PageDown, Insert, Delete, Backspace), punctuation, and modifier chords like Control_L+Alt_L+Delete; names outside that set fail with unknown key. Uppercase or shifted symbols (A, !) imply Shift.

NOTE on video: do not gate a screenshot on the device’s getVideoState.ready field. Measured on firmware 0.5.9, ready reports whether the device’s native capture PIPELINE is currently running — which is started per WebRTC session and stopped when the last one disconnects — not whether an HDMI signal is present. It reads false with error:no_signal even while screenshots succeed. The verb’s screenshot method is the authority; it opens the session and waits for a frame.

  • A reachable JetKVM device on the network
  • ffmpeg on the HOST running the check (it decodes the H.264 keyframe to PNG); provided by the layer-ffmpeg candy

A JetKVM runs an armv7l (32-bit ARM) uClibc/BusyBox userland with no package manager, so the distro charly packages cannot be installed on it. To put charly on the appliance, use the opencharly/charly-jetkvm tool, which delivers the published release binary + welded plugin set over ssh:

Terminal window
charly-jetkvm install --host [email protected]
charly-jetkvm verify --host [email protected]
charly-jetkvm status --host [email protected]
charly-jetkvm uninstall --host [email protected]

It downloads the release assets on the HOST (gh, TLS-validated) and streams them over ssh into /userdata/charly — the device’s own wget does not validate TLS, so an on-device download would be MITM-exposed. verify asserts charly version equals the requested CalVer and that a baked command word dispatches project-less.

The same tool derives the jetkvm: verb’s environment from the ONE ssh connection — charly-jetkvm env --host root@<device> --export reads the device’s local_auth_token and prints JETKVM_HOST + JETKVM_AUTH_TOKEN, so a device can be driven with no committed hostname and no hand-copied token:

Terminal window
eval "$(charly-jetkvm env --host [email protected] --export)"
charly check run jetkvm-device-readonly # status + screenshot
charly check run jetkvm-input-probe # move/click/drag/key/type + screenshots

There is deliberately NO charly mcp serve on the device. The appliance has ~199 MB RAM and one core; running the MCP server forked a full CLI model and exhausted memory, hanging the device. The appliance gets charly only for local project-less verbs. Driving the KVM remains the host-side jetkvm: verb above, which talks to the device’s control plane directly and needs no charly on it.

  • /charly-check:check — parent router; the jetkvm: verb dispatches out-of-process via candy/plugin-jetkvm.
  • /charly-check:vnc — the sibling desktop-automation verb (RFB for a container/VM display rather than a physical KVM).
  • /charly-check:adb — the sibling out-of-process device verb.
  • /charly-image:layer — layer authoring; the ordered plan: step list is part of every charly.yml.

MUST be invoked when the task involves a JetKVM device, the jetkvm: check verb, IP-KVM remote control (keyboard/video/mouse or power over the network), or capturing a screenshot from a machine via a JetKVM. Invoke this skill BEFORE reading source code or launching Explore agents.