• macOS
  • Open source
  • MIT
  • Renders with Ghostty

One card per project. Everything it’s running, on it.

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 state of your desk

Nine terminal tabs and no idea what’s in any of them.

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

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.

Silent deaths

Still running?

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

What does this project need?

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 card that already knows where the project stands.

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.

  • Git status

    Current branch, upstream tracking ref, and how far ahead or behind you are. Refreshed on a cycle in the background.

  • Issue & PR links

    The issue number is read from the branch name and the pull request for that branch is found for you — both a click away.

  • CI checks

    The result of the checks running against the branch, on the card, without opening the browser.

  • Terminals

    Shell sessions Wietty owns. One row each; click a row and it fills the pane.

  • Claude agents

    Agent sessions listed like any other row, with a bell when one wants your attention.

  • Processes

    Named commands Wietty starts, watches, and can stop, restart or kill.

  • Change indicator

    Edit wietty.json and the card flags it. Click the flag to apply the change; nothing runs behind your back.

Terminals

Real terminals, nothing to install first.

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 · zsh
 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

The bell tells you which row, not just that something happened.

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.

Notification Center
🔔 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

Named commands Wietty owns, not just watches.

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

Runs to completion

A test or lint run. Its state becomes the exit result — passed on 0, failed on anything else. Killable while it runs.

long_running

Stays in the foreground

npm run dev and friends. Stop runs your stop command if you set one, otherwise it escalates SIGINT, SIGTERM, SIGKILL.

daemon

Detaches and returns

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.

The status dot reads on two axes — fill is liveness, colour is outcome
DotReads asState
filled greenRunning — a live foreground process, or a daemon its probe says is up
open greenFinished and passed, exit 0
open redFailed or crashed, non-zero exit
open greyIdle, 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.

status probes
"cd src && sail ps | grep -q Up"
"vagrant status --machine-readable \
   | grep -q ',state,running'"
"docker compose ps --status running \
   | grep -q ."

wietty.json

The file is the source of truth.

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.

checkout-api / wietty.json
{
  "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

The card’s facts, as environment

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

A login shell, not an interactive one

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

Four commands from clone to running app.

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
  1. brew install xcodegenOnce per machine.
  2. git clone https://github.com/kloostermanw/wietty && cd wietty
  3. xcodegen generateWrites the Xcode project from project.yml.
  4. xcodebuild -scheme Wietty -destination 'platform=macOS' buildSwap build for test to run the suite.