enable trollshell pet

This commit is contained in:
müde 2026-07-31 20:58:54 +02:00
commit a7c7df8bf8

View file

@ -1,9 +1,97 @@
{ ... }:
{
config,
lib,
pkgs,
trollshell,
...
}:
let
trollshellPkgs = trollshell.packages.${pkgs.stdenv.hostPlatform.system};
# The claude bridge's loopback port, shared between the daemon and the pet
# that talks to it so there is one number to change. The address is fixed at
# 127.0.0.1 by the bridge and is not configurable: the bridge validates no
# bearer token at all, so reachability *is* the authorization boundary.
bridgePort = 8787;
in
{
programs.trollshell = {
enable = true;
weather.fallbackCity = "Berlin";
cliphist.enable = true;
stats.layout = "split";
plugins = {
pet = {
enable = true;
package = trollshellPkgs.hytte-plugin-pet;
env = {
PET_NAME = "foo";
PET_LLM_URL = "http://127.0.0.1:${toString bridgePort}";
# Not a real key. `hytte_ai_providers::load_key` checks this env
# override *before* ~/.config/trollshell/openrouter.key, so this dummy
# is what stops a genuine OpenRouter key being shipped to a loopback
# port. It is a security control, not a placeholder — don't drop it.
OPENROUTER_API_KEY = "local-bridge";
};
};
departures = {
enable = true;
package = trollshellPkgs.hytte-plugin-departures;
};
usage = {
enable = true;
package = trollshellPkgs.hytte-plugin-usage;
};
weather = {
enable = true;
package = trollshellPkgs.hytte-plugin-weather;
};
};
};
# The keyless loopback shim that puts an OpenAI-compatible face on headless
# `claude --print`, so pet rides the Claude Code subscription instead of
# OpenRouter. trollshell ships the package and a reference unit under `etc/`
# but no module option for it, so the unit is declared here — without it the
# pet's PET_LLM_URL above points at nothing and the plugin stays canned-only.
systemd.user.services.trollshell-claude-bridge = {
Unit = {
Description = "Keyless loopback OpenAI-compatible bridge to headless Claude Code";
PartOf = [ config.programs.trollshell.systemd.target ];
After = [ config.programs.trollshell.systemd.target ];
};
Service = {
Type = "simple";
ExecStart = lib.getExe trollshellPkgs.hytte-claude-bridge;
Restart = "on-failure";
RestartSec = 5;
Environment = [
"RUST_LOG=hytte_claude_bridge=info"
"CLAUDE_BRIDGE_PORT=${toString bridgePort}"
# `claude --model` for the child. Worth pinning: the bridge's default
# per-request budget is 8s (it must stay under the client's 10s), and
# the child otherwise inherits the model from ~/.claude/settings.json —
# currently opus, which would blow that budget on nearly every reply.
"CLAUDE_BRIDGE_MODEL=claude-haiku-4-5"
"CLAUDE_BRIDGE_TIMEOUT_SECS=9"
# Belt-and-braces; the copy that actually prevents a leak is the one on
# the pet above, because load_key runs in the plugin's process.
"OPENROUTER_API_KEY=local-bridge"
# The bridge shells out to `claude`, which is the OTEL wrapper from
# ./claude.nix — so pet chatter is counted as workstation usage.
"PATH=${config.home.profileDirectory}/bin:/run/current-system/sw/bin"
];
# SECURITY CONTROL — do not drop. These four would silently move `claude`
# off the subscription and onto metered API credits (or Bedrock/Vertex).
# The bridge cannot scrub them itself (`std::env::remove_var` is unsafe
# under edition 2024 and that workspace forbids unsafe), so it fails
# closed instead: it *refuses to start* if it finds any of them set.
UnsetEnvironment = "ANTHROPIC_API_KEY ANTHROPIC_AUTH_TOKEN CLAUDE_CODE_USE_BEDROCK CLAUDE_CODE_USE_VERTEX";
};
Install.WantedBy = [ config.programs.trollshell.systemd.target ];
};
}