File size: 4,329 Bytes
176d459
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"""`hermes meet node ...` subcommand tree.



Wired into the existing ``hermes meet`` parser by the plugin's top-level

CLI. This module only defines the subparsers and their dispatch — it

does not mutate the existing cli.py.

"""

from __future__ import annotations

import argparse
import asyncio
import json
import sys
from typing import Any

from plugins.google_meet.node.client import NodeClient
from plugins.google_meet.node.registry import NodeRegistry
from plugins.google_meet.node.server import NodeServer


def register_cli(subparser: argparse.ArgumentParser) -> None:
    """Add ``run / list / approve / remove / status / ping`` subparsers.



    *subparser* is the ``hermes meet node`` argparse object — typically

    the result of ``meet_parser.add_parser('node', ...)``.

    """
    sp = subparser.add_subparsers(dest="node_cmd", required=True)

    run = sp.add_parser("run", help="Start a node server on this machine.")
    run.add_argument("--host", default="0.0.0.0")
    run.add_argument("--port", type=int, default=18789)
    run.add_argument("--display-name", default="hermes-meet-node")
    run.set_defaults(func=node_command)

    lst = sp.add_parser("list", help="List approved remote nodes.")
    lst.set_defaults(func=node_command)

    app = sp.add_parser("approve", help="Register a remote node on the gateway.")
    app.add_argument("name")
    app.add_argument("url")
    app.add_argument("token")
    app.set_defaults(func=node_command)

    rm = sp.add_parser("remove", help="Forget a registered node.")
    rm.add_argument("name")
    rm.set_defaults(func=node_command)

    st = sp.add_parser("status", help="Ping a registered node.")
    st.add_argument("name")
    st.set_defaults(func=node_command)

    pg = sp.add_parser("ping", help="Alias for status.")
    pg.add_argument("name")
    pg.set_defaults(func=node_command)


def node_command(args: argparse.Namespace) -> int:
    """Dispatch for ``hermes meet node ...``.



    Returns a process exit code. Side-effects print to stdout/stderr.

    """
    cmd = getattr(args, "node_cmd", None)

    if cmd == "run":
        server = NodeServer(
            host=args.host,
            port=args.port,
            display_name=args.display_name,
        )
        token = server.ensure_token()
        print(f"[meet-node] display_name={server.display_name}")
        print(f"[meet-node] listening on ws://{args.host}:{args.port}")
        print(f"[meet-node] token (copy to gateway): {token}")
        print("[meet-node] approve with:")
        print(f"             hermes meet node approve <name> ws://<host>:{args.port} {token}")
        try:
            asyncio.run(server.serve())
        except KeyboardInterrupt:
            return 0
        except RuntimeError as exc:
            print(f"[meet-node] error: {exc}", file=sys.stderr)
            return 2
        return 0

    reg = NodeRegistry()

    if cmd == "list":
        nodes = reg.list_all()
        if not nodes:
            print("no nodes registered")
            return 0
        for n in nodes:
            print(f"{n['name']}\t{n['url']}\ttoken={n['token'][:6]}…")
        return 0

    if cmd == "approve":
        reg.add(args.name, args.url, args.token)
        print(f"approved node {args.name!r} at {args.url}")
        return 0

    if cmd == "remove":
        ok = reg.remove(args.name)
        print(f"removed {args.name!r}" if ok else f"no such node: {args.name!r}")
        return 0 if ok else 1

    if cmd in {"status", "ping"}:
        entry = reg.get(args.name)
        if entry is None:
            print(f"no such node: {args.name!r}", file=sys.stderr)
            return 1
        client = NodeClient(entry["url"], entry["token"])
        try:
            result = client.ping()
        except Exception as exc:  # noqa: BLE001 — surface any connection error
            print(json.dumps({"ok": False, "error": str(exc)}))
            return 1
        print(json.dumps({"ok": True, "node": args.name, **_coerce_dict(result)}))
        return 0

    print(f"unknown node command: {cmd!r}", file=sys.stderr)
    return 2


def _coerce_dict(value: Any) -> dict:
    return value if isinstance(value, dict) else {"result": value}