ha-mcp-dev 8.0.0.dev2224__tar.gz → 8.0.0.dev2228__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.
Files changed (195) hide show
  1. {ha_mcp_dev-8.0.0.dev2224/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.0.0.dev2228}/PKG-INFO +1 -1
  2. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/pyproject.toml +1 -1
  3. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/__main__.py +36 -33
  4. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/config.py +9 -8
  5. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/__init__.py +3 -1
  6. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/_persistence.py +10 -3
  7. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/_theme.py +9 -6
  8. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/locales/en.json +2 -2
  9. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/settings.html +12 -10
  10. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/settings.js +5 -3
  11. ha_mcp_dev-8.0.0.dev2228/src/ha_mcp/stdio_settings_sidecar.py +1390 -0
  12. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  13. ha_mcp_dev-8.0.0.dev2224/src/ha_mcp/stdio_settings_sidecar.py +0 -880
  14. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/LICENSE +0 -0
  15. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/MANIFEST.in +0 -0
  16. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/README.md +0 -0
  17. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/setup.cfg +0 -0
  18. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/__init__.py +0 -0
  19. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/_pypi_marker +0 -0
  20. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/_version.py +0 -0
  21. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/auth/__init__.py +0 -0
  22. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/auth/consent_form.py +0 -0
  23. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/auth/provider.py +0 -0
  24. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/backup_manager.py +0 -0
  25. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/browser_landing.py +0 -0
  26. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/client/__init__.py +0 -0
  27. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/client/rest_client.py +0 -0
  28. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/client/supervisor_client.py +0 -0
  29. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/client/websocket_client.py +0 -0
  30. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/client/websocket_listener.py +0 -0
  31. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  32. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  33. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  34. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  35. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  36. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  37. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/errors.py +0 -0
  38. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/llm_exposure.py +0 -0
  39. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/policy/__init__.py +0 -0
  40. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/policy/approval_queue.py +0 -0
  41. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/policy/evaluator.py +0 -0
  42. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/policy/handlers.py +0 -0
  43. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/policy/middleware.py +0 -0
  44. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/policy/model.py +0 -0
  45. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/policy/persistence.py +0 -0
  46. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/policy/value_sources.py +0 -0
  47. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/py.typed +0 -0
  48. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/read_only.py +0 -0
  49. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  50. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  51. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  52. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  53. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  54. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  55. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  56. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  57. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  58. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  59. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  60. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  61. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  62. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  63. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  64. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  65. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  66. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  67. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  68. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  69. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  70. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  71. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  72. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  73. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  74. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/server.py +0 -0
  75. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  76. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  77. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  78. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  79. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  80. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  81. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  82. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  83. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  84. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  85. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  86. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/locales/es.json +0 -0
  87. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
  88. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/locales/it.json +0 -0
  89. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  90. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
  91. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/settings_ui/settings.css +0 -0
  92. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/smoke_test.py +0 -0
  93. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/strict_bps.py +0 -0
  94. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/__init__.py +0 -0
  95. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/auto_backup.py +0 -0
  96. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/backup.py +0 -0
  97. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  98. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/best_practice_result.py +0 -0
  99. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/best_practice_variables.py +0 -0
  100. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/component_api.py +0 -0
  101. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/component_config_reads.py +0 -0
  102. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/component_devices.py +0 -0
  103. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/component_registries.py +0 -0
  104. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  105. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  106. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/device_control.py +0 -0
  107. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/helpers.py +0 -0
  108. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/radio/__init__.py +0 -0
  109. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/radio/base.py +0 -0
  110. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/radio/matter.py +0 -0
  111. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/radio/thread.py +0 -0
  112. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  113. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/radio/zwave.py +0 -0
  114. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/reference_validator.py +0 -0
  115. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/registry.py +0 -0
  116. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  117. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  118. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  119. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  120. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  121. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  122. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  123. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  124. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  125. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  126. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_addons.py +0 -0
  127. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_areas.py +0 -0
  128. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  129. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  130. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_calendar.py +0 -0
  131. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_camera.py +0 -0
  132. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_categories.py +0 -0
  133. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_code.py +0 -0
  134. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  135. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  136. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  137. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  138. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  139. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  140. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_dev.py +0 -0
  141. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_energy.py +0 -0
  142. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_entities.py +0 -0
  143. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  144. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_groups.py +0 -0
  145. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_hacs.py +0 -0
  146. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_history.py +0 -0
  147. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_integrations.py +0 -0
  148. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_labels.py +0 -0
  149. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_radio.py +0 -0
  150. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_registry.py +0 -0
  151. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_resources.py +0 -0
  152. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_search.py +0 -0
  153. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_service.py +0 -0
  154. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_services.py +0 -0
  155. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_system.py +0 -0
  156. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_themes.py +0 -0
  157. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_todo.py +0 -0
  158. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_traces.py +0 -0
  159. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_updates.py +0 -0
  160. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_utility.py +0 -0
  161. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  162. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  163. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  164. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/tools_zones.py +0 -0
  165. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/util_helpers.py +0 -0
  166. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/tools/validation_middleware.py +0 -0
  167. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/transforms/__init__.py +0 -0
  168. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/transforms/categorized_search.py +0 -0
  169. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  170. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/transport_security.py +0 -0
  171. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/update_check.py +0 -0
  172. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/utils/__init__.py +0 -0
  173. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/utils/config_hash.py +0 -0
  174. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/utils/config_write_lock.py +0 -0
  175. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/utils/data_paths.py +0 -0
  176. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/utils/domain_handlers.py +0 -0
  177. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  178. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  179. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/utils/operation_manager.py +0 -0
  180. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/utils/python_sandbox.py +0 -0
  181. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/utils/skill_loader.py +0 -0
  182. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/utils/usage_logger.py +0 -0
  183. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/visibility/__init__.py +0 -0
  184. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/visibility/enforcement.py +0 -0
  185. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/visibility/model.py +0 -0
  186. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/visibility/persistence.py +0 -0
  187. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp/visibility/resolver.py +0 -0
  188. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  189. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  190. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  191. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  192. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  193. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/tests/__init__.py +0 -0
  194. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/tests/test_constants.py +0 -0
  195. {ha_mcp_dev-8.0.0.dev2224 → ha_mcp_dev-8.0.0.dev2228}/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: 8.0.0.dev2224
