ha-mcp-dev 7.4.1.dev458__tar.gz → 7.4.1.dev459__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 (108) hide show
  1. {ha_mcp_dev-7.4.1.dev458/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.4.1.dev459}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/server.py +63 -10
  4. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/best_practice_checker.py +237 -24
  5. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_config_automations.py +19 -8
  6. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_config_scripts.py +13 -10
  7. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  8. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/LICENSE +0 -0
  9. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/MANIFEST.in +0 -0
  10. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/README.md +0 -0
  11. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/setup.cfg +0 -0
  12. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/__init__.py +0 -0
  13. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/__main__.py +0 -0
  14. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/_pypi_marker +0 -0
  15. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/_version.py +0 -0
  16. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/auth/__init__.py +0 -0
  17. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/auth/consent_form.py +0 -0
  18. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/auth/provider.py +0 -0
  19. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/client/__init__.py +0 -0
  20. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/client/rest_client.py +0 -0
  21. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/client/websocket_client.py +0 -0
  22. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/client/websocket_listener.py +0 -0
  23. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/config.py +0 -0
  24. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/errors.py +0 -0
  25. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/py.typed +0 -0
  26. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  27. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  28. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  29. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  30. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  31. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  32. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  33. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  34. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  35. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  36. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  37. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  38. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  39. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  40. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  41. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  42. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  43. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  44. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  45. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  46. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/settings_ui.py +0 -0
  47. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/smoke_test.py +0 -0
  48. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/__init__.py +0 -0
  49. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/backup.py +0 -0
  50. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/device_control.py +0 -0
  51. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/enhanced.py +0 -0
  52. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/helpers.py +0 -0
  53. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/reference_validator.py +0 -0
  54. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/registry.py +0 -0
  55. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/smart_search.py +0 -0
  56. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_addons.py +0 -0
  57. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_areas.py +0 -0
  58. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  59. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  60. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_calendar.py +0 -0
  61. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_camera.py +0 -0
  62. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_categories.py +0 -0
  63. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_code.py +0 -0
  64. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  65. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  66. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  67. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_energy.py +0 -0
  68. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_entities.py +0 -0
  69. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  70. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_groups.py +0 -0
  71. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_hacs.py +0 -0
  72. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_history.py +0 -0
  73. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_integrations.py +0 -0
  74. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_labels.py +0 -0
  75. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  76. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_registry.py +0 -0
  77. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_resources.py +0 -0
  78. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_search.py +0 -0
  79. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_service.py +0 -0
  80. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_services.py +0 -0
  81. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_system.py +0 -0
  82. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_todo.py +0 -0
  83. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_traces.py +0 -0
  84. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_updates.py +0 -0
  85. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_utility.py +0 -0
  86. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  87. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  88. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/tools_zones.py +0 -0
  89. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/tools/util_helpers.py +0 -0
  90. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/transforms/__init__.py +0 -0
  91. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/transforms/categorized_search.py +0 -0
  92. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/utils/__init__.py +0 -0
  93. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/utils/config_hash.py +0 -0
  94. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/utils/data_paths.py +0 -0
  95. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/utils/domain_handlers.py +0 -0
  96. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  97. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  98. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/utils/operation_manager.py +0 -0
  99. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/utils/python_sandbox.py +0 -0
  100. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp/utils/usage_logger.py +0 -0
  101. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  102. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  103. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  104. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  105. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  106. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/tests/__init__.py +0 -0
  107. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/tests/test_constants.py +0 -0
  108. {ha_mcp_dev-7.4.1.dev458 → ha_mcp_dev-7.4.1.dev459}/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.4.1.dev458
3
+ Version: 7.4.1.dev459
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.4.1.dev458"
7
+ version = "7.4.1.dev459"
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"
@@ -58,6 +58,48 @@ class HaResourcesAsTools(ResourcesAsTools):
58
58
  "list_resources": LIST_TOOL_NAME,
59
59
  "read_resource": READ_TOOL_NAME,
60
60
  }
