ha-mcp-dev 7.13.0.dev2005__tar.gz → 7.13.0.dev2009__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.
Files changed (187) hide show
  1. {ha_mcp_dev-7.13.0.dev2005/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.13.0.dev2009}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/pyproject.toml +1 -10
  3. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/client/rest_client.py +69 -19
  4. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/component_api.py +9 -1
  5. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/radio/matter.py +7 -0
  6. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/radio/zigbee.py +15 -0
  7. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/radio/zwave.py +14 -0
  8. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/reference_validator.py +58 -35
  9. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_areas.py +205 -141
  10. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_config_automations.py +63 -10
  11. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_config_scenes.py +50 -16
  12. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_config_scripts.py +64 -16
  13. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_dev.py +141 -0
  14. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_history.py +83 -57
  15. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_traces.py +99 -90
  16. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_zones.py +67 -52
  17. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/transforms/categorized_search.py +102 -79
  18. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/visibility/resolver.py +151 -71
  19. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  20. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/LICENSE +0 -0
  21. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/MANIFEST.in +0 -0
  22. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/README.md +0 -0
  23. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/setup.cfg +0 -0
  24. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/__init__.py +0 -0
  25. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/__main__.py +0 -0
  26. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/_pypi_marker +0 -0
  27. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/_version.py +0 -0
  28. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/auth/__init__.py +0 -0
  29. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/auth/consent_form.py +0 -0
  30. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/auth/provider.py +0 -0
  31. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/backup_manager.py +0 -0
  32. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/browser_landing.py +0 -0
  33. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/client/__init__.py +0 -0
  34. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/client/supervisor_client.py +0 -0
  35. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/client/websocket_client.py +0 -0
  36. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/client/websocket_listener.py +0 -0
  37. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/config.py +0 -0
  38. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  39. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  40. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  41. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  42. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  43. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  44. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/errors.py +0 -0
  45. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/llm_exposure.py +0 -0
  46. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/policy/__init__.py +0 -0
  47. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/policy/approval_queue.py +0 -0
  48. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/policy/evaluator.py +0 -0
  49. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/policy/handlers.py +0 -0
  50. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/policy/middleware.py +0 -0
  51. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/policy/model.py +0 -0
  52. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/policy/persistence.py +0 -0
  53. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/policy/value_sources.py +0 -0
  54. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/py.typed +0 -0
  55. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/read_only.py +0 -0
  56. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  57. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  58. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  59. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  60. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  61. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  62. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  63. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  64. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  65. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  66. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  67. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  68. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  69. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  70. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  71. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  72. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  73. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  74. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  75. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  76. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  77. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  78. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  79. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  80. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  81. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/server.py +0 -0
  82. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/__init__.py +0 -0
  83. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  84. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  85. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  86. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  87. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  88. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  89. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  90. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  91. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  92. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/_theme.py +0 -0
  93. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  94. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  95. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  96. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/locales/en.json +0 -0
  97. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  98. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/settings.css +0 -0
  99. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/settings.html +0 -0
  100. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/settings_ui/settings.js +0 -0
  101. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/smoke_test.py +0 -0
  102. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  103. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/strict_bps.py +0 -0
  104. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/__init__.py +0 -0
  105. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/auto_backup.py +0 -0
  106. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/backup.py +0 -0
  107. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  108. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/component_config_reads.py +0 -0
  109. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/component_devices.py +0 -0
  110. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/component_registries.py +0 -0
  111. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  112. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  113. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/device_control.py +0 -0
  114. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/enhanced.py +0 -0
  115. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/helpers.py +0 -0
  116. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/radio/__init__.py +0 -0
  117. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/radio/base.py +0 -0
  118. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/radio/thread.py +0 -0
  119. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/registry.py +0 -0
  120. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  121. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  122. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  123. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  124. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  125. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  126. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  127. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  128. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  129. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  130. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_addons.py +0 -0
  131. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  132. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  133. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_calendar.py +0 -0
  134. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_camera.py +0 -0
  135. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_categories.py +0 -0
  136. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_code.py +0 -0
  137. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  138. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  139. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  140. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_energy.py +0 -0
  141. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_entities.py +0 -0
  142. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  143. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_groups.py +0 -0
  144. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_hacs.py +0 -0
  145. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_integrations.py +0 -0
  146. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_labels.py +0 -0
  147. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_radio.py +0 -0
  148. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_registry.py +0 -0
  149. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_resources.py +0 -0
  150. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_search.py +0 -0
  151. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_service.py +0 -0
  152. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_services.py +0 -0
  153. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_system.py +0 -0
  154. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_themes.py +0 -0
  155. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_todo.py +0 -0
  156. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_updates.py +0 -0
  157. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_utility.py +0 -0
  158. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  159. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  160. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  161. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/util_helpers.py +0 -0
  162. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/tools/validation_middleware.py +0 -0
  163. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/transforms/__init__.py +0 -0
  164. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  165. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/transport_security.py +0 -0
  166. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/update_check.py +0 -0
  167. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/utils/__init__.py +0 -0
  168. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/utils/config_hash.py +0 -0
  169. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/utils/data_paths.py +0 -0
  170. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/utils/domain_handlers.py +0 -0
  171. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  172. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  173. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/utils/operation_manager.py +0 -0
  174. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/utils/python_sandbox.py +0 -0
  175. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/utils/skill_loader.py +0 -0
  176. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/utils/usage_logger.py +0 -0
  177. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/visibility/__init__.py +0 -0
  178. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/visibility/model.py +0 -0
  179. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp/visibility/persistence.py +0 -0
  180. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  181. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  182. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  183. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  184. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  185. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/tests/__init__.py +0 -0
  186. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/tests/test_constants.py +0 -0
  187. {ha_mcp_dev-7.13.0.dev2005 → ha_mcp_dev-7.13.0.dev2009}/tests/test_env_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.13.0.dev2005