3
+ Version: 8.0.0.dev2228
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ha-mcp-dev"
7
- version = "8.0.0.dev2224"
7
+ version = "8.0.0.dev2228"
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
@@ -901,27 +901,27 @@ def _maybe_spawn_settings_sidecar() -> None:
901
901
  is async; this happens before the main stdio loop so there's no
902
902
  nested-loop conflict with ``_run_entrypoint``'s own ``asyncio.run``.
903
903
 
904
- Performance: the dump constructs the full FastMCP server, which is
905
- heavy. Skip it (and the server build) when there's nothing to spawn
906
- for — sidecar disabled or already alive. Warm restarts that already
907
- have a sidecar pay zero cold-start tax from this path.
904
+ Performance: the dump constructs the full FastMCP server via the
905
+ cached ``_get_server()`` singleton the stdio session builds anyway,
906
+ so this only front-loads that cost. The dump runs as maybe_spawn's
907
+ ``prepare`` hook — winner-only, inside the spawn lock — because the
908
+ replacement sidecar must read a cache dumped by the SAME parent that
909
+ spawned it: a spawn-lock loser with a different environment could
910
+ otherwise overwrite the winner's cache (issue #2131 review), and the
911
+ sidecar reloads that shared file per request.
908
912
  """
909
913
  from ha_mcp.settings_ui import (
910
914
  _get_tool_metadata,
911
915
  dump_tool_metadata_cache,
912
916
  )
913
917
  from ha_mcp.stdio_settings_sidecar import (
914
- _existing_sidecar_alive,
915
918
  _is_disabled,
916
919
  maybe_spawn,
917
920
  )
918
921
 
919
- # Cheap gates first; skip the heavy metadata dump when the sidecar
920
- # would be a no-op anyway. Any condition that makes maybe_spawn()
921
- # short-circuit also makes the dump pointless (the running sidecar
922
- # already has a cache from a prior parent startup; a disabled
923
- # sidecar never reads one).
924
- if _is_disabled() or _existing_sidecar_alive():
922
+ # Disabled is the only dump-skipping gate; maybe_spawn() logs the
923
+ # skip reason (a disabled sidecar never reads the cache).
924
+ if _is_disabled():
925
925
  try:
926
926
  maybe_spawn()
927
927
  except Exception as e:
@@ -932,32 +932,35 @@ def _maybe_spawn_settings_sidecar() -> None:
932
932
  )
933
933
  return
934
934
 
935
- try:
936
- metadata = asyncio.run(_get_tool_metadata(_get_server()))
937
- dumped = dump_tool_metadata_cache(metadata)
938
- # Log a deliberate one-liner so users debugging an empty
939
- # settings page can see whether the parent's dump succeeded
940
- # by grepping the stdio process output (which Claude Desktop
941
- # surfaces in its MCP server log panel).
942
- logger.info(
943
- "Tool metadata cache: %d tools dumped, write %s",
944
- len(metadata),
945
- "succeeded" if dumped else "FAILED",
946
- )
947
- except Exception as e:
948
- # Cache dump is best-effort — the sidecar falls back to an empty
949
- # tools list rather than blocking stdio startup. Include the
950
- # exception class in the warning so ops can distinguish
935
+ def _dump_metadata_cache() -> None:
936
+ # One-off asyncio.run is safe here: this runs before the main
937
+ # stdio loop, so there's no nested-loop conflict with
938
+ # _run_entrypoint's own asyncio.run. Best-effort — the sidecar
939
+ # falls back to an empty tools list rather than blocking stdio
940
+ # startup. The exception class in the warning distinguishes
951
941
  # server-init failures (Pydantic ValidationError) from cache I/O
952
942
  # (OSError) from event-loop issues (RuntimeError).
953
- logger.warning(
954
- "Failed to dump tool metadata cache (%s)",
955
- type(e).__name__,
956
- exc_info=True,
957
- )
943
+ try:
944
+ metadata = asyncio.run(_get_tool_metadata(_get_server()))
945
+ dumped = dump_tool_metadata_cache(metadata)
946
+ # Deliberate one-liner so users debugging an empty settings
947
+ # page can see whether the parent's dump succeeded by
948
+ # grepping the stdio process output (which Claude Desktop
949
+ # surfaces in its MCP server log panel).
950
+ logger.info(
951
+ "Tool metadata cache: %d tools dumped, write %s",
952
+ len(metadata),
953
+ "succeeded" if dumped else "FAILED",
954
+ )
955
+ except Exception as e:
956
+ logger.warning(
957
+ "Failed to dump tool metadata cache (%s)",
958
+ type(e).__name__,
959
+ exc_info=True,
960
+ )
958
961
 
959
962
  try:
960
- maybe_spawn()
963
+ maybe_spawn(prepare=_dump_metadata_cache)
961
964
  except Exception as e:
962
965
  # Spawn failures already log inside maybe_spawn(); the bare
963
966
  # except here is a defense-in-depth guard for any unexpected
@@ -102,11 +102,12 @@ class Settings(BaseSettings):
102
102
  # WebSocket configuration (essential for async operations)
103
103
  enable_websocket: bool = Field(True, alias="ENABLE_WEBSOCKET")
104
104
 
105
- # Settings UI sidecar (stdio mode only, #1587). 0 = pick a free
106
- # ephemeral port at every spawn (default); 1024-65535 pins the sidecar
107
- # to a fixed port so the settings URL/origin stays stable across
108
- # restarts (bookmarks, browser localStorage). Read by run_main() in
109
- # stdio_settings_sidecar.py.
105
+ # Settings UI sidecar (stdio mode only, #1587). 0 (default) = pick a
106
+ # free ephemeral port on the first spawn and reuse it afterwards
107
+ # (persisted in ui.state, #2131) so the settings URL/origin stays
108
+ # stable across restarts; 1024-65535 pins a preferred fixed port
109
+ # instead (best-effort: falls back to an ephemeral one if taken).
110
+ # Read by run_main() in stdio_settings_sidecar.py.
110
111
  sidecar_pin_port: int = Field(0, alias="HA_MCP_SIDECAR_PORT")
111
112
 
112
113
  # Development/Debug configuration
@@ -859,9 +860,9 @@ ADVANCED_SETTINGS_FIELDS: tuple[AdvancedField, ...] = (
859
860
  AdvancedField("environment", "ENVIRONMENT", str, "diagnostics", True),
860
861
  AdvancedField("log_level", "LOG_LEVEL", str, "diagnostics", True),
861
862
  AdvancedField("debug", "DEBUG", bool, "diagnostics", True),
862
- # Settings UI sidecar (stdio-only). Pin the sidecar's port so the
863
- # settings URL/origin is stable across restarts; 0 = ephemeral
864
- # (default). #1587.
863
+ # Settings UI sidecar (stdio-only). 0 (default) = first spawn picks a
864
+ # free port and later spawns reuse it via ui.state (#2131); a value
865
+ # pins a preferred fixed port instead (best-effort, #1587).
865
866
  AdvancedField("sidecar_pin_port", "HA_MCP_SIDECAR_PORT", int, "sidecar", True),
866
867
  # NOTE: ``auto_backup_dir`` and ``auto_backup_calendar_lookahead_days``
867
868
  # are NOT in this tuple. They are in ``BACKUP_OVERRIDE_FIELDS`` (defined
@@ -626,7 +626,9 @@ def register_settings_routes(
626
626
  ("/api/settings/features", ["GET"], "get_feature_flags"),
627
627
  ("/api/settings/features", ["POST"], "save_feature_flags"),
628
628
  # Theme / accessibility prefs (#1574 review) — server-side copy so
629
- # they survive the stdio sidecar's per-spawn origin change
629
+ # they survive a stdio sidecar origin change (stable by default
630
+ # since #2131, but fresh on first spawn / lost ui.state / pin
631
+ # change / taken remembered port)
630
632
  ("/api/settings/theme", ["GET"], "get_theme_prefs"),
631
633
  ("/api/settings/theme", ["POST"], "save_theme_prefs"),
632
634
  # Advanced settings endpoints
@@ -67,7 +67,11 @@ def dump_tool_metadata_cache(metadata: list[dict[str, Any]]) -> bool:
67
67
  """
