Tab roulette
Which one rang?
A bell goes off somewhere. Wietty marks the row that rang and names it in the notification, so one click gets you there.
Wietty manages your development workspaces. Every project folder gets a card showing its git status, issue and PR links, CI checks, terminals, Claude agents, and supervised processes. Click a row, its terminal opens in the pane beside the sidebar.
❯ the queue worker died after the last deploy, take a look ● Read src/Jobs/ChargeCard.php ● Read storage/logs/queue.log ∟ retry limit hit on a null customer id ● Edit src/Jobs/ChargeCard.php 🔔 waiting on you — apply the fix?
▸▸ auto mode on (shift+tab to cycle)
The state of your desk
An agent in one tab, a dev server in another, a queue worker that stopped twenty minutes ago in a third. Something is ringing a bell and finding it means clicking through all nine. Wietty puts the whole picture in one window, so the state of a project is something you glance at rather than go looking for.
Tab roulette
A bell goes off somewhere. Wietty marks the row that rang and names it in the notification, so one click gets you there.
Silent deaths
A crashed worker looks exactly like an idle one until you check. Here liveness and outcome are two separate readings on one dot.
Setup by folklore
The commands a workspace runs live in a wietty.json next to the code, not in a stale README or someone’s shell history.
Workspace cards
A workspace is a project folder. Its card carries the facts you would otherwise assemble by hand from git status, a browser tab and a CI page.
Current branch, upstream tracking ref, and how far ahead or behind you are. Refreshed on a cycle in the background.
The issue number is read from the branch name and the pull request for that branch is found for you — both a click away.
The result of the checks running against the branch, on the card, without opening the browser.
Shell sessions Wietty owns. One row each; click a row and it fills the pane.
Agent sessions listed like any other row, with a bell when one wants your attention.
Named commands Wietty starts, watches, and can stop, restart or kill.
Edit wietty.json and the card flags it. Click the flag to apply the change; nothing runs behind your back.
Terminals
Opening a project on a fresh machine
Each terminal is a pseudo terminal the app owns, rendered by Ghostty. That needs nothing on your machine: no iTerm2, no tmux, not even Ghostty.app. If you do keep a Ghostty config, its font, theme, cursor and keybindings are used here too, so the terminal in Wietty looks like the terminal you already set up.
Sessions are deliberately not persistent. Every row is cleared when the app launches and reopens the moment you click it, so you never inherit a shell from last Tuesday.
⟶ checkout-api git:(feature-482) ✗ $ git log --oneline -3 a91f2c4 fix null customer id on retry 7d13b80 add charge card job test 2be04ff bump stripe sdk $
Bell notifications
An agent finishes while you are in another app
A terminal or agent that rings the bell marks its row with a bell and posts a macOS notification naming the workspace and the row. Clicking the notification brings Wietty forward and shows that terminal — the same thing clicking the row does. Sessions on a connected Mac notify too, carrying that connection’s name.
Permission is asked the first time something actually rings, not at launch. One notification per row until you visit it, so a shell beeping at an ambiguous tab completion can’t flood Notification Center, and visiting the row takes its notification back. Nothing is posted about a terminal already on screen in front of you.
🔔 Wietty checkout-api · codex rang the bell 🔔 Wietty marketing-site · npm run dev rang the bell · via studio-mini ― one per row, until visited
Processes
Terminals and agents are things Wietty drives wherever they run. A process is different: Wietty starts it in the workspace directory, tracks its state and exit code, streams its output to a log window, and can stop, restart or kill it from the row’s context menu.
short_running
A test or lint run. Its state becomes the exit result — passed on 0, failed on anything else. Killable while it runs.
long_running
npm run dev and friends. Stop runs your stop command if you set one, otherwise it escalates SIGINT, SIGTERM, SIGKILL.
daemon
sail up -d, vagrant up. The start command exits while the service keeps going, so give it a stop, and a status probe to read health.
| Dot | Reads as | State |
|---|---|---|
| filled green | Running — a live foreground process, or a daemon its probe says is up | |
| open green | Finished and passed, exit 0 | |
| open red | Failed or crashed, non-zero exit | |
| open grey | Idle, stopped, or never run |
Teaching Wietty to read a daemon’s health
A status probe is exit-code based: 0 means up, anything else means down. Its own output never decides health — it is captured and written to the log only when the probe fails, which is how you tell a broken probe apart from a service that is genuinely down. Write a fast, non-interactive one-liner; the usual idiom pipes a check into grep -q.
"cd src && sail ps | grep -q Up" "vagrant status --machine-readable \ | grep -q ',state,running'" "docker compose ps --status running \ | grep -q ."
wietty.json
Per-workspace configuration lives in a wietty.json in the project folder. Wietty reads it, and keeps it in sync for terminals and agents. Process definitions are read-only inside the app: you edit the file, the card raises its change indicator, and you click to apply. Commit it and everyone on the team gets the same stack.
{
"shell_init": [
"export PATH=$HOME/bin:$PATH"
],
"processes": {
"queue": {
"command": "cd src && sail artisan queue:work",
"kind": "long_running",
"auto_start": true
},
"sail": {
"command": "cd src && sail up -d",
"kind": "daemon",
"stop": "cd src && sail down",
"status": "cd src && sail ps | grep -q Up"
},
"pr": {
"command": "gh pr view $WIETTY_PR_NUMBER --web",
"kind": "short_running"
}
}
}
Workspace variables
Every process gets WIETTY_ variables — workspace path and name, branch, upstream, base branch, owner, repo, issue number, PR number. Reference them with ordinary shell syntax and the login shell expands them.
A command referencing a variable with no value is blocked rather than run against an empty target, and it tells you which variable was missing. allow_empty_vars opts a single process back in.
Environment and PATH
Commands run under $SHELL -l -c, so your login PATH — including a Homebrew brew shellenv line in ~/.zprofile — is already there. Most setups need no PATH configuration at all.
~/.zshrc is not sourced. If a tool goes missing, move that export into a login file or put it in shell_init, which keeps the setup with the project instead of in your dotfiles.
Build it
XcodeGen generates the Xcode project from project.yml, which is the source of truth. Wietty.xcodeproj/ is generated and gitignored, so it never lands in a diff.
$ git clone https://github.com/kloostermanw/wietty $ xcodegen generate
brew install xcodegenOnce per machine.git clone https://github.com/kloostermanw/wietty && cd wiettyxcodegen generateWrites the Xcode project from project.yml.xcodebuild -scheme Wietty -destination 'platform=macOS' buildSwap build for test to run the suite.