61
+ # Shared action-phrased keyword block for retrieval. Some MCP clients
62
+ # (Claude Code, others) rank candidate tools by token-overlap between
63
+ # the user's natural-language query and each tool's `description`
64
+ # field; FastMCP's terse defaults ("List MCP resources") never overlap
65
+ # with task-phrased queries like "create automation" or "writing
66
+ # trigger". This block lists the workflow positions where consulting
67
+ # the bundled skill reference files matters, so retrieval surfaces
68
+ # this tool when an agent is about to write config.
69
+ _USE_BEFORE_KEYWORDS = (
70
+ "Use BEFORE: creating or editing automations, scripts, scenes, "
71
+ "helpers, or dashboards; writing triggers, conditions, actions, "
72
+ "wait_template, or service calls; renaming entities or migrating "
73
+ "device_id to entity_id; calling ha_config_set_automation, "
74
+ "ha_config_set_script, ha_config_set_helper, ha_config_set_dashboard, "
75
+ "or ha_set_entity."
76
+ )
77
+ _DESCRIPTIONS: ClassVar[dict[str, str]] = {
78
+ LIST_TOOL_NAME: (
79
+ "List all available MCP resources, including bundled skill "
80
+ "reference files. " + _USE_BEFORE_KEYWORDS + " Pair with "
81
+ "ha_read_resource to load a specific guide."
82
+ ),
83
+ READ_TOOL_NAME: (
84
+ "Get the contents of an MCP resource by URI. Use this to load "
85
+ "skill reference files (e.g., "
86
+ "skill://home-assistant-best-practices/references/"
87
+ "automation-patterns.md) for guidance on native conditions and "
88
+ "triggers, helper selection, automation modes, template "
89
+ "guidelines, device control, and safe refactoring. "
90
+ + _USE_BEFORE_KEYWORDS
91
+ + " Use ha_list_resources to discover available URIs."
92
+ ),
93
+ }
94
+
95
+ @classmethod
96
+ def _rewrite(cls, tool: Tool, new_name: str) -> Tool:
97
+ """Return a copy of ``tool`` renamed and re-described for ha-mcp."""
98
+ update: dict[str, Any] = {"name": new_name}
99
+ description = cls._DESCRIPTIONS.get(new_name)
100
+ if description is not None:
101
+ update["description"] = description
102
+ return tool.model_copy(update=update)
61
103
 
62
104
  async def list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
63
105
  # Scan the entire result rather than slicing the tail so a future
@@ -72,7 +114,7 @@ class HaResourcesAsTools(ResourcesAsTools):
72
114
  if new_name is None:
73
115
  renamed.append(tool)
74
116
  continue
75
- renamed.append(tool.model_copy(update={"name": new_name}))
117
+ renamed.append(self._rewrite(tool, new_name))
76
118
  matches += 1
77
119
  if matches != len(self._RENAMES):
78
120
  logger.warning(
@@ -93,13 +135,9 @@ class HaResourcesAsTools(ResourcesAsTools):
93
135
  version: VersionSpec | None = None,
94
136
  ) -> Tool | None:
95
137
  if name == self.LIST_TOOL_NAME:
96
- return self._make_list_resources_tool().model_copy(
97
- update={"name": self.LIST_TOOL_NAME}
98
- )
138
+ return self._rewrite(self._make_list_resources_tool(), self.LIST_TOOL_NAME)
99
139
  if name == self.READ_TOOL_NAME:
100
- return self._make_read_resource_tool().model_copy(
101
- update={"name": self.READ_TOOL_NAME}
102
- )
140
+ return self._rewrite(self._make_read_resource_tool(), self.READ_TOOL_NAME)
103
141
  return await call_next(name, version=version)
104
142
 
105
143
  # Server icon configuration using GitHub-hosted images
@@ -772,12 +810,27 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
772
810
  tool_name = f"ha_get_skill_{skill_name.replace('-', '_')}"
773
811
  uri = f"skill://{skill_name}/SKILL.md"
774
812
 
813
+ # Action-phrased keyword block for retrieval. Some MCP clients
814
+ # rank candidate tools by token-overlap between the user's
815
+ # query and each tool's `description`; the upstream SKILL.md
816
+ # description is symptom-framed ("Agent uses Jinja2 templates
817
+ # where..."), which doesn't overlap with task-phrased queries
818
+ # like "create automation" / "set automation config" / "writing
819
+ # trigger". This block re-anchors the description in those
820
+ # task verbs so it surfaces when an agent is about to write
821
+ # config. Same shared keyword block as
822
+ # HaResourcesAsTools._USE_BEFORE_KEYWORDS above.
775
823
  tool_description = (
824
+ f"Get available reference files for the {skill_name} skill. "
776
825
  f"CALL THIS FIRST before performing matching actions. "
777
826
  f"{description}\n\n"
778
- f"Returns available reference files. Read each file via "
779
- f"resources/read (or ha_read_resource as a fallback) using "
780
- f"the file URI to load specific guides as needed."
827
+ f"{HaResourcesAsTools._USE_BEFORE_KEYWORDS} The reference "
828
+ f"files below cover automation patterns, helper selection, "
829
+ f"template guidelines, device control, dashboards, and safe "
830
+ f"refactoring.\n\n"
831
+ f"Read each reference file via resources/read (or "
832
+ f"ha_read_resource as a fallback) using the file URI to load "
833
+ f"specific guides as needed."
781
834
  )
782
835
 
783
836
  ref_files = self._collect_skill_ref_files(skill_dir, skill_name)
@@ -8,6 +8,30 @@ file via the bundled SkillsDirectoryProvider. The ``skill_prefix`` kwarg
8
8
  lets callers pass any URL prefix (e.g., a GitHub mirror) when skill://
9
9
  isn't reachable, or ``None`` to omit references entirely.
10
10
 
11
+ Each warning carries the native alternative inline (a concrete example or
12
+ short explanation) before the URI suffix, so clients that don't auto-fetch
13
+ resource URIs still receive actionable guidance.
14
+
15
+ The checker covers two layers:
16
+
17
+ 1. *Specific* detectors for known anti-pattern shapes — each emits a tailored
18
+ message that names the native alternative concretely.
19
+ 2. A *generic* fallback that fires when ``{{ ... }}`` or ``{% ... %}`` shows
20
+ up in a logic position (condition / trigger / wait_template / target field)
21
+ without matching a specific pattern. This catches new template misuse
22
+ without waiting for a regex to be added.
23
+
24
+ Allowlist by design — these positions are NOT walked by any recursion path,
25
+ so templates in them never trigger a warning even when present. They are the
26
+ documented legitimate dynamic-data positions per
27
+ ``template-guidelines.md#when-templates-are-appropriate``:
28
+
29
+ * Action ``data.*`` fields (notification messages, brightness, volume, etc.)
30
+ * Notification ``message`` / ``title`` bodies
31
+ * Action ``event_data.*`` (HA evaluates event_data as a template at runtime)
32
+ * Top-level ``variables.*``
33
+ * Action ``service_data.*`` (legacy alias for ``data``)
34
+
11
35
  Anti-patterns sourced from:
12
36
  https://github.com/homeassistant-ai/skills
13
37
  skill://home-assistant-best-practices
@@ -39,6 +63,14 @@ _RE_WEEKDAY = re.compile(
39
63
  r"\bnow\(\)\s*\.\s*(?:weekday|isoweekday)\s*\("
40
64
  r"|\bnow\(\)\s*\.\s*strftime\s*\(\s*['\"]%[Aaw]['\"]"
41
65
  )
66
+ # Date-component checks: now().date(), now().year/month/day.
67
+ # `\b` after year/month/day prevents matching `day_of_week`/`day_of_year`/etc.;
68
+ # `(?!\s*\()` rejects method-call shapes like `now().day()` that don't exist
69
+ # in HA's Jinja env.
70
+ _RE_NOW_DATE = re.compile(
71
+ r"\bnow\(\)\s*\.\s*date\s*\("
72
+ r"|\bnow\(\)\s*\.\s*(?:year|month|day)\b(?!\s*\()"
73
+ )
42
74
  # sun.sun entity references
43
75
  _RE_SUN = re.compile(r"(?:is_state|state_attr|states)\s*\(\s*['\"]sun\.sun['\"]")
44
76
  # states('x') in [...] or states('x') in (...)
@@ -47,6 +79,18 @@ _RE_STATE_IN = re.compile(r"states\s*\([^)]+\)\s+in\s+[\[(]")
47
79
  _RE_DIRECT_STATE = re.compile(r"\bstates\.\w+\.\w+\.state\b")
48
80
  # Motion entity pattern
49
81
  _RE_MOTION = re.compile(r"binary_sensor\.\w*motion", re.IGNORECASE)
