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 Python 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.
Read .claude-code-hermit/config.json.
_hermit_versions["claude-code-hermit"] is absent or less than 1.0.16:
AskUserQuestion: "Core hermit is not initialized. Run /claude-code-hermit:hatch now?"/claude-code-hermit:hatch, then continue.Read _hermit_versions["claude-code-homeassistant-hermit"] from config.json. Read the version field from ${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json.
AskUserQuestion: "Already set up. Re-verify HA access only (skip setup wizard)?". Yes → skip to §6. No → continue.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.
Run python3 -c "import yaml, dotenv" (or .venv/bin/python -c "import yaml, dotenv" if .venv/ exists).
python3 -m venv --help. If that fails, stop and tell the user: apt install python3-venv (or OS equivalent), then re-run hatch.AskUserQuestion: "Install Python deps into a project-local .venv? Recommended — isolates from system Python and works on PEP 668 hosts." Options: venv (default) / system / skip.
venv: run python3 -m venv ${CLAUDE_PLUGIN_ROOT}/.venv then ${CLAUDE_PLUGIN_ROOT}/.venv/bin/pip install PyYAML python-dotenv. Re-probe — if still failing, stop with a diagnostic.system: run pip install --user PyYAML python-dotenv. If it errors with externally-managed-environment (PEP 668), offer to fall back to venv automatically.skip: note that §6 will probe again and will fail if deps are absent; continue anyway.CLI check: run ${CLAUDE_PLUGIN_ROOT}/bin/ha-agent-lab boot status (read-only, no --probe) to confirm the launcher and Python package resolve correctly.
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 §3). 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 python -c). 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:
.venv, pip install) without exiting or re-invoking hatch.HOMEASSISTANT_LOCAL_URL in .env.HOMEASSISTANT_TOKEN.Resolve target file: Read .claude-code-hermit/state/hatch-options.json. Use the "target" field:
"local" → target_file = CLAUDE.local.md
"committed" or absent → target_file = CLAUDE.md
If the file doesn't exist (no hatch-options.json yet — operator's core hermit predates 1.1.1): detect core_install_scope from claude plugin list --json using the same precedence rules as core hatch Step 1.5 item 2 (filter entries where plugin name is claude-code-hermit and enabled == true; precedence local > project (both require projectPath == project root) > user (any projectPath) > null; map project → committed, local/user/null → local). Ask with AskUserQuestion (header: "Visibility") — scope-derived default at position 0 with (recommended): .local files (gitignored — operator-personal) / Committed files (shared with teammates). Write the canonical 5-field schema to .claude-code-hermit/state/hatch-options.json:
{
"target": "<choice>",
"core_install_scope": "<project|local|user|null>",
"stamped_at": "<current ISO 8601 timestamp with timezone offset>",
"stamped_by": "claude-code-homeassistant-hermit:hatch",
"version": "<current ha-hermit plugin version from ${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json>"
}
Read the plugin version from ${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json and the stamped version from .claude-code-hermit/config.json at _hermit_versions["claude-code-homeassistant-hermit"] (treat absent as null). Step 8 of this skill stamps that field at the end of every run, so on re-runs it reflects the version that last wrote the block. Look for the marker <!-- claude-code-homeassistant-hermit: Home Assistant Workflow --> in target_file:
${CLAUDE_PLUGIN_ROOT}/state-templates/CLAUDE-APPEND.md and either append it to target_file (marker absent — the Edit tool creates target_file if missing) or replace the marked block (marker present) — everything from the opening <!-- claude-code-homeassistant-hermit: Home Assistant Workflow --> through the matching closing <!-- /claude-code-homeassistant-hermit: Home Assistant Workflow -->, inclusive. The template is the source of truth; no operator prompt is needed.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 .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.
Write _hermit_versions["claude-code-homeassistant-hermit"] into .claude-code-hermit/config.json with the current plugin version.
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}
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)
✓ Python deps: <venv at .venv/ | system python> → OK / FAILED
✓ Python 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
✓ 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.
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
Presence history & tracker-health report — current home/away state, reliability, recent arrival/departure transitions, and activity patterns for person/device_tracker entities. Use when the operator asks about presence history or when a presence-dependent automation (locks, alarm, vacuum, climate) misbehaves.
development
Evening house brief — end-of-day security check, device status, and energy snapshot. Runs as a daily routine at 22:30 or on demand.
tools
Browse and explain the hermit's Home Assistant automations — list by topic, filter by keyword with plain-language YAML explanations, or sort by last-fired. Read-only. Use when the operator asks "what automations do I have / what does this one do / which haven't fired."
tools
On-demand HA-voice brainstorm — reads entity inventory, automation/script listings, and operator intent to surface at most 2 capability-gap ideas, each gated by proposal-triage before becoming a PROP. Invoke when the operator asks "what automations am I missing?", "any coverage gaps?", or "brainstorm improvements". Never runs autonomously.