ha-mcp-dev 7.14.2.dev2180__tar.gz → 7.14.2.dev2183__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 (192) hide show
  1. {ha_mcp_dev-7.14.2.dev2180/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.14.2.dev2183}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/pyproject.toml +27 -1
  3. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/__main__.py +1 -3
  4. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/backup_manager.py +3 -3
  5. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/policy/evaluator.py +1 -1
  6. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/_persistence.py +8 -8
  7. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/device_control.py +2 -2
  8. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/smart_search/_entities.py +1 -1
  9. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_addons.py +4 -7
  10. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_camera.py +1 -1
  11. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_config_dashboards.py +2 -3
  12. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_entities.py +2 -2
  13. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_registry.py +5 -1
  14. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_resources.py +1 -1
  15. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_traces.py +1 -1
  16. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/utils/domain_handlers.py +1 -1
  17. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/visibility/resolver.py +1 -1
  18. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  19. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/tests/test_env_manager.py +3 -1
  20. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/LICENSE +0 -0
  21. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/MANIFEST.in +0 -0
  22. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/README.md +0 -0
  23. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/setup.cfg +0 -0
  24. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/__init__.py +0 -0
  25. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/_pypi_marker +0 -0
  26. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/_version.py +0 -0
  27. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/auth/__init__.py +0 -0
  28. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/auth/consent_form.py +0 -0
  29. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/auth/provider.py +0 -0
  30. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/browser_landing.py +0 -0
  31. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/client/__init__.py +0 -0
  32. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/client/rest_client.py +0 -0
  33. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/client/supervisor_client.py +0 -0
  34. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/client/websocket_client.py +0 -0
  35. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/client/websocket_listener.py +0 -0
  36. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/config.py +0 -0
  37. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  38. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  39. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  40. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  41. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  42. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  43. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/errors.py +0 -0
  44. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/llm_exposure.py +0 -0
  45. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/policy/__init__.py +0 -0
  46. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/policy/approval_queue.py +0 -0
  47. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/policy/handlers.py +0 -0
  48. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/policy/middleware.py +0 -0
  49. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/policy/model.py +0 -0
  50. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/policy/persistence.py +0 -0
  51. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/policy/value_sources.py +0 -0
  52. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/py.typed +0 -0
  53. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/read_only.py +0 -0
  54. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  55. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  56. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  57. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  58. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  59. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  60. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  61. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  62. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  63. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  64. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  65. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  66. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  67. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  68. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  69. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  70. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  71. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  72. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  73. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  74. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  75. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  76. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  77. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  78. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  79. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/server.py +0 -0
  80. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/__init__.py +0 -0
  81. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  82. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  83. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  84. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  85. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  86. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  87. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  88. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  89. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/_theme.py +0 -0
  90. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  91. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  92. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  93. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/locales/en.json +0 -0
  94. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/locales/es.json +0 -0
  95. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
  96. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/locales/it.json +0 -0
  97. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  98. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
  99. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/settings.css +0 -0
  100. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/settings.html +0 -0
  101. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/settings_ui/settings.js +0 -0
  102. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/smoke_test.py +0 -0
  103. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  104. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/strict_bps.py +0 -0
  105. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/__init__.py +0 -0
  106. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/auto_backup.py +0 -0
  107. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/backup.py +0 -0
  108. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  109. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/component_api.py +0 -0
  110. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/component_config_reads.py +0 -0
  111. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/component_devices.py +0 -0
  112. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/component_registries.py +0 -0
  113. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  114. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  115. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/helpers.py +0 -0
  116. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/radio/__init__.py +0 -0
  117. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/radio/base.py +0 -0
  118. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/radio/matter.py +0 -0
  119. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/radio/thread.py +0 -0
  120. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  121. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/radio/zwave.py +0 -0
  122. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/reference_validator.py +0 -0
  123. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/registry.py +0 -0
  124. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  125. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  126. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  127. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  128. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  129. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  130. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  131. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  132. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  133. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_areas.py +0 -0
  134. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  135. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  136. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_calendar.py +0 -0
  137. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_categories.py +0 -0
  138. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_code.py +0 -0
  139. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  140. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  141. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  142. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  143. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  144. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_dev.py +0 -0
  145. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_energy.py +0 -0
  146. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  147. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_groups.py +0 -0
  148. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_hacs.py +0 -0
  149. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_history.py +0 -0
  150. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_integrations.py +0 -0
  151. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_labels.py +0 -0
  152. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_radio.py +0 -0
  153. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_search.py +0 -0
  154. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_service.py +0 -0
  155. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_services.py +0 -0
  156. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_system.py +0 -0
  157. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_themes.py +0 -0
  158. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_todo.py +0 -0
  159. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_updates.py +0 -0
  160. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_utility.py +0 -0
  161. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  162. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  163. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  164. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/tools_zones.py +0 -0
  165. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/util_helpers.py +0 -0
  166. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/tools/validation_middleware.py +0 -0
  167. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/transforms/__init__.py +0 -0
  168. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/transforms/categorized_search.py +0 -0
  169. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  170. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/transport_security.py +0 -0
  171. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/update_check.py +0 -0
  172. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/utils/__init__.py +0 -0
  173. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/utils/config_hash.py +0 -0
  174. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/utils/config_write_lock.py +0 -0
  175. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/utils/data_paths.py +0 -0
  176. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  177. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  178. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/utils/operation_manager.py +0 -0
  179. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/utils/python_sandbox.py +0 -0
  180. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/utils/skill_loader.py +0 -0
  181. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/utils/usage_logger.py +0 -0
  182. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/visibility/__init__.py +0 -0
  183. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/visibility/enforcement.py +0 -0
  184. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/visibility/model.py +0 -0
  185. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp/visibility/persistence.py +0 -0
  186. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  187. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  188. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  189. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  190. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  191. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/tests/__init__.py +0 -0
  192. {ha_mcp_dev-7.14.2.dev2180 → ha_mcp_dev-7.14.2.dev2183}/tests/test_constants.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.14.2.dev2180
