ha-mcp-dev 7.5.0.dev572__tar.gz → 7.5.0.dev574__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.dev572/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.5.0.dev574}/PKG-INFO +1 -1
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/pyproject.toml +1 -1
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/client/rest_client.py +32 -34
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/client/websocket_client.py +12 -1
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_config_automations.py +4 -5
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/util_helpers.py +165 -20
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/LICENSE +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/README.md +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/setup.cfg +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/settings_ui.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/smart_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/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.dev574"
|
|
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"
|
|
@@ -895,8 +895,6 @@ class HomeAssistantClient:
|
|
|
895
895
|
Raises:
|
|
896
896
|
HomeAssistantAPIError: If configuration invalid or API error
|
|
897
897
|
"""
|
|
898
|
-
import time
|
|
899
|
-
|
|
900
898
|
# Generate unique_id for new automation if not provided
|
|
901
899
|
if identifier is None:
|
|
902
900
|
unique_id = str(int(time.time() * 1000))
|
|
@@ -964,51 +962,51 @@ class HomeAssistantClient:
|
|
|
964
962
|
) from e
|
|
965
963
|
raise
|
|
966
964
|
|
|
967
|
-
#
|
|
968
|
-
#
|
|
969
|
-
#
|
|
970
|
-
#
|
|
971
|
-
# itself
|
|
972
|
-
|
|
965
|
+
# Upper-bound budget for the WS-driven discovery wait (and its REST
|
|
966
|
+
# fallback). Preserves the 6s ceiling PR #1384 tuned for the legacy
|
|
967
|
+
# cadence loop; the WS path resolves in <1s on the happy path because
|
|
968
|
+
# ``state_changed`` fires as soon as HA hydrates the new entity. The
|
|
969
|
+
# cadence tuple itself is gone — uniform polling at ``poll_interval``
|
|
970
|
+
# is fine when the WS happy path is the primary route. #1389's
|
|
971
|
+
# cadence-measurement instrumentation also retired with the loop;
|
|
972
|
+
# the WS waiter logs `time.monotonic()` elapsed via its own debug
|
|
973
|
+
# line at ``util_helpers._ws_wait_for_condition``.
|
|
974
|
+
_POLL_BUDGET_S: float = 6.0
|
|
973
975
|
|
|
974
976
|
async def _poll_for_automation_entity(self, unique_id: str) -> str | None:
|
|
975
|
-
"""
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
977
|
+
"""Discover the entity_id assigned to a newly created automation.
|
|
978
|
+
|
|
979
|
+
Delegates to ``wait_for_automation_entity_by_unique_id`` which uses
|
|
980
|
+
a ``state_changed`` WebSocket subscription filtered on
|
|
981
|
+
``new_state.attributes.id == unique_id`` and falls back to REST
|
|
982
|
+
polling of ``get_states()`` when the WebSocket is unavailable.
|
|
983
|
+
See #1152 / #1395 for context.
|
|
984
|
+
"""
|
|
985
|
+
# Local import: ``util_helpers`` imports from ``rest_client`` for
|
|
986
|
+
# the typed-error classes, so a module-level import here would be
|
|
987
|
+
# circular. Mirrors the same pattern in ``_get_waiter_ws_client``.
|
|
988
|
+
from ..tools.util_helpers import wait_for_automation_entity_by_unique_id
|
|
989
|
+
|
|
979
990
|
try:
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
for state in states:
|
|
984
|
-
if not state.get("entity_id", "").startswith("automation."):
|
|
985
|
-
continue
|
|
986
|
-
if state.get("attributes", {}).get("id") == unique_id:
|
|
987
|
-
entity_id = state.get("entity_id")
|
|
988
|
-
elapsed_ms = (time.monotonic() - start_monotonic) * 1000.0
|
|
989
|
-
logger.debug(
|
|
990
|
-
"entity-registration-elapsed: %.1fms "
|
|
991
|
-
"(unique_id=%s, entity_id=%s)",
|
|
992
|
-
elapsed_ms,
|
|
993
|
-
unique_id,
|
|
994
|
-
entity_id,
|
|
995
|
-
)
|
|
996
|
-
return entity_id
|
|
991
|
+
entity_id = await wait_for_automation_entity_by_unique_id(
|
|
992
|
+
self, unique_id, timeout=self._POLL_BUDGET_S
|
|
993
|
+
)
|
|
997
994
|
except HomeAssistantError as e:
|
|
998
995
|
# Narrow catch: programming bugs (TypeError/KeyError/etc.) propagate.
|
|
999
996
|
# Mirrors test-side _POLLING_TRANSIENT_ERRORS in
|
|
1000
997
|
# tests/src/e2e/utilities/wait_helpers.py and styleguide §
|
|
1001
998
|
# "Exception Handling in Test Polling Loops".
|
|
1002
999
|
logger.warning(
|
|
1003
|
-
f"Failed to
|
|
1000
|
+
f"Failed to discover entity_id for unique_id {unique_id}: {e}",
|
|
1004
1001
|
exc_info=True,
|
|
1005
1002
|
)
|
|
1006
1003
|
return None
|
|
1007
1004
|
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1005
|
+
if entity_id is not None:
|
|
1006
|
+
logger.debug(
|
|
1007
|
+
f"Found actual entity_id for unique_id {unique_id}: {entity_id}"
|
|
1008
|
+
)
|
|
1009
|
+
return entity_id
|
|
1012
1010
|
|
|
1013
1011
|
async def delete_automation_config(self, identifier: str) -> dict[str, Any]:
|
|
1014
1012
|
"""
|
|
@@ -400,7 +400,18 @@ class HomeAssistantWebSocketClient:
|
|
|
400
400
|
try:
|
|
401
401
|
await handler(data["event"])
|
|
402
402
|
except Exception as e:
|
|
403
|
-
|
|
403
|
+
# ``exc_info=True`` so handler bugs (AttributeError /
|
|
404
|
+
# KeyError / TypeError from schema-drift on the
|
|
405
|
+
# incoming event payload) leave a traceback rather
|
|
406
|
+
# than a one-line obscured error. Without this the
|
|
407
|
+
# dispatch loop keeps a single buggy handler from
|
|
408
|
+
# killing the WS, but the bug itself becomes
|
|
409
|
+
# invisible — handlers wired to ``asyncio.Event``
|
|
410
|
+
# nudges (see ``util_helpers._ws_wait_for_condition``)
|
|
411
|
+
# silently stop nudging and the calling waiter times
|
|
412
|
+
# out reporting "not found." #1395 silent-failure
|
|
413
|
+
# audit.
|
|
414
|
+
logger.error("Error in event handler: %s", e, exc_info=True)
|
|
404
415
|
|
|
405
416
|
def _ensure_send_lock(self) -> None:
|
|
406
417
|
"""Ensure the send lock belongs to the current event loop."""
|
{ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
@@ -56,11 +56,10 @@ logger = logging.getLogger(__name__)
|
|
|
56
56
|
|
|
57
57
|
# Distinctive prefix of the soft-failure warning emitted by
|
|
58
58
|
# ``ha_config_set_automation`` when ``_poll_for_automation_entity``
|
|
59
|
-
# exhausts ``
|
|
60
|
-
# Exported so tests
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
# a silent test drift.
|
|
59
|
+
# exhausts its budget (``_POLL_BUDGET_S``) without resolving the new
|
|
60
|
+
# automation's entity_id. Exported so future tests can detect a missed
|
|
61
|
+
# registration without hard-coding the literal — rewording the warning
|
|
62
|
+
# becomes a compile-time coupling rather than a silent test drift.
|
|
64
63
|
NOT_VERIFIED_WARNING_PREFIX = (
|
|
65
64
|
"Automation was submitted to Home Assistant but the entity was not found"
|
|
66
65
|
)
|
|
@@ -614,7 +614,7 @@ costs at most ~6 REST calls per wait (one post-subscribe sample plus
|
|
|
614
614
|
|
|
615
615
|
|
|
616
616
|
async def _legacy_poll_until(
|
|
617
|
-
|
|
617
|
+
identifier: str,
|
|
618
618
|
sample: Callable[[], Awaitable[Any]],
|
|
619
619
|
*,
|
|
620
620
|
timeout: float,
|
|
@@ -627,7 +627,10 @@ async def _legacy_poll_until(
|
|
|
627
627
|
backstop tick — it returns a truthy value when the wait should
|
|
628
628
|
succeed, ``None`` otherwise. Connection / auth errors propagate
|
|
629
629
|
(callers care about those); other transient errors raised inside
|
|
630
|
-
``sample`` are swallowed there.
|
|
630
|
+
``sample`` are swallowed there. ``identifier`` is the human-readable
|
|
631
|
+
name used in log lines — usually an entity_id but may be a
|
|
632
|
+
descriptor like ``automation[unique_id=...]`` for discovery waits
|
|
633
|
+
that don't know the entity_id up front.
|
|
631
634
|
"""
|
|
632
635
|
start = time.monotonic()
|
|
633
636
|
while time.monotonic() - start < timeout:
|
|
@@ -635,7 +638,7 @@ async def _legacy_poll_until(
|
|
|
635
638
|
result = await sample()
|
|
636
639
|
if result is not None:
|
|
637
640
|
logger.debug(
|
|
638
|
-
f"REST waiter: {description} for {
|
|
641
|
+
f"REST waiter: {description} for {identifier} resolved "
|
|
639
642
|
f"after {time.monotonic() - start:.2f}s"
|
|
640
643
|
)
|
|
641
644
|
return result
|
|
@@ -643,7 +646,7 @@ async def _legacy_poll_until(
|
|
|
643
646
|
raise
|
|
644
647
|
await asyncio.sleep(poll_interval)
|
|
645
648
|
logger.warning(
|
|
646
|
-
f"REST fallback: {description} for {
|
|
649
|
+
f"REST fallback: {description} for {identifier} timed out after {timeout}s"
|
|
647
650
|
)
|
|
648
651
|
return None
|
|
649
652
|
|
|
@@ -690,22 +693,23 @@ async def _get_waiter_ws_client(client: Any) -> Any:
|
|
|
690
693
|
|
|
691
694
|
async def _ws_wait_for_condition(
|
|
692
695
|
client: Any,
|
|
693
|
-
|
|
696
|
+
identifier: str,
|
|
694
697
|
sample: Callable[[], Awaitable[Any]],
|
|
695
698
|
*,
|
|
696
699
|
event_types: tuple[str, ...],
|
|
697
700
|
timeout: float,
|
|
698
701
|
poll_interval: float,
|
|
699
702
|
description: str,
|
|
703
|
+
event_filter: Callable[[dict[str, Any]], bool] | None = None,
|
|
700
704
|
) -> Any:
|
|
701
705
|
"""Subscribe to ``event_types``, sample after subscribe, wait on event.
|
|
702
706
|
|
|
703
707
|
Implements the standard "subscribe → sample → wait" pattern from #1152:
|
|
704
708
|
|
|
705
709
|
- The handler nudges a single ``asyncio.Event`` whenever HA pushes an
|
|
706
|
-
event
|
|
707
|
-
the polling-backstop timeout, then re-runs ``sample`` (the
|
|
708
|
-
source-of-truth check) to decide whether the wait succeeded.
|
|
710
|
+
event matching ``event_filter``. The main loop wakes on that nudge
|
|
711
|
+
or on the polling-backstop timeout, then re-runs ``sample`` (the
|
|
712
|
+
REST source-of-truth check) to decide whether the wait succeeded.
|
|
709
713
|
- Sample-after-subscribe (not before) closes the gap between the
|
|
710
714
|
caller's write returning and our subscription landing on the HA
|
|
711
715
|
side. The event for the write may have already fired by the time we
|
|
@@ -714,12 +718,22 @@ async def _ws_wait_for_condition(
|
|
|
714
718
|
we fall back to ``_legacy_poll_until``. The helpers' contract is
|
|
715
719
|
identical to the pre-#1152 REST loop in that case.
|
|
716
720
|
|
|
721
|
+
``identifier`` is used only for log lines — usually an entity_id but
|
|
722
|
+
may be a descriptor like ``automation[unique_id=...]`` for discovery
|
|
723
|
+
waits (#1395) that don't know the entity_id up front. When
|
|
724
|
+
``event_filter`` is None the default predicate matches events whose
|
|
725
|
+
``data["entity_id"]`` equals ``identifier`` — i.e. the standard
|
|
726
|
+
"watch this entity_id" shape used by ``wait_for_entity_*`` /
|
|
727
|
+
``wait_for_state_change``. Callers that need a different match shape
|
|
728
|
+
(e.g. "any automation with attributes.id == unique_id") pass a custom
|
|
729
|
+
``event_filter``.
|
|
730
|
+
|
|
717
731
|
Returns ``sample``'s truthy return value, or ``None`` on timeout.
|
|
718
732
|
"""
|
|
719
733
|
ws_client = await _get_waiter_ws_client(client)
|
|
720
734
|
if ws_client is None:
|
|
721
735
|
return await _legacy_poll_until(
|
|
722
|
-
|
|
736
|
+
identifier,
|
|
723
737
|
sample,
|
|
724
738
|
timeout=timeout,
|
|
725
739
|
poll_interval=poll_interval,
|
|
@@ -728,14 +742,19 @@ async def _ws_wait_for_condition(
|
|
|
728
742
|
|
|
729
743
|
nudge = asyncio.Event()
|
|
730
744
|
|
|
731
|
-
|
|
745
|
+
def _default_filter(event: dict[str, Any]) -> bool:
|
|
732
746
|
# HA nests ``entity_id`` under ``event["data"]`` for both
|
|
733
747
|
# state_changed and entity_registry_updated. The top-level fallback
|
|
734
748
|
# is defensive only — it lets a future schema drift degrade to a
|
|
735
749
|
# missed nudge rather than an AttributeError.
|
|
736
750
|
data = event.get("data") or {}
|
|
737
751
|
evt_entity = data.get("entity_id") or event.get("entity_id")
|
|
738
|
-
|
|
752
|
+
return bool(evt_entity == identifier)
|
|
753
|
+
|
|
754
|
+
filter_fn = event_filter if event_filter is not None else _default_filter
|
|
755
|
+
|
|
756
|
+
async def handler(event: dict[str, Any]) -> None:
|
|
757
|
+
if filter_fn(event):
|
|
739
758
|
nudge.set()
|
|
740
759
|
|
|
741
760
|
# Track which handlers / subscriptions we actually attached so cleanup
|
|
@@ -763,11 +782,11 @@ async def _ws_wait_for_condition(
|
|
|
763
782
|
"falling back to REST polling",
|
|
764
783
|
et,
|
|
765
784
|
description,
|
|
766
|
-
|
|
785
|
+
identifier,
|
|
767
786
|
e,
|
|
768
787
|
)
|
|
769
788
|
return await _legacy_poll_until(
|
|
770
|
-
|
|
789
|
+
identifier,
|
|
771
790
|
sample,
|
|
772
791
|
timeout=timeout,
|
|
773
792
|
poll_interval=poll_interval,
|
|
@@ -782,7 +801,7 @@ async def _ws_wait_for_condition(
|
|
|
782
801
|
result = await sample()
|
|
783
802
|
if result is not None:
|
|
784
803
|
logger.debug(
|
|
785
|
-
f"WS waiter: {description} for {
|
|
804
|
+
f"WS waiter: {description} for {identifier} resolved by "
|
|
786
805
|
f"post-subscribe sample after {time.monotonic() - start:.2f}s"
|
|
787
806
|
)
|
|
788
807
|
return result
|
|
@@ -798,13 +817,13 @@ async def _ws_wait_for_condition(
|
|
|
798
817
|
"WS connection dropped before wait loop for %s on %s — "
|
|
799
818
|
"completing via REST polling",
|
|
800
819
|
description,
|
|
801
|
-
|
|
820
|
+
identifier,
|
|
802
821
|
)
|
|
803
822
|
remaining = timeout - (time.monotonic() - start)
|
|
804
823
|
if remaining <= 0:
|
|
805
824
|
return None
|
|
806
825
|
return await _legacy_poll_until(
|
|
807
|
-
|
|
826
|
+
identifier,
|
|
808
827
|
sample,
|
|
809
828
|
timeout=remaining,
|
|
810
829
|
poll_interval=poll_interval,
|
|
@@ -834,13 +853,13 @@ async def _ws_wait_for_condition(
|
|
|
834
853
|
"WS connection dropped during %s for %s — completing "
|
|
835
854
|
"wait via REST polling",
|
|
836
855
|
description,
|
|
837
|
-
|
|
856
|
+
identifier,
|
|
838
857
|
)
|
|
839
858
|
remaining = timeout - (time.monotonic() - start)
|
|
840
859
|
if remaining <= 0:
|
|
841
860
|
return None
|
|
842
861
|
return await _legacy_poll_until(
|
|
843
|
-
|
|
862
|
+
identifier,
|
|
844
863
|
sample,
|
|
845
864
|
timeout=remaining,
|
|
846
865
|
poll_interval=poll_interval,
|
|
@@ -851,7 +870,7 @@ async def _ws_wait_for_condition(
|
|
|
851
870
|
result = await sample()
|
|
852
871
|
if result is not None:
|
|
853
872
|
logger.debug(
|
|
854
|
-
f"WS waiter: {description} for {
|
|
873
|
+
f"WS waiter: {description} for {identifier} resolved "
|
|
855
874
|
f"after {time.monotonic() - start:.2f}s"
|
|
856
875
|
)
|
|
857
876
|
return result
|
|
@@ -859,7 +878,7 @@ async def _ws_wait_for_condition(
|
|
|
859
878
|
raise
|
|
860
879
|
|
|
861
880
|
logger.warning(
|
|
862
|
-
f"WS waiter: {description} for {
|
|
881
|
+
f"WS waiter: {description} for {identifier} timed out after {timeout}s"
|
|
863
882
|
)
|
|
864
883
|
return None
|
|
865
884
|
finally:
|
|
@@ -1084,6 +1103,132 @@ async def wait_for_state_change(
|
|
|
1084
1103
|
return None
|
|
1085
1104
|
|
|
1086
1105
|
|
|
1106
|
+
async def wait_for_automation_entity_by_unique_id(
|
|
1107
|
+
client: Any,
|
|
1108
|
+
unique_id: str,
|
|
1109
|
+
timeout: float = 6.0,
|
|
1110
|
+
poll_interval: float = 0.3,
|
|
1111
|
+
) -> str | None:
|
|
1112
|
+
"""
|
|
1113
|
+
Discover the entity_id assigned to a newly-created automation by unique_id.
|
|
1114
|
+
|
|
1115
|
+
Used after ``POST /config/automation/config/{unique_id}`` to resolve the
|
|
1116
|
+
``automation.<slug>`` entity_id Home Assistant assigned. Listens to
|
|
1117
|
+
``state_changed`` events filtered to ``automation.*`` entities whose
|
|
1118
|
+
``new_state.attributes.id`` equals ``unique_id`` — HA's
|
|
1119
|
+
``BaseAutomationEntity.capability_attributes`` exposes ``unique_id`` as
|
|
1120
|
+
``CONF_ID`` on every emit, so the first state event for a fresh
|
|
1121
|
+
automation carries the match. Falls back to REST polling of
|
|
1122
|
+
``get_states()`` when the WebSocket is unavailable. See #1152 / #1395.
|
|
1123
|
+
|
|
1124
|
+
Args:
|
|
1125
|
+
client: HomeAssistantClient instance
|
|
1126
|
+
unique_id: The unique_id passed to ``POST /config/automation/config/{unique_id}``
|
|
1127
|
+
timeout: Maximum time to wait in seconds (preserves the legacy 6s budget)
|
|
1128
|
+
poll_interval: REST poll interval used for the WS-unavailable fallback
|
|
1129
|
+
|
|
1130
|
+
Returns:
|
|
1131
|
+
The discovered entity_id (e.g. ``"automation.morning_routine"``)
|
|
1132
|
+
or ``None`` on timeout.
|
|
1133
|
+
"""
|
|
1134
|
+
# Mutable closure cells: ``entity_id`` stashes the discovered
|
|
1135
|
+
# entity_id when the filter sees a matching event (sample() then
|
|
1136
|
+
# short-circuits the full get_states() scan). ``last_api_error``
|
|
1137
|
+
# tracks the most recent transient API failure during sampling so
|
|
1138
|
+
# the final timeout warning can distinguish "automation truly not
|
|
1139
|
+
# found" from "REST channel wedged the whole budget."
|
|
1140
|
+
captured: dict[str, str | None] = {"entity_id": None, "last_api_error": None}
|
|
1141
|
+
|
|
1142
|
+
async def sample() -> str | None:
|
|
1143
|
+
if captured["entity_id"] is not None:
|
|
1144
|
+
return captured["entity_id"]
|
|
1145
|
+
try:
|
|
1146
|
+
states = await client.get_states()
|
|
1147
|
+
except HomeAssistantAPIError as e:
|
|
1148
|
+
# Debug-level here is intentional — the waiter retries on
|
|
1149
|
+
# transient errors. The wedged-channel signal goes in the
|
|
1150
|
+
# final timeout warning via ``captured["last_api_error"]``.
|
|
1151
|
+
logger.debug(
|
|
1152
|
+
f"API error sampling get_states() for unique_id {unique_id}: {e}"
|
|
1153
|
+
)
|
|
1154
|
+
captured["last_api_error"] = str(e)
|
|
1155
|
+
return None
|
|
1156
|
+
for state in states:
|
|
1157
|
+
entity_id = state.get("entity_id")
|
|
1158
|
+
if not isinstance(entity_id, str) or not entity_id.startswith(
|
|
1159
|
+
"automation."
|
|
1160
|
+
):
|
|
1161
|
+
continue
|
|
1162
|
+
if state.get("attributes", {}).get("id") == unique_id:
|
|
1163
|
+
return entity_id
|
|
1164
|
+
return None
|
|
1165
|
+
|
|
1166
|
+
def event_filter(event: dict[str, Any]) -> bool:
|
|
1167
|
+
# state_changed payload shape:
|
|
1168
|
+
# event["data"] = {"entity_id": ..., "new_state": {"attributes": {...}}}
|
|
1169
|
+
# capability_attributes on BaseAutomationEntity guarantees attributes.id
|
|
1170
|
+
# carries the unique_id on the first state emission (the caller has
|
|
1171
|
+
# always just POSTed /config/automation/config/{unique_id}, so
|
|
1172
|
+
# unique_id is non-None by construction).
|
|
1173
|
+
#
|
|
1174
|
+
# Defensive ``isinstance`` guards mirror the ``sample()`` callback
|
|
1175
|
+
# above — the WS dispatcher swallows handler exceptions broadly
|
|
1176
|
+
# (``websocket_client.py``'s ``except Exception`` in the dispatch
|
|
1177
|
+
# loop), so a malformed payload reaching ``.startswith`` here would
|
|
1178
|
+
# silently fail to nudge and the wait would time out reporting
|
|
1179
|
+
# "not found" when the real cause was schema drift. Same shape-
|
|
1180
|
+
# hardening pattern as ``sample()``.
|
|
1181
|
+
data = event.get("data") or {}
|
|
1182
|
+
evt_entity = data.get("entity_id")
|
|
1183
|
+
if not isinstance(evt_entity, str) or not evt_entity.startswith("automation."):
|
|
1184
|
+
return False
|
|
1185
|
+
new_state = data.get("new_state") or {}
|
|
1186
|
+
attrs = new_state.get("attributes") if isinstance(new_state, dict) else None
|
|
1187
|
+
if not isinstance(attrs, dict) or attrs.get("id") != unique_id:
|
|
1188
|
+
return False
|
|
1189
|
+
# Guard against last-writer-wins: if two events for matching
|
|
1190
|
+
# automations arrived (HA storage forbids duplicate unique_id, but
|
|
1191
|
+
# don't coin-flip silently if it ever happens), keep the first
|
|
1192
|
+
# observed entity_id and log the collision.
|
|
1193
|
+
if captured["entity_id"] is None:
|
|
1194
|
+
captured["entity_id"] = evt_entity
|
|
1195
|
+
elif captured["entity_id"] != evt_entity:
|
|
1196
|
+
logger.warning(
|
|
1197
|
+
"Duplicate automation match for unique_id %s: %s already "
|
|
1198
|
+
"captured, ignoring %s",
|
|
1199
|
+
unique_id,
|
|
1200
|
+
captured["entity_id"],
|
|
1201
|
+
evt_entity,
|
|
1202
|
+
)
|
|
1203
|
+
return True
|
|
1204
|
+
|
|
1205
|
+
result = await _ws_wait_for_condition(
|
|
1206
|
+
client,
|
|
1207
|
+
identifier=f"automation[unique_id={unique_id}]",
|
|
1208
|
+
sample=sample,
|
|
1209
|
+
event_types=("state_changed",),
|
|
1210
|
+
timeout=timeout,
|
|
1211
|
+
poll_interval=poll_interval,
|
|
1212
|
+
description="automation entity discovery",
|
|
1213
|
+
event_filter=event_filter,
|
|
1214
|
+
)
|
|
1215
|
+
if isinstance(result, str):
|
|
1216
|
+
return result
|
|
1217
|
+
# `_ws_wait_for_condition` / `_legacy_poll_until` already logged the
|
|
1218
|
+
# generic "timed out" warning before returning None; just surface the
|
|
1219
|
+
# discovery-specific signal when REST sampling was wedged the whole
|
|
1220
|
+
# budget so operators can distinguish "automation never published"
|
|
1221
|
+
# from "REST channel down."
|
|
1222
|
+
if captured["last_api_error"] is not None:
|
|
1223
|
+
logger.warning(
|
|
1224
|
+
"Automation discovery for unique_id %s timed out with every "
|
|
1225
|
+
"REST sample failing; last error: %s",
|
|
1226
|
+
unique_id,
|
|
1227
|
+
captured["last_api_error"],
|
|
1228
|
+
)
|
|
1229
|
+
return None
|
|
1230
|
+
|
|
1231
|
+
|
|
1087
1232
|
async def fetch_entity_category(client: Any, entity_id: str, scope: str) -> str | None:
|
|
1088
1233
|
"""Fetch a category ID for an entity from the entity registry.
|
|
1089
1234
|
|
|
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.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/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
|
{ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/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
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_config_entry_flow.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_config_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/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.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/tools/tools_voice_assistant.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp/transforms/categorized_search.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/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.dev572 → ha_mcp_dev-7.5.0.dev574}/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.dev572 → ha_mcp_dev-7.5.0.dev574}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.5.0.dev572 → ha_mcp_dev-7.5.0.dev574}/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
|