ha-mcp-dev 7.14.2.dev2091__tar.gz → 7.14.2.dev2095__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.dev2091/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.14.2.dev2095}/PKG-INFO +1 -1
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/pyproject.toml +1 -1
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/dashboard_screenshot/paths.py +66 -40
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/config_entry_flow.py +156 -48
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_bug_report.py +36 -4
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_config_dashboards.py +165 -36
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/setup.cfg +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/locales/de.json +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/locales/en.json +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/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.dev2095"
|
|
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-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/dashboard_screenshot/paths.py
RENAMED
|
@@ -347,42 +347,28 @@ async def fetch_dashboard_render_config(
|
|
|
347
347
|
return cast(dict[str, Any], config)
|
|
348
348
|
|
|
349
349
|
|
|
350
|
-
def
|
|
350
|
+
def match_dashboard_view(
|
|
351
351
|
dashboard_url_path: str,
|
|
352
352
|
config: dict[str, Any],
|
|
353
|
-
view_path: str
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
"No view_path was supplied and the dashboard has no static views; "
|
|
373
|
-
"only the base route is available."
|
|
374
|
-
)
|
|
375
|
-
return DashboardRenderTarget(
|
|
376
|
-
dashboard_url_path=base_path,
|
|
377
|
-
view_path=None,
|
|
378
|
-
render_path=base_path,
|
|
379
|
-
view_index=None,
|
|
380
|
-
stable=False,
|
|
381
|
-
warnings=(warning,),
|
|
382
|
-
)
|
|
383
|
-
|
|
384
|
-
cleaned_view_path = view_path
|
|
385
|
-
if not cleaned_view_path.strip():
|
|
353
|
+
view_path: str,
|
|
354
|
+
*,
|
|
355
|
+
strategy_suggestions: tuple[str, ...] = (
|
|
356
|
+
"Use dashboard_path with the frontend route instead",
|
|
357
|
+
),
|
|
358
|
+
) -> tuple[int, dict[str, Any]]:
|
|
359
|
+
"""Match one named Lovelace view by its configured ``views[].path``.
|
|
360
|
+
|
|
361
|
+
Shared by screenshot view resolution and get-mode view scoping so both
|
|
362
|
+
honour identical semantics: exact match on the configured path, with
|
|
363
|
+
structured errors for an empty path, a strategy dashboard (no static
|
|
364
|
+
views to match), and a missing/ambiguous path (the not-found error lists
|
|
365
|
+
the available paths). Returns ``(view_index, view)``.
|
|
366
|
+
|
|
367
|
+
``strategy_suggestions`` lets each caller phrase the strategy-dashboard
|
|
368
|
+
remediation in its own vocabulary (the screenshot tools have a
|
|
369
|
+
``dashboard_path`` route parameter; config scoping does not).
|
|
370
|
+
"""
|
|
371
|
+
if not view_path.strip():
|
|
386
372
|
raise_tool_error(
|
|
387
373
|
create_error_response(
|
|
388
374
|
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
@@ -397,9 +383,9 @@ def resolve_dashboard_view(
|
|
|
397
383
|
"Strategy dashboards do not expose static named view paths.",
|
|
398
384
|
context={
|
|
399
385
|
"dashboard_url_path": dashboard_url_path,
|
|
400
|
-
"view_path":
|
|
386
|
+
"view_path": view_path,
|
|
401
387
|
},
|
|
402
|
-
suggestions=
|
|
388
|
+
suggestions=list(strategy_suggestions),
|
|
403
389
|
)
|
|
404
390
|
)
|
|
405
391
|
views = config.get("views")
|
|
@@ -408,7 +394,7 @@ def resolve_dashboard_view(
|
|
|
408
394
|
matches = [
|
|
409
395
|
(index, view)
|
|
410
396
|
for index, view in enumerate(views)
|
|
411
|
-
if _configured_view_path(view) ==
|
|
397
|
+
if _configured_view_path(view) == view_path
|
|
412
398
|
]
|
|
413
399
|
if len(matches) != 1:
|
|
414
400
|
available = [
|
|
@@ -420,17 +406,57 @@ def resolve_dashboard_view(
|
|
|
420
406
|
raise_tool_error(
|
|
421
407
|
create_error_response(
|
|
422
408
|
ErrorCode.RESOURCE_NOT_FOUND,
|
|
423
|
-
f"Dashboard view path '{
|
|
409
|
+
f"Dashboard view path '{view_path}' is {reason}.",
|
|
424
410
|
context={
|
|
425
411
|
"dashboard_url_path": dashboard_url_path,
|
|
426
|
-
"view_path":
|
|
412
|
+
"view_path": view_path,
|
|
427
413
|
"available_view_paths": available,
|
|
428
414
|
},
|
|
429
415
|
suggestions=["Use a view_path returned by ha_config_get_dashboard"],
|
|
430
416
|
)
|
|
431
417
|
)
|
|
418
|
+
return matches[0]
|
|
419
|
+
|
|
432
420
|
|
|
433
|
-
|
|
421
|
+
def resolve_dashboard_view(
|
|
422
|
+
dashboard_url_path: str,
|
|
423
|
+
config: dict[str, Any],
|
|
424
|
+
view_path: str | None,
|
|
425
|
+
) -> DashboardRenderTarget:
|
|
426
|
+
"""Resolve one named Lovelace view to a canonical frontend route."""
|
|
427
|
+
base_path = dashboard_frontend_path(dashboard_url_path)
|
|
428
|
+
if view_path is None:
|
|
429
|
+
views = config.get("views")
|
|
430
|
+
has_static_views = isinstance(views, list) and bool(views)
|
|
431
|
+
if "strategy" in config:
|
|
432
|
+
warning = (
|
|
433
|
+
"No view_path was supplied; this strategy dashboard generates "
|
|
434
|
+
"its views at runtime, so only the base route is available."
|
|
435
|
+
)
|
|
436
|
+
elif has_static_views:
|
|
437
|
+
warning = (
|
|
438
|
+
"No view_path was supplied; the dashboard base route renders "
|
|
439
|
+
"the first view visible to Puppet's Home Assistant user."
|
|
440
|
+
)
|
|
441
|
+
else:
|
|
442
|
+
warning = (
|
|
443
|
+
"No view_path was supplied and the dashboard has no static views; "
|
|
444
|
+
"only the base route is available."
|
|
445
|
+
)
|
|
446
|
+
return DashboardRenderTarget(
|
|
447
|
+
dashboard_url_path=base_path,
|
|
448
|
+
view_path=None,
|
|
449
|
+
render_path=base_path,
|
|
450
|
+
view_index=None,
|
|
451
|
+
stable=False,
|
|
452
|
+
warnings=(warning,),
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
view_index, _ = match_dashboard_view(dashboard_url_path, config, view_path)
|
|
456
|
+
cleaned_view_path = view_path
|
|
457
|
+
views = config.get("views")
|
|
458
|
+
if not isinstance(views, list):
|
|
459
|
+
views = []
|
|
434
460
|
render_path = _safe_named_render_path(base_path, cleaned_view_path)
|
|
435
461
|
if render_path is None:
|
|
436
462
|
return _fallback_view_target(
|
{ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/config_entry_flow.py
RENAMED
|
@@ -11,6 +11,7 @@ for the 15 helper types listed in FLOW_HELPER_TYPES.
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
import asyncio
|
|
14
|
+
import copy
|
|
14
15
|
import logging
|
|
15
16
|
from collections.abc import Iterator
|
|
16
17
|
from enum import StrEnum
|
|
@@ -78,6 +79,7 @@ _RECONFIGURE_SUCCESS_REASONS = frozenset(
|
|
|
78
79
|
"reconfigure_successful",
|
|
79
80
|
}
|
|
80
81
|
)
|
|
82
|
+
_MISSING_DEFAULT = object()
|
|
81
83
|
|
|
82
84
|
|
|
83
85
|
class _FlowType(StrEnum):
|
|
@@ -172,6 +174,47 @@ def _record_ignored_section_keys(
|
|
|
172
174
|
)
|
|
173
175
|
|
|
174
176
|
|
|
177
|
+
def _field_default_value(field: dict[str, Any]) -> Any:
|
|
178
|
+
"""Return a serialized schema field's default/suggested value, if present."""
|
|
179
|
+
description = field.get("description")
|
|
180
|
+
if isinstance(description, dict) and description.get("suggested_value") is not None:
|
|
181
|
+
return copy.deepcopy(description["suggested_value"])
|
|
182
|
+
if field.get("suggested_value") is not None:
|
|
183
|
+
return copy.deepcopy(field["suggested_value"])
|
|
184
|
+
if "default" in field:
|
|
185
|
+
return copy.deepcopy(field["default"])
|
|
186
|
+
return _MISSING_DEFAULT
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _schema_default_values(data_schema: list[Any]) -> dict[str, Any]:
|
|
190
|
+
"""Build default form data from serialized schema suggestions/defaults."""
|
|
191
|
+
defaults: dict[str, Any] = {}
|
|
192
|
+
for field in data_schema:
|
|
193
|
+
if not isinstance(field, dict):
|
|
194
|
+
continue
|
|
195
|
+
name = field.get("name")
|
|
196
|
+
nested_schema = field.get("schema")
|
|
197
|
+
if not isinstance(name, str):
|
|
198
|
+
continue
|
|
199
|
+
if isinstance(nested_schema, list):
|
|
200
|
+
nested_defaults = _schema_default_values(nested_schema)
|
|
201
|
+
if nested_defaults:
|
|
202
|
+
defaults[name] = nested_defaults
|
|
203
|
+
continue
|
|
204
|
+
default = _field_default_value(field)
|
|
205
|
+
if default is not _MISSING_DEFAULT:
|
|
206
|
+
defaults[name] = default
|
|
207
|
+
return defaults
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _required_section_defaults(field: dict[str, Any]) -> dict[str, Any]:
|
|
211
|
+
"""Return default data for a required section, otherwise an empty dict."""
|
|
212
|
+
nested_schema = field.get("schema")
|
|
213
|
+
if not field.get("required") or not isinstance(nested_schema, list):
|
|
214
|
+
return {}
|
|
215
|
+
return _schema_default_values(nested_schema)
|
|
216
|
+
|
|
217
|
+
|
|
175
218
|
def _ignored_keys_warnings(
|
|
176
219
|
ignored_config_keys: set[str], remaining_config: dict[str, Any]
|
|
177
220
|
) -> list[str]:
|
|
@@ -194,10 +237,89 @@ def _ignored_keys_warnings(
|
|
|
194
237
|
return warnings
|
|
195
238
|
|
|
196
239
|
|
|
240
|
+
def _consume_section_schema(
|
|
241
|
+
field: dict[str, Any],
|
|
242
|
+
explicit_section: dict[str, Any] | None,
|
|
243
|
+
remaining_config: dict[str, Any],
|
|
244
|
+
ignored_config_keys: set[str] | None,
|
|
245
|
+
consumed_config_keys: set[str] | None,
|
|
246
|
+
path_prefix: str,
|
|
247
|
+
) -> dict[str, Any]:
|
|
248
|
+
"""Consume config values for a nested flow section."""
|
|
249
|
+
nested_schema = field.get("schema")
|
|
250
|
+
if not isinstance(nested_schema, list):
|
|
251
|
+
return {}
|
|
252
|
+
|
|
253
|
+
name = field.get("name")
|
|
254
|
+
section_path = _section_path(path_prefix, name)
|
|
255
|
+
nested_data = _required_section_defaults(field)
|
|
256
|
+
|
|
257
|
+
if explicit_section is not None:
|
|
258
|
+
explicit_remaining = dict(explicit_section)
|
|
259
|
+
nested_data.update(
|
|
260
|
+
_consume_form_schema(
|
|
261
|
+
nested_schema,
|
|
262
|
+
explicit_remaining,
|
|
263
|
+
ignored_config_keys,
|
|
264
|
+
consumed_config_keys,
|
|
265
|
+
section_path,
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
_record_ignored_section_keys(
|
|
269
|
+
ignored_config_keys,
|
|
270
|
+
explicit_remaining,
|
|
271
|
+
section_path,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
nested_data.update(
|
|
275
|
+
_consume_form_schema(
|
|
276
|
+
nested_schema,
|
|
277
|
+
remaining_config,
|
|
278
|
+
ignored_config_keys,
|
|
279
|
+
consumed_config_keys,
|
|
280
|
+
section_path,
|
|
281
|
+
)
|
|
282
|
+
)
|
|
283
|
+
return nested_data
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _mark_consumed(
|
|
287
|
+
consumed_config_keys: set[str] | None,
|
|
288
|
+
path_prefix: str,
|
|
289
|
+
name: str,
|
|
290
|
+
) -> None:
|
|
291
|
+
"""Record that a caller-supplied form key, not an injected default, was used."""
|
|
292
|
+
if consumed_config_keys is not None:
|
|
293
|
+
consumed_config_keys.add(_section_path(path_prefix, name))
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _auto_confirm_form_payload(current_step: dict[str, Any]) -> dict[str, Any] | None:
|
|
297
|
+
"""Return payload for HA preview/confirmation-only forms we can safely advance."""
|
|
298
|
+
if "preview" not in current_step:
|
|
299
|
+
return None
|
|
300
|
+
data_schema = current_step.get("data_schema")
|
|
301
|
+
if not isinstance(data_schema, list) or len(data_schema) != 1:
|
|
302
|
+
return None
|
|
303
|
+
field = data_schema[0]
|
|
304
|
+
if not isinstance(field, dict) or not field.get("required"):
|
|
305
|
+
return None
|
|
306
|
+
name = field.get("name")
|
|
307
|
+
if not isinstance(name, str) or name in _MENU_SELECTION_KEYS:
|
|
308
|
+
return None
|
|
309
|
+
default = _field_default_value(field)
|
|
310
|
+
if default is not False:
|
|
311
|
+
return None
|
|
312
|
+
selector = field.get("selector")
|
|
313
|
+
if isinstance(selector, dict) and selector and "boolean" not in selector:
|
|
314
|
+
return None
|
|
315
|
+
return {name: True}
|
|
316
|
+
|
|
317
|
+
|
|
197
318
|
def _consume_form_schema(
|
|
198
319
|
data_schema: list[Any],
|
|
199
320
|
remaining_config: dict[str, Any],
|
|
200
321
|
ignored_config_keys: set[str] | None = None,
|
|
322
|
+
consumed_config_keys: set[str] | None = None,
|
|
201
323
|
path_prefix: str = "",
|
|
202
324
|
) -> dict[str, Any]:
|
|
203
325
|
"""Consume matching config values and shape nested flow sections.
|
|
@@ -208,7 +330,6 @@ def _consume_form_schema(
|
|
|
208
330
|
``ignored_config_keys`` with their dotted section path.
|
|
209
331
|
"""
|
|
210
332
|
form_data: dict[str, Any] = {}
|
|
211
|
-
missing = object()
|
|
212
333
|
|
|
213
334
|
for field in data_schema:
|
|
214
335
|
if not isinstance(field, dict):
|
|
@@ -217,47 +338,27 @@ def _consume_form_schema(
|
|
|
217
338
|
name = field.get("name")
|
|
218
339
|
nested_schema = field.get("schema")
|
|
219
340
|
if isinstance(nested_schema, list):
|
|
220
|
-
|
|
221
|
-
explicit_section =
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
_consume_form_schema(
|
|
238
|
-
nested_schema,
|
|
239
|
-
explicit_remaining,
|
|
240
|
-
ignored_config_keys,
|
|
241
|
-
section_path,
|
|
242
|
-
)
|
|
243
|
-
)
|
|
244
|
-
_record_ignored_section_keys(
|
|
245
|
-
ignored_config_keys,
|
|
246
|
-
explicit_remaining,
|
|
247
|
-
section_path,
|
|
248
|
-
)
|
|
249
|
-
nested_data.update(
|
|
250
|
-
_consume_form_schema(
|
|
251
|
-
nested_schema,
|
|
252
|
-
remaining_config,
|
|
253
|
-
ignored_config_keys,
|
|
254
|
-
section_path,
|
|
255
|
-
)
|
|
341
|
+
section_name = name if isinstance(name, str) else None
|
|
342
|
+
explicit_section = None
|
|
343
|
+
if section_name is not None and section_name in remaining_config:
|
|
344
|
+
explicit_value = remaining_config.pop(section_name)
|
|
345
|
+
if not isinstance(explicit_value, dict):
|
|
346
|
+
form_data[section_name] = explicit_value
|
|
347
|
+
_mark_consumed(consumed_config_keys, path_prefix, section_name)
|
|
348
|
+
continue
|
|
349
|
+
explicit_section = explicit_value
|
|
350
|
+
|
|
351
|
+
nested_data = _consume_section_schema(
|
|
352
|
+
field,
|
|
353
|
+
explicit_section,
|
|
354
|
+
remaining_config,
|
|
355
|
+
ignored_config_keys,
|
|
356
|
+
consumed_config_keys,
|
|
357
|
+
path_prefix,
|
|
256
358
|
)
|
|
257
|
-
|
|
258
359
|
if nested_data:
|
|
259
|
-
if
|
|
260
|
-
form_data[
|
|
360
|
+
if section_name is not None:
|
|
361
|
+
form_data[section_name] = nested_data
|
|
261
362
|
else:
|
|
262
363
|
form_data.update(nested_data)
|
|
263
364
|
continue
|
|
@@ -268,6 +369,7 @@ def _consume_form_schema(
|
|
|
268
369
|
and name in remaining_config
|
|
269
370
|
):
|
|
270
371
|
form_data[name] = remaining_config.pop(name)
|
|
372
|
+
_mark_consumed(consumed_config_keys, path_prefix, name)
|
|
271
373
|
|
|
272
374
|
return form_data
|
|
273
375
|
|
|
@@ -296,6 +398,7 @@ def _handle_form_step(
|
|
|
296
398
|
current_step: dict[str, Any],
|
|
297
399
|
remaining_config: dict[str, Any],
|
|
298
400
|
ignored_config_keys: set[str] | None = None,
|
|
401
|
+
consumed_config_keys: set[str] | None = None,
|
|
299
402
|
) -> dict[str, Any]:
|
|
300
403
|
"""Validate a form step and return form data to submit.
|
|
301
404
|
|
|
@@ -337,9 +440,10 @@ def _handle_form_step(
|
|
|
337
440
|
if key in _MENU_SELECTION_KEYS:
|
|
338
441
|
continue
|
|
339
442
|
form_data[key] = remaining_config.pop(key)
|
|
443
|
+
_mark_consumed(consumed_config_keys, "", key)
|
|
340
444
|
else:
|
|
341
445
|
form_data = _consume_form_schema(
|
|
342
|
-
data_schema, remaining_config, ignored_config_keys
|
|
446
|
+
data_schema, remaining_config, ignored_config_keys, consumed_config_keys
|
|
343
447
|
)
|
|
344
448
|
|
|
345
449
|
return form_data
|
|
@@ -783,13 +887,17 @@ async def _handle_flow_steps(
|
|
|
783
887
|
# for subsequent steps (HA can present multi-step forms, e.g.
|
|
784
888
|
# statistics: user step then pick-characteristic step).
|
|
785
889
|
saw_form_step = True
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
890
|
+
consumed_form_keys: set[str] = set()
|
|
891
|
+
form_data = _auto_confirm_form_payload(current_step)
|
|
892
|
+
if form_data is None:
|
|
893
|
+
form_data = _handle_form_step(
|
|
894
|
+
flow_id,
|
|
895
|
+
current_step,
|
|
896
|
+
remaining_config,
|
|
897
|
+
ignored_config_keys,
|
|
898
|
+
consumed_form_keys,
|
|
899
|
+
)
|
|
900
|
+
if consumed_form_keys:
|
|
793
901
|
any_form_key_consumed = True
|
|
794
902
|
logger.debug(
|
|
795
903
|
f"Flow step {step_num}: form submit "
|
{ha_mcp_dev-7.14.2.dev2091 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_bug_report.py
RENAMED
|
@@ -34,7 +34,7 @@ from ..utils.usage_logger import (
|
|
|
34
34
|
)
|
|
35
35
|
from .component_api import get_component_caps
|
|
36
36
|
from .helpers import log_tool_usage, register_tool_methods
|
|
37
|
-
from .util_helpers import ANSI_ESCAPE_RE
|
|
37
|
+
from .util_helpers import ANSI_ESCAPE_RE, JSON_STRING_COERCION, project_fields
|
|
38
38
|
|
|
39
39
|
logger = logging.getLogger(__name__)
|
|
40
40
|
|
|
@@ -676,10 +676,38 @@ class BugReportTools:
|
|
|
676
676
|
),
|
|
677
677
|
),
|
|
678
678
|
] = 10,
|
|
679
|
+
fields: Annotated[
|
|
680
|
+
str | list[str] | None,
|
|
681
|
+
JSON_STRING_COERCION,
|
|
682
|
+
Field(
|
|
683
|
+
default=None,
|
|
684
|
+
description=(
|
|
685
|
+
"Return only the specified top-level response keys — the "
|
|
686
|
+
"full response (both templates + logs + diagnostics, with "
|
|
687
|
+
"log content repeated across the raw keys and templates) "
|
|
688
|
+
"is very large. "
|
|
689
|
+
"None = full response. Typical for a runtime bug: "
|
|
690
|
+
"'runtime_bug_template,suggested_title,"
|
|
691
|
+
"runtime_bug_submit_url,duplicate_check_urls,"
|
|
692
|
+
"anonymization_guide,instructions'; for agent feedback "
|
|
693
|
+
"swap in agent_behavior_template and "
|
|
694
|
+
"agent_behavior_submit_url. The templates already embed "
|
|
695
|
+
"the relevant logs, so the raw log keys are only needed "
|
|
696
|
+
"for your own analysis. "
|
|
697
|
+
"Available keys: diagnostic_info, recent_logs, "
|
|
698
|
+
"startup_logs, addon_logs, core_error_log, log_count, "
|
|
699
|
+
"startup_log_count, formatted_report, "
|
|
700
|
+
"runtime_bug_template, agent_behavior_template, "
|
|
701
|
+
"anonymization_guide, suggested_title, "
|
|
702
|
+
"runtime_bug_submit_url, agent_behavior_submit_url, "
|
|
703
|
+
"duplicate_check_urls, missing_tool_hint, instructions."
|
|
704
|
+
),
|
|
705
|
+
),
|
|
706
|
+
] = None,
|
|
679
707
|
ctx: Context | None = None,
|
|
680
708
|
) -> dict[str, Any]:
|
|
681
709
|
"""
|
|
682
|
-
|
|
710
|
+
Get diagnostic information and templates for filing issue reports or feedback.
|
|
683
711
|
|
|
684
712
|
This tool generates templates for TWO types of reports:
|
|
685
713
|
1. **Runtime Bug Report** - For ha-mcp errors, failures, unexpected behavior
|
|
@@ -709,7 +737,10 @@ class BugReportTools:
|
|
|
709
737
|
- "That was inefficient"
|
|
710
738
|
|
|
711
739
|
**OUTPUT:**
|
|
712
|
-
Returns both templates plus diagnostic data.
|
|
740
|
+
Returns both templates plus diagnostic data. The full response is
|
|
741
|
+
LARGE (the captured logs appear in the raw log keys AND inside each
|
|
742
|
+
template) — pass fields=... to fetch only the keys you need once you
|
|
743
|
+
know which template applies. Key fields:
|
|
713
744
|
- `runtime_bug_template`, `agent_behavior_template` — pick based on context
|
|
714
745
|
- `recent_logs`, `startup_logs` — captured ha-mcp tool/server log entries
|
|
715
746
|
- `addon_logs` — addon container stdout/stderr (HA add-on installs only;
|
|
@@ -843,7 +874,7 @@ class BugReportTools:
|
|
|
843
874
|
for keyword in search_keywords[:3] # Limit to top 3 keywords
|
|
844
875
|
]
|
|
845
876
|
|
|
846
|
-
|
|
877
|
+
result: dict[str, Any] = {
|
|
847
878
|
"success": True,
|
|
848
879
|
"diagnostic_info": diagnostic_info,
|
|
849
880
|
"recent_logs": recent_logs,
|
|
@@ -934,6 +965,7 @@ class BugReportTools:
|
|
|
934
965
|
"CRITICAL: Always ANONYMIZE the report BEFORE presenting it in markdown code blocks!"
|
|
935
966
|
),
|
|
936
967
|
}
|
|
968
|
+
return project_fields(result, fields)
|
|
937
969
|
|
|
938
970
|
|
|
939
971
|
def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|