# MeshTerm **A full-screen terminal companion for [MeshCore](https://meshcore.io/) LoRa mesh devices** — tune your radio, chat across the mesh, watch the network live, and keep a longitudinal record of everything you overhear, all from a connected serial, Bluetooth, or network (TCP) companion. MeshTerm is interactive by default: a modern, keyboard-driven TUI built on [Rich](https://github.com/Textualize/rich) and [prompt_toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit). It is also fully scriptable — **every screen has a mirror `meshterm` subcommand** — so the same capabilities drive both an evening of exploring the mesh and a cron job. Every packet the radio overhears is recorded to a local SQLite database, so the longer you run it, the more your mesh's history is worth.
> MeshTerm is a side project, not a product. Bug reports are very welcome — but please > open an issue before writing a pull request. [More on how it's run](#how-this-project-is-run). --- ## Why MeshTerm - **One tool, two faces.** Launch `meshterm` for a full-screen menu; pass a subcommand for a scripted one-shot. The registry that builds the menu builds the CLI, so they never drift apart. - **It remembers.** A passive monitor logs every overheard advert and telemetry frame — SNR, RSSI, shared location — to SQLite from the moment the radio opens. Maps, contact lists, charts, and the Time Machine all read back that history. - **It measures.** Live trace with per-hop reliability, a coarse→refine→verify TX-power sweep, and a walkable graph of how the mesh actually hangs together. - **It talks.** Live channel and direct messaging, store-and-forward courier delivery for contacts that aren't there yet, and shareable channels as QR codes and `meshcore://` links. - **It connects however your companion does.** Serial (USB), Bluetooth LE, or TCP (a WiFi board or a network proxy) — the discoverable transports are auto-found and picked interactively, a network device is named by hand, and every kind reconnects live when a link drops. No radio? A built-in simulator covers development. - **It works offline.** The street map caches OpenStreetMap tiles to disk and falls back to a blank grid when there's no network — it never needs the internet to run. ## Install ### Download a build **[⬇ Latest release](https://github.com/jpmartineau/MeshTerm/releases/latest)** — one file, no Python needed. Windows, macOS (Intel and Apple silicon), and Linux (x64 and ARM64, so the uConsole is covered). MeshTerm is a terminal program, so **open a terminal and run it from there.** You can double-click it and it will work, but you'll get whatever console your system picks, and if anything goes wrong at startup the window closes before you can read why. **Windows** — open Windows Terminal or PowerShell, `cd` to your downloads, then: ```powershell .\meshterm-0.2.2-windows-x64.exe ``` **macOS and Linux** — open Terminal, `cd` to your downloads, then: ```bash chmod +x meshterm-0.2.2-* ./meshterm-0.2.2-* ``` Rename it to something you don't mind typing and put it somewhere on your `PATH`, and it becomes just `meshterm` from anywhere. > **Your system will complain the first time, and it's right to.** These builds aren't > code-signed — signing costs real money on both platforms and this is a free side > project. > > - **macOS**: *"cannot be opened because the developer cannot be verified"*. Clear it with > `xattr -d com.apple.quarantine meshterm-0.2.2-*` and run it again. > - **Windows**: *"Windows protected your PC"*. Click **More info → Run anyway**. > > Every release ships a `SHA256SUMS` file if you'd rather check the download first. > **Trying a build without touching your real data.** MeshTerm keeps everything in > `~/.meshterm` — your history database, contacts, channel keys — and *every* copy of > MeshTerm uses that same folder. Set `MESHTERM_HOME` to try one in isolation: > > ```bash > MESHTERM_HOME=~/meshterm-test ./meshterm-0.2.2-* # macOS, Linux > ``` > ```powershell > $env:MESHTERM_HOME = "$HOME\meshterm-test"; .\meshterm-0.2.2-windows-x64.exe > ``` > > Same variable if you run two radios and want them kept apart. ### Or install with pip You need **Python 3.10 or newer**. That's the only requirement — MeshTerm pulls in everything else itself. ```bash pip install git+https://github.com/jpmartineau/MeshTerm meshterm ``` `meshterm` opens the full-screen menu. That's the whole install. **Rather not touch your system Python?** [pipx](https://pipx.pypa.io/) puts the app in its own private environment and still gives you a plain `meshterm` command: ```bash pipx install git+https://github.com/jpmartineau/MeshTerm ``` **No radio yet?** `meshterm --mock` runs the entire app against a simulated mesh, so you can have a look around before you buy anything. ### Coming **`pip install mesh-term`**, once the package is published. It isn't yet — the name question is still being sorted out ([why](https://github.com/pypi/support/issues/12162)). Setting up for development instead? That's in [CONTRIBUTING.md](CONTRIBUTING.md). ## Quick start ```bash # Interactive full-screen menu (auto-discovers serial + Bluetooth companions) meshterm # Connect over Bluetooth (address from `meshterm devices`) meshterm --ble AA:BB:CC:DD:EE:FF info # Connect over the network to a TCP companion (host[:port], port defaults to 5000) meshterm --tcp 192.168.1.50 info # No radio attached? Use the built-in simulator for development. meshterm --mock # On a ClockworkPi PicoCalc (Luckfox Lyra / Calculinux) the handheld flavour is # auto-detected: a 53-column layout, a 16-slot palette, a compact glyph language on a # custom console font, and an F-key hint lane. Preview it anywhere, or inspect it: meshterm --mock --platform picocalc meshterm specimen ``` Handheld bring-up (fonts, palette, boot services) lives in `scripts/calculinux-setup.sh` + `scripts/calculinux-console-font.sh`. ## Features MeshTerm's menu answers one question — *what would you like to do?* — so its five sections are named for the doing, not the subject. Every interactive screen is listed here with its scripted equivalent, where one exists. ### 💬 Message — the people on the other end | Feature | What it does | Scripted | | --- | --- | --- | | **💬 Chat** | Live full-screen channel and direct messaging — a scrolling transcript with a pinned input line where sent and received messages stream together. Every message is logged; unread counts show in the menu header. | `meshterm chat send / history / list` | | **📻 Channels** | Create, join, reorder, mute, and share mesh channels — with QR codes and `meshcore://` share links. | `meshterm channels list / add / join / import / share / clear` | | **📨 Courier** | Store-and-forward outbox for contacts that aren't reachable yet. Queue a message; it goes out (with ack tracking and polite exponential backoff) the moment the contact is next heard, or at a scheduled time. | `meshterm courier queue / list / send / cancel / clear` | | **👥 Contacts** | This node and its known contacts — a recency heat-map, overheard packet counts, and full public keys with the path-hash prefix highlighted. | `meshterm contacts` | ### 📊 Watch — what the mesh is doing, and what it did | Feature | What it does | Scripted | | --- | --- | --- | | **📊 Dashboard** | The live mesh overview: a two-hour all-packet activity chart with a pulse line, session traffic tallies by packet class, and window RF health beside the radio's own live numbers. Repaints every second. | — | | **📰 Live feed** | Every packet as it arrives, newest first: time, what the frame *is* (its parsed payload class), and what it is *about* — the node for an advert, the channel for a channel text, sender → recipient for a direct message or a request, the tag for a trace — with reception quality beside it. Enter opens any row in the packet viewer, route graph and all. | — | | **🚨 Watchtower** | A passive sentinel over the nodes you star: silence alarms when a watched node goes quiet, SNR-sag warnings when reception degrades, recovery notes when it returns, and a heads-up when a never-before-seen node appears. Runs in the background all session; unacked alerts show as a header badge. | — | | **⏳ Time machine** | Everything the recorder ever heard, as braille charts: reception volume, the median-SNR band, hour-of-day rhythm, packets and nodes per day, and first-ever arrivals — over a switchable 24h / 7d / 30d / all-time window, per node or mesh-wide. | — | | **🎧 Monitor** | Passive recording is always on in the app. On the CLI, capture a bounded foreground window, tailing each overheard packet and summarising when it ends. It transmits nothing — it only listens. | `meshterm monitor --seconds 60` | ### 🧭 Explore — where the nodes are and how they reach each other | Feature | What it does | Scripted | | --- | --- | --- | | **🌍 Map** | Located nodes plotted over a real OpenStreetMap street basemap rendered as Unicode braille (streets, rivers, place names). Pannable and zoomable; repeaters highlighted and drawn on top. Falls back to a blank grid offline. | `meshterm map` (one-shot render) | | **🌐 Mesh walk** | The mesh's *observed shape*, walked one node at a time: an evidence graph built from trace walks, firmware routes, overheard relay chains, and repeater neighbour tables. SNR-coloured braille edges, quality bars, Enter to walk, ⌫ to backtrack, type to find any node. The map answers *where*; the walk answers *how it hangs together*. | — | | **🎯 Trace target** | A live trace screen: pick a target and watch each trace stream in hop by hop, with running per-hop medians and reliability. Compose or force a route through specific repeaters. A trace transmits **exactly once** (repeaters can blacklist nodes that burst) — sample more by running it again. | `meshterm trace --target …` | | **👣 Trace path** | The other half of tracing: compose the whole circuit by hand — out and back whichever way you choose — and walk it. The path composer suggests each next hop from the links actually observed, strongest first, and can fetch a repeater's neighbour table over the mesh when you hold its admin password. | `meshterm trace --path …` | | **🏆 Trophy case** | Every trace that comes home is scored, on seven boards: longest distance, farthest node, longest single leg, most nodes (with and without revisits), weakest surviving link, and biggest enclosed loop. Records are kept per hash width, and a record walk must be a *trail* — no link crossed twice the same way. | `meshterm records` | ### 🔧 This node — the radio in your hand | Feature | What it does | Scripted | | --- | --- | --- | | **📋 Device info** | The connected companion's identity and full radio configuration at a glance. | `meshterm info` | | **🔧 Device config** | View and *stage* changes to every setting (name, radio, behaviour, experimental) for review before applying, with TOML backup/restore. | `meshterm config` / `config set