From 6f68bb6c1970e26de1f66c073ea5d7fecd21840d Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Thu, 15 Oct 2026 00:56:04 +0200 Subject: feat(cli): invitations, join, decline, leave, devices, browser access join accepts an invitation or redeems a link pasted as sent, then has the node admit a key pair made here, with the invitation's code (a link's code goes only to the node it names; one for a node that is away is kept for the next connection). decline, leave and devices revoke ask first, and refuse without --yes where there is no terminal. devices approve countersigns the one waiting request a code matches, none when several do. browser-access is the application's setting, kept per machine and never enabled from the hub's mirror: on leaves a copy sealed under M on each node, off withdraws it, unset touches nothing. A key pair made for a first contact is now kept. Co-Authored-By: Claude Opus 5.5 --- .../src/meshbay_cli/commands/browser_access.py | 72 ++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 packages/meshbay-cli/src/meshbay_cli/commands/browser_access.py (limited to 'packages/meshbay-cli/src/meshbay_cli/commands/browser_access.py') diff --git a/packages/meshbay-cli/src/meshbay_cli/commands/browser_access.py b/packages/meshbay-cli/src/meshbay_cli/commands/browser_access.py new file mode 100644 index 0000000..e2b0b8e --- /dev/null +++ b/packages/meshbay-cli/src/meshbay_cli/commands/browser_access.py @@ -0,0 +1,72 @@ +""" +`browser-access`: whether this machine leaves, on each node, a copy of its keys +a browser can open with the passphrase — the application's Profile → Browser +access, kept by this machine (docs/MESHBAY_DESIGN.md §3.7). + +`on`: each group opened here leaves a copy sealed under the passphrase-derived +key, when its node holds none or an outdated one. `off`: the copy a node holds +is withdrawn. Never set: nothing is written or withdrawn, so a CLI nobody +configured never undoes what the application of another computer does. + +Set here and nowhere else. The hub keeps a mirror of it, written here as the +application writes it, but a mirror anyone with a hub session can write must +never make this machine upload keys: read, it only produces a message. +""" + +import json + +from ..client.hub import HubError +from ..errors import AUTH, CliError +from ..output import err, sentence +from .context import Context + +HELP = "whether a browser may open your groups: leave a copy of your keys on nodes, or not" + +EFFECT = { + "on": "Each group opened here from now on leaves a copy of your keys on its node, " + "sealed under your passphrase, so that a browser can open it.", + "off": "Each group opened here from now on withdraws the copy of your keys its node " + "holds; a browser will no longer open it with your passphrase.", +} + + +def add_parser(sub, parent) -> None: + p = sub.add_parser("browser-access", parents=[parent], help=HELP, + description=sentence(HELP) + ". As the application's Browser " + "access, for this machine. With no argument, shows the setting.") + p.add_argument("state", nargs="?", choices=("on", "off")) + + +async def run(ctx: Context) -> int: + a = ctx.args + account = ctx.account() + if not account.remembered: + raise CliError(AUTH, "This setting is kept with the saved session; run " + "`meshbay-cli login` first.") + if a.state is None: + try: + mirror = (await account.call(account.hub.preferences)).get("browser_access") + except (HubError, CliError): + mirror = None + mine = account.browser_access + if a.json: + print(json.dumps({"this_machine": mine, "hub": mirror})) + else: + print(f"This machine: {mine or 'not set (leaves the nodes as they are)'}") + print(f"The hub's copy: {mirror or 'none'} (informative: it sets nothing here)") + return 0 + + def put(state: dict, secrets: dict) -> None: + state["browser_access"] = a.state + await account.update(put) + try: + await account.call(account.hub.set_preference, "browser_access", a.state) + except (HubError, CliError) as e: + err(f"meshbay-cli: warning: the hub's copy of the setting was not updated " + f"({getattr(e, 'detail', None) or getattr(e, 'message', e)}); a browser may " + "explain a closed group less well.") + if a.json: + print(json.dumps({"this_machine": a.state})) + else: + print(f"Browser access: {a.state}. {EFFECT[a.state]}") + return 0 -- cgit v1.2.3