ha-mcp-dev 7.4.1.dev412__tar.gz → 7.4.1.dev414__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 (105) hide show
  1. {ha_mcp_dev-7.4.1.dev412/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.4.1.dev414}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_addons.py +2 -4
  4. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_bug_report.py +177 -7
  5. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_config_dashboards.py +273 -63
  6. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/util_helpers.py +4 -0
  7. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  8. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/LICENSE +0 -0
  9. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/MANIFEST.in +0 -0
  10. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/README.md +0 -0
  11. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/setup.cfg +0 -0
  12. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/__init__.py +0 -0
  13. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/__main__.py +0 -0
  14. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/_pypi_marker +0 -0
  15. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/_version.py +0 -0
  16. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/auth/__init__.py +0 -0
  17. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/auth/consent_form.py +0 -0
  18. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/auth/provider.py +0 -0
  19. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/client/__init__.py +0 -0
  20. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/client/rest_client.py +0 -0
  21. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/client/websocket_client.py +0 -0
  22. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/client/websocket_listener.py +0 -0
  23. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/config.py +0 -0
  24. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/errors.py +0 -0
  25. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/py.typed +0 -0
  26. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  27. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  28. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  29. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  30. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  31. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  32. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  33. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  34. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  35. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  36. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  37. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  38. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  39. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  40. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  41. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  42. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  43. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  44. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  45. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  46. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/server.py +0 -0
  47. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/settings_ui.py +0 -0
  48. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/smoke_test.py +0 -0
  49. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/__init__.py +0 -0
  50. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/backup.py +0 -0
  51. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  52. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/device_control.py +0 -0
  53. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/enhanced.py +0 -0
  54. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/helpers.py +0 -0
  55. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/reference_validator.py +0 -0
  56. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/registry.py +0 -0
  57. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/smart_search.py +0 -0
  58. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_areas.py +0 -0
  59. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  60. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_calendar.py +0 -0
  61. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_camera.py +0 -0
  62. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_categories.py +0 -0
  63. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  64. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  65. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  66. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  67. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_energy.py +0 -0
  68. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_entities.py +0 -0
  69. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  70. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_groups.py +0 -0
  71. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_hacs.py +0 -0
  72. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_history.py +0 -0
  73. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_integrations.py +0 -0
  74. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_labels.py +0 -0
  75. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  76. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_registry.py +0 -0
  77. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_resources.py +0 -0
  78. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_search.py +0 -0
  79. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_service.py +0 -0
  80. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_services.py +0 -0
  81. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_system.py +0 -0
  82. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_todo.py +0 -0
  83. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_traces.py +0 -0
  84. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_updates.py +0 -0
  85. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_utility.py +0 -0
  86. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  87. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  88. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/tools/tools_zones.py +0 -0
  89. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/transforms/__init__.py +0 -0
  90. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/transforms/categorized_search.py +0 -0
  91. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/utils/__init__.py +0 -0
  92. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/utils/config_hash.py +0 -0
  93. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/utils/domain_handlers.py +0 -0
  94. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  95. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/utils/operation_manager.py +0 -0
  96. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/utils/python_sandbox.py +0 -0
  97. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp/utils/usage_logger.py +0 -0
  98. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  99. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  100. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  101. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  102. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  103. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/tests/__init__.py +0 -0
  104. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/tests/test_constants.py +0 -0
  105. {ha_mcp_dev-7.4.1.dev412 → ha_mcp_dev-7.4.1.dev414}/tests/test_env_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.4.1.dev412
3
+ Version: 7.4.1.dev414
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 = "7.4.1.dev412"
7
+ version = "7.4.1.dev414"
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"
@@ -35,6 +35,7 @@ from .helpers import (
35
35
  log_tool_usage,
36
36
  raise_tool_error,
37
37
  )
38
+ from .util_helpers import ANSI_ESCAPE_RE
38
39
 
39
40
  logger = logging.getLogger(__name__)
40
41
 
@@ -45,9 +46,6 @@ _MAX_RESPONSE_SIZE = 50 * 1024
45
46
  # can lower this but never raise it.
46
47
  _MAX_WS_MESSAGES = 1000
47
48
 
48
- # ANSI escape code pattern for stripping terminal colors from addon output
49
- _ANSI_ESCAPE_RE = re.compile(r"\x1b\[[0-9;]*[a-zA-Z]")
50
-
51
49
  # Substrings that flag a WebSocket message as "signal" for the summarize pass.
52
50
  # Keep conservative: false negatives get elided, false positives just mean
53
51
  # no elision. Case-insensitive match on the JSON-stringified message.