68
68
  path = _get_tool_metadata_cache_path()
69
69
  try:
70
- path.write_text(json.dumps(metadata))
70
+ # Atomic (tmp-then-rename): since the dump moved ahead of the
71
+ # sidecar retire it overlaps the OLD still-serving sidecar on
72
+ # every startup, and a truncate-write would hand a concurrent
73
+ # /api/tools request an empty tool list from the torn file.
74
+ _atomic_write_json(path, metadata)
71
75
  except OSError:
72
76
  logger.warning("Failed to dump tool metadata cache to %s", path, exc_info=True)
73
77
  return False
@@ -203,10 +207,13 @@ def effective_tool_config(settings: Settings | None = None) -> dict[str, Any]:
203
207
  return {**cfg, "tools": tools}
204
208
 
205
209
 
206
- def _atomic_write_json(path: Path, payload: dict[str, Any]) -> None:
210
+ def _atomic_write_json(
211
+ path: Path, payload: dict[str, Any] | list[dict[str, Any]]
212
+ ) -> None:
207
213
  """Write ``payload`` to ``path`` atomically.
208
214
 
209
- Writes to ``<path>.tmp`` first and ``os.replace``s into place so a
215
+ Writes to a unique same-directory temp file (``mkstemp``, since
216
+ #1993) first and ``os.replace``s into place so a
210
217
  crash or out-of-space mid-write cannot leave a partial/empty file —
211
218
  callers that read the file back (``load_tool_config`` /
212
219
  ``_load_backup_settings_override``) would otherwise treat a
@@ -1,12 +1,15 @@
1
1
  """Theme / accessibility preference persistence for the settings UI.
