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:
@@ -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",
|
||||||
|
|||||||
@@ -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,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/
|
||||||
|
|||||||
Reference in New Issue
Block a user