ha-mcp-dev 7.14.2.dev2098__tar.gz → 7.14.2.dev2100__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ha_mcp_dev-7.14.2.dev2098/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.14.2.dev2100}/PKG-INFO +1 -1
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/pyproject.toml +1 -1
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_search.py +209 -47
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/setup.cfg +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/locales/de.json +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/locales/en.json +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/tests/test_env_manager.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ha-mcp-dev"
|
|
7
|
-
version = "7.14.2.
|
|
7
|
+
version = "7.14.2.dev2100"
|
|
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
|
|
@@ -181,7 +181,8 @@ _ALWAYS_KEEP_PROJECTION: frozenset[str] = frozenset(
|
|
|
181
181
|
# search actually scanned — caller value beyond the input echo.
|
|
182
182
|
"area_names",
|
|
183
183
|
# Entity-branch internal mode label ("exact_match", "fuzzy_search",
|
|
184
|
-
# "area_only", "area_filtered_query", "domain_listing"
|
|
184
|
+
# "area_only", "area_filtered_query", "domain_listing",
|
|
185
|
+
# "state_listing"). E2E tests
|
|
185
186
|
# pin its presence at 17+ assertion sites — callers verifiably
|
|
186
187
|
# rely on it to disambiguate which entity-search path produced
|
|
187
188
|
# the result, so retained at the envelope top instead of stripped.
|
|
@@ -335,8 +336,10 @@ def _compute_eligibility(
|
|
|
335
336
|
Returns ``(registry_eligible, body_eligible, body_skipped_by_intent_gate)``:
|
|
336
337
|
|
|
337
338
|
- ``registry_eligible``: the entity-registry branch runs whenever any of
|
|
338
|
-
``query`` / ``domain_filter`` / ``area_filter`` is set,
|
|
339
|
-
caller pinned config-only via an explicit ``search_types``.
|
|
339
|
+
``query`` / ``domain_filter`` / ``area_filter`` / ``state_filter`` is set,
|
|
340
|
+
except when the caller pinned config-only via an explicit ``search_types``.
|
|
341
|
+
``state_filter`` alone is sufficient — it enumerates every entity in that
|
|
342
|
+
state (issue #2002), mirroring the ``domain_filter``-only listing mode.
|
|
340
343
|
- ``body_eligible``: the config-body branch runs only when a ``query``
|
|
341
344
|
term is set AND the caller's inputs do not signal entity-only intent.
|
|
342
345
|
"Entity-only intent" = any of ``domain_filter`` / ``area_filter`` /
|
|
@@ -348,7 +351,9 @@ def _compute_eligibility(
|
|
|
348
351
|
``query`` + entity-filter without explicit pin). The orchestrator
|
|
349
352
|
surfaces a warning in this case so callers can opt back in.
|
|
350
353
|
"""
|
|
351
|
-
any_registry_input = bool(
|
|
354
|
+
any_registry_input = bool(
|
|
355
|
+
query_text or domain_filter_text or area_filter_text or state_filter_text
|
|
356
|
+
)
|
|
352
357
|
registry_eligible = any_registry_input and not explicit_config_only
|
|
353
358
|
entity_intent_signal = bool(
|
|
354
359
|
domain_filter_text or area_filter_text or state_filter_text
|
|
@@ -1248,13 +1253,30 @@ def _normalize_state_filter(state_filter: str | None) -> str | None:
|
|
|
1248
1253
|
return state_filter
|
|
1249
1254
|
|
|
1250
1255
|
|
|
1256
|
+
def _state_matches(record: dict[str, Any], state_filter: str) -> bool:
|
|
1257
|
+
"""True when ``record``'s state equals ``state_filter``, case-insensitively.
|
|
1258
|
+
|
|
1259
|
+
``state_filter`` is already lowercased by ``_normalize_state_filter``; the
|
|
1260
|
+
record side is lowered here so an uppercase entity state (e.g. an
|
|
1261
|
+
input_select holding "Vacation") still matches the documented
|
|
1262
|
+
case-insensitive contract.
|
|
1263
|
+
"""
|
|
1264
|
+
return (record.get("state") or "").lower() == state_filter
|
|
1265
|
+
|
|
1266
|
+
|
|
1251
1267
|
def _validate_entity_search_params(
|
|
1252
1268
|
query: str | None,
|
|
1253
1269
|
domain_filter: str | None,
|
|
1254
1270
|
area_filter: str | None,
|
|
1255
1271
|
result_fields: Any,
|
|
1272
|
+
state_filter: str | None = None,
|
|
1256
1273
|
) -> tuple[str, str | None, str | None, list[str] | None]:
|
|
1257
|
-
"""Validate and normalise inputs for entity search; returns (query, domain_filter, area_filter, parsed_result_fields).
|
|
1274
|
+
"""Validate and normalise inputs for entity search; returns (query, domain_filter, area_filter, parsed_result_fields).
|
|
1275
|
+
|
|
1276
|
+
``state_filter`` is not returned (the caller normalises it separately via
|
|
1277
|
+
``_normalize_state_filter``); it participates here only in the
|
|
1278
|
+
at-least-one-criterion check so a ``state_filter``-only call is accepted and
|
|
1279
|
+
enumerates every entity in that state (issue #2002)."""
|
|
1258
1280
|
parsed_result_fields: list[str] | None = None
|
|
1259
1281
|
if result_fields is not None:
|
|
1260
1282
|
try:
|
|
@@ -1278,10 +1300,16 @@ def _validate_entity_search_params(
|
|
|
1278
1300
|
domain_filter = domain_filter.strip().lower()
|
|
1279
1301
|
if area_filter:
|
|
1280
1302
|
area_filter = area_filter.strip()
|
|
1281
|
-
if
|
|
1303
|
+
if (
|
|
1304
|
+
not query.strip()
|
|
1305
|
+
and not domain_filter
|
|
1306
|
+
and not area_filter
|
|
1307
|
+
and not _normalize_state_filter(state_filter)
|
|
1308
|
+
):
|
|
1282
1309
|
raise_tool_error(
|
|
1283
1310
|
create_validation_error(
|
|
1284
|
-
"At least one of 'query', 'domain_filter',
|
|
1311
|
+
"At least one of 'query', 'domain_filter', 'area_filter', or "
|
|
1312
|
+
"'state_filter' must be set.",
|
|
1285
1313
|
parameter="query",
|
|
1286
1314
|
)
|
|
1287
1315
|
)
|
|
@@ -1524,7 +1552,7 @@ async def _exact_match_search(
|
|
|
1524
1552
|
results.append(match)
|
|
1525
1553
|
|
|
1526
1554
|
if state_filter:
|
|
1527
|
-
results = [r for r in results if r
|
|
1555
|
+
results = [r for r in results if _state_matches(r, state_filter)]
|
|
1528
1556
|
|
|
1529
1557
|
# Sort by score descending, tie-break on entity_id for stable
|
|
1530
1558
|
# pagination when many results share a score (visible substring
|
|
@@ -1575,10 +1603,11 @@ class SearchTools:
|
|
|
1575
1603
|
"script sequences, scene contents, helper bodies, "
|
|
1576
1604
|
"dashboard cards) in one call. Use this for any "
|
|
1577
1605
|
"find-something-in-HA question — entity OR config. "
|
|
1578
|
-
"Omit `query` to enumerate by `domain_filter
|
|
1579
|
-
"`area_filter` alone
|
|
1580
|
-
"configuration-body search is
|
|
1581
|
-
"because there is no term to match
|
|
1606
|
+
"Omit `query` to enumerate by `domain_filter`, "
|
|
1607
|
+
"`area_filter`, and/or `state_filter` alone "
|
|
1608
|
+
"(registry-listing mode); configuration-body search is "
|
|
1609
|
+
"skipped in that mode because there is no term to match "
|
|
1610
|
+
"against."
|
|
1582
1611
|
),
|
|
1583
1612
|
),
|
|
1584
1613
|
] = None,
|
|
@@ -1691,7 +1720,10 @@ class SearchTools:
|
|
|
1691
1720
|
default=None,
|
|
1692
1721
|
description=(
|
|
1693
1722
|
"Filter entity-registry results to a specific state "
|
|
1694
|
-
'(e.g. "on", "off", "unavailable"). Case-insensitive.'
|
|
1723
|
+
'(e.g. "on", "off", "unavailable"). Case-insensitive. '
|
|
1724
|
+
"Can be used standalone (no query/domain/area) to enumerate "
|
|
1725
|
+
"every entity in that state; entity_total_matches reflects "
|
|
1726
|
+
"the filtered count."
|
|
1695
1727
|
),
|
|
1696
1728
|
),
|
|
1697
1729
|
] = None,
|
|
@@ -1757,8 +1789,8 @@ class SearchTools:
|
|
|
1757
1789
|
|
|
1758
1790
|
Two surfaces run in parallel and return tagged results:
|
|
1759
1791
|
- **entities**: entity-registry matches (entity_id, friendly name,
|
|
1760
|
-
area). Filter with `domain_filter`/`area_filter`;
|
|
1761
|
-
enumerate a domain
|
|
1792
|
+
area). Filter with `domain_filter`/`area_filter`/`state_filter`;
|
|
1793
|
+
omit `query` to enumerate a domain, area, or state.
|
|
1762
1794
|
- **automations / scripts / scenes / helpers / dashboards**: matches
|
|
1763
1795
|
*inside* config definitions — triggers, actions, sequences, scene
|
|
1764
1796
|
entity-sets, helper bodies, dashboard cards. Driven by `query`;
|
|
@@ -1800,6 +1832,7 @@ class SearchTools:
|
|
|
1800
1832
|
- Which automations use an entity: ha_search("light.bed_light")
|
|
1801
1833
|
- Scenes touching a light: ha_search("light.kitchen", search_types=["scene"])
|
|
1802
1834
|
- Narrow the response to the entity bucket: ha_search("kitchen", fields=["entities"])
|
|
1835
|
+
- All unavailable entities: ha_search(state_filter="unavailable")
|
|
1803
1836
|
"""
|
|
1804
1837
|
try:
|
|
1805
1838
|
parsed_search_types = parse_string_list_param(search_types, "search_types")
|
|
@@ -1848,7 +1881,7 @@ class SearchTools:
|
|
|
1848
1881
|
raise_tool_error(
|
|
1849
1882
|
create_validation_error(
|
|
1850
1883
|
"ha_search requires a non-empty query, or one of "
|
|
1851
|
-
"domain_filter / area_filter to enumerate.",
|
|
1884
|
+
"domain_filter / area_filter / state_filter to enumerate.",
|
|
1852
1885
|
parameter="query",
|
|
1853
1886
|
)
|
|
1854
1887
|
)
|
|
@@ -2128,8 +2161,8 @@ class SearchTools:
|
|
|
2128
2161
|
default=None,
|
|
2129
2162
|
description=(
|
|
2130
2163
|
"Entity name to search for (fuzzy or exact match). "
|
|
2131
|
-
"Omit to list entities; `domain_filter
|
|
2132
|
-
"must be set in that mode."
|
|
2164
|
+
"Omit to list entities; `domain_filter`, `area_filter`, "
|
|
2165
|
+
"or `state_filter` must be set in that mode."
|
|
2133
2166
|
),
|
|
2134
2167
|
),
|
|
2135
2168
|
] = None,
|
|
@@ -2215,8 +2248,10 @@ class SearchTools:
|
|
|
2215
2248
|
"Filter results to entities in a specific state "
|
|
2216
2249
|
'(e.g. "on", "off", "unavailable"). Case-insensitive — '
|
|
2217
2250
|
"input is lowercased before matching. Applied server-side after "
|
|
2218
|
-
"search results are collected.
|
|
2219
|
-
"
|
|
2251
|
+
"search results are collected. Can be used standalone (no "
|
|
2252
|
+
"query/domain/area) to enumerate every entity in that state. "
|
|
2253
|
+
"For exact-match, domain-listing, and state-listing searches, "
|
|
2254
|
+
"total_matches reflects the filtered count. For fuzzy "
|
|
2220
2255
|
"searches, state_filter is page-only and total_matches remains "
|
|
2221
2256
|
"unfiltered (see state_filter_note in the response). "
|
|
2222
2257
|
"None = no state filter (default)."
|
|
@@ -2243,13 +2278,18 @@ class SearchTools:
|
|
|
2243
2278
|
) -> dict[str, Any]:
|
|
2244
2279
|
"""Search for entities (lights, sensors, switches, etc.) by name, domain, or area.
|
|
2245
2280
|
|
|
2281
|
+
Internal helper reached through the `ha_search` tool; the examples below
|
|
2282
|
+
use that tool as the entry point.
|
|
2283
|
+
|
|
2246
2284
|
When NOT to use: for searching inside automation, script, helper, or dashboard
|
|
2247
2285
|
*configurations* (e.g. which automations call a service or reference an entity),
|
|
2248
|
-
|
|
2286
|
+
search config bodies via `ha_search(query=...)`.
|
|
2249
2287
|
|
|
2250
|
-
To enumerate all entities of a domain, omit `query` and pass `domain_filter
|
|
2251
|
-
|
|
2252
|
-
|
|
2288
|
+
To enumerate all entities of a domain, omit `query` and pass `domain_filter` —
|
|
2289
|
+
e.g. `ha_search(domain_filter="calendar")` lists all calendars. To enumerate
|
|
2290
|
+
every entity in a state, omit `query` and pass `state_filter` — e.g.
|
|
2291
|
+
`ha_search(state_filter="unavailable")`. At least one of `query`,
|
|
2292
|
+
`domain_filter`, `area_filter`, or `state_filter` must be set.
|
|
2253
2293
|
|
|
2254
2294
|
``prefetched_states`` / ``prefetched_registry`` are the orchestrator's
|
|
2255
2295
|
shared snapshots; they only reach the regular (non-area, non-domain-only)
|
|
@@ -2257,7 +2297,7 @@ class SearchTools:
|
|
|
2257
2297
|
"""
|
|
2258
2298
|
query, domain_filter, area_filter, parsed_result_fields = (
|
|
2259
2299
|
_validate_entity_search_params(
|
|
2260
|
-
query, domain_filter, area_filter, result_fields
|
|
2300
|
+
query, domain_filter, area_filter, result_fields, state_filter
|
|
2261
2301
|
)
|
|
2262
2302
|
)
|
|
2263
2303
|
group_by_domain_bool = group_by_domain
|
|
@@ -2328,6 +2368,17 @@ class SearchTools:
|
|
|
2328
2368
|
parsed_result_fields,
|
|
2329
2369
|
)
|
|
2330
2370
|
|
|
2371
|
+
if state_filter and not domain_filter and (not query or not query.strip()):
|
|
2372
|
+
return await self._search_state_only(
|
|
2373
|
+
state_filter,
|
|
2374
|
+
limit,
|
|
2375
|
+
offset,
|
|
2376
|
+
include_hidden_bool,
|
|
2377
|
+
group_by_domain_bool,
|
|
2378
|
+
per_domain_limit_int,
|
|
2379
|
+
parsed_result_fields,
|
|
2380
|
+
)
|
|
2381
|
+
|
|
2331
2382
|
return await self._search_regular(
|
|
2332
2383
|
query,
|
|
2333
2384
|
domain_filter,
|
|
@@ -2358,6 +2409,7 @@ class SearchTools:
|
|
|
2358
2409
|
"query": query,
|
|
2359
2410
|
"domain_filter": domain_filter,
|
|
2360
2411
|
"area_filter": area_filter,
|
|
2412
|
+
"state_filter": state_filter,
|
|
2361
2413
|
},
|
|
2362
2414
|
)
|
|
2363
2415
|
)
|
|
@@ -2369,11 +2421,12 @@ class SearchTools:
|
|
|
2369
2421
|
"query": query,
|
|
2370
2422
|
"domain_filter": domain_filter,
|
|
2371
2423
|
"area_filter": area_filter,
|
|
2424
|
+
"state_filter": state_filter,
|
|
2372
2425
|
},
|
|
2373
2426
|
suggestions=[
|
|
2374
2427
|
"Check Home Assistant connection",
|
|
2375
2428
|
"Try simpler search terms",
|
|
2376
|
-
"Check area/domain filter spelling",
|
|
2429
|
+
"Check area/domain/state filter spelling",
|
|
2377
2430
|
],
|
|
2378
2431
|
)
|
|
2379
2432
|
return None # unreachable: error helpers above always raise
|
|
@@ -2639,7 +2692,7 @@ class SearchTools:
|
|
|
2639
2692
|
]
|
|
2640
2693
|
|
|
2641
2694
|
if state_filter:
|
|
2642
|
-
results = [r for r in results if r
|
|
2695
|
+
results = [r for r in results if _state_matches(r, state_filter)]
|
|
2643
2696
|
|
|
2644
2697
|
pagination = _build_pagination_metadata(total_matches, offset, limit, results)
|
|
2645
2698
|
|
|
@@ -2720,7 +2773,7 @@ class SearchTools:
|
|
|
2720
2773
|
|
|
2721
2774
|
all_results.sort(key=lambda x: (-x["score"], x["entity_id"]))
|
|
2722
2775
|
if state_filter:
|
|
2723
|
-
all_results = [r for r in all_results if r
|
|
2776
|
+
all_results = [r for r in all_results if _state_matches(r, state_filter)]
|
|
2724
2777
|
paginated = all_results[offset : offset + limit]
|
|
2725
2778
|
|
|
2726
2779
|
area_search_data: dict[str, Any] = {
|
|
@@ -2807,24 +2860,22 @@ class SearchTools:
|
|
|
2807
2860
|
# No add_timezone_metadata — see _search_area_with_query.
|
|
2808
2861
|
return empty_area_data
|
|
2809
2862
|
|
|
2810
|
-
async def
|
|
2863
|
+
async def _fetch_listing_snapshot(
|
|
2811
2864
|
self,
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
# gated: it only feeds the visibility area/label dimensions, so a
|
|
2827
|
-
# default/area-free config skips the fetch entirely.
|
|
2865
|
+
) -> tuple[list[dict[str, Any]], set[str], set[str], list[str]]:
|
|
2866
|
+
"""Fetch states + registries and resolve hidden/visibility sets for listing modes.
|
|
2867
|
+
|
|
2868
|
+
Shared by ``_search_domain_only`` and ``_search_state_only``: both
|
|
2869
|
+
enumerate the full state machine and need the same ``/api/states``
|
|
2870
|
+
snapshot, registry-derived hidden ids, and visibility-excluded set.
|
|
2871
|
+
Returns ``(states, hidden_ids, visibility_hidden, visibility_warnings)``.
|
|
2872
|
+
|
|
2873
|
+
Fetches states + the entity registry in parallel. Registry-list failure is
|
|
2874
|
+
tolerated (we just lose the hidden filter); states-fetch failure is fatal —
|
|
2875
|
+
auth/connection errors must propagate. The device registry is gated: it
|
|
2876
|
+
only feeds the visibility area/label dimensions, so a default/area-free
|
|
2877
|
+
config skips the fetch entirely.
|
|
2878
|
+
"""
|
|
2828
2879
|
need_device = await device_registry_needed_for_visibility()
|
|
2829
2880
|
fetch_coros: list[Any] = [
|
|
2830
2881
|
self._client.get_states(),
|
|
@@ -2860,6 +2911,27 @@ class SearchTools:
|
|
|
2860
2911
|
visibility_hidden, visibility_warnings = await load_hidden_set(
|
|
2861
2912
|
registry_result, states_result, self._client, device_result
|
|
2862
2913
|
)
|
|
2914
|
+
return states_result, hidden_ids, visibility_hidden, visibility_warnings
|
|
2915
|
+
|
|
2916
|
+
async def _search_domain_only(
|
|
2917
|
+
self,
|
|
2918
|
+
query: str | None,
|
|
2919
|
+
domain_filter: str,
|
|
2920
|
+
state_filter: str | None,
|
|
2921
|
+
limit: int,
|
|
2922
|
+
offset: int,
|
|
2923
|
+
include_hidden_bool: bool,
|
|
2924
|
+
group_by_domain_bool: bool,
|
|
2925
|
+
per_domain_limit_int: int | None,
|
|
2926
|
+
parsed_result_fields: list[str] | None,
|
|
2927
|
+
) -> dict[str, Any]:
|
|
2928
|
+
"""List all entities of a single domain (empty query + domain_filter)."""
|
|
2929
|
+
(
|
|
2930
|
+
states_result,
|
|
2931
|
+
hidden_ids,
|
|
2932
|
+
visibility_hidden,
|
|
2933
|
+
visibility_warnings,
|
|
2934
|
+
) = await self._fetch_listing_snapshot()
|
|
2863
2935
|
|
|
2864
2936
|
# Filter by domain. Hidden entities are kept by default (with score
|
|
2865
2937
|
# penalty applied below); ``include_hidden=False`` filters them out.
|
|
@@ -2895,7 +2967,7 @@ class SearchTools:
|
|
|
2895
2967
|
scored_entities.sort(key=lambda x: (-x["score"], x["entity_id"]))
|
|
2896
2968
|
if state_filter:
|
|
2897
2969
|
scored_entities = [
|
|
2898
|
-
e for e in scored_entities if e
|
|
2970
|
+
e for e in scored_entities if _state_matches(e, state_filter)
|
|
2899
2971
|
]
|
|
2900
2972
|
results = scored_entities[offset : offset + limit]
|
|
2901
2973
|
|
|
@@ -2925,6 +2997,96 @@ class SearchTools:
|
|
|
2925
2997
|
domain_list_data, [*visibility_warnings, *enrich_warnings]
|
|
2926
2998
|
)
|
|
2927
2999
|
|
|
3000
|
+
async def _search_state_only(
|
|
3001
|
+
self,
|
|
3002
|
+
state_filter: str,
|
|
3003
|
+
limit: int,
|
|
3004
|
+
offset: int,
|
|
3005
|
+
include_hidden_bool: bool,
|
|
3006
|
+
group_by_domain_bool: bool,
|
|
3007
|
+
per_domain_limit_int: int | None,
|
|
3008
|
+
parsed_result_fields: list[str] | None,
|
|
3009
|
+
) -> dict[str, Any]:
|
|
3010
|
+
"""List all entities in a given state (state_filter with no query/domain/area).
|
|
3011
|
+
|
|
3012
|
+
Mirrors ``_search_domain_only`` but enumerates across every domain,
|
|
3013
|
+
filtering on exact state equality (``state_filter`` is already lowercased
|
|
3014
|
+
by ``_normalize_state_filter``) so a single call answers "all unavailable
|
|
3015
|
+
entities" (issue #2002). The state filter is applied before pagination so
|
|
3016
|
+
``total_matches`` reflects the filtered count.
|
|
3017
|
+
"""
|
|
3018
|
+
(
|
|
3019
|
+
states_result,
|
|
3020
|
+
hidden_ids,
|
|
3021
|
+
visibility_hidden,
|
|
3022
|
+
visibility_warnings,
|
|
3023
|
+
) = await self._fetch_listing_snapshot()
|
|
3024
|
+
|
|
3025
|
+
# Exact state match — identical semantics to the exact/domain paths.
|
|
3026
|
+
# Hidden entities are kept by default (score-penalised below);
|
|
3027
|
+
# ``include_hidden=False`` filters them out. The visibility exclude is
|
|
3028
|
+
# applied before pagination so the counts below stay coherent.
|
|
3029
|
+
filtered_entities = [
|
|
3030
|
+
e
|
|
3031
|
+
for e in states_result
|
|
3032
|
+
if (eid := e.get("entity_id", "")) not in visibility_hidden
|
|
3033
|
+
and _state_matches(e, state_filter)
|
|
3034
|
+
and (include_hidden_bool or eid not in hidden_ids)
|
|
3035
|
+
]
|
|
3036
|
+
|
|
3037
|
+
# Score: 100 baseline for state membership (exact, not fuzzy); penalised
|
|
3038
|
+
# for hidden entries so they sort below visible peers. ``domain`` is
|
|
3039
|
+
# derived per record since results span every domain.
|
|
3040
|
+
scored_entities = []
|
|
3041
|
+
for entity in filtered_entities:
|
|
3042
|
+
entity_id = entity.get("entity_id", "")
|
|
3043
|
+
attributes = entity.get("attributes", {})
|
|
3044
|
+
score = apply_hidden_penalty(
|
|
3045
|
+
100, "_hidden" if entity_id in hidden_ids else None
|
|
3046
|
+
)
|
|
3047
|
+
scored_entities.append(
|
|
3048
|
+
{
|
|
3049
|
+
"entity_id": entity_id,
|
|
3050
|
+
"friendly_name": attributes.get("friendly_name", entity_id),
|
|
3051
|
+
"domain": entity_id.split(".")[0],
|
|
3052
|
+
"state": entity.get("state", "unknown"),
|
|
3053
|
+
"score": score,
|
|
3054
|
+
"match_type": "state_listing",
|
|
3055
|
+
}
|
|
3056
|
+
)
|
|
3057
|
+
scored_entities.sort(key=lambda x: (-x["score"], x["entity_id"]))
|
|
3058
|
+
results = scored_entities[offset : offset + limit]
|
|
3059
|
+
|
|
3060
|
+
state_list_data: dict[str, Any] = {
|
|
3061
|
+
"success": True,
|
|
3062
|
+
"query": None,
|
|
3063
|
+
"state_filter": state_filter,
|
|
3064
|
+
**_build_pagination_metadata(len(scored_entities), offset, limit, results),
|
|
3065
|
+
"results": results,
|
|
3066
|
+
"search_type": "state_listing",
|
|
3067
|
+
"note": (
|
|
3068
|
+
f"Listing all entities in state '{state_filter}' "
|
|
3069
|
+
"(state_filter with no query/domain/area)"
|
|
3070
|
+
),
|
|
3071
|
+
}
|
|
3072
|
+
|
|
3073
|
+
enrich_warnings = await self._maybe_enrich_entity_records(
|
|
3074
|
+
results, parsed_result_fields
|
|
3075
|
+
)
|
|
3076
|
+
_apply_result_fields_to_response(state_list_data, parsed_result_fields)
|
|
3077
|
+
_apply_by_domain_grouping(
|
|
3078
|
+
state_list_data,
|
|
3079
|
+
results,
|
|
3080
|
+
group_by_domain_bool,
|
|
3081
|
+
per_domain_limit_int,
|
|
3082
|
+
parsed_result_fields,
|
|
3083
|
+
)
|
|
3084
|
+
|
|
3085
|
+
# No add_timezone_metadata — see _search_area_with_query.
|
|
3086
|
+
return merge_visibility_warnings(
|
|
3087
|
+
state_list_data, [*visibility_warnings, *enrich_warnings]
|
|
3088
|
+
)
|
|
3089
|
+
|
|
2928
3090
|
async def _search_regular(
|
|
2929
3091
|
self,
|
|
2930
3092
|
query: str,
|
|
@@ -3013,7 +3175,7 @@ class SearchTools:
|
|
|
3013
3175
|
# page-only — smart_entity_search already paginated internally, so
|
|
3014
3176
|
# total_matches/has_more reflect the unfiltered dataset.
|
|
3015
3177
|
if state_filter and "results" in result and search_type == "fuzzy_search":
|
|
3016
|
-
filtered = [r for r in result["results"] if r
|
|
3178
|
+
filtered = [r for r in result["results"] if _state_matches(r, state_filter)]
|
|
3017
3179
|
result["results"] = filtered
|
|
3018
3180
|
result["count"] = len(filtered)
|
|
3019
3181
|
result["state_filter_note"] = (
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/client/supervisor_client.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/client/websocket_client.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/client/websocket_listener.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/dashboard_screenshot/__init__.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/dashboard_screenshot/capture.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/dashboard_screenshot/content.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/dashboard_screenshot/paths.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/dashboard_screenshot/provision.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|