Claude Skill

meshcore-packet-capture

Capture MeshCore Companion packets via BLE, serial, or TCP. Do not use this skill for unrelated requests; route to the nearest named specialist.

LLM Mart · 0 points · 10 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download magnus919-agent-skills-meshcore-packet-capture-addad86.zip · 16 KB
Part of magnus919/agent-skills — 145 skills

Install

skills CLI npx skills add https://github.com/magnus919/agent-skills/tree/main/meshcore-packet-capture
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install magnus919-agent-skills@llmmart
Git git clone https://github.com/magnus919/agent-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole magnus919/agent-skills collection as a plugin from our marketplace. Git is the plain clone.

README

MeshCore Packet Capture — Companion Radio Operations

Operate MeshCore Companion radio packet capture without reconstructing the upstream repository each time. The skill covers BLE, serial, and TCP connections, MQTT output, authentication, Docker, system services, NixOS, and evidence-first troubleshooting.

Why Install This Skill

After installing it, your agent can configure and run the right transport, distinguish Companion radios from repeaters and RoomServers, publish packets to one or more MQTT brokers, and troubleshoot failures at the radio, broker, container, and service layers.

It also preserves the details that are easy to get wrong: TOML/environment precedence, sequential MQTT broker slots, IATA-based topics, device-signing fallback, macOS BLE permissions, and Docker hardware access.

What You Get

Path Purpose
SKILL.md Operational workflow, safety boundaries, routing, and completion checks
references/configuration.md TOML/env schema, broker auth, topic templates, and packet filters
references/deployment-and-troubleshooting.md Manual, managed, Docker, NixOS, and diagnostic procedures
references/source-index.md Upstream commit, coverage inventory, and refresh notes

Quick Start

pipx install meshcore-packet-capture
meshcore-packet-capture --debug --no-mqtt
# Configure IATA + MQTT, then run:
meshcore-packet-capture --debug

Triggers

Use this skill for meshcore-packet-capture, MeshCore Companion radios, BLE/serial/TCP packet capture, MQTT packet publishing, LetsMesh, PACKETCAPTURE_*, config.toml, Docker Compose deployment, systemd, launchd, NixOS, or connection troubleshooting.

Requirements

Python 3.11+, the upstream runtime dependencies, a MeshCore Companion radio, and access to the selected transport. MQTT publishing additionally requires a reachable broker and either password or token authentication. Docker hardware access is most reliable on Linux.

Skill manifest

MeshCore Packet Capture

Use this skill when operating, configuring, deploying, or troubleshooting meshcore-packet-capture, especially requests involving Companion radios, BLE/serial/TCP capture, MQTT publishing, LetsMesh, TOML configuration, Docker, systemd, launchd, or NixOS.

Scope boundary

This project captures from MeshCore Companion radios only. Do not route repeater or RoomServer capture here; use meshcoretomqtt instead.

Operating workflow

  1. Identify the transport before changing configuration:
    • ble: direct address, device name, or general scan.
    • serial: one or more serial ports; the runtime uses the first configured port.
    • tcp: host and port, with SDK auto-reconnect enabled by default.
  2. Prefer the installed CLI:
    meshcore-packet-capture --help
    meshcore-packet-capture --debug
    
    For a checkout, use python3 -m meshcore_packet_capture or python3 packet_capture.py. Verified CLI boundary (v2.2.0): the parser exposes --output, --verbose, --debug, --no-mqtt, repeatable --config, and the neighbors on-demand pair --neighbors-now / --neighbors-exit (run one zero-hop neighbor discovery + scopes cycle immediately; with --neighbors-exit, quit when the cycle finishes). It does not expose --show-config or a dry-run flag. Run the exact installed binary with --help before documenting or using any other flag; do not invent a configuration-preview command.
  3. Configure one IATA code and at least one MQTT broker before expecting network uploads. LOC is a placeholder, not a useful deployment identity.
  4. Start with --no-mqtt when isolating radio connectivity. Add MQTT only after the device captures packets locally.
  5. Verify the actual boundary after every change: device connection logs, packet output, broker connection, and service/container status. Do not treat a successful install as proof of a working capture.

Mutation gate: when the request is read-only or plan-only, do not present stop/start/edit/delete/restart commands as actions to perform. Describe future mutations separately, label every example as unexecuted, and require target, scope, and rollback confirmation before the first mutation.

Configuration rules

