Skip to content

Lockyc/warden

Repository files navigation

warden

warden

A curator for your terminals — windows, projects, and (mostly) muxers all the way down.

Release CI Platform Built with Rust Tauri License

warden running two windows (personal + work) — a curator-style sidebar with grouped project tabs (live/cold dots) over embedded libghostty terminals

warden is a config-driven terminal multiplexer. One TOML file is the source of truth: it defines windows and the project tabs inside them. warden materializes itself from that config and hot-reloads on save. Each window carries a colour + title banner for at-a-glance identity; each tab is a real terminal opened in a working directory, running an optional command.

warden is generic and content-agnostic — it knows nothing about any specific tool, so the command a tab runs is whatever you want: a shell, a TUI, a build watcher, an agent launcher. It stands on its own.

It's also built for a flow: I pair each tab with agentmux (amux), a tmux-based agent launcher — so the stack nests warden → agentmux → tmux (warden itself embedding libghostty as its terminal surfaces). A multiplexer for a multiplexer for a multiplexer; it's turtles the rest of the way down.

Targets macOS. Linux is a possible future direction, not a commitment; the config crate stays platform-neutral to keep that door open. Windows isn't addressed today — the terminal surface is a macOS-native embed — but it's unstarted, not ruled out.

Features

  • A window per [[window]] — native macOS windows, each with a colour + title banner, a curator-style draggable sidebar, and the terminal under an overlay titlebar. The Window menu lists every configured window — raise an open one or reopen one you've closed (⌘⇧T reopens the last closed).
  • Persistent, not last-window-quit — warden opens the windows you mark open_on_start (default all) and shows a home surface when none are open, listing every configured window so you can raise or reopen one with a click. It's a persistent app: closing the last window never quits it — ⌘Q does.
  • Project tabs — each tab is a real terminal in a working directory. load_on_open tabs spawn at launch and keep running; the rest spawn lazily on first focus. Tabs can be grouped into labelled sidebar sections.
  • Project trees — point a [[window.root]] at a directory (e.g. ~/Developer) and warden auto-discovers every git project under it, rendering them as a collapsible tree of tabs — no per-project config needed. Pair it with amux probe/kill for a per-project session dot on every discovered project.
  • Live hot-reload — edit the config and windows and tabs are added, removed, recoloured, and re-sectioned live on save. A missing config offers to create a starter one and an invalid one shows the error, both on the home surface; a parse error mid-edit instead keeps the last-good windows up behind an error banner. The Config menu opens the config file in your default editor or reveals it in Finder, so you needn't remember its path.
  • Tab-row affordances — a letter/colour tile and a live/cold dot (filled when the terminal is spawned, hollow when cold). Hover a live dot for a ✕ that unloads the tab — kills the terminal and PTY; it respawns a fresh shell on next focus.
  • Pop-out tabs — pop the active tab into its own banner-only window with ⌘⇧O (or the ⤢ control on its row). It's session-preserving: the live terminal, its scrollback, and the running process move across untouched — no restart. Closing the popped-out window returns the tab to where it came from (reopening its origin window first if you'd closed it), and a ⇱ control pops it back in from the sidebar.
  • Live terminal affordances — URLs in a terminal are clickable: hover one for a hand cursor, click to open it in your default browser. And when a tab's process exits (the shell quits, an agent finishes), the tab goes cold — the dot empties and the row respawns a fresh shell on next focus — rather than stranding a dead "Process exited" screen.
  • Notifications — a background tab that rings the bell or emits a desktop-notification escape (OSC 9 / OSC 777) gets an amber badge, and a desktop notification additionally raises a macOS banner; the badge clears on focus. This is the channel agentmux's Claude hooks feed instead of shelling out to osascript.
  • Session-presence probes — a per-tab probe command drives a three-state dot from its exit code: cyan on exit 0 (live), a ghost on exit 3 (crashed but restorable), hollow otherwise — independent of whether warden's own terminal surface is loaded (details below).
  • Keyboard navigation (the Tab menu) — ⌘⇧[ / ⌘⇧] cycle the previous/next loaded tab (cold tabs are skipped) and ⌘1–⌘9 jump to a position; set tab_digit_keys = "cycle" to make ⌘1 / ⌘2 cycle instead (jumps shift to ⌘3–⌘9). ⌘W unloads the active tab, ⌘⇧W closes the window (Safari/Chrome convention), and ⌘⇧O pops the active tab into its own window (session-preserving; see above).
  • CLIwarden validate prints the resolved window/tab tree and warnings; warden fmt formats a config in warden's house TOML style.

To wire the session-presence dot — pairing with agentmux — set a tab's probe to a session check:

probe = '"$HOME/.agentmux/bin/amux" --probe'