82
+ # Any Jinja template marker — catch-all and target-field scan.
83
+ _RE_ANY_TEMPLATE = re.compile(r"\{\{|\{%")
84
+ # `this.X` self-reference (e.g. `{{ this.entity_id }}`)
85
+ _RE_THIS_REFERENCE = re.compile(r"\bthis\s*\.\s*\w+")
86
+
87
+ # Target sub-fields scanned for templates. These are the only keys allowed
88
+ # under ``target:`` in HA's modern action schema.
89
+ _TARGET_FIELDS = ("entity_id", "device_id", "area_id", "floor_id", "label_id")
90
+
91
+ # Keys that hold the service/action name in an action step. HA accepts both
92
+ # ``service:`` (legacy) and ``action:`` (modern, 2024+) for the same field.
93
+ _SERVICE_KEYS = ("service", "action")
50
94
 
51
95
 
52
96
  # ---------------------------------------------------------------------------
@@ -76,7 +120,7 @@ def check_automation_config(
76
120
  # Condition templates
77
121
  _check_condition_templates(config.get("condition", []), warnings, skill_prefix)
78
122
 
79
- # Action tree (wait_template + nested conditions)
123
+ # Action tree (wait_template + nested conditions + target templates)
80
124
  _check_action_tree(config.get("action", []), warnings, skill_prefix)
81
125
 
82
126
  # Trigger templates + device_id
@@ -132,12 +176,20 @@ def _check_condition_templates(
132
176
  for cond in _as_list(conditions):
133
177
  if isinstance(cond, str) and "{{" in cond:
134
178
  # Shorthand template condition
135
- _check_template_string(cond, warnings, skill_prefix)
179
+ _check_template_string(cond, warnings, skill_prefix, "condition")
136
180
  elif isinstance(cond, dict):
137
181
  if cond.get("condition") == "template":
138
182
  vt = cond.get("value_template", "")
139
183
  if isinstance(vt, str):
140
- _check_template_string(vt, warnings, skill_prefix)
184
+ _check_template_string(vt, warnings, skill_prefix, "condition")
185
+ else:
186
+ # Non-template conditions (numeric_state, state, etc.) can
187
+ # still carry a `value_template` field (numeric_state uses one
188
+ # to compute the numeric value being compared). Scan it too,
189
+ # otherwise these templates slip past every detector.
190
+ vt = cond.get("value_template", "")
191
+ if isinstance(vt, str) and "{{" in vt:
192
+ _check_template_string(vt, warnings, skill_prefix, "condition")
141
193
  # Recurse into compound conditions (and/or/not)
142
194
  nested = cond.get("conditions")
143
195
  if nested:
@@ -145,54 +197,99 @@ def _check_condition_templates(
145
197
 
146
198
 
147
199
  def _check_template_string(
148
- template: str, warnings: list[str], skill_prefix: str | None
200
+ template: str,
201
+ warnings: list[str],
202
+ skill_prefix: str | None,
203
+ position: str,
149
204
  ) -> None:
150
- """Check a single template string for known anti-patterns."""
205
+ """Check a single template string for known anti-patterns.
206
+
207
+ ``position`` is currently only "condition" (the function is called from
208
+ ``_check_condition_templates``). It's parameterized so both the warning
209
+ prefix AND the suggestion text adapt if a future caller passes "trigger".
210
+ The native shapes named here (numeric_state, state, time, sun) work as
211
+ both conditions and triggers in HA — only the noun changes.
212
+ """
213
+ initial_count = len(warnings)
214
+ label = position.capitalize()
215
+
151
216
  if _RE_NUMERIC_CMP.search(template):
152
217
  warnings.append(
153
- "Condition uses template with float/int comparison — use native "
154
- "`numeric_state` condition instead."
218
+ f"{label} uses template with float/int comparison — use native "
219
+ f"`numeric_state` {position} instead "
220
+ f"(e.g., `{position}: numeric_state, entity_id: sensor.temp, above: 25`). "
221
+ "Native conditions are validated at config load and don't bypass HA's schema."
155
222
  + _ref(skill_prefix, "automation-patterns.md#native-conditions")
156
223
  )
157
224
  if _RE_SUN.search(template):
158
225
  warnings.append(
159
- "Condition uses template referencing `sun.sun` — use native "
160
- "`sun` condition instead."
226
+ f"{label} uses template referencing `sun.sun` — use native "
227
+ f"`sun` {position} instead "
228
+ f"(e.g., `{position}: sun, after: sunset` or `before: sunrise`)."
161
229
  + _ref(skill_prefix, "automation-patterns.md#native-conditions")
162
230
  )
163
231
  elif _RE_IS_STATE.search(template):
164
232
  # Only flag if not already flagged as sun pattern
165
233
  warnings.append(
166
- "Condition uses template with `is_state()` — use native "
167
- "`state` condition instead."
234
+ f"{label} uses template with `is_state()` — use native "
235
+ f"`state` {position} instead "
236
+ f"(e.g., `{position}: state, entity_id: light.bedroom, state: 'on'`)."
168
237
  + _ref(skill_prefix, "automation-patterns.md#native-conditions")
169
238
  )
170
239
  if _RE_NOW_TIME.search(template):
171
240
  warnings.append(
172
- "Condition uses template with `now().hour/minute` — use native "
173
- "`time` condition instead."
241
+ f"{label} uses template with `now().hour/minute` — use native "
242
+ f"`time` {position} instead "
243
+ f"(e.g., `{position}: time, after: '09:00:00', before: '17:00:00'`)."
174
244
  + _ref(skill_prefix, "automation-patterns.md#native-conditions")
175
245
  )
176
246
  if _RE_WEEKDAY.search(template):
177
247
  warnings.append(
178
- "Condition uses template for day-of-week check — use native "
179
- "`time` condition with `weekday:` list instead."
248
+ f"{label} uses template for day-of-week check — use native "
249
+ f"`time` {position} with `weekday:` list instead "
250
+ f"(e.g., `{position}: time, weekday: ['mon', 'tue', 'wed']`)."
251
+ + _ref(skill_prefix, "automation-patterns.md#native-conditions")
252
+ )
253
+ if _RE_NOW_DATE.search(template):
254
+ warnings.append(
255
+ f"{label} uses date-based check (`now().date()` / `now().year/month/day`) — "
256
+ "for one-shot date-specific firing, use a `time` trigger and self-disable via "
257
+ "`automation.turn_off` with a hardcoded `entity_id` (the next `00:01` fire IS the "
258
+ "target date on creation day). For recurring date logic, expose a `sensor.date` via "
259
+ f"the `time_date` integration and use a `state` {position}."
180
260
  + _ref(skill_prefix, "automation-patterns.md#native-conditions")
181
261
  )
182
262
  if _RE_STATE_IN.search(template):
183
263
  warnings.append(
184
- "Condition uses template with `states(...) in [...]` — use native "
185
- "`state` condition with `state:` list instead."
264
+ f"{label} uses template with `states(...) in [...]` — use native "
265
+ f"`state` {position} with `state:` list instead "
266
+ f"(e.g., `{position}: state, entity_id: climate.living_room, state: ['heat', 'cool']`)."
186
267
  + _ref(skill_prefix, "automation-patterns.md#native-conditions")
187
268
  )
188
269
  if _RE_DIRECT_STATE.search(template):
189
270
  warnings.append(
190
- "Template uses `states.domain.entity.state` direct access which "
191
- "errors if entity doesn't exist — use `states('entity_id')` "
192
- "function instead."
271
+ f"{label} template uses `states.domain.entity.state` direct access which "
272
+ "errors if entity doesn't exist — use the `states('entity_id')` "
273
+ "function instead (returns 'unknown' if missing rather than raising)."
193
274
  + _ref(skill_prefix, "template-guidelines.md#common-patterns")
194
275
  )
195
276
 
277
+ # Generic fallback: any Jinja in this logic position that didn't match
278
+ # a specific detector. Catches new anti-patterns (issue #1011) and
279
+ # reframes #695 from "enumerate bad shapes" to "surface every template
280
+ # in a logic position". Specific detectors above keep their tailored
281
+ # messages.
282
+ if (
283
+ len(warnings) == initial_count
284
+ and _RE_ANY_TEMPLATE.search(template)
285
+ ):
286
+ warnings.append(
287
+ f"Template detected in {position} — if this maps to a native option "
288
+ "(`numeric_state`, `state`, `time`, `sun`, `zone`, `device`), use that "
289
+ "instead. Templates fail silently at runtime and bypass schema validation."
290
+ + _ref(skill_prefix, "template-guidelines.md#when-to-avoid-templates")
291
+ )
292
+
196
293
 
197
294
  # ---------------------------------------------------------------------------
198
295
  # Action tree checks
@@ -223,11 +320,22 @@ def _check_repeat_actions(
223
320
  def _check_action_tree(
224
321
  actions: Any, warnings: list[str], skill_prefix: str | None
225
322
  ) -> None:
226
- """Walk action tree checking for wait_template and nested conditions."""
323
+ """Walk action tree checking for wait_template, nested conditions, and target templates."""
227
324
  for action in _as_list(actions):
228
325
  if not isinstance(action, dict):
229
326
  continue
230
327
 
328
+ # Inline condition steps (e.g. `- condition: template, value_template: ...`
329
+ # in a sequence). Detect by `condition: <str>` AND no service/action key
330
+ # present — a service-call step uses `condition:` as a legacy run-if
331
+ # filter, not as a step kind. Without this branch, templates in
332
+ # condition shorthand inside scripts/automation actions slipped past
333
+ # the checker; only conditions in `if:`, `choose.conditions`, and
334
+ # `repeat.while/until` were inspected.
335
+ cond_kind = action.get("condition")
336
+ if isinstance(cond_kind, str) and not any(k in action for k in _SERVICE_KEYS):
337
+ _check_condition_templates([action], warnings, skill_prefix)
338
+
231
339
  if "wait_template" in action:
232
340
  warnings.append(
233
341
  "Action uses `wait_template` — consider `wait_for_trigger` "
@@ -237,6 +345,20 @@ def _check_action_tree(
237
345
  + _ref(skill_prefix, "automation-patterns.md#wait-actions")
238
346
  )
239
347
 
348
+ # Templated service dispatch: `service:`/`action:` containing `{{ }}`
349
+ # or any `service_template:` field. The native alternative is a
350
+ # `choose` (or `if/then/else`) action that picks between hardcoded
351
+ # service names based on state.
352
+ _check_service_template(action, warnings, skill_prefix)
353
+
354
+ # Templates in target sub-fields. Action `data`, `event_data`,
355
+ # `service_data`, notification message/title, and `variables` are
356
+ # legitimate dynamic-data positions per template-guidelines.md and
357
+ # are not walked by any recursion path here.
358
+ target = action.get("target")
359
+ if isinstance(target, dict):
360
+ _check_target_dict(target, warnings, skill_prefix)
361
+
240
362
  # Nested conditions in choose/if/repeat
241
363
  if "choose" in action:
242
364
  _check_choose_actions(action["choose"], warnings, skill_prefix)
@@ -252,6 +374,82 @@ def _check_action_tree(
252
374
  if "repeat" in action and isinstance(action["repeat"], dict):
253
375
  _check_repeat_actions(action["repeat"], warnings, skill_prefix)
254
376
 
377
+ # `parallel:` runs sub-actions concurrently — same shape as `sequence`,
378
+ # different semantics. Recurse so templates inside parallel branches
379
+ # are inspected the same as templates inside choose/repeat sequences.
380
+ if "parallel" in action and isinstance(action["parallel"], list):
381
+ _check_action_tree(action["parallel"], warnings, skill_prefix)
382
+
383
+
384
+ def _check_service_template(
385
+ action: dict[str, Any], warnings: list[str], skill_prefix: str | None
386
+ ) -> None:
387
+ """Flag template-based service dispatch in an action.
388
+
389
+ Three shapes:
390
+ - ``service_template:`` — legacy explicit way to template a service name.
391
+ Flag any value.
392
+ - ``service:`` containing ``{{`` — modern syntax with a template.
393
+ - ``action:`` containing ``{{`` — HA's 2024+ rename of ``service:``.
394
+
395
+ The native alternative is a ``choose`` (or ``if/then/else``) action that
396
+ dispatches to different hardcoded service names based on state.
397
+ """
398
+ if "service_template" in action:
399
+ warnings.append(
400
+ "Action uses `service_template` (legacy templated service dispatch) — "
401
+ "use a `choose` (or `if/then/else`) action that dispatches to different "
402
+ "hardcoded `action:` names based on state. Native dispatch validates "
403
+ "each service name at config load."
404
+ + _ref(skill_prefix, "automation-patterns.md#ifthen-vs-choose")
405
+ )
406
+ return
407
+ for key in _SERVICE_KEYS:
408
+ value = action.get(key)
409
+ if isinstance(value, str) and _RE_ANY_TEMPLATE.search(value):
410
+ warnings.append(
411
+ f"Action `{key}:` field contains a template — use a `choose` "
412
+ "(or `if/then/else`) action with hardcoded service names instead. "
413
+ "Templates here bypass HA's service-name validation and fail "
414
+ "silently if the resolved string is invalid."
415
+ + _ref(skill_prefix, "automation-patterns.md#ifthen-vs-choose")
416
+ )
417
+ return
418
+
419
+
420
+ def _check_target_dict(
421
+ target: dict[str, Any], warnings: list[str], skill_prefix: str | None
422
+ ) -> None:
423
+ """Flag any Jinja in target.entity_id/device_id/area_id/floor_id/label_id.
424
+
425
+ Templates in target fields bypass HA's entity-existence validation at
426
+ config load and fail silently if they resolve to a non-existent entity.
427
+ `{{ this.entity_id }}`-style self-references are especially pointless —
428
+ the calling automation/script already knows its own entity_id, so
429
+ hardcoding the literal is both simpler and safer.
430
+ """
431
+ for field in _TARGET_FIELDS:
432
+ value = target.get(field)
433
+ for item in _as_list(value):
434
+ if not isinstance(item, str) or not _RE_ANY_TEMPLATE.search(item):
435
+ continue
436
+ if _RE_THIS_REFERENCE.search(item):
437
+ warnings.append(
438
+ f"Action `target.{field}` uses a `this.*` self-reference template — "
439
+ f"hardcode the literal value instead. The self-reference is always "
440
+ f"resolvable at write time, so the template adds runtime cost without "
441
+ f"any flexibility."
442
+ + _ref(skill_prefix, "template-guidelines.md#when-to-avoid-templates")
443
+ )
444
+ else:
445
+ warnings.append(
446
+ f"Action `target.{field}` uses a template — prefer a hardcoded literal, "
447
+ f"or use a `choose` action with native conditions to dispatch to different "
448
+ f"hardcoded targets. Templates in target fields fail silently if they "
449
+ f"resolve to a non-existent entity."
450
+ + _ref(skill_prefix, "template-guidelines.md#when-to-avoid-templates")
451
+ )
452
+
255
453
 
256
454
  # ---------------------------------------------------------------------------
257
455
  # Trigger checks
@@ -277,14 +475,16 @@ def _check_triggers(
277
475
  + _ref(skill_prefix, "device-control.md#entity-id-vs-device-id")
278
476
  )
279
477
 
280
- # Template trigger with detectable native alternative
478
+ # Template trigger specific shapes first, generic fallback after.
281
479
  if platform == "template":
282
480
  vt = trigger.get("value_template", "")
283
481
  if isinstance(vt, str):
482
+ initial = len(warnings)
284
483
  if _RE_NUMERIC_CMP.search(vt):
285
484
  warnings.append(
286
485
  "Trigger uses template with float/int comparison — "
287
- "use native `numeric_state` trigger instead."
486
+ "use native `numeric_state` trigger instead "
487
+ "(e.g., `platform: numeric_state, entity_id: sensor.temp, above: 30`)."
288
488
  + _ref(
289
489
  skill_prefix,
290
490
  "automation-patterns.md#trigger-types",
@@ -293,7 +493,20 @@ def _check_triggers(
293
493
  if _RE_IS_STATE.search(vt):
294
494
  warnings.append(
295
495
  "Trigger uses template with `is_state()` — use "
296
- "native `state` trigger instead."
496
+ "native `state` trigger instead "
497
+ "(e.g., `platform: state, entity_id: light.x, to: 'on'`)."
498
+ + _ref(
499
+ skill_prefix,
500
+ "automation-patterns.md#trigger-types",
501
+ )
502
+ )
503
+ # Generic fallback for unmatched template triggers.
504
+ if len(warnings) == initial and _RE_ANY_TEMPLATE.search(vt):
505
+ warnings.append(
506
+ "Trigger uses `template` platform — if this maps to a native option "
507
+ "(`state`, `numeric_state`, `time`, `time_pattern`, `sun`, `zone`, "
508
+ "`event`), use that instead. Native triggers are event-driven; "
509
+ "template triggers re-evaluate on every state change."
297
510
  + _ref(
298
511
  skill_prefix,
299
512
  "automation-patterns.md#trigger-types",
@@ -371,6 +371,25 @@ class AutomationConfigTools:
371
371
  """
372
372
  Create or update a Home Assistant automation.
373
373
 
374
+ PREFER NATIVE SOLUTIONS OVER TEMPLATES (read this before writing any `{{ ... }}`):
375
+ Native triggers/conditions/actions are validated at config load, fail loudly, and
376
+ do not bypass HA's schema. Templates fail silently at runtime and obscure intent.
377
+ - `condition: numeric_state` instead of `{{ states('x') | float > N }}`
378
+ - `condition: state` (with `state:` list) instead of `{{ is_state(...) }}` /
379
+ `{{ states(x) in [...] }}`
380
+ - `condition: time` instead of `{{ now().hour ... }}` or `{{ now().weekday() ... }}`
381
+ - `condition: sun` instead of `{{ is_state('sun.sun', ...) }}`
382
+ - `wait_for_trigger` instead of `wait_template`
383
+ - `choose` action instead of template-based service names
384
+ - For one-shot date firing, use a `time` trigger plus `automation.turn_off` on a
385
+ hardcoded entity_id — not `{{ now().date() ... }}`.
386
+ - Hardcode `target.entity_id` literals — never `{{ this.entity_id }}`.
387
+ Templates are appropriate ONLY in `data.*` fields, notification message/title,
388
+ `event_data`, and `variables`. The reactive best-practice checker on this tool
389
+ will surface anything in a logic position that should be native; consult the
390
+ `best_practice_warnings` field on the response and fix before re-submitting.
391
+ For comprehensive guidance, call `ha_get_skill_home_assistant_best_practices`.
392
+
374
393
  Supports two modes: full config replacement OR Python transformation.
375
394
 
376
395
  WHEN TO USE WHICH MODE:
@@ -478,14 +497,6 @@ class AutomationConfigTools:
478
497
  }
479
498
  )
480
499
 
481
- PREFER NATIVE SOLUTIONS OVER TEMPLATES:
482
- Before using template triggers/conditions/actions, check if a native option exists:
483
- - Use `condition: state` with `state: [list]` instead of template for multiple states
484
- - Use `condition: state` with `attribute:` instead of template for attribute checks
485
- - Use `condition: numeric_state` instead of template for number comparisons
486
- - Use `wait_for_trigger` instead of `wait_template` when waiting for state changes
487
- - Use `choose` action instead of template-based service names
488
-
489
500
  TRIGGER TYPES: time, time_pattern, sun, state, numeric_state, event, device, zone, template, and more
490
501
  CONDITION TYPES: state, numeric_state, time, sun, template, device, zone, and more
491
502
  ACTION TYPES: service calls, delays, wait_for_trigger, wait_template, if/then/else, choose, repeat, parallel
@@ -285,6 +285,19 @@ class ConfigScriptTools:
285
285
  """
286
286
  Create or update a Home Assistant script.
287
287
 
288
+ PREFER NATIVE ACTIONS OVER TEMPLATES (read this before writing any `{{ ... }}`):
289
+ Native actions are validated at config load, fail loudly, and do not bypass HA's
290
+ schema. Templates in logic positions fail silently and obscure intent.
291
+ - `choose` / `if/then/else` instead of template-based service names
292
+ - `wait_for_trigger` instead of `wait_template`
293
+ - `repeat` with `for_each` instead of template loops
294
+ - Hardcode `target.entity_id` literals — never `{{ this.entity_id }}`.
295
+ Templates are appropriate ONLY in `data.*` fields, notification message/title,
296
+ `event_data`, and `variables`. The reactive best-practice checker on this tool
297
+ will surface anything in a logic position that should be native; consult the
298
+ `best_practice_warnings` field on the response and fix before re-submitting.
299
+ For comprehensive guidance, call `ha_get_skill_home_assistant_best_practices`.
300
+
288
301
  Supports two modes: full config replacement OR Python transformation.
289
302
 
290
303
  WHEN TO USE WHICH MODE:
@@ -393,16 +406,6 @@ class ConfigScriptTools:
393
406
  }
394
407
  })
395
408
 
396
- PREFER NATIVE ACTIONS OVER TEMPLATES:
397
- Before using template-based logic in scripts, check if native actions exist:
398
- - Use `choose` action instead of template-based service names
399
- - Use `if/then/else` action instead of template conditions
400
- - Use `repeat` action with `for_each` instead of template loops
401
- - Use `wait_for_trigger` instead of `wait_template` when waiting for state changes
402
- - Use native action variables instead of complex template calculations
403
-
404
- For detailed script configuration help, use ha_get_skill_home_assistant_best_practices.
405
-
406
409
  Note: Scripts use Home Assistant's action syntax. Check the documentation for advanced
407
410
  features like conditions, variables, parallel execution, and service call options.
408
411
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.4.1.dev458
3
+ Version: 7.4.1.dev459
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