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 --- packages/meshbay-cli/src/meshbay_cli/client/hub.py | 41 ++++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) (limited to 'packages/meshbay-cli/src/meshbay_cli/client/hub.py') diff --git a/packages/meshbay-cli/src/meshbay_cli/client/hub.py b/packages/meshbay-cli/src/meshbay_cli/client/hub.py index c8ce0c1..94d22da 100644 --- a/packages/meshbay-cli/src/meshbay_cli/client/hub.py +++ b/packages/meshbay-cli/src/meshbay_cli/client/hub.py @@ -1,9 +1,10 @@ """ The hub's API, as a member's client uses it. -The hub signs in, lists groups and the nodes serving them, mints the token a -node is shown, and relays the WebRTC offer. Nothing else: no listing, no file -and no byte of content passes through it (docs/MESHBAY_DESIGN.md §1.3, §8.4). +The hub signs in, lists groups and the nodes serving them, keeps memberships +(invitations, links, leaving) and preferences, mints the token a node is shown, +and relays the WebRTC offer. Nothing else: no listing, no file and no byte of +content passes through it (docs/MESHBAY_DESIGN.md §1.3, §8.4). """ import logging @@ -147,6 +148,40 @@ class Hub: raise CliError(NODE, f"The node did not answer the offer: {e.detail}") from e return r["sdp"] + # ── Joining and leaving ────────────────────────────────────────────────── + + async def invitations(self, token: str) -> list[dict]: + return (await self._call("GET", "/v1/groups/invitations", + token=token))["invitations"] + + async def accept_invitation(self, token: str, group_id: str) -> dict: + return await self._call("POST", f"/v1/groups/{group_id}/invitation/accept", + token=token) + + async def decline_invitation(self, token: str, group_id: str) -> dict: + return await self._call("POST", f"/v1/groups/{group_id}/invitation/decline", + token=token) + + async def preview_link(self, token: str, ticket: str) -> dict: + return await self._call("POST", "/v1/invite-links/preview", token=token, + json={"ticket": ticket}) + + async def redeem_link(self, token: str, ticket: str) -> dict: + return await self._call("POST", "/v1/invite-links/redeem", token=token, + json={"ticket": ticket}) + + async def leave_group(self, token: str, group_id: str) -> dict: + return await self._call("POST", f"/v1/groups/{group_id}/leave", token=token) + + # ── Preferences ────────────────────────────────────────────────────────── + + async def preferences(self, token: str) -> dict: + return await self._call("GET", "/v1/users/me/preferences", token=token) + + async def set_preference(self, token: str, key: str, value: str) -> dict: + return await self._call("PUT", f"/v1/users/me/preferences/{key}", token=token, + json={"value": value}) + def _sign_in_refusal(e: HubError) -> CliError: if e.status == 401: -- cgit v1.2.3