From 422647e7898b8df67cd11f8fb060aec23dec84aa Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Wed, 14 Oct 2026 22:20:15 +0200 Subject: 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 --- packages/meshbay-node/tests/test_licensing.py | 34 ++++++++++++++++++++--- packages/meshbay-node/tests/test_packaging_win.py | 5 +++- 2 files changed, 34 insertions(+), 5 deletions(-) (limited to 'packages/meshbay-node/tests') diff --git a/packages/meshbay-node/tests/test_licensing.py b/packages/meshbay-node/tests/test_licensing.py index 239633d..0cb85cc 100644 --- a/packages/meshbay-node/tests/test_licensing.py +++ b/packages/meshbay-node/tests/test_licensing.py @@ -11,6 +11,7 @@ LGPL. import importlib.util import json import re +import sys import tomllib from importlib import metadata from pathlib import Path @@ -22,7 +23,9 @@ VENDOR = STATIC / "vendor" DESKTOP = PACKAGES / "meshbay-client" / "src" ANDROID = PACKAGES / "meshbay-android" / "app" / "src" / "main" / "kotlin" KEYS = ANDROID / "org" / "meshbay" / "client" / "keys" +CLI = PACKAGES / "meshbay-cli" / "src" / "meshbay_cli" SPDX_LGPL = "// SPDX-License-Identifier: LGPL-3.0-or-later\n" +SPDX_LGPL_PY = "# SPDX-License-Identifier: LGPL-3.0-or-later\n" # The protocol layer in the clients (README, "Licence"): what a program needs to # speak to a hub and a node, under the LGPL in every language it exists in. LGPL_FILES = sorted( @@ -31,11 +34,13 @@ LGPL_FILES = sorted( + sorted(STATIC.glob("transport-*.js")) + [DESKTOP / f for f in ("keyring.js", "transcripts.js", "argon2-wasm.js")] + [KEYS / f for f in ("Kdf.kt", "Keyring.kt", "Transcripts.kt")] + + [CLI / "client" / "keys.py"] ) EXPECTED = { "meshbay-common": ("LGPL-3.0-or-later", ["COPYING", "COPYING.LESSER"]), "meshbay-hub": ("AGPL-3.0-or-later", ["LICENSE"]), "meshbay-node": ("AGPL-3.0-or-later", ["LICENSE"]), + "meshbay-cli": ("AGPL-3.0-or-later", ["LICENSE"]), } @@ -63,7 +68,7 @@ def test_licence_texts_are_the_right_ones(): agpl = (ROOT / "LICENSE").read_text(encoding="utf-8") assert "GNU AFFERO GENERAL PUBLIC LICENSE" in agpl and "Version 3" in agpl # Copies, because a wheel's license-files cannot reach outside its package. - for pkg in ("meshbay-hub", "meshbay-node"): + for pkg in ("meshbay-hub", "meshbay-node", "meshbay-cli"): assert (PACKAGES / pkg / "LICENSE").read_text(encoding="utf-8") == agpl, pkg common = PACKAGES / "meshbay-common" assert "GNU LESSER GENERAL PUBLIC LICENSE" in (common / "COPYING.LESSER").read_text( @@ -73,7 +78,8 @@ def test_licence_texts_are_the_right_ones(): def test_rpm_specs_and_the_client_agree_with_the_packages(): - for pkg in ("meshbay-common", "meshbay-hub", "meshbay-node", "meshbay-client"): + for pkg in ("meshbay-common", "meshbay-hub", "meshbay-node", "meshbay-cli", + "meshbay-client"): spec = (ROOT / "packaging" / "rpm" / f"{pkg}.spec").read_text(encoding="utf-8") want = EXPECTED.get(pkg, ("AGPL-3.0-or-later",))[0] assert re.search(rf"^License:\s+{re.escape(want)}\s*$", spec, re.M), pkg @@ -136,14 +142,18 @@ def test_every_vendored_file_has_its_provenance_and_licence(): def _sources(): - for tree, pattern in ((STATIC, "*.js"), (DESKTOP, "*.js"), (ANDROID, "**/*.kt")): + for tree, pattern in ((STATIC, "*.js"), (DESKTOP, "*.js"), (ANDROID, "**/*.kt"), + (CLI, "**/*.py")): for f in tree.glob(pattern): if "vendor" not in f.parts and "locales" not in f.parts: yield f def test_the_lgpl_files_are_exactly_the_ones_that_say_so(): - marked = sorted(f for f in _sources() if f.read_text(encoding="utf-8").startswith(SPDX_LGPL)) + def spdx(f: Path) -> str: + return SPDX_LGPL_PY if f.suffix == ".py" else SPDX_LGPL + + marked = sorted(f for f in _sources() if f.read_text(encoding="utf-8").startswith(spdx(f))) assert marked == LGPL_FILES @@ -202,6 +212,22 @@ def test_the_lgpl_layer_depends_on_nothing_under_the_agpl(): } for f in LGPL_FILES: src = f.read_text(encoding="utf-8") + if f.suffix == ".py": + # The standard library, meshbay-common (the LGPL layer itself) and + # permissive packages; never the client package it sits in. + for mod in re.findall(r"^\s*(?:from|import)\s+([\w.]+)", src, re.M): + top_name = mod.split(".")[0] + assert top_name and top_name != "meshbay_cli" and not mod.startswith("."), ( + f"{f.name} imports {mod}") + if top_name in sys.stdlib_module_names or top_name == "meshbay_common": + continue + dist = metadata.packages_distributions()[top_name][0] + md = metadata.distribution(dist).metadata + label = " ".join([md.get("License-Expression") or "", md.get("License") or ""] + + (md.get_all("Classifier") or [])) + assert "GPL" not in label, f"{f.name} imports {mod} ({dist}: {label[:80]})" + assert not re.search(r"^\s*from\s+\.", src, re.M), f"{f.name} has a relative import" + continue if f.suffix == ".kt": for imp in re.findall(r"^import (org\.meshbay\.[\w.]+)", src, re.M): owner = ( diff --git a/packages/meshbay-node/tests/test_packaging_win.py b/packages/meshbay-node/tests/test_packaging_win.py index 69eefc8..ed8104f 100644 --- a/packages/meshbay-node/tests/test_packaging_win.py +++ b/packages/meshbay-node/tests/test_packaging_win.py @@ -173,7 +173,10 @@ def test_the_frozen_exe_carries_a_version_resource(): spec = (WIN / "meshbay-node.spec").read_text(encoding="utf-8") assert "VSVersionInfo" in spec assert "version=_version_info" in spec, "EXE() is not given the version resource" - assert 'StringStruct("ProductName", "MeshBay Node")' in spec + assert '_version_info = _version_resource("meshbay-node", "MeshBay Node")' in spec + assert 'StringStruct("ProductName", description)' in spec + # The command-line client beside it, under its own name. + assert 'version=_version_resource("meshbay-cli", "MeshBay command-line client")' in spec assert '_pkg_version("meshbay-node")' in spec, "version is hardcoded, not read from the package" -- cgit v1.2.3