Issue: Parser incorrectly matched JSON lines (adverts, GRP_TXT messages)
as pending contacts because they contained colons. This caused false
positives like {"raw_hex": "..."} being parsed as contact name.
Root cause: meshcli outputs JSON for received messages (due to
json_log_rx=on setting) during pending_contacts command execution.
Original parser only checked for ':' presence, matching any JSON.
Solution: Added robust filtering before parsing:
1. Skip empty lines
2. Skip JSON lines (starting with { or [)
3. Skip meshcli prompt lines (ending with |*)
4. Validate public_key contains only hex characters (0-9, a-f, A-F)
This ensures only valid "ContactName: <hex_pubkey>" format is parsed.
Example of filtered content:
- SKIP: {"raw_hex": "25bc...", "payload_typename": "GRP_TXT"}
- SKIP: MarWoj|*
- PARSE: Skyllancer: f9ef123abc456...
Testing: No false positives with JSON lines in output.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Enabled manual contact approval mode in meshcli session initialization.
This requires explicit approval for new contacts attempting to connect,
providing enhanced security and network access control.
Changes:
- Added 'set manual_add_contacts on' to _init_session_settings()
- Updates session init log message to include manual_add_contacts status
- Created comprehensive technical documentation (technotes/pending-contacts-api.md)
Benefits:
- DoS prevention - blocks flooding with fake contact requests
- Network privacy - control who can see your node
- Trust model - explicit approval for all new contacts
- Spam filtering - reject unwanted connection attempts
Technical notes document includes:
- Problem statement and solution overview
- API endpoint specifications and examples
- Testing procedures and expected workflows
- Future UI integration plans
- Security considerations and recommendations
- Meshcli command reference
When manual approval is enabled, new contacts appear in pending list
(accessible via GET /pending_contacts) and must be approved via
POST /add_pending before they can communicate with the node.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added two new HTTP endpoints to meshcore-bridge for managing pending contacts
(contacts awaiting manual approval when manual_add_contacts mode is enabled):
New endpoints:
- GET /pending_contacts - List all pending contacts awaiting approval
- Parses meshcli output format: "Name: <hex_public_key>"
- Returns JSON array with {name, public_key} objects
- Includes raw_stdout for debugging
- POST /add_pending - Approve and add a pending contact
- Accepts JSON body: {"selector": "<name_or_pubkey>"}
- Validates selector is non-empty string
- Executes meshcli add_pending command via persistent session
Additional changes:
- Added curl to mc-webui Dockerfile for testing endpoints
- Updated README with Testing Bridge API section
- Included example curl commands and expected responses
Implementation notes:
- Uses existing MeshCLISession.execute_command() - no new processes
- Same persistent session and command queue architecture
- Consistent error handling with existing /cli endpoint
Enables future UI for manual contact approval workflow.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Critical fixes based on user feedback:
1. **Remove auto-recv polling (30s interval)**
- Polling with 'recv' doesn't fetch NEW messages, only reads from .msgs
- Wasteful - creates unnecessary command traffic
2. **Add msg_subscribe for real-time message reception**
- meshcli's msg_subscribe enables automatic message events
- Messages arrive via EventType.CHANNEL_MSG_RECV events
- No polling needed - truly asynchronous
3. **Make TZ configurable via .env instead of hardcoded**
- Changed docker-compose.yml: TZ=${TZ:-UTC}
- Added TZ=Europe/Warsaw to .env.example
- Users can now set their own timezone
4. **Remove unused shlex import**
- Not needed after switching to manual double-quote wrapping
Technical details:
- msg_subscribe sends subscription command to meshcli at init
- meshcli then emits events when messages arrive
- Events trigger TTY errors (harmless - meshcli tries to print_above)
- Messages are still saved to .msgs file by meshcli core
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
CRITICAL FIX: Messages stopped arriving after switching to persistent session.
Root cause: meshcli in interactive mode (Popen stdin/stdout) doesn't receive
messages automatically - requires explicit 'recv' command.
Changes:
- Add auto_recv_thread that calls 'recv' every 30 seconds in background
- This ensures continuous message synchronization without breaking /cli
- Messages are saved to .msgs file by meshcli as usual
- Add TZ=Europe/Warsaw to both containers for correct timestamps in logs
Technical details:
- auto-recv runs in separate thread via command queue (no blocking)
- First recv after 5s delay (let session initialize)
- Uses execute_command() internally, so respects command serialization
- Thread stops gracefully on shutdown_flag
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Users reported single quotes appearing in sent messages.
shlex.quote() uses single quotes which meshcli treats literally.
Changes:
- Replace shlex.quote() with custom double-quote wrapping
- Only quote args containing spaces or special chars
- Escape internal double quotes with backslash
- Example: ['public', 'To jest test'] → 'public "To jest test"'
meshcli should strip double quotes but preserve content.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Users reported messages with spaces were truncated to first word.
Root cause: ' '.join(args) didn't quote arguments with spaces.
Changes:
- Import shlex module
- Use shlex.quote() for each argument in execute_command()
- Example: ['public', 'To jest test'] → "public 'To jest test'"
This ensures meshcli receives multi-word messages correctly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
meshcli doesn't support 'echo' command, causing "Unknown command" errors.
Changed strategy from end-markers to timeout-based detection:
- Remove echo "___END_{cmd_id}___" markers
- Add _monitor_response_timeout() thread per command
- Track last_line_time timestamp for each response
- Complete command when no new lines received for 300ms
- Update _append_to_current_response() to update timestamps
This approach is more robust and doesn't depend on meshcli's command set.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Major refactor of meshcore-bridge to maintain a single long-lived meshcli
process instead of spawning new processes per request.
Changes:
- Add MeshCLISession class managing persistent subprocess.Popen session
- Implement thread-safe command queue with event-based synchronization
- Add stdout/stderr reader threads with JSON advert detection
- Log adverts automatically to {device_name}.adverts.jsonl with timestamp
- Add end-of-response markers (echo "___END_{cmd_id}___") for multiplexing
- Implement watchdog thread for auto-restart on meshcli crash
- Update /cli endpoint to delegate commands through persistent session
- Add MC_CONFIG_DIR and MC_DEVICE_NAME env vars to docker-compose.yml
Architecture benefits:
- No more USB port conflicts between concurrent requests
- Continuous advert logging without breaking /cli compatibility
- Better error recovery with automatic session restart
- Reduced overhead from process spawning
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fix critical bug in private message logging that prevented proper DM tracking.
Previous version (1.3.11) logged sent private messages with incomplete data:
- Missing recipient information
- Only included sender name in 'name' field
- Format: {"type": "SENT_MSG", "name": "MarWoj", "text": "...", ...}
Updated version (1.3.12) includes both sender and recipient:
- Added 'recipient' field with recipient's device name
- Added 'sender' field with sender's name
- Format: {"type": "SENT_MSG", "recipient": "SP7UNR_tdeck", "sender": "MarWoj", "name": "SP7UNR_tdeck", ...}
This fix enables proper message tracking in the DM module, as it now correctly
identifies both parties in private message exchanges.
Fix requested from meshcore-cli maintainers and implemented in v1.3.12.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Root cause: Multiple concurrent meshcli calls were fighting for USB access,
causing "Protocol error" and 504 Gateway Timeouts.
Changes to meshcore-bridge:
- Add threading.Lock to serialize meshcli subprocess calls
- Prevent concurrent USB access that causes OSError [Errno 71]
- Reduce DEFAULT_TIMEOUT from 30s to 10s
- Add detailed logging for lock acquisition and release
Changes to main API:
- Implement 30s cache for get_channels() to reduce USB calls
- Cache invalidation after channel create/join/delete operations
- Use cached channels in /api/channels and /api/messages/updates
- Reduce HTTP timeout from 30s to 12s (10s bridge + 2s buffer)
Impact:
- Eliminates race conditions when page loads (multiple API calls)
- Prevents USB port conflicts and protocol errors
- Faster response times due to caching
- No need for manual USB resets after container restarts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
BREAKING CHANGE: Switched from single-container to multi-container setup
This commit introduces a meshcore-bridge service that isolates USB device
access from the main application, resolving persistent USB timeout and
deadlock issues in Docker + VM environments.
Changes:
- Add meshcore-bridge/ - Lightweight HTTP API wrapper for meshcli
- Flask server exposes /cli endpoint (port 5001, internal only)
- Exclusive USB device access via --device flag
- Health check endpoint at /health
- Refactor app/meshcore/cli.py
- Replace subprocess calls with HTTP requests to bridge
- Add requests library dependency
- Better error handling for bridge communication
- Update docker-compose.yml
- Define meshcore-bridge and mc-webui services
- Create meshcore-net Docker network
- Add depends_on with health check condition
- Bridge gets USB device, main app uses HTTP only
- Modify Dockerfile
- Remove meshcore-cli installation from main app
- Lighter image without gcc dependencies
- Update config.py
- Add MC_BRIDGE_URL environment variable
- Remove meshcli_command property (no longer needed)
- Update documentation (README.md, .claude/instructions.md)
- Document 2-container architecture
- Add troubleshooting section for bridge
- Update prerequisites (no host meshcore-cli needed)
- Add architecture diagram in project structure
Benefits:
✅ Solves USB device locking after container restarts
✅ Restartable main app without USB reset
✅ Better separation of concerns
✅ Easier debugging (isolated meshcli logs)
✅ No manual USB recovery scripts needed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>