plugins/claude-code-homeassistant-hermit/skills/hatch/SKILL.md
One-time Home Assistant setup for this hermit. Configures HA access, connects to the official Home Assistant MCP Server integration, and verifies both the CLI and HA MCP. Run once per project after /claude-code-hermit:hatch.
npx skillsauth add gtapps/claude-code-hermit hatchInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Set up the Home Assistant layer for this project. Idempotent — safe to re-run; will skip completed steps and offer re-verify only.
Check whether .claude-code-hermit/config.json exists.
AskUserQuestion: "Core hermit is not initialized. Run /claude-code-hermit:hatch now?"claude-code-hermit:hatch):
.claude-code-hermit/state/hatch-resume.json with { "skill": "claude-code-homeassistant-hermit:hatch" }./claude-code-homeassistant-hermit:hatch.)"/claude-code-hermit:hatch via the Skill tool — terminal action, stop after the call..claude-code-hermit/bin/hermit-run domain-hatch preflight claude-code-homeassistant-hermit and parse the JSON verdict. Branch on action:
upgrade-core-package / upgrade-core-applied → relay the remedy string verbatim to the operator and stop.verify → AskUserQuestion: "Already set up. Re-verify HA access only (skip setup wizard)?". Yes → skip to §5. No → continue.full → continue with setup.ok: false → relay message and stop.Run ${CLAUDE_PLUGIN_ROOT}/bin/ha-agent-lab boot status and inspect the JSON output.
Important: do NOT use
grep,cat, orechoon.env— the deny-pattern hook blocks any Bash command whose arguments contain the literal stringTOKEN. Always use the CLI to check credential state.
token_configured: true and local_url non-null → proceed..env is missing or incomplete. Please create `.env` at the project root with:
HOMEASSISTANT_URL=http://homeassistant.local:8123 # or your remote URL
HOMEASSISTANT_TOKEN=<your long-lived access token>
Long-Lived Access Tokens: Home Assistant → Profile → Long-Lived Access Tokens.
AskUserQuestion: "When your .env is ready, type done to continue (or abort to stop)."
boot status and re-check. If still missing, repeat from step 1. If valid, proceed..env — it is the user's responsibility.Also check locale:
.claude-code-hermit/OPERATOR.md. If a ## HA hermit section has a - Language: entry, use it silently — do not re-ask.en, pt, es) Save it via ${CLAUDE_PLUGIN_ROOT}/bin/ha-agent-lab boot store --language <locale> (writes to OPERATOR.md under ## HA hermit).Do not collect or store the token — it stays in .env only.
The CLI runs on bun, which the core hermit requirement guarantees — no runtime deps to install.
Run ${CLAUDE_PLUGIN_ROOT}/bin/ha-agent-lab boot status (read-only, no --probe) to confirm the launcher resolves correctly. If it fails with "bun not found", stop and tell the user to install bun (https://bun.sh) — it is required by claude-code-hermit core.
Step A — Enable the integration in Home Assistant
Tell the user: go to Home Assistant → Settings → Devices & Services → Add Integration → search "Model Context Protocol Server". Enable it. This exposes the MCP endpoint at <your HA URL>/api/mcp.
Reference: https://www.home-assistant.io/integrations/mcp_server/
Step B — Write .mcp.json
Read the HA URL from the boot status JSON (active_url field, already fetched in §2). Read the token from .env using:
${CLAUDE_PLUGIN_ROOT}/bin/ha-agent-lab boot status
For the token value, use the Read tool on .env (not Bash — the deny-pattern hook blocks any Bash argument containing the literal string TOKEN, including via bun -e). Parse the HOMEASSISTANT_TOKEN=... line in-memory and use the value directly when writing .mcp.json. Do not echo the token to the conversation or log it.
Check the project root for .mcp.json:
homeassistant entry:
url or Authorization value contains ${ (old placeholder format) → rewrite that entry with literal values and tell the user the stale entry was replaced.{
"mcpServers": {
"homeassistant": {
"type": "http",
"url": "<HOMEASSISTANT_URL>/api/mcp",
"headers": { "Authorization": "Bearer <HOMEASSISTANT_TOKEN>" }
}
}
}
Replace <HOMEASSISTANT_URL> with the active_url from boot status (resolves to HOMEASSISTANT_URL, or HOMEASSISTANT_LOCAL_URL for existing installs) and <HOMEASSISTANT_TOKEN> with the literal values read above.
The name homeassistant is required — skills and the safety hook match on mcp__homeassistant__* tool IDs.
Note:
.mcp.jsonnow contains a live bearer token. Claude Code reads MCP env vars from the process environment, not from.env, so literal values are required here.
After writing .mcp.json, check the project's .gitignore:
.mcp.json is absent from it → append .mcp.json on a new line.Step C — Activate and verify
Tell the user: restart Claude Code in this project directory. On first use, Claude Code will prompt you to trust the homeassistant server — approve it. Then run /mcp to confirm homeassistant appears as connected. The next ha-boot will verify live HA connectivity.
Run ${CLAUDE_PLUGIN_ROOT}/bin/ha-agent-lab boot status --probe and present the result. If it fails:
HOMEASSISTANT_LOCAL_URL in .env.HOMEASSISTANT_TOKEN.Resolve target file: Step 1's preflight already returned target, target_file, target_default and needs_target_question.
If needs_target_question is true, ask with AskUserQuestion (header: "Visibility") — target_default at position 0 with (recommended): .local files (gitignored — operator-personal) / Committed files (shared with teammates). Then record it:
.claude-code-hermit/bin/hermit-run domain-hatch ensure-target claude-code-homeassistant-hermit --target <choice>
Then write the block:
.claude-code-hermit/bin/hermit-run domain-hatch sync-block claude-code-homeassistant-hermit
It appends the <!-- claude-code-homeassistant-hermit: Home Assistant Workflow --> block when the marker is absent and skips when it is already present. Refreshing an existing block on a version bump is hermit-evolve's job, not hatch's.
Stray-block migration (block stranded in the non-target file after a target flip) is handled one-shot by the Upgrade Instructions in this version's CHANGELOG entry, executed by hermit-evolve Step 7. Hatch itself stays focused on target-aware setup and steady-state refresh.
Read ha_safety_mode from .claude-code-hermit/config.json.
AskUserQuestion: "Current safety mode is <value>. Change it?" Yes → re-prompt. No → skip this step.lock, alarm_control_panel, security-related cover/button/switch):
strict (recommended) — always block autonomous actuation; work goes through a proposal instead.ask — operator is prompted before any actuation of a sensitive entity. Build/validate normally; both YAML apply and direct MCP calls require an explicit operator confirmation before execution.Write the chosen value to config.json as ha_safety_mode. Default to strict if the operator skips or is unsure.
Read ha_assist_control_enabled from .claude-code-hermit/config.json.
HassTurnOn, HassLightSet, etc.) pass through the safety gate and HA's expose-to-Assist setting controls which devices the agent can reach. Requires the HA MCP Server's control tools to be enabled and each entity exposed in HA (Settings → Voice assistants → Expose)."
ha_assist_control_enabled: true to config.json.Read ha_update_auto_apply from .claude-code-hermit/config.json.
ha-update-check always proposes pending Home Assistant updates for your review. With this on, accepting an add-on or HACS update installs it immediately (HA backs it up first, and rolls back on failure). Core, OS, and Supervisor updates always wait for your explicit go-ahead in chat, even with this enabled — those can affect dashboard access and have no software undo."
ha_update_auto_apply: true to config.json.Read .claude-code-hermit/knowledge-schema.md.
Check if either - analysis: or - **analysis**: is present in the file. This string only appears as the last Raw Captures bullet written — so its presence means both blocks were fully written on a prior run.
If absent, append the following block under ## Work Products (create the section header if the base schema only has a template stub):
- brief: morning/evening house brief. location: compiled/brief-<morning|evening>-<date>.md
- context: foundational house/system profile. location: compiled/context-house-profile-<date>.md
- presence-report: presence history and tracker diagnostics. location: compiled/presence-report-<date>.md
And under ## Raw Captures (create if absent):
- audit: HA operational audit (safety, integration-health, context-refresh). location: raw/audit-ha-<type>-<date>.md
- simulation: HA automation simulation result. location: raw/audit-ha-simulation-<slug>-<date>.md
- apply: HA automation apply result. location: raw/audit-ha-apply-<slug>-<date>.md
- remove: HA automation/script delete audit. location: raw/audit-ha-remove-<slug>-<date>.md
- analysis: HA pattern analysis. location: raw/patterns-<date>.md
If already present: skip (idempotent).
Use Edit to make the changes.
Run bun ${CLAUDE_PLUGIN_ROOT}/scripts/automode-env.ts .claude/settings.local.json — always .claude/settings.local.json, regardless of hatch_target: Claude Code's auto-mode classifier reads autoMode config only from local/user scope, never a committed project .claude/settings.json. This names the operator's Home Assistant instance (read from .env's HOMEASSISTANT_URL/HOMEASSISTANT_LOCAL_URL/HOMEASSISTANT_REMOTE_URL — the same set curl-host-gate.ts already trusts) as a trusted internal domain, so the classifier stops treating the hermit's nightly unattended reads (briefs, audits, context refresh) as unrecognized outbound calls. If the script prints SKIP|... (no HA URL configured yet), note it and move on — Step 2 already required a working .env before reaching here, so this should only skip on an unusual re-run. Additive and idempotent; safe to re-run on every hatch.
Write _hermit_versions["claude-code-homeassistant-hermit"] into .claude-code-hermit/config.json, set to self_version from Step 1's preflight.
Compiled templates: Copy ${CLAUDE_PLUGIN_ROOT}/state-templates/compiled/acknowledged-violations.md to .claude-code-hermit/compiled/acknowledged-violations.md if that file does not already exist. Set created in the frontmatter to today's ISO date. This gives the operator a ready-to-use suppression list for the safety audit.
Boot skill registration: Read config.boot_skill from config.json.
The skill name format is /<plugin-id>:<skill-id>. Parse the plugin-id as the text between / and :.
null or absent → set it to /claude-code-homeassistant-hermit:ha-boot./claude-code-homeassistant-hermit: → no-op (report "already set").<value> from another plugin — skipping to avoid conflict. Run /claude-code-hermit:hermit-settings boot-skill to update it manually."HA routine registration: config.routines is an array of objects with {id, schedule, skill, enabled, run_during_waiting}. For each HA routine below, check whether an entry with that id already exists in the array. If it does, skip. If not, prompt and merge it in.
Context refresh — "Add daily HA context-refresh routine (08:30 every day)? Keeps entity snapshots fresh automatically."
{"id": "daily-ha-context", "schedule": "30 8 * * *", "skill": "claude-code-homeassistant-hermit:ha-refresh-context", "enabled": true, "run_during_waiting": false}
Morning brief — three paths based on the current config.routines state:
Fresh install (no entry with id: "morning-brief" exists): prompt — "Add morning house brief routine? Delivers a unified morning summary combining house state and hermit context."
morning routine)? Recommended for always-on setups."
{"id": "morning-brief", "schedule": "30 8 * * *", "skill": "claude-code-homeassistant-hermit:ha-morning-brief", "enabled": true, "run_during_waiting": true}. If config.routines contains an entry with id: "morning" and enabled: true, set its enabled to false and emit: "Disabled core morning routine — morning-brief subsumes it."{"id": "morning-brief", "schedule": "0 9 * * *", "skill": "claude-code-homeassistant-hermit:ha-morning-brief", "enabled": false, "run_during_waiting": false}.Re-hatch upgrade (entry with id: "morning-brief" exists but has schedule: "0 9 * * *" OR run_during_waiting: false): prompt — "Your morning-brief routine uses the old schedule (09:00, not firing during waiting). Upgrade to unified mode (08:30, always-on)?"
schedule: "30 8 * * *", enabled: true, run_during_waiting: true. Then disable core morning if present and enabled (same logic as fresh install unified path).Already current (entry exists with schedule: "30 8 * * *" and run_during_waiting: true): skip (no-op, report "config is current — check enabled flag if the routine isn't firing").
Non-standard config (entry exists but matches none of the above conditions — e.g. custom schedule): skip (no-op, report "non-standard morning-brief config detected — skipping upgrade prompt").
Evening brief — "Add evening house-check routine (22:30 every day)? Delivers a brief end-of-day security and device confirmation."
{"id": "evening-brief", "schedule": "30 22 * * *", "skill": "claude-code-homeassistant-hermit:ha-evening-brief", "enabled": true, "run_during_waiting": true}. If config.routines contains an entry with id: "evening" and enabled: true, set its enabled to false and emit: "Disabled core evening routine — evening-brief subsumes it."After adding or updating any entries, remind the operator: "Run /claude-code-hermit:hermit-routines load to activate routines in the current session."
Scheduled checks registration: config.scheduled_checks is an array of periodic skill entries that the scheduled-checks routine (via reflect --scheduled-checks) invokes on a cadence and funnels through the proposal pipeline. For each entry below, check whether an existing record has the same id. If not, append it — no prompt needed, all three are safe read-only analyses.
{"id": "ha-patterns", "plugin": "claude-code-homeassistant-hermit", "skill": "claude-code-homeassistant-hermit:ha-analyze-patterns", "enabled": true, "trigger": "interval", "interval_days": 7}
{"id": "ha-safety-audit", "plugin": "claude-code-homeassistant-hermit", "skill": "claude-code-homeassistant-hermit:ha-safety-audit", "enabled": true, "trigger": "interval", "interval_days": 7}
{"id": "ha-integration-health", "plugin": "claude-code-homeassistant-hermit", "skill": "claude-code-homeassistant-hermit:ha-integration-health", "enabled": true, "trigger": "interval", "interval_days": 1}
{"id": "ha-update-check", "plugin": "claude-code-homeassistant-hermit", "skill": "claude-code-homeassistant-hermit:ha-update-check", "enabled": true, "trigger": "interval", "interval_days": 1}
These replace any need for CronCreate routines around analysis/observability — the scheduled-checks routine picks up whichever check is due, runs it, and any findings surface as proposals automatically.
Summarize:
hatch complete
✓ .env verified (user-managed)
✓ CLI: bin/ha-agent-lab boot status --probe → OK / FAILED
✓ .mcp.json: homeassistant entry written / already present
✓ CLAUDE.md updated
✓ config.json stamped v<version>
✓ boot_skill: /claude-code-homeassistant-hermit:ha-boot (set | already set | operator override preserved)
✓ Routines registered: daily-ha-context, morning-brief (disabled by default), evening-brief
✓ Scheduled checks registered: ha-patterns, ha-safety-audit, ha-integration-health, ha-update-check
✓ knowledge-schema.md: HA types added (or already present)
Manual steps remaining:
- Enable 'Model Context Protocol Server' integration in Home Assistant (if not done)
Settings → Devices & Services → Add Integration → search "MCP"
- Restart Claude Code and approve the 'homeassistant' server on first use
- Run /mcp to confirm 'homeassistant' is connected
Go always-on (recommended):
- Docker: /claude-code-hermit:docker-setup
Builds the container and walks you through channel pairing in one go.
- Bare tmux: .claude-code-hermit/bin/hermit-start
For channels (Discord/Telegram) with tmux, run
/claude-code-hermit:channel-setup first.
Prefer to test interactively first?
1. /claude-code-homeassistant-hermit:ha-boot
— single entry point: starts the hermit session, probes HA,
and auto-refreshes the context snapshot if stale/missing.
2. /claude-code-hermit:hermit-routines load
— activates scheduled routines in the current Claude session.
The always-on runtime does both of these automatically — the interactive
steps are only for a test drive before handing over to the runtime.
Ready to organize your house?
/claude-code-homeassistant-hermit:ha-setup-house
— create areas, assign entities and devices, provision helpers,
and scaffold starter automations.
Read by /claude-code-hermit:docker-security when the operator enables LAN containment + DNS policy. Each entry is surfaced as a per-entry confirmation prompt; nothing here is auto-applied.
The nabu.casa entry covers Nabu Casa Cloud (<id>.ui.nabu.casa) since dnsmasq's server=/nabu.casa/... pattern matches subdomains. home-assistant.io covers integration docs (www.home-assistant.io) and the developer API reference (developers.home-assistant.io) that skills consult when verifying REST/WebSocket endpoints. READ_FROM_ENV:HOMEASSISTANT_URL resolves to the hostname of the operator's configured HA instance — covers custom remote domains (e.g. ha.mydomain.com) that are not under nabu.casa. Operators on a self-hosted local HA instance should accept ASK_OPERATOR_FOR_HA_IP and provide the LAN IP of their HA box. mDNS / homeassistant.local does not work through dnsmasq — use the IP directly.
tools
Composes and delivers the daily fitness brief — a forward-looking morning read (readiness + today's plan) or a backward-looking evening read (today's training, or an earned-rest note, + tomorrow's setup) — in the operator's configured voice. Invoke with /claude-code-fitness-hermit:fitness-brief --morning|--evening|--slot <name>. Becomes the plugin's two daily beats — the morning Strava connectivity check and the evening activity sync, RPE binding, and Run deep-dive.
development
Renew the hermit's long-lived Claude login token over the channel, before it expires. Relays a one-time sign-in link to the operator, takes the code back, installs the new token, and restarts. Activates on messages like 'relogin', 'renew my login', 'reauth', 'the login is expiring', or when doctor's credential-expiry check flags setup-token.
development
Synthesizes the past 7 days of archived briefs into a weekly digest — top stories, emerging vs faded themes, category activity, and per-source performance built from archive frontmatter. Delivers to the operator's configured channel and archives a weekly note. Designed as a weekly routine. Invoke with /feed-hermit:weekly-digest.
development
Manage developing story arcs tracked across briefs — add, resolve, and list active arcs in compiled/story-arcs-*.md. Arc Watch keywords drive the feed-brief arc-tagging enrichment. Invoke with /feed-hermit:story-arcs add|resolve|list.