Configuration precedence is highest to lowest:

  1. Real PACKETCAPTURE_* process environment.
  2. TOML: /etc/meshcore-packet-capture/config.toml, then sorted config.d/*.toml.
  3. Legacy .env.local, then .env in the working/package directory.

Use --config PATH more than once to load only the named TOML files in order. Later files override earlier files. TOML broker blocks merge by name; enabled brokers are flattened into sequential PACKETCAPTURE_MQTT<n>_* slots. Keep user overrides in config.d/99-user.toml.

Minimal TOML shape:

[general]
iata = "SEA"

[capture]
connection_type = "serial"
# ble_pin = "123456"   # optional six-digit PIN for BLE connections

[serial]
ports = ["/dev/ttyUSB0"]

[[broker]]
name = "home"
enabled = true
server = "mqtt.example.com"
port = 1883
transport = "tcp"
# include_decoded = true   # publish the nested "decoded" object to this broker
# neighbors = true         # publish the neighbors snapshot to this broker

[broker.auth]
method = "password"
username = "user"
password = "secret"

Read references/configuration.md for the complete option map, broker authentication, topic templates, packet filters, and precedence edge cases.

Optional feature blocks (v2.1.0+)

  • Payload decoding (decode_payloads, include_decoded, decode_hashtag_channels, decode_channel_keys, decode_include_public): adds a nested decoded object with plain-text / structured fields. GRP_TXT channel messages are decrypted (sender, text), ADVERTs are parsed (name, role, lat/lon), and human-readable type/route labels plus path are included. Raw fields are unchanged. Direct messages (TXT_MSG) cannot be decrypted by a passive observer. Off by default; opt in per broker with include_decoded = true in its [[broker]] block.
  • Neighbors publishing (neighbors per broker plus neighbors_interval_hours, neighbors_discover_window, neighbors_command_timeout, neighbors_scope_timeout, neighbors_scope_min_timeout, neighbors_scope_gap, neighbors_cycle_timeout, neighbors_max, neighbors_self_scopes): publishes a periodic zero-hop neighbor table plus each neighbor's region scopes to the neighbors topic. Off by default; the cycle only runs when at least one enabled broker sets neighbors = true. Requires an IATA (or an explicit [broker.topics] neighbors). The interval is clamped to 12–336 hours to match the observer firmware. --neighbors-now / --neighbors-exit trigger a cycle on demand.
  • Log rotation (log_rotation = off|size|time, log_max_bytes, log_rotation_when, log_backup_count): only applies when an output file is given with --output.

MQTT and authentication

  • Brokers are sequential and have no fixed upper limit. A missing MQTT<n>_ENABLED terminates discovery, so do not leave a numbering gap.
  • Supported transports are tcp and websockets; use TLS explicitly for secure transport.
  • Password auth uses username and password.
  • Token auth uses Ed25519 JWT-style tokens. On-device signing is preferred when a connected radio supports it; Python signing is the fallback when a valid 64-byte private key is available.
  • Never put private keys in a public skill, command transcript, committed .env, or shared Docker Compose file. Prefer a protected key file or secret injection.
  • Topic placeholders are {IATA}, {IATA_lower}, {PUBLIC_KEY}, and {TOKEN}. Per-broker topics override global topics. RAW is not published unless explicitly configured. Topics per broker: status, packets, decoded, debug, raw, neighbors. The decoded topic carries the nested decoded object when include_decoded is enabled; the neighbors topic carries the periodic neighbor snapshot when neighbors is enabled.
  • Per-broker owner and email override the global capture.owner_public_key / capture.owner_email for token-auth brokers.
  • LetsMesh Analyzer brokers require a configured IATA. Do not silently accept the default LOC for a LetsMesh deployment.

Deployment defaults

Legacy hybrid installs are possible: a systemd or launchd unit may wrap a checkout under a user's home directory and load .env.local, without /opt or /etc TOML files. Inspect the running command, working directory, service unit, and environment/config source before choosing the modern managed-install path.

  • Manual/PyPI: pipx install meshcore-packet-capture gives the CLI and does not install a background service.
  • Managed Linux/macOS: the root bootstrap installer creates /opt/meshcore-packet-capture, /etc/meshcore-packet-capture, a virtual environment, and a system service. It installs the latest published release by default; use --tag or --branch to pin.
  • User service (v2.1.0+): for a local checkout on Linux, ./install.sh --user-service creates a per-user systemd service that runs from the checkout's .venv (pass --repo-dir PATH if the checkout is not the script's directory). Config files live in the repo itself (.env, .env.local, config.toml, config.d/). Remove with ./uninstall.sh --user-service from the same checkout; add --remove-venv to also delete the local .venv. Manage with systemctl --user status|restart meshcore-packet-capture and journalctl --user -u meshcore-packet-capture -f.
  • macOS BLE: use the per-user LaunchAgent because Bluetooth permission belongs to the login user. Serial/TCP can use a LaunchDaemon.
  • Docker: use the published image or docker compose up -d; serial needs a device mapping, while BLE generally needs privileged: true and may need host networking. Linux is the most reliable container host for hardware access.
  • NixOS: use services.meshcore-packet-capture and rebuild with sudo nixos-rebuild switch.

Read references/deployment-and-troubleshooting.md for service commands, Docker hardware access, migration/update behavior, and bounded diagnostics.

Output and verification

Normal mode prints minimal packet information. --verbose adds JSON packet data; --debug adds connection, retry, packet parsing, and MQTT diagnostics. --output PATH writes packet data to a file. Captured records include device identity, timestamp, packet type, route, payload length, raw hex, SNR, RSSI, and a hash. When decode_payloads is enabled, records also carry a nested decoded object with human-readable fields (channel message sender/text, ADVERT name/role/coordinates, type/route labels).

When troubleshooting, collect evidence in this order:

  1. meshcore-packet-capture --debug --no-mqtt and confirm a real BLE, serial, or TCP connection.
  2. Confirm packet records appear in console or the --output file.
  3. Enable one broker and inspect broker connection/reconnect messages.
  4. Check the resolved environment/config and topic names, without printing secrets.
  5. For a managed service, inspect the service's own logs and status. For Docker, use docker compose config, docker compose ps, and docker compose logs.

Do not delete the install directory, state directory, or configuration during diagnosis. The uninstaller is interactive and backs up user configuration, but it still performs destructive removal only after confirmation.

When not to use

Do not use this skill for MeshCore repeater or RoomServer packet capture, generic MQTT administration, or implementing changes inside the upstream repository. For code changes, use the repository's contribution workflow and verify the project tests separately.

References

File Load when
references/configuration.md Writing TOML/env configuration, MQTT topics, authentication, or packet filters
references/deployment-and-troubleshooting.md Installing, upgrading, running Docker/systemd/launchd/NixOS, or debugging a failed service
references/source-index.md Checking source coverage, version markers, or revalidating this skill against upstream

Completion condition

Stop when the selected transport connects, a packet is observed locally, the intended MQTT topic receives data if MQTT is in scope, and the relevant service/container reports healthy. If hardware, credentials, or broker access is unavailable, report that exact unverified boundary instead of claiming success.

Files (agent-skills)
  • evals
    • evals.json 3.9 KB
      {
        "schema_version": 1,
        "skill_name": "meshcore-packet-capture",
        "evals": [
          {
            "id": "neighbors-now-cli",
            "prompt": "A user wants to run a single neighbors discovery + scopes cycle against their connected MeshCore companion right now, then have the capture process keep running afterward. Which meshcore-packet-capture CLI flag combination triggers one cycle immediately without quitting? What flag would make it exit after the cycle?",
            "expected_output": "--neighbors-now runs one cycle immediately; --neighbors-exit (combined with --neighbors-now) exits once the cycle finishes.",
            "assertions": [
              "response_contains:--neighbors-now",
              "response_contains:--neighbors-exit",
              "exit_status:completed",
              "activation_evidence_contains:meshcore-packet-capture"
            ],
            "files": ["SKILL.md", "references/configuration.md"]
          },
          {
            "id": "neighbors-broker-optin",
            "prompt": "A deployment publishes packet captures to an MQTT broker but no neighbors snapshot ever appears, even though the firmware supports it. The capture logs show a connected radio and healthy broker. What per-broker config must be set, and what additional requirement exists for the neighbors topic?",
            "expected_output": "The broker block must set neighbors = true, and an IATA (or an explicit [broker.topics] neighbors override) is required; the cycle only runs when at least one enabled broker opts in.",
            "assertions": [
              "response_contains:neighbors = true",
              "response_contains:IATA",
              "exit_status:completed",
              "activation_evidence_contains:meshcore-packet-capture"
            ],
            "files": ["SKILL.md", "references/configuration.md"]
          },
          {
            "id": "payload-decoding-limits",
            "prompt": "With decode_payloads enabled, which MeshCore packet types gain a nested decoded object, and which type remains opaque to a passive observer?",
            "expected_output": "GRP_TXT channel messages are decrypted (sender, text) and ADVERTs are parsed (name, role, lat/lon); direct TXT_MSG messages cannot be decrypted by a passive observer.",
            "assertions": [
              "response_contains:GRP_TXT",
              "response_contains:TXT_MSG",
              "response_contains:decoded",
              "exit_status:completed",
              "activation_evidence_contains:meshcore-packet-capture"
            ],
            "files": ["SKILL.md", "references/configuration.md"]
          },
          {
            "id": "user-service-install",
            "prompt": "A Linux user has a local checkout of meshcore-packet-capture and wants a per-user systemd service without root install under /opt or /etc. What installer invocation creates it, and what command removes it later?",
            "expected_output": "./install.sh --user-service creates the per-user service (optionally with --repo-dir PATH); ./uninstall.sh --user-service removes it, with --remove-venv to also delete the local .venv.",
            "assertions": [
              "response_contains:--user-service",
              "response_contains:--repo-dir",
              "response_contains:--remove-venv",
              "exit_status:completed",
              "activation_evidence_contains:meshcore-packet-capture"
            ],
            "files": ["SKILL.md", "references/deployment-and-troubleshooting.md"]
          },
          {
            "id": "config-precedence",
            "prompt": "In meshcore-packet-capture, which source wins when the same setting appears in the process environment, an explicit --config TOML file, and a .env file? Where should a user put their own overrides on a managed install?",
            "expected_output": "Real process environment variables win over TOML, which wins over .env; user overrides belong in /etc/meshcore-packet-capture/config.d/99-user.toml (kept after updates).",
            "assertions": [
              "response_contains:99-user.toml",
              "response_contains:environment",
              "exit_status:completed",
              "activation_evidence_contains:meshcore-packet-capture"
            ],
            "files": ["SKILL.md", "references/configuration.md"]
          }
        ]
      }
      
  • references
    • configuration.md 8.4 KB
      # Configuration reference
      
      Source basis: `agessaman/meshcore-packet-capture` commit `c011f4e` (v2.2.0), inspected 2026-08-09. Re-check upstream before relying on version-sensitive details.
      
      ## TOML layout
      
      The primary file is `/etc/meshcore-packet-capture/config.toml`. Sorted `config.d/*.toml` files are applied afterward. The installer puts presets in `config.d/10-*.toml`; put local changes in `config.d/99-user.toml`.
      
      ```toml
      [general]
      iata = "SEA"
      log_level = "INFO"
      
      [topics]
      status = "meshcore/{IATA}/{PUBLIC_KEY}/status"
      packets = "meshcore/{IATA}/{PUBLIC_KEY}/packets"
      # raw = "meshcore/{IATA}/{PUBLIC_KEY}/raw"
      # neighbors = "meshcore/{IATA}/{PUBLIC_KEY}/neighbors"
      
      [capture]
      connection_type = "ble"       # ble, serial, tcp
      timeout = 30
      data_dir = "/var/lib/meshcore-packet-capture/data"
      max_connection_retries = 5    # 0 means infinite
      connection_retry_delay = 5
      health_check_interval = 30
      advert_interval_hours = 47     # 0 disables periodic adverts
      
      # Payload decoding (v2.1.0+): adds a nested "decoded" object with
      # plain-text / structured fields. GRP_TXT channel messages are decrypted
      # (sender, text), ADVERTs parsed (name, role, lat/lon), plus type/route labels.
      # decode_payloads = false
      # include_decoded = false           # global default; opt in per broker below
      # decode_hashtag_channels = ["bot", "weather"]
      # decode_channel_keys = "name=hexOrBase64,other=..."
      # decode_include_public = true
      
      # Neighbors publishing (v2.1.0+): periodic zero-hop neighbor table + each
      # neighbor's region scopes. Off by default; enabled per broker with
      # neighbors = true. Interval clamped to 12-336 hours.
      # neighbors_interval_hours = 24
      # neighbors_discover_window = 60
      # neighbors_command_timeout = 20
      # neighbors_scope_timeout = 0      # 0 = use device's own estimate
      # neighbors_scope_min_timeout = 8
      # neighbors_scope_gap = 2.0
      # neighbors_cycle_timeout = 600
      # neighbors_max = 32
      # neighbors_self_scopes = ""
      
      # Log rotation (v2.1.0+): only when --output is used
      # log_rotation = "off"             # off | size | time
      # log_max_bytes = "50MB"
      # log_rotation_when = "midnight"
      # log_backup_count = 5
      
      [serial]
      ports = ["/dev/ttyUSB0"]
      baud_rate = 115200
      timeout = 2
      
      [[broker]]
      name = "home"
      enabled = true
      server = "mqtt.example.com"
      port = 1883
      transport = "tcp"
      keepalive = 60
      qos = 0
      retain = false
      # include_decoded = true   # publish the "decoded" object to this broker
      # neighbors = false        # publish the neighbors snapshot to this broker
      
      [broker.auth]
      method = "password"            # password, token, or none
      username = "user"
      password = "secret"
      
      [broker.tls]
      enabled = false
      verify = true
      
      [broker.topics]
      packets = "custom/{IATA}/{PUBLIC_KEY}/packets"
      ```
      
      `[capture]` also accepts `tcp_host`, `tcp_port`, `ble_address`, `ble_device_name`, `ble_pin` (v2.1.0+, optional six-digit PIN for BLE connections; Linux may still require BlueZ pairing during first-time setup), `origin`, `private_key`, `private_key_file`, retry/backoff controls, stats controls, `drain_messages`, TCP keepalive controls, `upload_packet_types`, `exit_on_reconnect_fail`, and binary-interface controls. `config.toml.example` in the upstream repository is the authoritative complete list.
      
      ## Precedence and merging
      
      Runtime precedence is:
      
      1. Variables already present in the process environment.
      2. Explicit `--config PATH` files, in argument order, or the default `/etc/.../config.toml` plus sorted `config.d/*.toml`.
      3. `.env.local`, then `.env`.
      
      There is an important implementation detail: the application snapshots the real process environment before reading dotenv files. TOML may replace a value sourced only from `.env`, but never replaces a real process variable.
      
      TOML dictionaries deep-merge. Broker arrays merge by non-empty `name`; a same-named broker is deep-merged, and a new broker is appended. Disabled brokers are omitted when flattening to MQTT slots. Because the runtime discovers slots sequentially, keep enabled broker entries contiguous as `MQTT1`, `MQTT2`, and so on.
      
      ## Environment names
      
      Every flat runtime setting uses the `PACKETCAPTURE_` prefix:
      
      - Connection: `PACKETCAPTURE_CONNECTION_TYPE`, `PACKETCAPTURE_BLE_ADDRESS`, `PACKETCAPTURE_BLE_DEVICE_NAME`, `PACKETCAPTURE_BLE_PIN`, `PACKETCAPTURE_SERIAL_PORTS`, `PACKETCAPTURE_TCP_HOST`, `PACKETCAPTURE_TCP_PORT`.
      - Runtime: `PACKETCAPTURE_TIMEOUT`, `PACKETCAPTURE_MAX_CONNECTION_RETRIES`, `PACKETCAPTURE_CONNECTION_RETRY_DELAY`, `PACKETCAPTURE_HEALTH_CHECK_INTERVAL`, `PACKETCAPTURE_DRAIN_MESSAGES`, `PACKETCAPTURE_STATS_IN_STATUS_ENABLED`, `PACKETCAPTURE_STATS_REFRESH_INTERVAL`.
      - Identity/topics: `PACKETCAPTURE_IATA`, `PACKETCAPTURE_ORIGIN`, `PACKETCAPTURE_TOPIC_STATUS`, `PACKETCAPTURE_TOPIC_PACKETS`, `PACKETCAPTURE_TOPIC_DECODED`, `PACKETCAPTURE_TOPIC_DEBUG`, `PACKETCAPTURE_TOPIC_RAW`, `PACKETCAPTURE_TOPIC_NEIGHBORS`.
      - Decoding (v2.1.0+): `PACKETCAPTURE_DECODE_PAYLOADS`, `PACKETCAPTURE_INCLUDE_DECODED`, `PACKETCAPTURE_DECODE_HASHTAG_CHANNELS`, `PACKETCAPTURE_DECODE_CHANNEL_KEYS`, `PACKETCAPTURE_DECODE_INCLUDE_PUBLIC`.
      - Neighbors (v2.1.0+): `PACKETCAPTURE_NEIGHBORS_INTERVAL_HOURS`, `PACKETCAPTURE_NEIGHBORS_DISCOVER_WINDOW`, `PACKETCAPTURE_NEIGHBORS_COMMAND_TIMEOUT`, `PACKETCAPTURE_NEIGHBORS_SCOPE_TIMEOUT`, `PACKETCAPTURE_NEIGHBORS_SCOPE_MIN_TIMEOUT`, `PACKETCAPTURE_NEIGHBORS_SCOPE_GAP`, `PACKETCAPTURE_NEIGHBORS_CYCLE_TIMEOUT`, `PACKETCAPTURE_NEIGHBORS_MAX`, `PACKETCAPTURE_NEIGHBORS_SELF_SCOPES`.
      - Log rotation (v2.1.0+): `PACKETCAPTURE_LOG_ROTATION`, `PACKETCAPTURE_LOG_MAX_BYTES`, `PACKETCAPTURE_LOG_ROTATION_WHEN`, `PACKETCAPTURE_LOG_BACKUP_COUNT`.
      - Keys: `PACKETCAPTURE_PRIVATE_KEY` or `PACKETCAPTURE_PRIVATE_KEY_FILE`.
      
      Use the TOML names when writing configuration. Use the environment names when injecting Docker or service settings.
      
      ## MQTT broker slots
      
      For broker `n`, the environment form is `PACKETCAPTURE_MQTT<n>_*`. Common fields are `ENABLED`, `NAME`, `SERVER`, `PORT`, `TRANSPORT`, `USE_TLS`, `TLS_VERIFY`, `USERNAME`, `PASSWORD`, `USE_AUTH_TOKEN`, `TOKEN_AUDIENCE`, `TOKEN_TTL`, `TOKEN_OWNER`, `TOKEN_EMAIL`, `TOPIC_TOKEN`, `CLIENT_ID_PREFIX`, `QOS`, `RETAIN`, `KEEPALIVE`, plus v2.1.0+ `INCLUDE_DECODED` and `NEIGHBORS` per-broker feature flags.
      
      Supported topic placeholders:
      
      - `{IATA}`: uppercase IATA code
      - `{IATA_lower}`: lowercase IATA code
      - `{PUBLIC_KEY}`: connected radio public key
      - `{TOKEN}`: broker-specific or global topic token
      
      A broker-specific topic wins over the global topic. `RAW` has no implicit default and is skipped unless configured. Per-broker topic keys: `status`, `packets`, `decoded`, `debug`, `raw`, `neighbors`. If no topic is configured, an IATA-aware default is used when a non-`LOC` IATA exists; custom brokers without IATA use classic topics; LetsMesh brokers without IATA do not receive default topics.
      
      ## Authentication
      
      Password auth:
      
      ```toml
      [broker.auth]
      method = "password"
      username = "mqtt-user"
      password = "mqtt-password"
      ```
      
      Token auth:
      
      ```toml
      [broker.auth]
      method = "token"
      audience = "mqtt.example.com"
      token_ttl = 3600
      # owner = "64_HEX_CHAR_PUBLIC_KEY"
      # email = "owner@example.com"
      ```
      
      Per-broker `owner` and `email` (v2.1.0+) override the global `capture.owner_public_key` / `capture.owner_email` for that broker's token.
      
      Token generation prefers on-device signing through the connected MeshCore instance. If that fails, it can use a 64-byte MeshCore private key (128 hex characters) from configuration/environment/file, subject to device and firmware support. `AUTH_TOKEN_METHOD=python` forces Python signing; `AUTH_TOKEN_METHOD=meshcore-decoder` selects the external decoder path. Never log or paste the key.
      
      ## Packet filtering and output
      
      `PACKETCAPTURE_UPLOAD_PACKET_TYPES` accepts comma-separated numeric packet types. Known values are `0` REQ, `1` RESPONSE, `2` TXT_MSG, `3` ACK, `4` ADVERT, `5` GRP_TXT, `6` GRP_DATA, `7` ANON_REQ, `8` PATH, `9` TRACE, `10` MULTIPART, `11` CONTROL, `15` RAW_CUSTOM. Empty/unset means all packet types are uploaded; filtering affects MQTT upload, not local capture.
      
      `--output PATH` writes packet output. `--verbose` adds JSON to normal output. `--debug` enables detailed diagnostics. `--no-mqtt` disables publishing while leaving capture active. `--neighbors-now` runs one neighbors discovery + scopes cycle immediately (v2.1.0+); add `--neighbors-exit` to quit when the cycle finishes. With `decode_payloads` enabled, each record gains a nested `decoded` object (GRP_TXT sender/text, ADVERT name/role/lat/lon, type/route labels); direct TXT_MSG payloads remain opaque to a passive observer. Log rotation (`log_rotation`, `log_max_bytes`, `log_rotation_when`, `log_backup_count`) applies when `--output` names a file.
      
    • deployment-and-troubleshooting.md 7 KB
      # Deployment and troubleshooting
      
      Source basis: `agessaman/meshcore-packet-capture` commit `c011f4e` (v2.2.0), inspected 2026-08-09. These commands are operational guidance, not proof that a target host has the required hardware or credentials.
      
      ## Manual install
      
      For a CLI/manual run:
      
      ```bash
      pipx install meshcore-packet-capture
      meshcore-packet-capture --help
      meshcore-packet-capture --debug --no-mqtt
      ```
      
      The package requires Python 3.11+ and installs `meshcore==2.3.8` (pinned as of v2.2.0), `paho-mqtt`, BLE, serial, telemetry, and signing dependencies. A pipx install does not create a service or write `/etc` configuration.
      
      ## Managed installer
      
      The bootstrap installer is interactive and must run with root privileges:
      
      ```bash
      sudo bash -c "$(curl -fsSL https://raw.githubusercontent.com/agessaman/meshcore-packet-capture/main/install.sh)"
      ```
      
      Avoid `curl | sudo bash` for an interactive install. The bootstrap explicitly rejects stdin being consumed by the script. Download it to a temporary file if stdin is not a terminal.
      
      By default, the installer resolves the latest published GitHub release for the payload and falls back to `main` when no release exists. Pin explicitly when needed:
      
      ```bash
      sudo bash install.sh --tag v2.0.0
      sudo bash install.sh --branch main
      ```
      
      The installer creates `/opt/meshcore-packet-capture`, `/etc/meshcore-packet-capture/config.toml`, `/etc/meshcore-packet-capture/config.d/`, a virtual environment, and state under `/var/lib/meshcore-packet-capture`. It can select systemd, launchd, Docker, or manual mode. It also supports `python3 -m installer update` and `python3 -m installer migrate` for legacy `~/.meshcore-packet-capture` layouts.
      
      After installation, finish the user config and confirm the installer did not report missing MQTT broker or placeholder IATA. On Linux:
      
      ```bash
      sudo systemctl status meshcore-packet-capture
      sudo journalctl -u meshcore-packet-capture -f
      sudo systemctl restart meshcore-packet-capture
      ```
      
      ## User service (v2.1.0+)
      
      For a local checkout on Linux, install a per-user systemd service that runs from the checkout's `.venv`:
      
      ```bash
      ./install.sh --user-service               # from the checkout root
      ./install.sh --user-service --repo-dir /path/to/checkout
      ```
      
      Config files live in the repo itself: `.env`, `.env.local`, `config.toml`, and `config.d/*.toml` (loaded in sorted order). Manage it with user-level systemd:
      
      ```bash
      systemctl --user status meshcore-packet-capture
      systemctl --user restart meshcore-packet-capture
      journalctl --user -u meshcore-packet-capture -f
      ```
      
      Remove with `./uninstall.sh --user-service` from the same checkout; add `--remove-venv` to also delete the local `.venv`. The user-service path is distinct from the root managed install: no `/opt`, `/etc`, or root service is created.
      
      On macOS, BLE uses a per-user LaunchAgent because Bluetooth permissions are granted to the login user. Serial/TCP can use the system LaunchDaemon:
      
      ```bash
      launchctl list | grep meshcore-packet-capture
       tail -f /var/log/meshcore-packet-capture.log
      ```
      
      ## Docker
      
      The published Compose file uses `ghcr.io/agessaman/meshcore-packet-capture:latest`, persists `./data` at `/app/data`, and defaults to serial. Start and inspect it with:
      
      ```bash
      docker compose config
      docker compose up -d
      docker compose ps
      docker compose logs -f meshcore-capture
      ```
      
      For serial, map a stable host path from `/dev/serial/by-id/` to `/dev/ttyUSB0`. Do not default to `privileged: true` for serial. For BLE, set `privileged: true`, use `PACKETCAPTURE_CONNECTION_TYPE=ble`, optionally set `PACKETCAPTURE_BLE_ADDRESS` or `PACKETCAPTURE_BLE_DEVICE_NAME`, and try `network_mode: host` if discovery fails. Linux is the reliable container target; macOS and Windows container hardware access is limited.
      
      Mount TOML at `/etc/meshcore-packet-capture` for non-trivial configuration. Environment variables still win over the mounted TOML. `.env.local` is legacy and lower precedence than TOML.
      
      ## NixOS
      
      The repository includes a NixOS module under `services.meshcore-packet-capture`. Configure `connectionType`, the radio transport, `mqtt1`/`mqtt2`/additional brokers, IATA, and optional key settings, then run:
      
      ```bash
      sudo nixos-rebuild switch
      journalctl -u meshcore-packet-capture -f
      ```
      
      The module's documentation shows LetsMesh WebSocket/TLS examples and custom password-authenticated brokers. Verify the module source against the current checkout before copying options because Nix attribute names can drift independently of the Python environment-variable names.
      
      ## Evidence-first troubleshooting
      
      ### No device connection
      
      1. Run `meshcore-packet-capture --debug --no-mqtt` outside the service/container.
      2. Confirm `PACKETCAPTURE_CONNECTION_TYPE` is one of `ble`, `serial`, or `tcp`.
      3. For BLE, check Bluetooth permission, adapter visibility, address/name, `ble_pin` match, and container privileges. On Linux, BlueZ may require a first-time pairing even when a PIN is configured.
      4. For serial, verify the host device exists and the service user/container sees the mapped path.
      5. For TCP, verify host/port reachability and inspect SDK reconnect messages.
      
      Do not jump to MQTT diagnosis until local device connection succeeds.
      
      ### Packets connect but no MQTT data
      
      1. Confirm the broker's `ENABLED`, `SERVER`, and `PORT` values.
      2. Check that broker numbering has no gap.
      3. Confirm transport/TLS pairing: WebSockets commonly uses port 443 with TLS; raw MQTT commonly uses TCP.
      4. For token auth, verify audience, token TTL, device signing capability, and private-key fallback availability.
      5. Inspect resolved topic templates and IATA without exposing credentials.
      6. If packet filtering is configured, confirm the numeric packet type is included.
      7. If expecting a `decoded` object, verify `decode_payloads` is enabled and the target broker sets `include_decoded = true`. If expecting a neighbors snapshot, verify the broker sets `neighbors = true` and an IATA (or explicit `[broker.topics] neighbors`) exists; the cycle only runs when at least one enabled broker opts in.
      
      The process tolerates transient disconnects, retries commands, and uses MQTT grace periods. A single reconnect warning is not proof of a permanent failure; look for the later connected/failed state.
      
      ### Service starts then exits
      
      Run the exact runtime manually with `--debug --no-mqtt` to separate application failure from service supervision. Check Python version, installed package, permissions on the serial/BLE device, writable `data_dir`, and TOML parse errors. A service may intentionally exit after configured failure thresholds so systemd/launchd can restart it.
      
      ### Safe update or removal
      
      Before updating or uninstalling, identify the active service/container and back up `config.d/99-user.toml`. Stop and verify the old process before removing anything. The upstream uninstaller is interactive, backs up user configuration when selected, and can remove services, container/image, install files, logs, `/etc/meshcore-packet-capture`, and `/var/lib/meshcore-packet-capture`. Do not reproduce its `rm -rf` actions casually during troubleshooting.
      
    • source-index.md 4.1 KB
      # Source index
      
      This skill was built from the public repository [agessaman/meshcore-packet-capture](https://github.com/agessaman/meshcore-packet-capture), inspected at commit `c011f4e` on 2026-08-09. The checkout reported release `v2.2.0`, published 2026-07-30. Upstream is the source of truth when this skill conflicts with a newer release.
      
      ## Coverage inventory
      
      - [x] `README.md`: scope, install modes, configuration precedence, CLI, Docker, output, topics, troubleshooting
      - [x] `pyproject.toml`, `requirements.txt`: Python/runtime dependency floor (meshcore pinned `==2.3.8` as of v2.2.0) and CLI entry point
      - [x] `src/meshcore_packet_capture/__main__.py`: installed command dispatch
      - [x] `src/meshcore_packet_capture/packet_capture.py`: transports, retries, health checks, MQTT, topics, stats, filters, output, shutdown, neighbors CLI flags (`--neighbors-now`, `--neighbors-exit`)
      - [x] `src/meshcore_packet_capture/config_loader.py`: TOML loading, deep merge, named broker merge, environment flattening, decoding/neighbors/log-rotation keys
      - [x] `src/meshcore_packet_capture/auth_token.py`: device/Python/decoder signing and key format
      - [x] `src/meshcore_packet_capture/enums.py`: packet and route type vocabulary
      - [x] `src/meshcore_packet_capture/payload_decode.py`: GRP_TXT decryption, ADVERT parsing, structured decode fields (v2.1.0+)
      - [x] `src/meshcore_packet_capture/neighbors.py`: zero-hop neighbor discovery + region-scope collection for the neighbors topic (v2.1.0+)
      - [x] `config.toml.example`, `.env`: configuration surface and legacy aliases (decode/neighbors/log-rotation blocks, `ble_pin`, per-broker `include_decoded`/`neighbors`/`owner`/`email`)
      - [x] `presets/*.toml`: broker preset shape and sequential broker behavior
      - [x] `DOCKER.md`, `docker-compose.yml`, `Dockerfile`: container deployment and hardware access
      - [x] `NIXOS.md`, `nix/`: NixOS package/module deployment
      - [x] `install.sh`, `installer/`: bootstrap, release resolution, interactive install, update, migrate, `--user-service` flow
      - [x] `packaging/systemd/`, `packaging/launchd/`: service supervision and platform permissions
      - [x] `uninstall.sh`: backup, `--user-service` removal, and destructive cleanup boundaries
      - [x] `devtools/`: BLE/network diagnostic helpers, treated as optional development tools
      - [x] `tests/`: configuration, installer, CLI, packet parsing, JWT, presets, and lifecycle behavior
      - [x] `.github/workflows/`: Docker, Nix, pytest, and release automation
      
      ## Reconciliation notes
      
      - The README says the recommended meshcore dependency is `>=2.2.31`; `pyproject.toml` at v2.2.0 pins `==2.3.8` and the contact-injection shim was removed. The skill follows the pin.
      - TOML is described as primary, but the actual precedence depends on whether a value came from the real process environment or a dotenv file. The skill documents the implementation's snapshot behavior.
      - The README says the installer installs the latest release by default; `installer/__main__.py` and `install.sh` confirm explicit `--tag`/`--branch` overrides, plus the v2.1.0+ `--user-service` local-checkout flow.
      - Older README examples mention `docker-compose`; current `DOCKER.md` uses `docker compose`. Prefer the Compose v2 form.
      - `NIXOS.md` documents a bounded broker example; `config_loader.py` and tests show the Python runtime supports more than four enabled brokers. The skill follows the runtime for the general rule and treats Nix option count as module-specific.
      - Upstream contains compatibility/development helpers and a large implementation file. This skill distills operation and diagnosis rather than copying source code.
      
      ## Refresh procedure
      
      1. Fetch the current default branch and latest release with `gh`.
      2. Re-read `README.md`, `pyproject.toml`, `config.toml.example`, `DOCKER.md`, `NIXOS.md`, service templates, installer entry points, config loader, CLI parser, `payload_decode.py`, and `neighbors.py`.
      3. Run the upstream tests if dependencies and hardware-independent fixtures are available.
      4. Update this index's commit/date and reconcile every changed command, default, version, and environment variable before changing executable guidance.
      
  • README.md 1.9 KB
    # MeshCore Packet Capture — Companion Radio Operations
    
    Operate MeshCore Companion radio packet capture without reconstructing the upstream repository each time. The skill covers BLE, serial, and TCP connections, MQTT output, authentication, Docker, system services, NixOS, and evidence-first troubleshooting.
    
    ## Why Install This Skill
    
    After installing it, your agent can configure and run the right transport, distinguish Companion radios from repeaters and RoomServers, publish packets to one or more MQTT brokers, and troubleshoot failures at the radio, broker, container, and service layers.
    
    It also preserves the details that are easy to get wrong: TOML/environment precedence, sequential MQTT broker slots, IATA-based topics, device-signing fallback, macOS BLE permissions, and Docker hardware access.
    
    ## What You Get
    
    | Path | Purpose |
    |---|---|
    | `SKILL.md` | Operational workflow, safety boundaries, routing, and completion checks |
    | `references/configuration.md` | TOML/env schema, broker auth, topic templates, and packet filters |
    | `references/deployment-and-troubleshooting.md` | Manual, managed, Docker, NixOS, and diagnostic procedures |
    | `references/source-index.md` | Upstream commit, coverage inventory, and refresh notes |
    
    ## Quick Start
    
    ```bash
    pipx install meshcore-packet-capture
    meshcore-packet-capture --debug --no-mqtt
    # Configure IATA + MQTT, then run:
    meshcore-packet-capture --debug
    ```
    
    ## Triggers
    
    Use this skill for `meshcore-packet-capture`, MeshCore Companion radios, BLE/serial/TCP packet capture, MQTT packet publishing, LetsMesh, `PACKETCAPTURE_*`, `config.toml`, Docker Compose deployment, systemd, launchd, NixOS, or connection troubleshooting.
    
    ## Requirements
    
    Python 3.11+, the upstream runtime dependencies, a MeshCore Companion radio, and access to the selected transport. MQTT publishing additionally requires a reachable broker and either password or token authentication. Docker hardware access is most reliable on Linux.
    
  • SKILL.md 10.6 KB
    ---
    name: meshcore-packet-capture
    description: >-
      Capture MeshCore Companion packets via BLE, serial, or TCP. Do not use this skill for
      unrelated requests; route to the nearest named specialist.
    license: MIT
    compatibility: Requires Python 3.11+, a MeshCore Companion radio, and the project's runtime dependencies.
    ---
    
    # MeshCore Packet Capture
    
    Use this skill when operating, configuring, deploying, or troubleshooting [meshcore-packet-capture](https://github.com/agessaman/meshcore-packet-capture), especially requests involving Companion radios, BLE/serial/TCP capture, MQTT publishing, LetsMesh, TOML configuration, Docker, systemd, launchd, or NixOS.
    
    ## Scope boundary
    
    This project captures from MeshCore **Companion radios only**. Do not route repeater or RoomServer capture here; use `meshcoretomqtt` instead.
    
    ## Operating workflow
    
    1. Identify the transport before changing configuration:
       - `ble`: direct address, device name, or general scan.
       - `serial`: one or more serial ports; the runtime uses the first configured port.
       - `tcp`: host and port, with SDK auto-reconnect enabled by default.
    2. Prefer the installed CLI:
       ```bash
       meshcore-packet-capture --help
       meshcore-packet-capture --debug
       ```
       For a checkout, use `python3 -m meshcore_packet_capture` or `python3 packet_capture.py`.
       **Verified CLI boundary (v2.2.0):** the parser exposes `--output`, `--verbose`, `--debug`, `--no-mqtt`, repeatable `--config`, and the neighbors on-demand pair `--neighbors-now` / `--neighbors-exit` (run one zero-hop neighbor discovery + scopes cycle immediately; with `--neighbors-exit`, quit when the cycle finishes). It does not expose `--show-config` or a dry-run flag. Run the exact installed binary with `--help` before documenting or using any other flag; do not invent a configuration-preview command.
    3. Configure one IATA code and at least one MQTT broker before expecting network uploads. `LOC` is a placeholder, not a useful deployment identity.
    4. Start with `--no-mqtt` when isolating radio connectivity. Add MQTT only after the device captures packets locally.
    5. Verify the actual boundary after every change: device connection logs, packet output, broker connection, and service/container status. Do not treat a successful install as proof of a working capture.
    
    **Mutation gate:** when the request is read-only or plan-only, do not present stop/start/edit/delete/restart commands as actions to perform. Describe future mutations separately, label every example as unexecuted, and require target, scope, and rollback confirmation before the first mutation.
    
    ## Configuration rules
    
    Configuration precedence is highest to lowest:
    
    1. Real `PACKETCAPTURE_*` process environment.
    2. TOML: `/etc/meshcore-packet-capture/config.toml`, then sorted `config.d/*.toml`.
    3. Legacy `.env.local`, then `.env` in the working/package directory.
    
    Use `--config PATH` more than once to load only the named TOML files in order. Later files override earlier files. TOML broker blocks merge by `name`; enabled brokers are flattened into sequential `PACKETCAPTURE_MQTT<n>_*` slots. Keep user overrides in `config.d/99-user.toml`.
    
    Minimal TOML shape:
    
    ```toml
    [general]
    iata = "SEA"
    
    [capture]
    connection_type = "serial"
    # ble_pin = "123456"   # optional six-digit PIN for BLE connections
    
    [serial]
    ports = ["/dev/ttyUSB0"]
    
    [[broker]]
    name = "home"
    enabled = true
    server = "mqtt.example.com"
    port = 1883
    transport = "tcp"
    # include_decoded = true   # publish the nested "decoded" object to this broker
    # neighbors = true         # publish the neighbors snapshot to this broker
    
    [broker.auth]
    method = "password"
    username = "user"
    password = "secret"
    ```
    
    Read `references/configuration.md` for the complete option map, broker authentication, topic templates, packet filters, and precedence edge cases.
    
    ### Optional feature blocks (v2.1.0+)
    
    - **Payload decoding** (`decode_payloads`, `include_decoded`, `decode_hashtag_channels`, `decode_channel_keys`, `decode_include_public`): adds a nested `decoded` object with plain-text / structured fields. GRP_TXT channel messages are decrypted (sender, text), ADVERTs are parsed (name, role, lat/lon), and human-readable type/route labels plus path are included. Raw fields are unchanged. Direct messages (TXT_MSG) cannot be decrypted by a passive observer. Off by default; opt in per broker with `include_decoded = true` in its `[[broker]]` block.
    - **Neighbors publishing** (`neighbors` per broker plus `neighbors_interval_hours`, `neighbors_discover_window`, `neighbors_command_timeout`, `neighbors_scope_timeout`, `neighbors_scope_min_timeout`, `neighbors_scope_gap`, `neighbors_cycle_timeout`, `neighbors_max`, `neighbors_self_scopes`): publishes a periodic zero-hop neighbor table plus each neighbor's region scopes to the `neighbors` topic. Off by default; the cycle only runs when at least one enabled broker sets `neighbors = true`. Requires an IATA (or an explicit `[broker.topics] neighbors`). The interval is clamped to 12–336 hours to match the observer firmware. `--neighbors-now` / `--neighbors-exit` trigger a cycle on demand.
    - **Log rotation** (`log_rotation` = off|size|time, `log_max_bytes`, `log_rotation_when`, `log_backup_count`): only applies when an output file is given with `--output`.
    
    ## MQTT and authentication
    
    - Brokers are sequential and have no fixed upper limit. A missing `MQTT<n>_ENABLED` terminates discovery, so do not leave a numbering gap.
    - Supported transports are `tcp` and `websockets`; use TLS explicitly for secure transport.
    - Password auth uses `username` and `password`.
    - Token auth uses Ed25519 JWT-style tokens. On-device signing is preferred when a connected radio supports it; Python signing is the fallback when a valid 64-byte private key is available.
    - Never put private keys in a public skill, command transcript, committed `.env`, or shared Docker Compose file. Prefer a protected key file or secret injection.
    - Topic placeholders are `{IATA}`, `{IATA_lower}`, `{PUBLIC_KEY}`, and `{TOKEN}`. Per-broker topics override global topics. `RAW` is not published unless explicitly configured. Topics per broker: `status`, `packets`, `decoded`, `debug`, `raw`, `neighbors`. The `decoded` topic carries the nested decoded object when `include_decoded` is enabled; the `neighbors` topic carries the periodic neighbor snapshot when `neighbors` is enabled.
    - Per-broker `owner` and `email` override the global `capture.owner_public_key` / `capture.owner_email` for token-auth brokers.
    - LetsMesh Analyzer brokers require a configured IATA. Do not silently accept the default `LOC` for a LetsMesh deployment.
    
    ## Deployment defaults
    
    Legacy hybrid installs are possible: a systemd or launchd unit may wrap a checkout under a user's home directory and load `.env.local`, without `/opt` or `/etc` TOML files. Inspect the running command, working directory, service unit, and environment/config source before choosing the modern managed-install path.
    
    - **Manual/PyPI:** `pipx install meshcore-packet-capture` gives the CLI and does not install a background service.
    - **Managed Linux/macOS:** the root bootstrap installer creates `/opt/meshcore-packet-capture`, `/etc/meshcore-packet-capture`, a virtual environment, and a system service. It installs the latest published release by default; use `--tag` or `--branch` to pin.
    - **User service (v2.1.0+):** for a local checkout on Linux, `./install.sh --user-service` creates a per-user systemd service that runs from the checkout's `.venv` (pass `--repo-dir PATH` if the checkout is not the script's directory). Config files live in the repo itself (`.env`, `.env.local`, `config.toml`, `config.d/`). Remove with `./uninstall.sh --user-service` from the same checkout; add `--remove-venv` to also delete the local `.venv`. Manage with `systemctl --user status|restart meshcore-packet-capture` and `journalctl --user -u meshcore-packet-capture -f`.
    - **macOS BLE:** use the per-user LaunchAgent because Bluetooth permission belongs to the login user. Serial/TCP can use a LaunchDaemon.
    - **Docker:** use the published image or `docker compose up -d`; serial needs a device mapping, while BLE generally needs `privileged: true` and may need host networking. Linux is the most reliable container host for hardware access.
    - **NixOS:** use `services.meshcore-packet-capture` and rebuild with `sudo nixos-rebuild switch`.
    
    Read `references/deployment-and-troubleshooting.md` for service commands, Docker hardware access, migration/update behavior, and bounded diagnostics.
    
    ## Output and verification
    
    Normal mode prints minimal packet information. `--verbose` adds JSON packet data; `--debug` adds connection, retry, packet parsing, and MQTT diagnostics. `--output PATH` writes packet data to a file. Captured records include device identity, timestamp, packet type, route, payload length, raw hex, SNR, RSSI, and a hash. When `decode_payloads` is enabled, records also carry a nested `decoded` object with human-readable fields (channel message sender/text, ADVERT name/role/coordinates, type/route labels).
    
    When troubleshooting, collect evidence in this order:
    
    1. `meshcore-packet-capture --debug --no-mqtt` and confirm a real BLE, serial, or TCP connection.
    2. Confirm packet records appear in console or the `--output` file.
    3. Enable one broker and inspect broker connection/reconnect messages.
    4. Check the resolved environment/config and topic names, without printing secrets.
    5. For a managed service, inspect the service's own logs and status. For Docker, use `docker compose config`, `docker compose ps`, and `docker compose logs`.
    
    Do not delete the install directory, state directory, or configuration during diagnosis. The uninstaller is interactive and backs up user configuration, but it still performs destructive removal only after confirmation.
    
    ## When not to use
    
    Do not use this skill for MeshCore repeater or RoomServer packet capture, generic MQTT administration, or implementing changes inside the upstream repository. For code changes, use the repository's contribution workflow and verify the project tests separately.
    
    ## References
    
    | File | Load when |
    |---|---|
    | `references/configuration.md` | Writing TOML/env configuration, MQTT topics, authentication, or packet filters |
    | `references/deployment-and-troubleshooting.md` | Installing, upgrading, running Docker/systemd/launchd/NixOS, or debugging a failed service |
    | `references/source-index.md` | Checking source coverage, version markers, or revalidating this skill against upstream |
    
    ## Completion condition
    
    Stop when the selected transport connects, a packet is observed locally, the intended MQTT topic receives data if MQTT is in scope, and the relevant service/container reports healthy. If hardware, credentials, or broker access is unavailable, report that exact unverified boundary instead of claiming success.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related