@@ -661,7 +659,7 @@ async def _call_addon_ws(
661
659
  continue
662
660
 
663
661
  # Strip ANSI escape codes
664
- clean = _ANSI_ESCAPE_RE.sub("", message)
662
+ clean = ANSI_ESCAPE_RE.sub("", message)
665
663
  collected.append(clean)
666
664
  total_size += len(clean)
667
665
 
@@ -8,11 +8,13 @@ on how to create effective bug reports.
8
8
  import logging
9
9
  import os
10
10
  import platform
11
+ import re
11
12
  import sys
12
13
  from pathlib import Path
13
14
  from typing import Annotated, Any
14
15
  from urllib.parse import quote_plus
15
16
 
17
+ import httpx
16
18
  from pydantic import Field
17
19
 
18
20
  from ha_mcp import __version__
@@ -23,6 +25,7 @@ from ..utils.usage_logger import (
23
25
  get_startup_logs,
24
26
  )
25
27
  from .helpers import log_tool_usage
28
+ from .util_helpers import ANSI_ESCAPE_RE
26
29
 
27
30
  logger = logging.getLogger(__name__)
28
31
 
@@ -30,6 +33,25 @@ logger = logging.getLogger(__name__)
30
33
  RUNTIME_BUG_URL = "https://github.com/homeassistant-ai/ha-mcp/issues/new?template=runtime_bug.yml"
31
34
  AGENT_BEHAVIOR_URL = "https://github.com/homeassistant-ai/ha-mcp/issues/new?template=agent_behavior.yml"
32
35
 
36
+ # Max characters to include from addon container logs.
37
+ # 3000 chars ≈ 750 LLM tokens — keeps the tool response well below context budgets
38
+ # while still capturing enough recent output to diagnose most issues.
39
+ _ADDON_LOG_MAX_CHARS = 3000
40
+
41
+ # IPv4 sanitization: only redact addresses with strong network context so that
42
+ # four-segment version strings (e.g. "ha-mcp version 1.2.3.4") are preserved.
43
+ _IPV4_OCTET = r"(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)"
44
+ _IPV4 = rf"(?:{_IPV4_OCTET}\.){{3}}{_IPV4_OCTET}"
45
+ # IP followed by :port or /CIDR — always a network address, never a version.
46
+ _IPV4_WITH_PORT_OR_CIDR_RE = re.compile(rf"\b{_IPV4}(?::\d+|/\d{{1,2}})\b(?!\.\d)")
47
+ # IP preceded by a network keyword (from, to, host=, addr=, etc.).
48
+ _IPV4_AFTER_KEYWORD_RE = re.compile(
49
+ rf"\b((?:from|to|host|hostname|addr|address|ip|src|dst|server|client|peer|via)\b\s*[=:]?\s*){_IPV4}\b(?!\.\d)",
50
+ re.IGNORECASE,
51
+ )
52
+ # IP appearing inside a URL (`scheme://1.2.3.4...`).
53
+ _IPV4_IN_URL_RE = re.compile(rf"(://){_IPV4}\b(?!\.\d)")
54
+
33
55
 
34
56
  def _detect_installation_method() -> str:
35
57
  """
@@ -81,6 +103,113 @@ def _detect_platform() -> dict[str, str]:
81
103
  }
82
104
 
83
105
 
106
+ def _sanitize_log_text(text: str) -> str:
107
+ """Best-effort secret scrubber for log text.
108
+
109
+ Defense-in-depth, not exhaustive — bug reports still pass through human
110
+ review (see ``_generate_anonymization_guide``). Rules cover the most common
111
+ leak shapes seen in HA add-on logs:
112
+ JWTs, bearer tokens, long hex tokens, ``key=value`` style credentials,
113
+ URL userinfo, and IPv4 addresses with network context.
114
+ """
115
+ # JWT tokens (header.payload.signature)
116
+ text = re.sub(
117
+ r"eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+",
118
+ "[REDACTED_JWT]",
119
+ text,
120
+ )
121
+ # Bearer tokens — match any casing (BEARER, Bearer, bearer, BeArEr, …)
122
+ # via re.IGNORECASE, but preserve the original casing in the output by
123
+ # echoing m.group(1) back through the lambda.
124
+ text = re.sub(
125
+ r"\b(bearer)\s+\S+",
126
+ lambda m: f"{m.group(1)} [REDACTED]",
127
+ text,
128
+ flags=re.IGNORECASE,
129
+ )
130
+ # Generic key=value credentials (api_key, token, secret, password, etc.).
131
+ # Negative lookbehind for a letter so OPENAI_API_KEY=... still matches
132
+ # (underscore is a word-char, so \b doesn't fire there).
133
+ # "authorization" is intentionally omitted — the Bearer rule above already
134
+ # handles "Authorization: Bearer ..." and overlapping rules double-tap.
135
+ text = re.sub(
136
+ r"(?<![A-Za-z])(api[_-]?key|access[_-]?key|secret[_-]?key|token|secret|password|passwd)\b(\s*[:=]\s*)\S+",
137
+ r"\1\2[REDACTED]",
138
+ text,
139
+ flags=re.IGNORECASE,
140
+ )
141
+ # URL userinfo: scheme://user:password@host -> scheme://user:[REDACTED]@host
142
+ text = re.sub(
143
+ r"([a-zA-Z][a-zA-Z0-9+.-]*://)([^:/?#\s@]+):([^@/\s]+)@",
144
+ r"\1\2:[REDACTED]@",
145
+ text,
146
+ )
147
+ # Long hex strings (API keys, tokens) - 32+ contiguous hex chars
148
+ text = re.sub(
149
+ r"(?<![a-fA-F0-9])[a-fA-F0-9]{32,}(?![a-fA-F0-9])",
150
+ "[REDACTED_HEX]",
151
+ text,
152
+ )
153
+ # IPv4 addresses — only when there's strong network context, so that
154
+ # four-segment version strings (e.g. "version 1.2.3.4") survive intact.
155
+ text = _IPV4_WITH_PORT_OR_CIDR_RE.sub("[IP]", text)
156
+ text = _IPV4_IN_URL_RE.sub(r"\1[IP]", text)
157
+ text = _IPV4_AFTER_KEYWORD_RE.sub(r"\1[IP]", text)
158
+ return text
159
+
160
+
161
+ async def _fetch_addon_logs() -> str:
162
+ """Fetch ha-mcp addon container logs via the Supervisor REST API.
163
+
164
+ Only works when running as a Home Assistant add-on (SUPERVISOR_TOKEN set).
165
+ Uses /addons/self/logs which resolves to the calling addon's own logs via
166
+ the Supervisor's per-addon token binding — no slug interpolation needed.
167
+
168
+ Direct httpx against ``http://supervisor`` is the documented add-on access
169
+ pattern: it uses the Supervisor token directly (no extra HA hop) and
170
+ preserves the ``self`` shortcut, which the WebSocket ``supervisor/api``
171
+ proxy used by other tools may not.
172
+
173
+ Returns sanitized log text (last _ADDON_LOG_MAX_CHARS chars, with a
174
+ truncation marker prepended when truncation occurs), or empty string on
175
+ failure.
176
+ """
177
+ # Redundant with the caller's `install_method == "addon"` gate, but kept
178
+ # as a defensive guard for any direct callers added later.
179
+ token = os.environ.get("SUPERVISOR_TOKEN", "")
180
+ if not token:
181
+ return ""
182
+
183
+ try:
184
+ async with httpx.AsyncClient(timeout=10.0) as http_client:
185
+ resp = await http_client.get(
186
+ "http://supervisor/addons/self/logs",
187
+ headers={"Authorization": f"Bearer {token}"},
188
+ )
189
+ if resp.status_code != 200:
190
+ logger.info(
191
+ "Addon log fetch returned HTTP %s", resp.status_code
192
+ )
193
+ return ""
194
+
195
+ # Strip ANSI escape codes first, then sanitize, then truncate.
196
+ # Sanitizing before truncating prevents secrets that straddle the
197
+ # truncation boundary from leaking through.
198
+ cleaned = ANSI_ESCAPE_RE.sub("", resp.text)
199
+ sanitized = _sanitize_log_text(cleaned)
200
+ if len(sanitized) > _ADDON_LOG_MAX_CHARS:
201
+ marker = (
202
+ f"[...truncated, showing last {_ADDON_LOG_MAX_CHARS} of "
203
+ f"{len(sanitized)} chars...]\n"
204
+ )
205
+ return marker + sanitized[-_ADDON_LOG_MAX_CHARS:]
206
+ return sanitized
207
+ except httpx.RequestError as e:
208
+ logger.warning(f"Failed to fetch addon logs: {e}")
209
+
210
+ return ""
211
+
212
+
84
213
  def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
85
214
  """Register bug report tools with the MCP server."""
86
215
 
@@ -140,7 +269,12 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
140
269
  - "That was inefficient"
141
270
 
142
271
  **OUTPUT:**
143
- Returns both templates. Choose the appropriate one based on context.
272
+ Returns both templates plus diagnostic data. Key fields:
273
+ - `runtime_bug_template`, `agent_behavior_template` — pick based on context
274
+ - `recent_logs`, `startup_logs` — captured ha-mcp tool/server log entries
275
+ - `addon_logs` — addon container stdout/stderr (HA add-on installs only;
276
+ empty string otherwise)
277
+ - `suggested_title`, `duplicate_check_urls`, `anonymization_guide`
144
278
  """
145
279
  # Detect installation method and platform
146
280
  install_method = _detect_installation_method()
@@ -184,6 +318,11 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
184
318
  # Get startup logs (first minute of server operation)
185
319
  startup_logs = get_startup_logs()
186
320
 
321
+ # Fetch addon container logs when running as HA add-on
322
+ addon_logs = ""
323
+ if install_method == "addon":
324
+ addon_logs = await _fetch_addon_logs()
325
+
187
326
  # Format logs for inclusion (sanitized summary)
188
327
  log_summary = _format_logs_for_report(recent_logs)
189
328
  startup_log_summary = _format_startup_logs(startup_logs)
@@ -221,11 +360,19 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
221
360
  log_summary,
222
361
  ])
