A lightweight terminal client for CoreScope, a real-time analyzer for MeshCore LoRa mesh networks. CoreScope's own web frontend is a full browser app (Leaflet map, live packet feed, charts) which is heavy to run on small hardware. This is a from-scratch terminal reimplementation of the same idea: it talks to a CoreScope instance's existing public REST + WebSocket API and renders the live map, node/observer lists, live packet feed, and RF/topology analytics — no browser, no JS/CSS engine, low memory footprint. It's a thin client only: all decoding and aggregation happens server-side on CoreScope; this just visualizes what the API already provides.
Built with resource-constrained handhelds (e.g. the ClockworkPi uConsole) in mind, but it's a normal Go binary and runs on any Linux/macOS/Windows box with a terminal.
- Live map — ASCII/Unicode world map with nodes plotted at their real position, color-coded by role (repeater/companion/room/sensor/observer)
- Live packet animation — a travelling marker follows each packet's actual multi-hop route in real time, using CoreScope's own hop resolution (
resolved_path); anything without a resolvable hop just pulses at the receiving station - Fly-to search — jump the map to a named location by IATA airport code
- Node picker — cycle through the nodes currently on screen to see name, role, pubkey, and last-seen
- Node / observer tables — sortable by any column (ascending or descending), filterable by role (nodes) or region (observers)
- Live packet feed — scrolling real-time feed with decoded payload type, route type, signal strength
- RF / topology analytics — histogram and summary views, loaded on demand so a busy instance's multi-MB analytics payloads aren't polled in the background
Requires Go 1.24+ to build. The compiled binary is a single static executable with no runtime dependencies — copy it anywhere and run it.
Go modules used (fetched automatically by go build):
| Module | Purpose |
|---|---|
charmbracelet/bubbletea |
Terminal UI framework (Elm-style architecture) |
charmbracelet/bubbles |
TUI components (tables, viewport, text input) |
charmbracelet/lipgloss |
Terminal styling/layout |
gorilla/websocket |
WebSocket client for the live packet feed |
Check what you have first:
go versionThe easiest way, if your distro's repos are recent enough:
sudo apt install golang-goIf that installs a version older than 1.24 (common on Debian/Ubuntu, whose repos often lag behind upstream Go releases), install it from go.dev directly instead. On a uConsole or Raspberry Pi (ARM64):
curl -LO https://go.dev/dl/go1.24.2.linux-arm64.tar.gz
mkdir -p ~/.local
tar -C ~/.local -xzf go1.24.2.linux-arm64.tar.gz
echo 'export PATH=$HOME/.local/go/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
go versionOn a regular amd64 Linux box, swap linux-arm64 for linux-amd64 in the URL. Check go.dev/dl for the current release.
git clone <this-repository-url> corescope-tui
cd corescope-tui(Or copy the project directory over however you'd like — scp, a USB drive, etc. — if you haven't pushed it to a remote yet.)
go build -o corescope-tui ./cmd/corescope-tuiThe first build downloads the Go module dependencies listed above; subsequent builds are fast and fully offline.
Cross-compiling (e.g. building on a laptop to deploy onto a uConsole without needing Go installed there at all):
GOOS=linux GOARCH=arm64 go build -o corescope-tui-arm64 ./cmd/corescope-tuiCopy the resulting binary to the target machine with scp and run it directly — no Go toolchain or any other dependency needed on that machine.
./corescope-tui # connects to the default public instance
./corescope-tui --url https://your-instance.example # or point at a specific CoreScope deployment| Flag | Default | Description |
|---|---|---|
--url |
https://analyzer.00id.net |
CoreScope instance base URL |
--timeout |
15s |
REST request timeout per attempt (one automatic retry is always made on failure) |
--buffer |
500 |
Max packets kept in the live feed's ring buffer |
--debug |
(off) | Path to append connection/request diagnostics to — useful since the TUI takes over the terminal, so there's nowhere else to see errors |
1–5— switch tabs (Live / Map / Nodes / Observers / Analytics)q/Ctrl+C— quit
Map tab
+/-— zoom in/out[/]— fine zoom (a much smaller step, for tuning zoom level the same wayShift+arrows fine-tune position)- Arrow keys — pan (moves a fraction of the current view; bigger jumps the more zoomed out you are)
Shift+ arrows — nudge by exactly one grid cell regardless of zoom, for fine-tuning a position once you've zoomed inTab/Shift+Tab— cycle through the nodes currently on screen; the selected one shows name/role/pubkey/last-seen below the mapg— open "fly to": type an IATA airport code (e.g.SFO),↑/↓to pick a match,Enterto jump,Escto cancel0— re-fit the view to all currently loaded nodeso— toggle dual-observer view: some hardware is both a mesh node (repeater/companion/room) and an observer station — CoreScope's own web UI shows both facts at once (a small star overlay on the normal role icon), but a terminal cell can't overlay two glyphs, so this toggles between showing such a node by its primary role or by the observer glyph/color instead
Nodes tab
f— cycle role filter (all / repeater / companion / room / sensor / observer)s/S— cycle sort column (Last Seen / First Seen / Adverts / Name / Role) / toggle ascending-descending↑/↓— select a row
Observers tab
f— cycle region filter (all + whatever IATA codes are actually present in the current data)s/S— cycle sort column (Packets / Last Seen / Name / IATA) / toggle ascending-descending↑/↓— select a row
Analytics tab
←/→/Tab— switch between RF and topology charts
Every live packet pulses the observer station that received it. When CoreScope can resolve the packet's actual route — either because it's an ADVERT packet (which carries the sending node's own pubkey and exact position directly) or because the server's own hop-disambiguation already resolved the path's hash-prefixed hops to full pubkeys (resolved_path) — a marker travels the real route across the map instead of just pulsing in place.
The travelling marker is colored by payload type (ADVERT, TXT_MSG, REQ, GRP_TXT, ...), each given its own stable color spaced evenly around the hue wheel — so at a glance you can tell what kind of packet is moving, and simultaneous animations of different types are visually distinguishable from each other rather than all looking identical.
The map has no tile/image backend, but it's not just dots in blank space either:
- Coastlines and country/state borders render as a faint background layer, using embedded public-domain Natural Earth data — no network fetch, no runtime parsing, compiled directly into the binary. Country borders and coastlines are worldwide; state/province-level borders currently cover the US and Canada (Natural Earth's smallest-scale admin-1 dataset only has full-world coverage for a handful of large countries — easy to extend to others later if useful).
- The legend shows the current view's center and approximate width (
center: 47.68,-64.04 ~7628km), for absolute orientation independent of whatever nodes happen to be on screen.
cmd/corescope-tui/ entrypoint: flag parsing, wiring, starts the TUI program
internal/client/ typed REST + WebSocket client for CoreScope's API
internal/store/ in-memory state shared between the network layer and the UI
internal/geo/ lat/lon → terminal-grid projection (pan/zoom/fit, no map tiles)
internal/ui/ the TUI itself (bubbletea models, one file per tab + shared styles)
This covers live map, node/observer lists, live packet feed, and basic RF/topology analytics. Not implemented: channel chat decryption, VCR-style historical replay, geofilter tooling, and deeper analytics views (hash-collision, subpaths, rx-coverage) that CoreScope's web UI has.
- Connection times out completely, no HTTP response at all — the instance itself is likely unreachable; check the URL loads in a browser first.
- Empty/reset response from
/api/*but the homepage loads fine — some deployments block default library User-Agents (curl/x,Go-http-client/1.1). This client already identifies itself with an honest custom UA (corescope-tui/0.1) rather than a spoofed browser string, specifically to avoid that. decode: unexpected end of JSON input— the server returned an empty/truncated body. The client retries once automatically on this; if it persists, the instance itself may be having trouble.- If the default
--urlis down,https://live.meshcore.cais a known-working public CoreScope deployment.
Run with --debug <path> to log connection attempts, retries, and errors to a file for troubleshooting, since the TUI otherwise owns the whole terminal.