Happier Docs
hstack (local stack)

Menu bar (SwiftBar)

macOS menu bar integration for monitoring stacks and switching worktrees.

Menu bar (SwiftBar)

hstack ships a macOS menu bar plugin powered by SwiftBar.

SwiftBar runs a script on an interval and renders its output as native macOS menu items.

Features

  • Status at a glance with dynamic icons (green/orange/red)
    • Server health
    • Daemon status (PID + optional control server probe)
    • Autostart LaunchAgent status
    • Tailscale Serve status / URL (if configured)
  • Quick controls
    • Start / stop / restart the stack
    • Restart just the daemon (stack-safe)
    • Install / enable / disable / uninstall autostart
    • Enable / disable Tailscale Serve
  • Details
    • PID, CPU %, RAM MB, uptime (where available)
    • Useful URLs and file paths
    • Stack details include aggregate CPU/RAM (server+daemon+autostart) when running
    • Open logs in Console.app
  • Refresh control
    • Manual refresh
    • In-menu refresh interval toggles (includes slower intervals like 10m/15m/30m/1h/6h/12h/1d)
    • Uses a small helper script (extras/swiftbar/set-interval.sh) to avoid SwiftBar quoting issues
  • Stacks + services layout
    • Main stack is shown directly (no extra nesting level)
    • Each stack shows service rows (Server/Daemon/Autostart/Tailscale) with per-service submenus
  • Components (git/worktrees)
    • Available under a top-level Components submenu (to keep the main menu clean)
    • Shows repo/worktree status for the active monorepo checkout (<workspace>/main, <workspace>/dev, or a worktree under <workspace>/{pr,local,tmp}/...)
    • Each repo entry includes a Worktrees submenu listing all worktrees, with actions to switch/open
    • Quick actions: wt status/sync/update, PR worktree prompt, open shells/editors (wt shell/code/cursor)
    • Shows origin and upstream comparisons for the repo’s main branch (based on your last git fetch)
    • Uses a Git cache by default so SwiftBar refresh stays fast even with many stacks/worktrees

Modes: selfhost vs dev

The menu supports two modes:

  • Selfhost mode (selfhost): lightweight “control panel” for running Happier.
    • Shows only the main stack essentials (Server/Daemon/Autostart/Tailscale) plus a small Maintenance section.
    • Hides developer-oriented sections like stacks enumeration, git/worktrees, and worktree tooling.
  • Dev mode (dev): full stack control plane (stacks + Components (git/worktrees)).

How to switch modes

  • In the menu, use the Mode section at the top, or
  • From a terminal:
hstack menubar mode selfhost
hstack menubar mode dev

Stacks (multiple instances)

If you create additional stacks (see docs/stacks.md), the plugin shows:

  • Main stack (the default, stack name main)
  • Stacks section listing each stack found under ~/.happier/stacks/<name>/env

Each stack row renders the same “mini control panel” (server/daemon/autostart/logs + a few actions) with stack-specific ports, dirs, and LaunchAgent label.

The menu also includes:

  • stack new --interactive (create stacks)
  • stack edit <name> --interactive (edit stack port/server flavor/repo checkout)
  • stack wt <name> -- use --interactive (switch the stack’s repo checkout/worktree)
  • “PR worktree into this stack (prompt)” (creates wt pr ... --use scoped to the stack env)

Worktrees (quick entry points)

The menu also provides “jump off” actions for the worktree tooling:

  • hstack wt use --interactive
  • hstack wt new --interactive
  • hstack wt sync-all
  • hstack wt update-all --dry-run / hstack wt update-all
  • hstack wt pr ... (via an in-menu prompt)

For stack-specific worktree selection (which worktree a stack uses), use:

  • hstack stack edit <name> --interactive
    • or hstack stack wt <name> -- use --interactive

Note (worktree switching)

Happier is a single monorepo, so switching worktrees is always a repo-level action. Use:

  • hstack stack wt <name> -- use --interactive

