Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
1ac6874
update flake
fdlamotte Feb 28, 2026
e9a518f
add repeater commands reference documenting serial vs client mode
Mar 1, 2026
db59c48
Merge pull request #47 from stachuman/add-repeater-commands-doc
fdlamotte Mar 3, 2026
ed1c1d5
let some extraction work to the lib
fdlamotte Mar 5, 2026
7caca2d
offload channel messages decoding to meshcore_py
fdlamotte Mar 5, 2026
d01a9f6
change dependency to meshcore v2.2.19
fdlamotte Mar 5, 2026
512cc08
update meshcore_py to 2.2.20
fdlamotte Mar 5, 2026
5f1ab74
fix error in advert_echoes
fdlamotte Mar 6, 2026
f7a1286
update to meshcore-2.2.22
fdlamotte Mar 6, 2026
3e37c15
v1.4.8
fdlamotte Mar 6, 2026
68209df
some logic to specify path_hash_len when setting path
fdlamotte Mar 6, 2026
ba988a7
change path behaviour and document path and trace
fdlamotte Mar 6, 2026
f50d273
always show 1 byte path on prompt (use path to know the real path)
fdlamotte Mar 6, 2026
957da74
v1.4.9
fdlamotte Mar 6, 2026
e865027
update to meshcore 2.2.24
fdlamotte Mar 6, 2026
0661d52
implement advert_path
fdlamotte Mar 7, 2026
5636505
issue with errors in advert_path
fdlamotte Mar 8, 2026
9e6efeb
manage emojis in names in lc
fdlamotte Mar 8, 2026
3a1a2a2
update rn and ra
fdlamotte Mar 8, 2026
6e2c9bf
v1.4.12
fdlamotte Mar 8, 2026
944271d
Fix typos and formatting in README.md
jburgess777 Mar 9, 2026
73db417
Fix typo in README about init files
jburgess777 Mar 9, 2026
09e3339
Merge pull request #52 from jburgess777/patch-1
fdlamotte Mar 9, 2026
a3f6179
v1.5.0
fdlamotte Mar 10, 2026
57aa9e0
fix decrypt_channel_logs call
fdlamotte Mar 11, 2026
86bcc90
update to meshcore_py v2.3.2
fdlamotte Mar 23, 2026
52cdaa2
display channel_echoes even if message is not decrypted if channel is…
fdlamotte Mar 23, 2026
3952e4b
better handling of emojis display in channel_echoes
fdlamotte Mar 23, 2026
3b41f4e
issue with nested quotes
fdlamotte Mar 24, 2026
efcfca0
contact_lastmod can now be chained
fdlamotte Mar 26, 2026
4f28419
v1.5.4
fdlamotte Mar 26, 2026
fd7a0e1
fix set_channel
fdlamotte Mar 30, 2026
2d620f2
new add_contact command (pubkey, type, name)
fdlamotte Apr 3, 2026
2bb2dff
v1.5.6
fdlamotte Apr 9, 2026
efe06fa
Rename variable 'filename' to 'file_name'
flo1o1 Apr 13, 2026
37b8273
Merge pull request #58 from flo1o1/patch-1
fdlamotte Apr 14, 2026
a2c50a0
implementing get/set default_scope
fdlamotte Apr 25, 2026
e422835
update flake.nix
fdlamotte Apr 25, 2026
34aa631
package builds
zetneteork May 5, 2026
b37060a
package builds fix
zetneteork May 5, 2026
cae4fa8
fix workflow actions/upload-artifact@v4
zetneteork May 5, 2026
287deb9
github/workflow/build fix
zetneteork May 5, 2026
1e42cee
github/workflow/build fix
zetneteork May 5, 2026
08db87b
meshcore-cli.spec
zetneteork May 5, 2026
a766998
debian
zetneteork May 5, 2026
1d8077f
fix workflow
zetneteork May 5, 2026
2f888f7
fix workflow
zetneteork May 5, 2026
6a633be
fix workflow
zetneteork May 5, 2026
585b714
fix workflow
zetneteork May 5, 2026
2d25496
fix workflow
zetneteork May 5, 2026
32fc289
fix workflow split packages
zetneteork May 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.git
.gitignore
.github
.gitattributes
*.pyc
__pycache__
*.egg-info
dist/
build/
.pytest_cache
.tox
.coverage
.venv
venv/
env/
.env
*.md
!README.md
LICENSE
!LICENSE
debian/
docs/
*.spec
.dockerignore
Dockerfile
BUILDING.md
160 changes: 160 additions & 0 deletions .github/PACKAGING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# Packaging & Distribution for meshcore-cli

