Skip to content

Add the Home Assistant CLI on personal hosts - #135

Draft
gtbuchanan wants to merge 1 commit into
mainfrom
feature/home-assistant-cli
Draft

Add the Home Assistant CLI on personal hosts#135
gtbuchanan wants to merge 1 commit into
mainfrom
feature/home-assistant-cli

Conversation

@gtbuchanan

Copy link
Copy Markdown
Owner

Installs hass-cli on personal hosts and wires its credentials from the Bitwarden vault on Windows.

Install

Pinned in mise's global namespace as pipx:homeassistant-cli rather than installed per-platform. That namespace is otherwise reserved — dev toolchains stay in each project's mise.toml — and a user-facing application earns the exception: no project would ever pin it, so nothing downstream can be shadowed. uv is pinned alongside as the engine mise's pipx: backend installs through. On Termux aqua ships no android asset for uv, so the pkg build serves from PATH instead.

conf.d fragments

The global mise config moves from a single android-only config.toml to plain-TOML fragments under conf.d/, gated per-platform by their own .chezmoiignore.

The point of the split is Renovate. Its built-in mise manager parses these files directly and resolves both the pipx: (PyPI) and core datasources, so no version here needs a hand-written # renovate: annotation or a custom regex manager. Go template directives would make a file unparseable, which is why the platform split had to move out of the files and into the ignore. The manager needs one nudge — its built-in patterns key on a literal .config/mise/conf.d, which the chezmoi dot_config source path doesn't match — and managerFilePatterns is additive, so the repo-root mise.toml keeps matching by default.

The disable_tools rationale now sits beside each entry rather than in one block above the list, so a comment can't drift from the tool it explains.

Credentials

hass-cli reads its server and token from the environment on every invocation and has no config file, so something has to put them there. That something is mise's [env], not a shell wrapper.

A wrapper is the obvious design and it doesn't work: a function named hass-cli only exists in shells that loaded a profile, so agent harnesses, scripts, and any non-interactive shell resolve the mise shim instead, get nothing, and fall through to zeroconf discovery — Found no Home Assistant on local network. That's the common case, not an edge case. mise applies [env] in the shim path too, so activated shells, mise exec, scripts, Git Bash, and agents are all covered by one mechanism.

Two traps ruled out along the way, both recorded in the docs:

  • Dispatching through mise exec -- hass-cli looks tidier and is worse. In an activated shell mise is itself a function, and forwarding through it drops the standalone --, so mise's parser claims the first hass-cli flag colliding with one of its own (error: unexpected argument '--timeout' found). It fails only on colliding arguments, so it looks fine until it doesn't.
  • Passing --server / --token as flags would put a long-lived token in the command line, readable from any process listing.

hass-vault resolves the pair from the vault and caches it under DPAPI, mirroring the existing bw-session-windows.ps1 scheme — same protection, sliding idle window, expiry bound as the entropy. The cache is load-bearing: mise's computed-env cache is session-scoped and does not engage on the shims-only path non-interactive callers take, so an uncached resolver would pay a full vault round trip on every command, twice over (one exec() per variable).

Lookups end in || exit 0 so a locked vault, a renamed item, or an offline bw can't fail every mise command on the host. The tradeoff is that failure is quiet, since mise swallows an [env] exec's stderr — hass-vault check exists to diagnose that. Only check is safe to run by hand; token and server print a credential to stdout for mise to capture, so running one to test the setup would persist a long-lived token in a terminal, a log, or an agent transcript.

The vault item is found by search rather than a fixed id, so it stays renameable and no vault identifier is committed. An ambiguous match is refused rather than guessed.

Scope

Windows only for the credential half — the other platforms install the CLI but have no resolver yet, so the credential fragment isn't deployed there. That's unimplemented, not opted out: each platform needs its own resolver because the at-rest wrapping is bound to a platform API, and the fragment's gate widens as each one lands.

A gated note in user-level AGENTS.md tells agents the CLI is pre-authenticated, since the local doc doesn't reach agents working in other repos.

Verification

Exercised end to end on personal Windows against a live instance — PowerShell (activated and -NoProfile) and Git Bash, cold and warm cache, plus the fail-soft path with a deliberately bad item. hk check --all passes.

Android is unverified — the uv/pipx path there is reasoned from Termux's package index, not run.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

hass-cli is pinned in mise's global namespace rather than installed
per-platform. It earns that exception by not being a dev toolchain: no
project mise.toml would pin a user-facing application, so nothing
downstream can be shadowed by it. uv is pinned alongside it as the
engine mise's pipx backend installs through.

To keep the pin Renovate-tracked without hand-maintained annotations,
the global config moves from a single android-only config.toml to
plain-TOML fragments under conf.d/, gated per-platform by their own
.chezmoiignore. Renovate's built-in mise manager parses those directly,
so the only wiring is a file pattern for the chezmoi dot_config path.
Staying template-free is what buys that, which is why the platform
split had to move out of the files and into the ignore.

Credentials come from mise [env] rather than a shell wrapper. hass-cli
reads its server and token from the environment on every invocation
and has no config file, so a wrapper looked natural -- but one only
exists in shells that loaded a profile, leaving agents and scripts to
resolve the mise shim with nothing set. [env] is applied by the shim
too, so every caller is covered by one mechanism.

The resolver reads the vault and caches the pair under DPAPI, mirroring
the existing bw-session cache, because mise's computed-env cache does
not engage on the shims-only path that non-interactive callers take.
Lookups fail soft, so a locked vault cannot break every mise command on
the host. Only `check` is safe to run by hand; `token` and `server`
print a credential for mise to capture.

Windows only for now -- the other platforms install the CLI but have no
resolver yet, so the credential fragment is not deployed there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gtbuchanan
gtbuchanan force-pushed the feature/home-assistant-cli branch from 070ba79 to 9417ce0 Compare August 24, 2026 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant