ha-mcp-dev 7.14.2.dev2129__tar.gz → 7.14.2.dev2131__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.dev2129/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.14.2.dev2131}/PKG-INFO +1 -1
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/pyproject.toml +1 -1
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_yaml_read.py +217 -43
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/setup.cfg +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/locales/de.json +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/locales/en.json +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/utils/config_write_lock.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/visibility/enforcement.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/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.dev2131"
|
|
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
|
|
@@ -17,6 +17,7 @@ back door.
|
|
|
17
17
|
|
|
18
18
|
import asyncio
|
|
19
19
|
import logging
|
|
20
|
+
import re
|
|
20
21
|
from typing import Annotated, Any
|
|
21
22
|
|
|
22
23
|
from fastmcp.exceptions import ToolError
|
|
@@ -43,10 +44,46 @@ logger = logging.getLogger(__name__)
|
|
|
43
44
|
# fnmatches on the file name); this only decides single-file vs. glob.
|
|
44
45
|
_GLOB_METACHARS = ("*", "?", "[")
|
|
45
46
|
|
|
47
|
+
# The metacharacters that match their own literal names, and so are pattern
|
|
48
|
+
# syntax rather than a plausible spelling of a file name the caller means
|
|
49
|
+
# exactly. A bracket class is the ambiguous one and is deliberately absent.
|
|
50
|
+
_PATTERN_ONLY_METACHARS = ("*", "?")
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
# Wraps each metacharacter in a bracket class so fnmatch reads it literally.
|
|
53
|
+
# Deliberately not glob.escape: it splits a drive off first and returns that
|
|
54
|
+
# part UNESCAPED, and the split is ntpath on a Windows-hosted server - where a
|
|
55
|
+
# name like \\weird[a].yaml is taken for a drive in its entirety and comes back
|
|
56
|
+
# with its bracket intact. The one host-dependent step in a path that is
|
|
57
|
+
# otherwise POSIX and config-relative.
|
|
58
|
+
_METACHAR_RE = re.compile(r"([*?\[])")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _has_glob(name: str) -> bool:
|
|
62
|
+
"""True if ``name`` carries an fnmatch wildcard and must be expanded.
|
|
63
|
+
|
|
64
|
+
Takes the file name rather than the whole path: the component resolves
|
|
65
|
+
directories literally, so a metacharacter in a folder name belongs to that
|
|
66
|
+
folder's name and is not a pattern.
|
|
67
|
+
"""
|
|
68
|
+
return any(char in name for char in _GLOB_METACHARS)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _names_a_file(pattern: str) -> bool:
|
|
72
|
+
"""True if the request can be read as naming one file rather than a class.
|
|
73
|
+
|
|
74
|
+
``*`` and ``?`` are pattern syntax: they match their own literal names, so
|
|
75
|
+
a caller who wrote one wrote a pattern. A bracket class alone is ambiguous,
|
|
76
|
+
since such a name is legal on the filesystem and cannot match itself.
|
|
77
|
+
|
|
78
|
+
One predicate for two decisions that must not diverge - whether a path the
|
|
79
|
+
literal lookup returned counts as explicitly named, and whether the absence
|
|
80
|
+
of such a path is worth a warning. Honouring it in the warning alone would
|
|
81
|
+
leave a file genuinely called ``[ab]*.yaml`` strict, so one failed read of
|
|
82
|
+
it would discard every match the pattern half had already found: the shape
|
|
83
|
+
removed for ``*.yaml`` when provenance moved into the resolver, reachable
|
|
84
|
+
again through the other lookup.
|
|
85
|
+
"""
|
|
86
|
+
return not any(char in pattern for char in _PATTERN_ONLY_METACHARS)
|
|
50
87
|
|
|
51
88
|
|
|
52
89
|
def _call_failed(service: str, context: dict[str, Any]) -> None:
|
|
@@ -83,16 +120,18 @@ def _failure_text(payload: dict[str, Any]) -> str:
|
|
|
83
120
|
|
|
84
121
|
|
|
85
122
|
def _parse_error_result(
|
|
86
|
-
target: str, yaml_path: str, parse_error: str, *,
|
|
123
|
+
target: str, yaml_path: str, parse_error: str, *, is_expanded: bool
|
|
87
124
|
) -> tuple[None, str]:
|
|
88
|
-
"""Decide a file the component could not parse: raise for a
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
the
|
|
125
|
+
"""Decide a file the component could not parse: raise for a named target.
|
|
126
|
+
|
|
127
|
+
The explicitly named target raises rather than soft-degrading to a warning
|
|
128
|
+
that reads as "key absent" - it raises even when expansion turned up
|
|
129
|
+
siblings alongside it, discarding their results, because a named file that
|
|
130
|
+
cannot be parsed is the caller's question, not a stray unreadable neighbour.
|
|
131
|
+
An expanded target stays a per-file warning so one broken file does not
|
|
132
|
+
sink the whole search.
|
|
94
133
|
"""
|
|
95
|
-
if not
|
|
134
|
+
if not is_expanded:
|
|
96
135
|
raise_tool_error(
|
|
97
136
|
create_error_response(
|
|
98
137
|
ErrorCode.CONFIG_INVALID,
|
|
@@ -104,12 +143,39 @@ def _parse_error_result(
|
|
|
104
143
|
return None, f"{target} was not searched: {parse_error}."
|
|
105
144
|
|
|
106
145
|
|
|
146
|
+
def _read_exception_result(
|
|
147
|
+
target: str, error: BaseException, *, is_expanded: bool
|
|
148
|
+
) -> tuple[None, str]:
|
|
149
|
+
"""Decide a read that blew up outright.
|
|
150
|
+
|
|
151
|
+
An expanded target degrades to a warning so one unreadable file cannot sink
|
|
152
|
+
the search; the explicitly named target raises, as it does for every other
|
|
153
|
+
failure class in :func:`_evaluate_read`. The error is re-raised as it came
|
|
154
|
+
rather than wrapped, so the tool's own handler classifies it by type - the
|
|
155
|
+
other classes have no live exception to preserve and build a structured
|
|
156
|
+
:class:`ToolError` instead.
|
|
157
|
+
|
|
158
|
+
Anything that is not an ``Exception`` propagates whatever the target's
|
|
159
|
+
provenance - this is the one failure class here that raises for an expanded
|
|
160
|
+
target too. Cancellation and shutdown are not one file being unreadable,
|
|
161
|
+
and absorbing them into a warning next to ``success: true`` would report a
|
|
162
|
+
search that was called off as one that ran and found nothing. The per-item
|
|
163
|
+
fan-outs in ``tools_entities`` and ``tools_search`` draw the same line.
|
|
164
|
+
|
|
165
|
+
``str()`` is empty on a bare ``TimeoutError()``, so the class name stands
|
|
166
|
+
in - a warning reading "was not searched: ." names no reason at all.
|
|
167
|
+
"""
|
|
168
|
+
if not is_expanded or not isinstance(error, Exception):
|
|
169
|
+
raise error
|
|
170
|
+
return None, f"{target} was not searched: {str(error) or type(error).__name__}."
|
|
171
|
+
|
|
172
|
+
|
|
107
173
|
def _evaluate_read(
|
|
108
174
|
response: Any,
|
|
109
175
|
target: str,
|
|
110
176
|
yaml_path: str,
|
|
111
177
|
*,
|
|
112
|
-
|
|
178
|
+
is_expanded: bool,
|
|
113
179
|
include_content: bool,
|
|
114
180
|
include_parsed: bool,
|
|
115
181
|
) -> tuple[dict[str, Any] | None, str | None]:
|
|
@@ -117,37 +183,37 @@ def _evaluate_read(
|
|
|
117
183
|
|
|
118
184
|
Three outcomes, in the order they are decided:
|
|
119
185
|
|
|
120
|
-
* **warning** — the file could not be searched at all.
|
|
186
|
+
* **warning** — the file could not be searched at all. An expanded target
|
|
121
187
|
must not sink the whole search and discard the matches already found:
|
|
122
|
-
|
|
188
|
+
expansion is not restricted to ``*.yaml``, so ``packages/*`` can turn up
|
|
123
189
|
a README the component refuses to read, and a permission-denied or
|
|
124
190
|
non-UTF-8 ``.yaml`` lands here too. A syntactically broken file is the
|
|
125
191
|
same class — reporting it as "key absent" would claim a file was
|
|
126
|
-
inspected when it never was.
|
|
127
|
-
|
|
192
|
+
inspected when it never was. The explicitly named target raises
|
|
193
|
+
instead, even when expansion turned up siblings alongside it.
|
|
128
194
|
* **neither** — the file parsed and simply has no such key. The real
|
|
129
195
|
non-match, and the whole point of a glob search.
|
|
130
196
|
* **match** — the key is there.
|
|
131
197
|
"""
|
|
132
|
-
# Only reachable under a glob: with a single target return_exceptions is
|
|
133
|
-
# False, so gather itself raised.
|
|
134
198
|
if isinstance(response, BaseException):
|
|
135
|
-
return
|
|
199
|
+
return _read_exception_result(target, response, is_expanded=is_expanded)
|
|
136
200
|
|
|
137
201
|
if not isinstance(response, dict):
|
|
138
|
-
if
|
|
202
|
+
if is_expanded:
|
|
139
203
|
return None, f"{target} was not searched: unexpected read_file response."
|
|
140
204
|
_call_failed("read_file", {"file": target, "yaml_path": yaml_path})
|
|
141
205
|
unwrapped = unwrap_service_response(response)
|
|
142
206
|
|
|
143
207
|
if not unwrapped.get("success", True):
|
|
144
|
-
if not
|
|
208
|
+
if not is_expanded:
|
|
145
209
|
raise_tool_error(unwrapped)
|
|
146
210
|
return None, f"{target} was not searched: {_failure_text(unwrapped)}."
|
|
147
211
|
|
|
148
212
|
parse_error = unwrapped.get("parse_error")
|
|
149
213
|
if parse_error:
|
|
150
|
-
return _parse_error_result(
|
|
214
|
+
return _parse_error_result(
|
|
215
|
+
target, yaml_path, str(parse_error), is_expanded=is_expanded
|
|
216
|
+
)
|
|
151
217
|
|
|
152
218
|
if unwrapped.get("subtree") is None:
|
|
153
219
|
return None, None
|
|
@@ -160,18 +226,10 @@ def _evaluate_read(
|
|
|
160
226
|
return match, None
|
|
161
227
|
|
|
162
228
|
|
|
163
|
-
async def
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
glob is expanded through the component's ``list_files``, which matches the
|
|
168
|
-
file name only — so ``packages/*.yaml`` covers one directory level, not a
|
|
169
|
-
nested tree.
|
|
170
|
-
"""
|
|
171
|
-
if not _has_glob(file):
|
|
172
|
-
return [file]
|
|
173
|
-
|
|
174
|
-
directory, _, pattern = file.rpartition("/")
|
|
229
|
+
async def _list_files_matching(
|
|
230
|
+
client: Any, file: str, directory: str, pattern: str
|
|
231
|
+
) -> list[str]:
|
|
232
|
+
"""Return the non-directory paths in ``directory`` matching ``pattern``."""
|
|
175
233
|
unwrapped = _unwrap_or_raise(
|
|
176
234
|
await call_mcp_tools_service(
|
|
177
235
|
client,
|
|
@@ -192,6 +250,113 @@ async def _resolve_target_files(client: Any, file: str) -> list[str]:
|
|
|
192
250
|
)
|
|
193
251
|
|
|
194
252
|
|
|
253
|
+
def _absent_literal_warnings(
|
|
254
|
+
file: str, pattern: str, *, literal_found: bool, target_count: int
|
|
255
|
+
) -> list[str]:
|
|
256
|
+
"""Say so when nothing is literally named the way a bracketed request was.
|
|
257
|
+
|
|
258
|
+
Silence is what the caller reads as "your file was searched": the siblings
|
|
259
|
+
the class matched arrive as ``files_searched`` with nothing marking the
|
|
260
|
+
named file as never opened.
|
|
261
|
+
|
|
262
|
+
Two cases stay silent. A literal that was found needs no warning, and
|
|
263
|
+
neither does a request that :func:`_names_a_file` rejects - nobody named a
|
|
264
|
+
file, so nothing is missing. The literal lookup still runs for those, since
|
|
265
|
+
its results are merged as ordinary expansion matches; only the complaint is
|
|
266
|
+
dropped.
|
|
267
|
+
"""
|
|
268
|
+
if literal_found or not _names_a_file(pattern):
|
|
269
|
+
return []
|
|
270
|
+
if not target_count:
|
|
271
|
+
# Phrased for the degenerate case rather than reporting "the 0 file(s)
|
|
272
|
+
# matching it as a pattern", which reads as a search that happened.
|
|
273
|
+
return [
|
|
274
|
+
f"No file is literally named {file}, and nothing matched it as a "
|
|
275
|
+
f"pattern either."
|
|
276
|
+
]
|
|
277
|
+
return [
|
|
278
|
+
f"No file is literally named {file}; searched the {target_count} "
|
|
279
|
+
f"file(s) matching it as a pattern."
|
|
280
|
+
]
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
async def _resolve_target_files(
|
|
284
|
+
client: Any, file: str
|
|
285
|
+
) -> tuple[list[tuple[str, bool]], list[str]]:
|
|
286
|
+
"""Expand ``file`` into ``(path, is_expanded)`` pairs plus any warnings.
|
|
287
|
+
|
|
288
|
+
``is_expanded`` records where the path came from, and it is decided here
|
|
289
|
+
because only here is it known: a path produced by the pattern lookup is an
|
|
290
|
+
expansion, while the requested path itself, and what the escaped-literal
|
|
291
|
+
lookup returns for a request :func:`_names_a_file` accepts, were named
|
|
292
|
+
explicitly. Recovering that downstream by comparing the path against the
|
|
293
|
+
requested string would be wrong for ``*`` and ``?``, which match their own
|
|
294
|
+
literal names - a file genuinely called ``*.yaml`` would then be mistaken
|
|
295
|
+
for the name the caller typed and its read failure would sink the whole
|
|
296
|
+
search.
|
|
297
|
+
|
|
298
|
+
A plain path is returned as-is (the read itself reports a missing file). A
|
|
299
|
+
glob is expanded through the component's ``list_files``, which matches the
|
|
300
|
+
file name only, so ``packages/*.yaml`` covers one directory level rather
|
|
301
|
+
than a nested tree. Only the name is inspected for metacharacters: the
|
|
302
|
+
component resolves the directory literally, so ``pack[a]ges/svc.yaml``
|
|
303
|
+
names one file and is read as one rather than expanded.
|
|
304
|
+
|
|
305
|
+
A bracketed name is ambiguous: ``packages/svc[a].yaml`` is both a valid
|
|
306
|
+
pattern and a valid filename, and a closed bracket class is the one
|
|
307
|
+
metacharacter form whose pattern cannot match its own name (``fnmatch``
|
|
308
|
+
reads ``[a]`` as the single character ``a``, while ``*`` and ``?`` do match
|
|
309
|
+
themselves). Such a name is therefore looked up twice, as the pattern and
|
|
310
|
+
as the escaped literal, and the results are merged: the pattern alone would
|
|
311
|
+
report ``files_searched: 0`` for a file that exists, and the literal alone
|
|
312
|
+
would drop the siblings a deliberate class like ``svc[12].yaml`` is meant
|
|
313
|
+
to find. Both lookups run because a sibling that satisfies the class would
|
|
314
|
+
otherwise mask the file the caller named exactly. Only bracketed names pay
|
|
315
|
+
the second lookup, and it is issued concurrently with the first rather than
|
|
316
|
+
after it. The component likewise treats package folder names carrying
|
|
317
|
+
metacharacters as literal names (#1854).
|
|
318
|
+
|
|
319
|
+
The guard is any bracket, not a closed class: an unclosed or empty bracket
|
|
320
|
+
is literal to ``fnmatch`` and so matches its own name, which means both
|
|
321
|
+
lookups return it and the deduplication below is what keeps it a single
|
|
322
|
+
target. A listing failure on either lookup raises rather than degrading -
|
|
323
|
+
the directory is the same one in both calls, so a failure there is a broken
|
|
324
|
+
request, not one unreadable file among several.
|
|
325
|
+
|
|
326
|
+
When the literal lookup comes back empty the caller named a file that does
|
|
327
|
+
not exist, and the pattern's siblings would otherwise be reported as if the
|
|
328
|
+
named file had been searched; :func:`_absent_literal_warnings` decides what
|
|
329
|
+
to say about that.
|
|
330
|
+
"""
|
|
331
|
+
directory, _, pattern = file.rpartition("/")
|
|
332
|
+
if not _has_glob(pattern):
|
|
333
|
+
return [(file, False)], []
|
|
334
|
+
|
|
335
|
+
if "[" not in pattern:
|
|
336
|
+
matched = await _list_files_matching(client, file, directory, pattern)
|
|
337
|
+
return [(target, True) for target in matched], []
|
|
338
|
+
|
|
339
|
+
# The two lookups are independent, so run them together rather than paying
|
|
340
|
+
# two sequential round-trips; the reads below fan out for the same reason.
|
|
341
|
+
matched, literal = await asyncio.gather(
|
|
342
|
+
_list_files_matching(client, file, directory, pattern),
|
|
343
|
+
_list_files_matching(
|
|
344
|
+
client, file, directory, _METACHAR_RE.sub(r"[\1]", pattern)
|
|
345
|
+
),
|
|
346
|
+
)
|
|
347
|
+
# Both lookups always contribute targets; only whether a literal hit counts
|
|
348
|
+
# as *named* depends on the request. Under a pattern it is merged as an
|
|
349
|
+
# ordinary expansion match, so nobody named it and a bad read stays a
|
|
350
|
+
# warning.
|
|
351
|
+
literal_paths = set(literal)
|
|
352
|
+
named = literal_paths if _names_a_file(pattern) else set()
|
|
353
|
+
targets = sorted(set(matched) | literal_paths)
|
|
354
|
+
warnings = _absent_literal_warnings(
|
|
355
|
+
file, pattern, literal_found=bool(named), target_count=len(targets)
|
|
356
|
+
)
|
|
357
|
+
return [(target, target not in named) for target in targets], warnings
|
|
358
|
+
|
|
359
|
+
|
|
195
360
|
class YamlReadTools:
|
|
196
361
|
def __init__(self, client: Any) -> None:
|
|
197
362
|
self._client = client
|
|
@@ -273,15 +438,18 @@ class YamlReadTools:
|
|
|
273
438
|
try:
|
|
274
439
|
await _assert_mcp_tools_available(self._client)
|
|
275
440
|
|
|
276
|
-
|
|
277
|
-
targets
|
|
441
|
+
# Strictness is a property of each target, not of the request, so
|
|
442
|
+
# the resolver reports which targets were named rather than expanded.
|
|
443
|
+
targets, resolve_warnings = await _resolve_target_files(self._client, file)
|
|
278
444
|
|
|
279
445
|
extra: dict[str, Any] = {"include_parsed": True} if include_parsed else {}
|
|
280
446
|
# The reads are independent, so fan them out rather than paying N
|
|
281
447
|
# sequential round-trips to HA — a packages glob is routinely 10+
|
|
282
448
|
# files. gather preserves order, so matches stay sorted by file.
|
|
283
|
-
#
|
|
284
|
-
#
|
|
449
|
+
# An expanded target blowing up must not sink the search, so every
|
|
450
|
+
# exception comes back as a value; whether it degrades to a warning
|
|
451
|
+
# or is re-raised is then decided per target, like every other
|
|
452
|
+
# failure class.
|
|
285
453
|
responses = await asyncio.gather(
|
|
286
454
|
*(
|
|
287
455
|
call_mcp_tools_service(
|
|
@@ -289,24 +457,26 @@ class YamlReadTools:
|
|
|
289
457
|
"read_file",
|
|
290
458
|
{"path": target, "yaml_path": yaml_path, **extra},
|
|
291
459
|
)
|
|
292
|
-
for target in targets
|
|
460
|
+
for target, _ in targets
|
|
293
461
|
),
|
|
294
|
-
return_exceptions=
|
|
462
|
+
return_exceptions=True,
|
|
295
463
|
)
|
|
296
464
|
|
|
297
465
|
matches: list[dict[str, Any]] = []
|
|
298
|
-
warnings: list[str] =
|
|
299
|
-
|
|
466
|
+
warnings: list[str] = list(resolve_warnings)
|
|
467
|
+
files_unreadable = 0
|
|
468
|
+
for (target, is_expanded), response in zip(targets, responses, strict=True):
|
|
300
469
|
match, warning = _evaluate_read(
|
|
301
470
|
response,
|
|
302
471
|
target,
|
|
303
472
|
yaml_path,
|
|
304
|
-
|
|
473
|
+
is_expanded=is_expanded,
|
|
305
474
|
include_content=include_content,
|
|
306
475
|
include_parsed=include_parsed,
|
|
307
476
|
)
|
|
308
477
|
if warning:
|
|
309
478
|
warnings.append(warning)
|
|
479
|
+
files_unreadable += 1
|
|
310
480
|
elif match:
|
|
311
481
|
matches.append(match)
|
|
312
482
|
|
|
@@ -318,6 +488,10 @@ class YamlReadTools:
|
|
|
318
488
|
# Separates "the glob matched no files" from "no file defines
|
|
319
489
|
# the key" — same empty matches[], different fix.
|
|
320
490
|
"files_searched": len(targets),
|
|
491
|
+
# Keeps `count: 0` beside a non-zero files_searched from reading
|
|
492
|
+
# as "searched them, found nothing". Counted in the loop, not as
|
|
493
|
+
# len(warnings): those also carry resolver-level entries.
|
|
494
|
+
"files_unreadable": files_unreadable,
|
|
321
495
|
}
|
|
322
496
|
if warnings:
|
|
323
497
|
result["warnings"] = warnings
|
|
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.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/client/supervisor_client.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/client/websocket_client.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/client/websocket_listener.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/dashboard_screenshot/__init__.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/dashboard_screenshot/capture.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/dashboard_screenshot/content.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/dashboard_screenshot/paths.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/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.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/resources/skills-vendor/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_handlers_advanced.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_handlers_backups.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_handlers_fs.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_handlers_server.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_handlers_theme.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_handlers_tools.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_persistence.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_supervisor.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/_tools_meta.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/locales/README.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/locales/de.json
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/locales/en.json
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/locales/ru.json
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/locales/zh-Hans.json
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/settings_ui/settings.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/stdio_settings_sidecar.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/best_practice_checker.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/component_config_reads.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/component_devices.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/component_registries.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/config_entry_flow.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
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/reference_validator.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/__init__.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/_base.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/_config.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/_deep.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/_entities.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/_fetch.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/_overview.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/_scenes.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/smart_search/_scoring.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_blueprints.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_bug_report.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_categories.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_config_helpers.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_config_scenes.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_config_scripts.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_filesystem.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_integrations.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
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_voice_assistant.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/tools_yaml_config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/tools/validation_middleware.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/transforms/categorized_search.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/transforms/lite_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/utils/config_write_lock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/utils/kill_signal_diagnostics.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/utils/operation_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/visibility/enforcement.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp/visibility/persistence.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp_dev.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp_dev.egg-info/requires.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2129 → ha_mcp_dev-7.14.2.dev2131}/src/ha_mcp_dev.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|