Built for when the internet is gone.
Website: blackbox.host
π Credit & disclosure: Blackbox Node was originally created by @wadadawadada β see the upstream project at wadadawadada/blackbox_node. This repository is a MeshCore-native fork; all credit for the original project belongs to the upstream author. If you find it useful, please consider sponsoring their work.
Blackbox Node is an offline-first command post for MeshCore LoRa networks. It combines a fully local LLM, end-to-end encrypted radio messaging with delivery confirmation, telemetry, and Bitcoin + Cashu payments in one self-hosted web UI.
It is designed for off-grid communications, disaster response, field operations, community mesh networks, and anyone who wants local AI and situational awareness without cloud infrastructure, without a cell tower, and without a remote server.
MeshCore-native: rebuilt around MeshCore (upstream targets Meshtastic), drops TAK support, and adds agent commands and remote/split inference β see How this fork differs from upstream.
- Run a local offline AI assistant on your own machine with
llama.cpp - Turn a laptop plus MeshCore radio into a resilient LoRa mesh node
- Keep chat, telemetry, and node awareness working during outages
- Stand up a knowledge channel on the mesh β e.g. a
#schedulechannel where every message is answered from a live conference schedule, or/wikifor grounded offline facts - Get end-to-end encrypted DMs with delivery confirmation over radio
- Move value off-grid with Bitcoin and Cashu ecash
- Store everything locally in
./data/with no accounts and no hosted backend
The original blackbox_node targets Meshtastic. This fork is a ground-up MeshCore-native rebuild β no Meshtastic, no compatibility/abstraction layer β plus several capabilities the upstream doesn't have. If you're coming from upstream, these are the deviations:
- Python bridge rewritten on the async
meshcorelibrary; all Meshtastic/protobuf code removed. - Native MeshCore concepts throughout: contacts identified by public keys, adverts for discovery, learned paths with per-hop SNR tracing, room servers (store-and-forward threads), and E2E DMs with delivery confirmation (RTT ticks).
- All local state (favorites, chat history, wallet clients) is keyed by pubkey prefix β a fresh start, with no migration of old Meshtastic node IDs.
- Transports: serial + TCP (BLE deferred).
- The entire TAK stack (the
tak_fountain.pyfountain-code path, TAK events, settings, and UI) was deleted β unused in MeshCore deployments.
- A full-screen, game-title-style SETUP console (connection, identity, radio presets, channels, contacts, device stats, advanced, inference) replaces the old device modal, in a pixel-font retro aesthetic.
- Channel and contact sharing via
meshcore://URIs with dependency-free pixel QR codes. - Remote admin console: log into repeater/room nodes and issue CLI commands over the mesh.
- A pluggable KnowledgeSource + AgentCommand registry. Wikipedia (
/wiki) answers general questions; Pretalx (/pretalx) answers from a conferenceschedule.xmland is time-aware ("what's on now?", "next in room X?", "talks tomorrow"). - Adaptive engine: a native tool-calling loop on capable models, falling back to a queryβsearchβfetchβsynthesize pipeline on small ones. Answers are grounded in the fetched sources and length-aware for mesh delivery.
- Per-channel binding β explicit vs ambient. Each knowledge command can be bound to specific channels in one of two modes: explicit β the node reacts only to the typed
/command, and only on channels where that command is enabled; or ambient β a channel is dedicated to one source and every message there is treated as a question (no slash needed), e.g. a#schedulechannel that answers all schedule questions via Pretalx. Ambient relevance is gated in two stages β a schedule-keyword fast path, then a lenient LLM classifier (biased to answer, and it fails open so a classifier hiccup never drops a question) β so genuine questions get through while plain greetings and chatter are ignored. A question that matches nothing still gets a short reply ("No matching sessions found.") rather than silence, and if the model is unreachable the channel gets a brief "the bot is quite busy" notice instead of dead air. Airtime guards: one reply in flight per channel and a per-sender cooldown. Configured in AI Settings β Integrations. - Pre-event announcer (Pretalx). Optionally, a configurable number of minutes before each session starts, the node broadcasts a one-off announcement β start time, room, title, and speaker(s) β to the channel(s) Pretalx is bound to (regardless of explicit/ambient mode). Sessions starting at the same time are batched into one message (
π 10:00 Β· N talks Β· Room X β β¦ Β· +N more), bounded so a crowded slot never floods the channel. Each session fires once (deduped across restarts), never backfilling past talks, and a rescheduled talk re-announces with its corrected time. The schedule refresh interval is configurable (default 10 min) β lower it near a live event so time changes propagate quickly. Toggle, lead time, and refresh live in AI Settings β Integrations β Pretalx.
- Inference mode is configurable: the bundled local
llama-server, or a remote OpenAI-compatible endpoint over Tailscale β so a thin Raspberry Pi field node can run the radio + dashboard and offload the LLM to a home machine. Live toggle in SETUP β INFERENCE, env-var overrides, and a graceful shutdown that reaps child processes for cleanpm2/systemdrestarts. See Remote inference.
- Direct messages: a DM that begins with a known command (
/wiki,/weather, β¦) is always routed to command processing; free-form DMs get an AI reply only when mesh AI reply is enabled, otherwise they are delivered as plain direct messages. - Channels: built-in commands (
/summary,/battery, β¦) answer on the channels enabled under AI Settings β Integrations β Commands; knowledge commands answer per their explicit/ambient binding; other channel chatter is ignored unless the channel is ambient-bound. Infrastructure nodes (rooms, repeaters) are never auto-answered.
MeshCore is a free, open-source mesh radio firmware. Nodes exchange adverts to discover each other as contacts (identified by public keys), and route messages hop by hop over learned paths without internet or cellular infrastructure. Flash the companion firmware onto a supported board in your browser at flasher.meshcore.io.
The radios use LoRa (Long Range), a spread-spectrum radio modulation that can reach roughly 5-15+ km in open terrain. Dedicated repeater and room-server nodes extend the network, and they can be administered remotely over the mesh.
Popular hardware you can use with MeshCore:
- Heltec LoRa 32 - compact ESP32-based starter board
- LILYGO T-Beam - built-in GPS and battery management
- RAK WisBlock - modular and highly configurable
- Seeed SenseCAP T1000 - compact tracker-style form factor
These devices typically cost about $20-$60 and can run for days on a small battery or indefinitely on solar.
Runs a quantized LLM entirely on your machine via llama.cpp. No API keys, no cloud calls, and no internet required during normal operation. You can load small GGUF models for weak hardware or larger models for better responses.
Other MeshCore nodes in range can query your AI by radio. Send @bot your question or !ask your question from any MeshCore device and the response comes back over the air.
When AI over mesh is enabled, any node can send a slash command as a direct message to get an instant structured response β no open-ended question needed. Send /help to get the full list. Commands work even when the LLM is busy or unavailable; they run from live telemetry data without touching the model.
| Command | What it returns |
|---|---|
/help |
List of all available commands |
/wiki <question> |
Answer from Wikipedia via the local LLM (agent command β grounded, source-cited) |
/pretalx <question> |
Answer from a conference schedule (frab/Pretalx schedule.xml) β time-aware, grounded (agent command) |
/summary |
Node counts, network health, activity breakdown, temperature |
/weather |
Latest environment readings from telemetry sensors |
/activity |
Recent event counts (telemetry, messages, DMs) for the current window |
/battery |
Battery levels β average, low nodes, critical nodes |
/nodecheck <name> |
Status of a specific node: online state, last seen, SNR, hops, battery |
/trace <name> |
Route to a contact with per-hop SNR |
/advert [flood] |
Broadcast an advert (zero-hop, or flood) |
/wallet |
Your wallet balance and available off-grid Cashu proofs |
send <amount> <node> |
Send Cashu sats to a node by its short name (requires wallet client auth) |
Known commands are always processed when sent as a direct message, even with AI auto-reply off; a non-command DM is treated as a plain message (or answered by the AI when auto-reply is enabled).
Channels vs DMs. Slash commands always work in a direct message. On group channels the node answers only where you enable it: built-in commands on the channels chosen under AI Settings β Integrations β Commands, and knowledge commands (/wiki, /pretalx) per their per-channel explicit or ambient binding. In an ambient channel every message is answered by the bound source without a slash β ideal for a dedicated #schedule (Pretalx) or #wiki channel β while a lenient two-stage relevance filter (keyword fast path β yes-biased, fail-open LLM classifier) lets real questions through and ignores plain greetings and chatter. A question with no match gets a short "no match" reply, and an unreachable model gets a brief "busy" notice, so questions are never left silently hanging.
All inbound and outbound MeshCore messages are tracked in the web UI. Contacts are discovered via adverts and keyed by public key; positions, pull-based telemetry, battery levels, and environment readings are recorded and browsable. Direct messages are end-to-end encrypted with delivery confirmation (RTT ticks), and channel broadcasts (channels 0-7) are supported alongside room servers (store-and-forward threads) and a remote admin console for repeater/room nodes.
Blackbox Node includes a built-in Bitcoin wallet (on-chain, BIP-39/HD) and a Cashu ecash wallet for Lightning-compatible off-grid transactions.
Cashu tokens are bearer instruments that can be copied and pasted like text, so payments can move over MeshCore radio as plain messages. When connectivity returns, tokens can be melted back to Lightning or held as ecash.
This makes it possible to run basic economic activity such as tipping, paying for services, and splitting resources entirely over a radio mesh network.
You can approve specific mesh nodes to control your wallet remotely over DM. This lets you trigger payments from a handheld MeshCore device without touching the node's web UI.
Setup: In the wallet Settings tab, enable External Wallet Clients and add the node(s) you want to authorize from the dropdown.
Workflow:
- Send
/walletas a direct message to your node from an approved MeshCore device. The node replies with your current balance and available off-grid proofs:π° 22 sats Proofs: [6][16] send <amt> <node> - Reply with a send command using the target node's name (or public-key prefix):
The node sends a Cashu token directly to the target node over mesh, records the transaction in the wallet history, and confirms:
send 6 f4e5β Sent 6 sats to f4e5
Requirements:
- The node must have off-grid proofs prepared for the exact amount (prepare them in the wallet Send tab beforehand)
- Only direct messages from approved nodes trigger wallet commands β broadcast channel messages are ignored
- The feature requires no internet; the node uses pre-split offline proofs
- Off-grid community infrastructure
- Disaster communications and blackout fallback
- Field teams coordinating over MeshCore radios
- Rural or expedition deployments with no reliable internet
- Local-first AI nodes for neighborhoods, events, vehicles, or camps
| Mode | What it needs |
|---|---|
| Local offline AI only | A machine running Node.js and Python, plus a GGUF model file. No radio and no internet during runtime. |
| Full off-grid mesh node | Same as above, plus a MeshCore companion-firmware device connected by USB serial or TCP. |
The app starts in whatever mode it can. Radio and mesh features stay inactive until a device is connected.
Inference can run locally (the bundled llama-server, the default) or against a remote
OpenAI-compatible endpoint. This lets a small field device (e.g. a Raspberry Pi with a MeshCore
radio on USB) run the full dashboard + radio bridge while offloading the LLM to a more capable
machine at home reached over Tailscale.
Switch modes live from SETUP β INFERENCE (toggle LOCAL / REMOTE + remote URL), or via env vars.
| Variable | Purpose |
|---|---|
LLM_MODE |
local (default) or remote. Overrides the saved setting; when set, the SETUP fields show read-only. |
LLM_REMOTE_URL |
Base URL of the remote endpoint, e.g. https://mac-mini.tailnet.ts.net. A trailing / or /v1 is stripped. |
HOST |
Web UI bind address. Defaults to 127.0.0.1. Set to the device's tailnet IP (preferred) or 0.0.0.0 to reach the dashboard from other devices. |
PORT |
Web UI port (default 7860). |
LLM_PORT |
Local llama-server port (default 8080); used in local mode only. |
MESHCORE_ADDRESS |
Serial device or host:port, e.g. /dev/ttyACM0. Omit to auto-detect. |
BLACKBOX_SKIP_BOOTSTRAP=1 |
Skip the install-time download of llama-server + starter model (not needed on a remote-inference device). |
BLACKBOX_NO_BROWSER=1 |
Don't try to open a browser (headless). |
In remote mode the device needs no ./llama/ runtime and no GGUF models, and the model manager
(install/switch/delete) is disabled β the host owns the model.
Run a standalone llama-server bound to loopback, then front it with tailscale serve (TLS +
MagicDNS). Never bind llama-server to a public interface β it has no auth; rely on tailnet ACLs.
llama-server --host 127.0.0.1 --port 8080 --model <model>.gguf \
--ctx-size 4096 --jinja --reasoning off
tailscale serve --bg --https=443 http://127.0.0.1:8080
# the device then targets https://<host>.tailnet.ts.netIf MagicDNS is unavailable, target the 100.x.y.z tailnet IP instead.
# install without the local inference runtime / model (~550 MB saved):
BLACKBOX_SKIP_BOOTSTRAP=1 npm install
# (meshcore installs lazily on first bridge start; needs internet once.
# to pre-stage it: pip install --target ./pydeps meshcore)
LLM_MODE=remote \
LLM_REMOTE_URL=https://mac-mini.tailnet.ts.net \
HOST=<pi-tailnet-ip> \
MESHCORE_ADDRESS=/dev/ttyACM0 \
BLACKBOX_NO_BROWSER=1 \
npm startRun it under systemd with After=tailscaled.service and Restart=on-failure so it recovers on
reboot. If the remote is unreachable the dashboard stays up and the LLM status shows
remote-unreachable, recovering automatically when the endpoint returns.
npm install
npm startDuring npm install, the project bootstraps the local AI runtime automatically:
- installs JavaScript dependencies
- downloads a platform-matched
llama.cppruntime into./llama/if missing (Windows/Linux/macOS) - downloads a starter GGUF model into
./models/if missing - attempts to install the MeshCore Python package into
./pydeps/if Python is available
That is enough for the web UI and local AI to start on a clean machine.
Install these before anything else:
- Node.js 18+ - main runtime
- Python 3.11+ - MeshCore radio bridge
Verify both are available:
node --version
python --versiongit clone https://github.com/kalambet/blackbox_node.git
cd blackbox_node
npm installThis creates node_modules/ and runs the bootstrap installer for the local AI runtime.
Skip this step unless automatic bootstrap failed or you want to replace the runtime manually.
Otherwise, the llama/ folder must contain llama-server (llama-server.exe on Windows) and the companion runtime libraries from a matching prebuilt package in the llama.cpp releases page.
Extract llama-server (or llama-server.exe) and the bundled ggml/llama runtime libraries into ./llama/:
llama/
llama-server(.exe)
llama runtime libraries (.dll / .so / .dylib depending on OS)
Pick the build that matches your machine and OS/CPU:
- No GPU ->
...-cpu-... - NVIDIA GPU ->
...-cuda-... - AMD / Intel GPU ->
...-vulkan-... - Apple Silicon (optional acceleration) ->
...-metal-...
Skip this step unless automatic bootstrap failed or you want to add more models manually.
Otherwise, create the models/ folder and download at least one .gguf model file into it.
Option A - use the built-in model manager
- Run
npm start - Open
http://127.0.0.1:7860 - Go to Settings -> Models and click Install next to any model
Option B - download manually
mkdir modelsThen place any .gguf file into models/. Recommended starter: Qwen2.5-3B-Instruct-Q5_K_M.gguf (~2.3 GB).
Flash the MeshCore companion firmware onto your board at flasher.meshcore.io, then plug it in via USB (or expose it over TCP). Pick the transport and port in the SETUP > Connection screen; the serial port list is auto-detected.
No device? The app still starts fine. Mesh and telemetry features just show as disconnected.
npm install
npm startOn launch:
- the web UI opens at
http://127.0.0.1:7860 llama-server(orllama-server.exeon Windows) starts and loads the selected model- the Python MeshCore bridge (
bridge.py) connects to the configured serial or TCP radio - the installer prepares
./llama/,./models/, and tries to prepare./pydeps/
| Requirement | Notes |
|---|---|
| Node.js 18+ | Runtime for the web server |
| Python 3.11+ | Required only for the MeshCore radio bridge |
Internet during npm install |
Needed to download llama.cpp, a starter model, and optional Python deps |
./llama/llama-server (or llama-server.exe on Windows) |
Auto-downloaded on install if missing |
At least one .gguf in ./models/ |
Auto-downloaded on install if missing |
| MeshCore device on USB serial or TCP | Optional, but required for radio and telemetry |
AI
- Local LLM via
llama.cpp, fully offline after model download β or remote/split inference over Tailscale (see Remote inference) - Mesh-triggered queries with
@bot ...and!ask ... !resetclears per-peer conversation context- Slash commands over mesh DM:
/help,/summary,/weather,/activity,/battery,/nodecheck,/trace,/advert - Agent commands (
/wiki,/pretalx): pluggable knowledge sources with an adaptive tool-call / pipeline engine, grounded and source-cited; time-aware conference-schedule source; bindable per channel in explicit or ambient mode (a dedicated channel answers every message from one source) - Pretalx pre-event announcer: broadcasts each session (time, room, title, speakers) to the bound channel(s) a configurable number of minutes before it starts; batches same-time sessions, re-announces rescheduled talks, deduped across restarts, with a configurable schedule refresh interval
- Configurable system prompt, temperature, top-p, and token limits per mode
- Built-in model manager for curated GGUF downloads (disabled in remote-inference mode β the host owns the model)
MeshCore / Radio
- Serial and TCP transports with auto-detected serial port list
- Contacts discovered via adverts, keyed by public key, with type badges (chat / repeater / room)
- E2E encrypted DMs with delivery confirmation and RTT, ack-paced chunking for long messages
- Channel broadcasts (channels 0-7) with shareable
meshcore://channel URIs and pixel QR codes - Room servers: join, login, and fetch store-and-forward messages
- Remote admin console for repeaters and room servers (CLI over mesh)
- Pull-based telemetry (Cayenne LPP): battery, voltage, temperature, environment
- Trace path with per-hop SNR, cached out-path display, reset-path action
- Full-screen retro SETUP console: connection, identity, radio presets, channels, contacts, device stats, advanced tuning
- Weather and environment parsing from telemetry
nodes/list nodesquery returns a compact contact list over the meshweather/forecastquery returns the latest parsed weather data
Payments
- Bitcoin HD wallet (BIP-39, on-chain receive)
- Cashu ecash wallet: set a mint, receive/send tokens, melt to Lightning
- Cashu tokens are plain text, transferable over radio or any other text channel
- Lightning invoice payment via Cashu melt
- QR code generation for Bitcoin addresses and Lightning invoices
- Transaction history
- External Wallet Clients: approve specific mesh nodes to control your wallet remotely via DM commands
UI and storage
- Local web UI for message log, contact list, map, local chat, wallet, and settings
- AI Settings as a retro drill-down (General / Integrations β Commands Β· Wiki Β· Pretalx) matching the SETUP console, with per-channel command bindings
- All data stored locally in
./data/ - No external database, no accounts, no telemetry
| Setting | Value |
|---|---|
| Web UI | http://127.0.0.1:7860 (bind via HOST/PORT) |
| LLM backend | http://127.0.0.1:8080 local, or a remote endpoint β see Remote inference |
| Starter model installed by bootstrap | Qwen2.5-0.5B-Instruct-Q3_K_M.gguf |
| Default model | Qwen2.5-0.5B-Instruct-Q3_K_M.gguf |
- The app runs without a radio. MeshCore and mesh features simply show as disconnected.
- If Python is missing, the web UI and local AI still work, but MeshCore radio features stay unavailable.
- Radios must run the MeshCore companion firmware (flash at flasher.meshcore.io). Meshtastic firmware is not supported by this fork.
- TAK / ATAK / CoT support from earlier versions has been removed.
- Downloading models or auto-installing Python packages requires internet during
npm install. Runtime is otherwise fully local. - Cashu token operations usually require internet access to reach the mint. Tokens already in your wallet can still be held and transferred offline, but creating a new send offline only works when your existing proofs already match the exact amount; otherwise the wallet must contact the mint to split/change proofs.
This fork stands on the shoulders of the original blackbox_node by @wadadawadada β huge thanks for building the foundation this MeshCore-native version is built on. All donations below go to the original author; please support their work: https://github.com/wadadawadada/blackbox_node#donate