3
+ Version: 7.14.2.dev2183
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.14.2.dev2180"
7
+ version = "7.14.2.dev2183"
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
@@ -141,6 +141,26 @@ select = [
141
141
  "A", # builtin shadowing
142
142
  "LOG", # logging best practices
143
143
  "SIM", # simplify
144
+ "PLE", # pylint errors (real bugs: bad logging args, await outside async)
145
+ # Targeted pylint conventions/refactors/warnings. The PL family is NOT
146
+ # selected wholesale: PLC0415 (import-outside-top-level) fights the
147
+ # deliberate lazy-init architecture, and PLR2004 (magic values),
148
+ # PLR0913/PLR0917 (wide FastMCP tool signatures) and PLR0911 (too many
149
+ # returns -- a separate signal from C901's cyclomatic complexity, and
150
+ # noisy on this codebase's early-return validation style) are noise
151
+ # here. Add codes individually so a new upstream PLR rule does not land
152
+ # enabled by default.
153
+ "PLC0206", # dict-index-missing-items
154
+ "PLC0207", # missing-maxsplit-arg
155
+ "PLR0402", # manual-from-import
156
+ "PLR1711", # useless-return
157
+ "PLR1714", # repeated-equality-comparison
158
+ "PLR1730", # if-stmt-min-max
159
+ "PLR5501", # collapsible-else-if
160
+ "PLW0108", # unnecessary-lambda
161
+ "PLW0602", # global-variable-not-assigned
162
+ "PLW1510", # subprocess-run-without-check
163
+ "PLW2901", # redefined-loop-name
144
164
  ]