This project includes complete build configurations for multiple distribution formats and automated GitHub Actions workflows for building and publishing packages.

## 📦 Distribution Formats

### 1. Debian Package (.deb)
- **File**: [`debian/`](debian/)
- **Status**: ✅ Ready
- **Platform**: Debian, Ubuntu, Linux Mint
- **Installation**: download all Debian `.deb` assets from the release, then run `sudo apt install ./*.deb`

### 2. Fedora/RHEL Package (.rpm)
- **File**: [`meshcore-cli.spec`](meshcore-cli.spec)
- **Status**: ✅ Ready
- **Platform**: Fedora, RHEL, CentOS
- **Installation**: download all RPM assets from the release, then run `sudo dnf install ./*.rpm`

### 3. Docker Container
- **File**: [`Dockerfile`](Dockerfile)
- **Status**: ✅ Ready
- **Registry**: GitHub Container Registry (GHCR)
- **Installation**: `docker pull ghcr.io/fdlamotte/meshcore-cli`

### 4. Man Page
- **File**: [`docs/meshcli.1`](docs/meshcli.1)
- **Status**: ✅ Ready
- **Access**: `man meshcli` (after installation)

## 🚀 GitHub Actions Workflows

All workflows are configured to trigger automatically on git tags.

### Build Debian Package
- **Workflow**: [`.github/workflows/build-deb.yml`](.github/workflows/build-deb.yml)
- **Triggers**: Tags matching `v*`, manual dispatch
- **Output**: Uploads `meshcore-cli` plus its Python dependency `.deb` files to GitHub Releases
- **Platform**: Debian Bookworm container

### Build RPM Package
- **Workflow**: [`.github/workflows/build-rpm.yml`](.github/workflows/build-rpm.yml)
- **Triggers**: Tags matching `v*`, manual dispatch
- **Output**: Uploads `meshcore-cli` plus its Python dependency `.rpm` files to GitHub Releases
- **Platform**: Fedora latest container

### Build & Push Docker Image
- **Workflow**: [`.github/workflows/build-docker.yml`](.github/workflows/build-docker.yml)
- **Triggers**:
- Pushes to `main` and `develop` branches
- Tags matching `v*`
- Pull requests to `main`
- Manual dispatch
- **Output**: Pushes to GHCR with multiple tags
- **Platforms**: linux/amd64, linux/arm64

## 📝 Documentation

- **[BUILDING.md](BUILDING.md)** - Detailed guide for building packages locally
- **[PACKAGING.md](PACKAGING.md)** - Complete packaging overview and release procedure

## 🔄 Release Workflow

To create a release and trigger all build workflows:

```bash
# 1. Update version numbers
vim pyproject.toml
dch -i
vim meshcore-cli.spec

# 2. Commit and tag
git add .
git commit -m "Release version X.Y.Z"
git tag -a vX.Y.Z -m "Release meshcore-cli X.Y.Z"

# 3. Push (triggers all workflows)
git push origin main --follow-tags
```

This will automatically:
- ✅ Build Debian package → GitHub Releases
- ✅ Build RPM package → GitHub Releases
- ✅ Build & push Docker image → GHCR
- ✅ Create Release notes → GitHub Releases

## 📋 Files Overview

| File/Directory | Purpose |
|---|---|
| `debian/` | Debian package metadata and build rules |
| `meshcore-cli.spec` | Fedora/RHEL RPM specification |
| `Dockerfile` | Docker image definition |
| `docs/meshcli.1` | Unix man page |
| `.github/workflows/` | Automated build workflows |
| `BUILDING.md` | Local build instructions |
| `PACKAGING.md` | Comprehensive packaging guide |

## 🛠️ Local Testing

### Test Debian build
```bash
debuild -us -uc -b
sudo apt install ../*.deb
```

### Test RPM build
```bash
mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
rpmbuild -bb meshcore-cli.spec
sudo dnf install ~/rpmbuild/RPMS/noarch/*.rpm
```

### Test Docker build
```bash
docker build -t meshcore-cli:test .
docker run meshcore-cli:test -h
```

## 📦 Installation Methods

After release, users can install via:

