ha-mcp-dev 7.6.0.dev621__tar.gz → 7.6.0.dev623__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.6.0.dev621/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.6.0.dev623}/PKG-INFO +1 -1
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/pyproject.toml +1 -1
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/config.py +73 -8
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/server.py +28 -10
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/settings_ui.py +166 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/best_practice_checker.py +265 -129
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_config_automations.py +84 -30
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_config_dashboards.py +61 -10
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_config_helpers.py +60 -13
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_config_scenes.py +44 -6
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_config_scripts.py +50 -9
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_filesystem.py +1 -1
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_yaml_config.py +119 -7
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/util_helpers.py +285 -0
- ha_mcp_dev-7.6.0.dev623/src/ha_mcp/utils/skill_loader.py +271 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/LICENSE +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/README.md +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/setup.cfg +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/smart_search.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.6.0.dev621 → ha_mcp_dev-7.6.0.dev623}/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.6.0.
|
|
7
|
+
version = "7.6.0.dev623"
|
|
8
8
|
description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13,<3.14"
|
|
@@ -110,7 +110,7 @@ class Settings(BaseSettings):
|
|
|
110
110
|
|
|
111
111
|
# Master beta-features toggle. UI-only — intentionally not in any
|
|
112
112
|
# addon config.yaml schema. Consumed by the master gate in
|
|
113
|
-
# ``_apply_feature_flag_overrides``, which force-sets the
|
|
113
|
+
# ``_apply_feature_flag_overrides``, which force-sets the
|
|
114
114
|
# ``BETA_FEATURE_FIELDS`` sub-flags to False whenever this master is
|
|
115
115
|
# off. Dev addon ``start.py`` auto-writes ``ENABLE_BETA_FEATURES=true``
|
|
116
116
|
# whenever any beta sub-flag key is present in ``/data/options.json``
|
|
@@ -122,6 +122,27 @@ class Settings(BaseSettings):
|
|
|
122
122
|
# files. Disabled by default; only for YAML-only features with no UI/API path.
|
|
123
123
|
enable_yaml_config_editing: bool = Field(False, alias="ENABLE_YAML_CONFIG_EDITING")
|
|
124
124
|
|
|
125
|
+
# Per-key gates for ``automation`` / ``script`` / ``scene`` under
|
|
126
|
+
# ``packages/*.yaml``. The custom component accepts these three
|
|
127
|
+
# PACKAGES_ONLY_YAML_KEYS unconditionally; ha-mcp's UI exposes a
|
|
128
|
+
# toggle per key so an operator who wants YAML-managed
|
|
129
|
+
# automations/scripts/scenes in packages but not the others can
|
|
130
|
+
# narrow the surface. ha_config_set_yaml rejects packages/*.yaml
|
|
131
|
+
# writes for a disabled key client-side, and passes the disabled set
|
|
132
|
+
# to the custom component so the underlying service rejects too
|
|
133
|
+
# (writes of these keys to configuration.yaml are rejected
|
|
134
|
+
# independently of these flags). Each
|
|
135
|
+
# toggle is meaningful only when ``enable_yaml_config_editing`` is
|
|
136
|
+
# on; the UI nests these rows under that parent and dims them when
|
|
137
|
+
# the parent is off.
|
|
138
|
+
enable_yaml_packages_automation: bool = Field(
|
|
139
|
+
False, alias="ENABLE_YAML_PACKAGES_AUTOMATION"
|
|
140
|
+
)
|
|
141
|
+
enable_yaml_packages_script: bool = Field(
|
|
142
|
+
False, alias="ENABLE_YAML_PACKAGES_SCRIPT"
|
|
143
|
+
)
|
|
144
|
+
enable_yaml_packages_scene: bool = Field(False, alias="ENABLE_YAML_PACKAGES_SCENE")
|
|
145
|
+
|
|
125
146
|
# Seed values for tool visibility (comma-separated tool names).
|
|
126
147
|
# Used as initial config when no tool_config.json exists.
|
|
127
148
|
# The web settings UI (/settings) is the primary interface for managing these.
|
|
@@ -144,6 +165,18 @@ class Settings(BaseSettings):
|
|
|
144
165
|
# env-var users see the trade-off in their logs.
|
|
145
166
|
enable_lite_docstrings: bool = Field(False, alias="ENABLE_LITE_DOCSTRINGS")
|
|
146
167
|
|
|
168
|
+
# Mandatory best-practice skills — server-side master switch for the
|
|
169
|
+
# write-tool skill_content delivery feature (issue #1182). When True
|
|
170
|
+
# (default), the six write tools (automations / scripts / scenes /
|
|
171
|
+
# helpers / dashboards / yaml) attach the canonical best-practice
|
|
172
|
+
# reference files under ``skill_content`` on every successful write,
|
|
173
|
+
# plus auto-embed any sections cited by best-practice warnings. The
|
|
174
|
+
# per-call ``MandatoryBPS`` parameter on each tool controls whether
|
|
175
|
+
# the canonical files ship for that one call. This setting is the
|
|
176
|
+
# master gate above that — when False, NO skill_content goes out
|
|
177
|
+
# regardless of the per-call param or BP warnings. Default on.
|
|
178
|
+
enable_mandatory_bps: bool = Field(True, alias="ENABLE_MANDATORY_BPS")
|
|
179
|
+
|
|
147
180
|
# Filesystem tools — read/write/delete/list under the HA config dir.
|
|
148
181
|
# Previously gated by a direct ``os.getenv`` call in
|
|
149
182
|
# ``tools/tools_filesystem.py`` so callers (and the settings UI)
|
|
@@ -412,7 +445,28 @@ FEATURE_FLAG_FIELDS: tuple[FeatureFlagField, ...] = (
|
|
|
412
445
|
FeatureFlagField(
|
|
413
446
|
"enable_tool_security_policies", "ENABLE_TOOL_SECURITY_POLICIES", bool
|
|
414
447
|
),
|
|
448
|
+
# Non-beta, default-ON master switch for write-tool skill_content
|
|
449
|
+
# delivery (#1182). Grouped with the non-beta flags above the beta
|
|
450
|
+
# run below; intentionally NOT in BETA_FEATURE_FIELDS (it must not be
|
|
451
|
+
# gated by the beta master) nor in ADVANCED_SETTINGS_FIELDS (registries
|
|
452
|
+
# are name-disjoint per _validate_registries()).
|
|
453
|
+
FeatureFlagField("enable_mandatory_bps", "ENABLE_MANDATORY_BPS", bool),
|
|
415
454
|
FeatureFlagField("enable_yaml_config_editing", "ENABLE_YAML_CONFIG_EDITING", bool),
|
|
455
|
+
# Per-key sub-gates beneath enable_yaml_config_editing. Nested in
|
|
456
|
+
# the UI, dimmed when the parent is off. Also listed in
|
|
457
|
+
# BETA_FEATURE_FIELDS so they follow the same master-gate +
|
|
458
|
+
# addon-mode override path as the other beta flags — that is what
|
|
459
|
+
# makes the web-UI toggle take effect on the stable add-on (where
|
|
460
|
+
# they are not in config.yaml). See that tuple for the rationale.
|
|
461
|
+
FeatureFlagField(
|
|
462
|
+
"enable_yaml_packages_automation",
|
|
463
|
+
"ENABLE_YAML_PACKAGES_AUTOMATION",
|
|
464
|
+
bool,
|
|
465
|
+
),
|
|
466
|
+
FeatureFlagField(
|
|
467
|
+
"enable_yaml_packages_script", "ENABLE_YAML_PACKAGES_SCRIPT", bool
|
|
468
|
+
),
|
|
469
|
+
FeatureFlagField("enable_yaml_packages_scene", "ENABLE_YAML_PACKAGES_SCENE", bool),
|
|
416
470
|
FeatureFlagField("enable_lite_docstrings", "ENABLE_LITE_DOCSTRINGS", bool),
|
|
417
471
|
FeatureFlagField("enable_filesystem_tools", "HAMCP_ENABLE_FILESYSTEM_TOOLS", bool),
|
|
418
472
|
FeatureFlagField(
|
|
@@ -449,6 +503,16 @@ _FEATURE_FLAG_INT_BOUNDS: dict[str, tuple[int, int]] = {
|
|
|
449
503
|
# gate, never by the per-field iteration.
|
|
450
504
|
BETA_FEATURE_FIELDS: tuple[str, ...] = (
|
|
451
505
|
"enable_yaml_config_editing",
|
|
506
|
+
# Per-key sub-gates of enable_yaml_config_editing. Included here so
|
|
507
|
+
# they ride the same master gate + addon-mode override path as the
|
|
508
|
+
# other beta flags. Without this, the addon-mode short-circuit in
|
|
509
|
+
# ``_apply_feature_flag_overrides`` (and the ``get_feature_flag_origin``
|
|
510
|
+
# logic) would leave them dead on the stable add-on — reachable only
|
|
511
|
+
# via the dev add-on's config.yaml options. They still render NESTED
|
|
512
|
+
# under their parent in the web UI (not as separate beta-sub rows).
|
|
513
|
+
"enable_yaml_packages_automation",
|
|
514
|
+
"enable_yaml_packages_script",
|
|
515
|
+
"enable_yaml_packages_scene",
|
|
452
516
|
"enable_filesystem_tools",
|
|
453
517
|
"enable_custom_component_integration",
|
|
454
518
|
"enable_code_mode",
|
|
@@ -461,8 +525,9 @@ BETA_FEATURE_FIELDS: tuple[str, ...] = (
|
|
|
461
525
|
#
|
|
462
526
|
# - ``section`` groups fields in the Advanced section of the Server Settings
|
|
463
527
|
# tab: "connection", "search", "operations", "diagnostics", "tools_surface".
|
|
464
|
-
# The
|
|
465
|
-
# the UI renders below the Advanced section
|
|
528
|
+
# The beta sub-flags + the master live in a separate "beta" section that
|
|
529
|
+
# the UI renders below the Advanced section (the per-key yaml-packages
|
|
530
|
+
# sub-flags render nested under enable_yaml_config_editing within it).
|
|
466
531
|
# - ``editable=False`` marks display-only rows. Connection fields are
|
|
467
532
|
# non-editable from the running server (chicken-and-egg footgun);
|
|
468
533
|
# ``MCP_SERVER_VERSION`` is editable (it has an env alias) but the UI
|
|
@@ -613,7 +678,7 @@ def get_feature_flag_origin(env_name: str) -> str:
|
|
|
613
678
|
schema, ``start.py`` doesn't write the env var, and the master
|
|
614
679
|
falls through to env / file / default precedence so the
|
|
615
680
|
standalone web UI master path remains the gate.
|
|
616
|
-
- The
|
|
681
|
+
- The ``BETA_FEATURE_FIELDS`` (sub-flags) follow the same
|
|
617
682
|
shape — present in dev addon schema, absent from stable. Same
|
|
618
683
|
env-var-presence signal distinguishes them at runtime.
|
|
619
684
|
"""
|
|
@@ -632,8 +697,8 @@ def get_feature_flag_origin(env_name: str) -> str:
|
|
|
632
697
|
# Stable addon: env var never written → fall through to
|
|
633
698
|
# file/default. The master moved from "never schema-bound"
|
|
634
699
|
# to "schema-bound on dev only"; the same env-var-presence
|
|
635
|
-
# signal now distinguishes both for the master and the
|
|
636
|
-
# sub-flags.
|
|
700
|
+
# signal now distinguishes both for the master and the
|
|
701
|
+
# beta sub-flags.
|
|
637
702
|
if os.environ.get(env_name) is not None:
|
|
638
703
|
return "addon"
|
|
639
704
|
# else: stable / legacy-dev-no-master-key, fall through.
|
|
@@ -730,7 +795,7 @@ def _apply_feature_flag_overrides(settings: "Settings") -> None:
|
|
|
730
795
|
|
|
731
796
|
EXCEPTION: the beta-master + beta-sub-flag fields skip the
|
|
732
797
|
addon-mode short-circuit. The master isn't in any addon schema;
|
|
733
|
-
the
|
|
798
|
+
the sub-flags are in the dev-addon schema (where ``start.py``
|
|
734
799
|
writes the env var from options.json — env-var-wins skips the
|
|
735
800
|
file read here, leaving Supervisor authoritative) but NOT in the
|
|
736
801
|
stable schema (where the env var is never written, so the file
|
|
@@ -738,7 +803,7 @@ def _apply_feature_flag_overrides(settings: "Settings") -> None:
|
|
|
738
803
|
|
|
739
804
|
2. **Beta master gate**: after the per-field pass, if
|
|
740
805
|
``enable_beta_features`` is False on the resolved Settings,
|
|
741
|
-
force-set the
|
|
806
|
+
force-set the BETA_FEATURE_FIELDS to False regardless of
|
|
742
807
|
how they currently look. This is the "master toggle" semantics —
|
|
743
808
|
even a power user who sets ENABLE_YAML_CONFIG_EDITING=true via
|
|
744
809
|
env var still needs to flip the master before the flag takes
|
|
@@ -200,10 +200,13 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
200
200
|
|
|
201
201
|
Skills are vendored via a git submodule at resources/skills-vendor/.
|
|
202
202
|
The actual skill directories live under the skills/ subdirectory
|
|
203
|
-
within that repo.
|
|
203
|
+
within that repo. Delegates to
|
|
204
|
+
:func:`ha_mcp.utils.skill_loader.get_skills_dir` so the write-tool
|
|
205
|
+
``MandatoryBPS`` parameter resolves the same path.
|
|
204
206
|
"""
|
|
205
|
-
|
|
206
|
-
|
|
207
|
+
from .utils.skill_loader import get_skills_dir
|
|
208
|
+
|
|
209
|
+
return get_skills_dir()
|
|
207
210
|
|
|
208
211
|
def _build_skills_instructions(self) -> str | None:
|
|
209
212
|
"""Build server instructions from bundled skill frontmatter.
|
|
@@ -1434,13 +1437,28 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
1434
1437
|
)
|
|
1435
1438
|
)
|
|
1436
1439
|
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1440
|
+
# Hint goes at the top of the response so the LLM sees it before
|
|
1441
|
+
# parsing the (potentially large) content body. Scoped to the
|
|
1442
|
+
# best-practice skill because that's the one the write-tool
|
|
1443
|
+
# MandatoryBPS param gates; other skills (if any) are unrelated.
|
|
1444
|
+
from .tools.util_helpers import (
|
|
1445
|
+
_HA_BEST_PRACTICES_SKILL_NAME,
|
|
1446
|
+
_SKILL_GUIDE_MANDATORYBPS_HINT,
|
|
1447
|
+
)
|
|
1448
|
+
|
|
1449
|
+
response: dict[str, Any] = {}
|
|
1450
|
+
if skill == _HA_BEST_PRACTICES_SKILL_NAME:
|
|
1451
|
+
response["skill_content_hint"] = _SKILL_GUIDE_MANDATORYBPS_HINT
|
|
1452
|
+
response.update(
|
|
1453
|
+
{
|
|
1454
|
+
"success": True,
|
|
1455
|
+
"skill": skill,
|
|
1456
|
+
"file": file,
|
|
1457
|
+
"uri": f"skill://{skill}/{file}",
|
|
1458
|
+
"content": content,
|
|
1459
|
+
}
|
|
1460
|
+
)
|
|
1461
|
+
return response
|
|
1444
1462
|
|
|
1445
1463
|
# Helper methods required by EnhancedToolsMixin
|
|
1446
1464
|
|
|
@@ -814,6 +814,15 @@ _SETTINGS_HTML = (
|
|
|
814
814
|
left: 36px; top: 0; bottom: 0; width: 2px; background: var(--border); }
|
|
815
815
|
.feature-row.codemode-sub.dimmed { opacity: 0.55; }
|
|
816
816
|
.feature-row.codemode-sub.dimmed input { cursor: not-allowed; }
|
|
817
|
+
/* YAML packages per-key sub-rows — second-level nested under
|
|
818
|
+
enable_yaml_config_editing. Same dimming logic as codemode-sub
|
|
819
|
+
but gated by enable_yaml_config_editing (the parent flag) rather
|
|
820
|
+
than enable_code_mode. */
|
|
821
|
+
.feature-row.yaml-packages-sub { padding-left: 56px; position: relative; }
|
|
822
|
+
.feature-row.yaml-packages-sub::before { content: ""; position: absolute;
|
|
823
|
+
left: 36px; top: 0; bottom: 0; width: 2px; background: var(--border); }
|
|
824
|
+
.feature-row.yaml-packages-sub.dimmed { opacity: 0.55; }
|
|
825
|
+
.feature-row.yaml-packages-sub.dimmed input { cursor: not-allowed; }
|
|
817
826
|
/* Advanced settings sections — one row per
|
|
818
827
|
ADVANCED_SETTINGS_FIELDS entry, grouped by section. Visually
|
|
819
828
|
matches the .feature-row treatment so the Server Settings tab
|
|
@@ -2165,6 +2174,10 @@ const FEATURE_META = {
|
|
|
2165
2174
|
label: "Enable Tool Security Policies",
|
|
2166
2175
|
help: "Opt-in middleware that gates high-stakes MCP tool calls behind user approval. When enabled, tools that match a rule in the Tool Security Policies tab require you to click Approve in the web UI before they run. Off by default. Per-tool rules with optional argument conditions are configured in the Tool Security Policies tab. Requires restart to take effect.",
|
|
2167
2176
|
},
|
|
2177
|
+
enable_mandatory_bps: {
|
|
2178
|
+
label: "Attach best-practice skills on writes",
|
|
2179
|
+
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.",
|
|
2180
|
+
},
|
|
2168
2181
|
// Master beta toggle — gates the 5 sub-flags below at runtime
|
|
2169
2182
|
// (see config.py:_apply_feature_flag_overrides master gate). UI
|
|
2170
2183
|
// dims sub-rows when this is off and re-renders live on flip.
|
|
@@ -2176,6 +2189,18 @@ const FEATURE_META = {
|
|
|
2176
2189
|
label: "Enable YAML config editing (beta)",
|
|
2177
2190
|
help: "Beta feature — disabled by default. Allows AI assistants to add, replace, or remove top-level keys in configuration.yaml and packages/*.yaml. Only whitelisted keys are allowed (e.g., template, sensor, command_line, mqtt, knx); core keys like homeassistant, http, and recorder are blocked. Each edit validates YAML syntax, runs a config check, and creates an automatic backup. Changes to most keys require a full HA restart to take effect. See docs/beta.md for known limitations. Dedicated tools (automations, scripts, scenes, helpers, template sensors) should be preferred when available.",
|
|
2178
2191
|
},
|
|
2192
|
+
enable_yaml_packages_automation: {
|
|
2193
|
+
label: "Allow automation in packages/*.yaml",
|
|
2194
|
+
help: "Sub-toggle of YAML config editing. When on, ha_config_set_yaml accepts yaml_path='automation' inside packages/*.yaml. When off, the wrapper rejects the call client-side AND the custom component rejects it server-side. Storage-mode tools (ha_config_set_automation) cover the UI-managed path and are unaffected. Disabled by default.",
|
|
2195
|
+
},
|
|
2196
|
+
enable_yaml_packages_script: {
|
|
2197
|
+
label: "Allow script in packages/*.yaml",
|
|
2198
|
+
help: "Sub-toggle of YAML config editing. When on, ha_config_set_yaml accepts yaml_path='script' inside packages/*.yaml. When off, the wrapper rejects the call client-side AND the custom component rejects it server-side. Storage-mode tools (ha_config_set_script) cover the UI-managed path and are unaffected. Disabled by default.",
|
|
2199
|
+
},
|
|
2200
|
+
enable_yaml_packages_scene: {
|
|
2201
|
+
label: "Allow scene in packages/*.yaml",
|
|
2202
|
+
help: "Sub-toggle of YAML config editing. When on, ha_config_set_yaml accepts yaml_path='scene' inside packages/*.yaml. When off, the wrapper rejects the call client-side AND the custom component rejects it server-side. Storage-mode tools (ha_config_set_scene) cover the UI-managed path and are unaffected. Disabled by default.",
|
|
2203
|
+
},
|
|
2179
2204
|
enable_filesystem_tools: {
|
|
2180
2205
|
label: "Enable filesystem tools (beta)",
|
|
2181
2206
|
help: "Sets HAMCP_ENABLE_FILESYSTEM_TOOLS=true. Enables direct file read/write access to your Home Assistant filesystem. WARNING: This gives the MCP server sensitive direct file access to your system. Only enable if you trust the AI assistant with file operations. Requires restart to take effect.",
|
|
@@ -2200,6 +2225,16 @@ const FEATURE_META = {
|
|
|
2200
2225
|
// ``config.BETA_FEATURE_FIELDS`` without duplicating the name list here.
|
|
2201
2226
|
let BETA_SUB_FLAGS = new Set();
|
|
2202
2227
|
|
|
2228
|
+
// Sub-flags of ``enable_yaml_config_editing``. Rendered nested beneath
|
|
2229
|
+
// the parent in renderFeatureFlags so the dependency is visually
|
|
2230
|
+
// obvious. They are NOT in BETA_SUB_FLAGS — the parent is the gate,
|
|
2231
|
+
// and the master-off → parent-off cascade transitively covers them.
|
|
2232
|
+
const YAML_PACKAGES_SUB_FLAGS = [
|
|
2233
|
+
'enable_yaml_packages_automation',
|
|
2234
|
+
'enable_yaml_packages_script',
|
|
2235
|
+
'enable_yaml_packages_scene',
|
|
2236
|
+
];
|
|
2237
|
+
|
|
2203
2238
|
// Cached add-on flag. Each settings endpoint (/api/settings/features,
|
|
2204
2239
|
// /api/settings/advanced, /api/settings/backup-config) returns
|
|
2205
2240
|
// ``is_addon`` so the env-locked banner copy can adapt — the addon
|
|
@@ -2309,6 +2344,10 @@ function renderFeatureFlags(flags) {
|
|
|
2309
2344
|
Object.keys(FEATURE_META).forEach(fieldName => {
|
|
2310
2345
|
const f = flags[fieldName];
|
|
2311
2346
|
if (!f) return;
|
|
2347
|
+
// Skip yaml-packages sub-rows in the main pass — they're rendered
|
|
2348
|
+
// by renderYamlPackagesSubRows below right after their parent so
|
|
2349
|
+
// the nesting reads in source order.
|
|
2350
|
+
if (YAML_PACKAGES_SUB_FLAGS.includes(fieldName)) return;
|
|
2312
2351
|
const meta = FEATURE_META[fieldName];
|
|
2313
2352
|
const isMaster = fieldName === 'enable_beta_features';
|
|
2314
2353
|
const isBetaSub = BETA_SUB_FLAGS.has(fieldName);
|
|
@@ -2377,6 +2416,20 @@ function renderFeatureFlags(flags) {
|
|
|
2377
2416
|
renderFeatureFlags(_lastFeatureFlags);
|
|
2378
2417
|
}
|
|
2379
2418
|
}
|
|
2419
|
+
// Re-render on enable_yaml_config_editing flip so the 3
|
|
2420
|
+
// packages sub-rows dim/undim immediately. Same pattern as the
|
|
2421
|
+
// master flip above — value is mutated in the live cache and
|
|
2422
|
+
// the panel re-renders synchronously while the save POST runs
|
|
2423
|
+
// in the background.
|
|
2424
|
+
if (fieldName === 'enable_yaml_config_editing') {
|
|
2425
|
+
if (_lastFeatureFlags[fieldName]) {
|
|
2426
|
+
_lastFeatureFlags[fieldName] = {
|
|
2427
|
+
..._lastFeatureFlags[fieldName],
|
|
2428
|
+
value: input.checked,
|
|
2429
|
+
};
|
|
2430
|
+
renderFeatureFlags(_lastFeatureFlags);
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2380
2433
|
saveFeatureFlag(fieldName, input.checked);
|
|
2381
2434
|
});
|
|
2382
2435
|
const slider = document.createElement('span');
|
|
@@ -2413,6 +2466,74 @@ function renderFeatureFlags(flags) {
|
|
|
2413
2466
|
const codeModeOn = !!f.value;
|
|
2414
2467
|
renderCodeModeSubRows(targetBody, masterOn, codeModeOn);
|
|
2415
2468
|
}
|
|
2469
|
+
// After rendering the enable_yaml_config_editing parent, inject
|
|
2470
|
+
// its 3 per-key sub-rows (automation/script/scene). Dimmed when
|
|
2471
|
+
// either the master beta is off (parent forced off) or the parent
|
|
2472
|
+
// itself is off.
|
|
2473
|
+
if (fieldName === 'enable_yaml_config_editing') {
|
|
2474
|
+
const parentOn = !!f.value;
|
|
2475
|
+
renderYamlPackagesSubRows(flags, targetBody, masterOn, parentOn);
|
|
2476
|
+
}
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
function renderYamlPackagesSubRows(flags, parentEl, masterOn, parentOn) {
|
|
2481
|
+
YAML_PACKAGES_SUB_FLAGS.forEach(fieldName => {
|
|
2482
|
+
const f = flags[fieldName];
|
|
2483
|
+
if (!f) return;
|
|
2484
|
+
const meta = FEATURE_META[fieldName] || { label: fieldName, help: '' };
|
|
2485
|
+
const lockedByGate = !masterOn || !parentOn;
|
|
2486
|
+
const row = document.createElement('div');
|
|
2487
|
+
row.className = 'feature-row yaml-packages-sub' + (lockedByGate ? ' dimmed' : '');
|
|
2488
|
+
|
|
2489
|
+
const info = document.createElement('div');
|
|
2490
|
+
info.className = 'feature-info';
|
|
2491
|
+
const lockedNote = !f.editable
|
|
2492
|
+
? `<div class="feature-locked-note">` +
|
|
2493
|
+
(f.origin === 'env'
|
|
2494
|
+
? envLockedNoteHtml(f.env_var, fieldName)
|
|
2495
|
+
: escapeHtml(ORIGIN_LOCKED_NOTE[f.origin] || '')) +
|
|
2496
|
+
`</div>`
|
|
2497
|
+
: '';
|
|
2498
|
+
const infoNote = f.editable && ORIGIN_INFO_NOTE[f.origin]
|
|
2499
|
+
? `<div class="feature-locked-note">` +
|
|
2500
|
+
`${escapeHtml(ORIGIN_INFO_NOTE[f.origin])}</div>`
|
|
2501
|
+
: '';
|
|
2502
|
+
info.innerHTML =
|
|
2503
|
+
`<div class="feature-name">${escapeHtml(meta.label)}</div>` +
|
|
2504
|
+
`<div class="feature-help">${escapeHtml(meta.help)}</div>` +
|
|
2505
|
+
lockedNote + infoNote;
|
|
2506
|
+
|
|
2507
|
+
const control = document.createElement('div');
|
|
2508
|
+
control.className = 'feature-control';
|
|
2509
|
+
const label = document.createElement('label');
|
|
2510
|
+
label.className = 'switch';
|
|
2511
|
+
const input = document.createElement('input');
|
|
2512
|
+
input.type = 'checkbox';
|
|
2513
|
+
input.checked = !!f.value;
|
|
2514
|
+
input.disabled = !f.editable || lockedByGate;
|
|
2515
|
+
input.addEventListener('change', () => {
|
|
2516
|
+
// Keep the cached flag value in sync (parity with the parent/master
|
|
2517
|
+
// row handlers) so a later parent flip — which re-renders from
|
|
2518
|
+
// _lastFeatureFlags — reflects this sub-row's current state rather
|
|
2519
|
+
// than a stale value.
|
|
2520
|
+
if (_lastFeatureFlags[fieldName]) {
|
|
2521
|
+
_lastFeatureFlags[fieldName] = {
|
|
2522
|
+
..._lastFeatureFlags[fieldName],
|
|
2523
|
+
value: input.checked,
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
saveFeatureFlag(fieldName, input.checked);
|
|
2527
|
+
});
|
|
2528
|
+
const slider = document.createElement('span');
|
|
2529
|
+
slider.className = 'slider';
|
|
2530
|
+
label.appendChild(input);
|
|
2531
|
+
label.appendChild(slider);
|
|
2532
|
+
control.appendChild(label);
|
|
2533
|
+
|
|
2534
|
+
row.appendChild(info);
|
|
2535
|
+
row.appendChild(control);
|
|
2536
|
+
parentEl.appendChild(row);
|
|
2416
2537
|
});
|
|
2417
2538
|
}
|
|
2418
2539
|
|
|
@@ -4186,6 +4307,11 @@ def build_settings_handlers(
|
|
|
4186
4307
|
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
4187
4308
|
f"Refusing to flip env-pinned tools: {', '.join(rejected)}. "
|
|
4188
4309
|
"Unset DISABLED_TOOLS / PINNED_TOOLS first.",
|
|
4310
|
+
suggestions=[
|
|
4311
|
+
"Unset the DISABLED_TOOLS / PINNED_TOOLS environment "
|
|
4312
|
+
"variables (or remove them from your addon/Docker "
|
|
4313
|
+
"config), then restart to edit these tools from the UI.",
|
|
4314
|
+
],
|
|
4189
4315
|
context={"rejected": rejected},
|
|
4190
4316
|
),
|
|
4191
4317
|
status_code=409,
|
|
@@ -4539,6 +4665,12 @@ def build_settings_handlers(
|
|
|
4539
4665
|
"enable_beta_features=true in the same save, or "
|
|
4540
4666
|
"flip the master on first."
|
|
4541
4667
|
),
|
|
4668
|
+
suggestions=[
|
|
4669
|
+
"Include enable_beta_features=true in the same save "
|
|
4670
|
+
"payload as the sub-flag(s).",
|
|
4671
|
+
"Or turn on the master 'Enable beta features' toggle "
|
|
4672
|
+
"first, then enable the sub-flag(s).",
|
|
4673
|
+
],
|
|
4542
4674
|
context={"rejected": beta_sub_writes},
|
|
4543
4675
|
),
|
|
4544
4676
|
status_code=409,
|
|
@@ -4704,6 +4836,13 @@ def build_settings_handlers(
|
|
|
4704
4836
|
create_error_response(
|
|
4705
4837
|
ErrorCode.INTERNAL_ERROR,
|
|
4706
4838
|
"Supervisor helper returned ok=False with no error",
|
|
4839
|
+
suggestions=[
|
|
4840
|
+
"Check the Home Assistant Supervisor logs and "
|
|
4841
|
+
"the add-on logs for the underlying failure.",
|
|
4842
|
+
"Report this at "
|
|
4843
|
+
"https://github.com/homeassistant-ai/ha-mcp/issues "
|
|
4844
|
+
"if it persists — this indicates an internal bug.",
|
|
4845
|
+
],
|
|
4707
4846
|
),
|
|
4708
4847
|
status_code=500,
|
|
4709
4848
|
)
|
|
@@ -5239,6 +5378,11 @@ def build_settings_handlers(
|
|
|
5239
5378
|
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
5240
5379
|
f"{fname!r} is set via {env_name} env var — "
|
|
5241
5380
|
"unset it to edit here.",
|
|
5381
|
+
suggestions=[
|
|
5382
|
+
f"Unset the {env_name} environment variable (or "
|
|
5383
|
+
"remove it from your addon/Docker config), then "
|
|
5384
|
+
"restart to edit this setting from the UI.",
|
|
5385
|
+
],
|
|
5242
5386
|
context={"env_var": env_name},
|
|
5243
5387
|
),
|
|
5244
5388
|
status_code=409,
|
|
@@ -5279,6 +5423,10 @@ def build_settings_handlers(
|
|
|
5279
5423
|
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
5280
5424
|
f"{fname!r} must be between {bounds[0]} and "
|
|
5281
5425
|
f"{bounds[1]} (got {coerced}).",
|
|
5426
|
+
suggestions=[
|
|
5427
|
+
f"Provide a value for {fname} within the range "
|
|
5428
|
+
f"{bounds[0]}–{bounds[1]}.",
|
|
5429
|
+
],
|
|
5282
5430
|
),
|
|
5283
5431
|
status_code=400,
|
|
5284
5432
|
)
|
|
@@ -5288,6 +5436,9 @@ def build_settings_handlers(
|
|
|
5288
5436
|
create_error_response(
|
|
5289
5437
|
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
5290
5438
|
f"{fname!r} must be one of {list(choices)} (got {coerced!r}).",
|
|
5439
|
+
suggestions=[
|
|
5440
|
+
f"Set {fname} to one of: {', '.join(map(str, choices))}.",
|
|
5441
|
+
],
|
|
5291
5442
|
),
|
|
5292
5443
|
status_code=400,
|
|
5293
5444
|
)
|
|
@@ -5338,6 +5489,11 @@ def build_settings_handlers(
|
|
|
5338
5489
|
"writes in one batch; the UI should split these "
|
|
5339
5490
|
f"into separate POSTs ({sorted(file_only)})."
|
|
5340
5491
|
),
|
|
5492
|
+
suggestions=[
|
|
5493
|
+
"Submit addon-synced fields (e.g. backup_hint, "
|
|
5494
|
+
"verify_ssl) and override-file fields in separate "
|
|
5495
|
+
"save requests.",
|
|
5496
|
+
],
|
|
5341
5497
|
),
|
|
5342
5498
|
status_code=500,
|
|
5343
5499
|
)
|
|
@@ -5363,6 +5519,13 @@ def build_settings_handlers(
|
|
|
5363
5519
|
create_error_response(
|
|
5364
5520
|
ErrorCode.INTERNAL_ERROR,
|
|
5365
5521
|
"Supervisor helper returned ok=False with no error",
|
|
5522
|
+
suggestions=[
|
|
5523
|
+
"Check the Home Assistant Supervisor logs and "
|
|
5524
|
+
"the add-on logs for the underlying failure.",
|
|
5525
|
+
"Report this at "
|
|
5526
|
+
"https://github.com/homeassistant-ai/ha-mcp/issues "
|
|
5527
|
+
"if it persists — this indicates an internal bug.",
|
|
5528
|
+
],
|
|
5366
5529
|
),
|
|
5367
5530
|
status_code=500,
|
|
5368
5531
|
)
|
|
@@ -5462,6 +5625,9 @@ def build_settings_handlers(
|
|
|
5462
5625
|
create_error_response(
|
|
5463
5626
|
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
5464
5627
|
f"{fname!r} expects {ftype.__name__}, got {type(raw).__name__}.",
|
|
5628
|
+
suggestions=[
|
|
5629
|
+
f"Send {fname} as a {ftype.__name__} value.",
|
|
5630
|
+
],
|
|
5465
5631
|
),
|
|
5466
5632
|
status_code=400,
|
|
5467
5633
|
)
|