Implementation notes

  • Entry script: extras/swiftbar/hstack.5s.sh (installed into SwiftBar as hstack.<interval>.sh)
  • Shared functions: extras/swiftbar/lib/*.sh (sourced by the entry script)
  • Helper scripts:
    • extras/swiftbar/set-interval.sh
    • extras/swiftbar/set-server-flavor.sh

Install

1) Install SwiftBar

brew install --cask swiftbar

2) Install the plugin

hstack menubar install

If you want a different default refresh interval at install time:

HAPPIER_STACK_SWIFTBAR_INTERVAL=15m hstack menubar install

3) Open the active SwiftBar plugin folder

SwiftBar can be configured to use a custom plugin directory. To open the active one:

hstack menubar open

Uninstall

Remove the installed SwiftBar plugin files (does not delete your stacks/workspace):

hstack menubar uninstall

How refresh works (important)

SwiftBar’s refresh interval is controlled by the filename suffix:

  • hstack.30s.sh → every 30 seconds
  • hstack.5m.sh → every 5 minutes
  • hstack.1h.sh → every 1 hour

The plugin defaults to a slower interval (recommended), and also sets:

  • refreshOnOpen=false (recommended) to avoid surprise refreshes while you’re navigating the menu.

You can also change the interval directly from the menu via Refresh interval (it renames the plugin file and restarts SwiftBar).

Git cache (important for performance)

Git/worktree inspection is the most expensive part of the menu when you have many stacks. By default, the plugin runs in cached mode:

  • It renders git/worktree info from an on-disk cache under ~/.happier-stack/cache/swiftbar/git.
  • Normal menu refreshes do not run git commands (so refresh stays snappy).
  • The cache is refreshed explicitly (via menu actions), and can optionally refresh on TTL expiry.

Controls and settings:

  • Refresh now: open Components → Git cache and run:
    • “Refresh now (main components)”
    • “Refresh now (all stacks/components)”
    • or “Refresh now (this stack)” from a stack’s Components menu
  • TTL: HAPPIER_STACK_SWIFTBAR_GIT_TTL_SEC (default 21600 seconds = 6 hours)
  • Mode: HAPPIER_STACK_SWIFTBAR_GIT_MODE=cached|live (default cached)
  • (Optional) Background auto-refresh: HAPPIER_STACK_SWIFTBAR_GIT_AUTO_REFRESH_SCOPE=main|all|off (default main)

Notes:

  • Cached git info can be stale; it’s meant for at-a-glance signal.
  • Actions like worktree switching/build/dev are always live (they use hstack); only displayed git status is cached.

Maintenance (selfhost mode)

In selfhost mode, the menu includes a Maintenance section that can:

  • show whether a hstack update is available (from cached ~/.happier-stack/cache/update.json)
  • run:
    • hstack self check
    • hstack self update

Terminal preference for interactive actions

Many menu actions open a terminal (interactive wizards, long-running dev servers, etc). The plugin uses helper scripts so these run in your preferred terminal, using the same env var as wt shell:

  • HAPPIER_STACK_WT_TERMINAL=auto|ghostty|iterm|terminal|current

Notes:

  • auto tries ghostty → iTerm → Terminal → current.
  • Ghostty is best-effort; if your Ghostty build can’t execute the command automatically, the command is copied to your clipboard and Ghostty is opened in the repo directory.

Start SwiftBar at login (optional)

SwiftBar is independent from the hstack LaunchAgent/service.

  • In SwiftBar Preferences, enable Launch at Login, or
  • Add SwiftBar to macOS Login Items.

Troubleshooting

Plugin doesn’t show up

  • Ensure SwiftBar is running.
  • Check which plugin folder SwiftBar is using:
    • SwiftBar → Preferences → Plugin Folder
  • Open the active folder:
    • hstack menubar open

Daemon shows “auth required” / “no machine”

This happens on a fresh machine (or any new stack) when the daemon does not yet have credentials in the stack-specific CLI home directory.

What’s going on

  • The daemon stores credentials in access.key under the CLI home directory.
  • For stacks (including main), that’s typically:
    • ~/.happier/stacks/<name>/cli/access.key
  • When access.key is missing, the daemon enters an interactive auth flow and won’t become a “machine” until it completes.
    • Under launchd (autostart), this shows up as no machine and the daemon may appear “stopped”.

If it still needs auth

  • In SwiftBar, open the Daemon section:
    • If it shows auth_required, click Auth login (opens browser)
  • Or run manually:
hstack auth login

“Daemon stale” even though it’s running

The plugin checks:

  • daemon.state.json PID is alive, and
  • (optionally) the daemon control server responds.

If the daemon is running but the menu is stale, refresh and check the PID line under “Daemon”.

On this page