```bash
# Option 1: System package (Ubuntu/Debian)
sudo apt install ./*.deb

# Option 2: System package (Fedora/RHEL)
sudo dnf install ./*.rpm

# Option 3: Docker container
docker pull ghcr.io/fdlamotte/meshcore-cli:v1.5.8
docker run ghcr.io/fdlamotte/meshcore-cli:v1.5.8 -h

# Option 4: Python package (original)
pipx install meshcore-cli

# Option 5: Nix
nix run github:meshcore-dev/meshcore-cli#meshcore-cli
```

## 🔐 Security

- All packages built from tagged git commits
- Debian packages signed with `debuild`
- Docker images scanned for vulnerabilities
- Source archives created from git tags
- Non-root user in Docker container

## 📚 Additional Resources

- [Debian Packaging Guide](https://www.debian.org/doc/manuals/debian-new-maintainers-guide/)
- [Fedora Packaging Guidelines](https://docs.fedoraproject.org/en-US/packaging-guidelines/)
- [Docker Best Practices](https://docs.docker.com/develop/dev-best-practices/)
- [GitHub Actions Documentation](https://docs.github.com/en/actions)

---

**Status**: ✅ All packaging configurations ready for production use.

Push a git tag to begin automated builds!
131 changes: 131 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# meshcore-cli: AI Coding Instructions

## Project Overview
**meshcore-cli** is a terminal interface to MeshCore companion radios and repeaters over BLE, TCP, or Serial. The core value is bridging user commands across three network interfaces to radio nodes and repeaters while supporting both interactive chat mode and scripted command execution.

## Architecture & Major Components

### Entry Point & CLI Routing
- **Entry**: [src/meshcore_cli/meshcore_cli.py](src/meshcore_cli/meshcore_cli.py) - Single file (~4700 lines) containing all command routing
- **Main function**: `async def main(argv)` - Parses `-flags` (connection args), then dispatches to `process_cmds()` for command handling
- **Command routing**: Handled by `async def next_cmd(mc, cmds, json_output)` - massive match/case statement routing 50+ commands

### Connection Layer
Three interfaces handled identically via `MeshCore` API (external package):
- **BLE** (`bleak` library): Default; device address stored in `~/.config/meshcore/default_address`
- **TCP**: `-t hostname -p port` flags; connects via MeshCore.create_tcp()
- **Serial Direct** (`-s port -r` flags): Raw repeater text CLI mode (bypasses MeshCore, uses `pyserial`)
- **Serial via MeshCore**: `-s port` without `-r` treats serial as another MeshCore transport

### Command Processing Pipeline
1. Parse argv flags → determine connection type
2. Load init script (`~/.config/meshcore/init` or `<device-name>.init`) → configure session state
3. `process_cmds(cmds)` loops calling `next_cmd()` for each command
4. Commands return remaining args or empty list (stops processing)
5. Results printed as **synthetic text OR JSON** (prefix with `.` or use `-j` flag)

### Interactive Chat Mode
- **Default behavior**: No args → calls `process_cmds(["chat"])`
- **Chat implementation**: `async def interactive_loop()` - PromptSession with history, completers, event listeners
- **Repeater serial chat**: `async def repeater_loop(ser)` - Similar readline interface for raw serial mode

## Key Patterns & Conventions

### Command Implementation Pattern
All commands follow this structure in the `next_cmd()` match/case block:
```python
case "command_name" | "shortcut":
argnum = 2 # Expected args after command
# Validation
if len(cmds) < argnum:
print("Error")
# Call MeshCore API or handle locally
res = await mc.commands.api_method()
# Display result (synthetic or JSON)
if json_output:
print(json.dumps(res.payload, indent=2))
else:
print(f"Formatted: {res.payload['field']}")
```

### Contact Resolution
Helper function `async def get_contact_from_arg(mc, arg)` - resolves string names to contact bytes. Used before sending any message or command to a repeater/client.

### Event Handling
Async event listeners are attached before main loop:
- `async def process_event_message()` - Incoming mesh messages
- `async def handle_log_rx()` - Log stream events
- `async def handle_advert()` - Network advertisements
- These use function attributes as state: `process_event_message.color`, `msg_ack.max_attempts`, etc.

### Output Formatting
- **Color support**: ANSI escape codes defined at file top (`ANSI_BGREEN`, `ANSI_BRED`, etc.)
- **JSON mode**: Prefix command with `.` or use `-j` flag for structured output
- **SNR visualization in traces**: Red (SNR ≤ 0), gray (0 < SNR < 10), green (SNR ≥ 10)

## Critical Developer Workflows

### Testing CLI Commands
```bash
# Build and install in dev mode
pip install -e .

# Test BLE connection (select device interactively)
meshcli -S chat

# Test command chaining with JSON output
meshcli -j clock reboot

# Test serial repeater mode
meshcli -r -s /dev/ttyUSB0
```

### Adding a New Command
1. Add case in `next_cmd()` match/case block
2. Set `argnum` to expected argument count
3. Call `await mc.commands.method()` or handler function
4. Handle `EventType.ERROR` cases
5. Print synthetic (human-readable) + JSON paths
6. Add help text in `get_help_for()` function
7. Update README.md `## Usage` section

### Debugging
- Use `-D` flag for debug logging (uses `logger.debug()` throughout)
- Use `-j` for structured JSON output (easier to parse errors)
- Serial repeater mode requires `pyserial` installed

## Integration Points & Dependencies

### External Packages
- **meshcore** (≥2.3.7): Core radio API, provides `MeshCore` class, `EventType` enum
- **bleak** (≥0.22): BLE scanning and connection
- **prompt_toolkit**: Interactive CLI with history, completers, dialogs
- **pyserial**: Serial port communication
- **requests**: HTTP for future extensions
- **pycryptodome**: Encryption (via meshcore dependency)

### MeshCore API Contract
All MeshCore calls return events with:
- `.type`: `EventType.OK`, `EventType.ERROR`, `EventType.TEXT_MSG`, etc.
- `.payload`: Dictionary with response data
- Pattern: `res = await mc.commands.method(); if res.type == EventType.ERROR: handle_error()`

### Configuration Files
- `~/.config/meshcore/default_address`: Last used BLE device (stored as MAC or UUID)
- `~/.config/meshcore/init`: Global init script (executed before commands)
- `~/.config/meshcore/<device-name>.init`: Per-device init script
- `~/.config/meshcore/history`: REPL history (managed by prompt_toolkit)

## Repeater Command Routing (Special Case)
Repeaters support two transport paths (see `REPEATER_COMMANDS.md`):
- **Serial direct** (`-r -s /dev/ttyUSB0`): Raw text CLI - full firmware command set
- **Mesh tunneled** (`to repeater_name`): Wrapped in MeshCore cmd messages - subset of commands

Implementation: `async def process_repeater_line()` serializes commands and parses text responses from serial; `async def send_cmd()` wraps commands in MeshCore protocol for mesh tunneling.

## Development Notes
- Single-file codebase: All logic in `meshcore_cli.py` (no separate modules)
- Heavy use of function attributes for state: `func.variable = value` persists across calls
- ANSI colors hardcoded: No color library dependency, raw escape codes
- Async-only: All I/O is `async/await` (uses `asyncio`)
- Error handling: Generally catches and logs; JSON mode defers to `EventType.ERROR`
76 changes: 76 additions & 0 deletions .github/workflows/build-deb.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Build Debian Package

on:
push:
tags:
- 'v*'
workflow_dispatch:

permissions:
contents: write

jobs:
build-deb:
runs-on: ubuntu-latest
container: debian:bookworm

steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Install dependencies
run: |
apt-get update
apt-get install -y \
build-essential \
ca-certificates \
debhelper-compat \
devscripts \
dh-python \
equivs \
fakeroot \
git \
python3-installer \
python3-pip
mk-build-deps --install --remove --tool 'apt-get -y --no-install-recommends' debian/control

- name: Build Debian package
working-directory: ${{ github.workspace }}
run: debuild -us -uc -b

- name: Build Debian dependency packages
run: packaging/build-python-dependency-debs.sh

- name: Collect Debian package files
run: |
mkdir -p dist/debian
cp ../meshcore-cli*.deb ../meshcore-cli*.changes dist/debian/
ls -l dist/debian/

- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: debian-package
path: |
dist/debian/*.deb
dist/debian/*.changes
if-no-files-found: error
retention-days: 30

- name: Upload Debian package to Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v3
with:
files: |
dist/debian/*.deb
dist/debian/*.changes
fail_on_unmatched_files: true
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Upload to Release
if: startsWith(github.ref, 'refs/tags/')
run: |
echo "Debian package built and uploaded to release"
Loading