7.1 KiB
7.1 KiB
MeshBot — Workflow Rules
Change process (mandatory)
- Small, incremental changes — work is done one change at a time, in smaller parts; each part must be tested thoroughly with unit tests covering it before moving on. Connected issues/tasks that reference the same part of code MAY be done together on one branch/PR with a single version bump (e.g. two tweaks to the same codec function).
- Feature branch + PR — every change lands on a feature branch off
main(no direct pushes to main). The branch carries the commits below; open a pull request tomain. - Changelog entry — every such change is listed in
CHANGELOG.mdwith date, time and version (format:## v<version> — YYYY-MM-DD HH:MM, then bullet points of what changed). - Version bump — the version is updated in
package.json. - Verification must pass — there is no CI pipeline yet; before opening a PR both local checks must be green:
npm run buildandnpm test(vitest unit suite). - Tag + manual deploy after merge — once merged, tag the version on
main(e.g.v0.1.0). The service (http://bot.dev.kkhost.pl) is updated manually afterwards:docker compose up -d --buildon the host.
Verification
- Local checks —
npm run build(nest build) andnpm test(vitest; no e2e suite yet). Both must pass before a PR is opened. - Docker —
docker compose build, then afterdocker compose up -d:curl -s -H "Host: bot.dev.kkhost.pl" http://localhost/api/statusreturns JSON ({ connected, myNodeNum, host, port })."connected":falseis normal while the bridge/device is down — the service retries every 5 s.
API
- Live base URL —
http://bot.dev.kkhost.pl(Traefik → docker-compose stack on this host; container port 3000 via external network dev_net, no published ports). - Endpoints — all under the
/apiglobal prefix:GET /api/status({ connected, myNodeNum, host, port }),POST /api/send(body{ "text": "...", "to"?: <node number | node name | "!b" | "broadcast"> }— numbers pass through as-is (integer 1..0xffffffff), names resolve likeGET /api/nodes/:id, omitted/""/"!b"/"broadcast" → broadcast; unknown or ambiguous targets are a 400. Returns{ id }),GET /api/channels(the device's Meshtastic channels as{ index, name, uplinkEnabled, downlinkEnabled }[]; psk/keys redacted),GET /api/device(device info captured during the initial sync:{ myNodeNum, firmwareVersion?, metadata?, localConfig: {<section>: ...}, moduleConfig: {<module>: ...} }— DeviceMetadata as-is plus per-section Config/ModuleConfig messages; security publicKey/privateKey/adminKey and network wifiPsk redacted to"REDACTED"; in-memory, reset on reconnect),GET /api/messages(message history newest first: rx text withfrom, tx sends withto+statuspending→delivered/failed via routing ACKs — broadcasts stay pending because firmware strips want_ack; last 100 in memory (seeded from SQLite on startup) — backed by SQLite: re-upserted every MESH_POLL_MS and pruned to the newest 1000 rows, so it survives process restarts and bridge reconnects),POST /api/messages(body{ "text": "...", "from"?: <node number> }, returns{ id }; synthetic inbox injection for external triggers — records an rx entry in the history as if received on the mesh (channel 0) so external systems can push messages to trigger communication later; NOT transmitted over RF, nothing goes to the bridge or air; appears inGET /api/messageslike any other rx message),GET /api/telemetry(telemetry history newest first as{ from, at /* epoch ms */, kind, data }[]: live TELEMETRY_APP packets — device metrics and environment sensor readings where nodes have them; kind is the meshtastic.Telemetry oneof case name (deviceMetrics/environmentMetrics/airQualityMetrics/powerMetrics/localStats/healthMetrics/hostMetrics/trafficManagementStats), data that variant's payload with proto uint64 fields as decimal strings; last 100, in-memory, reset on reconnect),GET /api/nodes(known nodes sorted by num as{ num, longName?, shortName?, hwModel?, batteryLevel?, lastHeard: number|null, position?: { lat, lon, alt? }, snr?, hopsAway?, viaMqtt? }[], from the initial sync plus live POSITION_APP updates; reset on reconnect) andGET /api/nodes/:id(lookup by exact node number/numeric string, then case-insensitive exact long/short name, else unique substring; 404 when ambiguous or missing). Scalar API docs are served at/(OpenAPI JSON at/docs-json). Keep this list in sync with the controller. AllPOST /api/sendtraffic targets channel index 3 (test) on purpose — never the primary channel; override viaMESH_SEND_CHANNEL(default3). Message history is persisted to SQLite: file path fromMESH_DB_PATH(default./data/mesh-bot.db, parent dir auto-created), poller re-upserts everyMESH_POLL_MSms (default 1000) and prunes the table to the newest 1000 rows. Outbound notification webhook: when an RX text arrives on channelMESH_NOTIFY_CHANNEL(default3), a fire-and-forget HTTP POST of{text, from}(fromomitted when absent) goes toMESH_NOTIFY_URL— unset/empty disables it entirely; failed POSTs are retried up to 3 times with backoff (2 s / 10 s / 30 s) before being dropped; no response handling. Injected messages (POST /api/messages, channel 0) trigger it too when the notify channel is set to match. Messages from our own node are never notified (self-echo filter). - MCP endpoint —
POST /mcp(excluded from the/apiprefix in main.ts, like Scalar at/): an in-process MCP server over the official SDK's Streamable HTTP transport in stateless mode (sessionIdGenerator: undefined, freshMcpServer+ transport per request). Tools map 1:1 to MeshService methods:mesh_status,send_message(text, to?)(same resolution asPOST /api/send; errors surface as tool errors with the same messages),list_channels,list_nodes,get_node(id)(tool error when missing/ambiguous),get_messages,inject_message(text, from?)(synthetic inbox injection for external triggers — records an rx entry in the history as if received on the mesh; NOT sent over RF; same validation asPOST /api/messages),get_telemetry(same bounded recent window asGET /api/telemetry),device_info(same redaction asGET /api/device). Results are JSON in a text content block; sends ride channel index 3 like everything else. Note: the SDK's root entry is broken upstream (#2273) — import via subpaths (@modelcontextprotocol/sdk/server/mcp.jsetc.).
Other
- For longer terminal tasks you should use pty_* tools.
- Host npm (10.9.4) has an arborist crash bug ("edgesOut") — if plain
npmmisbehaves on installs, usenpx npm@latest .... The lockfile must stay in sync with package.json: the Docker build runsnpm ci.
- MESHTASTIC.md — rules for Meshtastic communication (owner authority, language, response protocol); read before acting on mesh messages.
- Hourly cron weather report job (
src/weather): fetches Open-Meteo current conditions and broadcasts a one-line report to the mesh on the pinned send channel; envWEATHER_CRON/WEATHER_LAT/WEATHER_LON/WEATHER_CITY.