summaryrefslogtreecommitdiffstats
path: root/CLAUDE.md
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-14 22:20:15 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-14 22:20:15 +0200
commit422647e7898b8df67cd11f8fb060aec23dec84aa (patch)
tree7ecca0e8df1bc8a7d26cf4b471bd44906dd9cfd1 /CLAUDE.md
parent28c88908b88c58cba4de08710f6bb268ede02d57 (diff)
downloadmeshbay-422647e7898b8df67cd11f8fb060aec23dec84aa.tar.gz
feat(cli): meshbay-cli, a command-line client for members
Signs in to the hub, lists the account's groups and their folders, and downloads and uploads files directly with each group's node, end-to-end encrypted. With no argument it opens a two-pane interactive mode (Textual). Session and device keys are kept encrypted under the system's key storage, or a 0600 key file where there is none; node keys are pinned at first contact; a new machine links as a device. Packaged on its own on Linux (deb and rpm, on meshbay-common's venv, with a man page generated from the parser) and frozen next to meshbay-node in the full Windows installer, not the light one, with a self-test the build runs. client/keys.py ports keyderive.js and is LGPL like it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'CLAUDE.md')
-rw-r--r--CLAUDE.md38
1 files changed, 28 insertions, 10 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index f889685..319dd24 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -32,6 +32,7 @@ meshbay/
│ ├── meshbay-common/ # Shared crypto + protocol — python3-meshbay-common RPM (LGPL; rest AGPL)
│ ├── meshbay-hub/ # Hub server (FastAPI + PostgreSQL) — meshbay-hub RPM
│ ├── meshbay-node/ # Node daemon + local UI — meshbay-node RPM
+│ ├── meshbay-cli/ # Command-line client for members (Python) — meshbay-cli package
│ ├── meshbay-client/ # Desktop client (Electron)
│ └── meshbay-android/ # Android client (WebView shell, Kotlin) — README.md
├── poc/ # POC spike scripts (reference, not production)
@@ -59,7 +60,7 @@ source .venv/bin/activate
# All dependencies are declared in the pyproject.toml files — a single pip install is enough
pip install -e "packages/meshbay-common[dev]" -e "packages/meshbay-hub[dev]" \
- -e "packages/meshbay-node[dev]"
+ -e "packages/meshbay-node[dev]" -e "packages/meshbay-cli[dev]"
```
The key deps (aioquic, watchdog, fastapi, blake3, etc.) are in the `pyproject.toml` files
@@ -99,7 +100,7 @@ in the right `pyproject.toml`.
### Package versions (SemVer)
- Format: `MAJOR.MINOR.PATCH`
- Pre-1.0: breaking changes bump MINOR, not MAJOR
-- All three packages share the same version number (released together)
+- All Python packages share the same version number (released together)
### Protocol versions (independent)
@@ -143,11 +144,12 @@ AGPL-3.0-or-later.** The protocol layer is `meshbay-common` (`COPYING.LESSER` +
`// SPDX-License-Identifier: LGPL-3.0-or-later`: `keyderive.js`, `crypto.js`,
`playlist-crypto.js`, `transport.js`, `transport-*.js`; `meshbay-client/src/`
`keyring.js`, `transcripts.js`, `argon2-wasm.js`; Android `keys/Kdf.kt`,
-`Keyring.kt`, `Transcripts.kt`. A file without that line has its package's
-licence: the AGPL (`LICENSE` at the root, copied into `meshbay-hub/` and
-`meshbay-node/` because a wheel's `license-files` cannot reach outside its
-package; into `static/licenses/` with the LGPL and GPL, so that every client
-carries all three).
+`Keyring.kt`, `Transcripts.kt`; `meshbay-cli`'s `client/keys.py` (first line
+`# SPDX-License-Identifier: LGPL-3.0-or-later`). A file without that line has
+its package's licence: the AGPL (`LICENSE` at the root, copied into
+`meshbay-hub/`, `meshbay-node/` and `meshbay-cli/` because a wheel's
+`license-files` cannot reach outside its package; into `static/licenses/` with
+the LGPL and GPL, so that every client carries all three).
- **An LGPL file depends only on LGPL files, permissive vendored code or the
platform.** One import of an AGPL module and a client using the layer is under
@@ -155,9 +157,9 @@ carries all three).
`window.meshbay`, a `Secrets` store) is reached as an injected interface, never
imported. `test_licensing.py` checks the closure.
- **The same piece has the same licence on every platform.** A port is LGPL when
- its original is (Keyring.kt ↔ keyring.js ↔ keyderive.js), and stays on the AGPL
- side when its original is part of a shell (DeviceKey.kt ↔ the device key in
- `main.js`). A new protocol file is added to the list here, to the README, and
+ its original is (Keyring.kt ↔ keyring.js ↔ keyderive.js ↔ the CLI's
+ `client/keys.py`), and stays on the AGPL side when its original is part of a
+ shell (DeviceKey.kt ↔ the device key in `main.js`). A new protocol file is added to the list here, to the README, and
to `LGPL_FILES` in the test, with its SPDX line. The Android application adds a §7 permission for
Google Play services (`meshbay-android/LICENSE-EXCEPTION.txt`).
@@ -1152,6 +1154,22 @@ which pytest also runs when `ANDROID_HOME` is set (`test_android_shell.py`).
| Downloads | `save/SaveSinks.kt`, `SaveNames.kt` | `OPENABLE` is `downloads.js`'s, compared by `test_android_downloads.py` |
| Casting | `cast/CastRelay.kt`, `CastControl.kt`, `CastChannels.kt`, `CastService.kt`, `shell/ShellWebView.kt` | `MeshBayCast` in logcat says what the receiver and the relay did |
+### Command-line client (`packages/meshbay-cli/`)
+
+| Need | File | Note |
+|---|---|---|
+| Actions and their parsers | `meshbay_cli/__main__.py` (`ACTIONS`), `commands/*.py` | §8.6. `test_every_action_is_run_here` refuses an action with no run in `test_cli_actions.py` |
+| Exit codes | `meshbay_cli/errors.py` | **a contract for scripts**; the man page explains each (`manpage.EXIT_CODES`, tested) |
+| Sign-in, renewal, the store | `client/account.py`, `client/store.py`, `client/hub.py` | renewal under the store's lock; the store key in the OS keyring, else a 0600 file |
+| Passphrase keys, bundle opening | `client/keys.py` | **LGPL, parity-tested** with `keyderive.js` (`test_bundle_kdf_parity.py`) |
+| Connection, handshake, identity, device linking | `client/session.py`, `client/groups.py`, `client/pool.py` | §5.2, §3.3 |
+| Remote paths, listing, local names | `client/paths.py`, `client/tree.py`, `client/names.py` | `names.UPLOAD_NAME` is copied from the node's `SAFE_UPLOAD_NAME` |
+| Downloads and uploads | `client/transfers.py`, `client/upload_plan.py` | §5.5; `upload_plan` is shared by `put` and the interactive mode |
+| Interactive mode | `meshbay_cli/tui/` | Textual; names are drawn as text, never markup |
+| Man page | `meshbay_cli/manpage.py` | generated from the parser at build time; `test_manpage.py` |
+| What a frozen build must load | `meshbay_cli/selftest.py` | `MESHBAY_CLI_SELFTEST=1 meshbay-cli`, run by the Windows build |
+| End to end against a real hub and node | `tests/test_e2e_local.py` | opt-in, `MESHBAY_CLI_E2E=1`; `MESHBAY_CLI_E2E_BIN` runs a packaged build instead |
+
### Test harnesses that drive the real thing
| Need | File |