
Meshyface is a chat-first Meshtastic dashboard that runs as a single Python service and serves a single-page web UI over HTTP.
The current UI exposes:
The Console workspace is a terminal-style control surface for packet traffic, history search, and mesh utility commands.
Tab or Enter
to accept, ArrowRight to accept the ghosted suffix, and ArrowUp /
ArrowDown to move through the popup.! narrows by node ID.live streams packet traffic until Ctrl+C or q. Use
live grep <text>, live rg <text>, live filter=<text>, or bare
live <text> to stream only matching live packet groups. Layer and
verbosity filters still apply, for example
live rg TEXT_MESSAGE_APP -vv --layer=2.grep <text> and rg <text> search retained packet/chat history with
context windows, limits, packet/chat source filters, and summary/packet
scope filters./search <text> filters the visible console output from the prompt without
starting a retained-history search.
Map view with node locations, links, common paths, clusters, and signal heatmap.

History view for node counts, online status, new nodes, and position reports.

Topology view showing observed links from the selected root node.

Trace view for a source, destination, nearby links, and per-hop packet details.

Telemetry chart comparing sensor history across multiple nodes.

Top cards for radio activity, node counts, packets, links, battery, and channel use.
Themes can reshape the full dashboard with custom colors, gradients, transparency, blur, fonts, particles, or a live-map background. These example palettes are custom themes; select any preview to open its full-resolution screenshot.
Meshyface nodes can share compact appearance packets containing a node-theme
recipe and an optional ghost or watermark. Configure and preview your node in
Settings > Appearance > Node Appearance, enable Share node appearance,
then use Broadcast appearance to publish it.
flowchart LR
Browser["Browser<br/>single-page UI"]
CDN["Vendored browser assets<br/>Leaflet + leaflet.heat + particles.js"]
Tiles["Basemap provider<br/>OpenStreetMap tile service"]
Server["ThreadingHTTPServer<br/>HTML shell + JSON API"]
Assets["Python template assembly<br/>meshdash/html* + meshdash/assets/*"]
State["State loaders<br/>live snapshot + history readers"]
Services["Write services<br/>chat, settings, tools, games, optional files"]
Tracker["DashboardTracker<br/>live receive path + in-memory buffers"]
History["HistoryStore / SQLite (WAL)<br/>chat, packets, rollups, settings"]
Radio["Meshtastic interface<br/>serial or TCP"]
Mesh["Meshtastic radio / mesh"]
Atlas["Bundled offline atlas<br/>reference basemap fallback"]
Browser -->|GET /| Server
Browser -->|GET /api/*| Server
Browser -->|POST write APIs| Server
Browser --> CDN
Browser --> Tiles
Browser -->|fallback basemap data| Atlas
Server --> Assets
Server --> State
Server --> Services
State <--> Tracker
State <--> History
Services <--> History
Services <--> Radio
Radio <--> Mesh
Radio -->|receive callbacks| Tracker
Start with prerequisites and dependencies, then choose one install path:
/opt/meshyface for a persistent GitHub-updatable host./data mounted for persistent state.--history-db is the final on-disk SQLite filename. The dashboard no longer
adds a connected-radio suffix, so any radio plugged into the dashboard
contributes to the same persisted packet, chat, node, and rollup history.
--no-history disables the persistent store and keeps only live in-memory
buffers.mesh_dashboard_theme_settings.json by default, or the file supplied via
--theme-settings-file.Operational commands that inspect or repair local dashboard data are documented in docs/maintenance.md.
The Links subview is a topology view, not a packet-route replay.
History mode draws from the stored link history saved in SQLite.Live mode draws from current-session link observations only.The current root is the node the graph is centered around. Selecting a different node changes the root and recomputes the numbered distance rings from that node.
--mesh-host <ip-or-dns>: TCP radio host--mesh-tcp-port <port>: TCP radio port, default 4403--mesh-port <path>: serial device path--default-gateway-host <host>: fallback TCP host if --mesh-host is not
provided and serial is still on the default path--default-gateway-port <port>: fallback TCP port for
--default-gateway-host--no-default-gateway: force serial unless --mesh-host is explicitly setRelated environment variables:
MESH_GATEWAY_HOSTMESH_GATEWAY_PORTMESH_DASH_MESH_PORT for the default serial path--http-host <host>: bind host, default 0.0.0.0--http-port <port>: bind port, default 8877--refresh-ms <ms>: browser poll interval, default 3000--packet-limit <n>: recent live packet buffer size, default 250--reset-ticker-scale-on-restart /
--no-reset-ticker-scale-on-restart--show-secrets: reveal private keys/passwords/PSKs in raw JSON panels--debug-mode / --no-debug-mode: expose debug-only dashboard surfaces such
as advanced network diagnosticsRelated environment variables:
MESH_DASH_PRIVATE_MODEMESH_DASH_API_TOKENMESH_DASH_GAMES_ENABLEMESH_DASH_VERSIONMESH_DASH_GIT_COMMITMESH_DASH_PR_NUMBERRuntime identity comes from the 12-character git commit, followed by an
optional pull-request number, for example abc123456789 · PR #43. The Software
panel displays this single revision instead of a package version. GitHub
merge/squash commit subjects are detected automatically; unmerged preview
deployments can set MESH_DASH_PR_NUMBER explicitly.
/api/revision is the canonical identity endpoint. Its revision and
build_ref fields report the runtime revision, while commit and pr_number
remain available as structured fields. /api/version remains as a legacy
endpoint, and its version field retains release/package metadata.
MESH_DASH_VERSION is likewise reserved for explicit release packaging and is
not shown in the dashboard.
--file-transfer-enable: enable the Files app; requires
--accept-file-transfer-traffic-disclaimer--file-transfer-auto-accept: accept direct inbound transfers without a
browser confirmation--file-transfer-max-bytes <bytes>: per-file limit, default 65536 and
constrained to 1024-524288Transfers use the beta MF_FILE_V2 protocol on private port 258 and are not
compatible with the former text-message transport. When a destination has a
usable detected hop count, Meshyface uses that count plus one, capped by the
configured radio hop limit. Hop data older than one hour falls back to the
configured limit. The Files app displays the selected limit and its source.
Related environment variables:
MESH_DASH_FILE_TRANSFER_ENABLEMESH_DASH_FILE_TRANSFER_AUTO_ACCEPTMESH_DASH_FILE_TRANSFER_MAX_BYTESMESH_DASH_ACCEPT_FILE_TRANSFER_TRAFFIC_DISCLAIMER--history-db <path>: base SQLite DB path--history-max-rows <n>: default 200000--history-retention-days <days>: default 30, use 0 to disable age
pruning--history-event-max-rows <n>: append-only packet event cap, default
200000--history-event-retention-days <days>: default 30--history-rollup-retention-days <days>: default 365--no-history: memory-only mode--seed-from-node-db: bootstrap live tracker from the connected radio NodeDBRelated environment variables:
MESH_DASH_HISTORY_DB--theme-presets <json>: optional custom theme preset file--theme-preset <name>: selected preset name--theme-settings-file <json>: persisted runtime theme selection fileBuilt-in presets:
default (safe Meshyface blue, particles disabled)custom (showcase theme with particles and background effects enabled)Fresh installs default to default unless a persisted theme settings file or
MESH_DASH_THEME_PRESET selects another preset.
Related environment variables:
MESH_DASH_THEME_PRESETSMESH_DASH_THEME_PRESETMESH_DASH_THEME_SETTINGS_FILE--private-mode and/or an API token for stricter write-path control.MESH_DASH_API_TOKEN over --api-token on shared or multi-user
hosts. A command-line token can be visible in process listings and retained
in shell history.Join Meshyface channel preset uses an intentionally public
shared Meshyface PSK for interoperability between users of this software. Do
not use that public channel for private traffic.--show-secrets exposes sensitive values in raw JSON panels; do not enable
it casually on shared displays.--no-allow-tokenless-raw-packet-download.Run the normal test suite:
python -m pytest
Run Ruff the same way CI does:
scripts/run_ruff_local.sh
Run the advisory app coverage report:
python -m pytest \
--cov=meshdash \
--cov=mesh_dashboard \
--cov=mesh_connection \
--cov-report=term
Run the local coverage gate with the stricter 85% minimum:
scripts/run_coverage_local.sh
Run the local GUI responsiveness benchmark before PRs:
scripts/run_gui_responsiveness_local.sh
Coverage intentionally excludes the ported Zork engine package from scoring, but standalone Zork and routing tests still run. GitHub Actions publishes the same coverage report as an advisory PR comment and artifact. CI fails below 80%. The local gate stays 5 percentage points higher than CI.
![]() |
--private-mode / --no-private-mode: strip public chat slices and block
selected public endpoints--api-token <token>: require auth on write endpoints via
Authorization: Bearer <token> or X-API-Token; prefer
MESH_DASH_API_TOKEN on shared hosts because command-line tokens may appear
in process listings and shell history--allow-tokenless-raw-packet-download /
--no-allow-tokenless-raw-packet-download: permit raw-packet DB downloads
without a token for loopback/private-LAN clients; enabled by default--games-enable / --no-games-enable: enable playable standalone Zork
console endpoints--backfill-environment-rollups: rebuild environment rollups once and exit;
see docs/maintenance.md--backfill-environment-rollups-reset: clear existing rollups before rebuild--node-history-hours <hours>: default selected-node window, default 72--node-history-max-points <n>: max points returned by
/api/history/node, default 1440