Help us shape the future of Go! Take the 2026 Go Developer Survey

clihv

package
v1.3.97 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 27, 2026 License: AGPL-3.0 Imports: 26 Imported by: 0

Documentation

Overview

Package clihv cmd/skywire-cli/commands/hv/bidi.go c4-vis-cli WebDriver BiDi support for the `hv` browser rigs, so `hv eval` and `hv probe` drive Waterfox/Firefox as well as Chromium/Brave.

Waterfox 6.6 (Firefox ESR 128+) dropped the CDP Remote Agent, so --remote-debugging-port there speaks BiDi and nothing else. Until now the only way to drive it was a clone out of tree — exactly the failure mode `hv shell` warns about, and it matters precisely when it is needed: a Brave tab wedged badly enough that CDP never answered `1+1` left the second browser as the only way to take a measurement.

Which protocol a port speaks is not a guess. GET /json/version answers on Chromium and 404s on Firefox, where /session answers 400 instead, so the detection below probes once and says which it picked.

The single-session rule

Firefox permits ONE active BiDi session and does not release it when the owning socket drops. That makes the naive one-shot — dial, evaluate, exit — a trap: interrupt it once and the session is stranded until the browser restarts, which on a browser holding hours of state is an expensive mistake to make from a CLI. Two paths are offered instead, and both are honest about the cost:

  • `hv drive` holds one long-lived session and tab and exposes them over a local control port; `hv eval --driver` and `hv probe --driver` then spend no session at all. This is the right answer when a browser is going to be driven more than once, which is every real session.
  • Without --driver, the one-shot path releases the session on every exit path — a deferred End, plus a signal handler for SIGINT/SIGTERM — and waits out a lagging prior teardown rather than failing on the first "Maximum number of active sessions".

The driver, its recovery layer and the control port are github.com/0magnet/ wfdrive/bidi, imported rather than copied. It adds no transitive dependency: its only requirement is github.com/coder/websocket, the same client the CDP commands here already use.

Package clihv cmd/skywire-cli/commands/hv/eval.go c4-vis-cli `hv eval` evaluates JavaScript in a browser, over CDP or WebDriver BiDi.

On Chromium/Brave it addresses a SPECIFIC CDP target by its webSocketDebuggerUrl — so an OOPIF iframe, a worker or a second wasm instance can be inspected directly, which URL-matching (cmd/hvinspect) cannot disambiguate when several targets share a URL:

curl -s localhost:9222/json/list | jq -r '.[] | "\(.type) \(.url) \(.webSocketDebuggerUrl)"'
skywire cli hv eval ws://localhost:9222/devtools/page/ABC 'typeof skywireVisor'

On Waterfox/Firefox there is no per-target websocket to address: BiDi is one endpoint and one session for the whole browser, so give the debug port and the protocol is detected:

skywire cli hv eval --port 9223 'document.title'
skywire cli hv eval --driver 127.0.0.1:9224 'document.title'

Prefer --driver, pointed at a running `hv drive`: Firefox allows one BiDi session and does not release it when a socket drops, so a session per invocation is a hazard the persistent driver exists to remove. Without it this releases the session on every exit path, signals included.

This was cmd/cdpeval, a standalone binary that never shipped with skywire; it lives here so it is present wherever the CLI is, alongside `hv shell` (which drives the visor terminal) and `hv drive`.

Package clihv cmd/skywire-cli/commands/hv/input.go c4-vis-cli `hv input` dispatches TRUSTED pointer input into a browser over CDP.

This exists because `hv eval` cannot test what it looks like it can. A synthetic event built in the page — new MouseEvent(...) then dispatchEvent — invokes the listener directly, so it fires no matter where a real pointer is. That is fine for checking a handler runs, and useless for anything about how the browser ROUTES pointer input: notably, that an <iframe> is its own browsing context and swallows the mousemove and mouseup of a drag that crosses it. A synthetic drag passes whether or not that bug is present.

Input.dispatchMouseEvent goes in at the browser level instead, so the events traverse — and are swallowed by — exactly what a hand at the mouse would. That makes the whole "a frame ate it" class testable without a person:

# raise a framed window by clicking the page inside it
skywire cli hv input ws://localhost:9222/devtools/page/ABC --click 400,300

# drag a title bar along a path that crosses another window's frame
skywire cli hv input ws://localhost:9222/devtools/page/ABC --drag 120,40:900,500

Coordinates are viewport CSS pixels, the same space getBoundingClientRect reports, so a test can read a target's box with `hv eval` and aim at it.

CDP only: WebDriver BiDi has input.performActions for the same job, but the BiDi path here addresses Firefox, where this class of bug is chased through `hv drive`.

Package clihv cmd/skywire-cli/commands/hv/notify.go c4-vis-cli

The desktop host-app sink for the visor-global notification hub: subscribes to a visor's notification stream and posts each event to THIS machine's notification center.

