diff --git a/docs/superpowers/specs/2026-07-29-alexa-media-player-design.md b/docs/superpowers/specs/2026-07-29-alexa-media-player-design.md new file mode 100644 index 0000000..1396ba4 --- /dev/null +++ b/docs/superpowers/specs/2026-07-29-alexa-media-player-design.md @@ -0,0 +1,152 @@ +# Amazon Alexa integration for Home Assistant (jupiter) — design + +**Date:** 2026-07-29 +**Machine:** jupiter +**Status:** approved, ready for implementation plan + +## Goal + +Let Home Assistant control and read the household Amazon Echo devices via the +unofficial [`alexa_media_player`](https://github.com/Alandtse/alexa_media_player) +integration: text-to-speech / announcements, media control, and per-device +sensors (last-called device, next alarm/timer, DND state, etc.). + +This uses the user's Amazon account through an unofficial API. It is a +config-flow integration: after the rebuild it is added through the HA UI, not +via YAML. + +## Non-goals (deferred) + +Alexa → HA voice control ("Alexa, turn on the light") is **out of scope**. It +would need either `emulated_hue` bound to port 80 on the LAN, or a public HTTPS +endpoint plus the AWS-Lambda Smart Home Skill. The user chose to decide on that +later. Nothing in this change touches the firewall, port 80, systemd unit +capabilities, or network exposure. + +## Approach + +Package the integration declaratively, following the existing `ha-sourdough` +pattern in `modules/environments/home-assistant/default.nix` (a +`buildHomeAssistantComponent` entry in `services.home-assistant.customComponents`). +No HACS runtime. + +Unlike sourdough, `alexa_media_player` has real Python dependencies. Its +`manifest.json` (v5.15.7) declares: + +``` +alexapy==1.29.25 +packaging>=20.3 +wrapt>=1.14.0 +dictor>=0.1.12,<0.2 +``` + +`buildHomeAssistantComponent` runs `manifestCheckPhase` at build time +(`check_manifest.py`): every requirement must resolve to an installed +distribution **whose version satisfies the specifier**, or the build fails. +Home Assistant repeats this check at runtime. Therefore each requirement must be +satisfied exactly — the exact `==1.29.25` pin in particular. + +Dependency status in the pinned nixpkgs (`nixos-25.11`): + +| Requirement | nixpkgs today | Action | +| ---------------------- | ------------- | --------------------------------------- | +| `alexapy==1.29.25` | 1.29.22 | **Bump** to 1.29.25 via override | +| `dictor>=0.1.12,<0.2` | *absent* | **Package** dictor 0.1.12 (new) | +| `wrapt>=1.14.0` | 1.17.2 | none — already satisfied | +| `packaging>=20.3` | 26.1 | none — already satisfied | + +`authcaptureproxy` (in nixpkgs at 1.3.7) is a transitive dependency of +`alexapy`, not listed in the manifest, so it needs no direct handling. + +## Components + +All changes live in `modules/environments/home-assistant/default.nix` (plus the +hashes below). Three small pieces, wired together in the module's `let` block: + +### 1. `dictor` package (new) + +Not in nixpkgs. Pure-Python, no runtime dependencies (`requires_dist: null`), +ships a `setup.py`. A minimal `pkgs.python3Packages.buildPythonPackage`: + +- pname `dictor`, version `0.1.12` +- `src = fetchPypi { pname = "dictor"; version = "0.1.12"; hash = "sha256-bbSDda4eU9ye2EToWzj04/v79qTmC+yjd1Fa0URTuRs="; }` +- setuptools format (legacy `setup.py`); `build-system = [ setuptools ]` +- `doCheck = false` (no meaningful test suite); `pythonImportsCheck = [ "dictor" ]` + +Build against the HA Python set so the version lands in HA's venv — i.e. use +`config.services.home-assistant.package.python.pkgs` (the same interpreter the +component check and HA runtime use), not the top-level `pkgs.python3Packages`. + +### 2. `alexapy` 1.29.25 (override) + +nixpkgs `alexapy` is fetched from **GitLab** (`keatontaylor/alexapy`, tag +`v`), not PyPI. Override just the version + src via +`overridePythonAttrs`, reusing the existing build-system and dependency list: + +- `version = "1.29.25"` +- `src = fetchFromGitLab { owner = "keatontaylor"; repo = "alexapy"; tag = "v1.29.25"; hash = "sha256-P/hvgqZVaBJF5dbmHrDjQMC+pwV3EEhKyFIS5KmhgD4="; }` + +This is a patch bump (1.29.22 → 1.29.25); the dependency set is expected to be +unchanged. Override the HA-Python-set `alexapy` so it shares the interpreter +with dictor and the component. + +### 3. `alexa_media_player` component (new `customComponents` entry) + +``` +buildHomeAssistantComponent { + owner = "Alandtse"; + domain = "alexa_media"; + version = "5.15.7"; + src = fetchFromGitHub { + owner = "Alandtse"; + repo = "alexa_media_player"; + rev = "v5.15.7"; + hash = "sha256-1rcZVSX1xA1Lc4qSu39MOitVEciZFhoPQy2y5+PpoAI="; + }; + dependencies = [ alexapy' dictor' wrapt packaging ]; # HA-python-set packages +} +``` + +`dependencies` must make every manifest requirement importable at the required +version for `manifestCheckPhase` to pass. Include all four (the two custom ones +plus `wrapt` and `packaging` from the HA Python set) to be explicit. + +## Data flow + +1. `nixos-rebuild switch` builds the component; `manifestCheckPhase` validates + the four requirements against the provided `dependencies`. +2. HA starts; the custom component is present but not configured. +3. User adds **Alexa Media Player** in Settings → Devices & Services, signs in + with the Amazon account (email/password; 2FA or app-password handled in the + UI flow at runtime). +4. HA creates media_player entities and per-device sensors; TTS/announce + services become available for automations. + +## Error handling / risks + +- **Version-pin drift.** If a future `alexa_media_player` bump changes the + `alexapy==` pin, `alexapy` must be re-bumped in lockstep, or the build fails + loudly at `manifestCheckPhase` (fail-safe, not silent). +- **Amazon login fragility.** The unofficial API can break on Amazon's side + (captcha/2FA changes). This is a runtime concern, independent of packaging; + not addressed here. +- **`dictor` upper bound `<0.2`.** 0.1.12 is the current release and satisfies + it. If nixpkgs later gains a `dictor` ≥ 0.2, prefer our pinned 0.1.12 for + this component. + +## Verification + +- `nix build '.#nixosConfigurations.jupiter.config.system.build.toplevel'` + succeeds — this exercises the build-time manifest-requirements check for all + three new/overridden derivations. +- `nixfmt-rfc-style` clean on the edited module. +- Post-deploy (manual, by the user): the integration appears under Add + Integration, and a TTS/announce call reaches an Echo. + +## Pinned artifacts + +| Artifact | Source | Ref / version | Hash | +| --------------------------- | ---------- | ------------- | ------------------------------------------------- | +| alexa_media_player | GitHub | v5.15.7 | sha256-1rcZVSX1xA1Lc4qSu39MOitVEciZFhoPQy2y5+PpoAI= | +| alexapy | GitLab | v1.29.25 | sha256-P/hvgqZVaBJF5dbmHrDjQMC+pwV3EEhKyFIS5KmhgD4= | +| dictor | PyPI sdist | 0.1.12 | sha256-bbSDda4eU9ye2EToWzj04/v79qTmC+yjd1Fa0URTuRs= |