ha-mcp-dev 7.5.0.dev586__tar.gz → 7.5.0.dev588__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.
- {ha_mcp_dev-7.5.0.dev586/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.5.0.dev588}/PKG-INFO +1 -1
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/pyproject.toml +1 -1
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/smart_search.py +17 -20
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_areas.py +80 -144
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_config_helpers.py +1 -1
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/LICENSE +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/README.md +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/setup.cfg +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/settings_ui.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/tests/test_env_manager.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ha-mcp-dev"
|
|
7
|
-
version = "7.5.0.
|
|
7
|
+
version = "7.5.0.dev588"
|
|
8
8
|
description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13,<3.14"
|
|
@@ -197,14 +197,13 @@ class SmartSearchTools:
|
|
|
197
197
|
aliases_map: dict[str, list[str]] = {}
|
|
198
198
|
if survivor_ids:
|
|
199
199
|
try:
|
|
200
|
-
entries_resp = await self.client.send_websocket_message(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
):
|
|
200
|
+
entries_resp = await self.client.send_websocket_message(
|
|
201
|
+
{
|
|
202
|
+
"type": "config/entity_registry/get_entries",
|
|
203
|
+
"entity_ids": survivor_ids,
|
|
204
|
+
}
|
|
205
|
+
)
|
|
206
|
+
if isinstance(entries_resp, dict) and entries_resp.get("success"):
|
|
208
207
|
for eid, entry in (
|
|
209
208
|
entries_resp.get("result", {}) or {}
|
|
210
209
|
).items():
|
|
@@ -232,11 +231,13 @@ class SmartSearchTools:
|
|
|
232
231
|
# Shallow copy + private-prefixed keys so downstream
|
|
233
232
|
# consumers that round-trip these dicts don't ship
|
|
234
233
|
# internal fields back to clients.
|
|
235
|
-
enriched.append(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
234
|
+
enriched.append(
|
|
235
|
+
{
|
|
236
|
+
**entity,
|
|
237
|
+
"_aliases": aliases_map.get(eid, []),
|
|
238
|
+
"_hidden_by": slim.get("hidden_by"),
|
|
239
|
+
}
|
|
240
|
+
)
|
|
240
241
|
|
|
241
242
|
entities = enriched
|
|
242
243
|
if domain_filter:
|
|
@@ -389,7 +390,7 @@ class SmartSearchTools:
|
|
|
389
390
|
# Two-pass area resolution. Pass 1 collects exact id / name /
|
|
390
391
|
# alias matches; if any are found, fuzzy aggregation is
|
|
391
392
|
# skipped entirely. This makes ``area_filter`` honor a
|
|
392
|
-
# literal area_id from ``
|
|
393
|
+
# literal area_id from ``ha_list_floors_areas`` — pre-fix a
|
|
393
394
|
# query like ``"bedroom_kids"`` would also fuzzy-match its
|
|
394
395
|
# parent ``"bedroom"`` (partial_ratio=100) and aggregate
|
|
395
396
|
# sibling areas' entities. Aliases (per-area registry, used
|
|
@@ -519,9 +520,7 @@ class SmartSearchTools:
|
|
|
519
520
|
),
|
|
520
521
|
"state": state_info.get("state", "unknown"),
|
|
521
522
|
"_hidden_by": (
|
|
522
|
-
"hidden"
|
|
523
|
-
if entity_id in hidden_entity_ids
|
|
524
|
-
else None
|
|
523
|
+
"hidden" if entity_id in hidden_entity_ids else None
|
|
525
524
|
),
|
|
526
525
|
}
|
|
527
526
|
)
|
|
@@ -538,9 +537,7 @@ class SmartSearchTools:
|
|
|
538
537
|
"domain": entity_id.split(".")[0],
|
|
539
538
|
"state": state_info.get("state", "unknown"),
|
|
540
539
|
"_hidden_by": (
|
|
541
|
-
"hidden"
|
|
542
|
-
if entity_id in hidden_entity_ids
|
|
543
|
-
else None
|
|
540
|
+
"hidden" if entity_id in hidden_entity_ids else None
|
|
544
541
|
),
|
|
545
542
|
}
|
|
546
543
|
for entity_id in area_entities
|
|
@@ -5,6 +5,7 @@ This module provides tools for listing, creating, updating, and deleting
|
|
|
5
5
|
Home Assistant areas and floors - essential organizational features for smart homes.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import asyncio
|
|
8
9
|
import logging
|
|
9
10
|
from typing import Annotated, Any, Literal
|
|
10
11
|
|
|
@@ -130,20 +131,20 @@ class AreaTools:
|
|
|
130
131
|
return message
|
|
131
132
|
|
|
132
133
|
# ============================================================
|
|
133
|
-
# AREA
|
|
134
|
+
# AREA & FLOOR LISTING
|
|
134
135
|
# ============================================================
|
|
135
136
|
|
|
136
137
|
@tool(
|
|
137
|
-
name="
|
|
138
|
+
name="ha_list_floors_areas",
|
|
138
139
|
tags={"Areas & Floors"},
|
|
139
140
|
annotations={
|
|
140
141
|
"idempotentHint": True,
|
|
141
142
|
"readOnlyHint": True,
|
|
142
|
-
"title": "List Areas",
|
|
143
|
+
"title": "List Floors and Areas",
|
|
143
144
|
},
|
|
144
145
|
)
|
|
145
146
|
@log_tool_usage
|
|
146
|
-
async def
|
|
147
|
+
async def ha_list_floors_areas(
|
|
147
148
|
self,
|
|
148
149
|
fields: Annotated[
|
|
149
150
|
str | list[str] | None,
|
|
@@ -151,9 +152,11 @@ class AreaTools:
|
|
|
151
152
|
default=None,
|
|
152
153
|
description=(
|
|
153
154
|
"Return only the specified top-level response keys to reduce "
|
|
154
|
-
'response size (e.g. ["
|
|
155
|
+
'response size (e.g. ["floors"]). '
|
|
155
156
|
"None = full response (default). "
|
|
156
|
-
"Available keys: success,
|
|
157
|
+
"Available keys: success, floor_count, area_count, "
|
|
158
|
+
"unassigned_count, orphaned_count, floors, unassigned_areas, "
|
|
159
|
+
"orphaned_areas, message."
|
|
157
160
|
),
|
|
158
161
|
),
|
|
159
162
|
] = None,
|
|
@@ -162,25 +165,32 @@ class AreaTools:
|
|
|
162
165
|
Field(
|
|
163
166
|
default=None,
|
|
164
167
|
description=(
|
|
165
|
-
"Project each area record
|
|
166
|
-
'E.g. ["area_id",
|
|
167
|
-
"None = full records (default).
|
|
168
|
-
"Available keys: area_id, name,
|
|
168
|
+
"Project each area record (in floors[].areas, unassigned_areas, "
|
|
169
|
+
'and orphaned_areas) to only the specified keys. E.g. ["area_id", '
|
|
170
|
+
'"name"] returns slim area records. None = full records (default). '
|
|
171
|
+
"Unknown keys yield empty records. Available keys: area_id, name, "
|
|
172
|
+
"icon, floor_id, aliases, picture, labels."
|
|
169
173
|
),
|
|
170
174
|
),
|
|
171
175
|
] = None,
|
|
172
176
|
) -> dict[str, Any]:
|
|
173
177
|
"""
|
|
174
|
-
List
|
|
178
|
+
List floors sorted by level ascending, each with their assigned areas nested, plus areas without a floor.
|
|
179
|
+
|
|
180
|
+
Use for location-based reasoning where floor-to-area relationships matter, such as "which rooms are on the ground floor" or operations scoped to a level. Optionally project the response with fields= (top-level keys) or area_fields= (per-area-record keys, applied uniformly across nested, unassigned, and orphaned buckets).
|
|
175
181
|
|
|
176
|
-
|
|
182
|
+
Floors with level=None sort alongside level 0 (ground floor). Areas without a floor assignment appear in unassigned_areas; areas whose floor_id points to a non-existent floor appear in orphaned_areas — a topology snapshot may diverge from individual list calls if the registries change between reads.
|
|
177
183
|
"""
|
|
184
|
+
# Validate projection params before any WS round-trips so a bad shape
|
|
185
|
+
# fails fast without burning two registry reads.
|
|
178
186
|
parsed_fields: list[str] | None = None
|
|
179
187
|
if fields is not None:
|
|
180
188
|
try:
|
|
181
189
|
parsed_fields = parse_string_list_param(
|
|
182
190
|
fields, "fields", allow_csv=True
|
|
183
191
|
)
|
|
192
|
+
if parsed_fields is not None and len(parsed_fields) == 0:
|
|
193
|
+
raise ValueError("fields must contain at least one key")
|
|
184
194
|
except ValueError as exc:
|
|
185
195
|
raise_tool_error(create_validation_error(str(exc), parameter="fields"))
|
|
186
196
|
parsed_area_fields: list[str] | None = None
|
|
@@ -195,141 +205,39 @@ class AreaTools:
|
|
|
195
205
|
raise_tool_error(
|
|
196
206
|
create_validation_error(str(exc), parameter="area_fields")
|
|
197
207
|
)
|
|
198
|
-
try:
|
|
199
|
-
message: dict[str, Any] = {
|
|
200
|
-
"type": "config/area_registry/list",
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
result = await self._client.send_websocket_message(message)
|
|
204
|
-
|
|
205
|
-
if result.get("success"):
|
|
206
|
-
areas = result.get("result", [])
|
|
207
|
-
_orig_areas = areas
|
|
208
|
-
if parsed_area_fields is not None:
|
|
209
|
-
areas = project_records(areas, parsed_area_fields)
|
|
210
|
-
response: dict[str, Any] = {
|
|
211
|
-
"success": True,
|
|
212
|
-
"count": len(areas),
|
|
213
|
-
"areas": areas,
|
|
214
|
-
"message": f"Found {len(areas)} area(s)",
|
|
215
|
-
}
|
|
216
|
-
if parsed_area_fields is not None:
|
|
217
|
-
_warn = result_fields_warning(
|
|
218
|
-
_orig_areas, areas, parsed_area_fields, param_name="area_fields"
|
|
219
|
-
)
|
|
220
|
-
if _warn:
|
|
221
|
-
response.setdefault("warnings", []).append(_warn)
|
|
222
|
-
return project_fields(response, parsed_fields)
|
|
223
|
-
else:
|
|
224
|
-
raise_tool_error(
|
|
225
|
-
create_error_response(
|
|
226
|
-
ErrorCode.SERVICE_CALL_FAILED,
|
|
227
|
-
result.get("error", "Failed to list areas"),
|
|
228
|
-
)
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
except ToolError:
|
|
232
|
-
raise
|
|
233
|
-
except Exception as e:
|
|
234
|
-
logger.error(f"Error listing areas: {e}")
|
|
235
|
-
exception_to_structured_error(
|
|
236
|
-
e,
|
|
237
|
-
context={"operation": "list_areas"},
|
|
238
|
-
suggestions=[
|
|
239
|
-
"Check Home Assistant connection",
|
|
240
|
-
"Verify WebSocket connection is active",
|
|
241
|
-
],
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
# ============================================================
|
|
245
|
-
# FLOOR TOOLS
|
|
246
|
-
# ============================================================
|
|
247
|
-
|
|
248
|
-
@tool(
|
|
249
|
-
name="ha_config_list_floors",
|
|
250
|
-
tags={"Areas & Floors"},
|
|
251
|
-
annotations={
|
|
252
|
-
"idempotentHint": True,
|
|
253
|
-
"readOnlyHint": True,
|
|
254
|
-
"title": "List Floors",
|
|
255
|
-
},
|
|
256
|
-
)
|
|
257
|
-
@log_tool_usage
|
|
258
|
-
async def ha_config_list_floors(self) -> dict[str, Any]:
|
|
259
|
-
"""
|
|
260
|
-
List all Home Assistant floors.
|
|
261
|
-
|
|
262
|
-
Returns floor ID, name, icon, level (0=ground, 1=first, -1=basement), and aliases.
|
|
263
|
-
"""
|
|
264
|
-
try:
|
|
265
|
-
message: dict[str, Any] = {
|
|
266
|
-
"type": "config/floor_registry/list",
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
result = await self._client.send_websocket_message(message)
|
|
270
|
-
|
|
271
|
-
if result.get("success"):
|
|
272
|
-
floors = result.get("result", [])
|
|
273
|
-
return {
|
|
274
|
-
"success": True,
|
|
275
|
-
"count": len(floors),
|
|
276
|
-
"floors": floors,
|
|
277
|
-
"message": f"Found {len(floors)} floor(s)",
|
|
278
|
-
}
|
|
279
|
-
else:
|
|
280
|
-
raise_tool_error(
|
|
281
|
-
create_error_response(
|
|
282
|
-
ErrorCode.SERVICE_CALL_FAILED,
|
|
283
|
-
result.get("error", "Failed to list floors"),
|
|
284
|
-
)
|
|
285
|
-
)
|
|
286
208
|
|
|
287
|
-
except ToolError:
|
|
288
|
-
raise
|
|
289
|
-
except Exception as e:
|
|
290
|
-
logger.error(f"Error listing floors: {e}")
|
|
291
|
-
exception_to_structured_error(
|
|
292
|
-
e,
|
|
293
|
-
context={"operation": "list_floors"},
|
|
294
|
-
suggestions=[
|
|
295
|
-
"Check Home Assistant connection",
|
|
296
|
-
"Verify WebSocket connection is active",
|
|
297
|
-
],
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
@tool(
|
|
301
|
-
name="ha_list_floors_areas",
|
|
302
|
-
tags={"Areas & Floors"},
|
|
303
|
-
annotations={
|
|
304
|
-
"idempotentHint": True,
|
|
305
|
-
"readOnlyHint": True,
|
|
306
|
-
"title": "List Floors and Areas",
|
|
307
|
-
},
|
|
308
|
-
)
|
|
309
|
-
@log_tool_usage
|
|
310
|
-
async def ha_list_floors_areas(self) -> dict[str, Any]:
|
|
311
|
-
"""
|
|
312
|
-
List floors sorted by level ascending, each with their assigned areas nested, plus areas without a floor.
|
|
313
|
-
|
|
314
|
-
Do not use for flat listings — ha_config_list_areas and ha_config_list_floors cover those.
|
|
315
|
-
|
|
316
|
-
Use for location-based reasoning where floor-to-area relationships matter, such as "which rooms are on the ground floor" or operations scoped to a level.
|
|
317
|
-
|
|
318
|
-
Floors with level=None sort alongside level 0 (ground floor). Areas without a floor assignment appear in unassigned_areas; areas whose floor_id points to a non-existent floor appear in orphaned_areas — a topology snapshot may diverge from individual list calls if the registries change between reads.
|
|
319
|
-
"""
|
|
320
209
|
progress: dict[str, Any] = {
|
|
321
210
|
"operation": "list_floors_areas",
|
|
322
211
|
"phase": "start",
|
|
323
212
|
}
|
|
324
213
|
try:
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
214
|
+
# Fetch both registries concurrently. Sequential awaits add a
|
|
215
|
+
# round-trip per call on the WS transport; gather halves the
|
|
216
|
+
# tool-side latency. Use return_exceptions=True so a failure on
|
|
217
|
+
# one side doesn't cancel the other — the post-fetch guard
|
|
218
|
+
# below reports both registries' state in the error context for
|
|
219
|
+
# diagnosis. Indexed access + explicit annotations rather than
|
|
220
|
+
# tuple-unpack — gather returns list[Any] which mypy can't
|
|
221
|
+
# statically narrow to a 2-tuple.
|
|
222
|
+
results = await asyncio.gather(
|
|
223
|
+
self._client.send_websocket_message(
|
|
224
|
+
{"type": "config/area_registry/list"}
|
|
225
|
+
),
|
|
226
|
+
self._client.send_websocket_message(
|
|
227
|
+
{"type": "config/floor_registry/list"}
|
|
228
|
+
),
|
|
229
|
+
return_exceptions=True,
|
|
331
230
|
)
|
|
332
|
-
progress["phase"] = "
|
|
231
|
+
progress["phase"] = "registries_fetched"
|
|
232
|
+
|
|
233
|
+
# Re-raise transport-level exceptions from either fetch so the
|
|
234
|
+
# outer except handler classifies them via exception_to_structured_error.
|
|
235
|
+
if isinstance(results[0], BaseException):
|
|
236
|
+
raise results[0]
|
|
237
|
+
if isinstance(results[1], BaseException):
|
|
238
|
+
raise results[1]
|
|
239
|
+
areas_result: dict[str, Any] = results[0]
|
|
240
|
+
floors_result: dict[str, Any] = results[1]
|
|
333
241
|
|
|
334
242
|
# A response with success=True but no "result" key is malformed —
|
|
335
243
|
# treat it as a service call failure rather than silently returning
|
|
@@ -360,7 +268,7 @@ class AreaTools:
|
|
|
360
268
|
# Partition areas into three disjoint sets:
|
|
361
269
|
# - nested: floor_id present AND points to a known floor
|
|
362
270
|
# - orphaned: floor_id present BUT points to a non-existent floor
|
|
363
|
-
# (race between the
|
|
271
|
+
# (race between the concurrent reads, or manual
|
|
364
272
|
# .storage inconsistency)
|
|
365
273
|
# - unassigned: no floor_id at all
|
|
366
274
|
# Orphaned is surfaced as a separate key so the LLM can diagnose
|
|
@@ -410,22 +318,50 @@ class AreaTools:
|
|
|
410
318
|
topology.sort(key=_floor_sort_key)
|
|
411
319
|
progress["phase"] = "sorted"
|
|
412
320
|
|
|
413
|
-
|
|
321
|
+
# Apply per-area projection across all 3 buckets uniformly.
|
|
322
|
+
# Snapshot pre-projection areas for the typo-guard warning.
|
|
323
|
+
_orig_all_areas = list(areas)
|
|
324
|
+
if parsed_area_fields is not None:
|
|
325
|
+
for floor in topology:
|
|
326
|
+
floor["areas"] = project_records(floor["areas"], parsed_area_fields)
|
|
327
|
+
unassigned_areas = project_records(unassigned_areas, parsed_area_fields)
|
|
328
|
+
orphaned_areas = project_records(orphaned_areas, parsed_area_fields)
|
|
329
|
+
|
|
330
|
+
response: dict[str, Any] = {
|
|
414
331
|
"success": True,
|
|
415
332
|
"floor_count": len(topology),
|
|
416
|
-
"area_count": len(
|
|
333
|
+
"area_count": len(_orig_all_areas),
|
|
417
334
|
"unassigned_count": len(unassigned_areas),
|
|
418
335
|
"orphaned_count": len(orphaned_areas),
|
|
419
336
|
"floors": topology,
|
|
420
337
|
"unassigned_areas": unassigned_areas,
|
|
421
338
|
"orphaned_areas": orphaned_areas,
|
|
422
339
|
"message": (
|
|
423
|
-
f"Found {len(topology)} floor(s), {len(
|
|
340
|
+
f"Found {len(topology)} floor(s), {len(_orig_all_areas)} area(s), "
|
|
424
341
|
f"{len(unassigned_areas)} unassigned, "
|
|
425
342
|
f"{len(orphaned_areas)} orphaned"
|
|
426
343
|
),
|
|
427
344
|
}
|
|
428
345
|
|
|
346
|
+
# Typo-guard: combine projected areas across buckets to detect the
|
|
347
|
+
# all-empty-records situation that signals an unknown area_fields key.
|
|
348
|
+
if parsed_area_fields is not None:
|
|
349
|
+
_projected_all = (
|
|
350
|
+
[a for f in topology for a in f["areas"]]
|
|
351
|
+
+ unassigned_areas
|
|
352
|
+
+ orphaned_areas
|
|
353
|
+
)
|
|
354
|
+
_warn = result_fields_warning(
|
|
355
|
+
_orig_all_areas,
|
|
356
|
+
_projected_all,
|
|
357
|
+
parsed_area_fields,
|
|
358
|
+
param_name="area_fields",
|
|
359
|
+
)
|
|
360
|
+
if _warn:
|
|
361
|
+
response.setdefault("warnings", []).append(_warn)
|
|
362
|
+
|
|
363
|
+
return project_fields(response, parsed_fields)
|
|
364
|
+
|
|
429
365
|
except ToolError:
|
|
430
366
|
raise
|
|
431
367
|
except Exception as e:
|
{ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_config_helpers.py
RENAMED
|
@@ -1154,7 +1154,7 @@ async def _validate_registry_ids(
|
|
|
1154
1154
|
f"area_id={area_id!r} does not exist in the area registry.",
|
|
1155
1155
|
context={"area_id": area_id},
|
|
1156
1156
|
suggestions=[
|
|
1157
|
-
"Use
|
|
1157
|
+
"Use ha_list_floors_areas() to list valid area IDs.",
|
|
1158
1158
|
'Pass area_id="" to clear the area assignment.',
|
|
1159
1159
|
f"Available area_ids: {sorted(valid_area_ids)}",
|
|
1160
1160
|
],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/resources/skills-vendor/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/best_practice_checker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_config_entry_flow.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_config_scripts.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/tools/tools_voice_assistant.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/transforms/categorized_search.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/transforms/lite_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp/utils/kill_signal_diagnostics.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.5.0.dev586 → ha_mcp_dev-7.5.0.dev588}/src/ha_mcp_dev.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|