ha-mcp-dev 8.0.0.dev2228__tar.gz → 8.0.0.dev2230__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.dev2230}/PKG-INFO +1 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/pyproject.toml +1 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/locales/de.json +1 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/locales/es.json +1 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/locales/fr.json +1 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/locales/it.json +1 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/locales/ru.json +1 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/locales/zh-Hans.json +1 -1
- ha_mcp_dev-8.0.0.dev2230/src/ha_mcp/tools/config_entry_flow.py +376 -0
- ha_mcp_dev-8.0.0.dev2230/src/ha_mcp/tools/config_entry_flow_form.py +694 -0
- ha_mcp_dev-8.0.0.dev2230/src/ha_mcp/tools/config_entry_flow_menu.py +185 -0
- ha_mcp_dev-8.0.0.dev2230/src/ha_mcp/tools/config_entry_flow_walker.py +728 -0
- ha_mcp_dev-8.0.0.dev2230/src/ha_mcp/tools/hacs_registration.py +393 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_config_helpers.py +24 -13
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_dev.py +17 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_hacs.py +214 -392
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_integrations.py +5 -2
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/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.dev2230}/LICENSE +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/MANIFEST.in +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/README.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/setup.cfg +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/locales/en.json +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/best_practice_result.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/best_practice_variables.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/utils/config_write_lock.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/visibility/enforcement.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/tests/__init__.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/tests/test_constants.py +0 -0
- {ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/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.dev2230"
|
|
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
|
{ha_mcp_dev-8.0.0.dev2228 → ha_mcp_dev-8.0.0.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/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.dev2230}/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")
|