3
+ Version: 7.13.0.dev2009
4
4
  Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
5
5
  Author-email: Julien <github@qc-h.net>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ha-mcp-dev"
7
- version = "7.13.0.dev2005"
7
+ version = "7.13.0.dev2009"
8
8
  description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
9
9
  readme = "README.md"
10
10
  # Upper bound widened to <3.15 so the package installs on Home Assistant's
@@ -178,21 +178,12 @@ ignore = [
178
178
  "src/ha_mcp/backup_manager.py" = ["C901"]
179
179
  "src/ha_mcp/tools/auto_backup.py" = ["C901"]
180
180
  "src/ha_mcp/tools/backup.py" = ["C901"]
181
- "src/ha_mcp/tools/radio/matter.py" = ["C901"]
182
- "src/ha_mcp/tools/radio/zigbee.py" = ["C901"]
183
- "src/ha_mcp/tools/radio/zwave.py" = ["C901"]
184
- "src/ha_mcp/tools/reference_validator.py" = ["C901"]
185
- "src/ha_mcp/tools/tools_areas.py" = ["C901"]
186
181
  "src/ha_mcp/tools/tools_config_automations.py" = ["C901"]
187
182
  "src/ha_mcp/tools/tools_config_scenes.py" = ["C901"]
188
183
  "src/ha_mcp/tools/tools_config_scripts.py" = ["C901"]
189
184
  "src/ha_mcp/tools/tools_dev.py" = ["C901"]
190
- "src/ha_mcp/tools/tools_history.py" = ["C901"]
191
185
  "src/ha_mcp/tools/tools_integrations.py" = ["C901"]
192
186
  "src/ha_mcp/tools/tools_system.py" = ["C901"]
193
- "src/ha_mcp/tools/tools_zones.py" = ["C901"]
194
- "src/ha_mcp/transforms/categorized_search.py" = ["C901"]
195
- "src/ha_mcp/visibility/resolver.py" = ["C901"]
196
187
  "tests/src/e2e/conftest.py" = ["C901"]
197
188
  "tests/src/e2e/error_handling/test_network_errors.py" = ["C901"]
198
189
  "tests/src/e2e/policy/test_readonly_mode.py" = ["C901"]
@@ -975,7 +975,11 @@ class HomeAssistantClient:
975
975
  raise
976
976
 
977
977
  async def upsert_automation_config(
978
- self, config: dict[str, Any], identifier: str | None = None
978
+ self,
979
+ config: dict[str, Any],
980
+ identifier: str | None = None,
981
+ *,
982
+ _resolved: bool = False,
979
983
  ) -> dict[str, Any]:
980
984
  """
981
985
  Create new automation or update existing one.
@@ -983,6 +987,12 @@ class HomeAssistantClient:
983
987
  Args:
984
988
  config: Automation configuration dictionary
985
989
  identifier: Optional automation entity_id or unique_id (None = create new)
990
+ _resolved: When True, ``identifier`` is already the resolved unique_id
991
+ (the caller ran ``_resolve_automation_id``), so the redundant
992
+ entity_id→unique_id lookup is skipped. Ignored on the create path
993
+ (``identifier is None``). The #1404 config-id mismatch guard below
994
+ is unaffected — it still compares ``config['id']`` to the resolved
995
+ unique_id.
986
996
 
987
997
  Returns:
988
998
  Result with automation unique_id and status
@@ -996,7 +1006,11 @@ class HomeAssistantClient:
996
1006
  operation = "created"
997
1007
  logger.debug(f"Creating new automation with unique_id: {unique_id}")
998
1008
  else:
999
- unique_id = await self._resolve_automation_id(identifier)
1009
+ unique_id = (
1010
+ identifier
1011
+ if _resolved
1012
+ else await self._resolve_automation_id(identifier)
1013
+ )
1000
1014
  operation = "updated"
1001
1015
  logger.debug(f"Updating automation with unique_id: {unique_id}")
1002
1016
 
@@ -1592,14 +1606,33 @@ class HomeAssistantClient:
1592
1606
  raise
1593
1607
 
1594
1608
  async def upsert_script_config(
1595
- self, config: dict[str, Any], script_id: str
1609
+ self,
1610
+ config: dict[str, Any],
1611
+ script_id: str,
1612
+ *,
1613
+ resolved_id: str | None = None,
1596
1614
  ) -> dict[str, Any]:
1597
- """Create or update Home Assistant script configuration."""
1598
- resolved_id = await self._resolve_script_id(script_id)
1615
+ """Create or update Home Assistant script configuration.
1616
+
1617
+ ``resolved_id``, when provided, is the already-resolved storage key used
1618
+ as the write target (the caller ran :meth:`_resolve_script_id`),
1619
+ skipping the redundant entity-registry lookup. ``script_id`` stays the
1620
+ CALLER's identifier and is used only to default a missing ``alias`` — a
1621
+ renamed script (whose storage key differs from the entity slug the
1622
+ caller passed) keeps its caller-facing name rather than resetting the
1623
+ user-visible alias to the stale storage key (#1935).
1624
+ """
1625
+ write_id = (
1626
+ resolved_id
1627
+ if resolved_id is not None
1628
+ else await self._resolve_script_id(script_id)
1629
+ )
1599
1630
  try:
1600
- endpoint = f"config/script/config/{resolved_id}"
1631
+ endpoint = f"config/script/config/{write_id}"
1601
1632
 
1602
- # Validate required fields
1633
+ # Default a missing alias from the CALLER's ``script_id`` (not
1634
+ # ``write_id``) so a renamed script's user-visible name is preserved
1635
+ # instead of being reset to the storage key.
1603
1636
  if "alias" not in config:
1604
1637
  config["alias"] = script_id
1605
1638
 
@@ -1614,7 +1647,7 @@ class HomeAssistantClient:
1614
1647
 
1615
1648
  return {
1616
1649
  "success": True,
1617
- "script_id": resolved_id,
1650
+ "script_id": write_id,
1618
1651
  "result": response.get("result", "ok"),
1619
1652
  "operation": "created" if response.get("result") == "ok" else "updated",
1620
1653
  }
@@ -1722,22 +1755,39 @@ class HomeAssistantClient:
1722
1755
  raise
1723
1756
 
1724
1757
  async def upsert_scene_config(
1725
- self, config: dict[str, Any], scene_id: str, *, _resolved: bool = False
1758
+ self,
1759
+ config: dict[str, Any],
1760
+ scene_id: str,
1761
+ *,
1762
+ resolved_id: str | None = None,
1726
1763
  ) -> dict[str, Any]:
1727
1764
  """Create or update Home Assistant scene configuration.
1728
1765
 
1729
- ``_resolved=True`` signals ``scene_id`` is already the resolved storage
1730
- key, skipping the redundant registry lookup (``resolve_scene_id`` is
1731
- idempotent, so the endpoint id is unchanged).
1766
+ ``resolved_id``, when provided, is the already-resolved storage key used
1767
+ as the write target (the caller ran :meth:`resolve_scene_id`), skipping
1768
+ the redundant registry lookup. ``scene_id`` stays the CALLER's identifier
1769
+ and is used only to default a missing ``name`` — a renamed scene (whose
1770
+ storage key differs from the slug the caller passed) keeps its
1771
+ caller-facing name rather than resetting to the stale storage key
1772
+ (#1935, mirrors the script fix).
1732
1773
  """
1733
- resolved_id = scene_id if _resolved else await self.resolve_scene_id(scene_id)
1774
+ write_id = (
1775
+ resolved_id
1776
+ if resolved_id is not None
1777
+ else await self.resolve_scene_id(scene_id)
1778
+ )
1734
1779
  try:
1735
- endpoint = f"config/scene/config/{resolved_id}"
1736
-
1737
- # Default a name when missing mirrors the script upsert behaviour
1738
- # so a bare config dict is still acceptable.
1780
+ endpoint = f"config/scene/config/{write_id}"
1781
+
1782
+ # Default a name when missing from the CALLER's ``scene_id`` (not
1783
+ # ``write_id``) mirrors the script upsert behaviour so a bare
1784
+ # config dict is acceptable, without resetting a renamed scene's
1785
+ # name to the storage key. Strip the ``scene.`` prefix: a caller may
1786
+ # pass the entity_id form (``resolve_scene_id`` accepts it), and HA
1787
+ # derives the entity_id from the name slug — persisting the prefixed
1788
+ # form would rename the scene and change its entity_id on a plain update.
1739
1789
  if "name" not in config:
1740
- config["name"] = scene_id
1790
+ config["name"] = scene_id.removeprefix("scene.")
1741
1791
 
1742
1792
  # Validate required field. ``entities`` is a dict keyed by entity_id,
1743
1793
  # not a list — distinct from script ``sequence`` and automation ``action``.
@@ -1757,7 +1807,7 @@ class HomeAssistantClient:
1757
1807
  # activity should diff before/after via ``ha_config_get_scene``.
1758
1808
  return {
1759
1809
  "success": True,
1760
- "scene_id": resolved_id,
1810
+ "scene_id": write_id,
1761
1811
  "result": response.get("result", "ok"),
1762
1812
  }
1763
1813
  except Exception as e:
@@ -273,9 +273,17 @@ async def get_component_caps(client: Any) -> ComponentCaps | None:
273
273
  if not base_url or not token:
274
274
  # Not a credentialed HA client — no WS connection to negotiate over.
275
275
  return None
276
+ # Thread verify_ssl so a verify_ssl=False client keys (and can establish)
277
+ # its own pooled connection rather than failing the probe or colliding with
278
+ # a default-verification entry — otherwise EVERY component capability is
279
+ # silently unreachable on an HTTPS + verify_ssl=False install. ``None`` is
280
+ # the pool's settings default, so a client without the attr is unchanged.
281
+ verify_ssl = getattr(client, "verify_ssl", None)
276
282
 
277
283
  try:
278
- ws = await get_websocket_client(url=base_url, token=token)
284
+ ws = await get_websocket_client(
285
+ url=base_url, token=token, verify_ssl=verify_ssl
286
+ )
279
287
  response = await ws.send_command(INFO_COMMAND)
280
288
  except HomeAssistantCommandError:
281
289
  # Old component (unknown_command) or an info handler bug: the
@@ -145,6 +145,13 @@ async def handle(client: Any, action: str, args: dict[str, Any]) -> dict[str, An
145
145
  # Returns setup_pin_code, setup_manual_code, setup_qr_code.
146
146
  return ok("matter", "share_out", commissioning=commissioning)
147
147
 
148
+ return await _handle_matter_more(client, action, args, device_id)
149
+
150
+
151
+ async def _handle_matter_more(
152
+ client: Any, action: str, args: dict[str, Any], device_id: Any
153
+ ) -> dict[str, Any]:
154
+ """Continuation of :func:`handle` for the remaining Matter actions."""
148
155
  if action == "interview":
149
156
  result = await ws_call(
150
157
  client,
@@ -272,6 +272,13 @@ async def handle(client: Any, action: str, args: dict[str, Any]) -> dict[str, An
272
272
  long_running=True,
273
273
  )
274
274
 
275
+ return await _handle_zigbee_groups(client, action, args, device_id)
276
+
277
+
278
+ async def _handle_zigbee_groups(
279
+ client: Any, action: str, args: dict[str, Any], device_id: Any
280
+ ) -> dict[str, Any]:
281
+ """Continuation of :func:`handle` for Zigbee group and binding actions."""
275
282
  # --- groups ------------------------------------------------------------ #
276
283
  if action == "group_add":
277
284
  group = await ws_call(
@@ -340,6 +347,14 @@ async def handle(client: Any, action: str, args: dict[str, Any]) -> dict[str, An
340
347
  )
341
348
  return ok("zigbee", "unbind", result=result)
342
349
 
350
+ return await _handle_zigbee_clusters(client, action, args, device_id)
351
+
352
+
353
+ async def _handle_zigbee_clusters(
354
+ client: Any, action: str, args: dict[str, Any], device_id: Any
355
+ ) -> dict[str, Any]:
356
+ """Continuation of :func:`handle` for Zigbee cluster-write and network actions."""
357
+ cluster_type = args.get("cluster_type", "in")
343
358
  # --- cluster writes (service-only) ------------------------------------- #
344
359
  if action == "cluster_write":
345
360
  ieee = await _resolve_ieee(client, device_id)
@@ -174,6 +174,13 @@ async def handle(client: Any, action: str, args: dict[str, Any]) -> dict[str, An
174
174
  result = await client.call_service("zwave_js", "ping", {"device_id": device_id})
175
175
  return ok("zwave", "ping", result=result)
176
176
 
177
+ return await _handle_zwave_inclusion(client, action, args, device_id)
178
+
179
+
180
+ async def _handle_zwave_inclusion(
181
+ client: Any, action: str, args: dict[str, Any], device_id: Any
182
+ ) -> dict[str, Any]:
183
+ """Continuation of :func:`handle` for Z-Wave inclusion/exclusion actions."""
177
184
  if action == "add":
178
185
  entry_id = await _zwave_entry_id(client)
179
186
  if not entry_id:
@@ -289,6 +296,13 @@ async def handle(client: Any, action: str, args: dict[str, Any]) -> dict[str, An
289
296
  long_running=True,
290
297
  )
291
298
 
299
+ return await _handle_zwave_maintenance(client, action, args, device_id)
300
+
301
+
302
+ async def _handle_zwave_maintenance(
303
+ client: Any, action: str, args: dict[str, Any], device_id: Any
304
+ ) -> dict[str, Any]:
305
+ """Continuation of :func:`handle` for Z-Wave node maintenance actions."""
292
306
  if action == "reinterview":
293
307
  result = await ws_call(
294
308
  client,
@@ -69,6 +69,55 @@ class ValidationWarning(TypedDict):
69
69
  reason: str
70
70
 
71
71
 
72
+ def _extract_service_ref(
73
+ sub_path: str,
74
+ value: str,
75
+ refs: list[ExtractedRef],
76
+ unvalidated_templates: list[int],
77
+ ) -> None:
78
+ """Record a service ref at *sub_path*, counting templates instead."""
79
+ if _is_template(value):
80
+ unvalidated_templates[0] += 1
81
+ else:
82
+ refs.append({"path": sub_path, "value": value, "kind": "service"})
83
+
84
+
85
+ def _extract_entity_refs(
86
+ sub_path: str,
87
+ value: Any,
88
+ refs: list[ExtractedRef],
89
+ unvalidated_templates: list[int],
90
+ ) -> bool:
91
+ """Record entity ref(s) from a str or list *value*; return True if handled.
92
+
93
+ Returns False when *value* is neither a str nor a list so the caller can
94
+ keep recursing into it (an ``entity_id`` mapped to a nested structure).
95
+ """
96
+ if isinstance(value, str):
97
+ if _is_template(value):
98
+ unvalidated_templates[0] += 1
99
+ else:
100
+ refs.append({"path": sub_path, "value": value, "kind": "entity"})
101
+ return True
102
+ if isinstance(value, list):
103
+ for i, item in enumerate(value):
104
+ if not isinstance(item, str):
105
+ continue
106
+ item_path = f"{sub_path}[{i}]"
107
+ if _is_template(item):
108
+ unvalidated_templates[0] += 1
109
+ else:
110
+ refs.append(
111
+ {
112
+ "path": item_path,
113
+ "value": item,
114
+ "kind": "entity",
115
+ }
116
+ )
117
+ return True
118
+ return False
119
+
120
+
72
121
  def extract_refs(config: Any) -> WalkerResult:
73
122
  """Pull every literal service/entity reference out of *config*.
74
123
 
@@ -97,43 +146,17 @@ def extract_refs(config: Any) -> WalkerResult:
97
146
  sub_path = f"{path}.{key}" if path else key
98
147
 
99
148
  if key in _SERVICE_KEYS and isinstance(value, str):
100
- if _is_template(value):
101
- unvalidated_templates[0] += 1
102
- else:
103
- refs.append(
104
- {"path": sub_path, "value": value, "kind": "service"}
105
- )
149
+ _extract_service_ref(sub_path, value, refs, unvalidated_templates)
150
+ continue
151
+
152
+ if key in _ENTITY_KEYS and _extract_entity_refs(
153
+ sub_path, value, refs, unvalidated_templates
154
+ ):
106
155
  continue
107
156
 
108
- if key in _ENTITY_KEYS:
109
- if isinstance(value, str):
110
- if _is_template(value):
111
- unvalidated_templates[0] += 1
112
- else:
113
- refs.append(
114
- {"path": sub_path, "value": value, "kind": "entity"}
115
- )
116
- continue
117
- if isinstance(value, list):
118
- for i, item in enumerate(value):
119
- if not isinstance(item, str):
120
- continue
121
- item_path = f"{sub_path}[{i}]"
122
- if _is_template(item):
123
- unvalidated_templates[0] += 1
124
- else:
125
- refs.append(
126
- {
127
- "path": item_path,
128
- "value": item,
129
- "kind": "entity",
130
- }
131
- )
132
- continue
133
-
134
- # Neither a service nor an entity key: recurse so deeply
135
- # nested action blocks (choose/if/parallel/repeat) still
136
- # get walked.
157
+ # Neither a service nor an entity key (or an entity key whose
158
+ # value is neither str nor list): recurse so deeply nested
159
+ # action blocks (choose/if/parallel/repeat) still get walked.
137
160
  _walk(value, sub_path)
138
161
 
139
162
  elif isinstance(node, list):