A native macOS app that watches all your running Claude Code CLI sessions and shows a floating panel whenever one is waiting for your input — so you can see at a glance which agents need attention and jump straight to them.
- Floating notification panel (non-focus-stealing) lists every Claude session waiting for input, with project name, git branch, working directory, and how long it's been waiting — the branch is what tells two agents in the same repo apart
- Put the panel where you want it — pick a screen corner in Preferences, or drag the panel by its header to any spot on any display and it stays there
- Sound alert plays when the panel first appears, not on subsequent updates
- Click to jump directly to the right iTerm2 tab/pane
- Jump from the keyboard with ⌃⌥1–9 anywhere in the system; each row shows its own shortcut, and the modifiers are configurable in Preferences — Control-Option is the default because it is the only offered combination that Preview, Messages, Finder and Xcode don't already bind to 1–9
- Dismiss individual sessions (✕ per row) or all at once (✕ in header)
- Menu bar icon for quick access and preferences
- Plan usage meters at the top of the menu bar dropdown — the same
"Current session" and "Current week (all models)" figures
/usagereports, as colored bars with a countdown to each reset
Claude Code session
→ Stop / Notification hook fires fleet-status.sh
→ Writes ~/.claude/fleet-status/<session_id>.json { state: "waiting" }
→ Claudifications polls for file changes
→ Panel appears / updates
When you act on a session (click or dismiss), the state is updated to "dismissed" and the row disappears.
Plan usage rides a separate path, because Claude Code exposes subscription limits only to the status line — not to hooks:
Claude Code session (re-renders its status line)
→ pipes session JSON, incl. a `rate_limits` block, to usage-statusline.py
→ writes ~/.claude/claudifications/usage.json
→ Claudifications reads it when you open the menu bar dropdown
Two things follow from that. The readout only refreshes while a session is on
screen, so it is dimmed once the reading is over 15 minutes old — and if a
usage window has reset since the last reading, that bar shows — rather than a
stale number. Claude Code also only sends rate_limits to subscribers, and only
after a session's first API response, so a brand-new install shows nothing until
you have used a session.
rate_limits carries exactly two windows, five_hour and seven_day, which
correspond to /usage's Current session and Current week (all models)
rows (verified by matching their reset timestamps). /usage's third row,
Current week (Fable), has no equivalent — the per-model breakdown exists
only behind an authenticated GET /api/oauth/usage, which would require reading
and refreshing your OAuth credentials from the Keychain. Refreshing a token
another process owns risks invalidating the CLI's own credentials, so this app
deliberately doesn't go there.
- macOS 13+
- Claude Code CLI with hooks support
- iTerm2 — optional, required for click-to-jump
- Xcode or
xcodegen+xcodebuildto build from source
Grab Claudifications.zip from the latest release,
unzip it, and move Claudifications.app to /Applications.
Release builds are signed with an Apple Developer ID and notarized by Apple, so
they open normally — no xattr dance and no Privacy & Security override.
Then launch the app and choose Install Hooks from the menu bar icon.
Claudifications checks for new releases on its own (about once a day) and offers to install them when found — no need to manually re-download for future updates. Check for Updates… in the menu bar triggers a check any time; turn automatic checks off in Preferences → Updates if you'd rather update manually. Updates are downloaded from GitHub Releases and verified with Sparkle before install.
git clone https://github.com/curtisgalloway/claudifications.git
cd claudifications
./install.shThen follow the printed next steps (launch the app and wire the hooks in settings.json).
make install # builds and copies to /ApplicationsThen install the hook and wire settings.json manually:
Install the hook
mkdir -p ~/.claude/hooks
cp hooks/fleet-status.sh hooks/usage-statusline.py ~/.claude/hooks/
chmod +x ~/.claude/hooks/fleet-status.sh ~/.claude/hooks/usage-statusline.pyWire the hook in ~/.claude/settings.json
{
"hooks": {
"Stop": [
{"hooks": [{"type": "command", "command": "~/.claude/hooks/fleet-status.sh waiting"}]}
],
"Notification": [
{"hooks": [{"type": "command", "command": "~/.claude/hooks/fleet-status.sh waiting"}]}
],
"PreToolUse": [
{"hooks": [{"type": "command", "command": "~/.claude/hooks/fleet-status.sh working"}]}
]
},
"statusLine": {"type": "command", "command": "~/.claude/hooks/usage-statusline.py"}
}Claude Code allows exactly one status line. Install Hooks in the menu will
never overwrite an existing one — it tells you instead. To run both, have your
own status line also invoke ~/.claude/hooks/usage-statusline.py; it prints a
compact 5h 12% 7d 34% summary you can append to your own output.
The Claude Code Notification hook can also trigger iTerm2's own notification system. To suppress the duplicate:
iTerm2 → Settings → Profiles → Terminal → Filter Alerts → uncheck "Send escape sequence-generated alerts"
~/.claude/
settings.json ← hook + statusLine wiring lives here
hooks/
fleet-status.sh ← writes session state files (this repo)
usage-statusline.py ← writes plan usage (this repo)
fleet-status/
<session_id>.json ← per-session runtime state, auto-managed
claudifications/
usage.json ← latest plan usage reading, auto-managed
usage.json sits outside fleet-status/ on purpose: the app sweeps that
directory and deletes anything that isn't a valid session record.
Panel never appears
- Verify the hook fires: run
claudein a terminal, let it stop, then checkls ~/.claude/fleet-status/ - Confirm
settings.jsonhas the hooks and is valid JSON
Plan usage shows "No reading yet"
- The status line only runs in an interactive session —
claude -pwon't trigger it - Confirm the wiring:
~/.claude/hooks/usage-statusline.pyexists andstatusLineinsettings.jsonpoints at it - Check for a reading:
cat ~/.claude/claudifications/usage.json rate_limitsreaches the status line only for Claude subscribers, and only after the session's first API response
iTerm2 jump doesn't work
- Make sure iTerm2 has Automation permission: System Settings → Privacy & Security → Automation → Claudifications → iTerm2 ✓
- The jump uses
ITERM_SESSION_IDfrom the environment when Claude Code starts — it only works in sessions launched from iTerm2
.github/workflows/build.yml builds every push to main and every PR, and
attaches the built app as a workflow artifact.
To cut a release, push a tag:
git tag v1.2.3
git push origin v1.2.3.github/workflows/release.yml builds it, stamps CFBundleShortVersionString
from the tag (minus the leading v) and CFBundleVersion from the run number,
signs and notarizes the bundle, packages it with ditto, and publishes a
GitHub Release with install instructions and a SHA-256.
The MARKETING_VERSION / CURRENT_PROJECT_VERSION values in project.yml are
placeholders for local builds — only tagged CI builds carry a real version.
Local builds are ad-hoc signed (CODE_SIGN_IDENTITY: "-"), so a clone builds
and runs with no Apple account. Only release.yml signs for real: it re-signs
the finished bundle with the Developer ID identity, submits it to Apple's
notary service, staples the ticket to the .app, and only then zips it.
build.yml deliberately stays unsigned — PRs from forks can't read secrets.
The hardened runtime is on for every configuration, which is why
Claudifications/Claudifications.entitlements exists: com.apple.security.automation.apple-events
is what lets click-to-jump drive iTerm2. Building locally with the same setting
means an entitlement problem shows up on your machine, not in a release.
Six repository secrets drive it (Settings → Secrets and variables → Actions):
| Secret | What it holds |
|---|---|
MACOS_CERT_P12 |
base64 of the exported Developer ID Application certificate + private key |
MACOS_CERT_PASSWORD |
the password set when exporting that .p12 |
APPLE_ASC_KEY_ID |
App Store Connect API Key ID |
APPLE_ASC_ISSUER_ID |
App Store Connect API Issuer ID |
APPLE_ASC_KEY_P8 |
base64 of the AuthKey_<KeyID>.p8 file |
SPARKLE_PRIVATE_KEY |
the EdDSA private key generate_keys -x exports, used to sign each release for Sparkle |
To produce them:
Certificate. Xcode → Settings → Accounts → your team → Manage Certificates
→ + → Developer ID Application. Then in Keychain Access, right-click the
new Developer ID Application: … identity → Export → .p12 with a password.
base64 -i DeveloperID.p12 | pbcopy # → MACOS_CERT_P12API key. App Store Connect → Users and Access → Integrations → App Store
Connect API → Team Keys → Generate API Key, role Developer. It must be
a team key: personal keys are rejected by the notary service. The .p8
downloads exactly once.
base64 -i AuthKey_XXXXXXXXXX.p8 | pbcopy # → APPLE_ASC_KEY_P8Sparkle key. Generated once, locally, from the bin/ tools in Sparkle's
release archive (the
SPM package project.yml depends on doesn't include them):
./bin/generate_keys # public key → paste into Info.plist's SUPublicEDKey
./bin/generate_keys -x key.pem # private key → SPARKLE_PRIVATE_KEY, then delete key.pemCompromising this key would let someone push a malicious "update" to every installed copy of the app, so treat it like the Developer ID certificate: keep a durable copy in 1Password, and don't let a plaintext export linger on disk.
docs/appcast.xml is Sparkle's update feed, served over GitHub Pages (repo
Settings → Pages: source = main branch, /docs folder) at
https://curtisgalloway.github.io/claudifications/appcast.xml. Each release
run downloads Sparkle's generate_appcast tool, signs Claudifications.zip
with SPARKLE_PRIVATE_KEY, and merges the new entry into docs/appcast.xml
via scripts/merge_appcast_item.py — then commits and pushes that one file
straight to main. It's the only thing this workflow ever writes to main
outside a PR, and it only runs from a v* tag push.
To check a published release from a clean machine:
spctl --assess --type exec -vvv /Applications/Claudifications.app
# → accepted / source=Notarized Developer IDApache 2.0 — see LICENSE.

