Mined another devvm user's Claude sessions for repeated, hand-rolled command patterns worth absorbing into the shared CLI. The dominant signal was Home Assistant "Sofia" work: a `kubectl | base64 | jq` token-extraction pipeline re-derived ~420x, and a bespoke non-interactive `ssh -o …` invocation reinvented ~30x — every session. The existing `home-assistant-sofia.py` already covers the API but goes unused from an arbitrary cwd (needs an env var set + a cwd-relative path), so agents bypassed it and hand-rolled everything. Add two verbs covering exactly the gaps the `ha` MCP can't (entity state/control stays with the MCP): - `ha token [--instance sofia|london]` (read): resolves the long-lived API token live from k8s secret openclaw/openclaw-secrets via the ambient kubeconfig — no pre-set env var. Composes as `curl -H "Authorization: Bearer $(homelab ha token)"`. - `ha ssh [--instance sofia|london] -- <cmd>` (write): deterministic non-interactive ssh to the HA host using the invoking user's key. Also fix the root cause: `home-assistant-sofia.py` now falls back to `homelab ha token` when its env var is unset (works from any directory), and the home-assistant skill points agents at these verbs + `homelab metrics query` instead of hand-rolled curls. README + ADR-0012 + AGENTS.md updated per the per-verb-group convention. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 KiB
3 KiB
homelab Home Assistant verbs: token resolution + host SSH, not entity control
v0.7 adds ha token and ha ssh. They were chosen by mining a heavy HA
operator's sessions: across ~1,900 shell commands the single most-repeated line
(420×) was a hand-rolled kubectl … | base64 -d | python -c '…token' pipeline,
and a bespoke ssh -o StrictHostKeyChecking=no -o … invocation was redefined as
a shell function ~30× — both re-derived from scratch every session. The existing
home-assistant-sofia.py already covers the API, but it goes unused from an
arbitrary cwd (it needs HOME_ASSISTANT_SOFIA_TOKEN set and is referenced by a
cwd-relative path), so agents bypassed it. A global verb on $PATH closes that
gap for every user in every directory.
Decisions
- Only the two gaps the
haMCP can't fill. ThehaMCP server already does entity state and control (get_state,call_service, history, logs). Per the CLI's founding rule — MCP-encoded actions are out of scope (ADR-0004) — we do not reimplementon/off/list/state. We add only token resolution and host SSH, neither of which an API-only MCP can provide. The value is endpoint/secret/host resolution, exactly likenet/dns(ADR-0010). ha tokenresolves live from the cluster, not from an env var. It reads k8s Secretopenclaw/openclaw-secrets, fieldskill_secrets(a base64 JSON blob of several tokens), and prints the per-instance key (home_assistant_sofia_token/home_assistant_token) via the ambient kubeconfig. This is robust to env drift — the precise failure that made agents re-derive the pipeline. Read-tier, prints the bare token to stdout so it composes in$(…), mirroringmemory secret.ha sshis deterministic and per-user. Flags are fixed for unattended use:-F /dev/null(ignore user ssh-config),StrictHostKeyChecking=no+UserKnownHostsFile=/dev/null(no host-key prompt/record — agents have no TTY),BatchMode=yes+ConnectTimeout=10(fail fast, never hang). The key is the invoking user's~/.ssh/id_ed25519, so the verb isn't tied to whoever first wrote the workflow; that user's key must be enrolled on the HA host. Write-tier (runs an arbitrary remote command).- sofia is the default; london is structural. The devvm sits on the Sofia
LAN, so
vbarzin@192.168.1.8is reachable and is the default instance. london (hassio@192.168.8.103) is in the instance map soha token --instance londonworks (a pure secret read), butha ssh --instance londongenerally won't connect from here — london is remote. We model it correctly rather than pretend it's reachable. - Scope held at two verbs.
ha api(an authenticated curl passthrough for the endpoints the MCP/script don't cover —/api/template,/reload,check_config,/error_log) was deferred: onceha tokenexists, raw curl is already unblocked, and a generic passthrough overlaps the MCP. Re-measure viausage top(ADR-0011); add targeted sugar verbs only if those endpoints are still hand-rolled often.