so the dot shows whether its amux session is alive — amux --probe exits 0 for the agent session or a lingering frame (so the dot stays lit if the agent exits but the frame wrapper is still up; a plain bare-amux without a frame only ever has the agent). It exits 3 instead when the session's gone but restorable — a plain amux launch here would offer its restore picker — which warden shows as a ghost rather than cyan. amux owns the session naming and socket layout, so the probe stays a one-liner that can't drift from amux's internals. probe_interval controls the cadence (0 = check on focus/hot-reload only). Name amux by absolute path: warden runs the probe via sh -c with the .app's own env, which is minimal on a Finder/Dock launch — amux's internal tmux calls then resolve via the login-shell PATH warden imports at startup. Not using agentmux? Point probe at any check that exits 0 when your session exists.

A tab's optional kill command severs the session the dot represents: click the cyan dot once to arm, click again to confirm, and warden runs kill fire-and-forget — the surface stays open, and the probe re-runs immediately to update the dot. With agentmux, set it to '"$HOME/.agentmux/bin/amux" --kill' (cwd = the tab dir): the mirror of amux --probe, it tears down the whole project — the agent session plus its frame and scratch terminal — so it reaps exactly what the probe detects. Since the control lives on the presence dot, kill only does anything on a tab that also sets probe (no probe ⇒ no dot to click).

The mirror also holds. When the probe reports the session gone on a tab whose terminal is still live — hollow or ghost alike — the same dot becomes a one-click start: warden types the tab's cmd into the existing shell, so a dead or crashed agent session restarts (or restores) in place — scrollback preserved, no terminal respawn.

Not yet built (see docs/FOLLOWUPS.md): ad-hoc cmd+T / cmd+N tabs and windows.

Config

~/.config/warden/config.toml (override with WARDEN_CONFIG):

shell = "fish -l"            # global default shell (omit → your login shell, run as login)
format_on_save = true        # optional; rewrite this file tidy on each clean save (default off)
density = "compact"          # optional; "comfortable" (default) or "compact" (condensed chrome)
auto_update = false          # optional; check for a new release on launch + every 6h (default true; menu check stays)

[[window]]                   # a native macOS window
title  = "work"
colour = "#0f8a8a"           # optional; omit for a neutral default
width  = 1500                # optional; initial width (px, default 1500)
height = 1000                # optional; initial height (px, default 1000)
cmd    = "amux"              # this window's default startup command (each tab can override)

  [[window.tab]]             # a project terminal
  title      = "myproject"   # optional; defaults to the dir basename
  dir        = "~/code/myproject"
  load_on_open = true        # optional; spawn at launch and keep running

  [[window.tab]]
  title = "notes"
  dir   = "~/notes"
  cmd   = ""                 # opt out: just a bare shell here

  [[window.group]]           # optional: a labelled sidebar section
  name = "services"
    [[window.group.tab]]     # same fields as [[window.tab]]
    title = "api"
    dir   = "~/code/api"

A window has its own colour + title banner; its tabs are project terminals. width and height set the initial window size (defaults 1500×1000; saved state overrides after the first launch). Each tab opens a shell; a tab's cmd is auto-run inside that shell (it's typed in, not exec'd, so a shell function like agentmux's amux works and you drop back to a live shell when it exits). Both shell and cmd cascade — set them globally, per-window, or per-tab, and the nearest level wins (cmd = "" opts a level out of an inherited command). load_on_open tabs start at launch and keep running in the background. Tabs can be grouped into labelled sidebar sections with [[window.group]]; loose [[window.tab]]s (no group) appear first in a headerless section. Grouping is cosmetic — it just sections the sidebar. Set format_on_save = true to have warden rewrite the config in house style on each clean hot-reload (the same formatting warden fmt applies). Set density = "compact" to condense the chrome (type + spacing scaled down proportionally) when you want denser tab lists; the default is "comfortable". It hot-reloads like everything else.

Install

Download (no build): grab warden-<version>-macos.zip from the latest release, unzip, and move warden.app to /Applications. Release builds are signed with Developer ID and notarized, so they open without a Gatekeeper block. macOS only.

Guided (Claude Code): run /warden:install — it checks prerequisites (Xcode Command Line Tools, Rust, the Tauri CLI), builds warden from source, installs it to /Applications, and seeds your config.

One-liner:

curl -fsSL https://raw.githubusercontent.com/lockyc/warden/main/install.sh | bash

This clones warden to ~/.warden, builds the release bundle (cargo tauri build), installs warden.app to /Applications, and seeds ~/.config/warden/config.toml from the example if you don't already have one. Re-run it any time to update (it git-pulls and rebuilds). macOS only.

Prerequisites: macOS, Xcode Command Line Tools, a Rust toolchain (rustup). The installer installs the Tauri CLI itself if missing.

Updates

warden updates itself — no reinstall. On launch, every 6 hours while open (warden is long-running), and via warden ▸ Check for Updates…, it checks GitHub for a newer release; when one exists the sidebar shows an Update available: v X bar with a one-click Update & Relaunch.

  • Confirm-to-install — nothing installs silently; you approve each update, and the bar's × dismisses it for the session.
  • Signed — each update is verified against warden's own minisign key before it installs, independent of Apple notarization.
  • Opt out with auto_update = false (the Check for Updates… menu item still works).