145
165
  ignore = [
146
166
  "E501", # line too long — formatter handles this
@@ -163,6 +183,12 @@ ignore = [
163
183
  [tool.ruff.lint.per-file-ignores]
164
184
  "__init__.py" = ["F401"]
165
185
  "tests/**/*" = ["E501", "B011"]
186
+ # Temporary: the equivalent PLC0207/PLR5501 fixes landed in
187
+ # homeassistant-addon-webhook-proxy-dev/ only, per the dev-first/promote-only
188
+ # flow (allow-stable-edit is for stable-only hotfixes, not routine sweeps —
189
+ # see homeassistant-addon-webhook-proxy/AGENTS.md "Dev-first, promote-only").
190
+ # Remove this ignore once the next promote carries the fix into stable.
191
+ "homeassistant-addon-webhook-proxy/**" = ["PLC0207", "PLR5501"]
166
192
  # C901 is enforced repo-wide with no per-file exemptions (the grandfathered
167
193
  # list was fully cleared by issue #925). Do NOT add "path" = ["C901"] entries
168
194
  # here — extract helpers to bring the function below the threshold instead:
@@ -574,8 +574,6 @@ def _get_timestamped_uvicorn_log_config() -> dict:
574
574
 
575
575
  async def _cleanup_resources() -> None:
576
576
  """Clean up all server resources gracefully."""
577
- global _server
578
-
579
577
  logger.info("Cleaning up server resources...")
580
578
 
581
579
  # Close WebSocket listener service if running
@@ -730,7 +728,7 @@ def _signal_handler(signum: int, frame: Any) -> None:
730
728
  This handler initiates graceful shutdown on first signal.
731
729
  On second signal, forces immediate exit.
732
730
  """
733
- global _shutdown_in_progress, _shutdown_event
731
+ global _shutdown_in_progress
734
732
 
735
733
  sig_name = signal.Signals(signum).name
736
734
 
@@ -1128,15 +1128,15 @@ def _diff_dict_node(
1128
1128
  max_ops: int,
1129
1129
  ) -> None:
1130
1130
  """Diff two dicts into JSON-Patch ops (add/remove/recurse per key)."""
1131
- for key in stored:
1131
+ for key, stored_value in stored.items():
1132
1132
  seg = _pointer_segment(str(key))
1133
1133
  sub_path = f"{path}/{seg}"
1134
1134
  if key not in current:
1135
- out.append({"op": "add", "path": sub_path, "value": stored[key]})
1135
+ out.append({"op": "add", "path": sub_path, "value": stored_value})
1136
1136
  if len(out) >= max_ops:
1137
1137
  return
1138
1138
  else:
1139
- _diff_node(stored[key], current[key], sub_path, out, max_ops)
1139
+ _diff_node(stored_value, current[key], sub_path, out, max_ops)
1140
1140
  if len(out) >= max_ops:
1141
1141
  return
1142
1142
  for key in current:
@@ -128,7 +128,7 @@ def match_predicate(predicate: Predicate, args: dict[str, Any]) -> bool:
128
128
 
129
129
 
130
130
  def match_rule(rule: Rule, tool_name: str, args: dict[str, Any]) -> bool:
131
- if rule.tool_name != "*" and rule.tool_name != tool_name:
131
+ if rule.tool_name not in ("*", tool_name):
132
132
  return False
133
133
  return all(match_predicate(p, args) for p in rule.when)
134
134
 
@@ -116,14 +116,14 @@ def _seed_tool_config_from_env(settings: Settings) -> dict[str, str]:
116
116
  tools: dict[str, str] = {}
117
117
  disabled_raw = getattr(settings, "disabled_tools", "")
118
118
  if disabled_raw:
119
- for name in disabled_raw.split(","):
120
- name = name.strip()
119
+ for raw_name in disabled_raw.split(","):
120
+ name = raw_name.strip()
121
121
  if name:
122
122
  tools[name] = "disabled"
123
123
  pinned_raw = getattr(settings, "pinned_tools", "")
124
124
  if pinned_raw:
125
- for name in pinned_raw.split(","):
126
- name = name.strip()
125
+ for raw_name in pinned_raw.split(","):
126
+ name = raw_name.strip()
127
127
  if name and name not in tools:
128
128
  tools[name] = "pinned"
129
129
  return tools
@@ -176,12 +176,12 @@ def env_pinned_tools(settings: Settings | None = None) -> dict[str, str]:
176
176
  if settings is None:
177
177
  settings = get_global_settings()
178
178
  pinned: dict[str, str] = {}
179
- for name in (settings.disabled_tools or "").split(","):
180
- name = name.strip()
179
+ for raw_name in (settings.disabled_tools or "").split(","):
180
+ name = raw_name.strip()
181
181
  if name:
182
182
  pinned[name] = "disabled"
183
- for name in (settings.pinned_tools or "").split(","):
184
- name = name.strip()
183
+ for raw_name in (settings.pinned_tools or "").split(","):
184
+ name = raw_name.strip()
185
185
  if name:
186
186
  pinned[name] = "pinned"
187
187
  return pinned
@@ -126,7 +126,7 @@ class DeviceControlTools:
126
126
  )
127
127
  )
128
128
 
129
- domain = entity_id.split(".")[0]
129
+ domain = entity_id.split(".", maxsplit=1)[0]
130
130
  handler = get_domain_handler(domain)
131
131
 
132
132
  # Validate entity exists if requested
@@ -749,7 +749,7 @@ class DeviceControlTools:
749
749
  try:
750
750
  if "." not in entity_id:
751
751
  return None
752
- domain = entity_id.split(".")[0]
752
+ domain = entity_id.split(".", maxsplit=1)[0]
753
753
  handler = get_domain_handler(domain)
754
754
  valid_actions = handler.get("valid_actions", ["on", "off", "toggle"])
755
755
  if action not in valid_actions:
@@ -680,7 +680,7 @@ class EntitySearchMixin(_SearchBase):
680
680
  "_hidden_by": "hidden" if entity_id in hidden_entity_ids else None,
681
681
  }
682
682
  if include_domain:
683
- record["domain"] = entity_id.split(".")[0]
683
+ record["domain"] = entity_id.split(".", maxsplit=1)[0]
684
684
  return record
685
685
 
686
686
  @classmethod
@@ -80,15 +80,13 @@ def _slice_ws_messages(
80
80
  offset/limit were applied.
81
81
  """
82
82
  total_collected = len(messages)
83
- if offset < 0:
84
- offset = 0
83
+ offset = max(offset, 0)
85
84
  if offset > total_collected:
86
85
  sliced: list[Any] = []
87
86
  elif limit is None:
88
87
  sliced = messages[offset:]
89
88
  else:
90
- if limit < 0:
91
- limit = 0
89
+ limit = max(limit, 0)
92
90
  sliced = messages[offset : offset + limit]
93
91
 
94
92
  pagination: dict[str, Any] = {
@@ -158,9 +156,8 @@ def _summarize_ws_messages(
158
156
  flush(i)
159
157
  run_start = None
160
158
  result.append(msg)
161
- else:
162
- if run_start is None:
163
- run_start = i
159
+ elif run_start is None:
160
+ run_start = i
164
161
 
165
162
  if run_start is not None:
166
163
  flush(len(messages))
@@ -120,7 +120,7 @@ class CameraTools:
120
120
  "Expected format: camera.entity_name"
121
121
  )
122
122
 
123
- domain = entity_id.split(".")[0]
123
+ domain = entity_id.split(".", maxsplit=1)[0]
124
124
  if domain != "camera":
125
125
  raise ValueError(
126
126
  f"Entity {entity_id} is not a camera entity. "
@@ -745,9 +745,8 @@ def _card_matches(
745
745
  pattern = entity_id.replace(".", r"\.").replace("*", ".*")
746
746
  if not any(re.match(pattern, e) for e in all_entities if e):
747
747
  return False
748
- else:
749
- if entity_id not in all_entities:
750
- return False
748
+ elif entity_id not in all_entities:
749
+ return False
751
750
 
752
751
  # Heading filter (for heading cards or section titles)
753
752
  if heading is not None:
@@ -594,8 +594,8 @@ class EntityTools:
594
594
  context={"new_entity_id": new_entity_id},
595
595
  )
596
596
  )
597
- current_domain = entity_id.split(".")[0]
598
- new_domain = new_entity_id.split(".")[0]
597
+ current_domain = entity_id.split(".", maxsplit=1)[0]
598
+ new_domain = new_entity_id.split(".", maxsplit=1)[0]
599
599
  if current_domain != new_domain:
600
600
  raise_tool_error(
601
601
  create_error_response(
@@ -50,7 +50,11 @@ def _process_device_domain(
50
50
  return value, is_z2m, zwave_node_id
51
51
  # Z2M: identifier is ["mqtt", "zigbee2mqtt_0xIEEE"] or "zigbee2mqtt_bridge_0xIEEE"
52
52
  if domain == "mqtt" and "zigbee2mqtt" in value.lower():
53
- extracted = "0x" + value.split("_0x")[-1] if "_0x" in value else ieee_address
53
+ extracted = (
54
+ "0x" + value.rsplit("_0x", maxsplit=1)[-1]
55
+ if "_0x" in value
56
+ else ieee_address
57
+ )
54
58
  return extracted, True, zwave_node_id
55
59
  # Z-Wave JS: identifier is ["zwave_js", "{home_id}-{node_id}"]
56
60
  if domain == "zwave_js" and "-" in value:
@@ -333,7 +333,7 @@ def _decode_legacy_worker_url(url: str) -> str | None:
333
333
  return None
334
334
  try:
335
335
  # Extract base64 part: https://worker.dev/{base64}?type=module
336
- encoded = url[len(prefix) :].split("?")[0]
336
+ encoded = url[len(prefix) :].split("?", maxsplit=1)[0]
337
337
  # validate=True mirrors _decode_data_uri: urlsafe_b64decode otherwise
338
338
  # silently DISCARDS non-alphabet characters, so a junk path could
339
339
  # decode to plausible text and be masked as inline content.
@@ -734,7 +734,7 @@ def _categorize_step_path(path: str, domain: str) -> str | None:
734
734
  return "condition"
735
735
  if path == "action" or path.startswith(("action/", "sequence/")):
736
736
  return "action"
737
- if domain == "script" and path.split("/")[0].isdigit():
737
+ if domain == "script" and path.split("/", maxsplit=1)[0].isdigit():
738
738
  return "action"
739
739
  return None
740
740
 
@@ -205,7 +205,7 @@ def get_domain_handler(entity_id: str) -> dict[str, Any]:
205
205
  # Fallback for invalid entity ID format
206
206
  return get_default_handler()
207
207
 
208
- domain = entity_id.split(".")[0]
208
+ domain = entity_id.split(".", maxsplit=1)[0]
209
209
  return DOMAIN_HANDLERS.get(domain, get_default_handler())
210
210
 
211
211
 
@@ -185,7 +185,7 @@ def _is_assist_exposed(
185
185
  entry.get("entity_category") is not None or entry.get("hidden_by") is not None
186
186
  ):
187
187
  return False
188
- domain = eid.split(".")[0]
188
+ domain = eid.split(".", maxsplit=1)[0]
189
189
  if domain in DEFAULT_EXPOSED_DOMAINS:
190
190
  return True
191
191
  if domain == "binary_sensor":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.14.2.dev2180
3
+ Version: 7.14.2.dev2183
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
@@ -220,7 +220,9 @@ class HomeAssistantTestEnvironment:
220
220
  cmd = [sys.executable, "-m", "pytest", "tests/src/e2e/", "-v", "--tb=short"]
221
221
 
222
222
  try:
223
- result = subprocess.run(cmd, env=env, cwd=Path(__file__).parent.parent)
223
+ result = subprocess.run(
224
+ cmd, env=env, cwd=Path(__file__).parent.parent, check=False
225
+ )
224
226
  if result.returncode == 0:
225
227
  logger.info("✅ All tests passed!")
226
228
  else: