domesti-bot 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- domesti_bot-0.1.0/.gitignore +29 -0
- domesti_bot-0.1.0/LICENSE +21 -0
- domesti_bot-0.1.0/PKG-INFO +329 -0
- domesti_bot-0.1.0/README.md +286 -0
- domesti_bot-0.1.0/app/__init__.py +1 -0
- domesti_bot-0.1.0/app/_build_metadata.py +6 -0
- domesti_bot-0.1.0/app/androidtv_device_manager.py +703 -0
- domesti_bot-0.1.0/app/api/__init__.py +1 -0
- domesti_bot-0.1.0/app/api/app.py +629 -0
- domesti_bot-0.1.0/app/api/schemas.py +268 -0
- domesti_bot-0.1.0/app/api/settings_routes.py +170 -0
- domesti_bot-0.1.0/app/api/static/.gitkeep +0 -0
- domesti_bot-0.1.0/app/api/static/compact-layout-prototype.html +550 -0
- domesti_bot-0.1.0/app/api/static/dist/main.js +2 -0
- domesti_bot-0.1.0/app/api/static/dist/main.js.map +7 -0
- domesti_bot-0.1.0/app/api/static/icons/app-icon-192x192.png +0 -0
- domesti_bot-0.1.0/app/api/static/icons/app-icon-512x512.png +0 -0
- domesti_bot-0.1.0/app/api/static/icons/app-icon.svg +23 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/bulb.svg +6 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/desk.svg +8 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/fan.svg +12 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/garage_closed.svg +10 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/garage_open.svg +5 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/lamp.svg +7 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/lantern.svg +7 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/led.svg +8 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/light.svg +12 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/outlet.svg +6 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/pendant.svg +11 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/plug.svg +6 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/room_attic.svg +7 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/room_basement.svg +7 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/room_bathroom.svg +7 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/room_bedroom.svg +7 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/room_dining.svg +7 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/room_garage.svg +6 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/room_guest.svg +7 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/room_hall.svg +7 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/room_kitchen.svg +6 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/room_laundry.svg +7 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/room_living.svg +8 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/room_office.svg +10 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/room_porch.svg +7 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/speaker.svg +6 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/speaker_paused.svg +8 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/speaker_playing.svg +7 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/speaker_unknown.svg +8 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/strip.svg +7 -0
- domesti_bot-0.1.0/app/api/static/icons/compact/table.svg +8 -0
- domesti_bot-0.1.0/app/api/static/index.html +696 -0
- domesti_bot-0.1.0/app/api/static/manifest.webmanifest +24 -0
- domesti_bot-0.1.0/app/api/static/sw.js +74 -0
- domesti_bot-0.1.0/app/api/ui_state.py +644 -0
- domesti_bot-0.1.0/app/build_info.py +91 -0
- domesti_bot-0.1.0/app/db/__init__.py +37 -0
- domesti_bot-0.1.0/app/db/base.py +9 -0
- domesti_bot-0.1.0/app/db/engine.py +40 -0
- domesti_bot-0.1.0/app/db/legacy_migrations.py +37 -0
- domesti_bot-0.1.0/app/db/models.py +71 -0
- domesti_bot-0.1.0/app/db/schema.py +27 -0
- domesti_bot-0.1.0/app/db/secrets.py +125 -0
- domesti_bot-0.1.0/app/db/secrets_key.py +96 -0
- domesti_bot-0.1.0/app/db/session.py +25 -0
- domesti_bot-0.1.0/app/device_manager.py +109 -0
- domesti_bot-0.1.0/app/device_state_watcher.py +279 -0
- domesti_bot-0.1.0/app/domesti_bot_cli.py +2516 -0
- domesti_bot-0.1.0/app/gotailwind_device_manager.py +375 -0
- domesti_bot-0.1.0/app/kasa_device_manager.py +700 -0
- domesti_bot-0.1.0/app/kasa_discovery_store.py +379 -0
- domesti_bot-0.1.0/app/logging_config.py +221 -0
- domesti_bot-0.1.0/app/rule_engine.py +276 -0
- domesti_bot-0.1.0/app/sonos_device_manager.py +393 -0
- domesti_bot-0.1.0/app/tailwind_credentials.py +30 -0
- domesti_bot-0.1.0/app/ui_compact_icon.py +173 -0
- domesti_bot-0.1.0/config/__init__.py +1 -0
- domesti_bot-0.1.0/config/serve.py +367 -0
- domesti_bot-0.1.0/pyproject.toml +103 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
.idea/
|
|
3
|
+
.vscode/
|
|
4
|
+
|
|
5
|
+
# Local git worktrees; never commit as nested repo entries.
|
|
6
|
+
.worktrees/
|
|
7
|
+
|
|
8
|
+
*.sqlite
|
|
9
|
+
*.sqlite-journal
|
|
10
|
+
__pycache__/
|
|
11
|
+
*.py[cod]
|
|
12
|
+
.pytest_cache/
|
|
13
|
+
.mypy_cache/
|
|
14
|
+
.ruff_cache/
|
|
15
|
+
.venv/
|
|
16
|
+
|
|
17
|
+
# Runtime artifacts that should never be committed.
|
|
18
|
+
logs/
|
|
19
|
+
*.log
|
|
20
|
+
|
|
21
|
+
# Fernet master key for encrypted SQLite secrets (see domesti-secrets.json.example).
|
|
22
|
+
domesti-secrets.json
|
|
23
|
+
|
|
24
|
+
# Web bundle: source lives under web/, built output under app/api/static/dist/.
|
|
25
|
+
# The dist/ directory is rebuilt by `pnpm run build` (locally, in CI, and via
|
|
26
|
+
# scripts/on-deploy in production); it must not be committed.
|
|
27
|
+
web/node_modules/
|
|
28
|
+
.pnpm-store/
|
|
29
|
+
app/api/static/dist/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Henrique Andrade
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: domesti-bot
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Self-hosted LAN control surface for TP-Link Kasa, Sonos, and GoTailwind devices, with a tile-based web UI.
|
|
5
|
+
Project-URL: Homepage, https://github.com/the-hcma/domesti-bot
|
|
6
|
+
Project-URL: Repository, https://github.com/the-hcma/domesti-bot
|
|
7
|
+
Project-URL: Issues, https://github.com/the-hcma/domesti-bot/issues
|
|
8
|
+
Author-email: Henrique Andrade <thehcma@users.noreply.github.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: domesti-bot,fastapi,home-automation,kasa,sonos,tailwind
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Environment :: Web Environment
|
|
15
|
+
Classifier: Framework :: FastAPI
|
|
16
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Topic :: Home Automation
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Requires-Dist: click==8.4.0
|
|
27
|
+
Requires-Dist: cryptography>=48.0.0
|
|
28
|
+
Requires-Dist: fastapi>=0.136.1
|
|
29
|
+
Requires-Dist: gotailwind[cli]
|
|
30
|
+
Requires-Dist: httpx>=0.28.1
|
|
31
|
+
Requires-Dist: idna==3.15
|
|
32
|
+
Requires-Dist: prompt-toolkit>=3.0.43
|
|
33
|
+
Requires-Dist: pychromecast>=14.0.10
|
|
34
|
+
Requires-Dist: pyproj
|
|
35
|
+
Requires-Dist: python-kasa
|
|
36
|
+
Requires-Dist: requests>=2.34.2
|
|
37
|
+
Requires-Dist: soco>=0.31.0
|
|
38
|
+
Requires-Dist: sqlalchemy>=2.0.49
|
|
39
|
+
Requires-Dist: uvicorn[standard]>=0.47.0
|
|
40
|
+
Requires-Dist: watchfiles==1.2.0
|
|
41
|
+
Requires-Dist: zeroconf>=0.149.7
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
# domesti-bot
|
|
45
|
+
|
|
46
|
+
[](https://github.com/the-hcma/domesti-bot/actions/workflows/ci.yml)
|
|
47
|
+
[](https://www.python.org/)
|
|
48
|
+
[](LICENSE)
|
|
49
|
+
|
|
50
|
+
A self-hosted home-automation control surface for the devices on your home
|
|
51
|
+
network. `domesti-bot` discovers and controls TP-Link Kasa smart plugs/lights,
|
|
52
|
+
Sonos zones, and GoTailwind garage-door controllers, then exposes them through
|
|
53
|
+
a small tile-based web UI for one-tap control from any phone or laptop on the
|
|
54
|
+
same LAN.
|
|
55
|
+
|
|
56
|
+
The project is intentionally narrow in scope: no cloud round-trips, no
|
|
57
|
+
external accounts beyond the ones each device family already requires, and no
|
|
58
|
+
heavyweight rules engine. Everything runs on a single machine inside the
|
|
59
|
+
network the devices are on — typically the same Linux server that already
|
|
60
|
+
hosts other always-on services.
|
|
61
|
+
|
|
62
|
+
## Features
|
|
63
|
+
|
|
64
|
+
- **TP-Link Kasa / Tapo (`python-kasa`)** — auto-discovery, on/off toggle per
|
|
65
|
+
device, per-family "Turn off all", with sticky exclusions for devices you
|
|
66
|
+
don't want bulk-actions to touch. Handles newer KLAP-encrypted devices via
|
|
67
|
+
an interactive `kasa-creds` REPL command.
|
|
68
|
+
- **Sonos (`soco`)** — zone discovery, per-zone pause/resume, per-family
|
|
69
|
+
"Pause all". Gracefully handles UPnP 701 ("Transition not available", e.g.
|
|
70
|
+
empty queue) with a surfaced action-error toast in the UI.
|
|
71
|
+
- **GoTailwind garage doors** — open/close per door, "Close all", and
|
|
72
|
+
idempotent operations so a "Close everything" bulk action survives doors
|
|
73
|
+
that are already closed. The Tailwind Local Control Key can be stored
|
|
74
|
+
encrypted in the discovery SQLite database (see [Encrypted secrets](#encrypted-secrets)).
|
|
75
|
+
- **Encrypted secrets** — Fernet-encrypted values in SQLite (Tailwind token
|
|
76
|
+
today); master key in gitignored `domesti-secrets.json` at the repo root.
|
|
77
|
+
Create it with the `setup-secrets` REPL command or copy
|
|
78
|
+
`domesti-secrets.json.example`.
|
|
79
|
+
- **Web UI** (`/`) — tile-based control, family-color frames, optimistic UI
|
|
80
|
+
updates with an 8-second grace window, backend-connectivity status, mobile
|
|
81
|
+
viewport support, and standardized colour rules (green active, red per-tile
|
|
82
|
+
off, orange bulk actions). Talks to a stable, OpenAPI-typed HTTP surface under `/v1/…`.
|
|
83
|
+
- **REPL CLI** (`scripts/domesti-bot`) — same discovery / control surface
|
|
84
|
+
exposed as an interactive `prompt_toolkit` shell for scripting and
|
|
85
|
+
troubleshooting, including `setup-secrets` to create `domesti-secrets.json`.
|
|
86
|
+
- **Continuous state monitoring** — background pollers keep the UI's view of
|
|
87
|
+
Kasa, Sonos, and Tailwind state in sync without manual refresh.
|
|
88
|
+
|
|
89
|
+
## Quick start
|
|
90
|
+
|
|
91
|
+
Requires **Python ≥ 3.11** (3.14 is the targeted runtime).
|
|
92
|
+
|
|
93
|
+
### Install from PyPI (recommended)
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pipx install domesti-bot
|
|
97
|
+
domesti-bot-server # HTTP API + web UI on a free loopback port
|
|
98
|
+
domesti-bot-server --listen-all # LAN-visible bind for phone / tablet testing
|
|
99
|
+
domesti-bot # interactive REPL for troubleshooting
|
|
100
|
+
domesti-bot --version # package version and source commit
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Set `DOMESTI_API_KEY` when binding to the LAN or any network you do not fully
|
|
104
|
+
trust. See [Configuration](#configuration) below.
|
|
105
|
+
|
|
106
|
+
PyPI releases are built with the web bundle included; no Node.js is required at
|
|
107
|
+
runtime. See [`docs/RELEASING.md`](docs/RELEASING.md) for how maintainers publish.
|
|
108
|
+
|
|
109
|
+
### Develop from a git checkout
|
|
110
|
+
|
|
111
|
+
Uses [`uv`](https://docs.astral.sh/uv/) for dependency management.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
git clone https://github.com/the-hcma/domesti-bot.git
|
|
115
|
+
cd domesti-bot
|
|
116
|
+
uv sync --group dev
|
|
117
|
+
cd web && pnpm install --frozen-lockfile && pnpm run build && cd ..
|
|
118
|
+
|
|
119
|
+
# Start the HTTP server (binds 127.0.0.1 on a free port; auto-opens browser)
|
|
120
|
+
./scripts/domesti-bot-server
|
|
121
|
+
|
|
122
|
+
# Or expose to the LAN so you can validate the UI from a phone
|
|
123
|
+
./scripts/domesti-bot-server --listen-all
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The startup banner prints the URL the server is listening on, including one
|
|
127
|
+
`[http] network: http://<lan-ip>:<port>` line per non-loopback interface when
|
|
128
|
+
`--listen-all` is passed.
|
|
129
|
+
|
|
130
|
+
Need the device-control REPL instead of the HTTP API? Run
|
|
131
|
+
`./scripts/domesti-bot` and follow the prompts.
|
|
132
|
+
|
|
133
|
+
## Configuration
|
|
134
|
+
|
|
135
|
+
Most operation is zero-config — devices are discovered on the LAN via mDNS /
|
|
136
|
+
broadcast probes, and discovered configurations are persisted in an SQLite
|
|
137
|
+
cache (`~/.config/domesti-bot/kasa_discovery.sqlite3` by default) so subsequent
|
|
138
|
+
startups are fast.
|
|
139
|
+
|
|
140
|
+
Optional environment variables:
|
|
141
|
+
|
|
142
|
+
| Variable | Effect |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `DOMESTI_API_KEY` | When set, every `/v1/…` endpoint requires the `X-Domesti-Api-Key` header. Unset = unauthenticated (intended for trusted LAN only). |
|
|
145
|
+
| `DOMESTI_LISTEN_HOST` | Default bind address for the HTTP server. Overridden by `--listen-host` / `--listen-all`. |
|
|
146
|
+
| `DOMESTI_LISTEN_PORT` | Default TCP port. `0` = OS-allocated (the dev default). |
|
|
147
|
+
| `KASA_USERNAME` / `KASA_PASSWORD` | TP-Link cloud credentials for KLAP-encrypted devices (Tapo / newer Kasa). Required only if you have at least one such device. |
|
|
148
|
+
| `TAILWIND_TOKEN` | GoTailwind Local Control Key (six-digit code from the Tailwind dashboard). Overrides the encrypted DB copy when set. |
|
|
149
|
+
| `DOMESTI_SECRETS_KEY` | Fernet master key for encrypted SQLite secrets. Overrides `domesti-secrets.json` when set. |
|
|
150
|
+
| `DOMESTI_SECRETS_FILE` | Override path to the secrets JSON file (default: `./domesti-secrets.json` at repo root). |
|
|
151
|
+
|
|
152
|
+
Pass `--help` to either script for the complete flag list.
|
|
153
|
+
|
|
154
|
+
## Encrypted secrets
|
|
155
|
+
|
|
156
|
+
Discovery state (device configs, display names, UI preferences, cached Tailwind
|
|
157
|
+
host, and similar) lives in a single SQLite file. **Upgrading domesti-bot does
|
|
158
|
+
not wipe that file** — existing rows keep working; new tables (such as
|
|
159
|
+
`app_secrets` for encrypted values) are added automatically on first access.
|
|
160
|
+
|
|
161
|
+
To encrypt secrets at rest (for example the Tailwind token saved from the web
|
|
162
|
+
UI), configure a Fernet master key:
|
|
163
|
+
|
|
164
|
+
1. Copy the template and generate a key (or use the REPL helper):
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
cp domesti-secrets.json.example domesti-secrets.json
|
|
168
|
+
# in the REPL: setup-secrets
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`setup-secrets` can generate a new key or accept an existing one, writes
|
|
172
|
+
`domesti-secrets.json` with mode `0600`, and reminds you to restart the
|
|
173
|
+
server. The file is listed in `.gitignore` — never commit it.
|
|
174
|
+
|
|
175
|
+
2. Restart `domesti-bot-server` so the process reads the file.
|
|
176
|
+
|
|
177
|
+
3. On **desktop** browsers, open the **☰** menu → **Settings** and paste the
|
|
178
|
+
six-digit Tailwind Local Control Key. It is stored encrypted in SQLite and
|
|
179
|
+
is never shown again. Restart once more so discovery picks up the token.
|
|
180
|
+
|
|
181
|
+
**Precedence for the Tailwind token:** `--tailwind-token` → `TAILWIND_TOKEN`
|
|
182
|
+
env → encrypted row in SQLite. **Precedence for the Fernet key:**
|
|
183
|
+
`DOMESTI_SECRETS_KEY` env → `domesti_secrets_key` in `domesti-secrets.json`.
|
|
184
|
+
|
|
185
|
+
For systemd, you can still use `EnvironmentFile=` for `TAILWIND_TOKEN` instead
|
|
186
|
+
of the database path; see [`docs/AGENTS.md`](docs/AGENTS.md) for security notes.
|
|
187
|
+
|
|
188
|
+
## Web UI overview
|
|
189
|
+
|
|
190
|
+
After starting the server, the landing page hydrates a tile UI:
|
|
191
|
+
|
|
192
|
+
- One section per device family (`Lights & plugs`, `Sonos zones`,
|
|
193
|
+
`Garage doors`) with a family-coloured icon and frame.
|
|
194
|
+
- One tile per device. Tap to toggle (on/off, play/pause, open/close); the
|
|
195
|
+
tile updates optimistically and reconciles with the next background poll
|
|
196
|
+
(every 5 seconds).
|
|
197
|
+
- Per-family bulk button (`Turn off all`, `Pause all`, `Close all`) and a
|
|
198
|
+
global `Turn off / pause / close everything` button at the top (warm orange,
|
|
199
|
+
distinct from red per-tile off controls).
|
|
200
|
+
- On **desktop** viewports, a **☰** menu with **Settings** (Tailwind token).
|
|
201
|
+
The menu is hidden on mobile form factors.
|
|
202
|
+
- Per-tile "Exclude from all-off" (and analogous) checkbox so the top-of-page
|
|
203
|
+
bulk action skips devices you don't want it touching.
|
|
204
|
+
- Connectivity indicator: family frames turn red when the backend is
|
|
205
|
+
unreachable; all controls grey out until the next poll succeeds.
|
|
206
|
+
|
|
207
|
+
## Progressive Web App (PWA)
|
|
208
|
+
|
|
209
|
+
The landing page is installable as a PWA on phones and desktops that support
|
|
210
|
+
it. Assets live under `app/api/static/`:
|
|
211
|
+
|
|
212
|
+
- `manifest.webmanifest` — name, icons, `display: standalone`
|
|
213
|
+
- `sw.js` — service worker (also served at `GET /sw.js` so scope covers `/`)
|
|
214
|
+
- `icons/` — launcher icons referenced by the manifest
|
|
215
|
+
|
|
216
|
+
The TypeScript bundle registers the worker on load. After you deploy a new
|
|
217
|
+
version, the service worker cache version in `sw.js` (for example
|
|
218
|
+
`domesti-bot-pwa-v15`) must be bumped so installed clients pick up HTML, CSS,
|
|
219
|
+
and `dist/main.js` changes.
|
|
220
|
+
|
|
221
|
+
**Install requirements:** Chromium-based browsers need a secure context
|
|
222
|
+
(`https://` or `http://127.0.0.1`). On a plain HTTP LAN URL, you still get
|
|
223
|
+
manifest metadata in some browsers, but the install prompt may not appear until
|
|
224
|
+
you terminate TLS or use loopback. When the server is reachable with
|
|
225
|
+
`--listen-all`, open the dashboard from your phone at
|
|
226
|
+
`http://<server-lan-ip>:<port>/` and use the in-app install banner when
|
|
227
|
+
offered.
|
|
228
|
+
|
|
229
|
+
## Project layout
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
domesti-bot/
|
|
233
|
+
├── app/ Domain code (device managers, rule engine)
|
|
234
|
+
│ ├── *_device_manager.py One per family (kasa, sonos, gotailwind, …)
|
|
235
|
+
│ ├── db/ SQLAlchemy models + encrypted secrets
|
|
236
|
+
│ ├── kasa_discovery_store.py SQLite cache facade (shared by all managers)
|
|
237
|
+
│ └── api/ FastAPI HTTP surface (subpackage)
|
|
238
|
+
├── config/serve.py uvicorn entrypoint
|
|
239
|
+
├── tests/python/ pytest suite (hermetic + LAN-integration)
|
|
240
|
+
├── web/src/ TypeScript source for the tile UI
|
|
241
|
+
├── scripts/domesti-bot REPL CLI
|
|
242
|
+
├── scripts/domesti-bot-server HTTP server launcher
|
|
243
|
+
├── production/ systemd unit template + on-deploy hooks
|
|
244
|
+
└── docs/AGENTS.md Developer reference (canonical)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`AGENTS.md` at the root is a symlink to `docs/AGENTS.md` — both paths point
|
|
248
|
+
to the same canonical developer reference.
|
|
249
|
+
|
|
250
|
+
## Development
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
# One-time setup
|
|
254
|
+
uv sync
|
|
255
|
+
|
|
256
|
+
# The full set of CI gates, in the order they run on every PR:
|
|
257
|
+
uv run pyright # type errors over app/, config/, scripts/, tests/
|
|
258
|
+
uv run pytest -m "not integration" -n auto # hermetic (parallel; matches CI)
|
|
259
|
+
shellcheck $(git ls-files scripts production/scripts | grep -Ev '\.(py|md|txt|yml|yaml|json|toml)$')
|
|
260
|
+
|
|
261
|
+
# Frontend, when web/src/ is touched:
|
|
262
|
+
cd web
|
|
263
|
+
pnpm install --frozen-lockfile
|
|
264
|
+
pnpm run typecheck
|
|
265
|
+
pnpm run build # writes app/api/static/dist/main.js
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The full set of code-style, testing, security, and Git workflow conventions is
|
|
269
|
+
documented in [`docs/AGENTS.md`](docs/AGENTS.md). Notable rules:
|
|
270
|
+
|
|
271
|
+
- Python 3.14 targeted, modern typing only (`list[str]`, not `List[str]`),
|
|
272
|
+
every public function annotated, `pyright` enforced.
|
|
273
|
+
- `uv` for dependency management — never `pip` directly.
|
|
274
|
+
- Methods and module-level functions sorted alphabetically inside each class.
|
|
275
|
+
- Sigs require `from __future__ import annotations`.
|
|
276
|
+
- All commits via Graphite-stacked PRs; `main` is protected, direct pushes
|
|
277
|
+
are blocked at the server.
|
|
278
|
+
- Conventional Commit messages, GPG-signed.
|
|
279
|
+
|
|
280
|
+
## Production deployment
|
|
281
|
+
|
|
282
|
+
The production target is a **systemd user unit**. The template at
|
|
283
|
+
[`etc/systemd/domesti-bot.service`](etc/systemd/domesti-bot.service) is what
|
|
284
|
+
[`repository-helpers`](https://github.com/the-hcma/repository-helpers)
|
|
285
|
+
`setup-service` installs (same `@@REPO_DIR@@` contract as fpdf). It passes
|
|
286
|
+
`--listen-all --listen-port 8003` so the API listens on all interfaces (use
|
|
287
|
+
`DOMESTI_API_KEY` on untrusted LANs). `ExecStartPost` curls `GET /health` on
|
|
288
|
+
loopback until the process answers. The deploy hook [`scripts/on-deploy`](scripts/on-deploy)
|
|
289
|
+
runs `uv sync`, rebuilds the web bundle when needed, and lets `setup-service`
|
|
290
|
+
restart the unit. For a **system**-level unit with a dedicated user, see
|
|
291
|
+
[`production/systemd/domesti-bot-server.service.template`](production/systemd/domesti-bot-server.service.template).
|
|
292
|
+
|
|
293
|
+
`docs/AGENTS.md` has the deployment-specific details — auth keys, log paths,
|
|
294
|
+
service management commands.
|
|
295
|
+
|
|
296
|
+
## Contributing
|
|
297
|
+
|
|
298
|
+
**Contributions are welcome and appreciated.** Issues, bug reports, feature
|
|
299
|
+
requests, and PRs are all on the table — whether you've spotted a typo,
|
|
300
|
+
hit an edge case with your specific Kasa/Sonos/Tailwind hardware, or want to
|
|
301
|
+
add a brand-new device family, the door is open.
|
|
302
|
+
|
|
303
|
+
The project uses [Graphite](https://graphite.dev) for stacked PRs. The
|
|
304
|
+
practical workflow is:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
# 1. Start a stack from main
|
|
308
|
+
gt create feat/your-idea
|
|
309
|
+
|
|
310
|
+
# 2. Make the change, run the local gates (pyright + pytest, see Development)
|
|
311
|
+
# Each gate is also enforced in CI.
|
|
312
|
+
|
|
313
|
+
# 3. Commit + open PR
|
|
314
|
+
gt create --all --message "feat: short description"
|
|
315
|
+
gt submit --no-interactive --publish
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
For larger changes, stack the work into focused PRs so each one is
|
|
319
|
+
independently reviewable. The [stack of PRs](https://github.com/the-hcma/domesti-bot/pulls)
|
|
320
|
+
visible on this repo is itself an example of the pattern.
|
|
321
|
+
|
|
322
|
+
The full Git / commit / PR conventions, including the merge-it label flow and
|
|
323
|
+
the protected-`main` ruleset, live in [`docs/AGENTS.md`](docs/AGENTS.md) under
|
|
324
|
+
the *Commits, Stacking & Pull Requests* section.
|
|
325
|
+
|
|
326
|
+
## License
|
|
327
|
+
|
|
328
|
+
MIT License — see [LICENSE](LICENSE) for the full text. Copyright (c) 2026
|
|
329
|
+
Henrique Andrade.
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# domesti-bot
|
|
2
|
+
|
|
3
|
+
[](https://github.com/the-hcma/domesti-bot/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.python.org/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
A self-hosted home-automation control surface for the devices on your home
|
|
8
|
+
network. `domesti-bot` discovers and controls TP-Link Kasa smart plugs/lights,
|
|
9
|
+
Sonos zones, and GoTailwind garage-door controllers, then exposes them through
|
|
10
|
+
a small tile-based web UI for one-tap control from any phone or laptop on the
|
|
11
|
+
same LAN.
|
|
12
|
+
|
|
13
|
+
The project is intentionally narrow in scope: no cloud round-trips, no
|
|
14
|
+
external accounts beyond the ones each device family already requires, and no
|
|
15
|
+
heavyweight rules engine. Everything runs on a single machine inside the
|
|
16
|
+
network the devices are on — typically the same Linux server that already
|
|
17
|
+
hosts other always-on services.
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **TP-Link Kasa / Tapo (`python-kasa`)** — auto-discovery, on/off toggle per
|
|
22
|
+
device, per-family "Turn off all", with sticky exclusions for devices you
|
|
23
|
+
don't want bulk-actions to touch. Handles newer KLAP-encrypted devices via
|
|
24
|
+
an interactive `kasa-creds` REPL command.
|
|
25
|
+
- **Sonos (`soco`)** — zone discovery, per-zone pause/resume, per-family
|
|
26
|
+
"Pause all". Gracefully handles UPnP 701 ("Transition not available", e.g.
|
|
27
|
+
empty queue) with a surfaced action-error toast in the UI.
|
|
28
|
+
- **GoTailwind garage doors** — open/close per door, "Close all", and
|
|
29
|
+
idempotent operations so a "Close everything" bulk action survives doors
|
|
30
|
+
that are already closed. The Tailwind Local Control Key can be stored
|
|
31
|
+
encrypted in the discovery SQLite database (see [Encrypted secrets](#encrypted-secrets)).
|
|
32
|
+
- **Encrypted secrets** — Fernet-encrypted values in SQLite (Tailwind token
|
|
33
|
+
today); master key in gitignored `domesti-secrets.json` at the repo root.
|
|
34
|
+
Create it with the `setup-secrets` REPL command or copy
|
|
35
|
+
`domesti-secrets.json.example`.
|
|
36
|
+
- **Web UI** (`/`) — tile-based control, family-color frames, optimistic UI
|
|
37
|
+
updates with an 8-second grace window, backend-connectivity status, mobile
|
|
38
|
+
viewport support, and standardized colour rules (green active, red per-tile
|
|
39
|
+
off, orange bulk actions). Talks to a stable, OpenAPI-typed HTTP surface under `/v1/…`.
|
|
40
|
+
- **REPL CLI** (`scripts/domesti-bot`) — same discovery / control surface
|
|
41
|
+
exposed as an interactive `prompt_toolkit` shell for scripting and
|
|
42
|
+
troubleshooting, including `setup-secrets` to create `domesti-secrets.json`.
|
|
43
|
+
- **Continuous state monitoring** — background pollers keep the UI's view of
|
|
44
|
+
Kasa, Sonos, and Tailwind state in sync without manual refresh.
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
Requires **Python ≥ 3.11** (3.14 is the targeted runtime).
|
|
49
|
+
|
|
50
|
+
### Install from PyPI (recommended)
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pipx install domesti-bot
|
|
54
|
+
domesti-bot-server # HTTP API + web UI on a free loopback port
|
|
55
|
+
domesti-bot-server --listen-all # LAN-visible bind for phone / tablet testing
|
|
56
|
+
domesti-bot # interactive REPL for troubleshooting
|
|
57
|
+
domesti-bot --version # package version and source commit
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Set `DOMESTI_API_KEY` when binding to the LAN or any network you do not fully
|
|
61
|
+
trust. See [Configuration](#configuration) below.
|
|
62
|
+
|
|
63
|
+
PyPI releases are built with the web bundle included; no Node.js is required at
|
|
64
|
+
runtime. See [`docs/RELEASING.md`](docs/RELEASING.md) for how maintainers publish.
|
|
65
|
+
|
|
66
|
+
### Develop from a git checkout
|
|
67
|
+
|
|
68
|
+
Uses [`uv`](https://docs.astral.sh/uv/) for dependency management.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
git clone https://github.com/the-hcma/domesti-bot.git
|
|
72
|
+
cd domesti-bot
|
|
73
|
+
uv sync --group dev
|
|
74
|
+
cd web && pnpm install --frozen-lockfile && pnpm run build && cd ..
|
|
75
|
+
|
|
76
|
+
# Start the HTTP server (binds 127.0.0.1 on a free port; auto-opens browser)
|
|
77
|
+
./scripts/domesti-bot-server
|
|
78
|
+
|
|
79
|
+
# Or expose to the LAN so you can validate the UI from a phone
|
|
80
|
+
./scripts/domesti-bot-server --listen-all
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The startup banner prints the URL the server is listening on, including one
|
|
84
|
+
`[http] network: http://<lan-ip>:<port>` line per non-loopback interface when
|
|
85
|
+
`--listen-all` is passed.
|
|
86
|
+
|
|
87
|
+
Need the device-control REPL instead of the HTTP API? Run
|
|
88
|
+
`./scripts/domesti-bot` and follow the prompts.
|
|
89
|
+
|
|
90
|
+
## Configuration
|
|
91
|
+
|
|
92
|
+
Most operation is zero-config — devices are discovered on the LAN via mDNS /
|
|
93
|
+
broadcast probes, and discovered configurations are persisted in an SQLite
|
|
94
|
+
cache (`~/.config/domesti-bot/kasa_discovery.sqlite3` by default) so subsequent
|
|
95
|
+
startups are fast.
|
|
96
|
+
|
|
97
|
+
Optional environment variables:
|
|
98
|
+
|
|
99
|
+
| Variable | Effect |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `DOMESTI_API_KEY` | When set, every `/v1/…` endpoint requires the `X-Domesti-Api-Key` header. Unset = unauthenticated (intended for trusted LAN only). |
|
|
102
|
+
| `DOMESTI_LISTEN_HOST` | Default bind address for the HTTP server. Overridden by `--listen-host` / `--listen-all`. |
|
|
103
|
+
| `DOMESTI_LISTEN_PORT` | Default TCP port. `0` = OS-allocated (the dev default). |
|
|
104
|
+
| `KASA_USERNAME` / `KASA_PASSWORD` | TP-Link cloud credentials for KLAP-encrypted devices (Tapo / newer Kasa). Required only if you have at least one such device. |
|
|
105
|
+
| `TAILWIND_TOKEN` | GoTailwind Local Control Key (six-digit code from the Tailwind dashboard). Overrides the encrypted DB copy when set. |
|
|
106
|
+
| `DOMESTI_SECRETS_KEY` | Fernet master key for encrypted SQLite secrets. Overrides `domesti-secrets.json` when set. |
|
|
107
|
+
| `DOMESTI_SECRETS_FILE` | Override path to the secrets JSON file (default: `./domesti-secrets.json` at repo root). |
|
|
108
|
+
|
|
109
|
+
Pass `--help` to either script for the complete flag list.
|
|
110
|
+
|
|
111
|
+
## Encrypted secrets
|
|
112
|
+
|
|
113
|
+
Discovery state (device configs, display names, UI preferences, cached Tailwind
|
|
114
|
+
host, and similar) lives in a single SQLite file. **Upgrading domesti-bot does
|
|
115
|
+
not wipe that file** — existing rows keep working; new tables (such as
|
|
116
|
+
`app_secrets` for encrypted values) are added automatically on first access.
|
|
117
|
+
|
|
118
|
+
To encrypt secrets at rest (for example the Tailwind token saved from the web
|
|
119
|
+
UI), configure a Fernet master key:
|
|
120
|
+
|
|
121
|
+
1. Copy the template and generate a key (or use the REPL helper):
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
cp domesti-secrets.json.example domesti-secrets.json
|
|
125
|
+
# in the REPL: setup-secrets
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`setup-secrets` can generate a new key or accept an existing one, writes
|
|
129
|
+
`domesti-secrets.json` with mode `0600`, and reminds you to restart the
|
|
130
|
+
server. The file is listed in `.gitignore` — never commit it.
|
|
131
|
+
|
|
132
|
+
2. Restart `domesti-bot-server` so the process reads the file.
|
|
133
|
+
|
|
134
|
+
3. On **desktop** browsers, open the **☰** menu → **Settings** and paste the
|
|
135
|
+
six-digit Tailwind Local Control Key. It is stored encrypted in SQLite and
|
|
136
|
+
is never shown again. Restart once more so discovery picks up the token.
|
|
137
|
+
|
|
138
|
+
**Precedence for the Tailwind token:** `--tailwind-token` → `TAILWIND_TOKEN`
|
|
139
|
+
env → encrypted row in SQLite. **Precedence for the Fernet key:**
|
|
140
|
+
`DOMESTI_SECRETS_KEY` env → `domesti_secrets_key` in `domesti-secrets.json`.
|
|
141
|
+
|
|
142
|
+
For systemd, you can still use `EnvironmentFile=` for `TAILWIND_TOKEN` instead
|
|
143
|
+
of the database path; see [`docs/AGENTS.md`](docs/AGENTS.md) for security notes.
|
|
144
|
+
|
|
145
|
+
## Web UI overview
|
|
146
|
+
|
|
147
|
+
After starting the server, the landing page hydrates a tile UI:
|
|
148
|
+
|
|
149
|
+
- One section per device family (`Lights & plugs`, `Sonos zones`,
|
|
150
|
+
`Garage doors`) with a family-coloured icon and frame.
|
|
151
|
+
- One tile per device. Tap to toggle (on/off, play/pause, open/close); the
|
|
152
|
+
tile updates optimistically and reconciles with the next background poll
|
|
153
|
+
(every 5 seconds).
|
|
154
|
+
- Per-family bulk button (`Turn off all`, `Pause all`, `Close all`) and a
|
|
155
|
+
global `Turn off / pause / close everything` button at the top (warm orange,
|
|
156
|
+
distinct from red per-tile off controls).
|
|
157
|
+
- On **desktop** viewports, a **☰** menu with **Settings** (Tailwind token).
|
|
158
|
+
The menu is hidden on mobile form factors.
|
|
159
|
+
- Per-tile "Exclude from all-off" (and analogous) checkbox so the top-of-page
|
|
160
|
+
bulk action skips devices you don't want it touching.
|
|
161
|
+
- Connectivity indicator: family frames turn red when the backend is
|
|
162
|
+
unreachable; all controls grey out until the next poll succeeds.
|
|
163
|
+
|
|
164
|
+
## Progressive Web App (PWA)
|
|
165
|
+
|
|
166
|
+
The landing page is installable as a PWA on phones and desktops that support
|
|
167
|
+
it. Assets live under `app/api/static/`:
|
|
168
|
+
|
|
169
|
+
- `manifest.webmanifest` — name, icons, `display: standalone`
|
|
170
|
+
- `sw.js` — service worker (also served at `GET /sw.js` so scope covers `/`)
|
|
171
|
+
- `icons/` — launcher icons referenced by the manifest
|
|
172
|
+
|
|
173
|
+
The TypeScript bundle registers the worker on load. After you deploy a new
|
|
174
|
+
version, the service worker cache version in `sw.js` (for example
|
|
175
|
+
`domesti-bot-pwa-v15`) must be bumped so installed clients pick up HTML, CSS,
|
|
176
|
+
and `dist/main.js` changes.
|
|
177
|
+
|
|
178
|
+
**Install requirements:** Chromium-based browsers need a secure context
|
|
179
|
+
(`https://` or `http://127.0.0.1`). On a plain HTTP LAN URL, you still get
|
|
180
|
+
manifest metadata in some browsers, but the install prompt may not appear until
|
|
181
|
+
you terminate TLS or use loopback. When the server is reachable with
|
|
182
|
+
`--listen-all`, open the dashboard from your phone at
|
|
183
|
+
`http://<server-lan-ip>:<port>/` and use the in-app install banner when
|
|
184
|
+
offered.
|
|
185
|
+
|
|
186
|
+
## Project layout
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
domesti-bot/
|
|
190
|
+
├── app/ Domain code (device managers, rule engine)
|
|
191
|
+
│ ├── *_device_manager.py One per family (kasa, sonos, gotailwind, …)
|
|
192
|
+
│ ├── db/ SQLAlchemy models + encrypted secrets
|
|
193
|
+
│ ├── kasa_discovery_store.py SQLite cache facade (shared by all managers)
|
|
194
|
+
│ └── api/ FastAPI HTTP surface (subpackage)
|
|
195
|
+
├── config/serve.py uvicorn entrypoint
|
|
196
|
+
├── tests/python/ pytest suite (hermetic + LAN-integration)
|
|
197
|
+
├── web/src/ TypeScript source for the tile UI
|
|
198
|
+
├── scripts/domesti-bot REPL CLI
|
|
199
|
+
├── scripts/domesti-bot-server HTTP server launcher
|
|
200
|
+
├── production/ systemd unit template + on-deploy hooks
|
|
201
|
+
└── docs/AGENTS.md Developer reference (canonical)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`AGENTS.md` at the root is a symlink to `docs/AGENTS.md` — both paths point
|
|
205
|
+
to the same canonical developer reference.
|
|
206
|
+
|
|
207
|
+
## Development
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# One-time setup
|
|
211
|
+
uv sync
|
|
212
|
+
|
|
213
|
+
# The full set of CI gates, in the order they run on every PR:
|
|
214
|
+
uv run pyright # type errors over app/, config/, scripts/, tests/
|
|
215
|
+
uv run pytest -m "not integration" -n auto # hermetic (parallel; matches CI)
|
|
216
|
+
shellcheck $(git ls-files scripts production/scripts | grep -Ev '\.(py|md|txt|yml|yaml|json|toml)$')
|
|
217
|
+
|
|
218
|
+
# Frontend, when web/src/ is touched:
|
|
219
|
+
cd web
|
|
220
|
+
pnpm install --frozen-lockfile
|
|
221
|
+
pnpm run typecheck
|
|
222
|
+
pnpm run build # writes app/api/static/dist/main.js
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The full set of code-style, testing, security, and Git workflow conventions is
|
|
226
|
+
documented in [`docs/AGENTS.md`](docs/AGENTS.md). Notable rules:
|
|
227
|
+
|
|
228
|
+
- Python 3.14 targeted, modern typing only (`list[str]`, not `List[str]`),
|
|
229
|
+
every public function annotated, `pyright` enforced.
|
|
230
|
+
- `uv` for dependency management — never `pip` directly.
|
|
231
|
+
- Methods and module-level functions sorted alphabetically inside each class.
|
|
232
|
+
- Sigs require `from __future__ import annotations`.
|
|
233
|
+
- All commits via Graphite-stacked PRs; `main` is protected, direct pushes
|
|
234
|
+
are blocked at the server.
|
|
235
|
+
- Conventional Commit messages, GPG-signed.
|
|
236
|
+
|
|
237
|
+
## Production deployment
|
|
238
|
+
|
|
239
|
+
The production target is a **systemd user unit**. The template at
|
|
240
|
+
[`etc/systemd/domesti-bot.service`](etc/systemd/domesti-bot.service) is what
|
|
241
|
+
[`repository-helpers`](https://github.com/the-hcma/repository-helpers)
|
|
242
|
+
`setup-service` installs (same `@@REPO_DIR@@` contract as fpdf). It passes
|
|
243
|
+
`--listen-all --listen-port 8003` so the API listens on all interfaces (use
|
|
244
|
+
`DOMESTI_API_KEY` on untrusted LANs). `ExecStartPost` curls `GET /health` on
|
|
245
|
+
loopback until the process answers. The deploy hook [`scripts/on-deploy`](scripts/on-deploy)
|
|
246
|
+
runs `uv sync`, rebuilds the web bundle when needed, and lets `setup-service`
|
|
247
|
+
restart the unit. For a **system**-level unit with a dedicated user, see
|
|
248
|
+
[`production/systemd/domesti-bot-server.service.template`](production/systemd/domesti-bot-server.service.template).
|
|
249
|
+
|
|
250
|
+
`docs/AGENTS.md` has the deployment-specific details — auth keys, log paths,
|
|
251
|
+
service management commands.
|
|
252
|
+
|
|
253
|
+
## Contributing
|
|
254
|
+
|
|
255
|
+
**Contributions are welcome and appreciated.** Issues, bug reports, feature
|
|
256
|
+
requests, and PRs are all on the table — whether you've spotted a typo,
|
|
257
|
+
hit an edge case with your specific Kasa/Sonos/Tailwind hardware, or want to
|
|
258
|
+
add a brand-new device family, the door is open.
|
|
259
|
+
|
|
260
|
+
The project uses [Graphite](https://graphite.dev) for stacked PRs. The
|
|
261
|
+
practical workflow is:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
# 1. Start a stack from main
|
|
265
|
+
gt create feat/your-idea
|
|
266
|
+
|
|
267
|
+
# 2. Make the change, run the local gates (pyright + pytest, see Development)
|
|
268
|
+
# Each gate is also enforced in CI.
|
|
269
|
+
|
|
270
|
+
# 3. Commit + open PR
|
|
271
|
+
gt create --all --message "feat: short description"
|
|
272
|
+
gt submit --no-interactive --publish
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
For larger changes, stack the work into focused PRs so each one is
|
|
276
|
+
independently reviewable. The [stack of PRs](https://github.com/the-hcma/domesti-bot/pulls)
|
|
277
|
+
visible on this repo is itself an example of the pattern.
|
|
278
|
+
|
|
279
|
+
The full Git / commit / PR conventions, including the merge-it label flow and
|
|
280
|
+
the protected-`main` ruleset, live in [`docs/AGENTS.md`](docs/AGENTS.md) under
|
|
281
|
+
the *Commits, Stacking & Pull Requests* section.
|
|
282
|
+
|
|
283
|
+
## License
|
|
284
|
+
|
|
285
|
+
MIT License — see [LICENSE](LICENSE) for the full text. Copyright (c) 2026
|
|
286
|
+
Henrique Andrade.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""domesti-bot application package: device managers, rules, REPL CLI, HTTP API."""
|