hydra

Terminal replacement for Loopback — virtual audio devices and routing on macOS, from a ratatui TUI.
Log | Files | Refs | README | LICENSE

commit 3e18dcf2055188a06a98262e8439e481e83975df
parent 52b4f527012273d89595572605c099c000e4052e
Author: Matthew Gantenbein <ganten1998@gmail.com>
Date:   Sun, 31 May 2026 17:41:05 -0500

build: one-command install.sh / uninstall.sh + README install section

install.sh orchestrates the full setup in safe order: cargo release build →
sign Hydra.app → load LaunchAgent → build+install driver (the sudo/coreaudiod
step is last and prompts). --no-driver stops before the virtual mic. Symlinks
a `hydra` launcher to /usr/local/bin. uninstall.sh reverses it. README now
leads with ./install.sh and the TUI keymap.

Verified the non-sudo stages run clean end to end: release binaries built,
Hydra.app signs, universal Hydra.driver builds. The sudo stages (agent load,
driver install) are intentionally left for the user to trigger.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Diffstat:
MREADME.md | 29+++++++++++++++++++++++------
Ainstall.sh | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Auninstall.sh | 32++++++++++++++++++++++++++++++++
3 files changed, 116 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md @@ -23,17 +23,34 @@ Clients talk to it over `~/Library/Application Support/hydra/hydrad.sock`. ## Status -**Phase 0 — skeleton.** Workspace builds; daemon answers `Ping`/`GetState`; TUI renders the -Navi-themed shell and shows live connection status. CoreAudio engine + driver land in P1–P3. +Working: per-app audio **capture → monitor** (verified on real hardware), **combine** +multiple apps into one route, live gain/mute, peak meters, route **persistence** across +restarts, a `query` subcommand + SketchyBar widget, and a buildable+signed virtual-audio +**driver** that makes "Hydra" selectable as an input in other apps. See `TESTING.md` for +what's verified vs. what needs your one-time approval. -## Run it +## Install ```sh -cargo run -p hydrad # terminal 1: start the daemon -cargo run -p hydra # terminal 2: the TUI (press q to quit, r to refresh) -cargo test # unit tests (model + wire-format round-trips + theme parsing) +./install.sh # build everything; prompts before the sudo driver step +./install.sh --no-driver # capture→speakers only (no sudo, no virtual mic yet) +./uninstall.sh # remove it all ``` +Then launch the control panel with `hydra`, and pick **Hydra** as the microphone in any +app to receive the routed audio. + +## Dev loop + +```sh +cargo run -p hydrad # terminal 1: the daemon (or run the signed bundle for capture TCC) +cargo run -p hydra # terminal 2: the TUI +cargo test # unit tests +``` + +TUI keys: `↑↓` select · `⏎` monitor · `␣` mark · `c` combine · `o` output · `a` all apps · +`⇥` switch pane · `m` mute · `+/-` gain · `d` stop · `q` quit. + Swap the theme: copy `themes/navi.toml` to `~/Library/Application Support/hydra/theme.toml`. ## License diff --git a/install.sh b/install.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# install.sh — one-command Hydra setup. +# +# Builds and installs everything in the right order, pausing before the steps that +# need sudo or restart audio so nothing happens behind your back: +# +# 1. build the Rust workspace (release) +# 2. build + sign Hydra.app (the daemon, with the audio-capture entitlement) +# 3. load the daemon as a per-user LaunchAgent (routing persists across logins) +# 4. build + install the Hydra virtual-audio driver ← sudo + ~1s audio glitch +# +# After this, "Hydra" is selectable as an input device in other apps, and the `hydra` +# TUI drives routing. Re-run any time; it's idempotent. +# +# Usage: +# ./install.sh # full install (prompts before the sudo driver step) +# ./install.sh --no-driver # everything except the driver (capture→speakers only) +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")" && pwd)" +WITH_DRIVER=1 +[ "${1:-}" = "--no-driver" ] && WITH_DRIVER=0 + +say() { printf '\n\033[1;36m▸ %s\033[0m\n' "$1"; } + +say "1/4 Building the Rust workspace (release)" +cargo build --manifest-path "$ROOT/Cargo.toml" --release --workspace + +say "2/4 Building + signing Hydra.app" +"$ROOT/scripts/bundle.sh" release + +say "3/4 Loading the daemon as a LaunchAgent" +"$ROOT/scripts/install-agent.sh" + +# Install a `hydra` launcher on PATH for convenience (best-effort). +if [ -w /usr/local/bin ] || sudo -n true 2>/dev/null; then + say "Installing the 'hydra' command to /usr/local/bin" + sudo ln -sf "$ROOT/target/release/hydra" /usr/local/bin/hydra 2>/dev/null \ + || ln -sf "$ROOT/target/release/hydra" /usr/local/bin/hydra 2>/dev/null \ + || echo " (skipped — add $ROOT/target/release to PATH yourself)" +fi + +if [ "$WITH_DRIVER" = "1" ]; then + say "4/4 Building the Hydra virtual-audio driver" + "$ROOT/scripts/build-driver.sh" + say "Installing the driver (needs sudo; restarts coreaudiod, ~1s audio glitch)" + "$ROOT/scripts/install-driver.sh" +else + say "4/4 Skipped driver install (--no-driver). Run ./scripts/install-driver.sh later." +fi + +cat <<DONE + +✓ Hydra installed. + + Launch the control panel: hydra (or: cargo run -p hydra) + In another app, pick "Hydra" as the microphone/input to receive routed audio. + + Daemon logs: /tmp/hydrad.err.log + Uninstall: ./uninstall.sh +DONE diff --git a/uninstall.sh b/uninstall.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# uninstall.sh — remove everything install.sh set up. Reverses in safe order. +set -euo pipefail + +LABEL="com.ganten.hydrad" +HAL="/Library/Audio/Plug-Ins/HAL/Hydra.driver" + +say() { printf '\n\033[1;36m▸ %s\033[0m\n' "$1"; } + +say "Stopping + unloading the daemon LaunchAgent" +launchctl bootout "gui/$(id -u)/$LABEL" 2>/dev/null || true +rm -f "$HOME/Library/LaunchAgents/$LABEL.plist" + +say "Removing the 'hydra' command" +sudo rm -f /usr/local/bin/hydra 2>/dev/null || rm -f /usr/local/bin/hydra 2>/dev/null || true + +if [ -d "$HAL" ]; then + say "Removing the driver (sudo; restarts coreaudiod, ~1s audio glitch)" + sudo rm -rf "$HAL" + sudo launchctl kickstart -k system/com.apple.audio.coreaudiod +fi + +say "Removing the control socket" +rm -f "$HOME/Library/Application Support/hydra/hydrad.sock" + +cat <<DONE + +✓ Hydra uninstalled. + Kept: your config + theme at ~/Library/Application Support/hydra/ + the device manifest at /Library/Application Support/hydra/ (harmless) + Delete those by hand if you want a clean slate. +DONE