Bundle
dsh-smarthome
Home Assistant control for DeepSeek Harness agents — read entity states, query history, and call services behind an approval gate.
- Source
- YLifeOnlyOnce
- stars
- 6 stars
- License
- MIT
- Updated
- Updated 2 hours ago
Readme
<p align="center">
<img src="docs/assets/banner.png" alt="dsh-smarthome" width="820">
</p>
<p align="center">
<b>Home Assistant control for <a href="https://github.com/deepseek-ai/deepseek-harness">DeepSeek Harness</a> agents.</b><br>
Read entity states · query history · call services — every state-changing call sits behind a human approval gate.
</p>
<p align="center">
<a href="README.zh.md">中文</a> ·
<a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/dsh--plugin-ecosystem-4d7cfe" alt="dsh-plugin"></a> ·
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT"></a> ·
<a href="https://github.com/YLifeOnlyOnce/dsh-smarthome/actions/workflows/ci.yml"><img src="https://github.com/YLifeOnlyOnce/dsh-smarthome/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
</p>
<p align="center">Zero runtime dependencies beyond the harness itself. Uses Home Assistant's built-in REST API — no MQTT, no WebSocket, no extra daemon.</p>
---
## ✨ What it looks like
Click any image to open the live demo — [`docs/demo.html`](docs/demo.html) simulates the full DSH conversation, and its live console talks to the bundled HA emulator (no real Home Assistant needed).
| ① Ask | ② Approval gate | ③ Done — state changed |
|---|---|---|
| <img src="docs/assets/demo-start.png" width="380" alt="start"> | <img src="docs/assets/demo-approval.png" width="380" alt="approval"> | <img src="docs/assets/demo-final.png" width="380" alt="final"> |
| The agent lists your lights with `ha_list_entities`. | `ha_call_service` pauses for a human approval dialog. | Approved — `ha_get_state` confirms the light turned on. |
**And the Web UI dashboard card** — call `ha_dashboard` and get a live snapshot of the whole home rendered right in the conversation:
<p align="center"><img src="docs/assets/dashboard.png" width="420" alt="home dashboard"></p>
**The Home Assistant it plugs into** — a typical smart-home dashboard like this one (illustration):
<p align="center"><img src="docs/assets/ha-mockup.png" width="680" alt="Home Assistant dashboard illustration"></p>
## 🎯 What can it do?
Talk to your home the way you talk to an assistant — every write goes through a human approval gate first.
| You say | What happens |
|---|---|
| "Check the whole house — which devices are still on?" | Agent scans with `ha_list_entities` / `ha_get_state` and summarizes |
| "Show me the home dashboard." | `ha_dashboard` renders a **live dashboard card** in the conversation — devices, scenes and recent changes at a glance |
| "Wait until the washer finishes, then tell me on my phone." | `ha_wait_for_state` polls to completion → `ha_notify` pushes to HA |
| "What's the weather tomorrow — should I take an umbrella?" | `ha_weather` returns a structured forecast |
| "Set the bedroom light to 200 brightness." | `ha_call_service` → **approval dialog** → executes → state updates instantly |
| "Turn off every light in the living room." | **Area targeting** — one call controls the whole room |
| "Start cinema mode." | Scene activation: dimmed lights + TV on — a whole cascade of devices in one shot (`ha_events` shows each change live) |
| "What changed in the house in the last hour?" | Real-time `state_changed` events from the **WebSocket** feed |
| "Is the living room warm enough? Compare with the bedroom." | `ha_get_state` / `ha_render_template` over sensors |
| "Turn everything off, I'm leaving." | One scene (`scene.away`) or a multi-entity service call |
## 💡 Why it's good — how useful is it?
- **One-line install**: `dsh plugin --profile web add dsh-smarthome`, then just talk to the agent.
- **Safe by default**: every state-changing call stops for human approval; `allowedDomains` is a second deny-list belt. The agent can never touch your home without you saying yes.
- **Natural language control**: no apps to fiddle with, no API docs to memorize — "dim the lights" just works.
- **Always current**: state changes reach the agent in real time over WebSocket, so it never "thinks" the light is still on when you switched it off.
- **Lightweight**: zero runtime dependencies — plain REST + Node's built-in WebSocket. No MQTT broker, no extra daemon.
- **Try it without Home Assistant**: the repo ships a demo emulator + interactive demo page — 5 minutes to a full feel of the plugin.
- **Engineered, not hacked together**: 36 tests including a full **real agent-loop end-to-end** suite, strict TypeScript, CI.
## 💻 Your computer is the control center
dsh-smarthome runs right where dsh runs — **on your computer**. No phone app, no separate bridge, no context switch:
- **👀 Monitor while you work** — the dashboard card (`ha_dashboard`) and the real-time event feed (`ha_events`) keep the whole house visible next to your editor: what's on, what just changed.
- **🎙️ Control by voice** — add a community voice plugin to dsh (search the [`dsh-plugin` topic](https://github.com/topics/dsh-plugin) for *voice*; e.g. [dsh-voice](https://github.com/zhuiyueya/dsh-voice) does zero-key speech input + read-aloud, `dsh-voice-chat` does real-time speech) and just **speak**: *"turn the bedroom light to 200"* → approval → done. Hands-free while you keep typing.
- **🖥️ Everything in one window** — code, watch the house, and control devices without ever leaving dsh. Every state-changing call still waits for your approval.
## 🛠 Features
| Tool | Description | Approval |
|---|---|---|
| `ha_health` | Verify the connection; return instance name, version, timezone, WebSocket status | read |
| `ha_list_entities` | List entities, filter by domain (`light`, `switch`, `sensor`…) and text | read |
| `ha_list_areas` | List rooms (areas) via the WebSocket API, e.g. `living_room` | read |
| `ha_list_devices` | List physical devices via the WebSocket device registry | read |
| `ha_get_state` | Full state + attributes of one entity | read |
| `ha_history` | State-change timeline over a time window | read |
| `ha_events` | Recent real-time state changes buffered from the WebSocket | read |
| `ha_list_scenes` | List one-click scenes (`cinema`, `goodnight`, `away`…) | read |
| `ha_dashboard` | Full home snapshot rendered as a **dashboard card** in the Web UI | read |
| `ha_wait_for_state` | Poll until an entity matches / stops matching a state (washer done, temperature reached…) | read |
| `ha_notify` | Send a notification via Home Assistant (persistent / phone / speaker) | no gate |
| `ha_weather` | Current weather + structured forecast from the weather entity | read |
| `ha_call_service` | Call any service — by **entity**, by **area** (whole room), by **device**, or **scene** | **ask** |
| `ha_render_template` | Render a Jinja2 template server-side | **ask** |
Example prompts:
> "Check that Home Assistant is reachable, then list the lights in the living room."
>
> "Set the living room light to 60% brightness." *(triggers an approval request)*
>
> "Show me the boiler switch history for the last 24 hours."
>
> "Turn off every light in the bedroom." *(area targeting — one call, whole room)*
>
> "Start cinema mode." *(scene activation — lights dim, TV turns on)*
>
> "What changed in the house in the last hour?" *(real-time `ha_events`)*
## 📦 Install
Requires **dsh ≥ 0.1.0-rc.6** (current npm latest).
```sh
# From npm (recommended — prebuilt):
dsh plugin --profile web add dsh-smarthome
# Or from GitHub (source install — pnpm builds on the fly):
# dsh plugin --profile web add github:YLifeOnlyOnce/dsh-smarthome
# If pnpm refuses to run the prepare build on a git dependency, allow it once:
# add this to <profile>/pnpm-workspace.yaml, then re-run the add:
# allowBuilds:
# dsh-smarthome: true
```
Restart `dsh --profile web` after installing. Manage it in **Settings → Plugins**.
## 🧪 Try it without Home Assistant (demo mode)
No HA instance? The repo ships a **fake HA emulator** with a small living demo home whose state *actually changes* when you call services — perfect for trying the plugin before wiring up real hardware.
```sh
git clone https://github.com/YLifeOnlyOnce/dsh-smarthome
cd dsh-smarthome
pnpm install
pnpm demo:ha # serves a fake Home Assistant at http://127.0.0.1:8124
```
In another terminal, configure the plugin (add to your profile's `cordis.patch.yml`):
```yaml
- id: smarthome
config:
baseUrl: http://127.0.0.1:8124
tokenEnv: HOME_ASSISTANT_TOKEN
```
Then start dsh and try:
```sh
HOME_ASSISTANT_TOKEN=demo-token dsh --profile web
```
> "Check that Home Assistant is reachable, then list the lights."
>
> "Turn on the bedroom light at 200 brightness." — an approval request pops up; approve it, and `ha_get_state` will show the light is actually `on` with `brightness: 200`.
>
> "Turn off every light in the living room." — area targeting via the WebSocket area registry.
>
> "What changed in the last minute?" — real-time `state_changed` events from the WebSocket feed.
The emulator also drifts the temperature sensor every few seconds, so `ha_history` and `ha_events` always have fresh data. Any `Bearer` token works; `demo-token` is just the convention.
**Want to preview the UI without running dsh at all?** Open [`docs/demo.html`](docs/demo.html) in a browser: it replays a simulated DSH conversation (tool cards + the approval dialog), and its live console talks to the emulator directly when it's running.
Ready-to-paste configs (demo / real HA / no-approval) live in [`examples/cordis.patch.yml`](examples/cordis.patch.yml).
## ⚙️ Configuration
Create a long-lived access token in Home Assistant: **Profile → Security → Long-lived access tokens**.
Override the plugin row in your profile's `cordis.patch.yml` (later layers win):
```yaml
- id: smarthome
config:
baseUrl: http://192.168.1.10:8123 # your Home Assistant instance
token: '' # prefer tokenEnv over committing a token
tokenEnv: HOME_ASSISTANT_TOKEN # env var holding the token
timeoutMs: 15000
requireApproval: true # human approval for state-changing calls
allowedDomains: [] # e.g. ["light", "switch"]; empty = all domains
maxHistoryEvents: 200
wsEnabled: true # real-time events + area registry (WebSocket)
eventBufferSize: 50 # rolling ha_events buffer size
```
Then run dsh with the variable set:
```sh
HOME_ASSISTANT_TOKEN=<token> dsh --profile web
```
`baseUrl` defaults to `http://homeassistant.local:8123` (the standard Home Assistant mDNS host). If no token is configured the plugin still loads — every tool call fails with a clear "not configured" message instead of crashing the harness.
### How the token is resolved
`tokenEnv` is a **credential reference** resolved through the harness's [credential seam](https://github.com/deepseek-ai/deepseek-harness/tree/main/packages/credentials): when the `credentials` service is present, the value is read from its layered sources (process environment → `<cwd>/.env` → `$DSH_HOME/.env`), falling back to `process.env` directly otherwise. The token is re-resolved **per request / per socket connection**, so a rotated credential reaches the very next call without a restart.
## 🔒 Security
- A Home Assistant token can control **everything** in your instance — there is no per-entity scope. That is why `requireApproval` defaults to `true` and `ha_call_service` / `ha_render_template` always route through the harness approval seam.
- `allowedDomains` is a second belt: when set, service calls on other domains are denied outright.
- Prefer `tokenEnv` over `token` so the secret never lands in a committed config file.
## 🛠 Development
```sh
pnpm install
pnpm typecheck # strict TS against the published @deepseek-ai/* types
pnpm build # bundle lib/ (ESM + d.ts)
pnpm test # 24 tests: client suite + real ToolRuntime integration + full agent-loop E2E
node scripts/capture-demo.mjs # regenerate the README screenshots
```
## 📋 Compatibility
### Real Home Assistant compatibility
- Uses the **v1 REST API** (`/api/states`, `/api/services/…`, `/api/history/…`, `/api/template`, `/api/config`) and the **WebSocket API** (`/api/websocket`: auth, `subscribe_events`, `config/area_registry/list`, `config/device_registry/list`) — the same protocols the official HA frontend speaks.
- Requires a **long-lived access token** (Profile → Security → Long-lived access tokens).
- Caveats: self-signed HTTPS certificates are not supported (use `http://` or a valid cert); a restricted token that cannot call services will fail `ha_call_service`.
DeepSeek Harness is in developer preview and changes fast. This plugin is verified against the published `@deepseek-ai/dsh@0.1.0-rc.7` line; if a harness update breaks it, please open an issue.
## 📄 License
MIT
Install
dsh plugin --profile web add github:YLifeOnlyOnce/dsh-smarthome
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-smarthome from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.