ha-mcp-dev 7.14.2.dev2100__tar.gz → 7.14.2.dev2105__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.dev2100/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.14.2.dev2105}/PKG-INFO +1 -1
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/pyproject.toml +1 -1
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/backup_manager.py +26 -7
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/client/rest_client.py +180 -28
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/config.py +56 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/locales/de.json +2 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/locales/en.json +2 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/locales/ru.json +2 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/settings.js +22 -11
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_config_scenes.py +97 -13
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_filesystem.py +142 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_yaml_config.py +11 -1
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/setup.cfg +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/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.dev2105"
|
|
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
|
|
@@ -2128,8 +2128,19 @@ async def _restore_yaml(client: Any, entity_id: str, config: Any) -> Any:
|
|
|
2128
2128
|
``edit_yaml_config`` is the only write path that reaches HA config
|
|
2129
2129
|
files (``write_file`` rejects them), so YAML restore goes through it
|
|
2130
2130
|
with ``action="replace"``.
|
|
2131
|
+
|
|
2132
|
+
The operator's extra write keys (#1887) must ride along: the write that
|
|
2133
|
+
produced this snapshot was allowed only because of them, so omitting
|
|
2134
|
+
them here would make an auto-captured backup unrestorable. Note the
|
|
2135
|
+
asymmetry with ``disabled_packages_keys``, whose default is permissive
|
|
2136
|
+
and can therefore be left off.
|
|
2131
2137
|
"""
|
|
2132
|
-
from .
|
|
2138
|
+
from .config import get_global_settings
|
|
2139
|
+
from .tools.tools_filesystem import (
|
|
2140
|
+
assert_extra_yaml_keys_supported,
|
|
2141
|
+
call_mcp_tools_service,
|
|
2142
|
+
effective_extra_yaml_write_keys,
|
|
2143
|
+
)
|
|
2133
2144
|
from .tools.util_helpers import unwrap_service_response
|
|
2134
2145
|
|
|
2135
2146
|
# Split on the LAST "::" (see _fetch_yaml) so an exotic file path
|
|
@@ -2137,15 +2148,23 @@ async def _restore_yaml(client: Any, entity_id: str, config: Any) -> Any:
|
|
|
2137
2148
|
file, sep, yaml_path = entity_id.rpartition("::")
|
|
2138
2149
|
if not sep or not file or not yaml_path:
|
|
2139
2150
|
raise ValueError(f"Invalid yaml snapshot target: {entity_id!r}")
|
|
2151
|
+
service_data: dict[str, Any] = {
|
|
2152
|
+
"file": file,
|
|
2153
|
+
"action": "replace",
|
|
2154
|
+
"yaml_path": yaml_path,
|
|
2155
|
+
"content": str(config),
|
|
2156
|
+
}
|
|
2157
|
+
extra_keys = await effective_extra_yaml_write_keys(client, get_global_settings())
|
|
2158
|
+
if extra_keys:
|
|
2159
|
+
# Same version gate as the write path: without it a component that
|
|
2160
|
+
# predates the field rejects the whole restore call over an option
|
|
2161
|
+
# the snapshot being restored may not even use.
|
|
2162
|
+
await assert_extra_yaml_keys_supported(client, extra_keys)
|
|
2163
|
+
service_data["extra_allowed_keys"] = extra_keys
|
|
2140
2164
|
result = await call_mcp_tools_service(
|
|
2141
2165
|
client,
|
|
2142
2166
|
"edit_yaml_config",
|
|
2143
|
-
|
|
2144
|
-
"file": file,
|
|
2145
|
-
"action": "replace",
|
|
2146
|
-
"yaml_path": yaml_path,
|
|
2147
|
-
"content": str(config),
|
|
2148
|
-
},
|
|
2167
|
+
service_data,
|
|
2149
2168
|
)
|
|
2150
2169
|
if isinstance(result, dict):
|
|
2151
2170
|
result = unwrap_service_response(result)
|
|
@@ -8,7 +8,8 @@ import logging
|
|
|
8
8
|
import os
|
|
9
9
|
import ssl
|
|
10
10
|
import time
|
|
11
|
-
from
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any, NoReturn
|
|
12
13
|
|
|
13
14
|
import httpx
|
|
14
15
|
from websockets.exceptions import WebSocketException
|
|
@@ -97,6 +98,54 @@ class HomeAssistantAPIError(HomeAssistantError):
|
|
|
97
98
|
self.response_data = response_data
|
|
98
99
|
|
|
99
100
|
|
|
101
|
+
@dataclass(frozen=True)
|
|
102
|
+
class SceneResolution:
|
|
103
|
+
"""Outcome of resolving a scene identifier via the entity registry.
|
|
104
|
+
|
|
105
|
+
``registry_hit`` records whether ``config/entity_registry/get`` returned a
|
|
106
|
+
usable ``unique_id`` - i.e. the entity actually EXISTS. It is the signal that
|
|
107
|
+
separates a genuinely-missing scene from one that exists but is not editable
|
|
108
|
+
through HA's scene config API (a Hue/vendor scene, or a raw-YAML scene): both
|
|
109
|
+
resolve fine yet 404 on ``config/scene/config/{id}``, because that endpoint is
|
|
110
|
+
backed only by the managed ``scenes.yaml`` (issue #1971). ``platform`` carries
|
|
111
|
+
the registry entry's integration domain (``"hue"``, ``"homeassistant"``, …)
|
|
112
|
+
for error-message enrichment only - it never drives the classification, since
|
|
113
|
+
a raw-YAML scene is ``platform="homeassistant"`` yet still 404s.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
storage_key: str
|
|
117
|
+
registry_hit: bool
|
|
118
|
+
platform: str | None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class SceneStorageConfigNotFoundError(HomeAssistantAPIError):
|
|
122
|
+
"""A scene entity resolved in the registry, but ``config/scene/config/{id}``
|
|
123
|
+
(backed by ``scenes.yaml``) has no matching entry.
|
|
124
|
+
|
|
125
|
+
The entity EXISTS; it is simply not an editable Home Assistant storage scene
|
|
126
|
+
(Hue/vendor scene, or a raw-YAML scene). Raised instead of a bare 404 so the
|
|
127
|
+
tools layer can surface ``CONFIG_NOT_FOUND`` rather than the misleading
|
|
128
|
+
``ENTITY_NOT_FOUND``/``RESOURCE_NOT_FOUND`` (issue #1971). Subclasses
|
|
129
|
+
``HomeAssistantAPIError`` so existing ``except HomeAssistantAPIError`` sites
|
|
130
|
+
keep treating it as the 404 it is.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
def __init__(
|
|
134
|
+
self,
|
|
135
|
+
scene_id: str,
|
|
136
|
+
*,
|
|
137
|
+
platform: str | None = None,
|
|
138
|
+
storage_key: str | None = None,
|
|
139
|
+
):
|
|
140
|
+
self.scene_id = scene_id
|
|
141
|
+
self.platform = platform
|
|
142
|
+
self.storage_key = storage_key
|
|
143
|
+
msg = f"Scene not found in editable storage: {scene_id}"
|
|
144
|
+
if storage_key and storage_key != scene_id:
|
|
145
|
+
msg += f" (resolved storage key: {storage_key})"
|
|
146
|
+
super().__init__(msg, status_code=404)
|
|
147
|
+
|
|
148
|
+
|
|
100
149
|
class HomeAssistantCommandError(HomeAssistantError):
|
|
101
150
|
"""WebSocket command returned success=False.
|
|
102
151
|
|
|
@@ -1773,20 +1822,23 @@ class HomeAssistantClient:
|
|
|
1773
1822
|
) from e
|
|
1774
1823
|
raise
|
|
1775
1824
|
|
|
1776
|
-
async def
|
|
1777
|
-
"""
|
|
1778
|
-
Resolve a scene identifier to its storage key via the entity registry.
|
|
1825
|
+
async def _resolve_scene(self, identifier: str) -> SceneResolution:
|
|
1826
|
+
"""Resolve a scene identifier to its storage key AND registry metadata.
|
|
1779
1827
|
|
|
1780
|
-
|
|
1781
|
-
the
|
|
1782
|
-
|
|
1783
|
-
|
|
1828
|
+
Backs :meth:`resolve_scene_id`. Returns the storage key together with
|
|
1829
|
+
``registry_hit`` (did the entity actually resolve?) and ``platform`` - the
|
|
1830
|
+
two signals a bare storage key throws away. Callers that hit a config-API
|
|
1831
|
+
404 need ``registry_hit`` to tell "entity missing" from "entity exists but
|
|
1832
|
+
has no editable storage config", and ``platform`` to name the owning
|
|
1833
|
+
integration in the resulting error (issue #1971).
|
|
1784
1834
|
|
|
1785
1835
|
Args:
|
|
1786
1836
|
identifier: Scene ID (with or without ``scene.`` prefix)
|
|
1787
1837
|
|
|
1788
1838
|
Returns:
|
|
1789
|
-
|
|
1839
|
+
A :class:`SceneResolution`. On any lookup failure it falls back to the
|
|
1840
|
+
bare id with ``registry_hit=False`` - a write path must not treat the
|
|
1841
|
+
guessed bare id as a confirmed resolve.
|
|
1790
1842
|
"""
|
|
1791
1843
|
bare_id = identifier.removeprefix("scene.")
|
|
1792
1844
|
entity_id = f"scene.{bare_id}"
|
|
@@ -1795,13 +1847,18 @@ class HomeAssistantClient:
|
|
|
1795
1847
|
{"type": "config/entity_registry/get", "entity_id": entity_id}
|
|
1796
1848
|
)
|
|
1797
1849
|
if result.get("success") is not False:
|
|
1798
|
-
|
|
1850
|
+
entry = result.get("result") or {}
|
|
1851
|
+
unique_id = entry.get("unique_id")
|
|
1799
1852
|
if unique_id:
|
|
1800
1853
|
if unique_id != bare_id:
|
|
1801
1854
|
logger.debug(
|
|
1802
1855
|
f"Resolved scene entity_id {entity_id} to storage key {unique_id}"
|
|
1803
1856
|
)
|
|
1804
|
-
return
|
|
1857
|
+
return SceneResolution(
|
|
1858
|
+
storage_key=str(unique_id),
|
|
1859
|
+
registry_hit=True,
|
|
1860
|
+
platform=entry.get("platform"),
|
|
1861
|
+
)
|
|
1805
1862
|
except HomeAssistantConnectionError:
|
|
1806
1863
|
# A dead transport must not become a silent guess: the fallback
|
|
1807
1864
|
# below assumes the storage key equals the bare entity_id, which is
|
|
@@ -1814,19 +1871,111 @@ class HomeAssistantClient:
|
|
|
1814
1871
|
f"Entity registry lookup failed for {entity_id}, using bare id: {bare_id}",
|
|
1815
1872
|
exc_info=True,
|
|
1816
1873
|
)
|
|
1817
|
-
return bare_id
|
|
1874
|
+
return SceneResolution(storage_key=bare_id, registry_hit=False, platform=None)
|
|
1875
|
+
|
|
1876
|
+
async def resolve_scene_id(self, identifier: str) -> str:
|
|
1877
|
+
"""
|
|
1878
|
+
Resolve a scene identifier to its storage key via the entity registry.
|
|
1879
|
+
|
|
1880
|
+
Scenes may be renamed in the HA UI, changing the entity_id but keeping
|
|
1881
|
+
the original storage key. Like :meth:`_resolve_script_id`, this returns
|
|
1882
|
+
the bare storage key and needs a WebSocket entity registry lookup because
|
|
1883
|
+
the state attributes do not surface it; the parity ends there, since the
|
|
1884
|
+
underlying :meth:`_resolve_scene` additionally carries registry metadata
|
|
1885
|
+
(``registry_hit``/``platform``) that the script resolver has no analogue for.
|
|
1886
|
+
|
|
1887
|
+
Thin wrapper over :meth:`_resolve_scene` for callers that only need the
|
|
1888
|
+
storage key (e.g. the write path); the read/delete paths use the richer
|
|
1889
|
+
:class:`SceneResolution` directly.
|
|
1890
|
+
|
|
1891
|
+
Args:
|
|
1892
|
+
identifier: Scene ID (with or without ``scene.`` prefix)
|
|
1893
|
+
|
|
1894
|
+
Returns:
|
|
1895
|
+
The storage key for the configuration API
|
|
1896
|
+
"""
|
|
1897
|
+
return (await self._resolve_scene(identifier)).storage_key
|
|
1898
|
+
|
|
1899
|
+
async def _scene_state_exists(self, storage_key: str) -> bool:
|
|
1900
|
+
"""Return True if ``scene.<storage_key>`` exists in the state machine.
|
|
1901
|
+
|
|
1902
|
+
An ``id``-less YAML scene has no registry ``unique_id`` (so
|
|
1903
|
+
``registry_hit`` is False) yet still registers a state-machine entity.
|
|
1904
|
+
This one-shot existence check separates that real-but-not-storage scene
|
|
1905
|
+
from a genuinely-absent one after a config-API 404 (issue #1971). A 404
|
|
1906
|
+
here is a clean "absent"; a transport failure propagates (fail loud).
|
|
1907
|
+
"""
|
|
1908
|
+
try:
|
|
1909
|
+
await self.get_entity_state(f"scene.{storage_key}")
|
|
1910
|
+
return True
|
|
1911
|
+
except HomeAssistantAPIError as e:
|
|
1912
|
+
if e.status_code == 404:
|
|
1913
|
+
return False
|
|
1914
|
+
raise
|
|
1915
|
+
|
|
1916
|
+
async def _raise_scene_config_404(
|
|
1917
|
+
self,
|
|
1918
|
+
scene_id: str,
|
|
1919
|
+
resolved_id: str,
|
|
1920
|
+
resolution: SceneResolution | None,
|
|
1921
|
+
) -> NoReturn:
|
|
1922
|
+
"""Translate a scene config-API 404 into the right exception.
|
|
1923
|
+
|
|
1924
|
+
When the registry resolve succeeded (``registry_hit``), the entity exists
|
|
1925
|
+
and the 404 means "no editable storage config" → raise
|
|
1926
|
+
:class:`SceneStorageConfigNotFoundError` carrying the platform. On a
|
|
1927
|
+
registry miss the entity may still be an ``id``-less YAML scene, which has
|
|
1928
|
+
no registry ``unique_id`` yet exists in the state machine: a state hit
|
|
1929
|
+
upgrades the 404 to the same not-storage-scene case with ``platform=None``
|
|
1930
|
+
(issue #1971). Only a state miss too is the historic genuinely-absent bare
|
|
1931
|
+
404. The state check is skipped when ``resolution`` is None (the internal
|
|
1932
|
+
``_resolved`` re-fetch fast path, where a 404 is a genuine disappearance).
|
|
1933
|
+
Always raises.
|
|
1934
|
+
"""
|
|
1935
|
+
if resolution is not None:
|
|
1936
|
+
if resolution.registry_hit:
|
|
1937
|
+
raise SceneStorageConfigNotFoundError(
|
|
1938
|
+
scene_id, platform=resolution.platform, storage_key=resolved_id
|
|
1939
|
+
)
|
|
1940
|
+
if await self._scene_state_exists(resolved_id):
|
|
1941
|
+
raise SceneStorageConfigNotFoundError(
|
|
1942
|
+
scene_id, platform=None, storage_key=resolved_id
|
|
1943
|
+
)
|
|
1944
|
+
msg = f"Scene not found: {scene_id}"
|
|
1945
|
+
if resolved_id != scene_id:
|
|
1946
|
+
msg += f" (resolved storage key: {resolved_id})"
|
|
1947
|
+
raise HomeAssistantAPIError(msg, status_code=404)
|
|
1818
1948
|
|
|
1819
1949
|
async def get_scene_config(
|
|
1820
|
-
self,
|
|
1950
|
+
self,
|
|
1951
|
+
scene_id: str,
|
|
1952
|
+
*,
|
|
1953
|
+
_resolved: bool = False,
|
|
1954
|
+
resolution: SceneResolution | None = None,
|
|
1821
1955
|
) -> dict[str, Any]:
|
|
1822
1956
|
"""Get Home Assistant scene configuration by scene_id.
|
|
1823
1957
|
|
|
1958
|
+
``resolution`` lets a caller that already ran :meth:`_resolve_scene` pass
|
|
1959
|
+
the full outcome through: the endpoint uses its ``storage_key`` and its
|
|
1960
|
+
``registry_hit``/``platform`` drive the config-404 classification (issue
|
|
1961
|
+
#1971) with no extra lookup. It takes precedence over ``_resolved``.
|
|
1962
|
+
|
|
1824
1963
|
``_resolved=True`` signals ``scene_id`` is already the resolved storage
|
|
1825
1964
|
key (the caller ran :meth:`resolve_scene_id`), skipping the redundant
|
|
1826
1965
|
registry lookup. ``resolve_scene_id`` is idempotent, so the result is
|
|
1827
|
-
identical either way.
|
|
1966
|
+
identical either way. This fast path is the internal re-fetch of an
|
|
1967
|
+
already-resolved scene (its sole caller is the post-write re-fetch), where
|
|
1968
|
+
a subsequent 404 is a genuine disappearance, not the not-storage-scene
|
|
1969
|
+
case; the ``registry_hit``/``platform`` signals a config 404 needs are
|
|
1970
|
+
deliberately unavailable there.
|
|
1828
1971
|
"""
|
|
1829
|
-
|
|
1972
|
+
if resolution is not None:
|
|
1973
|
+
resolved_id = resolution.storage_key
|
|
1974
|
+
elif _resolved:
|
|
1975
|
+
resolved_id = scene_id
|
|
1976
|
+
else:
|
|
1977
|
+
resolution = await self._resolve_scene(scene_id)
|
|
1978
|
+
resolved_id = resolution.storage_key
|
|
1830
1979
|
try:
|
|
1831
1980
|
endpoint = f"config/scene/config/{resolved_id}"
|
|
1832
1981
|
response = await self._request("GET", endpoint)
|
|
@@ -1834,10 +1983,7 @@ class HomeAssistantClient:
|
|
|
1834
1983
|
return {"success": True, "scene_id": resolved_id, "config": response}
|
|
1835
1984
|
except HomeAssistantAPIError as e:
|
|
1836
1985
|
if e.status_code == 404:
|
|
1837
|
-
|
|
1838
|
-
if resolved_id != scene_id:
|
|
1839
|
-
msg += f" (resolved storage key: {resolved_id})"
|
|
1840
|
-
raise HomeAssistantAPIError(msg, status_code=404) from e
|
|
1986
|
+
await self._raise_scene_config_404(scene_id, resolved_id, resolution)
|
|
1841
1987
|
raise
|
|
1842
1988
|
except Exception as e:
|
|
1843
1989
|
logger.error(f"Failed to get scene config for {scene_id}: {e}")
|
|
@@ -1904,15 +2050,24 @@ class HomeAssistantClient:
|
|
|
1904
2050
|
raise
|
|
1905
2051
|
|
|
1906
2052
|
async def delete_scene_config(
|
|
1907
|
-
self,
|
|
2053
|
+
self,
|
|
2054
|
+
scene_id: str,
|
|
2055
|
+
*,
|
|
2056
|
+
resolution: SceneResolution | None = None,
|
|
1908
2057
|
) -> dict[str, Any]:
|
|
1909
2058
|
"""Delete Home Assistant scene configuration.
|
|
1910
2059
|
|
|
1911
|
-
``
|
|
1912
|
-
|
|
1913
|
-
|
|
2060
|
+
``resolution`` lets a caller that already ran :meth:`_resolve_scene` pass
|
|
2061
|
+
the full outcome through - the endpoint uses its ``storage_key`` while
|
|
2062
|
+
``scene_id`` stays the caller's slug for messages, and its ``registry_hit``
|
|
2063
|
+
drives the config-404 classification (issue #1971) with no extra lookup.
|
|
2064
|
+
Otherwise the id is resolved here.
|
|
1914
2065
|
"""
|
|
1915
|
-
|
|
2066
|
+
if resolution is not None:
|
|
2067
|
+
resolved_id = resolution.storage_key
|
|
2068
|
+
else:
|
|
2069
|
+
resolution = await self._resolve_scene(scene_id)
|
|
2070
|
+
resolved_id = resolution.storage_key
|
|
1916
2071
|
try:
|
|
1917
2072
|
endpoint = f"config/scene/config/{resolved_id}"
|
|
1918
2073
|
response = await self._request("DELETE", endpoint)
|
|
@@ -1925,10 +2080,7 @@ class HomeAssistantClient:
|
|
|
1925
2080
|
}
|
|
1926
2081
|
except HomeAssistantAPIError as e:
|
|
1927
2082
|
if e.status_code == 404:
|
|
1928
|
-
|
|
1929
|
-
if resolved_id != scene_id:
|
|
1930
|
-
msg += f" (resolved storage key: {resolved_id})"
|
|
1931
|
-
raise HomeAssistantAPIError(msg, status_code=404) from e
|
|
2083
|
+
await self._raise_scene_config_404(scene_id, resolved_id, resolution)
|
|
1932
2084
|
elif e.status_code == 405:
|
|
1933
2085
|
raise HomeAssistantAPIError(
|
|
1934
2086
|
f"Cannot delete scene '{scene_id}': The HTTP DELETE method is blocked. "
|
|
@@ -203,6 +203,25 @@ class Settings(BaseSettings):
|
|
|
203
203
|
)
|
|
204
204
|
enable_yaml_packages_scene: bool = Field(False, alias="ENABLE_YAML_PACKAGES_SCENE")
|
|
205
205
|
|
|
206
|
+
# Operator-configured extra top-level keys ha_config_set_yaml may write,
|
|
207
|
+
# comma-separated, on top of the custom component's built-in allowlist
|
|
208
|
+
# (#1887). For YAML-first integrations that are valid on one install but
|
|
209
|
+
# not worth hardcoding globally. Additive only, and never a way past the
|
|
210
|
+
# component's YAML_KEY_DENYLIST: that floor is enforced component-side
|
|
211
|
+
# (the authoritative layer) and is deliberately not mirrored here, so
|
|
212
|
+
# there is one copy to keep correct. A denied key typed into this setting
|
|
213
|
+
# is simply ignored, with the component's explanation on first use.
|
|
214
|
+
# Nor does it lift the packages-only restriction on automation/script/
|
|
215
|
+
# scene: those keep reaching packages/*.yaml through their own per-key
|
|
216
|
+
# toggles and stay rejected in configuration.yaml.
|
|
217
|
+
# Empty (the default) keeps today's behaviour exactly.
|
|
218
|
+
# Registered in ADVANCED_SETTINGS_FIELDS (section ``beta_yamlkeys``), not
|
|
219
|
+
# FEATURE_FLAG_FIELDS, because it is a value rather than a toggle – the
|
|
220
|
+
# same placement the code-mode sub-settings use. Meaningful only when
|
|
221
|
+
# ``enable_yaml_config_editing`` is on; the UI nests it under that parent
|
|
222
|
+
# like the per-key toggles above.
|
|
223
|
+
extra_yaml_write_keys: str = Field("", alias="HA_MCP_EXTRA_YAML_KEYS")
|
|
224
|
+
|
|
206
225
|
# Seed values for tool visibility (comma-separated tool names).
|
|
207
226
|
# Used as initial config when no tool_config.json exists.
|
|
208
227
|
# The web settings UI (/settings) is the primary interface for managing these.
|
|
@@ -621,6 +640,7 @@ AdvancedSection = Literal[
|
|
|
621
640
|
"tools_surface",
|
|
622
641
|
"sidecar",
|
|
623
642
|
"beta_codemode",
|
|
643
|
+
"beta_yamlkeys",
|
|
624
644
|
"developer",
|
|
625
645
|
]
|
|
626
646
|
|
|
@@ -896,6 +916,18 @@ ADVANCED_SETTINGS_FIELDS: tuple[AdvancedField, ...] = (
|
|
|
896
916
|
"beta_codemode",
|
|
897
917
|
True,
|
|
898
918
|
),
|
|
919
|
+
# Extra YAML write keys (issue #1887). Same shape as the code-mode
|
|
920
|
+
# sub-settings above: a non-bool value that belongs visually under a
|
|
921
|
+
# feature toggle rather than in the advanced panel, so it lives here
|
|
922
|
+
# with its own section and the features renderer nests it beneath
|
|
923
|
+
# "Enable YAML config editing".
|
|
924
|
+
AdvancedField(
|
|
925
|
+
"extra_yaml_write_keys",
|
|
926
|
+
"HA_MCP_EXTRA_YAML_KEYS",
|
|
927
|
+
str,
|
|
928
|
+
"beta_yamlkeys",
|
|
929
|
+
True,
|
|
930
|
+
),
|
|
899
931
|
# Developer mode (issue #1775). Lives in ADVANCED_SETTINGS_FIELDS —
|
|
900
932
|
# not FEATURE_FLAG_FIELDS — so it renders in its own "Developer"
|
|
901
933
|
# section at the very bottom of the Server Settings tab instead of
|
|
@@ -1185,6 +1217,30 @@ def _apply_one_feature_flag_override(
|
|
|
1185
1217
|
)
|
|
1186
1218
|
|
|
1187
1219
|
|
|
1220
|
+
def parse_extra_yaml_write_keys(settings: "Settings") -> list[str]:
|
|
1221
|
+
"""Parse ``extra_yaml_write_keys`` into a clean key list (#1887).
|
|
1222
|
+
|
|
1223
|
+
Whitespace and empty entries are dropped and the result is deduplicated
|
|
1224
|
+
and sorted, so the service payload is deterministic and unaffected by how
|
|
1225
|
+
the operator spaced the setting.
|
|
1226
|
+
|
|
1227
|
+
The component's ``YAML_KEY_DENYLIST`` is NOT applied here: that floor
|
|
1228
|
+
lives component-side, in the layer that authorizes the write. Mirroring it
|
|
1229
|
+
would mean a second copy to keep in lockstep for no gain: a denied key sent
|
|
1230
|
+
on the wire is dropped there anyway.
|
|
1231
|
+
|
|
1232
|
+
Lives in this module rather than next to the YAML tool because the backup
|
|
1233
|
+
restore path needs it too, and ``backup_manager`` importing a ``tools_*``
|
|
1234
|
+
module would add an import edge that binds ``tools_yaml_config``'s
|
|
1235
|
+
module-level names at restore time.
|
|
1236
|
+
"""
|
|
1237
|
+
# Direct attribute access, matching ``_disabled_packages_keys``: a future
|
|
1238
|
+
# rename must raise loudly rather than silently return an empty list,
|
|
1239
|
+
# which would read as "the operator configured nothing".
|
|
1240
|
+
raw = settings.extra_yaml_write_keys or ""
|
|
1241
|
+
return sorted({segment.strip() for segment in raw.split(",") if segment.strip()})
|
|
1242
|
+
|
|
1243
|
+
|
|
1188
1244
|
def _apply_beta_master_gate(settings: "Settings") -> None:
|
|
1189
1245
|
"""Force BETA_FEATURE_FIELDS to False when ``enable_beta_features`` is off.
|
|
1190
1246
|
|
{ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/locales/de.json
RENAMED
|
@@ -412,6 +412,8 @@
|
|
|
412
412
|
"advanced.code_mode_max_invocations.help": "API-/Tool-Aufruf-Grenze pro Sandbox-Lauf. Neustart erforderlich.",
|
|
413
413
|
"advanced.code_mode_saved_tools_path.label": "Pfad für gespeicherte Tools",
|
|
414
414
|
"advanced.code_mode_saved_tools_path.help": "JSON-Datei, die verwendet wird, um benutzerdefinierte Tools über Neustarts hinweg beizubehalten. Neustart erforderlich.",
|
|
415
|
+
"advanced.extra_yaml_write_keys.label": "Zusätzliche YAML-Schreib-Keys",
|
|
416
|
+
"advanced.extra_yaml_write_keys.help": "Kommagetrennte Top-Level-Keys, die ha_config_set_yaml zusätzlich zu den eingebauten schreiben darf – für YAML-first-Integrationen auf dieser Installation (z. B. alert2). Keys, die die Vertrauensgrenze von Home Assistant selbst verändern, lassen sich nie hinzufügen und werden ignoriert. Erfordert Custom Component 1.2.4 oder neuer.",
|
|
415
417
|
"advanced.sidecar_pin_port.label": "Einstellungs-UI-Sidecar-Port",
|
|
416
418
|
"advanced.sidecar_pin_port.help": "0 wählt bei jedem Neustart einen freien Port; 1024-65535 heftet einen bevorzugten Port an. Neustart erforderlich.",
|
|
417
419
|
"advanced.enable_dev_mode.label": "Entwicklermodus",
|
{ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/locales/en.json
RENAMED
|
@@ -412,6 +412,8 @@
|
|
|
412
412
|
"advanced.code_mode_max_invocations.help": "API/tool-call cap per sandbox run. Restart required.",
|
|
413
413
|
"advanced.code_mode_saved_tools_path.label": "Saved-tools path",
|
|
414
414
|
"advanced.code_mode_saved_tools_path.help": "JSON file used to persist custom tools across restarts. Restart required.",
|
|
415
|
+
"advanced.extra_yaml_write_keys.label": "Extra YAML write keys",
|
|
416
|
+
"advanced.extra_yaml_write_keys.help": "Comma-separated top-level keys ha_config_set_yaml may write in addition to the built-in ones, for YAML-first integrations on this install (e.g. alert2). Keys that redefine Home Assistant's own trust boundary can never be added and are ignored. Requires custom component 1.2.4 or newer.",
|
|
415
417
|
"advanced.sidecar_pin_port.label": "Settings UI sidecar port",
|
|
416
418
|
"advanced.sidecar_pin_port.help": "0 chooses a free port each restart; 1024–65535 pins a preferred port. Restart required.",
|
|
417
419
|
"advanced.enable_dev_mode.label": "Developer mode",
|
{ha_mcp_dev-7.14.2.dev2100 → ha_mcp_dev-7.14.2.dev2105}/src/ha_mcp/settings_ui/locales/ru.json
RENAMED
|
@@ -412,6 +412,8 @@
|
|
|
412
412
|
"advanced.code_mode_max_invocations.help": "Лимит вызовов API и инструментов за один запуск. Нужен перезапуск.",
|
|
413
413
|
"advanced.code_mode_saved_tools_path.label": "Путь к сохранённым инструментам",
|
|
414
414
|
"advanced.code_mode_saved_tools_path.help": "JSON-файл для хранения пользовательских инструментов между перезапусками. Нужен перезапуск.",
|
|
415
|
+
"advanced.extra_yaml_write_keys.label": "Дополнительные YAML-ключи для записи",
|
|
416
|
+
"advanced.extra_yaml_write_keys.help": "Список верхнеуровневых ключей через запятую, которые ha_config_set_yaml может записывать вдобавок к встроенным – для YAML-ориентированных интеграций на этой установке (например, alert2). Ключи, меняющие саму границу доверия Home Assistant, добавить нельзя, они игнорируются. Требуется пользовательский компонент 1.2.4 или новее.",
|
|
415
417
|
"advanced.sidecar_pin_port.label": "Порт sidecar интерфейса настроек",
|
|
416
418
|
"advanced.sidecar_pin_port.help": "0 выбирает свободный порт при каждом запуске; 1024–65535 закрепляет желаемый порт. Нужен перезапуск.",
|
|
417
419
|
"advanced.enable_dev_mode.label": "Режим разработчика",
|
|
@@ -2167,7 +2167,9 @@ function renderFeatureFlags(flags) {
|
|
|
2167
2167
|
// bottom.
|
|
2168
2168
|
if (fieldName === 'enable_code_mode') {
|
|
2169
2169
|
const codeModeOn = !!f.value;
|
|
2170
|
-
|
|
2170
|
+
renderAdvancedSubRows(
|
|
2171
|
+
targetBody, 'beta_codemode', 'codemode-sub', !masterOn || !codeModeOn
|
|
2172
|
+
);
|
|
2171
2173
|
}
|
|
2172
2174
|
// After rendering the enable_yaml_config_editing parent, inject
|
|
2173
2175
|
// its 3 per-key sub-rows (automation/script/scene). Dimmed when
|
|
@@ -2179,6 +2181,11 @@ function renderFeatureFlags(flags) {
|
|
|
2179
2181
|
cssClass: 'yaml-packages-sub',
|
|
2180
2182
|
lockedByGate: !masterOn || !parentOn,
|
|
2181
2183
|
});
|
|
2184
|
+
// Extra write keys (#1887) – same nesting depth as the per-key
|
|
2185
|
+
// toggles above, but a text value from the advanced cache.
|
|
2186
|
+
renderAdvancedSubRows(
|
|
2187
|
+
targetBody, 'beta_yamlkeys', 'yaml-packages-sub', !masterOn || !parentOn
|
|
2188
|
+
);
|
|
2182
2189
|
}
|
|
2183
2190
|
// After the enable_mandatory_bps parent row, inject its strict-mode
|
|
2184
2191
|
// sub-row. This whole group is non-beta, so the only gate is the
|
|
@@ -2210,7 +2217,7 @@ function renderFeatureFlags(flags) {
|
|
|
2210
2217
|
// master beta AND parent; mandatory-bps: parent only) and pass the CSS
|
|
2211
2218
|
// class that carries the group's indent/guide-bar depth. The number/text
|
|
2212
2219
|
// code-mode sub-numerics are NOT rendered here — they save through
|
|
2213
|
-
// commitAdvancedEdit and live in
|
|
2220
|
+
// commitAdvancedEdit and live in renderAdvancedSubRows.
|
|
2214
2221
|
function renderSubFlagRows(flags, parentEl, subFieldNames, { cssClass, lockedByGate }) {
|
|
2215
2222
|
subFieldNames.forEach(fieldName => {
|
|
2216
2223
|
const f = flags[fieldName];
|
|
@@ -2276,8 +2283,13 @@ function renderSubFlagRows(flags, parentEl, subFieldNames, { cssClass, lockedByG
|
|
|
2276
2283
|
});
|
|
2277
2284
|
}
|
|
2278
2285
|
|
|
2279
|
-
|
|
2280
|
-
|
|
2286
|
+
// Shared renderer for non-bool sub-rows that live in ADVANCED_SETTINGS_FIELDS
|
|
2287
|
+
// but render nested under a feature toggle (code-mode numerics under
|
|
2288
|
+
// enable_code_mode, extra YAML write keys under enable_yaml_config_editing).
|
|
2289
|
+
// They save through commitAdvancedEdit rather than saveFeatureFlag, which is
|
|
2290
|
+
// why renderSubFlagRows (checkbox-only) cannot serve them.
|
|
2291
|
+
function renderAdvancedSubRows(parentEl, section, cssClass, lockedByGate) {
|
|
2292
|
+
const cmRows = (_advancedFields || []).filter(x => x.section === section);
|
|
2281
2293
|
cmRows.forEach(f => {
|
|
2282
2294
|
const meta = localizeMeta(
|
|
2283
2295
|
'advanced',
|
|
@@ -2285,9 +2297,7 @@ function renderCodeModeSubRows(parentEl, masterOn, codeModeOn) {
|
|
|
2285
2297
|
ADVANCED_FIELD_META[f.field] || { label: f.field, help: '' }
|
|
2286
2298
|
);
|
|
2287
2299
|
const row = document.createElement('div');
|
|
2288
|
-
|
|
2289
|
-
const dimmed = lockedByGate;
|
|
2290
|
-
row.className = 'feature-row codemode-sub' + (dimmed ? ' dimmed' : '');
|
|
2300
|
+
row.className = 'feature-row ' + cssClass + (lockedByGate ? ' dimmed' : '');
|
|
2291
2301
|
|
|
2292
2302
|
const info = document.createElement('div');
|
|
2293
2303
|
info.className = 'feature-info';
|
|
@@ -3390,6 +3400,7 @@ const ADVANCED_FIELD_META = {
|
|
|
3390
3400
|
code_mode_max_recursion: { label: "Code-mode max recursion", help: "Recursion-depth cap per sandbox run. Restart required." },
|
|
3391
3401
|
code_mode_max_invocations: { label: "Code-mode max invocations", help: "API/tool-call cap per sandbox run. Restart required." },
|
|
3392
3402
|
code_mode_saved_tools_path:{ label: "Saved-tools path", help: "JSON file where ha_manage_custom_tool persists saved tools across restarts. Restart required." },
|
|
3403
|
+
extra_yaml_write_keys: { label: "Extra YAML write keys", help: "Comma-separated top-level keys ha_config_set_yaml may write in addition to the built-in ones, for YAML-first integrations on this install (e.g. alert2). Keys that redefine Home Assistant's own trust boundary can never be added and are ignored. Requires custom component 1.2.4 or newer." },
|
|
3393
3404
|
sidecar_pin_port: { label: "Settings UI sidecar port", help: "0 = a new free port each restart (default); set 1024–65535 to pin a fixed port so the settings URL stays stable across restarts. Falls back to a free port if the pinned one is busy. Restart required." },
|
|
3394
3405
|
enable_dev_mode: { label: "Developer mode", help: "⚠ DANGER: registers hidden developer tools (ha_dev_manage_server, ha_dev_manage_settings) that let AI agents change server settings and replace the running server version (e.g. install a PR build). For development and testing only. Restart required." },
|
|
3395
3406
|
};
|
|
@@ -3493,10 +3504,10 @@ async function loadAdvancedSettings() {
|
|
|
3493
3504
|
renderAdvancedSection('advSidecar', bySection.sidecar || []);
|
|
3494
3505
|
renderAdvancedSection('advDeveloper', bySection.developer || []);
|
|
3495
3506
|
applySidecarAvailability(data.is_stdio !== false);
|
|
3496
|
-
// Re-render feature flags so the
|
|
3497
|
-
//
|
|
3498
|
-
//
|
|
3499
|
-
// flags haven't loaded yet.
|
|
3507
|
+
// Re-render feature flags so the advanced-backed sub-rows (code_mode
|
|
3508
|
+
// numerics, extra YAML write keys) show up beneath their parent toggles
|
|
3509
|
+
// (race: loadFeatureFlags may have run before _advancedFields was
|
|
3510
|
+
// populated). Cheap no-op if feature flags haven't loaded yet.
|
|
3500
3511
|
if (Object.keys(_lastFeatureFlags).length > 0) {
|
|
3501
3512
|
renderFeatureFlags(_lastFeatureFlags);
|
|
3502
3513
|
}
|