ha-mcp-dev 7.12.3.dev1882__tar.gz → 7.12.3.dev1885__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.12.3.dev1882/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.12.3.dev1885}/PKG-INFO +2 -2
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/README.md +1 -1
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/pyproject.toml +1 -1
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/config.py +18 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/errors.py +6 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/server.py +64 -9
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/settings_ui/__init__.py +97 -39
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/settings_ui/settings.css +34 -12
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/settings_ui/settings.js +62 -9
- ha_mcp_dev-7.12.3.dev1885/src/ha_mcp/strict_bps.py +272 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_config_automations.py +4 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_config_dashboards.py +4 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_config_helpers.py +4 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_config_scenes.py +4 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_config_scripts.py +4 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_yaml_config.py +4 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/utils/kill_signal_diagnostics.py +13 -2
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885/src/ha_mcp_dev.egg-info}/PKG-INFO +2 -2
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/LICENSE +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/setup.cfg +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.12.3.dev1882 → ha_mcp_dev-7.12.3.dev1885}/tests/test_env_manager.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ha-mcp-dev
|
|
3
|
-
Version: 7.12.3.
|
|
3
|
+
Version: 7.12.3.dev1885
|
|
4
4
|
Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
|
|
5
5
|
Author-email: Julien <github@qc-h.net>
|
|
6
6
|
License: MIT
|
|
@@ -268,7 +268,7 @@ Spend less time configuring, more time enjoying your smart home.
|
|
|
268
268
|
| **HACS** | `ha_get_hacs_info`, `ha_manage_hacs` |
|
|
269
269
|
| **Helper Entities** | `ha_config_list_helpers`, `ha_config_set_helper`, `ha_remove_helpers_integrations` |
|
|
270
270
|
| **History & Statistics** | `ha_get_automation_traces`, `ha_get_history`, `ha_get_logs` |
|
|
271
|
-
| **Integrations** | `ha_get_integration`, `ha_get_system_health`, `
|
|
271
|
+
| **Integrations** | `ha_get_integration`, `ha_get_system_health`, `ha_set_integration` |
|
|
272
272
|
| **Labels & Categories** | `ha_config_get_category`, `ha_config_get_label`, `ha_config_remove_category`, `ha_config_remove_label`, `ha_config_set_category`, `ha_config_set_label` |
|
|
273
273
|
| **Matter** | `ha_manage_radio` |
|
|
274
274
|
| **Scenes** | `ha_config_get_scene`, `ha_config_remove_scene`, `ha_config_set_scene` |
|
|
@@ -235,7 +235,7 @@ Spend less time configuring, more time enjoying your smart home.
|
|
|
235
235
|
| **HACS** | `ha_get_hacs_info`, `ha_manage_hacs` |
|
|
236
236
|
| **Helper Entities** | `ha_config_list_helpers`, `ha_config_set_helper`, `ha_remove_helpers_integrations` |
|
|
237
237
|
| **History & Statistics** | `ha_get_automation_traces`, `ha_get_history`, `ha_get_logs` |
|
|
238
|
-
| **Integrations** | `ha_get_integration`, `ha_get_system_health`, `
|
|
238
|
+
| **Integrations** | `ha_get_integration`, `ha_get_system_health`, `ha_set_integration` |
|
|
239
239
|
| **Labels & Categories** | `ha_config_get_category`, `ha_config_get_label`, `ha_config_remove_category`, `ha_config_remove_label`, `ha_config_set_category`, `ha_config_set_label` |
|
|
240
240
|
| **Matter** | `ha_manage_radio` |
|
|
241
241
|
| **Scenes** | `ha_config_get_scene`, `ha_config_remove_scene`, `ha_config_set_scene` |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ha-mcp-dev"
|
|
7
|
-
version = "7.12.3.
|
|
7
|
+
version = "7.12.3.dev1885"
|
|
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
|
|
@@ -237,6 +237,18 @@ class Settings(BaseSettings):
|
|
|
237
237
|
# regardless of the per-call param or BP warnings. Default on.
|
|
238
238
|
enable_mandatory_bps: bool = Field(True, alias="ENABLE_MANDATORY_BPS")
|
|
239
239
|
|
|
240
|
+
# Strict best-practices gate (issue #1779) — child flag of
|
|
241
|
+
# ``enable_mandatory_bps``. When effective, the six write tools are
|
|
242
|
+
# HARD-BLOCKED unless the call carries the acknowledgment key that is
|
|
243
|
+
# published only inside the best-practices skill content served by
|
|
244
|
+
# ``ha_get_skill_guide`` (modeled on the Hubitat MCP acknowledgment
|
|
245
|
+
# gate). Default ON so strict mode is active whenever the parent is on;
|
|
246
|
+
# inert when the parent is off — that cascade is enforced at the
|
|
247
|
+
# consumption site (``strict_bps.strict_bps_effective``), not here,
|
|
248
|
+
# because this flag is deliberately NOT a beta sub-flag and there is no
|
|
249
|
+
# config-level parent gate for non-beta flags.
|
|
250
|
+
enable_strict_mandatory_bps: bool = Field(True, alias="ENABLE_STRICT_MANDATORY_BPS")
|
|
251
|
+
|
|
240
252
|
# Filesystem tools — read/write/delete/list under the HA config dir.
|
|
241
253
|
# Previously gated by a direct ``os.getenv`` call in
|
|
242
254
|
# ``tools/tools_filesystem.py`` so callers (and the settings UI)
|
|
@@ -656,6 +668,12 @@ FEATURE_FLAG_FIELDS: tuple[FeatureFlagField, ...] = (
|
|
|
656
668
|
# gated by the beta master) nor in ADVANCED_SETTINGS_FIELDS (registries
|
|
657
669
|
# are name-disjoint per _validate_registries()).
|
|
658
670
|
FeatureFlagField("enable_mandatory_bps", "ENABLE_MANDATORY_BPS", bool),
|
|
671
|
+
# Child flag of enable_mandatory_bps (#1779). Non-beta like its
|
|
672
|
+
# parent, so it belongs here and NOT in BETA_FEATURE_FIELDS; kept out
|
|
673
|
+
# of ADVANCED_SETTINGS_FIELDS too (registries are name-disjoint).
|
|
674
|
+
FeatureFlagField(
|
|
675
|
+
"enable_strict_mandatory_bps", "ENABLE_STRICT_MANDATORY_BPS", bool
|
|
676
|
+
),
|
|
659
677
|
FeatureFlagField("enable_yaml_config_editing", "ENABLE_YAML_CONFIG_EDITING", bool),
|
|
660
678
|
FeatureFlagField("enable_yaml_edit_confirm", "ENABLE_YAML_EDIT_CONFIRM", bool),
|
|
661
679
|
# Per-key sub-gates beneath enable_yaml_config_editing. Nested in
|
|
@@ -100,6 +100,12 @@ class ErrorCode(StrEnum):
|
|
|
100
100
|
# because the server-wide Read Only Mode toggle is on.
|
|
101
101
|
READ_ONLY_MODE = "READ_ONLY_MODE"
|
|
102
102
|
|
|
103
|
+
# Strict mandatory best-practices gate (#1779). A gated write tool was
|
|
104
|
+
# blocked because strict mode is effective and the call did not carry
|
|
105
|
+
# the acknowledgment key published only in the best-practices skill
|
|
106
|
+
# content. Fail-closed: nothing was changed.
|
|
107
|
+
BPS_ACKNOWLEDGMENT_REQUIRED = "BPS_ACKNOWLEDGMENT_REQUIRED"
|
|
108
|
+
|
|
103
109
|
# Mandatory auto-backup (#1579). A write that requires a pre-write
|
|
104
110
|
# snapshot was blocked because the snapshot could not be captured
|
|
105
111
|
# (fetch failed, the snapshot write failed — e.g. disk full — or the
|
|
@@ -257,6 +257,13 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
257
257
|
# queues a pointless approval request.
|
|
258
258
|
self._apply_read_only_middleware()
|
|
259
259
|
|
|
260
|
+
# Strict mandatory best-practices gate (#1779) — always installed,
|
|
261
|
+
# self-no-ops at call time. Registered immediately AFTER the
|
|
262
|
+
# read-only middleware so a read-only rejection wins over the
|
|
263
|
+
# strict-BPS one (a write blocked by read-only mode should not be
|
|
264
|
+
# asked for an acknowledgment key it could never usefully supply).
|
|
265
|
+
self._apply_strict_bps_middleware()
|
|
266
|
+
|
|
260
267
|
# Wire tool security policies middleware (#966) — opt-in via
|
|
261
268
|
# ENABLE_TOOL_SECURITY_POLICIES. Must come last so the middleware
|
|
262
269
|
# wraps the final tool surface (including the search proxies).
|
|
@@ -1041,6 +1048,41 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
1041
1048
|
|
|
1042
1049
|
self.mcp.add_middleware(ReadOnlyMiddleware(list_tools=_list_all_tools))
|
|
1043
1050
|
|
|
1051
|
+
def _apply_strict_bps_middleware(self) -> None:
|
|
1052
|
+
"""Install the strict best-practices gate (#1779).
|
|
1053
|
+
|
|
1054
|
+
Always installed — the middleware self-no-ops at call time
|
|
1055
|
+
(consults ``strict_bps_effective()`` per call), so a toggle applies
|
|
1056
|
+
live in standalone-HTTP/embedded mode like ``read_only_mode``
|
|
1057
|
+
(other modes pick it up on restart).
|
|
1058
|
+
|
|
1059
|
+
Also emits a startup WARNING when the child flag is on while its
|
|
1060
|
+
parent (``enable_mandatory_bps``) is off: strict mode is inert in
|
|
1061
|
+
that configuration, and env-var users (Docker, uvx, pip) would
|
|
1062
|
+
otherwise have no signal that their strict toggle does nothing.
|
|
1063
|
+
Precedent: the lite-docstrings warning.
|
|
1064
|
+
"""
|
|
1065
|
+
from .strict_bps import StrictBpsMiddleware
|
|
1066
|
+
|
|
1067
|
+
if self.settings.enable_strict_mandatory_bps and (
|
|
1068
|
+
not self.settings.enable_mandatory_bps
|
|
1069
|
+
):
|
|
1070
|
+
logger.warning(
|
|
1071
|
+
"ENABLE_STRICT_MANDATORY_BPS is on but ENABLE_MANDATORY_BPS is "
|
|
1072
|
+
"off — strict best-practices mode is INERT. The strict gate only "
|
|
1073
|
+
"engages when the mandatory-BPS master switch is also on. Enable "
|
|
1074
|
+
"ENABLE_MANDATORY_BPS to activate the acknowledgment gate."
|
|
1075
|
+
)
|
|
1076
|
+
|
|
1077
|
+
# Unfiltered catalog lookup so the gate can pass through calls to
|
|
1078
|
+
# gate-map tools that never registered (e.g. ha_config_set_yaml with
|
|
1079
|
+
# yaml editing off) — those get FastMCP's unknown-tool error instead
|
|
1080
|
+
# of a key-fetch misdirection (#1820 review).
|
|
1081
|
+
async def _list_all_tools() -> Any:
|
|
1082
|
+
return await self.mcp.local_provider._list_tools()
|
|
1083
|
+
|
|
1084
|
+
self.mcp.add_middleware(StrictBpsMiddleware(list_tools=_list_all_tools))
|
|
1085
|
+
|
|
1044
1086
|
# Shared action-phrased keyword block for retrieval. Some MCP clients
|
|
1045
1087
|
# (Claude Code, others) rank candidate tools by token-overlap between
|
|
1046
1088
|
# the user's natural-language query and each tool's `description`
|
|
@@ -1295,14 +1337,13 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
1295
1337
|
),
|
|
1296
1338
|
] = None,
|
|
1297
1339
|
) -> dict[str, Any]:
|
|
1298
|
-
#
|
|
1299
|
-
#
|
|
1300
|
-
#
|
|
1301
|
-
#
|
|
1302
|
-
#
|
|
1303
|
-
#
|
|
1304
|
-
|
|
1305
|
-
return self._handle_skill_guide_call(skills_dir, skill, file)
|
|
1340
|
+
# Re-read the skills dir live on every call (#1820 review):
|
|
1341
|
+
# the strict-BPS gate also reads it live, so a registration-time
|
|
1342
|
+
# capture deadlocks the one recovery path — vendor absent at
|
|
1343
|
+
# boot (gate fails open), operator runs `git submodule update
|
|
1344
|
+
# --init` in place, the gate flips ON, but a captured None here
|
|
1345
|
+
# would keep the acknowledgment key unobtainable until restart.
|
|
1346
|
+
return self._handle_skill_guide_call(self._get_skills_dir(), skill, file)
|
|
1306
1347
|
|
|
1307
1348
|
self.mcp.tool(
|
|
1308
1349
|
name=SKILL_TOOL_NAME,
|
|
@@ -1560,10 +1601,24 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
1560
1601
|
# parsing the (potentially large) content body. Scoped to the
|
|
1561
1602
|
# best-practice skill because that's the one the write-tool
|
|
1562
1603
|
# MandatoryBPS param gates; other skills (if any) are unrelated.
|
|
1604
|
+
from .strict_bps import strict_bps_ack_line, strict_bps_effective
|
|
1563
1605
|
from .tools.util_helpers import _HA_BEST_PRACTICES_SKILL_NAME
|
|
1564
1606
|
|
|
1607
|
+
is_best_practices = skill == _HA_BEST_PRACTICES_SKILL_NAME
|
|
1608
|
+
|
|
1609
|
+
# Publish the acknowledgment key inside the best-practices content
|
|
1610
|
+
# itself (#1779) when strict mode is effective — this Tier-3 read is
|
|
1611
|
+
# the ONLY caller-facing surface that carries the key. Prepended to
|
|
1612
|
+
# the content body so a model that reads the guide obtains the key
|
|
1613
|
+
# it must pass as BestPracticeKey on gated writes. The skill://
|
|
1614
|
+
# resource surface reads raw files and does NOT get this line; that
|
|
1615
|
+
# is accepted — resource-preferring clients recover via the block
|
|
1616
|
+
# error's suggestion, which points them back at this tool.
|
|
1617
|
+
if is_best_practices and strict_bps_effective():
|
|
1618
|
+
content = f"{strict_bps_ack_line()}\n\n{content}"
|
|
1619
|
+
|
|
1565
1620
|
response: dict[str, Any] = {}
|
|
1566
|
-
if
|
|
1621
|
+
if is_best_practices:
|
|
1567
1622
|
response["skill_content_hint"] = _SKILL_GUIDE_MANDATORYBPS_HINT
|
|
1568
1623
|
response.update(
|
|
1569
1624
|
{
|
|
@@ -19,7 +19,7 @@ import os
|
|
|
19
19
|
import re
|
|
20
20
|
import time
|
|
21
21
|
import uuid
|
|
22
|
-
from collections.abc import Awaitable, Callable
|
|
22
|
+
from collections.abc import Awaitable, Callable, Container
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
from typing import TYPE_CHECKING, Any, Literal, NamedTuple, NotRequired, TypedDict
|
|
25
25
|
|
|
@@ -1248,6 +1248,49 @@ def _schedule_supervisor_self_restart(
|
|
|
1248
1248
|
task.add_done_callback(_BACKGROUND_RESTART_TASKS.discard)
|
|
1249
1249
|
|
|
1250
1250
|
|
|
1251
|
+
def _reject_child_flags_without_parent(
|
|
1252
|
+
raw_flags: dict[str, Any],
|
|
1253
|
+
parent_field: str,
|
|
1254
|
+
child_fields: Container[str],
|
|
1255
|
+
message: Callable[[list[str]], str],
|
|
1256
|
+
suggestions: list[str],
|
|
1257
|
+
) -> JSONResponse | None:
|
|
1258
|
+
"""Reject a feature-flag save that enables child flags while their
|
|
1259
|
+
parent stays off after the merge.
|
|
1260
|
+
|
|
1261
|
+
A child flag is only valid when ``parent_field`` is truthy AFTER the
|
|
1262
|
+
merge. The post-merge parent is derived from the payload (if present),
|
|
1263
|
+
else the live ``Settings`` value — the same value the runtime gate
|
|
1264
|
+
will see. A child turned truthy against an
|
|
1265
|
+
off parent would be forced back off at runtime, so reject it now
|
|
1266
|
+
rather than let the user learn the save was a no-op at next startup.
|
|
1267
|
+
Turning the parent off alone is NOT rejected: children absent from the
|
|
1268
|
+
payload keep their persisted value and the runtime gate handles them.
|
|
1269
|
+
|
|
1270
|
+
Returns a 409 ``JSONResponse`` — ``message`` receives every offending
|
|
1271
|
+
child, which is also echoed in ``context["rejected"]`` — or ``None``
|
|
1272
|
+
when there is nothing to reject.
|
|
1273
|
+
"""
|
|
1274
|
+
rejected = [k for k in raw_flags if k in child_fields and bool(raw_flags[k])]
|
|
1275
|
+
effective_parent = bool(
|
|
1276
|
+
raw_flags.get(
|
|
1277
|
+
parent_field,
|
|
1278
|
+
getattr(get_global_settings(), parent_field),
|
|
1279
|
+
)
|
|
1280
|
+
)
|
|
1281
|
+
if rejected and not effective_parent:
|
|
1282
|
+
return JSONResponse(
|
|
1283
|
+
create_error_response(
|
|
1284
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
1285
|
+
message(rejected),
|
|
1286
|
+
suggestions=suggestions,
|
|
1287
|
+
context={"rejected": rejected},
|
|
1288
|
+
),
|
|
1289
|
+
status_code=409,
|
|
1290
|
+
)
|
|
1291
|
+
return None
|
|
1292
|
+
|
|
1293
|
+
|
|
1251
1294
|
def build_settings_handlers(
|
|
1252
1295
|
server: HomeAssistantSmartMCPServer | None,
|
|
1253
1296
|
*,
|
|
@@ -1943,14 +1986,11 @@ def build_settings_handlers(
|
|
|
1943
1986
|
status_code=400,
|
|
1944
1987
|
)
|
|
1945
1988
|
|
|
1946
|
-
# Master beta-gate check: a sub-flag
|
|
1947
|
-
# when the master ``enable_beta_features`` is on AFTER the
|
|
1948
|
-
#
|
|
1949
|
-
#
|
|
1950
|
-
#
|
|
1951
|
-
# resulting master state would still be off — the runtime gate
|
|
1952
|
-
# would force them False anyway and the user should know the
|
|
1953
|
-
# save was a no-op rather than learning at next startup.
|
|
1989
|
+
# Master beta-gate check: a beta sub-flag may only be enabled
|
|
1990
|
+
# when the master ``enable_beta_features`` is on AFTER the merge
|
|
1991
|
+
# (see ``_reject_child_flags_without_parent`` for the post-merge
|
|
1992
|
+
# derivation and why a parent-off-only save is not rejected). All
|
|
1993
|
+
# offending sub-flags are listed in the rejection.
|
|
1954
1994
|
#
|
|
1955
1995
|
# Applied in BOTH standalone and addon mode.
|
|
1956
1996
|
# The earlier "skip in addon mode" carve-out existed because
|
|
@@ -1959,43 +1999,61 @@ def build_settings_handlers(
|
|
|
1959
1999
|
# one-cycle legacy bridge. On dev addon, start.py writes the
|
|
1960
2000
|
# master env var from the schema-bound options key. On stable
|
|
1961
2001
|
# addon, the master is not in schema and the standalone
|
|
1962
|
-
# web-UI master path remains the gate (the gate read
|
|
2002
|
+
# web-UI master path remains the gate (the gate read
|
|
1963
2003
|
# falls through to the override-file value). Either way the
|
|
1964
2004
|
# gate is sound to apply uniformly.
|
|
1965
2005
|
from ..config import (
|
|
1966
2006
|
BETA_FEATURE_FIELDS as _BETA_SUB,
|
|
1967
2007
|
)
|
|
1968
2008
|
|
|
1969
|
-
|
|
1970
|
-
raw_flags
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
2009
|
+
beta_rejection = _reject_child_flags_without_parent(
|
|
2010
|
+
raw_flags,
|
|
2011
|
+
"enable_beta_features",
|
|
2012
|
+
_BETA_SUB,
|
|
2013
|
+
lambda rejected: (
|
|
2014
|
+
"Cannot enable beta sub-flag(s) "
|
|
2015
|
+
f"{', '.join(rejected)} while the master "
|
|
2016
|
+
"'Enable beta features' toggle is off. Include "
|
|
2017
|
+
"enable_beta_features=true in the same save, or "
|
|
2018
|
+
"flip the master on first."
|
|
2019
|
+
),
|
|
2020
|
+
[
|
|
2021
|
+
"Include enable_beta_features=true in the same save "
|
|
2022
|
+
+ "payload as the sub-flag(s).",
|
|
2023
|
+
"Or turn on the master 'Enable beta features' toggle "
|
|
2024
|
+
+ "first, then enable the sub-flag(s).",
|
|
2025
|
+
],
|
|
1974
2026
|
)
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
2027
|
+
if beta_rejection is not None:
|
|
2028
|
+
return beta_rejection
|
|
2029
|
+
|
|
2030
|
+
# Strict-mandatory-BPS dependency gate: strict mode
|
|
2031
|
+
# (``enable_strict_mandatory_bps``) is a child of
|
|
2032
|
+
# ``enable_mandatory_bps`` and is inert unless the parent is on.
|
|
2033
|
+
# Same post-merge derivation and no-parent-off-rejection as the
|
|
2034
|
+
# beta gate above (see ``_reject_child_flags_without_parent``).
|
|
2035
|
+
strict_rejection = _reject_child_flags_without_parent(
|
|
2036
|
+
raw_flags,
|
|
2037
|
+
"enable_mandatory_bps",
|
|
2038
|
+
("enable_strict_mandatory_bps",),
|
|
2039
|
+
lambda _rejected: (
|
|
2040
|
+
"Cannot enable strict best-practices mode "
|
|
2041
|
+
"('enable_strict_mandatory_bps') while the parent "
|
|
2042
|
+
"'Attach best-practice skills on writes' "
|
|
2043
|
+
"(enable_mandatory_bps) toggle is off. Strict mode is "
|
|
2044
|
+
"a child of that toggle and has no effect without it. "
|
|
2045
|
+
"Include enable_mandatory_bps=true in the same save, or "
|
|
2046
|
+
"turn the parent on first."
|
|
2047
|
+
),
|
|
2048
|
+
[
|
|
2049
|
+
"Include enable_mandatory_bps=true in the same save "
|
|
2050
|
+
+ "payload as enable_strict_mandatory_bps.",
|
|
2051
|
+
"Or turn on the parent 'Attach best-practice skills on "
|
|
2052
|
+
+ "writes' toggle first, then enable strict mode.",
|
|
2053
|
+
],
|
|
2054
|
+
)
|
|
2055
|
+
if strict_rejection is not None:
|
|
2056
|
+
return strict_rejection
|
|
1999
2057
|
|
|
2000
2058
|
# Build the validated override dict. Reject unknown fields and
|
|
2001
2059
|
# env-locked fields up front so the user gets a precise error
|
|
@@ -504,18 +504,40 @@
|
|
|
504
504
|
|
|
505
505
|
.feature-row.beta-master-row { font-weight: 600; padding-top: 20px; border-top: 2px solid var(--border); margin-top: 8px; }
|
|
506
506
|
.feature-row.beta-master-row .feature-help { font-weight: 400; }
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
.feature-row.beta-sub
|
|
510
|
-
.feature-row.
|
|
511
|
-
.feature-row.
|
|
512
|
-
.feature-row.
|
|
513
|
-
.feature-row.
|
|
514
|
-
.feature-row.codemode-sub
|
|
515
|
-
.feature-row.yaml-packages-sub
|
|
516
|
-
.feature-row.
|
|
517
|
-
|
|
518
|
-
|
|
507
|
+
/* Nested sub-flag rows share one guide-bar treatment; only the indent
|
|
508
|
+
depth differs (see the depth-1 / depth-2 blocks below). */
|
|
509
|
+
.feature-row.beta-sub,
|
|
510
|
+
.feature-row.codemode-sub,
|
|
511
|
+
.feature-row.yaml-packages-sub,
|
|
512
|
+
.feature-row.mandatory-bps-sub { position: relative; }
|
|
513
|
+
.feature-row.beta-sub::before,
|
|
514
|
+
.feature-row.codemode-sub::before,
|
|
515
|
+
.feature-row.yaml-packages-sub::before,
|
|
516
|
+
.feature-row.mandatory-bps-sub::before {
|
|
517
|
+
content: ""; position: absolute; top: 0; bottom: 0; width: 2px; background: var(--border);
|
|
518
|
+
}
|
|
519
|
+
.feature-row.beta-sub.dimmed,
|
|
520
|
+
.feature-row.codemode-sub.dimmed,
|
|
521
|
+
.feature-row.yaml-packages-sub.dimmed,
|
|
522
|
+
.feature-row.mandatory-bps-sub.dimmed { opacity: 0.38; }
|
|
523
|
+
.feature-row.beta-sub.dimmed input,
|
|
524
|
+
.feature-row.codemode-sub.dimmed input,
|
|
525
|
+
.feature-row.yaml-packages-sub.dimmed input,
|
|
526
|
+
.feature-row.mandatory-bps-sub.dimmed input { cursor: not-allowed; }
|
|
527
|
+
|
|
528
|
+
/* Depth 1 — nested directly under a top-level toggle (beta-sub under the
|
|
529
|
+
master; strict-mode under the non-beta enable_mandatory_bps row). */
|
|
530
|
+
.feature-row.beta-sub,
|
|
531
|
+
.feature-row.mandatory-bps-sub { padding-left: 32px; }
|
|
532
|
+
.feature-row.beta-sub::before,
|
|
533
|
+
.feature-row.mandatory-bps-sub::before { left: 12px; }
|
|
534
|
+
|
|
535
|
+
/* Depth 2 — nested under a beta-sub parent (yaml packages under
|
|
536
|
+
enable_yaml_config_editing; code-mode numerics under enable_code_mode). */
|
|
537
|
+
.feature-row.codemode-sub,
|
|
538
|
+
.feature-row.yaml-packages-sub { padding-left: 56px; }
|
|
539
|
+
.feature-row.codemode-sub::before,
|
|
540
|
+
.feature-row.yaml-packages-sub::before { left: 36px; }
|
|
519
541
|
|
|
520
542
|
/* ── ADVANCED SETTINGS ───────────────────────────────────────────────── */
|
|
521
543
|
.adv-section-title {
|
|
@@ -1555,6 +1555,10 @@ const FEATURE_META = {
|
|
|
1555
1555
|
label: "Attach best-practice skills on writes",
|
|
1556
1556
|
help: "Master switch for the write-tool skill content delivery feature (issue #1182). When enabled (default), the six config write tools (automations, scripts, scenes, helpers, dashboards, raw YAML) attach the canonical Home Assistant best-practice reference files under skill_content on every successful write, plus auto-embed any reference sections cited by best-practice warnings. Each tool also exposes a per-call MandatoryBPS parameter the agent can set to false on subsequent calls once it has the content. When this master switch is off, NO skill_content goes out regardless of the per-call parameter or BP warnings. Leave on if your LLM benefits from inline guidance; turn off to minimise tokens when using an LLM that has the best-practice files indexed via skills or another retrieval path. Requires restart to take effect.",
|
|
1557
1557
|
},
|
|
1558
|
+
enable_strict_mandatory_bps: {
|
|
1559
|
+
label: "Strict best-practices mode",
|
|
1560
|
+
help: "Strict mode: prevents the client from using the tool until it can prove that it read the best practices. While on, the six best-practice write tools (automations, scripts, scenes, helpers, dashboards, raw YAML) are blocked and return an error directing the client to read the best-practices skill via ha_get_skill_guide and pass back the acknowledgment key it obtains there. Nested under \"Attach best-practice skills on writes\" above and inert while that parent toggle is off. Requires restart to take effect (applies live in standalone HTTP mode).",
|
|
1561
|
+
},
|
|
1558
1562
|
// Master beta toggle — gates the 5 sub-flags below at runtime
|
|
1559
1563
|
// (see config.py:_apply_feature_flag_overrides master gate). UI
|
|
1560
1564
|
// dims sub-rows when this is off and re-renders live on flip.
|
|
@@ -1613,7 +1617,7 @@ let BETA_SUB_FLAGS = new Set();
|
|
|
1613
1617
|
// Sub-flags of ``enable_yaml_config_editing`` (confirm-flow toggle +
|
|
1614
1618
|
// per-key packages gates). They ARE in BETA_SUB_FLAGS (it mirrors
|
|
1615
1619
|
// config.BETA_FEATURE_FIELDS verbatim), but the main render pass skips
|
|
1616
|
-
// them via the includes() guard below, and
|
|
1620
|
+
// them via the includes() guard below, and renderSubFlagRows
|
|
1617
1621
|
// re-renders them nested beneath their parent — so they never appear
|
|
1618
1622
|
// as standalone beta-sub rows.
|
|
1619
1623
|
const YAML_PACKAGES_SUB_FLAGS = [
|
|
@@ -1623,6 +1627,16 @@ const YAML_PACKAGES_SUB_FLAGS = [
|
|
|
1623
1627
|
'enable_yaml_packages_scene',
|
|
1624
1628
|
];
|
|
1625
1629
|
|
|
1630
|
+
// Sub-flag of ``enable_mandatory_bps`` (strict best-practices mode).
|
|
1631
|
+
// Unlike YAML_PACKAGES_SUB_FLAGS this is NON-beta — it is NOT in
|
|
1632
|
+
// BETA_SUB_FLAGS and its only gate is the parent toggle. The main
|
|
1633
|
+
// render pass skips it via the includes() guard below, and
|
|
1634
|
+
// renderSubFlagRows re-renders it nested beneath its parent so
|
|
1635
|
+
// it never appears as a standalone top-level row.
|
|
1636
|
+
const MANDATORY_BPS_SUB_FLAGS = [
|
|
1637
|
+
'enable_strict_mandatory_bps',
|
|
1638
|
+
];
|
|
1639
|
+
|
|
1626
1640
|
// Cached add-on flag. Each settings endpoint (/api/settings/features,
|
|
1627
1641
|
// /api/settings/advanced, /api/settings/backup-config) returns
|
|
1628
1642
|
// ``is_addon`` so the env-locked banner copy can adapt — the addon
|
|
@@ -1733,9 +1747,12 @@ function renderFeatureFlags(flags) {
|
|
|
1733
1747
|
const f = flags[fieldName];
|
|
1734
1748
|
if (!f) return;
|
|
1735
1749
|
// Skip yaml-packages sub-rows in the main pass — they're rendered
|
|
1736
|
-
// by
|
|
1750
|
+
// by renderSubFlagRows below right after their parent so
|
|
1737
1751
|
// the nesting reads in source order.
|
|
1738
1752
|
if (YAML_PACKAGES_SUB_FLAGS.includes(fieldName)) return;
|
|
1753
|
+
// Same for the strict-mode sub-row — rendered by
|
|
1754
|
+
// renderSubFlagRows right after its enable_mandatory_bps parent.
|
|
1755
|
+
if (MANDATORY_BPS_SUB_FLAGS.includes(fieldName)) return;
|
|
1739
1756
|
const meta = FEATURE_META[fieldName];
|
|
1740
1757
|
const isMaster = fieldName === 'enable_beta_features';
|
|
1741
1758
|
const isBetaSub = BETA_SUB_FLAGS.has(fieldName);
|
|
@@ -1820,6 +1837,18 @@ function renderFeatureFlags(flags) {
|
|
|
1820
1837
|
renderFeatureFlags(_lastFeatureFlags);
|
|
1821
1838
|
}
|
|
1822
1839
|
}
|
|
1840
|
+
// Re-render on enable_mandatory_bps flip so the strict-mode
|
|
1841
|
+
// sub-row dims/undims immediately. Same live-cache pattern as
|
|
1842
|
+
// the master and yaml-config flips above.
|
|
1843
|
+
if (fieldName === 'enable_mandatory_bps') {
|
|
1844
|
+
if (_lastFeatureFlags[fieldName]) {
|
|
1845
|
+
_lastFeatureFlags[fieldName] = {
|
|
1846
|
+
..._lastFeatureFlags[fieldName],
|
|
1847
|
+
value: input.checked,
|
|
1848
|
+
};
|
|
1849
|
+
renderFeatureFlags(_lastFeatureFlags);
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1823
1852
|
saveFeatureFlag(fieldName, input.checked);
|
|
1824
1853
|
});
|
|
1825
1854
|
const slider = document.createElement('span');
|
|
@@ -1864,7 +1893,20 @@ function renderFeatureFlags(flags) {
|
|
|
1864
1893
|
// itself is off.
|
|
1865
1894
|
if (fieldName === 'enable_yaml_config_editing') {
|
|
1866
1895
|
const parentOn = !!f.value;
|
|
1867
|
-
|
|
1896
|
+
renderSubFlagRows(flags, targetBody, YAML_PACKAGES_SUB_FLAGS, {
|
|
1897
|
+
cssClass: 'yaml-packages-sub',
|
|
1898
|
+
lockedByGate: !masterOn || !parentOn,
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
// After the enable_mandatory_bps parent row, inject its strict-mode
|
|
1902
|
+
// sub-row. This whole group is non-beta, so the only gate is the
|
|
1903
|
+
// parent toggle (no master beta involved).
|
|
1904
|
+
if (fieldName === 'enable_mandatory_bps') {
|
|
1905
|
+
const parentOn = !!f.value;
|
|
1906
|
+
renderSubFlagRows(flags, targetBody, MANDATORY_BPS_SUB_FLAGS, {
|
|
1907
|
+
cssClass: 'mandatory-bps-sub',
|
|
1908
|
+
lockedByGate: !parentOn,
|
|
1909
|
+
});
|
|
1868
1910
|
}
|
|
1869
1911
|
// After the enable_filesystem_tools row, inject the custom-directories
|
|
1870
1912
|
// editor (issue #1567). Dimmed when either the master beta is off or
|
|
@@ -1877,14 +1919,23 @@ function renderFeatureFlags(flags) {
|
|
|
1877
1919
|
});
|
|
1878
1920
|
}
|
|
1879
1921
|
|
|
1880
|
-
|
|
1881
|
-
|
|
1922
|
+
// Shared renderer for bool sub-flag rows nested under a parent toggle
|
|
1923
|
+
// (yaml-packages under enable_yaml_config_editing, strict mode under
|
|
1924
|
+
// enable_mandatory_bps). Each row is a checkbox+slider whose change
|
|
1925
|
+
// handler syncs _lastFeatureFlags and POSTs via saveFeatureFlag, dimmed
|
|
1926
|
+
// + input-disabled when ``lockedByGate`` is true. Callers precompute
|
|
1927
|
+
// lockedByGate from whatever gates apply to their group (yaml-packages:
|
|
1928
|
+
// master beta AND parent; mandatory-bps: parent only) and pass the CSS
|
|
1929
|
+
// class that carries the group's indent/guide-bar depth. The number/text
|
|
1930
|
+
// code-mode sub-numerics are NOT rendered here — they save through
|
|
1931
|
+
// commitAdvancedEdit and live in renderCodeModeSubRows.
|
|
1932
|
+
function renderSubFlagRows(flags, parentEl, subFieldNames, { cssClass, lockedByGate }) {
|
|
1933
|
+
subFieldNames.forEach(fieldName => {
|
|
1882
1934
|
const f = flags[fieldName];
|
|
1883
1935
|
if (!f) return;
|
|
1884
1936
|
const meta = FEATURE_META[fieldName] || { label: fieldName, help: '' };
|
|
1885
|
-
const lockedByGate = !masterOn || !parentOn;
|
|
1886
1937
|
const row = document.createElement('div');
|
|
1887
|
-
row.className = 'feature-row
|
|
1938
|
+
row.className = 'feature-row ' + cssClass + (lockedByGate ? ' dimmed' : '');
|
|
1888
1939
|
|
|
1889
1940
|
const info = document.createElement('div');
|
|
1890
1941
|
info.className = 'feature-info';
|
|
@@ -1900,7 +1951,7 @@ function renderYamlPackagesSubRows(flags, parentEl, masterOn, parentOn) {
|
|
|
1900
1951
|
`${escapeHtml(ORIGIN_INFO_NOTE[f.origin])}</div>`
|
|
1901
1952
|
: '';
|
|
1902
1953
|
info.innerHTML =
|
|
1903
|
-
`<div class="feature-name">${escapeHtml(meta.label)}</div>` +
|
|
1954
|
+
`<div class="feature-name" id="label-feature-${fieldName}">${escapeHtml(meta.label)}</div>` +
|
|
1904
1955
|
`<div class="feature-help">${escapeHtml(meta.help)}</div>` +
|
|
1905
1956
|
lockedNote + infoNote;
|
|
1906
1957
|
|
|
@@ -1913,6 +1964,7 @@ function renderYamlPackagesSubRows(flags, parentEl, masterOn, parentOn) {
|
|
|
1913
1964
|
input.name = 'feature:' + fieldName;
|
|
1914
1965
|
input.checked = !!f.value;
|
|
1915
1966
|
input.disabled = !f.editable || lockedByGate;
|
|
1967
|
+
input.setAttribute('aria-labelledby', 'label-feature-' + fieldName);
|
|
1916
1968
|
input.addEventListener('change', () => {
|
|
1917
1969
|
// Keep the cached flag value in sync (parity with the parent/master
|
|
1918
1970
|
// row handlers) so a later parent flip — which re-renders from
|
|
@@ -1980,7 +2032,7 @@ function renderCodeModeSubRows(parentEl, masterOn, codeModeOn) {
|
|
|
1980
2032
|
`<div class="feature-locked-note">${envLockedNoteHtml(f.env_var, f.field)}</div>`;
|
|
1981
2033
|
}
|
|
1982
2034
|
info.innerHTML =
|
|
1983
|
-
`<div class="feature-name">${escapeHtml(meta.label)}</div>` +
|
|
2035
|
+
`<div class="feature-name" id="label-feature-${f.field}">${escapeHtml(meta.label)}</div>` +
|
|
1984
2036
|
`<div class="feature-help">${escapeHtml(meta.help)}</div>` +
|
|
1985
2037
|
lockedNote;
|
|
1986
2038
|
|
|
@@ -2003,6 +2055,7 @@ function renderCodeModeSubRows(parentEl, masterOn, codeModeOn) {
|
|
|
2003
2055
|
inputEl.disabled = disabled;
|
|
2004
2056
|
inputEl.dataset.advField = f.field;
|
|
2005
2057
|
inputEl.name = 'adv:' + f.field;
|
|
2058
|
+
inputEl.setAttribute('aria-labelledby', 'label-feature-' + f.field);
|
|
2006
2059
|
inputEl.addEventListener('change', () => {
|
|
2007
2060
|
let v;
|
|
2008
2061
|
if (f.type === 'int') v = parseInt(inputEl.value, 10);
|