mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-08-22 12:53:32 +02:00
fix(claude_desktop): address ha-cli review findings
- Check ha_mcp_token before SUPERVISOR_TOKEN: this add-on always sets homeassistant_api, so the admin-equivalent Supervisor token was always present and silently shadowed a user's deliberately scoped-down ha_mcp_token, defeating the documented scoping path (Codex P1). - Make ha-cli itself refuse to run when enable_ha_api_helper is false, instead of only removing the CLAUDE.md guidance text — disabling the option now actually disables the helper (Codex P2). - Normalize HA_BASE_URL to include /api when the user omits it, so REST calls don't 404 (CodeRabbit). - Read/write CLAUDE.md with explicit UTF-8 in the ha-api-helper removal block, matching the emoji/special characters Claude tends to write there (CodeRabbit). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -294,7 +294,7 @@ import re
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
path = Path(os.environ["CLAUDE_MD"])
|
path = Path(os.environ["CLAUDE_MD"])
|
||||||
text = path.read_text()
|
text = path.read_text(encoding="utf-8")
|
||||||
pattern = re.compile(
|
pattern = re.compile(
|
||||||
r"\n*<!-- BEGIN ha-api-helper \(managed by claude_desktop addon\) -->.*?"
|
r"\n*<!-- BEGIN ha-api-helper \(managed by claude_desktop addon\) -->.*?"
|
||||||
r"<!-- END ha-api-helper \(managed by claude_desktop addon\) -->\n?",
|
r"<!-- END ha-api-helper \(managed by claude_desktop addon\) -->\n?",
|
||||||
@@ -302,7 +302,7 @@ pattern = re.compile(
|
|||||||
)
|
)
|
||||||
new = pattern.sub("", text)
|
new = pattern.sub("", text)
|
||||||
if new != text:
|
if new != text:
|
||||||
path.write_text(new)
|
path.write_text(new, encoding="utf-8")
|
||||||
PY
|
PY
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,15 @@ therefore never reachable.
|
|||||||
Authentication and the base URL are resolved automatically, in this order:
|
Authentication and the base URL are resolved automatically, in this order:
|
||||||
|
|
||||||
1. $HA_BASE_URL + $HA_TOKEN explicit override (advanced/scoped)
|
1. $HA_BASE_URL + $HA_TOKEN explicit override (advanced/scoped)
|
||||||
2. $SUPERVISOR_TOKEN Supervisor Core-API proxy
|
2. `ha_mcp_token` in /data/options.json scoped user long-lived token -> :8123
|
||||||
(needs homeassistant_api: true,
|
3. $SUPERVISOR_TOKEN Supervisor Core-API proxy fallback
|
||||||
which this add-on sets — zero setup)
|
(admin-equivalent; needs
|
||||||
3. `ha_mcp_token` in /data/options.json user long-lived token -> :8123
|
homeassistant_api: true, which this
|
||||||
|
add-on sets — zero setup)
|
||||||
|
|
||||||
|
The scoped token is checked before the Supervisor fallback so setting
|
||||||
|
`ha_mcp_token` actually narrows access instead of being shadowed by the
|
||||||
|
always-present admin-equivalent Supervisor token.
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
ha-cli get <path> GET e.g. get config/automation/config/1700000000
|
ha-cli get <path> GET e.g. get config/automation/config/1700000000
|
||||||
@@ -49,19 +54,26 @@ def _load_option(name):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _helper_enabled():
|
||||||
|
"""Mirror config.yaml's enable_ha_api_helper default (true) when unset."""
|
||||||
|
value = _load_option("enable_ha_api_helper")
|
||||||
|
return value is not False
|
||||||
|
|
||||||
|
|
||||||
def resolve_endpoint():
|
def resolve_endpoint():
|
||||||
"""Return (rest_base, ws_url, token) for the best available auth path."""
|
"""Return (rest_base, ws_url, token) for the best available auth path."""
|
||||||
base = os.environ.get("HA_BASE_URL")
|
base = os.environ.get("HA_BASE_URL")
|
||||||
token = os.environ.get("HA_TOKEN")
|
token = os.environ.get("HA_TOKEN")
|
||||||
if base and token:
|
if base and token:
|
||||||
rest = base.rstrip("/")
|
rest = base.rstrip("/")
|
||||||
|
if not rest.endswith("/api"):
|
||||||
|
rest += "/api"
|
||||||
ws = rest.replace("http", "ws", 1).rsplit("/api", 1)[0] + "/api/websocket"
|
ws = rest.replace("http", "ws", 1).rsplit("/api", 1)[0] + "/api/websocket"
|
||||||
return rest, ws, token
|
return rest, ws, token
|
||||||
|
|
||||||
token = os.environ.get("SUPERVISOR_TOKEN")
|
# Checked before SUPERVISOR_TOKEN: this add-on always sets homeassistant_api,
|
||||||
if token:
|
# so the admin-equivalent Supervisor token is otherwise always present and
|
||||||
return "http://supervisor/core/api", "ws://supervisor/core/websocket", token
|
# would shadow a user's deliberately scoped-down ha_mcp_token.
|
||||||
|
|
||||||
token = _load_option("ha_mcp_token")
|
token = _load_option("ha_mcp_token")
|
||||||
if token:
|
if token:
|
||||||
return (
|
return (
|
||||||
@@ -70,9 +82,14 @@ def resolve_endpoint():
|
|||||||
token,
|
token,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
token = os.environ.get("SUPERVISOR_TOKEN")
|
||||||
|
if token:
|
||||||
|
return "http://supervisor/core/api", "ws://supervisor/core/websocket", token
|
||||||
|
|
||||||
sys.exit(
|
sys.exit(
|
||||||
"ha-cli: no credentials. Expected $SUPERVISOR_TOKEN (default inside the "
|
"ha-cli: no credentials. Expected ha_mcp_token in the add-on options "
|
||||||
"add-on), or $HA_BASE_URL+$HA_TOKEN, or ha_mcp_token in the add-on options."
|
"(scoped user), $SUPERVISOR_TOKEN (admin-equivalent fallback, default "
|
||||||
|
"inside the add-on), or an explicit $HA_BASE_URL+$HA_TOKEN override."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -170,6 +187,12 @@ def main(argv):
|
|||||||
print(__doc__)
|
print(__doc__)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
if not _helper_enabled():
|
||||||
|
sys.exit(
|
||||||
|
"ha-cli: disabled (enable_ha_api_helper is false in the add-on "
|
||||||
|
"options). Enable it there to let Claude configure Home Assistant."
|
||||||
|
)
|
||||||
|
|
||||||
rest_base, ws_url, token = resolve_endpoint()
|
rest_base, ws_url, token = resolve_endpoint()
|
||||||
cmd, args = argv[0], argv[1:]
|
cmd, args = argv[0], argv[1:]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user