diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-23 18:16:02 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-23 18:16:02 +0200 |
| commit | 1d169141f3a5542efda2f7fdb0bb88308c06191b (patch) | |
| tree | 86162905ad819d8c9dd325ccf9629bcdacc50bbf /packages/meshbay-node/src/meshbay_node/hub_client.py | |
| parent | 35a7764db3f58a93c32206cb3ce74bb2f03967e7 (diff) | |
| download | meshbay-1d169141f3a5542efda2f7fdb0bb88308c06191b.tar.gz | |
feat(node): member invite --link and member cancel in the CLI
The CLI makes both halves itself — the node's code, then the hub's
ticket bound to the address — and prints the link; a refused ticket
takes the code back, and cancel takes back both. The CLI never asks the
hub to mail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/hub_client.py')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/hub_client.py | 42 |
1 files changed, 42 insertions, 0 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/hub_client.py b/packages/meshbay-node/src/meshbay_node/hub_client.py index 58bf657..06d3503 100644 --- a/packages/meshbay-node/src/meshbay_node/hub_client.py +++ b/packages/meshbay-node/src/meshbay_node/hub_client.py @@ -85,6 +85,11 @@ class HubClient: async def __aenter__(self): return self + @property + def hub_url(self) -> str: + """The hub's address as this node was configured with it.""" + return self._config.hub_url + async def __aexit__(self, *_): await self.close() @@ -258,6 +263,43 @@ class HubClient: r.raise_for_status() return r.json() + async def create_invite_link(self, group_id: str, email: str, expires_at: str, + node_invite_id: str) -> dict: + """ + The hub's half of an invitation link: a ticket bound to `email`. + + Never with `send_email`: the hub refuses mail to a node token, and the + CLI mails nothing — the operator sends the link (docs/USERGUIDE.md §7). + """ + if self._session is None: + raise RuntimeError("Not logged in") + await self.ensure_fresh_token() + r = await self._http.post( + f"/v1/groups/{group_id}/invite-links", + json={"email": email, "expires_at": expires_at, + "node_invite_id": node_invite_id}, + headers=self._session.auth_headers, + ) + r.raise_for_status() + return r.json() + + async def list_invite_links(self, group_id: str) -> list[dict]: + if self._session is None: + raise RuntimeError("Not logged in") + await self.ensure_fresh_token() + r = await self._http.get(f"/v1/groups/{group_id}/invite-links", + headers=self._session.auth_headers) + r.raise_for_status() + return r.json().get("links", []) + + async def delete_invite_link(self, group_id: str, link_id: str) -> None: + if self._session is None: + raise RuntimeError("Not logged in") + await self.ensure_fresh_token() + r = await self._http.delete(f"/v1/groups/{group_id}/invite-links/{link_id}", + headers=self._session.auth_headers) + r.raise_for_status() + # ── Persistent WebSocket (signaling + revocations) ────────────────────── async def send_ws(self, data: str) -> None: |