diff --git a/plugins/platforms/photon/README.md b/plugins/platforms/photon/README.md index a2cd92ec4a7f..f78be5d41d93 100644 --- a/plugins/platforms/photon/README.md +++ b/plugins/platforms/photon/README.md @@ -116,6 +116,7 @@ All env vars are documented in `plugin.yaml`. The most important: | `PHOTON_ALLOWED_USERS` | your number (set by setup) | Comma-separated E.164 allowlist | | `PHOTON_REQUIRE_MENTION` | false | Gate group chats on a wake word | | `PHOTON_MAX_INLINE_ATTACHMENT_BYTES` | 20 MB | Max inbound attachment size the sidecar reads & inlines | +| `PHOTON_TELEMETRY` | false | Spectrum SDK telemetry — toggle with `hermes photon telemetry on\|off` (restart the gateway to apply) | ## Attachments & limitations diff --git a/plugins/platforms/photon/cli.py b/plugins/platforms/photon/cli.py index 789746860f85..f93b33f2c956 100644 --- a/plugins/platforms/photon/cli.py +++ b/plugins/platforms/photon/cli.py @@ -7,6 +7,7 @@ Subcommands: setup full first-time setup (device login + project + user + sidecar) status show login + project + sidecar dep state install-sidecar npm install inside plugins/platforms/photon/sidecar/ + telemetry show or toggle Spectrum SDK telemetry (on/off) The device-code login runs automatically as the first step of ``setup``; there is no standalone ``login`` verb (matching how every other Hermes @@ -58,6 +59,15 @@ def register_cli(parser: argparse.ArgumentParser) -> None: subs.add_parser("status", help="Show login + project + sidecar dep state") subs.add_parser("install-sidecar", help="Run npm install inside the sidecar directory") + p_telemetry = subs.add_parser( + "telemetry", + help="Show or toggle Spectrum SDK telemetry (on/off)", + ) + p_telemetry.add_argument( + "state", nargs="?", choices=("on", "off"), + help="Turn telemetry on or off (omit to show the current state)", + ) + parser.set_defaults(func=dispatch) @@ -75,6 +85,8 @@ def dispatch(args: argparse.Namespace) -> int: return _cmd_status(args) if sub == "install-sidecar": return _cmd_install_sidecar(args) + if sub == "telemetry": + return _cmd_telemetry(args) print(f"unknown subcommand: {sub}", file=sys.stderr) return 2 @@ -303,6 +315,7 @@ def _cmd_status(_args: argparse.Namespace) -> int: sidecar_installed = (_SIDECAR_DIR / "node_modules").exists() print(f" node binary : {node_bin or '✗ missing (install Node 18+)'}") print(f" sidecar deps : {'✓ installed' if sidecar_installed else '✗ run `hermes photon install-sidecar`'}") + print(f" telemetry : {'on' if _telemetry_enabled() else 'off'} (`hermes photon telemetry on|off`)") return 0 @@ -323,6 +336,37 @@ def _cmd_install_sidecar(_args: argparse.Namespace) -> int: return _install_sidecar() +def _telemetry_enabled() -> bool: + """Read PHOTON_TELEMETRY from the env / ~/.hermes/.env. + + Mirrors the sidecar's truthy set (index.mjs) so the state shown here + always matches what the sidecar will actually do. + """ + try: + from hermes_cli.config import get_env_value + raw = get_env_value("PHOTON_TELEMETRY") + except ImportError: + raw = os.getenv("PHOTON_TELEMETRY") + return (raw or "").strip().lower() in ("1", "true", "yes", "on") + + +def _cmd_telemetry(args: argparse.Namespace) -> int: + state = getattr(args, "state", None) + if state is None: + print(f"Photon telemetry: {'on' if _telemetry_enabled() else 'off'}") + print(" Toggle with `hermes photon telemetry on` / `hermes photon telemetry off`.") + return 0 + try: + from hermes_cli.config import save_env_value + save_env_value("PHOTON_TELEMETRY", "true" if state == "on" else "false") + except Exception as e: + print(f"could not save PHOTON_TELEMETRY: {e}", file=sys.stderr) + return 1 + print(f"✓ Spectrum telemetry turned {state} (PHOTON_TELEMETRY in ~/.hermes/.env)") + print(" Restart the gateway for the sidecar to pick it up: hermes gateway restart") + return 0 + + def _install_sidecar() -> int: npm = shutil.which("npm") or "npm" if not shutil.which(npm): diff --git a/plugins/platforms/photon/plugin.yaml b/plugins/platforms/photon/plugin.yaml index 12ade17f8e58..c9e149cbca4f 100644 --- a/plugins/platforms/photon/plugin.yaml +++ b/plugins/platforms/photon/plugin.yaml @@ -74,3 +74,7 @@ optional_env: description: "Human label for the home channel" prompt: "Home channel display name" password: false + - name: PHOTON_TELEMETRY + description: "Enable Spectrum SDK telemetry in the sidecar (true/false, default false; toggle with `hermes photon telemetry on|off`)" + prompt: "Enable Spectrum telemetry? (true/false)" + password: false diff --git a/plugins/platforms/photon/sidecar/index.mjs b/plugins/platforms/photon/sidecar/index.mjs index d1c44d7c51bc..065e90d84cb6 100644 --- a/plugins/platforms/photon/sidecar/index.mjs +++ b/plugins/platforms/photon/sidecar/index.mjs @@ -38,6 +38,8 @@ // PHOTON_SIDECAR_TOKEN // Optional: // PHOTON_SIDECAR_BIND (default 127.0.0.1) +// PHOTON_TELEMETRY enable Spectrum SDK telemetry ("true"/"1"/"on"/"yes"; +// default off — toggle with `hermes photon telemetry`) import http from "node:http"; import crypto from "node:crypto"; @@ -48,6 +50,9 @@ const projectSecret = process.env.PHOTON_PROJECT_SECRET; const port = parseInt(process.env.PHOTON_SIDECAR_PORT || "8789", 10); const bind = process.env.PHOTON_SIDECAR_BIND || "127.0.0.1"; const sharedToken = process.env.PHOTON_SIDECAR_TOKEN; +const telemetry = /^(1|true|yes|on)$/i.test( + (process.env.PHOTON_TELEMETRY || "").trim() +); // Inbound binary content is read into memory and base64-inlined on the NDJSON // event so the Python adapter can cache the real bytes (and the agent can see @@ -94,6 +99,7 @@ const app = await Spectrum({ projectSecret, providers: [imessage.config()], options: { flattenGroups: true }, + telemetry, }); // ---------------------------------------------------------------------------