devices-common
Shared foundation for the jarvisfriends devices-* family — devices-cpu,
devices-gpu, devices-memory, devices-drive, devices-bios,
devices-monitor, devices-npu. Each category repo implements one small
interface; this module turns that into four consumption surfaces:
- Library — typed snapshots from Go code.
- CLI — one shared flag set for every
devices-* binary.
- TUI — a live dashboard built on snap
(gradient gauges, sparklines, sortable tables), mountable as a
tui-base router page.
- Web — a JSON + SSE API and an embeddable
<jf-devices> web component.
The model
Values split along two axes:
- Kind —
Static values are one-time lookups (model numbers, serial
numbers, specs); the engine caches them after the first read. Dynamic
values change over time (usage, temperatures, clocks, power) and are
re-collected on every request.
- Level —
basic (default) is the cheap common set every platform can
produce. detailed and full progressively add values that cost more to
retrieve: vendor CLIs, WMI queries, privileged files. You opt into the
latency instead of paying it by default.
Everything works without cgo. Builds with cgo can add more (Snapshot.CGO
says which build you got); values that cannot be read are reported as Gaps
with reasons, never silently dropped.
Library
import (
"github.com/jarvisfriends/devices-common/devices"
"github.com/jarvisfriends/devices-cpu/cpu"
)
e := devices.New(cpu.New())
// On demand (static answers are cached after the first call).
snap, err := e.Snapshot(ctx, devices.Detailed)
// Async one-shot.
res := <-e.Request(ctx, devices.Basic)
// Every 0.25 seconds until ctx is canceled.
for res := range e.Stream(ctx, devices.Basic, devices.Every(0.25)) {
fmt.Println(res.Snapshot.Taken, res.Snapshot.Fields)
}
CLI
Every devices-* binary speaks the same grammar — report by default, with
tui and web subcommands:
$ devices-cpu # one snapshot, aligned text
$ devices-cpu --level full # everything we know how to read
$ devices-cpu --static --json # specs/serials as JSON, once
$ devices-cpu --every 0.25 # four samples a second until Ctrl-C
$ devices-cpu --json --every 1 --count 10 # NDJSON, ten samples
$ devices-cpu tui # live dashboard
$ devices-cpu tui --level detailed --every 0.5
$ devices-cpu web :8080 # HTTP API + web component
TUI
tui.Run(engine, level, every) runs the standalone dashboard. The main
view stays readable by design: category-wide values render as gauge and
sparkline rows, and devices render as sortable tables carrying only their
Basic fields — grouped by field shape, so disks and volumes get separate,
cleanly aligned tables instead of one sparse union. Everything deeper is
one interaction away: Enter or double-click a row to open a live detail
popup that collects at Full and shows every value for that one device,
tagged by level.
Mouse works throughout: click a header to sort that column, click a row to
select it, double-click to open details, wheel to scroll the table under
the pointer. tab moves keyboard focus between tables, / filters, l
cycles the level, p pauses, esc closes the popup, q quits.
The same model embeds snap/page.Base, so it drops straight into a
tui-base app:
router.NewWithOptions(router.Options{
AppName: "hw",
ExtraPages: []router.RegisteredPage{{Title: "CPU", Model: tui.NewModel(e, devices.Basic, time.Second)}},
})
Web
web.New(collectors...) hosts any number of categories:
GET /api/devices — hosted categories
GET /api/devices/{category}?level=basic&kind=all|static|dynamic
GET /api/devices/{category}/stream?level=basic&every=0.25 — SSE
GET /devices.js — the web component
GET / — a demo page with one component per category
Embed on any site:
<script src="http://host:8080/devices.js"></script>
<jf-devices category="cpu" every="0.5" level="detailed"></jf-devices>
web.Server is an http.Handler, so it also mounts inside a larger mux.
Implementing a category
type Collector interface {
Category() string
Static(ctx context.Context, level devices.Level) (devices.Snapshot, error)
Dynamic(ctx context.Context, level devices.Level) (devices.Snapshot, error)
}
Build fields with the helpers (devices.Str, devices.Pct, devices.Bytes,
…), put per-instance values on Device entries (stable IDs), report
unreadable values as Gaps, and prefer well-maintained open source sources
(gopsutil, ghw, cpuid, vendor CLIs) over hand-rolled platform code.
License
MIT — see LICENSE.