$npx -y skills add dcb/homeassistant-claude-kit --skill setup-customizeRun after setup-infrastructure to map rooms, entities, and preferences to the dashboard and automation templates. Conversational and resumable. Trigger phrases: "customize my home", "set up rooms", "configure automations", "map my entities", "set up my dashboard", "finish setup",
| 1 | # Setup Customize |
| 2 | |
| 3 | This skill maps your Home Assistant instance to the dashboard and automation templates |
| 4 | through a guided interview. It is **resumable** — if the conversation ends mid-way, |
| 5 | re-invoke this skill and it will pick up from the last checkpoint. |
| 6 | |
| 7 | See `references/question-patterns.md` for detailed question wording and example answers |
| 8 | for each domain. |
| 9 | |
| 10 | ## Step 0: Check Prerequisites |
| 11 | |
| 12 | Verify `setup-state.json` exists and infrastructure is complete: |
| 13 | |
| 14 | ```python |
| 15 | import json, sys, os |
| 16 | if not os.path.exists('setup-state.json'): |
| 17 | print('NOT_READY'); sys.exit(0) |
| 18 | with open('setup-state.json') as f: |
| 19 | state = json.load(f) |
| 20 | schema = state.get('schema_version', 0) |
| 21 | if schema > 1: |
| 22 | print('SCHEMA_WARNING') |
| 23 | phase = state.get('session', {}).get('current_phase', '') |
| 24 | infra = state.get('infrastructure', {}).get('steps_completed', []) |
| 25 | if 'infrastructure_complete' in phase or 'pull' in infra: |
| 26 | answers = state.get('answers', {}) |
| 27 | if answers.get('rooms') or phase.startswith('customize:'): |
| 28 | print('RESUME') |
| 29 | print(f'PHASE:{phase}') |
| 30 | print(f'ROOMS_DONE:{",".join(answers.get("rooms", {}).keys())}') |
| 31 | else: |
| 32 | print('FRESH') |
| 33 | else: |
| 34 | print('NOT_READY') |
| 35 | ``` |
| 36 | |
| 37 | Run via `python3 -c "..."` and check the output: |
| 38 | |
| 39 | - **`NOT_READY`**: Tell user to run `setup-infrastructure` first. |
| 40 | - **`SCHEMA_WARNING`**: State file from newer version — proceed with caution. |
| 41 | - **`RESUME`**: Load checkpoint. Tell user: "Welcome back! You were at [phase]. Rooms done: [list]. Continuing." |
| 42 | - **`FRESH`**: Begin from Phase 1. |
| 43 | |
| 44 | ### Checkpoint Writing Pattern |
| 45 | |
| 46 | After EVERY user answer, update `setup-state.json` with granular progress: |
| 47 | |
| 48 | ```python |
| 49 | import json |
| 50 | def save_checkpoint(phase, answers_update=None, files_written=None): |
| 51 | with open('setup-state.json') as f: |
| 52 | state = json.load(f) |
| 53 | state['session']['current_phase'] = phase |
| 54 | if answers_update: |
| 55 | state.setdefault('answers', {}).update(answers_update) |
| 56 | if files_written: |
| 57 | state.setdefault('files_written', []).extend(files_written) |
| 58 | with open('setup-state.json', 'w') as f: |
| 59 | json.dump(state, f, indent=2) |
| 60 | ``` |
| 61 | |
| 62 | Example calls: |
| 63 | - `save_checkpoint('customize:room_mapping', {'rooms': {'living_room': {'light': '...', 'motion': '...'}}})` |
| 64 | - `save_checkpoint('customize:domain_selection', {'domains_selected': ['lighting', 'climate']})` |
| 65 | - `save_checkpoint('customize:notifications', {'notify_targets': {'primary': 'notify.mobile_app_x'}})` |
| 66 | - `save_checkpoint('customize:files', files_written=['config/automations/lighting.yaml'])` |
| 67 | |
| 68 | ## Step 1: Discover Entity + Area + Floor Registries |
| 69 | |
| 70 | **Primary method: Use registry data.** Entity-to-room assignment should come from the |
| 71 | device/entity registries (via `area_id`) whenever possible. This is the authoritative source. |
| 72 | |
| 73 | **Fallback: Name inference + user confirmation.** If the registries have sparse area |
| 74 | assignments (common in setups where the user hasn't organized areas in HA), you may infer |
| 75 | room assignments from entity ID naming patterns (e.g., `bedroom_motion` → bedroom). |
| 76 | However, when using name inference, you MUST: |
| 77 | 1. Clearly mark inferred assignments as "inferred (not in registry)" |
| 78 | 2. Ask the user to confirm ALL inferred assignments before proceeding |
| 79 | 3. Never present inferred data as verified fact |
| 80 | |
| 81 | ### 1a. Query Floor + Area Registries |
| 82 | |
| 83 | Get the authoritative room and floor structure: |
| 84 | |
| 85 | ```bash |
| 86 | source .env && ssh "$SSH_USER@$HA_HOST" "source /etc/profile.d/claude-ha.sh; source ${HA_REMOTE_PATH:=/config/}.env; ha-ws raw config/floor_registry/list" 2>/dev/null |
| 87 | source .env && ssh "$SSH_USER@$HA_HOST" "source /etc/profile.d/claude-ha.sh; source ${HA_REMOTE_PATH}.env; ha-ws raw config/area_registry/list" 2>/dev/null |
| 88 | ``` |
| 89 | |
| 90 | This gives you: |
| 91 | - All floors with IDs and names |
| 92 | - All areas with `floor_id` assignments |
| 93 | - **Do NOT ask the user about floors if this data is available.** |
| 94 | |
| 95 | ### 1b. Query Device + Entity Registries |
| 96 | |
| 97 | Get the authoritative entity-to-area mappings: |
| 98 | |
| 99 | ```bash |
| 100 | source .env && ssh "$SSH_USER@$HA_HOST" "source /etc/profile.d/claude-ha.sh; source ${HA_REMOTE_PATH}.env; ha-ws raw config/device_registry/list" 2>/dev/null |
| 101 | source .env && ssh "$SSH_USER@$HA_HOST" "source /etc/profile.d/claude-ha.sh; source ${HA_REMOTE_PATH}.env; ha-ws raw config/entity_registry/list" 2>/dev/null |
| 102 | ``` |
| 103 | |
| 104 | **Entity-to-area resolution chain:** |
| 105 | 1. Check `entity_registry` → if the entity has a direct `area_id`, use it |
| 106 | 2. Otherwise, find the entity's `device_id` → look up that device in `device_registry` → use the device's `area_id` |
| 107 | 3. If neither has an `area_id`, the entity is unassigned — note it but do NOT guess |
| 108 | |
| 109 | ### 1c. Query Entities by Domain |
| 110 | |
| 111 | For each relevant domain, query the live en |