An unofficial terminal app for MeshCore LoRa mesh networks — monitor live packets with no hardware required, or connect directly to your node to chat, manage repeaters, and diagnose your mesh.
Work in progress. The tool is usable but not complete — expect rough edges and missing features. Open an issue if you hit a bug or have a feature request.
Monitor mode — no MeshCore device required
- Live packet stream — see every hop in real time, with relay hops resolved to node names
- Message decryption — supply your channel keys and GroupText messages are shown in plain text
- Interactive map — plot packet origins with SNR/RSSI overlay (optional
[map]extra) - MQTT or letsmesh REST as packet source — configurable in
config.toml - Expandable observer rows — unfold a packet to see every individual observer report
- Log panel — toggle with
l, resize with+/-, write to file with--log-file
Companion mode — requires a connected MeshCore node and the [companion] extra
- Connect over TCP, Serial, or BLE — auto-discovery, BLE scan, and recent-connection history
- F2 Channels — read and send group messages; import your channel keys to the device
- F3 Contacts — ping, DM, request telemetry/trace, login, and reboot contacts
- F4 Device info — firmware version, radio parameters, battery, and uptime at a glance
- Saved passwords — per-repeater and default login passwords in
~/.config/meshcore-tools/ - Unread indicators — amber dot on tab labels when new messages arrive
Node database CLI
- Resolve short public-key prefixes to node names
- Populated from the letsmesh API and
map.meshcore.dev - Node blacklist in
config.tomlto filter spurious or noisy nodes
F1 Monitor — live packets with relay-path resolution
Channels, Contacts, and Companion tabs
| F2 Channels — group messaging with import | F3 Contacts — ping, DM, telemetry |
|---|---|
![]() |
![]() |
F4 Companion device info
| Packet detail | Map view |
|---|---|
![]() |
![]() |
Requires uv.
# Monitor only — no MeshCore device needed
uv tool install git+https://github.com/sgrimee/meshcore-tools
# With companion support (F2 Channels, F3 Contacts, F4 Device info)
uv tool install "meshcore-tools[companion] @ git+https://github.com/sgrimee/meshcore-tools"
# All features (companion + map rendering + MQTT source)
uv tool install "meshcore-tools[companion,map,mqtt] @ git+https://github.com/sgrimee/meshcore-tools"# Monitor only — no MeshCore device needed
uv tool install .
# With companion support (F2 Channels, F3 Contacts, F4 Device info)
uv tool install --extra companion .
# All features (companion + map rendering + MQTT source)
uv tool install --all-extras .Then build the node database for the first time:
meshcore-tools nodes updateThe default region is LUX. Pass --region YOUR_REGION or set it once in config.toml — it is saved for future runs.
meshcore-tools launch full TUI (same as `monitor`)
meshcore-tools monitor [--region R] [--poll N] [--log-file FILE]
meshcore-tools nodes update [--region REGION] refresh node database
meshcore-tools nodes lookup <hex_prefix> find node by key prefix (1+ hex chars)
meshcore-tools nodes list [--by-key] list all nodes
| Option | Default | Description |
|---|---|---|
--region |
saved or LUX |
Region filter for the letsmesh API |
--poll |
5 (letsmesh) / 1 (MQTT) |
Polling interval in seconds |
--log-file |
— | Write debug log to a file in addition to the in-app panel |
# Rebuild the node database
meshcore-tools nodes update
# Look up a node by first byte of public key
meshcore-tools nodes lookup 7d
# Look up by two bytes
meshcore-tools nodes lookup ab4b
# List all nodes sorted alphabetically
meshcore-tools nodes list
# List all nodes sorted by public key
meshcore-tools nodes list --by-key
# Start the TUI
meshcore-tools| Key | Action |
|---|---|
d |
Toggle detail side panel |
shift+d |
Open detail popup |
m |
Toggle map side panel |
shift+m |
Open map popup |
b |
Toggle layout: right-stacked ↔ bottom side-by-side |
a |
Toggle follow mode (auto-scroll to newest packet) |
f |
Filter packets by observer or node in path |
n |
Cycle path display: names → src+hex → hex |
w |
Toggle path word-wrap |
p |
Pause / resume live updates |
r |
Force refresh |
c |
Clear packet list |
l |
Toggle log panel |
+ / - |
Resize log panel |
C |
Open connection dialog |
q |
Quit |
Side panels update live as you move the cursor. Both panels can be open at the same time. Use b to switch between panels stacked on the right (default) or placed side by side at the bottom of the screen.
Follow mode (a) is off by default — the cursor stays on the selected packet while new packets arrive in the background. Turn it on to auto-scroll to the newest packet.
Input-file nodes (from input/*.txt) are highlighted in yellow in the path column and detail panel, making it easy to spot your own nodes in packet paths.
The monitor decrypts GroupText messages using channel keys stored in ~/.config/meshcore-tools/secrets.toml under a [channels] table:
[channels]
"Public" = "8b3387e9c5cdea6ac9e5edbaa115cd72"
"#wardriving" = "e3c26491e9cd321e3a6be50d57d54acf"
"MyChannel" = "52d21b5e68a130279cce6b64c0f8bcd4"Each entry maps a channel name to its 32-char hex AES-128 key. Hashtag channels with no explicit key have their key derived automatically from the channel name — you can omit the value or leave it empty.
If you have a connected companion device, the easiest way to populate this file is to connect and let the app auto-sync: channel keys received from the device are persisted to secrets.toml automatically on connect. You can also push your local keys to the device from the F2 Channels tab using the Import channels button.
When a message is successfully decrypted, the sender name and message text are shown in the detail panel instead of "encrypted".
See secrets.toml.sample for an annotated template.
Companion features require the [companion] extra and a connected MeshCore device. Press C from anywhere in the TUI to open the connection dialog.
Three transport types are supported:
| Transport | How to connect |
|---|---|
| TCP | Enter host and port (e.g. 192.168.1.10:5000) |
| Serial | Select from auto-discovered /dev/tty* / COMx ports |
| BLE | Scan for nearby devices; enter a PIN if pairing is required |
Recent connections are shown expanded by default. The last successful connection is auto-restored on the next launch.
- Channel list on the left, message log and input on the right
- Use
←/→to switch channels - Send messages up to 133 characters
- Import channels button pushes keys from
secrets.tomlto the device - New incoming messages trigger an amber unread dot on the tab label
- Contact list on the left; contextual actions on the right based on contact type
- Available actions: Ping, DM (direct message), Status, Telemetry, Trace, Login, Reboot
- Login prompts for a password and saves it if successful (see Password management below)
- Unread DMs show an amber dot on the tab label
Shows device name, public key, firmware version, battery level, uptime, and LoRa radio parameters (frequency, bandwidth, spreading factor, TX power, coding rate). Includes a free-form command input for advanced use.
Passwords are stored with 600 permissions in ~/.config/meshcore-tools/secrets.toml:
default_password— fallback used for all logins[passwords]table — per-repeater passwords keyed by public key; take precedence over the default[channels]table — channel keys for GroupText decryption; auto-populated on companion connect
Saved passwords are pre-filled in the login dialog automatically.
Two files live under $XDG_CONFIG_HOME/meshcore-tools/ (defaults to ~/.config/meshcore-tools/ on Linux and macOS). Both are created automatically as needed. See config.toml.sample and secrets.toml.sample in the project root for annotated templates.
config.toml — general settings, connection state, and filter rules:
[general]
region = "LUX"
[filtering]
blacklist = ["deadbeef", "cafebabe"] # public-key prefixes or name substrings to hide
[packet_source]
type = "mqtt" # "letsmesh" (default) or "mqtt"
[mqtt]
broker = "localhost"
port = 1883
topic = "meshcore/raw"
# username = "..."
# password = "..."secrets.toml (always 600 permissions) — login passwords and channel keys:
default_password = "hunter2"
[passwords]
"abcdef1234..." = "repeater-specific-pw"
[channels]
"Public" = "8b3387e9c5cdea6ac9e5edbaa115cd72"
"#wardriving" = "e3c26491e9cd321e3a6be50d57d54acf"| Key | Default | Description |
|---|---|---|
general.region |
LUX |
Default region for API queries |
filtering.blacklist |
[] |
Name substrings or key prefixes to suppress in the monitor |
packet_source.type |
letsmesh |
Packet source: letsmesh or mqtt |
mqtt.broker |
localhost |
MQTT broker hostname |
mqtt.port |
1883 |
MQTT broker port |
mqtt.topic |
meshcore/raw |
MQTT topic pattern |
mqtt.username / mqtt.password |
— | Optional MQTT credentials |
The database is stored in nodes.json (gitignored, auto-generated). Run meshcore-tools nodes update to create or refresh it.
The live APIs only know nodes that have been seen recently. Input files let you register your own nodes — and nodes of friends — so they are always resolvable by name in the monitor, even if they haven't appeared in the API data yet. Short key prefixes are accepted, so you don't need the full 64-char public key; on nodes update they are matched against the API data and upgraded automatically.
The easiest way to populate a file is to copy-paste the output of list contacts from meshcore-cli. Format (one node per line):
name TYPE pubkey_hex [routing]
name— node nameTYPE—CLI(client) orREP(repeater)pubkey_hex— hex public key, full 64 chars or a shorter prefixrouting— optional, e.g.Floodor0 hop
Lines may optionally be prefixed with a line number (1→).
See input/example.txt.sample for a sample. Copy it to a new .txt file in the same directory and edit it with your own nodes.
On meshcore-tools nodes update, the tool fetches data from two sources:
- letsmesh (
api.letsmesh.net) — node list for the region; partial keys from input files are matched and upgraded to full 64-char keys. Use--regionto select a region (default:LUX). - map.meshcore.dev — node coordinates (lat/lon); backfills any node in the database that lacks coordinates.
This is an unofficial project. Bug reports, feature requests, and pull requests are welcome.
- Report a bug or request a feature: github.com/sgrimee/meshcore-tools/issues
- For non-trivial pull requests, please open an issue first to discuss the approach.
- Dev setup:
uv sync --all-extras— seeCLAUDE.mdfor contributor notes.
- letsmesh analyzer — packet analysis and node list API (
api.letsmesh.net) - MeshCore — firmware and node map API (
map.meshcore.dev) - meshcore-cli — CLI for interacting with MeshCore nodes
- meshcore_py — Python library for companion connectivity (
meshcorePyPI package)





