homeassistant-repl 0.2.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.
@@ -0,0 +1,261 @@
1
+ Metadata-Version: 2.3
2
+ Name: homeassistant-repl
3
+ Version: 0.2.0
4
+ Summary: Home Assistant REPL - the powerful developer shell for custom component developers
5
+ Classifier: Development Status :: 3 - Alpha
6
+ Classifier: Natural Language :: English
7
+ Classifier: Operating System :: OS Independent
8
+ Classifier: Environment :: Console
9
+ Classifier: Topic :: Home Automation
10
+ Classifier: Topic :: Software Development :: Debuggers
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Typing :: Typed
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Requires-Dist: prompt-toolkit>=3.0
16
+ Requires-Dist: pygments>=2.18
17
+ Requires-Dist: pyyaml>=6.0
18
+ Requires-Dist: rich>=15.0.0
19
+ Requires-Dist: websockets>=13.0
20
+ Requires-Python: >=3.14
21
+ Project-URL: Homepage, https://homeassistant-repl.rhizomatics.org.uk
22
+ Project-URL: Repository, https://github.com/rhizomatics/homeassistant-repl
23
+ Project-URL: Documentation, https://homeassistant-repl.rhizomatics.org.uk
24
+ Project-URL: Issues, https://github.com/rhizomatics/homeassistant-repl/issues
25
+ Project-URL: Changelog, https://github.com/rhizomatics/homeassistant-repl/blob/main/CHANGELOG.md
26
+ Description-Content-Type: text/markdown
27
+
28
+ # Home Assistant REPL
29
+
30
+ [![Rhizomatics Open Source](https://img.shields.io/badge/rhizomatics%20open%20source-lightseagreen)](https://github.com/rhizomatics)
31
+ ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/rhizomatics/homeassistant-repl/python-package.yml)
32
+ [![PyPI](https://img.shields.io/pypi/v/homeassistant-repl)](https://pypi.org/project/homeassistant-repl/)
33
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/homeassistant-repl)](https://www.python.org/downloads/)
34
+ [![Docs](https://img.shields.io/badge/docs-latest-blue)](https://homeassistant-repl.rhizomatics.org.uk)
35
+ ![GitHub](https://img.shields.io/github/license/rhizomatics/homeassistant-repl)
36
+ ![GitHub last commit](https://img.shields.io/github/last-commit/rhizomatics/homeassistant-repl)
37
+
38
+ <img src="https://homeassistant-repl.rhizomatics.org.uk/assets/icon.png" width="128" height="128" align="left" alt="A slice of cherry pie, drawn like a 1990s Visual Basic icon">
39
+
40
+ A REPL shell custom designed for Home Assistant custom component developers, tinkerers and native Python speakers. Makes it easy as pie!
41
+
42
+ It is an opinionated REPL ([Read-Eval-Print-Loop](https://en.wikipedia.org/wiki/Read–eval–print_loop)) shell that aims are to make it easier without any configuration to:
43
+
44
+ - Exploring of the APIs in context of a live working instance
45
+ - Trialling out snippets of code
46
+ - Debugging code (but see note below)
47
+ - Hotfixing issues that don't have built in support to do so from existing components.
48
+
49
+ <!-- termynal -->
50
+ ```bash
51
+ $ uv run --with homeassistant-repl ha-repl --token=<insert token here>
52
+ Home Assistant REPL (API client mode) connected to ws://homeassistant.local:8123/api/websocket. `obj` only, read-only - no `hass`. Ctrl-D to exit.
53
+ >>> obj["/mqtt/binary_sensor/kitchen_terrace_window_tilt"].state
54
+ 'off'
55
+ >>> [o.name for o in obj["/rflink/binary_sensor"].values() if o.state=='unavailable']
56
+ ['Shed Intruder Alarm', 'Panic Keyfob']
57
+ >>> {(o.entity_id, o.state) for o in obj.find(domain="binary_sensor",platform="mqtt")}
58
+ {
59
+ ('binary_sensor.terrace_pir_occupancy', 'unavailable'),
60
+ ('binary_sensor.scullery_smoke_alarm_battery_low', 'off'),
61
+ ('binary_sensor.kitchen_terrace_window_tweaked', 'off'),
62
+ ('binary_sensor.scullery_water_detector_water_leak', 'dry'),
63
+ ('binary_sensor.boiler_co_detector_battery_low', 'off'),
64
+ ('binary_sensor.pantry_sensor_occupancy', 'off')
65
+ }
66
+ >>>
67
+ ```
68
+
69
+ If you're not already comfortable using Python tools to manipulate data on the fly, or better REPL shells in other languages, this is a great way to learn, and faster at the keyboard than clicking around Jupyter notebooks.
70
+
71
+ This is primarily for developers of custom components, and their LLM agents, though may be of interest for other folk tinkering with Home Assistant. It is a potentially sharp tool, so NOT appropriate for general Home Assistant users.
72
+
73
+ ## Features
74
+
75
+ - Integrated with `rich` for pretty object printouts and stack traces
76
+ - Access to all entities via dictionary like interface, `obj`
77
+ - Usual multi-line editing support and history of Python
78
+ - Dedicated shell that can be run without installation with `uv`
79
+ - Usable from inside `ipython` shell, Marimo notebooks or plain `python -m asyncio`
80
+
81
+ All of the above works with standard Home Assistant APIs, referred to as `api` mode.
82
+
83
+ Home Assistant REPL also has an advanced `custom` mode that taps directly into a live Home Assistant using an optional server component available via [HACS](http://hacs.xyz).
84
+
85
+ ## Custom Mode
86
+
87
+ This mode requires a custom component to be installed on the target Home Assistant server via HACS, or use the supplied scripts to install on a local devcontainer. It adds:
88
+
89
+ - Full access to the core Home Assistant Python API via `hass`
90
+ - Read/write access to the actual objects, e.g. entities and their helpers
91
+ - A frisson of danger
92
+
93
+ ### HA REPL Server
94
+
95
+ A HACS component that taps into the Home Assistant and acts as a session server over web sockets.
96
+
97
+ Needed for `custom` mode only, since `api` mode only uses standard Home Assistant APIs.
98
+
99
+ ## Future Developments
100
+
101
+ See the [Roadmap](./developer/design/roadmap.md) for where this might go, and your feedback welcome.
102
+
103
+ >[!NOTE]
104
+ > It is not intended to ever be a replacement for a Python debugger, although it may complement one. It also does not intend to replicate [PyScript](https://pyscript.net), instead focusing on standard python (PyScript uses MicroPython) even at expense of general usability or home assistance access, and not a general automation script execution service. For most non-developer cases, [homeassistant-cli](https://pypi.org/project/homeassistant-cli/) is a better choice, with pre-packaged access to devices, entities, services etc.
105
+
106
+ ## Quick Start
107
+
108
+ Use the `api` mode with `uv` (traditional install also available via `pip install homeassistant-repl`).
109
+
110
+ If Home Assistant available via `http://homeassistant.local:8123` then you can skip the `--url` argument. You will also need to create a [Long Lived Access Token](https://developers.home-assistant.io/docs/auth_api/#long-lived-access-token) by going to the personal settings on your Home Assistant mobile or desktop app.
111
+
112
+ ```bash
113
+ uv run --with homeassistant-repl ha-repl --token <<<my long lived access token>>>
114
+ ```
115
+
116
+ ## Using the Shell
117
+
118
+ The shell is a full Python REPL shell, with multi-line editing, history etc, living inside an asyncio loop that exposes the live Home Assistant instance as:
119
+
120
+ * `obj` - the object tree exposed as a dictionary object and common methods
121
+
122
+ In `custom` mode it also offers:
123
+
124
+ * `hass` - the `HomeAssistant` class at the root of the Python API
125
+
126
+ So I can write code at the command line like:
127
+
128
+ ```python
129
+ pir = obj["/rflink/binary_sensor/hall_pir"]
130
+ pir.state = "on" # non-strict mode, sets entity state with repl as context
131
+ ```
132
+
133
+ The return value of the object is returned to the shell, value printed and available to Python code as `_`. Tracebacks are printed also, as if they were local (in general everything feels like its local)
134
+
135
+ See also [Alternative Integration](alternative_integration.md) options.
136
+
137
+ ## The Object Tree
138
+
139
+ All of the objects (only entities for now) are arranged in a giant tree, like a file system, exposed as the global variable `objs` and implemented as Python [Mapping](https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping) object (which provides the [MappingView](https://docs.python.org/3/library/collections.abc.html#collections.abc.MappingView) views [ItemsView](https://docs.python.org/3/library/collections.abc.html#collections.abc.ItemsView) and [KeysView](https://docs.python.org/3/library/collections.abc.html#collections.abc.KeysView))
140
+
141
+ `objs` offers:
142
+
143
+ - Dictionary style access, using `[]`
144
+ - Raises `KeyError` if entity or sub-path doesn't exist
145
+ - Each level of the tree returns a sub-tree
146
+ - `keys()`,`values()`,`items()` of the sub-tree shows only that level
147
+ - An `OrderedView` is used rather than plain `MappingView` so can be accessed like a `list` and items are alphabetically organized
148
+ - `find()`
149
+ - Returns a flat iterable of the entire tree
150
+ - Optionally restrict by `platform`,`area`,`label`
151
+ - `show()`
152
+ - Dump the most useful info on an object to console
153
+
154
+ All the usual Python tricks can of course also be used, iterators, comprehensions, classes, lambdas or a simple `len()`.
155
+
156
+ ### `obj[]`
157
+
158
+ This allows dictionary ('Mapping') access to the object tree.
159
+
160
+ In the example tree below, the objects and subtrees of objects can be accessed like:
161
+
162
+ ```python
163
+ obj["/rflink/sensor/shed_temperature"].state # prints out temperature
164
+ obj[
165
+ "/rflink/sensor/shed_temperature"
166
+ ].state_attributes # prints out additional attributes
167
+ obj["/rflink"] # the 'light','binary_sensor' and 'sensor' subtrees for rflink
168
+ obj["/rflink/sensor"] # all the sensors for rflink
169
+ len(obj["/rflink/sensor"]) # count of rflink sensors in this example
170
+ ```
171
+
172
+ ##### Example Tree
173
+
174
+ ```
175
+ ...
176
+ - mqtt
177
+ - rflink
178
+ - light
179
+ - staircase_ceiling
180
+ - shed
181
+ - switch
182
+ - upstairs_pixie
183
+ - binary_sensor
184
+ - porch_pir
185
+ - shed_door
186
+ - sensor
187
+ - shed_temperature
188
+ - kitchen_humidity
189
+ ...
190
+ ```
191
+
192
+ >[!NOTE]
193
+ >In the roadmap, there will be a visual Object Browser to view and select entities. For now, it is accessible only via Python code. It also may extend beyond entities, to things like areas, users, categories and devices.
194
+
195
+ #### `obj.find('..')`
196
+
197
+ Where the dictionary access gives a nested directory view of the object tree, `find` provides a flat iteration with no order guarantees, so its fast and simple and can be sorted the usual Python way if needed.
198
+
199
+ `find` also has built in filters, to narrow the big list of objects by one or more `platform`,`domain`,`area`,`label` - each of these will take a single string or list of strings, and they can be combined to narrow down the list.
200
+
201
+ ```python
202
+ {(o.entity_id, o.state) for o in obj.find(domain="binary_sensor")}
203
+ ```
204
+
205
+ ##### Raw Objects
206
+
207
+ In API Client mode, `find()` returns a local proxy for the remote class, normalized to look more like the same object you'd get in custom mode. Switching `raw=True` will bypass this and you'll get the object untouched as it was received from the API.
208
+
209
+ #### `obj.find_paths(..)`
210
+
211
+ Identical to `obj.find()` except it only returns an iterable of the object paths rather than the objects themselves. Ideal for plugging into some logic that will then call `obj[path]` on each one.
212
+
213
+ ```python
214
+ list(objs.find(area="kitchen")) # list names of all entities in kitchen
215
+ sorted(objs.find(area=["kitchen", "shed"]))
216
+ ```
217
+
218
+ #### `obj.find_names(..)`
219
+
220
+ Same as `obj.find_paths()` except it returns the object bare name, as it would appear in Home Assistant, e.g. `sensor.bathroom_humidity`
221
+
222
+ #### `obj.show(..)`
223
+
224
+ The `show` function will give a pretty version of an object where it knows how. For entities this means it combines the `RegistryEntry` and `Entity` information, drops some boring internal stuff, filters out all the `None` values and turns `datetime.datetime` structures into local date times.
225
+
226
+ ```yaml
227
+ obj.show('/unifi/sensor/kitchen_wifi_cpu_utilization')
228
+ ```
229
+
230
+ ## Starting the Shell
231
+
232
+ Use the `HASS_SERVER` environment variable, exported or in a local `.env` file, or the `--url` command line argument if the Home Assistant server is not running locally at usual address( i.e. `http://homeassistant.local:8123`). A [long lived access token](https://developers.home-assistant.io/docs/auth_api/#long-lived-access-token) is needed at `--token` or in an `HASS_TOKEN` environment variable.
233
+
234
+ ### Custom Mode for Real Server
235
+
236
+ On a real instance, install via HACS:
237
+ - it's not in the default HACS repository, so you'll have to add `https://github.com/rhizomatics/homeassistant-repl` as a Custom Repository from the top-right dot menu first
238
+ - Search for *Home Assistant REPL* in the HACS menu and choose *Download*
239
+ - Restart Home Assistant for it to recognize the new custom component available
240
+ - From **Settings → Devices & services → Add integration** find **Home Assistant REPL Server** in the list and install, there's no further config needed
241
+ - Alternatively add `ha_repl_server:` to `configuration.yaml`, which is imported as a config entry)
242
+ - Run `ha-repl` with the `custom` argument
243
+
244
+
245
+ The quickest way to run the shell is using *uv*, which you can do without cloning this repo or making any other downloads.
246
+
247
+ ```bash
248
+ uv run --with homeassistant-repl ha-repl
249
+ ```
250
+ Get help on the arguments in the usual way,
251
+
252
+ ```bash
253
+ uv run --with homeassistant-repl ha-repl --help ,
254
+ ```
255
+
256
+ If you do have this repo checked out, you can also use a direct `run` which means you can also tinker locally with `homeassistant_repl` code.
257
+
258
+ ```bash
259
+ uv run ha-repl
260
+ ```
261
+
@@ -0,0 +1,234 @@
1
+ # Home Assistant REPL
2
+
3
+ [![Rhizomatics Open Source](https://img.shields.io/badge/rhizomatics%20open%20source-lightseagreen)](https://github.com/rhizomatics)
4
+ ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/rhizomatics/homeassistant-repl/python-package.yml)
5
+ [![PyPI](https://img.shields.io/pypi/v/homeassistant-repl)](https://pypi.org/project/homeassistant-repl/)
6
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/homeassistant-repl)](https://www.python.org/downloads/)
7
+ [![Docs](https://img.shields.io/badge/docs-latest-blue)](https://homeassistant-repl.rhizomatics.org.uk)
8
+ ![GitHub](https://img.shields.io/github/license/rhizomatics/homeassistant-repl)
9
+ ![GitHub last commit](https://img.shields.io/github/last-commit/rhizomatics/homeassistant-repl)
10
+
11
+ <img src="https://homeassistant-repl.rhizomatics.org.uk/assets/icon.png" width="128" height="128" align="left" alt="A slice of cherry pie, drawn like a 1990s Visual Basic icon">
12
+
13
+ A REPL shell custom designed for Home Assistant custom component developers, tinkerers and native Python speakers. Makes it easy as pie!
14
+
15
+ It is an opinionated REPL ([Read-Eval-Print-Loop](https://en.wikipedia.org/wiki/Read–eval–print_loop)) shell that aims are to make it easier without any configuration to:
16
+
17
+ - Exploring of the APIs in context of a live working instance
18
+ - Trialling out snippets of code
19
+ - Debugging code (but see note below)
20
+ - Hotfixing issues that don't have built in support to do so from existing components.
21
+
22
+ <!-- termynal -->
23
+ ```bash
24
+ $ uv run --with homeassistant-repl ha-repl --token=<insert token here>
25
+ Home Assistant REPL (API client mode) connected to ws://homeassistant.local:8123/api/websocket. `obj` only, read-only - no `hass`. Ctrl-D to exit.
26
+ >>> obj["/mqtt/binary_sensor/kitchen_terrace_window_tilt"].state
27
+ 'off'
28
+ >>> [o.name for o in obj["/rflink/binary_sensor"].values() if o.state=='unavailable']
29
+ ['Shed Intruder Alarm', 'Panic Keyfob']
30
+ >>> {(o.entity_id, o.state) for o in obj.find(domain="binary_sensor",platform="mqtt")}
31
+ {
32
+ ('binary_sensor.terrace_pir_occupancy', 'unavailable'),
33
+ ('binary_sensor.scullery_smoke_alarm_battery_low', 'off'),
34
+ ('binary_sensor.kitchen_terrace_window_tweaked', 'off'),
35
+ ('binary_sensor.scullery_water_detector_water_leak', 'dry'),
36
+ ('binary_sensor.boiler_co_detector_battery_low', 'off'),
37
+ ('binary_sensor.pantry_sensor_occupancy', 'off')
38
+ }
39
+ >>>
40
+ ```
41
+
42
+ If you're not already comfortable using Python tools to manipulate data on the fly, or better REPL shells in other languages, this is a great way to learn, and faster at the keyboard than clicking around Jupyter notebooks.
43
+
44
+ This is primarily for developers of custom components, and their LLM agents, though may be of interest for other folk tinkering with Home Assistant. It is a potentially sharp tool, so NOT appropriate for general Home Assistant users.
45
+
46
+ ## Features
47
+
48
+ - Integrated with `rich` for pretty object printouts and stack traces
49
+ - Access to all entities via dictionary like interface, `obj`
50
+ - Usual multi-line editing support and history of Python
51
+ - Dedicated shell that can be run without installation with `uv`
52
+ - Usable from inside `ipython` shell, Marimo notebooks or plain `python -m asyncio`
53
+
54
+ All of the above works with standard Home Assistant APIs, referred to as `api` mode.
55
+
56
+ Home Assistant REPL also has an advanced `custom` mode that taps directly into a live Home Assistant using an optional server component available via [HACS](http://hacs.xyz).
57
+
58
+ ## Custom Mode
59
+
60
+ This mode requires a custom component to be installed on the target Home Assistant server via HACS, or use the supplied scripts to install on a local devcontainer. It adds:
61
+
62
+ - Full access to the core Home Assistant Python API via `hass`
63
+ - Read/write access to the actual objects, e.g. entities and their helpers
64
+ - A frisson of danger
65
+
66
+ ### HA REPL Server
67
+
68
+ A HACS component that taps into the Home Assistant and acts as a session server over web sockets.
69
+
70
+ Needed for `custom` mode only, since `api` mode only uses standard Home Assistant APIs.
71
+
72
+ ## Future Developments
73
+
74
+ See the [Roadmap](./developer/design/roadmap.md) for where this might go, and your feedback welcome.
75
+
76
+ >[!NOTE]
77
+ > It is not intended to ever be a replacement for a Python debugger, although it may complement one. It also does not intend to replicate [PyScript](https://pyscript.net), instead focusing on standard python (PyScript uses MicroPython) even at expense of general usability or home assistance access, and not a general automation script execution service. For most non-developer cases, [homeassistant-cli](https://pypi.org/project/homeassistant-cli/) is a better choice, with pre-packaged access to devices, entities, services etc.
78
+
79
+ ## Quick Start
80
+
81
+ Use the `api` mode with `uv` (traditional install also available via `pip install homeassistant-repl`).
82
+
83
+ If Home Assistant available via `http://homeassistant.local:8123` then you can skip the `--url` argument. You will also need to create a [Long Lived Access Token](https://developers.home-assistant.io/docs/auth_api/#long-lived-access-token) by going to the personal settings on your Home Assistant mobile or desktop app.
84
+
85
+ ```bash
86
+ uv run --with homeassistant-repl ha-repl --token <<<my long lived access token>>>
87
+ ```
88
+
89
+ ## Using the Shell
90
+
91
+ The shell is a full Python REPL shell, with multi-line editing, history etc, living inside an asyncio loop that exposes the live Home Assistant instance as:
92
+
93
+ * `obj` - the object tree exposed as a dictionary object and common methods
94
+
95
+ In `custom` mode it also offers:
96
+
97
+ * `hass` - the `HomeAssistant` class at the root of the Python API
98
+
99
+ So I can write code at the command line like:
100
+
101
+ ```python
102
+ pir = obj["/rflink/binary_sensor/hall_pir"]
103
+ pir.state = "on" # non-strict mode, sets entity state with repl as context
104
+ ```
105
+
106
+ The return value of the object is returned to the shell, value printed and available to Python code as `_`. Tracebacks are printed also, as if they were local (in general everything feels like its local)
107
+
108
+ See also [Alternative Integration](alternative_integration.md) options.
109
+
110
+ ## The Object Tree
111
+
112
+ All of the objects (only entities for now) are arranged in a giant tree, like a file system, exposed as the global variable `objs` and implemented as Python [Mapping](https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping) object (which provides the [MappingView](https://docs.python.org/3/library/collections.abc.html#collections.abc.MappingView) views [ItemsView](https://docs.python.org/3/library/collections.abc.html#collections.abc.ItemsView) and [KeysView](https://docs.python.org/3/library/collections.abc.html#collections.abc.KeysView))
113
+
114
+ `objs` offers:
115
+
116
+ - Dictionary style access, using `[]`
117
+ - Raises `KeyError` if entity or sub-path doesn't exist
118
+ - Each level of the tree returns a sub-tree
119
+ - `keys()`,`values()`,`items()` of the sub-tree shows only that level
120
+ - An `OrderedView` is used rather than plain `MappingView` so can be accessed like a `list` and items are alphabetically organized
121
+ - `find()`
122
+ - Returns a flat iterable of the entire tree
123
+ - Optionally restrict by `platform`,`area`,`label`
124
+ - `show()`
125
+ - Dump the most useful info on an object to console
126
+
127
+ All the usual Python tricks can of course also be used, iterators, comprehensions, classes, lambdas or a simple `len()`.
128
+
129
+ ### `obj[]`
130
+
131
+ This allows dictionary ('Mapping') access to the object tree.
132
+
133
+ In the example tree below, the objects and subtrees of objects can be accessed like:
134
+
135
+ ```python
136
+ obj["/rflink/sensor/shed_temperature"].state # prints out temperature
137
+ obj[
138
+ "/rflink/sensor/shed_temperature"
139
+ ].state_attributes # prints out additional attributes
140
+ obj["/rflink"] # the 'light','binary_sensor' and 'sensor' subtrees for rflink
141
+ obj["/rflink/sensor"] # all the sensors for rflink
142
+ len(obj["/rflink/sensor"]) # count of rflink sensors in this example
143
+ ```
144
+
145
+ ##### Example Tree
146
+
147
+ ```
148
+ ...
149
+ - mqtt
150
+ - rflink
151
+ - light
152
+ - staircase_ceiling
153
+ - shed
154
+ - switch
155
+ - upstairs_pixie
156
+ - binary_sensor
157
+ - porch_pir
158
+ - shed_door
159
+ - sensor
160
+ - shed_temperature
161
+ - kitchen_humidity
162
+ ...
163
+ ```
164
+
165
+ >[!NOTE]
166
+ >In the roadmap, there will be a visual Object Browser to view and select entities. For now, it is accessible only via Python code. It also may extend beyond entities, to things like areas, users, categories and devices.
167
+
168
+ #### `obj.find('..')`
169
+
170
+ Where the dictionary access gives a nested directory view of the object tree, `find` provides a flat iteration with no order guarantees, so its fast and simple and can be sorted the usual Python way if needed.
171
+
172
+ `find` also has built in filters, to narrow the big list of objects by one or more `platform`,`domain`,`area`,`label` - each of these will take a single string or list of strings, and they can be combined to narrow down the list.
173
+
174
+ ```python
175
+ {(o.entity_id, o.state) for o in obj.find(domain="binary_sensor")}
176
+ ```
177
+
178
+ ##### Raw Objects
179
+
180
+ In API Client mode, `find()` returns a local proxy for the remote class, normalized to look more like the same object you'd get in custom mode. Switching `raw=True` will bypass this and you'll get the object untouched as it was received from the API.
181
+
182
+ #### `obj.find_paths(..)`
183
+
184
+ Identical to `obj.find()` except it only returns an iterable of the object paths rather than the objects themselves. Ideal for plugging into some logic that will then call `obj[path]` on each one.
185
+
186
+ ```python
187
+ list(objs.find(area="kitchen")) # list names of all entities in kitchen
188
+ sorted(objs.find(area=["kitchen", "shed"]))
189
+ ```
190
+
191
+ #### `obj.find_names(..)`
192
+
193
+ Same as `obj.find_paths()` except it returns the object bare name, as it would appear in Home Assistant, e.g. `sensor.bathroom_humidity`
194
+
195
+ #### `obj.show(..)`
196
+
197
+ The `show` function will give a pretty version of an object where it knows how. For entities this means it combines the `RegistryEntry` and `Entity` information, drops some boring internal stuff, filters out all the `None` values and turns `datetime.datetime` structures into local date times.
198
+
199
+ ```yaml
200
+ obj.show('/unifi/sensor/kitchen_wifi_cpu_utilization')
201
+ ```
202
+
203
+ ## Starting the Shell
204
+
205
+ Use the `HASS_SERVER` environment variable, exported or in a local `.env` file, or the `--url` command line argument if the Home Assistant server is not running locally at usual address( i.e. `http://homeassistant.local:8123`). A [long lived access token](https://developers.home-assistant.io/docs/auth_api/#long-lived-access-token) is needed at `--token` or in an `HASS_TOKEN` environment variable.
206
+
207
+ ### Custom Mode for Real Server
208
+
209
+ On a real instance, install via HACS:
210
+ - it's not in the default HACS repository, so you'll have to add `https://github.com/rhizomatics/homeassistant-repl` as a Custom Repository from the top-right dot menu first
211
+ - Search for *Home Assistant REPL* in the HACS menu and choose *Download*
212
+ - Restart Home Assistant for it to recognize the new custom component available
213
+ - From **Settings → Devices & services → Add integration** find **Home Assistant REPL Server** in the list and install, there's no further config needed
214
+ - Alternatively add `ha_repl_server:` to `configuration.yaml`, which is imported as a config entry)
215
+ - Run `ha-repl` with the `custom` argument
216
+
217
+
218
+ The quickest way to run the shell is using *uv*, which you can do without cloning this repo or making any other downloads.
219
+
220
+ ```bash
221
+ uv run --with homeassistant-repl ha-repl
222
+ ```
223
+ Get help on the arguments in the usual way,
224
+
225
+ ```bash
226
+ uv run --with homeassistant-repl ha-repl --help ,
227
+ ```
228
+
229
+ If you do have this repo checked out, you can also use a direct `run` which means you can also tinker locally with `homeassistant_repl` code.
230
+
231
+ ```bash
232
+ uv run ha-repl
233
+ ```
234
+
@@ -0,0 +1,140 @@
1
+ [project]
2
+ name = "homeassistant-repl"
3
+ version = "0.2.0"
4
+ description = "Home Assistant REPL - the powerful developer shell for custom component developers"
5
+ readme = "README.md"
6
+ requires-python = ">=3.14"
7
+ dependencies = [
8
+ "prompt-toolkit>=3.0",
9
+ "pygments>=2.18",
10
+ "pyyaml>=6.0",
11
+ "rich>=15.0.0",
12
+ "websockets>=13.0",
13
+ ]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Natural Language :: English",
17
+ "Operating System :: OS Independent",
18
+ "Environment :: Console",
19
+ "Topic :: Home Automation",
20
+ "Topic :: Software Development :: Debuggers",
21
+ "Intended Audience :: Developers",
22
+ "Typing :: Typed",
23
+ "Programming Language :: Python",
24
+ "Programming Language :: Python :: 3.14",
25
+ ]
26
+
27
+ [project.scripts]
28
+ ha-repl = "homeassistant_repl.cli:main"
29
+
30
+ [project.urls]
31
+ Homepage = "https://homeassistant-repl.rhizomatics.org.uk"
32
+ Repository = "https://github.com/rhizomatics/homeassistant-repl"
33
+ Documentation = "https://homeassistant-repl.rhizomatics.org.uk"
34
+ Issues = "https://github.com/rhizomatics/homeassistant-repl/issues"
35
+ Changelog = "https://github.com/rhizomatics/homeassistant-repl/blob/main/CHANGELOG.md"
36
+
37
+ [dependency-groups]
38
+ dev = [
39
+ "homeassistant",
40
+ "types-Pygments",
41
+ "pytest-homeassistant-custom-component",
42
+ "pytest-asyncio>=0.24",
43
+ "ruff>=0.15.0",
44
+ "pre-commit>=4.4.0",
45
+ "pytest>=9.0.0",
46
+ "pytest-mock>=3.15.0",
47
+ "pytest-cov>=7.0.0",
48
+ "pytest-xdist",
49
+ "pytest-timeout",
50
+ "coverage",
51
+ "icdiff",
52
+ "genbadge[all]",
53
+ "actionlint-py",
54
+ "codespell",
55
+ "mypy",
56
+ ]
57
+ docs = [
58
+ "properdocs",
59
+ "mkdocs-materialx",
60
+ "mkdocs-minify-plugin",
61
+ "mkdocs-autorefs",
62
+ "mkdocs-pagetree-plugin",
63
+ "mkdocs-coverage",
64
+ "termynal",
65
+ "mkdocs-homepage-copier",
66
+ "pymdown-extensions",
67
+ "mkdocs-meta-descriptions-plugin",
68
+ "mkdocs-git-revision-date-localized-plugin",
69
+ "mkdocs-htmlproofer-plugin",
70
+ "mkdocs-llmstxt>=0.5.0,<0.6.0",
71
+ ]
72
+
73
+ [build-system]
74
+ requires = ["uv_build>=0.9.18,<0.13.0"]
75
+ build-backend = "uv_build"
76
+
77
+ [tool.uv]
78
+ compile-bytecode = true
79
+ managed = true
80
+ exclude-newer = "7 days"
81
+ add-bounds = "major"
82
+
83
+ [tool.uv.exclude-newer-package]
84
+ uv_build = "2 days"
85
+
86
+ [tool.uv.build-backend]
87
+ module-root = "src"
88
+ module-name = "homeassistant_repl"
89
+
90
+ [tool.ruff.lint]
91
+ preview = true
92
+ unfixable = ["B"]
93
+
94
+ [tool.ruff.format]
95
+ preview = true
96
+
97
+ [tool.pytest.ini_options]
98
+ asyncio_mode = "auto"
99
+ testpaths = ["tests"]
100
+ pythonpath = ["."]
101
+ norecursedirs = [
102
+ ".git",
103
+ "templates",
104
+ ]
105
+ addopts = [
106
+ "--timeout=30",
107
+ "--junitxml=junit/test-results.xml",
108
+ "--cov-report=xml:cov.xml",
109
+ "--cov-report=html",
110
+ "--cov-report=term-missing",
111
+ "--cov=custom_components/ha_repl_server",
112
+ "--cov=src",
113
+ "--cov-fail-under=45",
114
+ ]
115
+
116
+ [tool.coverage.run]
117
+ branch = true
118
+ source = ["src"]
119
+
120
+ [tool.coverage.report]
121
+ fail_under = 0
122
+
123
+ [tool.coverage.xml]
124
+ output = "cov.xml"
125
+
126
+ [tool.coverage.html]
127
+ directory = "htmlcov"
128
+
129
+ [tool.mypy]
130
+ mypy_path = "src"
131
+
132
+ [tool.codespell]
133
+ skip = ".venv,.git,htmlcov,site/**,./.*,*.csv,*.json,README.*.md,support"
134
+ count = true
135
+ quiet-level = 2
136
+ ignore-words-list = "hass,referer"
137
+
138
+ [tool.bandit]
139
+ exclude_dirs = ["tests"]
140
+ skips = ["B105"]