223
362
 
363
+ if addon_logs:
364
+ report_lines.extend([
365
+ "",
366
+ "=== Add-on Container Logs ===",
367
+ addon_logs,
368
+ ])
369
+
224
370
  formatted_report = "\n".join(report_lines)
225
371
 
226
372
  # Generate BOTH templates
227
373
  runtime_bug_template = _generate_runtime_bug_template(
228
- diagnostic_info, log_summary, startup_log_summary, recent_logs, startup_logs
374
+ diagnostic_info, log_summary, startup_log_summary, recent_logs, startup_logs,
375
+ addon_logs=addon_logs,
229
376
  )
230
377
 
231
378
  agent_behavior_template = _generate_agent_behavior_template(
@@ -250,6 +397,7 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
250
397
  "diagnostic_info": diagnostic_info,
251
398
  "recent_logs": recent_logs,
252
399
  "startup_logs": startup_logs,
400
+ "addon_logs": addon_logs,
253
401
  "log_count": len(recent_logs),
254
402
  "startup_log_count": len(startup_logs),
255
403
  "formatted_report": formatted_report,
@@ -291,7 +439,8 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
291
439
  f" - Runtime bugs: {RUNTIME_BUG_URL}\n"
292
440
  f" - Agent behavior: {AGENT_BEHAVIOR_URL}\n"
293
441
  " d. Ask them to fill in the description sections\n"
294
- " e. Remind them to review for any remaining personal information before submitting\n\n"
442
+ " e. For HA add-on installs, the runtime bug template includes a collapsible '📦 Add-on Container Logs' section auto-filled from addon_logs — keep it as-is\n"
443
+ " f. Remind them to review for any remaining personal information before submitting\n\n"
295
444
  "CRITICAL: Always ANONYMIZE the report BEFORE presenting it in markdown code blocks!"
296
445
  ),
