███████╗██╗ ██╗██╗██████╗ ███████╗ ██╗ ██╗ ██████╗ ███████╗████████╗
╚══███╔╝██║ ██║██║██╔══██╗██╔════╝ ██║ ██║██╔═══██╗██╔════╝╚══██╔══╝
███╔╝ ██║ █╗ ██║██║██████╔╝█████╗█████╗███████║██║ ██║███████╗ ██║
███╔╝ ██║███╗██║██║██╔══██╗██╔══╝╚════╝██╔══██║██║ ██║╚════██║ ██║
███████╗╚███╔███╔╝██║██║ ██║███████╗ ██║ ██║╚██████╔╝███████║ ██║
╚══════╝ ╚══╝╚══╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝
[UNIVERSAL LOCAL HOST // SYSINFO · FS · EXEC · PTY · KV · OS]
"One pipe. One binary. The whole machine — reachable from anywhere."
zwire-host is a single self-contained Rust binary (~500 KB, no Python, no
psutil) that exposes the local machine to any app over one JSON message
protocol. It began as the Chrome native-messaging host for
zwire's HUD; it is now a
universal local endpoint you can talk to from a browser extension and from
tmux, emacs, desktop apps, plugins, shell scripts, and any language — because it
also runs as a Unix-socket daemon speaking newline-delimited JSON, the one
protocol every tool already has.
It streams live system stats (sysinfo), runs PTY terminals
(portable-pty), crawls and watches/tails the filesystem, execs
commands, runs background jobs that notify on completion, lists/kills
processes, brokers a pub/sub event bus that federates across a mesh of
peered hosts, keeps a per-app key/value store, and does
clipboard / notify / open. Every capability is reachable over every
transport, and the whole thing is also a Rust library so sibling hosts (e.g.
zpwrchrome-host) can embed it.
zwire · zpwrchrome · strykelang
Table of Contents
- [0x00] Overview
- [0x01] Transports
- [0x02] Protocol / Commands
- [0x03] CLI
- [0x04] Library use (embed as a dependency)
- [0x05] Chrome install
- [0x06] Build · Cross-Platform · CI
- [0x07] License
[0x00] Overview
Extensions, editors, and plugins can't read the machine or spawn a shell.
zwire-host does the privileged work once and hands it to everyone: a live
statusbar (cpu / mem / net / battery / temp …), an embedded terminal, a
filesystem crawler, a command runner, and a small state store. Shipping it as
one static Rust binary means the consuming bundle has zero runtime
dependencies — no system Python, no pip install psutil, nothing to break on
a fresh machine.
[0x01] Transports
Both transports feed the same dispatcher, so every command below works over either one.
| Transport | For | Framing |
|---|---|---|
| Native messaging (default) | Chrome / browser extensions | little-endian u32 length + JSON body, on stdin/stdout |
Local-socket daemon (serve) |
tmux, emacs, desktop apps, plugins, any language | newline-delimited JSON (one object per line) |
The daemon uses each platform's native local IPC — a Unix domain socket on macOS/Linux and a named pipe on Windows — so it runs everywhere your apps do:
- macOS / Linux —
$ZWIRE_HOST_SOCK, else$XDG_RUNTIME_DIR/zwire-host.sock, else~/.zwire/host.sock. Created0600under a0700dir — owner-only, since it exposesexec/fs/pty. - Windows —
$ZWIRE_HOST_SOCK, else the per-user pipe\\.\pipe\zwire-host-<user>. (--socket <name>overrides the pipe name.)
Requests may carry an id; it is echoed on the matching reply so a client can
multiplex many in-flight requests, streams, and terminals over one connection.
[0x02] Protocol / Commands
Discovery & state
| Message | Reply / effect |
|---|---|
{"cmd":"hello"} |
{ok,host,version,os,arch,pid,caps:[…]} — feature-test the host. |
{"cmd":"hostinfo"} |
one-shot machine facts: os, arch, kernel, hostname, user, cpus, mem, LAN ip. |
{"cmd":"kv_set","app":"myapp","key":"cfg","value":{…}} |
write ~/.myapp/kv/cfg.json. |
{"cmd":"kv_get" / "kv_merge" / "kv_del" / "kv_keys",…} |
read / shallow-merge / delete / list keys. |
System stats
| Message | Reply / effect |
|---|---|
{"cmd":"sysinfo_once"} |
one {sys:{…}} snapshot. |
{"cmd":"sysinfo_start","interval_ms":2000} |
stream {sys:{…}} every interval — cpu · mem · swap · disk · net rate · disk I/O rate · load · uptime · battery · temp · host · LAN/WAN ip. |
{"cmd":"sysinfo_stop"} |
stop the stream. |
Filesystem (paths accept a leading ~)
| Message | Reply / effect |
|---|---|
{"cmd":"fs_read","path":…} |
{ok,b64,text?}. |
{"cmd":"fs_write"/"fs_append","path":…,"text"|"b64":…} |
write / append. |
{"cmd":"fs_list","path":…} |
one-level {entries:[{name,dir,size}]}. |
{"cmd":"fs_walk","path":…,"depth"?,"ext"?,"dirs_only"?,"contains"?} |
recursive crawl → {count,truncated,entries:[{path,name,dir,size}]}. |
{"cmd":"fs_stat" / "fs_mkdir" / "fs_rm","path":…} |
stat / mkdir -p / remove (recursive for dirs). |
File watching (streaming observers, keyed by id)
| Message | Reply / effect |
|---|---|
{"cmd":"fs_watch","id"?,"path":…,"recursive"?,"interval_ms"?} |
stream {"ev":"fs","kind":"created|modified|removed","path":…} on change. |
{"cmd":"fs_tail","id"?,"path":…,"from"?:"start"} |
stream {"ev":"line","data":…} as lines are appended (tail -f; survives rotation). |
{"cmd":"watch_stop","id"?} / {"cmd":"watch_list"} |
stop an observer / list active ones. |
Exec & OS
| Message | Reply / effect |
|---|---|
{"cmd":"exec","program":…,"args":[…],"cwd"?,"env"?,"stdin"?} |
run to completion → {ok,code,stdout,stderr} (base64). |
{"cmd":"open","target":…} |
open a path/URL with the OS default handler. |
{"cmd":"clipboard_get"} / {"cmd":"clipboard_set","text":…} |
read / write the clipboard. |
{"cmd":"notify","title":…,"body":…} |
desktop notification. |
Background jobs (long-running commands; run in the daemon, survive the connection)
| Message | Reply / effect |
|---|---|
{"cmd":"job_start","program":…,"args":[…],"label"?,"notify"?} |
spawn a background job → {ok,job:<id>} immediately; fires a desktop notification on completion (notify, default true). |
{"cmd":"job_list"} |
non-destructive status of every job → [{id,label,running,code}]. |
{"cmd":"job_result","id":N} |
fetch+remove one finished job → {code,stdout,stderr} (base64). |
{"cmd":"job_poll"} |
drain all finished jobs at once. |
Process tools
| Message | Reply / effect |
|---|---|
{"cmd":"ps","filter"?,"limit"?} |
processes by memory → [{pid,name,mem,cpu}]. |
{"cmd":"kill","pid":N,"signal"?} |
signal a process (term default, or kill). |
{"cmd":"which","program":…} |
resolve a program to its $PATH location → {path}. |
Pub/sub event bus (the host as a coordination hub across apps)
| Message | Reply / effect |
|---|---|
{"cmd":"sub","topic":…} |
subscribe this connection; thereafter receive {"ev":"pub","topic":…,"data":…} frames. |
{"cmd":"unsub","topic":…} |
stop receiving a topic. |
{"cmd":"pub","topic":…,"data":…} |
fan a message out to every subscriber → {ok,delivered:N}. |
The daemon itself publishes on scheme / ui whenever those change, so a
subscribed app (a HUD, an editor) gets live theme sync without polling.
stryke hooks & scripting (runs stryke via a bundled sidecar — the browser never spawns it directly)
| Message | Reply / effect |
|---|---|
{"cmd":"hooks_events"} |
lifecycle-event catalog + action verbs → {events:[…],actions:[…]}. |
{"cmd":"hooks_save","hook":{name,event,enabled,timeout_ms?}} |
create/update a hook (scaffolds a starter <id>.st) → {ok,hook}. |
{"cmd":"hooks_list" / "hooks_delete" / "hooks_set_enabled" / "hooks_get_script" / "hooks_set_script" / "hooks_script_path",…} |
manage hooks + their stryke scripts. |
{"cmd":"hook_fire","event":…,"payload":{…}} |
run every enabled hook bound to event; each script's {actions:[…]} is dispatched (notify/open/exec/pub). |
{"cmd":"hooks_test_run","id":…,"sample":{…}} |
dry-run a hook (parses actions, does not dispatch). |
{"cmd":"stryke_run","code":"p 1+1","stdin"?} |
run inline stryke (stryke -E) → {ok,stdout,stderr,code,timedOut}. |
{"cmd":"stryke_lsp_start" / "stryke_lsp_send" / "stryke_lsp_stop",…} |
drive a per-connection stryke --lsp server; frames arrive as {"ev":"stryke-lsp-rx","message":…}. |
stryke is resolved via ZWIRE_STRYKE → the sibling next to this host (the
bundled sidecar) → $PATH → cargo/Homebrew, so an installed zwire needs no
system stryke.
Host-to-host peering (a mesh of daemons across machines)
Run daemons with TCP peering and the bus federates across machines — a
publish (or a scheme/ui change) on one host reaches subscribers on every
peer — and you can run a request on another host:
# machine A: listen for peers
# machine B: listen, and dial A
| Message | Reply / effect |
|---|---|
{"cmd":"peers"} |
{self, peers:[…]} — connected peers. |
{"cmd":"peer_connect","addr":"host:port"} |
dial a new peer at runtime. |
{"cmd":"remote","peer":"host:port","request":{…}} |
run a request on another host → {reply:…}. |
Inbound TCP is gated by a shared --token (or $ZWIRE_HOST_TOKEN): a connection
must auth / peer_hello with it before anything privileged. Local Unix-socket
clients are trusted and never need it. Federation is single-hop (a forwarded
event is delivered locally but not re-forwarded), which covers star and
fully-meshed topologies without loops.
PTY terminals (multiplexed by id)
| Message | Reply / effect |
|---|---|
{"cmd":"pty_spawn","id"?,"rows":R,"cols":C,"shell"?,"args"?,"cwd"?,"env"?} |
spawn a shell; stream {ev:"output","b64":…} (and pty:id when keyed). |
{"cmd":"pty_write","id"?,"data"|"b64":…} |
feed input. |
{"cmd":"pty_resize","id"?,"rows":R,"cols":C} / {"cmd":"pty_kill","id"?} |
resize / kill; kill emits {ev:"exit"}. |
Legacy zwire scheme/ui (unchanged): {"cmd":"get"} (replies with version +
scheme + ui), {"scheme":"matrix"}, {"ui":{…}} bridge ~/.zwire/hud-scheme
~/.zwire/hud-ui.json.
[0x03] CLI
& # run the socket daemon
|
From any tool that can write a line to the endpoint — no client library
needed. zwire-host call is the portable path; or connect to the socket/pipe
directly:
# macOS / Linux — raw Unix socket
|
# any platform — via the bundled client
[0x04] Library use (embed as a dependency)
The crate is a library too (zwire_host), so sibling hosts can pull it in to
crawl and exec without re-implementing anything:
[]
= { = "https://github.com/MenkeTechnologies/zwire-host" }
use api;
// crawl the filesystem
for e in walk
// run a command, get bytes back
let out = exec.unwrap;
println!;
Or drive the whole dispatcher yourself over any transport with
zwire_host::{Peer, Session}, or just delegate main to
zwire_host::run(std::env::args().skip(1).collect()).
[0x05] Chrome install
Point a native-messaging host manifest's path at the binary and list the
allowed extension origins:
Drop it in the browser's NativeMessagingHosts/ directory (or the profile's).
zwire's scripts/localinstall.sh builds this binary and wires the manifest
automatically when packaging the .app.
[0x06] Build · Cross-Platform · CI
sysinfo and portable-pty abstract the OS, so the same source builds for
macOS · Linux · Windows. Both transports work on all three: native messaging
everywhere, and the serve/call daemon over Unix domain sockets on macOS/Linux
and named pipes on Windows (via interprocess, a Windows-only dependency).
Battery reporting is native on every platform: pmset on macOS,
/sys/class/power_supply on Linux, and GetSystemPowerStatus on Windows —
absent on machines with no battery (desktops, VMs), where the segment is omitted.
CI runs the four canonical polish gates on Ubuntu + macOS + Windows:
[0x07] License
MIT © MenkeTechnologies