ha-mcp-dev 8.0.0.dev2228__tar.gz → 8.0.0.dev2233__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-8.0.0.dev2228/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.0.0.dev2233}/PKG-INFO +1 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/pyproject.toml +1 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/__main__.py +12 -5
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/client/websocket_client.py +30 -2
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/locales/de.json +1 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/locales/es.json +1 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/locales/fr.json +1 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/locales/it.json +1 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/locales/ru.json +1 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/locales/zh-Hans.json +1 -1
- ha_mcp_dev-8.0.0.dev2233/src/ha_mcp/tools/config_entry_flow.py +376 -0
- ha_mcp_dev-8.0.0.dev2233/src/ha_mcp/tools/config_entry_flow_form.py +694 -0
- ha_mcp_dev-8.0.0.dev2233/src/ha_mcp/tools/config_entry_flow_menu.py +185 -0
- ha_mcp_dev-8.0.0.dev2233/src/ha_mcp/tools/config_entry_flow_walker.py +728 -0
- ha_mcp_dev-8.0.0.dev2233/src/ha_mcp/tools/hacs_registration.py +393 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_config_helpers.py +24 -13
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_dev.py +17 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_hacs.py +214 -392
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_integrations.py +5 -2
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp_dev.egg-info/SOURCES.txt +4 -0
- ha_mcp_dev-8.0.0.dev2228/src/ha_mcp/tools/config_entry_flow.py +0 -1718
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/LICENSE +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/MANIFEST.in +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/README.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/setup.cfg +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/locales/en.json +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/best_practice_result.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/best_practice_variables.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/utils/config_write_lock.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/visibility/enforcement.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/tests/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/tests/test_constants.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/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 = "8.0.0.
|
|
7
|
+
version = "8.0.0.dev2233"
|
|
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
|
|
@@ -690,11 +690,18 @@ async def _run_with_shutdown(server_coro: Coroutine[Any, Any, Any]) -> None:
|
|
|
690
690
|
logger.error("Server task cancelled without a shutdown signal")
|
|
691
691
|
raise
|
|
692
692
|
finally:
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
693
|
+
# Cancel-and-abandon, not wait_for: wait_for awaits the cancelled
|
|
694
|
+
# coroutine before raising, and the cleanup stack swallows
|
|
695
|
+
# CancelledError at several layers (per-client in
|
|
696
|
+
# WebSocketManager.disconnect, in client.disconnect's own task-cancel
|
|
697
|
+
# guard), so a hung close handshake could block shutdown past the
|
|
698
|
+
# budget. A straggler is left to the runner's own task sweep.
|
|
699
|
+
cleanup_task = asyncio.ensure_future(_cleanup_resources())
|
|
700
|
+
_done, cleanup_pending = await asyncio.wait(
|
|
701
|
+
{cleanup_task}, timeout=SHUTDOWN_TIMEOUT_SECONDS
|
|
702
|
+
)
|
|
703
|
+
if cleanup_pending:
|
|
704
|
+
cleanup_task.cancel()
|
|
698
705
|
logger.warning("Resource cleanup timed out")
|
|
699
706
|
|
|
700
707
|
try:
|
|
@@ -1251,10 +1251,31 @@ class WebSocketManager:
|
|
|
1251
1251
|
self._last_used[key] = time.monotonic()
|
|
1252
1252
|
return existing
|
|
1253
1253
|
|
|
1254
|
-
# Remove stale client if present
|
|
1254
|
+
# Remove stale client if present. Disconnect it too: a client
|
|
1255
|
+
# whose connection dropped can still own a parked reader task and
|
|
1256
|
+
# a half-open socket, and simply dropping the reference abandons
|
|
1257
|
+
# both to garbage collection — the GC's asyncgen finalizer then
|
|
1258
|
+
# acloses the reader's ``Connection.__aiter__`` mid-``__anext__``
|
|
1259
|
+
# and logs ``aclose(): asynchronous generator is already
|
|
1260
|
+
# running`` (issue #2127). Same-loop by construction: a loop
|
|
1261
|
+
# change already detached the pool above, so ``existing`` was
|
|
1262
|
+
# built on ``current_loop`` and can be awaited here.
|
|
1255
1263
|
if existing:
|
|
1256
1264
|
self._clients.pop(key, None)
|
|
1257
1265
|
self._last_used.pop(key, None)
|
|
1266
|
+
try:
|
|
1267
|
+
await existing.disconnect()
|
|
1268
|
+
except asyncio.CancelledError:
|
|
1269
|
+
# The caller itself was cancelled mid-cleanup: propagate.
|
|
1270
|
+
# Swallowing here would let a cancelled operation keep
|
|
1271
|
+
# doing network I/O and leave a fresh connection retained
|
|
1272
|
+
# in the pool.
|
|
1273
|
+
raise
|
|
1274
|
+
except (OSError, RuntimeError):
|
|
1275
|
+
logger.warning(
|
|
1276
|
+
"Error disconnecting stale WebSocket client",
|
|
1277
|
+
exc_info=True,
|
|
1278
|
+
)
|
|
1258
1279
|
|
|
1259
1280
|
factory = self._client_factory or HomeAssistantWebSocketClient
|
|
1260
1281
|
client = (
|
|
@@ -1291,7 +1312,14 @@ class WebSocketManager:
|
|
|
1291
1312
|
if stale:
|
|
1292
1313
|
try:
|
|
1293
1314
|
await stale.disconnect()
|
|
1294
|
-
except
|
|
1315
|
+
except asyncio.CancelledError:
|
|
1316
|
+
# The caller itself was cancelled mid-eviction: propagate.
|
|
1317
|
+
# Eviction runs after the fresh client was pooled, so a
|
|
1318
|
+
# swallow here would hand a connected client back to a
|
|
1319
|
+
# cancelled caller (mirror of the stale-replacement clause
|
|
1320
|
+
# in get_client).
|
|
1321
|
+
raise
|
|
1322
|
+
except (OSError, RuntimeError):
|
|
1295
1323
|
logger.warning(
|
|
1296
1324
|
"Error disconnecting evicted WebSocket client",
|
|
1297
1325
|
exc_info=True,
|
{ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/locales/de.json
RENAMED
|
@@ -735,7 +735,7 @@
|
|
|
735
735
|
},
|
|
736
736
|
"ha_manage_hacs": {
|
|
737
737
|
"title": "HACS verwalten",
|
|
738
|
-
"description": "
|
|
738
|
+
"description": "Verwalte HACS (Home Assistant Community Store) — installiere/aktualisiere, entferne oder füge benutzerdefinierte Repositories hinzu."
|
|
739
739
|
},
|
|
740
740
|
"ha_manage_pipeline": {
|
|
741
741
|
"title": "Assist-Pipeline verwalten",
|
{ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/locales/es.json
RENAMED
|
@@ -735,7 +735,7 @@
|
|
|
735
735
|
},
|
|
736
736
|
"ha_manage_hacs": {
|
|
737
737
|
"title": "Gestionar HACS",
|
|
738
|
-
"description": "
|
|
738
|
+
"description": "Gestionar HACS (Home Assistant Community Store) — instalar/actualizar, eliminar o añadir repositorios personalizados."
|
|
739
739
|
},
|
|
740
740
|
"ha_manage_pipeline": {
|
|
741
741
|
"title": "Gestionar canalización de Assist",
|
{ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/locales/fr.json
RENAMED
|
@@ -735,7 +735,7 @@
|
|
|
735
735
|
},
|
|
736
736
|
"ha_manage_hacs": {
|
|
737
737
|
"title": "Gérer HACS",
|
|
738
|
-
"description": "
|
|
738
|
+
"description": "Gérer HACS (Home Assistant Community Store) — installer/mettre à jour, supprimer ou ajouter des dépôts personnalisés."
|
|
739
739
|
},
|
|
740
740
|
"ha_manage_pipeline": {
|
|
741
741
|
"title": "Gérer le pipeline Assist",
|
{ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/locales/it.json
RENAMED
|
@@ -735,7 +735,7 @@
|
|
|
735
735
|
},
|
|
736
736
|
"ha_manage_hacs": {
|
|
737
737
|
"title": "Gestisci HACS",
|
|
738
|
-
"description": "
|
|
738
|
+
"description": "Gestisci HACS (Home Assistant Community Store) — installare/aggiornare, rimuovere o aggiungere repository personalizzati."
|
|
739
739
|
},
|
|
740
740
|
"ha_manage_pipeline": {
|
|
741
741
|
"title": "Gestisci le pipeline di Assist",
|
{ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/locales/ru.json
RENAMED
|
@@ -735,7 +735,7 @@
|
|
|
735
735
|
},
|
|
736
736
|
"ha_manage_hacs": {
|
|
737
737
|
"title": "Управлять HACS",
|
|
738
|
-
"description": "
|
|
738
|
+
"description": "Управление HACS (Магазин сообщества Home Assistant) — устанавливать/обновлять, удалять или добавлять пользовательские репозитории."
|
|
739
739
|
},
|
|
740
740
|
"ha_manage_pipeline": {
|
|
741
741
|
"title": "Управлять конвейером Assist",
|
{ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2233}/src/ha_mcp/settings_ui/locales/zh-Hans.json
RENAMED
|
@@ -735,7 +735,7 @@
|
|
|
735
735
|
},
|
|
736
736
|
"ha_manage_hacs": {
|
|
737
737
|
"title": "管理 HACS",
|
|
738
|
-
"description": "管理 HACS
|
|
738
|
+
"description": "管理 HACS (Home Assistant 社区商店) — 安装/更新、移除或添加自定义存储库。"
|
|
739
739
|
},
|
|
740
740
|
"ha_manage_pipeline": {
|
|
741
741
|
"title": "管理 Assist 管线",
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Config Entry Flow API machinery for Home Assistant MCP server.
|
|
3
|
+
|
|
4
|
+
This module provides the shared machinery for creating and updating
|
|
5
|
+
config-entry-based helpers (template, group, utility_meter, etc.) via the
|
|
6
|
+
Config Entry Flow API.
|
|
7
|
+
|
|
8
|
+
The create/update entry point is the unified ha_config_set_helper tool in
|
|
9
|
+
tools_config_helpers.py, which routes to create_flow_helper / update_flow_helper
|
|
10
|
+
for the 15 helper types listed in FLOW_HELPER_TYPES.
|
|
11
|
+
|
|
12
|
+
The same flow walkers drive every other config-entry surface, not just
|
|
13
|
+
helpers: ``ha_set_integration`` creates entries for arbitrary domains through
|
|
14
|
+
``create_config_entry`` and edits them through ``update_config_entry_options``,
|
|
15
|
+
and ``ha_config_set_helper(helper_type="config_subentry")`` drives subentry
|
|
16
|
+
flows through ``set_config_subentry``.
|
|
17
|
+
|
|
18
|
+
The step machinery those entry points drive lives in three sibling modules,
|
|
19
|
+
imported in one direction only (menu <- form <- walker <- here):
|
|
20
|
+
|
|
21
|
+
- ``config_entry_flow_menu``: menu selection keys and menu-step handling
|
|
22
|
+
- ``config_entry_flow_form``: form-step schema consumption and reuse tracking
|
|
23
|
+
- ``config_entry_flow_walker``: step submission, HA error translation, flow
|
|
24
|
+
introspection, and the two flow walkers
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import asyncio
|
|
28
|
+
import logging
|
|
29
|
+
from typing import Any, Literal
|
|
30
|
+
|
|
31
|
+
from ..errors import ErrorCode, create_error_response
|
|
32
|
+
from .config_entry_flow_form import _extract_schema_field_names
|
|
33
|
+
from .config_entry_flow_walker import (
|
|
34
|
+
_FlowType,
|
|
35
|
+
_handle_config_subentry_flow_steps,
|
|
36
|
+
_handle_flow_steps,
|
|
37
|
+
)
|
|
38
|
+
from .helpers import raise_tool_error
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
# 15 helpers that use Config Entry Flow API (Issue #324).
|
|
43
|
+
SUPPORTED_HELPERS = Literal[
|
|
44
|
+
"template",
|
|
45
|
+
"group",
|
|
46
|
+
"utility_meter",
|
|
47
|
+
"derivative",
|
|
48
|
+
"min_max",
|
|
49
|
+
"threshold",
|
|
50
|
+
"integration",
|
|
51
|
+
"statistics",
|
|
52
|
+
"trend",
|
|
53
|
+
"random",
|
|
54
|
+
"filter",
|
|
55
|
+
"tod",
|
|
56
|
+
"generic_thermostat",
|
|
57
|
+
"switch_as_x",
|
|
58
|
+
"generic_hygrostat",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
# Value-set form of SUPPORTED_HELPERS for runtime routing checks.
|
|
62
|
+
# Exported for import by tools_config_helpers.ha_config_set_helper.
|
|
63
|
+
FLOW_HELPER_TYPES: frozenset[str] = frozenset(
|
|
64
|
+
{
|
|
65
|
+
"template",
|
|
66
|
+
"group",
|
|
67
|
+
"utility_meter",
|
|
68
|
+
"derivative",
|
|
69
|
+
"min_max",
|
|
70
|
+
"threshold",
|
|
71
|
+
"integration",
|
|
72
|
+
"statistics",
|
|
73
|
+
"trend",
|
|
74
|
+
"random",
|
|
75
|
+
"filter",
|
|
76
|
+
"tod",
|
|
77
|
+
"generic_thermostat",
|
|
78
|
+
"switch_as_x",
|
|
79
|
+
"generic_hygrostat",
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
# Module-level flow machinery
|
|
86
|
+
#
|
|
87
|
+
# These functions are shared by the unified ha_config_set_helper tool in
|
|
88
|
+
# tools_config_helpers.py. They take a client instance as an explicit
|
|
89
|
+
# parameter so the same logic can be used from any caller.
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
async def set_config_subentry(
|
|
94
|
+
client: Any,
|
|
95
|
+
entry_id: str,
|
|
96
|
+
subentry_type: str,
|
|
97
|
+
config_dict: dict[str, Any],
|
|
98
|
+
*,
|
|
99
|
+
subentry_id: str | None = None,
|
|
100
|
+
show_advanced_options: bool | None = None,
|
|
101
|
+
) -> dict[str, Any]:
|
|
102
|
+
"""Create or reconfigure a config subentry via its flow.
|
|
103
|
+
|
|
104
|
+
Presence of ``subentry_id`` is the discriminator: omitted creates a new
|
|
105
|
+
subentry, provided reconfigures that existing subentry.
|
|
106
|
+
``show_advanced_options`` is a no-op on HA 2026.6+ and kept only for older
|
|
107
|
+
HA versions pending removal before HA 2027.6.
|
|
108
|
+
"""
|
|
109
|
+
flow_result = await client.start_config_subentry_flow(
|
|
110
|
+
entry_id,
|
|
111
|
+
subentry_type,
|
|
112
|
+
subentry_id=subentry_id,
|
|
113
|
+
show_advanced_options=show_advanced_options,
|
|
114
|
+
)
|
|
115
|
+
flow_id = flow_result.get("flow_id")
|
|
116
|
+
|
|
117
|
+
if not flow_id:
|
|
118
|
+
raise_tool_error(
|
|
119
|
+
create_error_response(
|
|
120
|
+
ErrorCode.SERVICE_CALL_FAILED,
|
|
121
|
+
"Failed to start config subentry flow",
|
|
122
|
+
suggestions=[
|
|
123
|
+
"Use ha_get_integration(include_subentries=True) to confirm "
|
|
124
|
+
"the parent entry and available subentry metadata.",
|
|
125
|
+
],
|
|
126
|
+
context={
|
|
127
|
+
"entry_id": entry_id,
|
|
128
|
+
"subentry_type": subentry_type,
|
|
129
|
+
"subentry_id": subentry_id,
|
|
130
|
+
"details": flow_result,
|
|
131
|
+
},
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
result = await _handle_config_subentry_flow_steps(
|
|
137
|
+
client,
|
|
138
|
+
flow_id,
|
|
139
|
+
flow_result,
|
|
140
|
+
config_dict,
|
|
141
|
+
is_reconfigure=subentry_id is not None,
|
|
142
|
+
)
|
|
143
|
+
except Exception:
|
|
144
|
+
try:
|
|
145
|
+
await asyncio.wait_for(
|
|
146
|
+
client.abort_config_subentry_flow(flow_id), timeout=5.0
|
|
147
|
+
)
|
|
148
|
+
except Exception as abort_err:
|
|
149
|
+
logger.warning(
|
|
150
|
+
"Failed to abort config subentry flow %s after error: %s",
|
|
151
|
+
flow_id,
|
|
152
|
+
abort_err,
|
|
153
|
+
)
|
|
154
|
+
raise
|
|
155
|
+
|
|
156
|
+
response = {
|
|
157
|
+
"success": True,
|
|
158
|
+
"entry_id": entry_id,
|
|
159
|
+
"subentry_type": subentry_type,
|
|
160
|
+
"subentry_id": subentry_id,
|
|
161
|
+
"operation": result["operation"],
|
|
162
|
+
"flow_result": result["flow_result"],
|
|
163
|
+
"message": f"Config subentry {result['operation']} successfully",
|
|
164
|
+
}
|
|
165
|
+
if result.get("warnings"):
|
|
166
|
+
response["warnings"] = result["warnings"]
|
|
167
|
+
return response
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
async def get_user_step_field_names(client: Any, helper_type: str) -> set[str] | None:
|
|
171
|
+
"""Return field names in the user-step form schema for ``helper_type``.
|
|
172
|
+
|
|
173
|
+
Starts a config flow, peeks at the initial step's ``data_schema``,
|
|
174
|
+
and immediately aborts the flow. Used to decide whether to fold the
|
|
175
|
+
top-level ``name`` parameter into the form payload — some helpers
|
|
176
|
+
(e.g. ``switch_as_x``) take their entity name from the source switch
|
|
177
|
+
and reject ``name`` as an extra key.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
A set of field names if the initial step is a form. ``None`` if
|
|
181
|
+
the flow type is not introspectable from the top step (menu or
|
|
182
|
+
unexpected) — callers should fall back to the legacy behaviour
|
|
183
|
+
in that case to avoid regressing menu helpers (template, group).
|
|
184
|
+
Also returns ``None`` if the introspection itself fails; the
|
|
185
|
+
subsequent real flow will surface the error in context.
|
|
186
|
+
"""
|
|
187
|
+
flow_id = None
|
|
188
|
+
try:
|
|
189
|
+
flow_result = await client.start_config_flow(helper_type)
|
|
190
|
+
flow_id = flow_result.get("flow_id")
|
|
191
|
+
if flow_result.get("type") != _FlowType.FORM:
|
|
192
|
+
return None
|
|
193
|
+
return _extract_schema_field_names(flow_result.get("data_schema"))
|
|
194
|
+
except Exception as e:
|
|
195
|
+
logger.debug(f"Schema introspection failed for {helper_type}: {e}")
|
|
196
|
+
return None
|
|
197
|
+
finally:
|
|
198
|
+
if flow_id:
|
|
199
|
+
try:
|
|
200
|
+
await asyncio.wait_for(client.abort_config_flow(flow_id), timeout=5.0)
|
|
201
|
+
except Exception as abort_err:
|
|
202
|
+
logger.warning(
|
|
203
|
+
f"Failed to abort introspection flow {flow_id}: {abort_err}"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
async def update_config_entry_options(
|
|
208
|
+
client: Any,
|
|
209
|
+
entry_id: str,
|
|
210
|
+
config_dict: dict[str, Any],
|
|
211
|
+
*,
|
|
212
|
+
expected_domain: str | None = None,
|
|
213
|
+
noun: str = "integration",
|
|
214
|
+
) -> dict[str, Any]:
|
|
215
|
+
"""Update an existing config entry via its options flow.
|
|
216
|
+
|
|
217
|
+
When ``expected_domain`` is provided, verifies the entry's domain matches
|
|
218
|
+
it first (the helper path passes the helper_type; the generic
|
|
219
|
+
``ha_set_integration`` path passes ``None`` to accept any domain). Starts
|
|
220
|
+
an options flow, walks the flow steps, and returns the result. Aborts the
|
|
221
|
+
flow on error. ``noun`` only affects response wording.
|
|
222
|
+
"""
|
|
223
|
+
config_entry = await client.get_config_entry(entry_id)
|
|
224
|
+
actual_domain = config_entry.get("domain")
|
|
225
|
+
if expected_domain is not None and actual_domain != expected_domain:
|
|
226
|
+
raise_tool_error(
|
|
227
|
+
create_error_response(
|
|
228
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
229
|
+
f"entry_id '{entry_id}' belongs to domain '{actual_domain}', not '{expected_domain}'",
|
|
230
|
+
suggestions=[
|
|
231
|
+
f"Use ha_get_integration(domain='{expected_domain}') to find valid entry IDs",
|
|
232
|
+
],
|
|
233
|
+
context={
|
|
234
|
+
"entry_id": entry_id,
|
|
235
|
+
"expected": expected_domain,
|
|
236
|
+
"actual": actual_domain,
|
|
237
|
+
},
|
|
238
|
+
)
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
flow_result = await client.start_options_flow(entry_id)
|
|
242
|
+
flow_id = flow_result.get("flow_id")
|
|
243
|
+
|
|
244
|
+
if not flow_id:
|
|
245
|
+
raise_tool_error(
|
|
246
|
+
create_error_response(
|
|
247
|
+
ErrorCode.SERVICE_CALL_FAILED,
|
|
248
|
+
"Failed to start options flow",
|
|
249
|
+
suggestions=[
|
|
250
|
+
"Check that the entry supports options (supports_options=true)"
|
|
251
|
+
],
|
|
252
|
+
context={"entry_id": entry_id, "details": flow_result},
|
|
253
|
+
)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
result = await _handle_flow_steps(
|
|
258
|
+
client,
|
|
259
|
+
flow_id,
|
|
260
|
+
flow_result,
|
|
261
|
+
config_dict,
|
|
262
|
+
submit_fn=client.submit_options_flow_step,
|
|
263
|
+
helper_type=expected_domain,
|
|
264
|
+
)
|
|
265
|
+
except Exception:
|
|
266
|
+
try:
|
|
267
|
+
await asyncio.wait_for(client.abort_options_flow(flow_id), timeout=5.0)
|
|
268
|
+
except Exception as abort_err:
|
|
269
|
+
logger.warning(
|
|
270
|
+
f"Failed to abort options flow {flow_id} after error: {abort_err}"
|
|
271
|
+
)
|
|
272
|
+
raise
|
|
273
|
+
|
|
274
|
+
entry = result["entry"].get("result", {})
|
|
275
|
+
response = {
|
|
276
|
+
"success": True,
|
|
277
|
+
"entry_id": entry_id,
|
|
278
|
+
"title": entry.get("title"),
|
|
279
|
+
"domain": actual_domain,
|
|
280
|
+
"message": f"{actual_domain} {noun} updated successfully",
|
|
281
|
+
"updated": True,
|
|
282
|
+
}
|
|
283
|
+
if result.get("warnings"):
|
|
284
|
+
response["warnings"] = result["warnings"]
|
|
285
|
+
return response
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
async def update_flow_helper(
|
|
289
|
+
client: Any,
|
|
290
|
+
helper_type: str,
|
|
291
|
+
config_dict: dict[str, Any],
|
|
292
|
+
entry_id: str,
|
|
293
|
+
) -> dict[str, Any]:
|
|
294
|
+
"""Update an existing flow-based helper via its options flow.
|
|
295
|
+
|
|
296
|
+
Verifies the entry domain matches helper_type, starts an options flow,
|
|
297
|
+
walks the flow steps, and returns the result. Aborts the flow on error.
|
|
298
|
+
"""
|
|
299
|
+
return await update_config_entry_options(
|
|
300
|
+
client,
|
|
301
|
+
entry_id,
|
|
302
|
+
config_dict,
|
|
303
|
+
expected_domain=helper_type,
|
|
304
|
+
noun="helper",
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
async def create_config_entry(
|
|
309
|
+
client: Any,
|
|
310
|
+
domain: str,
|
|
311
|
+
config_dict: dict[str, Any],
|
|
312
|
+
*,
|
|
313
|
+
noun: str = "integration",
|
|
314
|
+
) -> dict[str, Any]:
|
|
315
|
+
"""Create a config entry by driving ``domain``'s config flow.
|
|
316
|
+
|
|
317
|
+
Starts a config flow, walks the flow steps (menus and multi-step forms),
|
|
318
|
+
and returns the result. Aborts the flow on error. ``noun`` only affects
|
|
319
|
+
response wording.
|
|
320
|
+
"""
|
|
321
|
+
flow_result = await client.start_config_flow(domain)
|
|
322
|
+
flow_id = flow_result.get("flow_id")
|
|
323
|
+
|
|
324
|
+
if not flow_id:
|
|
325
|
+
raise_tool_error(
|
|
326
|
+
create_error_response(
|
|
327
|
+
ErrorCode.SERVICE_CALL_FAILED,
|
|
328
|
+
"Failed to start config flow",
|
|
329
|
+
suggestions=[
|
|
330
|
+
f"Check that the {noun} domain exists and Home Assistant is reachable"
|
|
331
|
+
],
|
|
332
|
+
context={"domain": domain, "details": flow_result},
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
try:
|
|
337
|
+
result = await _handle_flow_steps(
|
|
338
|
+
client,
|
|
339
|
+
flow_id,
|
|
340
|
+
flow_result,
|
|
341
|
+
config_dict,
|
|
342
|
+
helper_type=domain,
|
|
343
|
+
)
|
|
344
|
+
except Exception:
|
|
345
|
+
try:
|
|
346
|
+
await asyncio.wait_for(client.abort_config_flow(flow_id), timeout=5.0)
|
|
347
|
+
except Exception as abort_err:
|
|
348
|
+
logger.warning(
|
|
349
|
+
f"Failed to abort config flow {flow_id} after error: {abort_err}"
|
|
350
|
+
)
|
|
351
|
+
raise
|
|
352
|
+
|
|
353
|
+
entry = result["entry"].get("result", {})
|
|
354
|
+
response = {
|
|
355
|
+
"success": True,
|
|
356
|
+
"entry_id": entry.get("entry_id"),
|
|
357
|
+
"title": entry.get("title"),
|
|
358
|
+
"domain": domain,
|
|
359
|
+
"message": f"{domain} {noun} created successfully",
|
|
360
|
+
}
|
|
361
|
+
if result.get("warnings"):
|
|
362
|
+
response["warnings"] = result["warnings"]
|
|
363
|
+
return response
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
async def create_flow_helper(
|
|
367
|
+
client: Any,
|
|
368
|
+
helper_type: str,
|
|
369
|
+
config_dict: dict[str, Any],
|
|
370
|
+
) -> dict[str, Any]:
|
|
371
|
+
"""Create a new flow-based helper via the config flow.
|
|
372
|
+
|
|
373
|
+
Starts a config flow, walks the flow steps, and returns the result.
|
|
374
|
+
Aborts the flow on error.
|
|
375
|
+
"""
|
|
376
|
+
return await create_config_entry(client, helper_type, config_dict, noun="helper")
|