# Shell-environment feature hooks: the `_bashEnvFragments` # accumulator, the `/etc/hyperhive/bash-env.sh` file it renders to, # and the cargo `--message-format short` injector that contributes to # it. Loaded via `$BASH_ENV` for non-interactive shells (claude's # `Bash` tool runs `bash -c`) and via `programs.bash` for interactive # ones. { lib, config, ... }: { # Internal accumulator for shell snippets that should land in # `/etc/hyperhive/bash-env.sh`. Per-feature hooks set this via # `lib.mkIf` gated on their own option; the lines type merges # all contributions across modules into one file. Generic by # design so future hooks don't need to rename this file or # invent a parallel dispatcher. options.hyperhive._bashEnvFragments = lib.mkOption { type = lib.types.lines; default = ""; internal = true; description = '' Shell snippets concatenated into `/etc/hyperhive/bash-env.sh`. Feature hooks contribute via `lib.mkIf` gated on their own option. When empty, the file isn't created, `BASH_ENV` stays unset, and the interactive bashrc hook is omitted — zero cost when no feature is on. Internal — set indirectly via the per-feature options that own the gate (e.g. `hyperhive.cargo.shortMessages`). ''; }; options.hyperhive.cargo.shortMessages = lib.mkOption { type = lib.types.bool; default = true; example = false; description = '' Auto-inject `--message-format short` on cargo compile subcommands (`build`, `check`, `clippy`, `test`, `run`, `doc`, `bench`, `install`, `rustc`, `fix`) when claude (or anything else) invokes `cargo` inside this container. Saves tokens + context — the verbose default output floods the response window with per-crate progress lines that carry no signal beyond the warning/error summary. Implementation: contributes a `cargo` shell function to `/etc/hyperhive/bash-env.sh` (see `hyperhive._bashEnvFragments`). Loaded via `BASH_ENV` for non-interactive shells (`bash -c` — what the claude `Bash` tool runs) and sourced from `programs.bash.interactiveShellInit` for interactive shells. The function: - handles the `+toolchain` selector prefix (`cargo +nightly build` works); - passes through cleanly when the caller already specified `--message-format` (any form); - leaves non-compile subcommands (`new`, `add`, `search`, third-party `cargo-*` subcommands) untouched so they don't error on the unknown flag. Set to `false` for agents that need full cargo output (e.g. tooling that parses `--message-format json` programmatically and doesn't pass the flag explicitly). ''; }; config = { # Cargo `--message-format short` injector. `command cargo …` falls # back to the un-wrapped binary in PATH (the rust toolchain's cargo # — either from `environment.systemPackages` or from whatever # `nix develop` shell the agent's working in). hyperhive._bashEnvFragments = lib.mkIf config.hyperhive.cargo.shortMessages '' # Auto-injects --message-format short on cargo compile # subcommands so per-crate progress lines don't flood # claude's context. Bypassed when the caller already passes # --message-format (any form). cargo() { # Strip leading +toolchain selectors (cargo +nightly …). local pre=() while [ "''${1:0:1}" = "+" ] && [ -n "''${1:-}" ]; do pre+=("$1") shift done case "''${1:-}" in build|check|clippy|test|run|doc|bench|install|rustc|fix) local sub="$1" shift local arg for arg in "$@"; do case "$arg" in --message-format|--message-format=*) command cargo "''${pre[@]}" "$sub" "$@" return $? ;; esac done command cargo "''${pre[@]}" "$sub" --message-format short "$@" ;; *) command cargo "''${pre[@]}" "$@" ;; esac } ''; # Single bash-env file with all configured shell fragments. # Wiring is gated on at least one fragment being active so a # fully feature-disabled agent has neither the file nor the # `BASH_ENV` / interactive sourcing — zero cost in that case. environment.etc."hyperhive/bash-env.sh" = lib.mkIf (config.hyperhive._bashEnvFragments != "") { text = config.hyperhive._bashEnvFragments; }; # Non-interactive bash invocations (claude's `Bash` tool runs # `bash -c`) source $BASH_ENV at startup — drops every active # feature hook's snippet into scope without touching # `/etc/profile` (login-only). environment.variables = lib.mkIf (config.hyperhive._bashEnvFragments != "") { BASH_ENV = "/etc/hyperhive/bash-env.sh"; }; # Interactive shells don't honour BASH_ENV — wire the same file # in via the bashrc hook so operator SSH sessions get the same # hook surface as claude's non-interactive calls. Gated on at # least one fragment being active so we don't write a no-op # source line into `/etc/bashrc` on fully-feature-disabled agents. programs.bash.interactiveShellInit = lib.mkIf (config.hyperhive._bashEnvFragments != "") '' if [ -r /etc/hyperhive/bash-env.sh ]; then . /etc/hyperhive/bash-env.sh fi ''; }; }