Move UPGRADING.md to docs/ with versioned headings, extract LetsMesh decoding to docs/letsmesh.md

This commit is contained in:
Louis King
2026-04-17 20:41:37 +01:00
parent 57e7197eea
commit 03afe57a16
6 changed files with 115 additions and 74 deletions
+7 -6
View File
@@ -1,13 +1,13 @@
---
name: docs-sync
description: "Audits and fixes discrepancies between project source code (Python config, Docker Compose files) and primary documentation files (README.md, AGENTS.md, UPGRADING.md, .env.example, SCHEMAS.md). Extracts environment variables from Pydantic Settings, Click CLI options, and os.getenv calls; parses Docker Compose services, profiles, volumes, and env passthroughs; verifies feature flags, CLI commands, and file paths referenced in documentation. Produces a structured audit report and applies fixes to keep documentation accurate and up-to-date. Invoke after any config change, env var addition/removal, Docker service modification, feature flag change, or when documentation drift is suspected."
description: "Audits and fixes discrepancies between project source code (Python config, Docker Compose files) and primary documentation files (README.md, AGENTS.md, docs/upgrading.md, .env.example, SCHEMAS.md). Extracts environment variables from Pydantic Settings, Click CLI options, and os.getenv calls; parses Docker Compose services, profiles, volumes, and env passthroughs; verifies feature flags, CLI commands, and file paths referenced in documentation. Produces a structured audit report and applies fixes to keep documentation accurate and up-to-date. Invoke after any config change, env var addition/removal, Docker service modification, feature flag change, or when documentation drift is suspected."
license: MIT
compatibility: opencode
metadata:
author: https://github.com/agessaman
version: "0.1.0"
domain: quality
triggers: documentation sync, docs audit, env vars, config drift, .env.example, README, AGENTS.md, UPGRADING.md, SCHEMAS.md, docker compose docs, feature flags, documentation update, keep docs in sync, documentation accuracy
triggers: documentation sync, docs audit, env vars, config drift, .env.example, README, AGENTS.md, docs/upgrading.md, SCHEMAS.md, docker compose docs, feature flags, documentation update, keep docs in sync, documentation accuracy
role: specialist
scope: review
output-format: report
@@ -37,7 +37,8 @@ The following files are the documentation targets. All must be kept in sync:
|------|------|
| `README.md` | User-facing reference: env var tables, Docker instructions, feature list |
| `AGENTS.md` | AI agent instructions: env var list, project structure, conventions |
| `UPGRADING.md` | Upgrade guide: deprecated vars, new vars, migration steps |
| `docs/upgrading.md` | Upgrade guide: deprecated vars, new vars, migration steps |
| `docs/letsmesh.md` | LetsMesh packet decoding: normalization, channel keys, message handling |
| `.env.example` | Example environment file with comments and defaults |
| `SCHEMAS.md` | Event JSON schemas and database column mappings |
@@ -144,7 +145,7 @@ Load detailed guidance based on context:
- Treat Python source code (`common/config.py`, CLI modules, `os.getenv` calls) as the single source of truth for environment variables
- Treat `docker-compose*.yml` files as the source of truth for Docker configuration
- Check ALL five documentation files on every audit
- Check ALL primary documentation files on every audit
- Include `.env.example` comment verification
- Include `docker-compose*.yml` inline comment verification
- Verify default values match exactly (type-aware: `true` vs `"true"`, port numbers as strings vs ints)
@@ -159,8 +160,8 @@ Load detailed guidance based on context:
- Add documentation for variables that don't exist in source
- Remove content from documentation without confirming it's stale in source
- Change the formatting style of existing documentation (match surrounding content)
- Modify UPGRADING.md historical content (deprecated var lists, old instructions)
- Skip any of the 5 documentation files
- Modify docs/upgrading.md historical content (deprecated var lists, old instructions)
- Skip any of the primary documentation files
- Guess at defaults — always verify against actual source code
- Treat test compose files (`tests/e2e/`) as documentation targets (they are test fixtures)
@@ -96,13 +96,14 @@ AGENTS.md has a "Key variables" subsection under "Environment Variables". Verify
- [ ] References to `PLAN.md` — should be removed (file deleted)
- [ ] References to `TASKS.md` — should be removed (file deleted)
- [ ] References to `SCHEMAS.md` — should remain (file exists)
- [ ] References to `UPGRADING.md` — should remain (file exists)
- [ ] References to `docs/upgrading.md` — should remain (file exists)
- [ ] References to `docs/letsmesh.md` — should remain (file exists)
## 3. UPGRADING.md
## 3. docs/upgrading.md
### Deprecated Variables
UPGRADING.md lists variables to remove during upgrade. Verify:
docs/upgrading.md lists variables to remove during upgrade. Verify:
- [ ] Each deprecated var truly no longer exists in `config.py` or any CLI module
- [ ] Removal instructions are clear
@@ -131,6 +132,20 @@ UPGRADING.md lists variables to remove during upgrade. Verify:
- [ ] Column renames documented accurately (e.g., `receiver_node_id``observer_node_id`)
- [ ] Table renames documented accurately (e.g., `event_receivers``event_observers`)
## 3b. docs/letsmesh.md
### Packet Decoding Documentation
docs/letsmesh.md documents the LetsMesh packet normalization and decoding behavior. Verify:
- [ ] MQTT subscription topics match `subscriber.py` topic patterns
- [ ] Payload type mappings match `letsmesh_decoder.py` and `letsmesh_normalizer.py` logic
- [ ] Channel key handling documented matches `COLLECTOR_CHANNEL_KEYS` config behavior
- [ ] Known channel indexes (`17 -> Public`, `217 -> #test`) match built-in defaults in decoder
- [ ] Message normalization rules match collector handler implementations
- [ ] GPS/location update behavior documented matches advertisement handler logic
- [ ] No stale decoder behavior documented (e.g., references to Node.js decoder)
## 4. .env.example
### Section Structure
@@ -209,7 +224,7 @@ For each table documented in SCHEMAS.md:
## Cross-File Consistency Checks
These checks ensure all 5 files are consistent with each other:
These checks ensure all primary documentation files are consistent with each other:
### Env Var Coverage Matrix
@@ -221,7 +236,7 @@ Every hub-consumed env var should appear in:
| `README.md` | Yes | Table row with default + description |
| `AGENTS.md` | Yes | Mentioned in env vars section |
| `.env.example` | Yes | Entry with default + comment |
| `UPGRADING.md` | Only if new/renamed/deprecated | Migration instruction |
| `docs/upgrading.md` | Only if new/renamed/deprecated | Migration instruction |
Every passthrough env var should appear in:
@@ -240,13 +255,13 @@ Every passthrough env var should appear in:
### Stale Reference Sweep
Check all 5 files for references to removed items:
Check all primary documentation files for references to removed items:
- [ ] `PLAN.md` — removed, references should be deleted from AGENTS.md
- [ ] `TASKS.md` — removed, references should be deleted from AGENTS.md
- [ ] Old compose profiles (`receiver`, `sender`, `mock`) — should only exist in UPGRADING.md as deprecated
- [ ] Old service names (`interface-receiver`, `interface-sender`) — should only exist in UPGRADING.md as deprecated
- [ ] Old env var names (`COLLECTOR_LETSMESH_DECODER_*`, `SERIAL_BAUD`, etc.) — should only exist in UPGRADING.md as deprecated
- [ ] Old compose profiles (`receiver`, `sender`, `mock`) — should only exist in docs/upgrading.md as deprecated
- [ ] Old service names (`interface-receiver`, `interface-sender`) — should only exist in docs/upgrading.md as deprecated
- [ ] Old env var names (`COLLECTOR_LETSMESH_DECODER_*`, `SERIAL_BAUD`, etc.) — should only exist in docs/upgrading.md as deprecated
## Applying Fixes
@@ -256,5 +271,5 @@ When discrepancies are found:
2. **Determine scope** — which files need updating
3. **Make minimal edits** — only change what's wrong, preserve surrounding formatting
4. **Match existing style** — tables use same column order, comments use same format, sections use same headers
5. **Preserve historical content**UPGRADING.md deprecated var lists are historical, do not remove them
5. **Preserve historical content**docs/upgrading.md deprecated var lists are historical, do not remove them
6. **Verify after editing** — re-read the changed section to confirm accuracy
+7 -2
View File
@@ -15,7 +15,7 @@ This document provides context and guidelines for AI coding assistants working o
- `source .venv/bin/activate`
* You MUST install all project dependencies using `pip install -e ".[dev]"` command`
* You MUST install `pre-commit` for quality checks
* You MUST keep project documentation in sync with behavior/config/schema changes made in code (at minimum update relevant sections in `README.md`, `SCHEMAS.md`, and/or `UPGRADING.md` when applicable)
* You MUST keep project documentation in sync with behavior/config/schema changes made in code (at minimum update relevant sections in `README.md`, `SCHEMAS.md`, `docs/upgrading.md`, `docs/letsmesh.md` when applicable)
* Before commiting:
- Run **targeted tests** for the components you changed, not the full suite:
- `pytest tests/test_web/` for web-only changes (templates, static JS, web routes)
@@ -37,7 +37,8 @@ MeshCore Hub is a Python 3.14+ monorepo for managing and orchestrating MeshCore
## Key Documentation
- [SCHEMAS.md](SCHEMAS.md) - MeshCore event JSON schemas and database mappings
- [UPGRADING.md](UPGRADING.md) - Upgrade guide for breaking changes
- [docs/upgrading.md](docs/upgrading.md) - Upgrade guide for breaking changes
- [docs/letsmesh.md](docs/letsmesh.md) - LetsMesh packet decoding details
## Technology Stack
@@ -335,6 +336,10 @@ meshcore-hub/
├── docker-compose.dev.yml # Development overrides (port mappings)
├── docker-compose.prod.yml # Production overrides (proxy network)
├── docker-compose.traefik.yml # Optional Traefik labels
├── docs/ # Documentation
│ ├── images/ # Screenshots and images
│ ├── letsmesh.md # LetsMesh packet decoding details
│ └── upgrading.md # Upgrade guide for breaking changes
└── SCHEMAS.md
```
+10 -30
View File
@@ -8,7 +8,7 @@
Python 3.14+ platform for managing and orchestrating MeshCore mesh networks.
> [!WARNING]
> **BREAKING CHANGES** - The latest release replaces Mosquitto with a JWT-based MQTT broker, removes the proprietary receiver service in favor of [meshcore-packet-capture](https://github.com/agessaman/meshcore-packet-capture), and renames `receiver_node_id` to `observer_node_id` in the database. If upgrading from a previous version, see [UPGRADING.md](UPGRADING.md) for migration steps.
> **BREAKING CHANGES** - The latest release replaces Mosquitto with a JWT-based MQTT broker, removes the proprietary receiver service in favor of [meshcore-packet-capture](https://github.com/agessaman/meshcore-packet-capture), and renames `receiver_node_id` to `observer_node_id` in the database. If upgrading from a previous version, see [docs/upgrading.md](docs/upgrading.md) for migration steps.
![MeshCore Hub Web Dashboard](docs/images/web.png)
@@ -354,31 +354,7 @@ All components are configured via environment variables. Create a `.env` file or
#### LetsMesh Packet Decoding
The collector subscribes to packets published by [meshcore-packet-capture](https://github.com/agessaman/meshcore-packet-capture):
- `<prefix>/+/+/packets`
- `<prefix>/+/+/status`
- `<prefix>/+/+/internal`
Normalization behavior:
- `status` packets are stored as informational `letsmesh_status` events and are not mapped to `advertisement` rows.
- Decoder payload type `4` is mapped to `advertisement` when node identity metadata is present.
- Decoder payload type `11` (control discover response) is mapped to `contact`.
- Decoder payload type `9` is mapped to `trace_data`.
- Decoder payload type `8` is mapped to informational `path_updated` events.
- Decoder payload type `1` can map to native response events (`telemetry_response`, `battery`, `path_updated`, `status_response`) when decrypted structured content is available.
- `packet_type=5` packets are mapped to `channel_msg_recv`.
- `packet_type=1`, `2`, and `7` packets are mapped to `contact_msg_recv` when decryptable text is available.
- For channel packets, if a channel key is available, a channel label is attached (for example `Public` or `#test`) for UI display.
- In the messages feed and dashboard channel sections, known channel indexes are preferred for labels (`17 -> Public`, `217 -> #test`) to avoid stale channel-name mismatches.
- Additional channel names are loaded from `COLLECTOR_CHANNEL_KEYS` when entries are provided as `label=hex` (for example `bot=<key>`).
- Decoder-advertisement packets with location metadata update node GPS (`lat/lon`) for map display.
- This keeps advertisement listings focused on node advert traffic only, not observer status telemetry.
- Packets without decryptable message text are kept as informational `letsmesh_packet` events and are not shown in the messages feed; when decode succeeds the decoded JSON is attached to those packet log events.
- When decoder output includes a human sender (`payload.decoded.decrypted.sender`), message text is normalized to `Name: Message` before storage; receiver/observer names are never used as sender fallback.
- The collector keeps built-in keys for `Public` and `#test`, and merges any additional keys from `COLLECTOR_CHANNEL_KEYS`.
- Docker runtime uses the native Python `meshcoredecoder` library (no external Node.js dependency).
For details on how the collector normalizes and decodes LetsMesh packets, see [docs/letsmesh.md](docs/letsmesh.md).
### Webhooks
@@ -822,15 +798,19 @@ meshcore-hub/
├── docker-compose.dev.yml # Development overrides (port mappings)
├── docker-compose.prod.yml # Production overrides (proxy network)
├── docker-compose.traefik.yml # Optional Traefik labels
├── SCHEMAS.md # Event schema documentation
├── UPGRADING.md # Upgrade guide for breaking changes
└── AGENTS.md # AI assistant guidelines
├── docs/ # Documentation
│ ├── images/ # Screenshots and images
│ ├── letsmesh.md # LetsMesh packet decoding details
│ └── upgrading.md # Upgrade guide for breaking changes
├── SCHEMAS.md # Event schema documentation
└── AGENTS.md # AI assistant guidelines
```
## Documentation
- [SCHEMAS.md](SCHEMAS.md) - MeshCore event schemas
- [UPGRADING.md](UPGRADING.md) - Upgrade guide for breaking changes
- [docs/upgrading.md](docs/upgrading.md) - Upgrade guide for breaking changes
- [docs/letsmesh.md](docs/letsmesh.md) - LetsMesh packet decoding details
- [AGENTS.md](AGENTS.md) - Guidelines for AI coding assistants
## Contributing
+36
View File
@@ -0,0 +1,36 @@
# LetsMesh Packet Decoding
The collector subscribes to packets published by [meshcore-packet-capture](https://github.com/agessaman/meshcore-packet-capture):
- `<prefix>/+/+/packets`
- `<prefix>/+/+/status`
- `<prefix>/+/+/internal`
## Normalization Behavior
- `status` packets are stored as informational `letsmesh_status` events and are not mapped to `advertisement` rows.
- Decoder payload type `4` is mapped to `advertisement` when node identity metadata is present.
- Decoder payload type `11` (control discover response) is mapped to `contact`.
- Decoder payload type `9` is mapped to `trace_data`.
- Decoder payload type `8` is mapped to informational `path_updated` events.
- Decoder payload type `1` can map to native response events (`telemetry_response`, `battery`, `path_updated`, `status_response`) when decrypted structured content is available.
- `packet_type=5` packets are mapped to `channel_msg_recv`.
- `packet_type=1`, `2`, and `7` packets are mapped to `contact_msg_recv` when decryptable text is available.
## Channel Keys
- For channel packets, if a channel key is available, a channel label is attached (for example `Public` or `#test`) for UI display.
- In the messages feed and dashboard channel sections, known channel indexes are preferred for labels (`17 -> Public`, `217 -> #test`) to avoid stale channel-name mismatches.
- Additional channel names are loaded from `COLLECTOR_CHANNEL_KEYS` when entries are provided as `label=hex` (for example `bot=<key>`).
- The collector keeps built-in keys for `Public` and `#test`, and merges any additional keys from `COLLECTOR_CHANNEL_KEYS`.
## Location and Messages
- Decoder-advertisement packets with location metadata update node GPS (`lat/lon`) for map display.
- This keeps advertisement listings focused on node advert traffic only, not observer status telemetry.
- Packets without decryptable message text are kept as informational `letsmesh_packet` events and are not shown in the messages feed; when decode succeeds the decoded JSON is attached to those packet log events.
- When decoder output includes a human sender (`payload.decoded.decrypted.sender`), message text is normalized to `Name: Message` before storage; receiver/observer names are never used as sender fallback.
## Decoder Runtime
- Docker runtime uses the native Python `meshcoredecoder` library (no external Node.js dependency).
+30 -26
View File
@@ -1,8 +1,12 @@
# Upgrading MeshCore Hub
This guide covers upgrading from a previous MeshCore Hub release to the current version. The latest release includes **breaking changes** to the MQTT broker, packet capture service, and data ingestion pipeline.
This guide covers upgrading from a previous MeshCore Hub release to the current version. Check the relevant version section below before upgrading.
## Overview of Changes
## v0.9.0
This release includes **breaking changes** to the MQTT broker, packet capture service, and data ingestion pipeline.
### Overview of Changes
| Area | Before | After |
|------|--------|-------|
@@ -20,7 +24,7 @@ This guide covers upgrading from a previous MeshCore Hub release to the current
| Container names | `meshcore-*` | Parameterized via `COMPOSE_PROJECT_NAME` (default: `hub-*`) |
| Volume names | `meshcore_*` | Parameterized via `COMPOSE_PROJECT_NAME` (default: `hub_*`) |
## Step 1: Backup
### Step 1: Backup
**Do not skip this step.** Back up all data volumes before proceeding.
@@ -40,7 +44,7 @@ docker run --rm -v meshcore_hub_data:/data -v $(pwd)/backup:/backup \
alpine sh -c "cd / && tar xzf /backup/meshcore_hub_data-YYYYMMDD-HHMMSS.tar.gz"
```
## Step 2: Stop and Remove Containers
### Step 2: Stop and Remove Containers
Stop all services and remove orphaned containers from the old configuration:
@@ -50,7 +54,7 @@ docker compose --profile all down --remove-orphans
> **Important:** Do NOT use `--volumes` / `-v`. That would delete your database. The `--remove-orphans` flag cleans up old services (like `interface-receiver`, `interface-sender`) that no longer exist in the new compose file.
## Step 3: Rename Docker Volumes
### Step 3: Rename Docker Volumes
Container and volume names are now parameterized via `COMPOSE_PROJECT_NAME`. The default is `hub`, so volumes are renamed from `meshcore_*` to `hub_*`.
@@ -60,7 +64,7 @@ First, check which volumes you have:
docker volume ls | grep meshcore
```
### Volumes to migrate
#### Volumes to migrate
These volumes always need migrating:
@@ -70,7 +74,7 @@ These volumes always need migrating:
> **Note:** `observer_data` and `mqtt_data` are new — they are created automatically on first run and do not need migrating.
### Option A: Rename (Docker Engine 23.0+)
#### Option A: Rename (Docker Engine 23.0+)
> **Note:** `docker volume rename` is not available in all Docker builds (e.g., Docker Desktop). If the command is not found, use Option B instead.
@@ -78,7 +82,7 @@ These volumes always need migrating:
docker volume rename meshcore_hub_data hub_data
```
### Option B: Copy (all Docker versions)
#### Option B: Copy (all Docker versions)
If `docker volume rename` is not available in your Docker build:
@@ -97,7 +101,7 @@ docker volume rm meshcore_hub_data
> **Note:** After migrating volumes, you may see warnings like `volume "hub_data" already exists but was not created by Docker Compose. Use \`external: true\` to use an existing volume`. This is safe to ignore — it appears because the volumes were created manually during migration rather than by Docker Compose. Fresh deployments will not see this warning.
## Step 4: Update Configuration Files
### Step 4: Update Configuration Files
Download the latest configuration files:
@@ -113,9 +117,9 @@ wget -O .env.example https://raw.githubusercontent.com/ipnet-mesh/meshcore-hub/m
Then compare your existing `.env` against the new `.env.example` and update it (see Step 5).
## Step 5: Migrate Your `.env` File
### Step 5: Migrate Your `.env` File
### Variables to Remove
#### Variables to Remove
These variables no longer exist and should be removed from your `.env`:
@@ -148,7 +152,7 @@ MQTT_EXTERNAL_PORT=1883
MQTT_WS_PORT=9001
```
### Variables to Update
#### Variables to Update
| Variable | Old Value | New Value | Notes |
|----------|-----------|-----------|-------|
@@ -159,7 +163,7 @@ MQTT_WS_PORT=9001
> **Note:** The Python-level defaults for `MQTT_TRANSPORT` and `MQTT_WS_PATH` are now `websockets` and `/`, matching the Docker Compose and `.env.example` values. No additional configuration is needed for non-Docker users.
### Variables to Add
#### Variables to Add
```bash
# Docker Compose project name (container and volume prefix)
@@ -177,7 +181,7 @@ PACKETCAPTURE_IATA=LOC
All other `PACKETCAPTURE_*` variables have sensible defaults in `docker-compose.yml` and only need to be set in `.env` if you want to override them. See `.env.example` for the full list.
## Step 6: Run Database Migration
### Step 6: Run Database Migration
The migration renames `receiver_node_id``observer_node_id` across all event tables, `event_receivers``event_observers`, and `received_at``observed_at` in the event observers table:
@@ -191,9 +195,9 @@ This runs automatically as part of the `core` profile, but can also be run stand
docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile migrate run --rm migrate
```
## Step 7: Start Services
### Step 7: Start Services
### With local MQTT broker (single-host deployment)
#### With local MQTT broker (single-host deployment)
```bash
# Start everything including the MQTT broker
@@ -203,14 +207,14 @@ docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile mqtt --
docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile mqtt --profile core --profile observer up -d
```
### With external MQTT broker
#### With external MQTT broker
```bash
# Start core services only (broker runs elsewhere)
docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile core up -d
```
### Verify
#### Verify
```bash
# Check all containers are running
@@ -223,16 +227,16 @@ docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile all log
open http://localhost:8080
```
## Notes
### Notes
### JWT-Based Packet Capture Authentication
#### JWT-Based Packet Capture Authentication
The new packet capture service ([meshcore-packet-capture](https://github.com/agessaman/meshcore-packet-capture)) uses the LetsMesh Observer model:
- **No custom MQTT credentials needed for publishing.** Authentication is handled via JWT tokens signed by the capture device's hardware public key. The MQTT broker validates the JWT and authorizes publishing automatically.
- The collector connects as a **subscriber** to read all published events, including `/internal` topics. Configure `MQTT_USERNAME` and `MQTT_PASSWORD` to match the broker's subscriber account.
### Production MQTT Configuration
#### Production MQTT Configuration
In production, the MQTT WebSocket server should be hosted behind a TLS/SSL-terminated reverse proxy (e.g., Nginx Proxy Manager, Caddy, Traefik) under the `/mqtt` path. The proxy handles TLS termination and forwards plain WebSocket connections to the broker on port 1883.
@@ -254,7 +258,7 @@ MQTT_TLS=true
MQTT_TOKEN_AUDIENCE=mqtt.example.com # your public domain
```
### Existing LetsMesh Observer Installs
#### Existing LetsMesh Observer Installs
If you already run [meshcore-packet-capture](https://github.com/agessaman/meshcore-packet-capture) separately, configure **MQTT server #3** to point at your MeshCore Hub MQTT broker. Servers #1 and #2 are reserved for Let's Mesh US (`mqtt-us-v1.letsmesh.net`) and Let's Mesh EU (`mqtt-eu-v1.letsmesh.net`) respectively.
@@ -269,7 +273,7 @@ PACKETCAPTURE_MQTT3_USE_AUTH_TOKEN=true
PACKETCAPTURE_MQTT3_TOKEN_AUDIENCE=mqtt.localhost
```
### Removed Services
#### Removed Services
The following Docker Compose services have been removed:
@@ -281,7 +285,7 @@ The following Docker Compose services have been removed:
The `observer` service uses the [meshcore-packet-capture](https://github.com/agessaman/meshcore-packet-capture) image and is included in `docker-compose.yml` under the `observer` profile for an easy transition.
### New Docker Compose File Structure
#### New Docker Compose File Structure
The Docker Compose configuration is now split into multiple files:
@@ -307,7 +311,7 @@ docker compose -f docker-compose.yml -f docker-compose.prod.yml -f docker-compos
Container and volume names are parameterized via `COMPOSE_PROJECT_NAME` in `.env`. This enables multiple instances (e.g., `hub-prod`, `hub-beta`) on the same Docker host.
### Removed API Endpoints
#### Removed API Endpoints
The command dispatch API endpoints have been removed:
@@ -315,7 +319,7 @@ The command dispatch API endpoints have been removed:
- `POST /api/v1/commands/send-channel-message`
- `POST /api/v1/commands/send-advertisement`
### Native Python Decoder
#### Native Python Decoder
The Node.js `meshcore-decoder` CLI tool has been replaced by the native Python `meshcoredecoder` library. This means: