feat(photon): full channel parity — gateway setup, pairing, PII redaction, doc fixes

Brings Photon in line with how every other Hermes gateway channel
behaves, instead of being a one-off with its own surfaces.

- gateway setup: register a `setup_fn` so Photon appears in
  `hermes gateway setup` (the unified wizard) and runs the same
  device-login + project + user + sidecar flow as `hermes photon setup`.
  Adds `cli.gateway_setup()` as the zero-arg entry point.
- PII redaction: flip `pii_safe` False -> True. The comment already
  said iMessage E.164 numbers should be redacted; the value contradicted
  it. Now matches BlueBubbles (the other iMessage channel) which is in
  _PII_SAFE_PLATFORMS — phone numbers are stripped before reaching the LLM.
- Pairing/authz: already worked via the registry's allowed_users_env /
  allow_all_env generic path in authz_mixin; documented it. The adapter
  forwards unauthorized DMs to the gateway (no intake gating), so the
  pairing handshake fires and `hermes pairing approve photon <CODE>` works.
- Docs: fixed the `hermes photon status` output block to match the real
  labels (project key / webhook key, not project secret / webhook secret),
  added the missing PHOTON_API_HOST / PHOTON_DASHBOARD_HOST /
  PHOTON_HOME_CHANNEL_NAME env vars, and added gateway-setup +
  authorize-users sections mirroring the other channel docs.

Validation: 26/26 photon tests, 6504/6504 gateway+plugins tests, registry
E2E confirms setup_fn dispatch + pii_safe + authz envs all wired.
This commit is contained in:
teknium1
2026-06-08 13:38:30 -07:00
committed by Teknium
parent 630318e958
commit d7f42e368e
3 changed files with 80 additions and 8 deletions
+10 -4
View File
@@ -696,6 +696,10 @@ async def _standalone_send(
def register(ctx) -> None: def register(ctx) -> None:
"""Called by the Hermes plugin loader at startup.""" """Called by the Hermes plugin loader at startup."""
# Local import to avoid argparse work at module load; reused for both the
# gateway-setup hook and the `hermes photon` CLI command below.
from . import cli as _cli
ctx.register_platform( ctx.register_platform(
name="photon", name="photon",
label="Photon iMessage", label="Photon iMessage",
@@ -709,6 +713,9 @@ def register(ctx) -> None:
"Spectrum project, links your phone number, installs the " "Spectrum project, links your phone number, installs the "
"spectrum-ts sidecar)." "spectrum-ts sidecar)."
), ),
# Surfaces Photon in `hermes gateway setup` alongside every other
# channel — same unified onboarding wizard, no Photon-only detour.
setup_fn=_cli.gateway_setup,
env_enablement_fn=_env_enablement, env_enablement_fn=_env_enablement,
cron_deliver_env_var="PHOTON_HOME_CHANNEL", cron_deliver_env_var="PHOTON_HOME_CHANNEL",
standalone_sender_fn=_standalone_send, standalone_sender_fn=_standalone_send,
@@ -717,8 +724,9 @@ def register(ctx) -> None:
max_message_length=_MAX_MESSAGE_LENGTH, max_message_length=_MAX_MESSAGE_LENGTH,
emoji="📱", emoji="📱",
# iMessage carries E.164 phone numbers — treat session descriptions # iMessage carries E.164 phone numbers — treat session descriptions
# as PII-sensitive so they get redacted in logs. # as PII-sensitive so they get redacted before reaching the LLM
pii_safe=False, # (matches the BlueBubbles iMessage channel in _PII_SAFE_PLATFORMS).
pii_safe=True,
allow_update_command=True, allow_update_command=True,
platform_hint=( platform_hint=(
"You are communicating via Photon Spectrum (iMessage). " "You are communicating via Photon Spectrum (iMessage). "
@@ -730,8 +738,6 @@ def register(ctx) -> None:
) )
# Register CLI subcommands — `hermes photon ...` # Register CLI subcommands — `hermes photon ...`
from . import cli as _cli # local import to avoid argparse at module load
ctx.register_cli_command( ctx.register_cli_command(
name="photon", name="photon",
help="Set up and manage the Photon iMessage integration", help="Set up and manage the Photon iMessage integration",
+25
View File
@@ -300,6 +300,31 @@ def _cmd_webhook(args: argparse.Namespace) -> int:
return 2 return 2
# ---------------------------------------------------------------------------
# Gateway-setup entry point
#
# `hermes gateway setup` discovers platforms via the registry and calls each
# entry's zero-arg ``setup_fn``. Photon registers this function so it appears
# in the unified setup wizard alongside every other channel — same onboarding
# surface, no Photon-specific detour. It runs the identical device-login +
# project + user + sidecar flow as ``hermes photon setup`` with interactive
# defaults (phone is prompted when stdin is a TTY).
def gateway_setup() -> None:
"""Run Photon first-time setup from the `hermes gateway setup` wizard."""
args = argparse.Namespace(
photon_command="setup",
project_name=None,
phone=None,
first_name=None,
last_name=None,
email=None,
no_browser=False,
skip_sidecar_install=False,
)
_cmd_setup(args)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Small interactive helpers # Small interactive helpers
+45 -4
View File
@@ -45,12 +45,20 @@ endpoint we'll retire the sidecar in a follow-up release.
## First-time setup ## First-time setup
Either run the unified gateway wizard and pick **Photon iMessage**:
```bash
hermes gateway setup
```
…or run the Photon setup directly (the wizard calls the same flow):
```bash ```bash
# Device-code login + project + user + sidecar deps, all in one # Device-code login + project + user + sidecar deps, all in one
hermes photon setup --phone +15551234567 hermes photon setup --phone +15551234567
``` ```
The wizard: The setup:
1. Opens `https://app.photon.codes/` for device approval 1. Opens `https://app.photon.codes/` for device approval
2. Creates a Spectrum-enabled project under your account 2. Creates a Spectrum-enabled project under your account
@@ -62,6 +70,36 @@ Credentials are stored in `~/.hermes/auth.json` under
`credential_pool.photon` (bearer token) and `credential_pool.photon` (bearer token) and
`credential_pool.photon_project` (project id + secret). `credential_pool.photon_project` (project id + secret).
## Authorizing users
Photon uses the same authorization model as every other Hermes
channel. Choose one approach:
**DM pairing (default).** When an unknown number messages your Photon
line, Hermes replies with a pairing code. Approve it with:
```bash
hermes pairing approve photon <CODE>
```
Use `hermes pairing list` to see pending codes and approved users.
**Pre-authorize specific numbers** (in `~/.hermes/.env`):
```bash
PHOTON_ALLOWED_USERS=+15551234567,+15559876543
```
**Open access** (dev only, in `~/.hermes/.env`):
```bash
PHOTON_ALLOW_ALL_USERS=true
```
When `PHOTON_ALLOWED_USERS` is set, unknown senders are silently
ignored rather than offered a pairing code (the allowlist signals you
deliberately restricted access).
## Registering the webhook ## Registering the webhook
Photon needs a public URL it can POST to. Expose your local listener Photon needs a public URL it can POST to. Expose your local listener
@@ -109,17 +147,17 @@ Photon iMessage status
────────────────────── ──────────────────────
device token : ✓ stored device token : ✓ stored
project id : 3c90c3cc-0d44-4b50-... project id : 3c90c3cc-0d44-4b50-...
project secret : ✓ stored project key : ✓ stored
webhook key : ✓ set
node binary : /usr/bin/node node binary : /usr/bin/node
sidecar deps : ✓ installed sidecar deps : ✓ installed
webhook secret : ✓ set
``` ```
Common issues: Common issues:
- **`sidecar deps : ✗ run hermes photon install-sidecar`** — Node is - **`sidecar deps : ✗ run hermes photon install-sidecar`** — Node is
installed but `spectrum-ts` isn't. Run the suggested command. installed but `spectrum-ts` isn't. Run the suggested command.
- **`webhook secret : ⚠ unset — verification disabled`** — the - **`webhook key : ⚠ unset — verification disabled`** — the
plugin will accept ANY POST to the webhook URL, which is unsafe. plugin will accept ANY POST to the webhook URL, which is unsafe.
Re-run `hermes photon webhook register` and store the secret. Re-run `hermes photon webhook register` and store the secret.
- **`PHOTON_WEBHOOK_PORT` already in use** — set a different port via - **`PHOTON_WEBHOOK_PORT` already in use** — set a different port via
@@ -160,8 +198,11 @@ hermes photon webhook delete <webhook-id> # remove one
| `PHOTON_SIDECAR_AUTOSTART`| `true` | Whether the adapter spawns the sidecar | | `PHOTON_SIDECAR_AUTOSTART`| `true` | Whether the adapter spawns the sidecar |
| `PHOTON_NODE_BIN` | `which node` | Override the Node binary path | | `PHOTON_NODE_BIN` | `which node` | Override the Node binary path |
| `PHOTON_HOME_CHANNEL` | (unset) | Default space ID for cron / notifications | | `PHOTON_HOME_CHANNEL` | (unset) | Default space ID for cron / notifications |
| `PHOTON_HOME_CHANNEL_NAME`| (unset) | Human label for the home channel |
| `PHOTON_ALLOWED_USERS` | (unset) | Comma-separated E.164 allowlist | | `PHOTON_ALLOWED_USERS` | (unset) | Comma-separated E.164 allowlist |
| `PHOTON_ALLOW_ALL_USERS` | `false` | Dev only — accept any sender | | `PHOTON_ALLOW_ALL_USERS` | `false` | Dev only — accept any sender |
| `PHOTON_API_HOST` | `spectrum.photon.codes` | Override the Spectrum management API host |
| `PHOTON_DASHBOARD_HOST` | `app.photon.codes` | Override the dashboard / device-login host |
[photon]: https://photon.codes/ [photon]: https://photon.codes/
[app]: https://app.photon.codes/ [app]: https://app.photon.codes/