2
2
 
3
3
  The browser keeps these in localStorage for synchronous pre-paint reads,
4
- but localStorage is origin-scoped and the stdio settings sidecar binds a
5
- random free port per spawn — every session is a fresh origin that starts
6
- empty. The server-side copy here survives that: POSTs land in
7
- ``theme_prefs.json`` next to the other settings files, and the page
8
- handler seeds them back into the served HTML (``server-prefs`` head
9
- script) so a fresh origin paints with the user's saved choices.
4
+ but localStorage is origin-scoped and the stdio settings sidecar's port
5
+ can change — historically on every spawn, now (#2131) only on the first
6
+ spawn, a lost ``ui.state``, a pin change, or a remembered port another
7
+ process took — and each port change is a fresh origin that starts
8
+ empty. The
9
+ server-side copy here survives that: POSTs land in ``theme_prefs.json``
10
+ next to the other settings files, and the page handler seeds them back
11
+ into the served HTML (``server-prefs`` head script) so a fresh origin
12
+ paints with the user's saved choices.
10
13
 
11
14
  Kept as a leaf module (no imports from the settings_ui package) so the
12
15
  handler families and ``__init__`` can depend on it without cycles.
@@ -63,7 +63,7 @@
63
63
  "policies.pending.empty": "No pending approvals.",
64
64
  "policies.rules.title": "Gated tools",
65
65
  "policies.rules.empty": "No tools currently security-gated. Enable per-tool gating from the <a href=\"#\" data-panel-link=\"tools\">Tools</a> tab.",
66
- "accessibility.intro": "These settings apply immediately and are saved in this browser and on the server, so they survive restarts in every mode (including stdio, where the settings page moves to a fresh port each session). The docs site offers the same controls in its navigation bar; it has no server and saves per browser.",
66
+ "accessibility.intro": "These settings apply immediately and are saved in this browser and on the server, so they survive restarts in every mode (including stdio, where the settings page can occasionally move to a new port). The docs site offers the same controls in its navigation bar; it has no server and saves per browser.",
67
67
  "accessibility.storage_blocked": "Your browser is blocking site storage; choices apply for this session only.",
68
68
  "accessibility.theme.title": "Theme",
69
69
  "accessibility.theme.help": "One-click color schemes. Auto, the default, follows your OS preference and flips live when it changes.",
@@ -420,7 +420,7 @@
420
420
  "advanced.extra_yaml_write_keys.label": "Extra YAML write keys",
421
421
  "advanced.extra_yaml_write_keys.help": "Comma-separated top-level keys ha_config_set_yaml may write in addition to the built-in ones, for YAML-first integrations on this install (e.g. alert2). Keys that redefine Home Assistant's own trust boundary can never be added and are ignored. Requires custom component 1.2.4 or newer.",
422
422
  "advanced.sidecar_pin_port.label": "Settings UI sidecar port",
423
- "advanced.sidecar_pin_port.help": "0 chooses a free port each restart; 1024–65535 pins a preferred port. Restart required.",
423
+ "advanced.sidecar_pin_port.help": "0 picks a free port on first start and keeps it for later restarts; 1024–65535 pins a preferred port (falls back to a free one if taken). Restart required.",
424
424
  "advanced.enable_dev_mode.label": "Developer mode",
425
425
  "advanced.enable_dev_mode.help": "⚠ Registers hidden tools that can change settings and replace the running server. Development and testing only. Restart required.",
426
426
  "addon.backup_hint.name": "Full-HA snapshot suggestion level (LLM-facing)",
@@ -15,12 +15,14 @@
15
15
  system sans-serif. -->
16
16
  <script data-purpose="server-prefs" data-prefs="__HA_MCP_THEME_PREFS__">
17
17
  // #1574 review: theme/accessibility prefs also persist server-side
18
- // (theme_prefs.json) because stdio respawns its settings sidecar on a
19
- // random port — a fresh origin whose localStorage starts empty. The page
20
- // handler substitutes the data-prefs attribute at request time; this
21
- // seeds only MISSING localStorage keys (the browser's own latest choice
22
- // wins) before the anti-FOUC resolver below reads them. The docs site is
23
- // a static build and stays localStorage-only.
18
+ // (theme_prefs.json) because the stdio sidecar's port — stable by
19
+ // default since #2131, but fresh on first spawn, a lost ui.state, a
20
+ // pin change, or a taken remembered port — is the browser origin, and
21
+ // a new origin's localStorage starts empty. The page handler
22
+ // substitutes the data-prefs attribute at request time; this seeds
23
+ // only MISSING localStorage keys (the browser's own latest choice
24
+ // wins) before the anti-FOUC resolver below reads them. The docs site
25
+ // is a static build and stays localStorage-only.
24
26
  (function () {
25
27
  var el = document.currentScript;
26
28
  var raw = el ? el.getAttribute("data-prefs") : "";
@@ -366,10 +368,10 @@
366
368
  <div class="panel" id="panel-accessibility" role="tabpanel" aria-labelledby="tab-accessibility" tabindex="0">
367
369
  <p class="tool-desc" style="margin-bottom:16px" data-i18n="accessibility.intro">
368
370
  These settings apply immediately and are saved in this browser and on the
369
- server, so they survive restarts in every mode (including stdio, where the
370
- settings page moves to a fresh port each session). The docs site offers
371
- the same controls in its navigation bar; it has no server and saves per
372
- browser.
371
+ server, so they survive restarts in every mode (including stdio, where
372
+ the settings page can occasionally move to a new port). The docs site
373
+ offers the same controls in its navigation bar; it has no server and
374
+ saves per browser.
373
375
  </p>
374
376
  <p class="a11y-contrast-warning" id="a11y-storage-note" hidden data-i18n="accessibility.storage_blocked">
375
377
  Your browser is blocking site storage; choices apply for this session
@@ -3534,7 +3534,7 @@ const ADVANCED_FIELD_META = {
3534
3534
  code_mode_max_invocations: { label: "Code-mode max invocations", help: "API/tool-call cap per sandbox run. Restart required." },
3535
3535
  code_mode_saved_tools_path:{ label: "Saved-tools path", help: "JSON file where ha_manage_custom_tool persists saved tools across restarts. Restart required." },
3536
3536
  extra_yaml_write_keys: { label: "Extra YAML write keys", help: "Comma-separated top-level keys ha_config_set_yaml may write in addition to the built-in ones, for YAML-first integrations on this install (e.g. alert2). Keys that redefine Home Assistant's own trust boundary can never be added and are ignored. Requires custom component 1.2.4 or newer." },
3537
- sidecar_pin_port: { label: "Settings UI sidecar port", help: "0 = a new free port each restart (default); set 1024–65535 to pin a fixed port so the settings URL stays stable across restarts. Falls back to a free port if the pinned one is busy. Restart required." },
3537
+ sidecar_pin_port: { label: "Settings UI sidecar port", help: "0 picks a free port on first start and keeps it for later restarts; 1024–65535 pins a preferred port (falls back to a free one if taken). Restart required." },
3538
3538
  enable_dev_mode: { label: "Developer mode", help: "⚠ DANGER: registers hidden developer tools (ha_dev_manage_server, ha_dev_manage_settings) that let AI agents change server settings and replace the running server version (e.g. install a PR build). For development and testing only. Restart required." },
3539
3539
  };
3540
3540
 
@@ -4020,8 +4020,10 @@ loadFsCustomPaths();
4020
4020
 
4021
4021
  // #1574 review: localStorage is the synchronous store the anti-FOUC
4022
4022
  // script reads at paint time, but it is origin-scoped and the stdio
4023
- // sidecar binds a fresh random port (= fresh empty origin) per session.
4024
- // This hook therefore (a) mirrors every change to the server
4023
+ // sidecar's port (= the origin) can still change: stable by default
4024
+ // since #2131, yet fresh on first spawn, a lost ui.state, a pin
4025
+ // change, or a taken remembered port. This hook therefore (a) mirrors
4026
+ // every change to the server
4025
4027
  // (./api/settings/theme -> theme_prefs.json), which seeds the next
4026
4028
  // fresh origin via the server-prefs head script, and (b) surfaces a
4027
4029
  // blocked localStorage (private mode) once instead of silently losing