Install

Two routes to the app, and one step that both routes need. Take the disk image unless you have a reason to build from source.

Get the app

Download the signed disk image

This is the route to prefer. The disk image is signed with a Developer ID, notarised by Apple, and stapled.

Download for macOS

v0.3.1 · Apple silicon · 1059 KB · macOS 14 or later

Verify the download before you open it. coffee-bar asks you to trust an app that overrides your Mac's own sleep policy, so it gives you the means to test that trust first.

shasum -a 256 coffee-bar-0.3.1.dmg
# ca72a571f5595da27d377bbb69ed0fe3b20869bcc9823caeb490fbc2afc2badd

spctl --assess --type open --context context:primary-signature -vv coffee-bar-0.3.1.dmg
# accepted
# source=Notarized Developer ID

The notarisation ticket is stapled to the disk image, so the second command answers from the ticket in the file. It needs no network connection.

From 0.2.1 the app inside the image carries its own ticket too, so the copy you drag to /Applications launches on a machine that has never been online. In 0.2.0 only the image was stapled, and that first launch had to ask Apple.

Or build from source with Homebrew

brew tap ArangoGutierrez/coffee-bar
brew install coffee-bar

Both routes ship 0.3.1. Either way you get the same two things: the app bundle, assembled by the repository's own build script, and the coffee-bar-probe binary.

What differs is the signature, not the features. Homebrew compiles on your machine, so that copy is signed only ad hoc and is not notarised. The disk image is signed with a Developer ID, notarised by Apple and stapled, which is what lets Gatekeeper accept a copy that arrived over the network. Take the disk image unless you have a reason to build from source.

The formula lives in a separate tap repository, ArangoGutierrez/homebrew-coffee-bar, because brew tap user/repo resolves to github.com/user/homebrew-repo. Homebrew formulae do not write to /Applications, so the app lands in the Homebrew prefix and the install prints the one command that links it there.

Wire the five hooks

Both routes need this step. coffee-bar learns what your agent sessions are doing from Claude Code hooks and from nothing else, so until these entries exist the app runs and no session event ever reaches it. coffee-bar never writes your settings file for you.

Let coffee-bar write the entries

Open Preferences… from the foot of the panel, or press ⌘, while coffee-bar is frontmost. Under Agent tools each settings file is listed with a Copy hook snippet button beside it. Click the one next to ~/.claude/settings.json and the whole block is on your pasteboard, ready to merge into the file.

Prefer that button to the block below. It is generated from the same constant the health check reads, so it cannot tell you to wire a set the panel then reports as missing. It carries the ingest endpoint belonging to the tool you picked, which is how an arriving payload is attributed to that tool at all, and it puts the matcher key on exactly the events that need one. The block below explains none of that, and a hook config wrong in any of those ways fails silently: the app sees nothing and the panel still says the file is wired.

The button writes to the pasteboard and stops there. Merging is yours, because that file is yours.

Or paste the block by hand

Worth doing if you want to read what you are pasting before it goes in, or if coffee-bar is not running yet.

Add these to ~/.claude/settings.json:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "curl -sS -o /dev/null --fail-with-body --max-time 5 --unix-socket \"$HOME/Library/Application Support/coffee-bar/ingest.sock\" -X POST --data-binary @- http://localhost/event"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "curl -sS -o /dev/null --fail-with-body --max-time 5 --unix-socket \"$HOME/Library/Application Support/coffee-bar/ingest.sock\" -X POST --data-binary @- http://localhost/event"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "curl -sS -o /dev/null --fail-with-body --max-time 5 --unix-socket \"$HOME/Library/Application Support/coffee-bar/ingest.sock\" -X POST --data-binary @- http://localhost/event"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "curl -sS -o /dev/null --fail-with-body --max-time 5 --unix-socket \"$HOME/Library/Application Support/coffee-bar/ingest.sock\" -X POST --data-binary @- http://localhost/event"
          }
        ]
      }
    ],
    "PermissionDenied": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "curl -sS -o /dev/null --fail-with-body --max-time 5 --unix-socket \"$HOME/Library/Application Support/coffee-bar/ingest.sock\" -X POST --data-binary @- http://localhost/event"
          }
        ]
      }
    ]
  }
}

The two tool events take a matcher; the other three take none.

Keep -o /dev/null. It pairs with --fail-with-body, which exists to print the server's error body rather than swallow it — and curl prints a body to standard output, which is where Claude Code looks for a hook's decision. Without the redirect, an ingest error reaches your agent as something to act on. The exit status, which is the part --fail-with-body is wanted for, is unaffected.

If your settings file already has a hooks key, merge these entries into it. Pasting the block whole replaces whatever hooks you already run.

First run

Restart Claude Code, so that the new entries load. Start coffee-bar before the next session: the app creates the socket, and a hook that finds no socket makes Claude Code report an error on every event.

Then open the panel and read its advisory lines. The panel names any hook it cannot find in ~/.claude/settings.json, and it says separately when this copy of coffee-bar is not listening on its socket. Those are two different questions, so the panel answers them on two different lines.

coffee-bar has no Dock icon and opens no window. Look for the cup at the right end of the menu bar, near the clock, and click it to open the panel. If you cannot see the cup, the app is almost certainly running: on a full menu bar the cup is there and you cannot see it, and on a MacBook with a notch it is usually behind the notch.

Read the Docs page next. It defines all three controls — Serving in the panel, Display and Battery floor in the Preferences window — what coffee-bar asks macOS for, and when it refuses to hold.

Requirements