zwire-host 0.3.1

Universal local host: system stats, filesystem, exec, PTY & kv store, reachable from Chrome native-messaging or a Unix-socket JSON daemon
Documentation
███████╗██╗    ██╗██╗██████╗ ███████╗    ██╗  ██╗ ██████╗ ███████╗████████╗
╚══███╔╝██║    ██║██║██╔══██╗██╔════╝    ██║  ██║██╔═══██╗██╔════╝╚══██╔══╝
  ███╔╝ ██║ █╗ ██║██║██████╔╝█████╗█████╗███████║██║   ██║███████╗   ██║
 ███╔╝  ██║███╗██║██║██╔══██╗██╔══╝╚════╝██╔══██║██║   ██║╚════██║   ██║
███████╗╚███╔███╔╝██║██║  ██║███████╗    ██║  ██║╚██████╔╝███████║   ██║
╚══════╝ ╚══╝╚══╝ ╚═╝╚═╝  ╚═╝╚══════╝    ╚═╝  ╚═╝ ╚═════╝ ╚══════╝   ╚═╝

CI License: MIT platforms docs

[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

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. Created 0600 under a 0700 dir — owner-only, since it exposes exec/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.

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
zwire-host serve --tcp 0.0.0.0:7420 --token SECRET --name laptop
# machine B: listen, and dial A
zwire-host serve --tcp 0.0.0.0:7420 --token SECRET --name desktop --peer A.local:7420
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"}, {"scheme":"matrix"}, {"ui":{…}} bridge ~/.zwire/hud-scheme + ~/.zwire/hud-ui.json.

[0x03] CLI

zwire-host serve &                                   # run the socket daemon
zwire-host call '{"cmd":"hostinfo"}'                 # one request, one reply
zwire-host call '{"cmd":"fs_walk","path":"~/src","ext":"rs"}'
echo '{"cmd":"exec","program":"git","args":["status"]}' | zwire-host call
zwire-host call --stream '{"cmd":"sysinfo_start"}'   # keep printing frames

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
printf '{"cmd":"sysinfo_once"}\n' | nc -U ~/.zwire/host.sock
# any platform — via the bundled client
zwire-host call '{"cmd":"sysinfo_once"}'

[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:

[dependencies]
zwire-host = { git = "https://github.com/MenkeTechnologies/zwire-host" }
use zwire_host::api;

// crawl the filesystem
for e in api::walk("~/src", Some("rs")) {
    println!("{}", e.path.display());
}

// run a command, get bytes back
let out = api::exec("git", ["status", "--porcelain"]).unwrap();
println!("exit {:?}: {}", out.code, out.stdout_str());

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:

{ "name": "com.zwire.hud", "type": "stdio",
  "path": "/abs/path/to/zwire-host",
  "allowed_origins": ["chrome-extension://<id>/"] }

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

cargo build --release          # -> target/release/zwire-host (~500 KB)
cargo test                     # exercises the protocol over both transports

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:

cargo fmt --all --check
cargo clippy --all-targets -- -D warnings
cargo doc --no-deps                        # RUSTDOCFLAGS=-D warnings
cargo test

[0x07] License

MIT © MenkeTechnologies