297
446
  }
@@ -312,8 +461,8 @@ def _format_logs_for_report(logs: list[dict[str, Any]]) -> str:
312
461
 
313
462
  line = f" {timestamp} | {tool_name} | {success} | {exec_time:.0f}ms"
314
463
  if error:
315
- # Truncate error to avoid leaking sensitive info
316
- error_short = str(error)[:100]
464
+ # Sanitize before truncating so secrets straddling the cut survive redaction.
465
+ error_short = _sanitize_log_text(str(error))[:100]
317
466
  line += f" | Error: {error_short}"
318
467
  lines.append(line)
319
468
 
@@ -332,7 +481,8 @@ def _format_startup_logs(logs: list[dict[str, Any]]) -> str:
332
481
  logger_name = log.get("logger", "")
333
482
  message = log.get("message", "")
334
483
 
335
- # Truncate long messages
484
+ # Sanitize before truncating so secrets straddling the cut survive redaction.
485
+ message = _sanitize_log_text(message)
336
486
  if len(message) > 200:
337
487
  message = message[:200] + "..."
338
488
 
@@ -447,6 +597,8 @@ def _generate_runtime_bug_template(
447
597
  startup_log_summary: str,
448
598
  recent_logs: list[dict[str, Any]],
449
599
  startup_logs: list[dict[str, Any]],
600
+ *,
601
+ addon_logs: str = "",
450
602
  ) -> str:
451
603
  """
452
604
  Generate a runtime bug report template matching runtime_bug.md format.
@@ -475,6 +627,24 @@ def _generate_runtime_bug_template(
475
627
  {startup_log_summary}
476
628
  ```
477
629
 
630
+ </details>
631
+ """
632
+
633
+ # Show addon container logs section only when available (addon installs only)
634
+ addon_section = ""
635
+ if addon_logs:
636
+ addon_section = f"""
637
+ ---
638
+
639
+ ## 📦 Add-on Container Logs
640
+
641
+ <details>
642
+ <summary>Click to expand ha-mcp add-on logs</summary>
643
+
644
+ ```
645
+ {addon_logs}
646
+ ```
647
+
478
648
  </details>
479
649
  """
480
650
 
@@ -539,7 +709,7 @@ def _generate_runtime_bug_template(
539
709
  ```
540
710
 
541
711
  </details>
542
- {startup_section}
712
+ {startup_section}{addon_section}
543
713
  ---
544
714
 
545
715
  ## 💡 Additional Context