diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-14 22:20:15 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-14 22:20:15 +0200 |
| commit | 422647e7898b8df67cd11f8fb060aec23dec84aa (patch) | |
| tree | 7ecca0e8df1bc8a7d26cf4b471bd44906dd9cfd1 /packages/meshbay-cli/src/meshbay_cli/tui/app.py | |
| parent | 28c88908b88c58cba4de08710f6bb268ede02d57 (diff) | |
| download | meshbay-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 'packages/meshbay-cli/src/meshbay_cli/tui/app.py')
| -rw-r--r-- | packages/meshbay-cli/src/meshbay_cli/tui/app.py | 76 |
1 files changed, 76 insertions, 0 deletions
diff --git a/packages/meshbay-cli/src/meshbay_cli/tui/app.py b/packages/meshbay-cli/src/meshbay_cli/tui/app.py new file mode 100644 index 0000000..dbb773c --- /dev/null +++ b/packages/meshbay-cli/src/meshbay_cli/tui/app.py @@ -0,0 +1,76 @@ +""" +`meshbay-cli` with no argument: a full-screen terminal interface. + +Textual, because it runs on the same asyncio loop as aiortc and httpx rather +than beside them, and on Linux terminals and Windows' consoles alike. Signed in +already, it opens on the two panes; otherwise on the sign-in screen first. + +Nothing is printed while it runs — a line on stderr would tear the screen — so +the log goes to a file in the CLI's data directory instead. +""" + +import logging + +from textual.app import App + +from ..client.account import Account, no_passphrase +from ..client.hub import Hub +from ..client.store import data_dir +from ..commands.context import Context +from ..errors import CliError +from .screens import DeviceCode, MainScreen, SignInScreen + + +class MeshBayApp(App): + TITLE = "MeshBay" + + def __init__(self, ctx: Context): + super().__init__() + self.ctx = ctx + self.hub = ctx.hub + + async def use_hub(self, url: str) -> None: + if url != self.hub.url: + await self.hub.aclose() + self.hub = Hub(url) + self.ctx.hub = self.hub + self.ctx.hub_url = url + + def on_mount(self) -> None: + account = None + if self.ctx.store.peek().get("hub") == self.hub.url: + try: + account = Account.open(self.hub, None, no_passphrase, self.ctx.store) + except CliError as e: + self.notify(e.message, severity="warning", markup=False) + if account is not None and account.remembered: + self.start(account) + else: + self.push_screen(SignInScreen()) + + def start(self, account: Account) -> None: + account.on_device_code = DeviceCode(self) + if isinstance(self.screen, SignInScreen): + self.switch_screen(MainScreen(account)) + else: + self.push_screen(MainScreen(account)) + + +def _log_to_file() -> None: + root = logging.getLogger() + for h in list(root.handlers): + root.removeHandler(h) + try: + directory = data_dir() + directory.mkdir(parents=True, exist_ok=True) + handler = logging.FileHandler(directory / "interactive.log", encoding="utf-8") + except OSError: + handler = logging.NullHandler() + handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")) + root.addHandler(handler) + + +async def run_interactive(ctx: Context) -> int: + _log_to_file() + await MeshBayApp(ctx).run_async() + return 0 |