A tiny macOS menu bar app for running the bash commands you keep forgetting.
Butt lives in your menu bar. Click the icon, pick a command, and it runs in the background. The icon turns green if it worked and red if it didn't. Commands can be grouped, given a working folder, and prompt for arguments at run time. Every run's stdout and stderr is saved to a log file you can browse later.
Born from the everyday tax of context-switching to a terminal, hunting for the right directory, and re-typing the same incantation for the hundredth time. Butt is the smallest possible thing that fixes that.
- Menu bar only — no dock icon, no window cluttering your desktop.
- Groups — organise commands into collapsible submenus.
- Working directory per command — no more
cd ~/projects/foo && …. - Runtime arguments — define
{{name}}placeholders and Butt prompts you for them when you click. Supports plain and secret (password-style) inputs, default values, and format hints. - Live status — the menu bar icon shows the last run's outcome at a glance:
- green check — last run succeeded
- red exclamation — last run failed
- yellow play — something is currently running
- Log viewer — every run is recorded with timing, exit code, arguments, and full stdout+stderr captured live. Cancel long-running commands from the log viewer.
- Configurable shell — defaults to
/bin/zsh -l -c …so your aliases and PATH match Terminal.
- macOS 14.0 (Sonoma) or later
- Apple Silicon (M-series) Mac
- Swift 5.10+ toolchain (ships with Xcode 15.3 / Command Line Tools)
git clone https://github.com/<your-username>/butt.git
cd butt
./build.shThe script produces build/Butt.app. Drag it to /Applications (or run it
straight from build/):
open build/Butt.appTo build and launch in one step:
./build.sh runFor a debug build:
./build.sh debugClick the menu bar icon and choose Quit Butt (⌘Q while the menu is
open).
Butt seeds two example commands under an "Examples" group so you can try it immediately:
- Hello — prompts for a name and prints
Hello <name>! - Disk usage (home) — runs
du -sh ~
Click the menu bar icon, hover over Examples, and pick one.
Open Configure… from the menu (⌘,).
Each command has:
| Field | Notes |
|---|---|
| Name | Shown in the menu. |
| Group | Used as the submenu name. New groups are created on the fly. |
| Working Folder | Resolved with ~ expansion. The shell is launched with this as cwd. |
| Shell | Defaults to /bin/zsh. Anything that takes -c works (/bin/bash, /usr/bin/env fish, …). |
Login shell (-l) |
When on, your login profile (PATH, aliases) is loaded. Recommended for parity with Terminal. |
| Command | The bash snippet. Multi-line is fine. Use {{argName}} to reference arguments. |
| Arguments | Each argument has a name (used in {{…}}), a placeholder/format hint, an optional default, and a "secret" flag for password-style inputs. |
| Notes | Free-form, not used at run time. |
Arguments are substituted into the command verbatim. If an argument may contain whitespace or shell-meaningful characters, quote the placeholder:
git checkout "{{branch}}"
curl -H "Authorization: Bearer {{token}}" https://api.example.com/{{path}}Mark tokens, passwords, etc. as Secret so the input field hides the value
and the run record stores it as ••• instead of the plaintext.
⌘S saves changes. ⌘R saves and runs. The Run button does the same.
The menu bar icon reflects an aggregate state:
- terminal (template) — idle, no recent runs
- green check — last run succeeded
- red exclamation — last run failed
- yellow play — at least one run is in progress
Each command in the menu also shows its own last-run indicator, so you can tell at a glance which deploys went out and which need attention.
Open Show Logs… (⌘L) to see the most recent runs (newest first).
For each run you get:
- Status, exit code, duration, started-at
- The argument values that were used (secrets masked)
- The full captured stdout + stderr, live-tailing while the run is in flight
- A "Reveal in Finder" shortcut to the raw log file
- A Cancel button while the run is still going
Butt keeps the most recent 200 runs on disk and prunes older ones automatically. Clear All in the sidebar wipes the lot.
Everything lives under ~/Library/Application Support/Butt/:
~/Library/Application Support/Butt/
├── config.json # your commands and groups
├── runs.json # run history index
└── logs/
└── <run-uuid>.log # one file per run, full stdout+stderr
config.json is plain JSON — feel free to back it up, sync it across
machines, or hand-edit it.
rm -rf /Applications/Butt.app
rm -rf ~/Library/Application\ Support/ButtButt is ~700 lines of Swift built as a single Swift Package executable wrapped
in a .app bundle. The UI is SwiftUI (MenuBarExtra + Window scenes),
state is held in @Observable services, and commands are spawned with
Foundation.Process against the configured shell. Stdout/stderr pipes are
read asynchronously via readabilityHandler and streamed straight to the
on-disk log file, so the log viewer can live-tail the output without buffering
in memory.
The bundle sets LSUIElement = true and the app sets activation policy to
.accessory, so it's menu-bar-only with no dock icon.
butt/
├── Package.swift
├── build.sh # builds + assembles + ad-hoc signs the .app
├── Resources/
│ └── Info.plist
└── Sources/Butt/
├── ButtApp.swift # @main, scenes, app delegate
├── AppState.swift
├── Models/ # ButtCommand, CommandRun
├── Services/ # CommandStore, LogStore, CommandRunner, Paths
└── Views/ # SwiftUI views
Issues and pull requests are welcome. Some easy wins if you're looking for a starting point:
- A real app icon (currently a placeholder SF Symbol)
- Drag-to-reorder for groups and commands in the configure window
- Import / export of
config.jsonfrom the UI - Optional desktop notifications when long-running commands finish
- A "duplicate command" action
Before opening a PR, please run swift build -c release --arch arm64 and
make sure the app launches cleanly with ./build.sh run.
MIT — see LICENSE.