Apple Silicon only. Re-running the installer (or downloading the .zip) is needed only to bootstrap the first updater-capable version — 0.6.0 — after which updates land in-app.

Build & use

With just (run just to list recipes):

just run launches against examples/config.toml, whose tabs point at the mock project tree documented in examples/projects/README.md.

just hooks        # once per clone: enable .githooks (pre-push doc gate + active-[patch] guard)
just run          # launch the app against examples/config.toml (never touches your real config)
just validate     # validate the demo config (pass a path to validate another)
just test         # workspace tests
just fmt          # format Rust sources (cargo fmt)
just clippy       # lint (warnings as errors)
just gate         # the full pre-merge gate CI runs (fmt-check, clippy, tests)
just build        # build the release warden.app (needs: cargo install tauri-cli --version ^2)
just deploy       # build, install to /Applications, and relaunch

core.hooksPath is per-clone local git config that the repo can't carry, so run just hooks once after cloning — without it neither git hook is active.

Builds are signed with Developer ID and notarized automatically when the Apple signing/notary env vars are set in the build environment (APPLE_SIGNING_IDENTITY pointing at a Developer ID Application cert, plus APPLE_ID/APPLE_PASSWORD/APPLE_TEAM_ID, or APPLE_API_KEY*) — so release artifacts open on other Macs without a Gatekeeper block. Without those vars (e.g. building from source as a contributor), the build is ad-hoc/unsigned and just deploy strips the Gatekeeper quarantine xattr so the local copy still runs.

Or with cargo directly:

cargo build
cargo test
cargo run -p warden-app                                # launch the app (macOS; reads WARDEN_CONFIG or ~/.config/warden/config.toml)
cargo run -p warden-config --bin warden -- validate    # validate ~/.config/warden/config.toml
cargo run -p warden-config --bin warden -- validate path/to/config.toml
cargo run -p warden-config --bin warden -- fmt         # format ~/.config/warden/config.toml in place
cargo run -p warden-config --bin warden -- fmt path/to/config.toml
cargo run -p warden-config --bin warden -- fmt --check path/to/config.toml  # check only, no write

warden-app materializes a window for each [[window]] and hot-reloads on save; edit the config while it's running to watch windows and tabs appear, disappear, and recolour live.

warden validate prints the resolved windows/tabs and any warnings; exit code 0 (ok), 1 (load/parse/validation error), 2 (usage). warden fmt rewrites a config in warden's house TOML style — consistent indentation, aligned =, section spacing (--check reports without writing, for a CI gate); format_on_save = true applies the same formatting automatically on each clean save.

Layout

  • crates/warden-config/ — the config crate (library + warden CLI).
  • crates/warden-app/ — the macOS Tauri app: windows, the sidebar tab list, libghostty surfaces behind the TerminalSurface seam, and hot-reload wiring.
  • assets/ — icon masters (icon.svg, icon-app.svg), rendered PNGs, the macOS warden.icns, and build-icons.sh to regenerate the rasters from the SVGs.
  • docs/FOLLOWUPS.md — tracked list of intentionally-deferred work.

Related projects

warden is built on three shared library crates. Building it from source pulls them in automatically — they're pinned Git dependencies, resolved by a plain cargo build / just run with nothing extra to install:

  • chrome-core — the sidebar chrome (banner, grouped tab rows, resize drag, density tokens). A build-dependency: its CSS/JS is materialized into warden's bundled web assets at compile time.
  • config-core — the TOML config engine (parse, validate, format, hot-reload diff) behind warden's config and warden fmt.
  • shell-core — the shared release tooling + a sliver of Tauri runtime setup. build.rs materializes the release scripts (git-ignored) and stamps the build; the app registers window-state/updater/process via its register_plugins.

Those same cores are also shared with two sibling apps, curator (curates browser tabs) and lector (curates local documentation sites), the way warden curates terminals. Neither is a dependency of warden — they're peer projects that just draw from the same cores. (warden's embedded terminal is a separate, vendored third-party component; see the License note below.)

If you want to iterate on a shared core, just chrome-dev builds warden against a sibling ../chrome-core checkout (including uncommitted edits) and just chrome-pin re-pins to its pushed commit afterward; just config-dev / just config-pin and just shell-dev / just shell-pin are the same pair for ../config-core and ../shell-core. Never commit an active patch — just gate and a .githooks/pre-commit guard both refuse while one is live.

License

MIT — see LICENSE.

The vendored libghostty binary (crates/warden-app/vendor/) is Ghostty compiled from an unmodified, pinned upstream commit by lockyc/libghostty-build and distributed under Ghostty's MIT license; see crates/warden-app/vendor/LICENSE-ghostty and PROVENANCE.md in that directory.

About

Curated terminals - a macOS console of grouped project tabs of embedded libghostty terminal surfaces.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors