ha-mcp-dev 7.14.2.dev2188__tar.gz → 7.14.2.dev2190__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.dev2188/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.14.2.dev2190}/PKG-INFO +1 -1
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/pyproject.toml +1 -1
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/client/websocket_listener.py +0 -2
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/device_control.py +99 -32
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_service.py +6 -4
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/util_helpers.py +2 -31
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/utils/domain_handlers.py +36 -17
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/utils/operation_manager.py +20 -12
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/setup.cfg +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/locales/de.json +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/locales/en.json +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/locales/es.json +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/locales/it.json +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/utils/config_write_lock.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/visibility/enforcement.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/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.dev2190"
|
|
8
8
|
description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
# Upper bound widened to <3.15 so the package installs on Home Assistant's
|
{ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/client/websocket_listener.py
RENAMED
|
@@ -9,7 +9,6 @@ import asyncio
|
|
|
9
9
|
import logging
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
-
from ..config import get_global_settings
|
|
13
12
|
from ..utils.operation_manager import get_operation_manager, update_pending_operations
|
|
14
13
|
from .websocket_client import HomeAssistantWebSocketClient, get_websocket_client
|
|
15
14
|
|
|
@@ -21,7 +20,6 @@ class WebSocketListenerService:
|
|
|
21
20
|
|
|
22
21
|
def __init__(self) -> None:
|
|
23
22
|
"""Initialize the WebSocket listener service."""
|
|
24
|
-
self.settings = get_global_settings()
|
|
25
23
|
self.operation_manager = get_operation_manager()
|
|
26
24
|
self.websocket_client: HomeAssistantWebSocketClient | None = None
|
|
27
25
|
self.listener_task: asyncio.Task | None = None
|
|
@@ -301,33 +301,50 @@ class DeviceControlTools:
|
|
|
301
301
|
action: str,
|
|
302
302
|
parameters: dict[str, Any] | None,
|
|
303
303
|
) -> tuple[str, dict[str, Any] | None]:
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
"
|
|
307
|
-
|
|
308
|
-
"open": "open_cover" if domain == "cover" else "turn_on",
|
|
309
|
-
"close": "close_cover" if domain == "cover" else "turn_off",
|
|
310
|
-
"set": "turn_on" if domain == "light" else "set_temperature",
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
service_name = service_mapping.get(action, action)
|
|
304
|
+
if domain == "climate" and action in ("heat", "cool", "auto", "heat_cool"):
|
|
305
|
+
parameters = dict(parameters or {})
|
|
306
|
+
parameters["hvac_mode"] = action
|
|
307
|
+
return "set_hvac_mode", parameters
|
|
314
308
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
parameters = {}
|
|
320
|
-
parameters["hvac_mode"] = action
|
|
321
|
-
elif action == "set":
|
|
322
|
-
service_name = "set_temperature"
|
|
309
|
+
service_name = self._SERVICE_OVERRIDES.get(
|
|
310
|
+
(domain, action), self._GENERIC_SERVICE_MAP.get(action, action)
|
|
311
|
+
)
|
|
312
|
+
return service_name, parameters
|
|
323
313
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
314
|
+
# Domain-specific action-to-service mappings; anything not listed
|
|
315
|
+
# falls through to _GENERIC_SERVICE_MAP, then to the action verbatim.
|
|
316
|
+
# tests/src/e2e/workflows/device_control/test_action_service_resolution.py
|
|
317
|
+
# pins every DOMAIN_HANDLERS table entry through this resolver against
|
|
318
|
+
# a live instance's service list.
|
|
319
|
+
_SERVICE_OVERRIDES: ClassVar[dict[tuple[str, str], str]] = {
|
|
320
|
+
("cover", "open"): "open_cover",
|
|
321
|
+
("cover", "close"): "close_cover",
|
|
322
|
+
("cover", "stop"): "stop_cover",
|
|
323
|
+
("cover", "set"): "set_cover_position",
|
|
324
|
+
("light", "set"): "turn_on",
|
|
325
|
+
# lock.open exists; the generic mapping's turn_on does not.
|
|
326
|
+
("lock", "open"): "open",
|
|
327
|
+
("climate", "set"): "set_temperature",
|
|
328
|
+
("media_player", "play"): "media_play",
|
|
329
|
+
("media_player", "pause"): "media_pause",
|
|
330
|
+
("media_player", "stop"): "media_stop",
|
|
331
|
+
("media_player", "next"): "media_next_track",
|
|
332
|
+
("media_player", "previous"): "media_previous_track",
|
|
333
|
+
("media_player", "set"): "volume_set",
|
|
334
|
+
("alarm_control_panel", "arm_home"): "alarm_arm_home",
|
|
335
|
+
("alarm_control_panel", "arm_away"): "alarm_arm_away",
|
|
336
|
+
("alarm_control_panel", "arm_night"): "alarm_arm_night",
|
|
337
|
+
("alarm_control_panel", "disarm"): "alarm_disarm",
|
|
338
|
+
}
|
|
329
339
|
|
|
330
|
-
|
|
340
|
+
_GENERIC_SERVICE_MAP: ClassVar[dict[str, str]] = {
|
|
341
|
+
"on": "turn_on",
|
|
342
|
+
"off": "turn_off",
|
|
343
|
+
"toggle": "toggle",
|
|
344
|
+
"open": "turn_on",
|
|
345
|
+
"close": "turn_off",
|
|
346
|
+
"set": "set_temperature",
|
|
347
|
+
}
|
|
331
348
|
|
|
332
349
|
_DOMAIN_PARAMS: ClassVar[dict[str, list[str]]] = {
|
|
333
350
|
"light": ["brightness", "color_temp_kelvin", "rgb_color", "effect"],
|
|
@@ -1274,13 +1291,20 @@ class DeviceControlTools:
|
|
|
1274
1291
|
return response
|
|
1275
1292
|
|
|
1276
1293
|
async def get_bulk_operation_status(
|
|
1277
|
-
self, operation_ids: list[str]
|
|
1294
|
+
self, operation_ids: list[str], timeout_seconds: int = 10
|
|
1278
1295
|
) -> dict[str, Any]:
|
|
1279
1296
|
"""
|
|
1280
1297
|
Check status of multiple operations.
|
|
1281
1298
|
|
|
1299
|
+
Polls all operations concurrently under one shared
|
|
1300
|
+
``timeout_seconds`` window, so the wall time is bounded by the
|
|
1301
|
+
window rather than growing with the batch size. Per-item failures
|
|
1302
|
+
become structured entries in ``detailed_results`` rather than
|
|
1303
|
+
aborting the batch.
|
|
1304
|
+
|
|
1282
1305
|
Args:
|
|
1283
1306
|
operation_ids: List of operation IDs to check
|
|
1307
|
+
timeout_seconds: Wait window applied to every operation
|
|
1284
1308
|
|
|
1285
1309
|
Returns:
|
|
1286
1310
|
Status summary for all operations
|
|
@@ -1296,26 +1320,69 @@ class DeviceControlTools:
|
|
|
1296
1320
|
)
|
|
1297
1321
|
)
|
|
1298
1322
|
|
|
1299
|
-
# Check all operations
|
|
1300
|
-
|
|
1301
|
-
for
|
|
1302
|
-
|
|
1303
|
-
|
|
1323
|
+
# Check all operations concurrently under one shared wait window.
|
|
1324
|
+
# Per-item failures must not abort the batch: get_device_operation_status
|
|
1325
|
+
# raises ToolError for failed / timed-out / not-found operations, so each
|
|
1326
|
+
# one is caught and folded back into detailed_results as a structured
|
|
1327
|
+
# entry — otherwise the first bad operation would discard the status of
|
|
1328
|
+
# every other one. The whole parsed error payload is preserved (context
|
|
1329
|
+
# keys like entity_id / duration_ms sit at its top level), with the
|
|
1330
|
+
# batch "status" field layered on for the summary counts.
|
|
1331
|
+
error_code_to_status = {
|
|
1332
|
+
ErrorCode.SERVICE_CALL_FAILED.value: "failed",
|
|
1333
|
+
ErrorCode.TIMEOUT_OPERATION.value: "timeout",
|
|
1334
|
+
ErrorCode.RESOURCE_NOT_FOUND.value: "not_found",
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
async def check_one(op_id: str) -> dict[str, Any]:
|
|
1338
|
+
try:
|
|
1339
|
+
return await self.get_device_operation_status(
|
|
1340
|
+
op_id, timeout_seconds=timeout_seconds
|
|
1341
|
+
)
|
|
1342
|
+
except ToolError as e:
|
|
1343
|
+
try:
|
|
1344
|
+
err = json.loads(str(e))
|
|
1345
|
+
except ValueError:
|
|
1346
|
+
err = {"success": False, "error": {"message": str(e)}}
|
|
1347
|
+
error_info = err.get("error") or {}
|
|
1348
|
+
return {
|
|
1349
|
+
**err,
|
|
1350
|
+
"operation_id": op_id,
|
|
1351
|
+
"status": error_code_to_status.get(
|
|
1352
|
+
error_info.get("code", ""), "failed"
|
|
1353
|
+
),
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
statuses = list(
|
|
1357
|
+
await asyncio.gather(*(check_one(op_id) for op_id in operation_ids))
|
|
1358
|
+
)
|
|
1304
1359
|
|
|
1305
1360
|
# Summarize results
|
|
1306
1361
|
completed = len([s for s in statuses if s.get("status") == "completed"])
|
|
1307
1362
|
failed = len([s for s in statuses if s.get("status") in ["failed", "timeout"]])
|
|
1363
|
+
not_found = len([s for s in statuses if s.get("status") == "not_found"])
|
|
1308
1364
|
pending = len([s for s in statuses if s.get("status") == "pending"])
|
|
1309
1365
|
|
|
1366
|
+
# The batch call itself succeeded; per-item failures live inside
|
|
1367
|
+
# detailed_results (batch-item pattern). Top-level ``success`` is
|
|
1368
|
+
# the repo-wide response contract every tool return carries.
|
|
1310
1369
|
return {
|
|
1370
|
+
"success": True,
|
|
1311
1371
|
"total_operations": len(operation_ids),
|
|
1312
1372
|
"completed": completed,
|
|
1313
1373
|
"failed": failed,
|
|
1374
|
+
"not_found": not_found,
|
|
1314
1375
|
"pending": pending,
|
|
1315
1376
|
"all_complete": pending == 0,
|
|
1316
1377
|
"summary": {
|
|
1317
1378
|
"success_rate": f"{completed}/{len(operation_ids)}",
|
|
1318
|
-
|
|
1379
|
+
# Terminal fraction (nothing left in flight) — consistent
|
|
1380
|
+
# with all_complete; success_rate carries the successful
|
|
1381
|
+
# fraction separately.
|
|
1382
|
+
"completion_percentage": (
|
|
1383
|
+
(completed + failed + not_found) / len(operation_ids)
|
|
1384
|
+
)
|
|
1385
|
+
* 100,
|
|
1319
1386
|
},
|
|
1320
1387
|
"detailed_results": statuses,
|
|
1321
1388
|
"recommendations": (
|
|
@@ -1324,7 +1391,7 @@ class DeviceControlTools:
|
|
|
1324
1391
|
"Check failed operations for specific error messages",
|
|
1325
1392
|
"Retry failed operations with different parameters if needed",
|
|
1326
1393
|
]
|
|
1327
|
-
if pending > 0 or failed > 0
|
|
1394
|
+
if pending > 0 or failed > 0 or not_found > 0
|
|
1328
1395
|
else ["All operations completed successfully!"]
|
|
1329
1396
|
),
|
|
1330
1397
|
}
|
|
@@ -1385,13 +1385,15 @@ class ServiceTools:
|
|
|
1385
1385
|
timeout_seconds: int = 10,
|
|
1386
1386
|
) -> dict[str, Any]:
|
|
1387
1387
|
"""
|
|
1388
|
-
|
|
1388
|
+
Get the status of one or more device operations with real-time WebSocket verification.
|
|
1389
1389
|
|
|
1390
1390
|
Pass a single operation_id string to check one operation, or a list of IDs
|
|
1391
1391
|
to check multiple operations at once (bulk status).
|
|
1392
1392
|
|
|
1393
|
-
The timeout_seconds
|
|
1394
|
-
|
|
1393
|
+
The timeout_seconds wait window bounds both modes. Bulk checks poll
|
|
1394
|
+
all operations concurrently under one shared window and report
|
|
1395
|
+
per-item failures inside detailed_results instead of aborting the
|
|
1396
|
+
batch.
|
|
1395
1397
|
|
|
1396
1398
|
Use this to track operations initiated by ha_bulk_control or ha_call_service.
|
|
1397
1399
|
For current entity states, use ha_get_state instead.
|
|
@@ -1401,7 +1403,7 @@ class ServiceTools:
|
|
|
1401
1403
|
# before the body runs, so operation_id is already the final shape.
|
|
1402
1404
|
if isinstance(operation_id, list):
|
|
1403
1405
|
result = await self._device_tools.get_bulk_operation_status(
|
|
1404
|
-
operation_ids=operation_id
|
|
1406
|
+
operation_ids=operation_id, timeout_seconds=timeout_seconds
|
|
1405
1407
|
)
|
|
1406
1408
|
return cast(dict[str, Any], result)
|
|
1407
1409
|
result = await self._device_tools.get_device_operation_status(
|
|
@@ -77,43 +77,14 @@ def summarize_theme_listing(raw_themes: dict[str, Any]) -> dict[str, Any]:
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
def
|
|
81
|
-
"""
|
|
82
|
-
or lists in place.
|
|
80
|
+
def public_fields(d: dict[str, Any]) -> dict[str, Any]:
|
|
81
|
+
"""Return a shallow copy of ``d`` with leading-underscore keys removed.
|
|
83
82
|
|
|
84
83
|
The ha-mcp tool layer enriches entity / area dicts with internal
|
|
85
84
|
fields like ``_hidden_by`` and ``_aliases`` so downstream branches
|
|
86
85
|
can rank without re-querying the entity registry. Those keys must
|
|
87
86
|
not leak through public tool returns: this helper centralises the
|
|
88
87
|
convention so individual call sites don't have to remember to strip.
|
|
89
|
-
|
|
90
|
-
Mutates in place and returns the same reference for chaining. Cycle
|
|
91
|
-
guard via ``_seen`` (id-tracked) keeps the recursion safe if a
|
|
92
|
-
future caller ever feeds it a non-tree structure — JSON payloads
|
|
93
|
-
don't, but the helper is now a generic utility (importable from
|
|
94
|
-
``server.py``) so the protection is cheap insurance.
|
|
95
|
-
"""
|
|
96
|
-
if _seen is None:
|
|
97
|
-
_seen = set()
|
|
98
|
-
obj_id = id(obj)
|
|
99
|
-
if obj_id in _seen:
|
|
100
|
-
return obj
|
|
101
|
-
if isinstance(obj, dict):
|
|
102
|
-
_seen.add(obj_id)
|
|
103
|
-
for key in [k for k in obj if isinstance(k, str) and k.startswith("_")]:
|
|
104
|
-
obj.pop(key, None)
|
|
105
|
-
for value in obj.values():
|
|
106
|
-
strip_internal_fields(value, _seen)
|
|
107
|
-
elif isinstance(obj, list):
|
|
108
|
-
_seen.add(obj_id)
|
|
109
|
-
for item in obj:
|
|
110
|
-
strip_internal_fields(item, _seen)
|
|
111
|
-
return obj
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def public_fields(d: dict[str, Any]) -> dict[str, Any]:
|
|
115
|
-
"""Return a shallow copy of ``d`` with leading-underscore keys
|
|
116
|
-
removed. Non-mutating counterpart to :func:`strip_internal_fields`.
|
|
117
88
|
Shallow only — list/dict values are shared with the source, so a
|
|
118
89
|
later mutation of those values would propagate.
|
|
119
90
|
"""
|
|
@@ -10,7 +10,7 @@ from typing import Any
|
|
|
10
10
|
# Domain-specific handler configurations
|
|
11
11
|
DOMAIN_HANDLERS = {
|
|
12
12
|
"light": {
|
|
13
|
-
"valid_actions": ["on", "off", "toggle", "set"
|
|
13
|
+
"valid_actions": ["on", "off", "toggle", "set"],
|
|
14
14
|
"parameters": [
|
|
15
15
|
"brightness",
|
|
16
16
|
"color_temp_kelvin",
|
|
@@ -30,7 +30,16 @@ DOMAIN_HANDLERS = {
|
|
|
30
30
|
"supports_color": True,
|
|
31
31
|
},
|
|
32
32
|
"climate": {
|
|
33
|
-
"valid_actions": [
|
|
33
|
+
"valid_actions": [
|
|
34
|
+
"on",
|
|
35
|
+
"off",
|
|
36
|
+
"toggle",
|
|
37
|
+
"set",
|
|
38
|
+
"heat",
|
|
39
|
+
"cool",
|
|
40
|
+
"auto",
|
|
41
|
+
"heat_cool",
|
|
42
|
+
],
|
|
34
43
|
"parameters": [
|
|
35
44
|
"temperature",
|
|
36
45
|
"target_temp_high",
|
|
@@ -60,7 +69,17 @@ DOMAIN_HANDLERS = {
|
|
|
60
69
|
"supports_color": False,
|
|
61
70
|
},
|
|
62
71
|
"media_player": {
|
|
63
|
-
"valid_actions": [
|
|
72
|
+
"valid_actions": [
|
|
73
|
+
"on",
|
|
74
|
+
"off",
|
|
75
|
+
"play",
|
|
76
|
+
"pause",
|
|
77
|
+
"stop",
|
|
78
|
+
"toggle",
|
|
79
|
+
"set",
|
|
80
|
+
"next",
|
|
81
|
+
"previous",
|
|
82
|
+
],
|
|
64
83
|
"parameters": [
|
|
65
84
|
"volume_level",
|
|
66
85
|
"media_content_id",
|
|
@@ -73,7 +92,7 @@ DOMAIN_HANDLERS = {
|
|
|
73
92
|
"supports_media": True,
|
|
74
93
|
},
|
|
75
94
|
"fan": {
|
|
76
|
-
"valid_actions": ["on", "off", "toggle"
|
|
95
|
+
"valid_actions": ["on", "off", "toggle"],
|
|
77
96
|
# HA removed the legacy `speed` param / `fan.set_speed` service in the
|
|
78
97
|
# 2021-2022 percentage migration (absent in 2026.6). Use percentage
|
|
79
98
|
# (fan.set_percentage) and preset_mode (fan.set_preset_mode).
|
|
@@ -116,7 +135,7 @@ DOMAIN_HANDLERS = {
|
|
|
116
135
|
"supports_modes": True,
|
|
117
136
|
},
|
|
118
137
|
"humidifier": {
|
|
119
|
-
"valid_actions": ["on", "off", "toggle"
|
|
138
|
+
"valid_actions": ["on", "off", "toggle"],
|
|
120
139
|
"parameters": ["humidity", "mode"],
|
|
121
140
|
"quick_actions": ["toggle", "humidity_up", "humidity_down"],
|
|
122
141
|
"state_attributes": ["current_humidity", "target_humidity"],
|
|
@@ -124,7 +143,7 @@ DOMAIN_HANDLERS = {
|
|
|
124
143
|
"supports_modes": True,
|
|
125
144
|
},
|
|
126
145
|
"camera": {
|
|
127
|
-
"valid_actions": ["
|
|
146
|
+
"valid_actions": ["on", "off", "snapshot", "record"],
|
|
128
147
|
"parameters": ["filename", "duration"],
|
|
129
148
|
"quick_actions": ["take_snapshot"],
|
|
130
149
|
"state_attributes": ["entity_picture", "access_token"],
|
|
@@ -132,7 +151,7 @@ DOMAIN_HANDLERS = {
|
|
|
132
151
|
"supports_recording": True,
|
|
133
152
|
},
|
|
134
153
|
"scene": {
|
|
135
|
-
"valid_actions": ["
|
|
154
|
+
"valid_actions": ["on", "turn_on"],
|
|
136
155
|
"parameters": [],
|
|
137
156
|
"quick_actions": ["activate"],
|
|
138
157
|
"state_attributes": ["state"],
|
|
@@ -140,7 +159,7 @@ DOMAIN_HANDLERS = {
|
|
|
140
159
|
"action_only": True,
|
|
141
160
|
},
|
|
142
161
|
"script": {
|
|
143
|
-
"valid_actions": ["
|
|
162
|
+
"valid_actions": ["on", "off", "toggle", "turn_on", "turn_off"],
|
|
144
163
|
"parameters": [],
|
|
145
164
|
"quick_actions": ["run", "stop"],
|
|
146
165
|
"state_attributes": ["state"],
|
|
@@ -148,7 +167,7 @@ DOMAIN_HANDLERS = {
|
|
|
148
167
|
"can_be_stopped": True,
|
|
149
168
|
},
|
|
150
169
|
"automation": {
|
|
151
|
-
"valid_actions": ["
|
|
170
|
+
"valid_actions": ["on", "off", "toggle", "turn_on", "turn_off", "trigger"],
|
|
152
171
|
"parameters": [],
|
|
153
172
|
"quick_actions": ["enable", "disable", "trigger"],
|
|
154
173
|
"state_attributes": ["state", "last_triggered"],
|
|
@@ -192,20 +211,20 @@ DOMAIN_HANDLERS = {
|
|
|
192
211
|
}
|
|
193
212
|
|
|
194
213
|
|
|
195
|
-
def get_domain_handler(
|
|
196
|
-
"""Get domain-specific configuration for an entity.
|
|
214
|
+
def get_domain_handler(entity_or_domain: str) -> dict[str, Any]:
|
|
215
|
+
"""Get domain-specific configuration for an entity ID or bare domain.
|
|
197
216
|
|
|
198
217
|
Args:
|
|
199
|
-
|
|
218
|
+
entity_or_domain: Full entity ID ('light.living_room') or bare
|
|
219
|
+
domain ('light'). Both call sites in device_control pass the
|
|
220
|
+
bare domain — the old dot-guard sent those to the default
|
|
221
|
+
handler, making every per-domain valid_actions table
|
|
222
|
+
unreachable (e.g. climate's 'heat' was rejected upfront).
|
|
200
223
|
|
|
201
224
|
Returns:
|
|
202
225
|
Domain handler configuration dictionary
|
|
203
226
|
"""
|
|
204
|
-
|
|
205
|
-
# Fallback for invalid entity ID format
|
|
206
|
-
return get_default_handler()
|
|
207
|
-
|
|
208
|
-
domain = entity_id.split(".", maxsplit=1)[0]
|
|
227
|
+
domain = entity_or_domain.split(".", maxsplit=1)[0] if entity_or_domain else ""
|
|
209
228
|
return DOMAIN_HANDLERS.get(domain, get_default_handler())
|
|
210
229
|
|
|
211
230
|
|
{ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/utils/operation_manager.py
RENAMED
|
@@ -23,7 +23,6 @@ class OperationStatus(Enum):
|
|
|
23
23
|
COMPLETED = "completed"
|
|
24
24
|
FAILED = "failed"
|
|
25
25
|
TIMEOUT = "timeout"
|
|
26
|
-
CANCELLED = "cancelled"
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
@dataclass
|
|
@@ -286,42 +285,51 @@ class OperationManager:
|
|
|
286
285
|
|
|
287
286
|
initial_count = len(self.operations)
|
|
288
287
|
|
|
289
|
-
# Remove completed operations
|
|
290
|
-
# Remove failed/
|
|
288
|
+
# Remove completed operations 5 minutes after completion
|
|
289
|
+
# Remove failed/timed-out operations 1 minute after completion
|
|
290
|
+
# (TIMEOUT can be set outside this pass: get_operation() marks an
|
|
291
|
+
# expired PENDING op in place on the read path — anchoring the TTL
|
|
292
|
+
# on completion_time keeps such an op queryable for its full
|
|
293
|
+
# terminal minute even when it timed out long after start_time)
|
|
291
294
|
# Remove expired pending operations
|
|
292
295
|
to_remove = []
|
|
293
296
|
|
|
294
297
|
for op_id, operation in self.operations.items():
|
|
295
|
-
|
|
298
|
+
terminal_anchor = operation.completion_time or operation.start_time
|
|
299
|
+
age_seconds = (current_time * 1000 - terminal_anchor) / 1000
|
|
296
300
|
|
|
297
301
|
if (
|
|
298
302
|
operation.status == OperationStatus.COMPLETED and age_seconds > 300
|
|
299
303
|
) or (
|
|
300
|
-
operation.status in
|
|
304
|
+
operation.status in (OperationStatus.FAILED, OperationStatus.TIMEOUT)
|
|
301
305
|
and age_seconds > 60
|
|
302
306
|
):
|
|
303
307
|
to_remove.append(op_id)
|
|
304
308
|
elif operation.status == OperationStatus.PENDING and operation.is_expired:
|
|
305
|
-
# Mark as timeout
|
|
309
|
+
# Mark as timeout and KEEP it for the terminal minute — same
|
|
310
|
+
# treatment as a read-path timeout, so a poll shortly after
|
|
311
|
+
# expiry reports "timeout" rather than not_found regardless
|
|
312
|
+
# of which path noticed first. The TTL branch above reclaims
|
|
313
|
+
# it once completion_time is a minute old.
|
|
306
314
|
operation.status = OperationStatus.TIMEOUT
|
|
307
315
|
operation.completion_time = current_time * 1000
|
|
308
|
-
to_remove.append(op_id)
|
|
309
316
|
|
|
310
317
|
# Remove operations
|
|
311
318
|
for op_id in to_remove:
|
|
312
319
|
del self.operations[op_id]
|
|
313
320
|
|
|
314
|
-
# If still over limit, remove oldest
|
|
321
|
+
# If still over limit, remove oldest terminal operations (never
|
|
322
|
+
# in-flight PENDING ones — those expire on their own timeout).
|
|
315
323
|
if len(self.operations) > self.max_operations:
|
|
316
|
-
|
|
324
|
+
terminal_ops = [
|
|
317
325
|
(op_id, op)
|
|
318
326
|
for op_id, op in self.operations.items()
|
|
319
|
-
if op.status
|
|
327
|
+
if op.status != OperationStatus.PENDING
|
|
320
328
|
]
|
|
321
|
-
|
|
329
|
+
terminal_ops.sort(key=lambda x: x[1].completion_time or 0)
|
|
322
330
|
|
|
323
331
|
excess = len(self.operations) - self.max_operations
|
|
324
|
-
for op_id, _ in
|
|
332
|
+
for op_id, _ in terminal_ops[:excess]:
|
|
325
333
|
del self.operations[op_id]
|
|
326
334
|
|
|
327
335
|
removed_count = initial_count - len(self.operations)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/client/supervisor_client.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/client/websocket_client.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/dashboard_screenshot/__init__.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/dashboard_screenshot/capture.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/dashboard_screenshot/content.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/dashboard_screenshot/paths.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/dashboard_screenshot/provision.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2188 → ha_mcp_dev-7.14.2.dev2190}/src/ha_mcp/resources/skills-vendor/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|