It exists because "the visor" and "the user" are often not the same machine:

  • The visor runs in a container (the docker e2e env) or on a headless box. pkg/osnotify finds no desktop session there and the hub's host-OS tier is dead, so notifications silently drop no matter what the apps publish.
  • You browse a visor's app UI over the LAN. Plain http on a non-loopback address is not a secure context, so the browser's Notification API is unavailable and the UI can't alert you either — and the visor's own host-OS notification would pop on the visor's machine, where nobody is sitting.

Run this where the user actually is and both gaps close. It is the desktop counterpart of the Android foreground service, speaking the same SSE contract (see pkg/visor/hypervisor_handlers_notify.go), which is exactly what the hub's host-app tier was designed for.

While it is connected the visor stops posting its own host-OS notifications (subscriber presence outranks the OS sink), so events are never doubled.

Package clihv cmd/skywire-cli/commands/hv/probe.go c4-vis-cli `hv probe` watches one page load and streams what the page reports: console output, uncaught exceptions, renderer crashes and load progress.

It exists because "the tab is not answering" is not a diagnosis. A busy main thread, a page with no execution context, a stalled navigation and a crashed renderer all look identical from outside — `hv eval` simply returns nothing for every one of them, and guessing which it is has cost several wrong diagnoses. Watching the event stream during a load tells them apart, and it is how the orphaned-SharedWorker bug was found: the page logged its fallback at 45.7s, which named the failing wait exactly.

skywire cli hv probe --navigate http://127.0.0.1:7998/ --seconds 90
skywire cli hv probe --ws ws://localhost:9222/devtools/page/ABC --eval 'typeof skywireVisor'
skywire cli hv probe --port 9223 --navigate https://127.0.0.1:8443/ --seconds 30

With no --ws it opens a fresh tab, which is usually what you want: a reused tab carries the state of whatever ran in it before.

Waterfox/Firefox speaks WebDriver BiDi rather than CDP and is detected by --port; the stream is the same shape, minus the renderer-crash event, which BiDi has no equivalent for. Prefer --driver against a running `hv drive` when the browser will be probed more than once — see bidi.go on why a session per invocation is a hazard there.

Package clihv cmd/skywire-cli/commands/hv/root.go c4-vis-cli

Package clihv cmd/skywire-cli/commands/hv/serve.go c4-vis-cli

Package clihv cmd/skywire-cli/commands/hv/shell.go c4-vis-cli `hv shell` drives the visor shell in a Chromium/Brave tab over CDP: it opens the hypervisor UI, opens the console window, types commands into the terminal and captures what came back.

It exists because the other browser rigs cannot type. cmd/hvinspect and `hv eval` evaluate JS, and `hv drive` drives Waterfox over BiDi — but the visor shell is an xterm canvas fed by keyboard events, so verifying that a shell applet works means synthesizing real key events and reading the result back. Doing that by hand, or from a throwaway script, is how a regression in an applet goes unnoticed.

This is the one `hv` browser verb that is CDP-only. Typing needs BiDi's input.* module, which the driver behind `hv drive` does not implement, and a half-implemented typist that silently drops keys into a canvas would be worse than none: the screenshot looks the same either way. Drive the shell in Chromium/Brave; use `hv eval --port <bidi>` to read state out of Waterfox.

Attach to a browser started with --remote-debugging-port=9222:

skywire cli hv shell --url http://127.0.0.1:7998/ pk aliases
skywire cli hv shell --url http://127.0.0.1:8123/ --no-console 'echo hi'

Each argument is one command line typed into the shell. The screenshot and the page console go to --out, and every console error and uncaught exception is printed, because a hung applet looks exactly like a working one in a screenshot until you read the log.

Index

Constants

This section is empty.

Variables

View Source
var RootCmd = &cobra.Command{
	Use:   "hv",
	Short: "Hypervisor / wasm-visor tools",
	Long: `Tools for serving, driving and bridging the hypervisor UI and the
standalone wasm-visor.

Serve:
  serve    serve the keyless standalone wasm-visor desk over HTTP (reverse-proxy with Caddy)

Desktop bridge:
  notify   show a remote visor's app notifications on THIS machine (SSE bridge)

Browser automation (needs --remote-debugging-port):
  probe    watch one page load and stream console, exceptions and crashes
  eval     evaluate JavaScript in a page
  drive    hold a persistent WebDriver BiDi session on a local control port
  shell    drive the visor shell in a browser tab and capture the result

probe and eval speak CDP to Chromium/Brave and WebDriver BiDi to
Waterfox/Firefox, detected from the debug port. shell is CDP-only: it
synthesizes keystrokes, which BiDi's input.* module would be needed for.

Firefox allows ONE BiDi session and does not release it when a socket drops.
Run drive once and point --driver at it rather than spending a session per
command.`,
}

RootCmd is the `hv` command group: tools for serving, driving and bridging the hypervisor UI / wasm-visor.

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL