Your coding agents, live in the notch.
NotchPal is a tiny macOS menu bar agent that shows what Claude Code and OpenAI Codex are doing right now — in the Dynamic Island area of your MacBook's notch.
See it running at mac.simonveprek.cz →
Important
NotchPal is source-available from this GitHub repository. You may use it, copy it, modify it, fork it, and redistribute it. You may not sell NotchPal or charge for a product or service whose value substantially derives from it. Keep the license and attribution when sharing a copy.
Not "an agent is running". Not a spinner. The actual work:
◆ Reading NotchController.swift 2:14
lines 40–99 · NotchPal
◆ Running swift build -c release 18s
Build the package · NotchPal
◆ Patching EventServer.swift 1:02
and 3 more · NotchPal
When nothing is running, NotchPal shows nothing at all.
- Why
- Requirements
- Install
- How it works
- What it reports
- When the notch opens
- Design notes
- Configuration
- Privacy
- Troubleshooting
- Development
- See more
- Trademarks
- License
Agentic coding means long stretches where the model is working and you are not. You end up alt-tabbing to a terminal to answer one of three questions:
- Is it still going?
- What is it doing?
- Is it waiting on me?
The notch is dead space directly in your field of view. NotchPal puts those three answers there, and gets out of the way the moment they stop mattering.
| macOS | 26 (Tahoe) or later |
| Xcode | 26 or later |
| Swift | 6.2 toolchain |
| Hardware | Any Mac. On Macs without a notch, the panel floats below the menu bar instead |
NotchPal works with either agent, or both at once. You do not need both installed.
NotchPal is intended to be cloned or downloaded from this GitHub repository and built locally. Official source and updates live here; paid redistributions are not authorized.
Grab the latest NotchPal-x.y.z.zip from
Releases, unzip it, and move
NotchPal.app to Applications.
Warning
Release builds are not notarized, so macOS will refuse to open the app the first time — "Apple could not verify NotchPal is free of malware". Notarizing requires a paid Apple Developer ID this project does not have.
On macOS 15 and later, right-click → Open no longer bypasses this. Either:
- Try to open the app, click Done, then go to System Settings › Privacy & Security, scroll to Security, and click Open Anyway; or
- Clear the quarantine flag yourself:
xattr -dr com.apple.quarantine /Applications/NotchPal.app
Building from source below avoids this entirely — the binary never gets quarantined because it never came from the internet.
git clone https://github.com/simonveprek/NotchPal.git
cd NotchPal
open Package.swiftPick the NotchPal scheme and hit Run. A notch glyph appears in your menu bar and a card drops down confirming that NotchPal is listening.
Then register the hooks — either from the menu bar item, or:
swift run NotchPal --install-hooksNotchPal runs fine as a bare executable, but a bundle gives it a proper identity and lets it launch at login like any other app.
make installThis builds NotchPal.app, copies it to /Applications, registers the hooks, and
launches it.
swift run NotchPal --statussocket: /Users/you/Library/Application Support/NotchPal/notchpal.sock
reporter: /path/to/.build/debug/notchpal-report
Claude Code: installed — /Users/you/.claude/settings.json
Codex: installed — /Users/you/.codex/hooks.json
Codex needs one extra step. Claude Code runs the hooks as soon as they are registered; Codex deliberately ignores any new or changed hook until you have reviewed exactly what it runs. Enable them once:
- Codex app — open Settings and allow Hooks.
- Codex CLI — run
/hooksand trust the NotchPal command.
Then start a session in either agent; the notch should come alive on your first prompt.
Claude Code and Codex converged on nearly the same lifecycle hook contract — same
event names, same session_id / cwd / hook_event_name envelope, same
tool_input blob. NotchPal exploits that: one decoder serves both agents.
┌──────────────┐ hook JSON ┌───────────────┐ 1 line of JSON ┌───────────┐
│ Claude Code │ ─────────────► │notchpal-report│ ─────────────────► │ NotchPal │
│ Codex │ on stdin │ (tiny CLI) │ Unix socket │ (menu bar)│
└──────────────┘ └───────────────┘ └─────┬─────┘
│
┌─────▼─────┐
│ notch │
└───────────┘
notchpal-report is spawned by the agent for each lifecycle event. It parses the
payload, summarizes it into a short structured event, writes one line to a Unix
domain socket, and exits. It runs on the agent's critical path, so it follows two
absolute rules:
- Always exit 0. A non-zero exit from a hook can block the agent's tool call.
- Never write to stdout. Agents parse hook stdout as JSON control output.
If NotchPal is not running, the socket connect fails, and the reporter exits silently in about a millisecond. There is no retry and no queue — a dropped status update is worth far less than a stalled agent.
NotchPal listens on the socket, folds events into per-session state, and drives
DynamicNotchKit.
| Agent | File | Events |
|---|---|---|
| Claude Code | ~/.claude/settings.json |
SessionStart SessionEnd UserPromptSubmit PreToolUse PostToolUse PostToolUseFailure Stop StopFailure Notification PermissionRequest SubagentStart SubagentStop PreCompact PostCompact |
| Codex | ~/.codex/hooks.json |
the same, minus the three Claude-only events |
NotchPal subscribes to the events that change what the notch should show, and no others — every extra subscription is another process spawned on the agent's critical path.
The installer is written to be a good guest in a file you own: existing keys are
preserved, a timestamped .bak is taken before every write, and uninstall removes
exactly what install added.
swift run NotchPal --uninstall-hooksThe whole point is that the detail survives the trip. Every tool gets its own reading:
| Tool | Shows |
|---|---|
Read |
Reading NotchController.swift · lines 40–99 |
Edit |
Editing EventServer.swift · replacing every match |
Write |
Writing README.md · 4.2 KB |
apply_patch (Codex) |
Patching schema.ts · and 3 more |
Bash |
Running swift build -c release · the command's description |
Grep |
Searching “TODO” · *.swift |
Glob |
Finding Sources/**/*.swift |
WebFetch |
Fetching developer.apple.com · /design/… |
WebSearch |
Searching “liquid glass” · web |
Task |
Delegating explore the parser · Explore |
update_plan (Codex) |
Working on Write the parser · step 2 of 3 |
mcp__github__create_issue |
Calling create issue · github |
| anything unknown | the tool name, de-underscored, rather than a blank |
Between tool calls it reports what just finished and how long it took:
◆ Continuing 1:47
Last update: reading NotchController.swift in 0.4s · NotchPal
Durations are live. A tool call is timed from its PreToolUse to the matching
PostToolUse by tool_use_id, so overlapping calls do not confuse the clock.
An ambient display earns its place by being quiet.
| State | Notch |
|---|---|
| Nothing running | Hidden |
| Working | Compact — mark on the left, verb and timer on the right |
| Turn starts / ends | Expands for a couple of seconds, then settles |
| Agent needs permission | Expands and stays until it is resolved |
| You hover it | Expands, because that is you asking |
| You hover a row | An ✕ appears — click it to dismiss that session |
Expanding on every tool call would strobe. If you want more, there is one setting — Expand when the work changes — which expands briefly when the agent switches between kinds of work (reading → running → editing). It is off by default.
Interrupting a turn, quitting the CLI, or closing the terminal fires no Stop and
no SessionEnd. NotchPal notices staleness rather than waiting to be told. Reasoning,
continuation, and tool activity remain live for up to 30 minutes of silence; Codex
can legitimately reason for several minutes without emitting a hook event, so a
shorter timeout makes a live turn disappear and then reappear on its next tool call.
You can always click a row to dismiss it, or use Forget sessions in the menu.
Apple's guidance is explicit that Liquid Glass belongs to the navigation and control layer, and that you should not stack it:
Always avoid glass on glass. Stacking Liquid Glass elements can quickly make the interface feel cluttered.
On a notched Mac the panel is opaque black; on every other Mac DynamicNotchKit
floats a .popover material. Either way, the panel is already the material
layer. So NotchPal applies .glassEffect in exactly one place — the tinted disc
behind each agent's mark — where it is a genuine control-layer object above the
panel. It carries a glassEffectID inside a GlassEffectContainer, so when a
second agent starts the two discs fuse and separate.
Everything else on the card is plain vibrant type, which is what Apple tells you to use on top of a material.
Sources/NotchPal/Vector/SVGPath.swift is a complete SVG path-data parser — full
grammar (M m L l H h V v C c S s Q q T t A a Z z), implicit repeated argument
sets, unseparated negative numbers, and proper elliptical-arc handling via
endpoint-to-centre conversion.
The two marks ship as their published path data and are parsed into SwiftUI
Shapes at launch. That means they are exact at any size, take any tint, animate
as real geometry, and the repo contains zero binary assets.
You should be able to tell which agent is working from the corner of your eye, before reading anything. So the motion comes from each mark's own geometry:
- Claude — pulse. The burst collapses almost completely, then expands beyond its resting size. The full-range motion stays obvious even in the compact notch.
- Codex — ratchet. The knot has six-fold symmetry, so a sixth of a turn lands it exactly back on itself. Rather than sliding, it advances one notch at a time and rests — a mechanism stepping forward, not a wheel spinning.
Claude keeps its coral fill. The OpenAI knot is white for maximum contrast against the black notch, with the surrounding glass retaining its green tint.
All motion derives from a pausable monotonic clock, so stopping mid-gesture
freezes rather than snapping back. accessibilityReduceMotion disables it all.
The expanded card shows three things: what, the detail behind it, and how long. There is no agent name (the mark says that), no model name, no tool counter, and no progress bar. An indeterminate bar cannot tell you anything the mark's own motion does not already say.
Everything lives in the menu bar item.
| Setting | Default |
|---|---|
| Claude Code hooks | off until you turn them on |
| Codex hooks | off until you turn them on |
| Expand when the work changes | off |
| Open at Login | off |
Open at Login registers the app with macOS through SMAppService, so it also
appears under System Settings › General › Login Items and can be turned off
there. It needs the bundled app — running the executable straight from
swift build gives macOS no bundle to register, and the menu item says so.
Environment:
| Variable | Effect |
|---|---|
NOTCHPAL_SOCKET |
Override the socket path |
NOTCHPAL_VERBOSE=1 |
Make notchpal-report log to stderr |
NotchPal is entirely local and makes no network calls of any kind.
- The transport is a Unix domain socket,
chmod 0600, in your Application Support directory. Nothing is exposed to the network, and there is no port. - Events are held in memory only. Nothing is written to disk, ever.
- Prompts and assistant messages are truncated to 140 characters in the reporter, before they leave the hook.
- Sessions are forgotten after 30 minutes of silence, and on quit.
Worth knowing: file paths, shell commands, and search patterns from your sessions are rendered on screen. If you share your screen while an agent runs, that is what will be visible.
The menu bar item is missing.
You are probably running a build older than the AppKit status item change. NotchPal
creates its NSStatusItem directly rather than using SwiftUI's MenuBarExtra,
precisely because MenuBarExtra needs a bundled app and therefore never appears
when you Run an SPM executable from Xcode.
The app launches and nothing happens. That is the expected first run. NotchPal has no window and no Dock icon. It shows a card on launch confirming it is listening — if you missed it, use Show status in the menu.
Hooks are installed but the notch never moves.
swift run NotchPal --status # is the reporter path right?
echo '{"session_id":"x","hook_event_name":"UserPromptSubmit","prompt":"hi"}' \
| .build/debug/notchpal-report --agent claude --verbosedelivered means the socket is fine and the problem is the hook registration.
no listener means NotchPal is not running.
For Codex, also open /hooks and check that the NotchPal hooks are trusted.
Codex skips new or changed command hooks until you approve their current hash.
Hooks say "update needed".
The registered path points at a different copy of notchpal-report than the running
app — usually a debug build after installing to /Applications. Toggle the hooks
off and on.
A session is stuck showing work that finished.
Click the row to dismiss it, or Forget sessions. If it happens repeatedly with
long reasoning turns, raise stallLimit for .thinking in
Sources/NotchPal/AgentSession.swift.
swift build # compile
swift test # 19 tests, no simulator or network needed
make app # assemble build/NotchPal.app
make status # socket, reporter path, hook state
swift Scripts/make-icon.swift # redraw Resources/AppIcon.icnsThe icon is a notch: a menu bar line that dips down and comes back out. It is the
same construction as the status item glyph in Sources/NotchPal/NotchGlyph.swift,
rendered at 1024pt — one mark at two sizes, not two that resemble each other.
Like the brand marks, it is drawn in code rather than stored as a binary asset, so every size is rendered from the vector routine instead of downsampled, and a reviewer can see what changed from the diff.
It is monochrome on purpose. A coral-to-green stroke naming both agents was tried first, but the two hues cross through a muddy olive at the midpoint — exactly where the glyph's most important detail sits. Colour belongs in the notch at runtime, where it carries state; the icon stays quiet.
Sources/
NotchPalCore/ pure Foundation — no SwiftUI, so the reporter stays fast
Agent.swift which agents exist and where they keep config
AgentEvent.swift the wire model
HookPayload.swift hook JSON → AgentEvent (both agents)
ActivityDescriptor.swift tool_input → a readable sentence
HookInstaller.swift merge/unmerge hooks in the agents' own config
EventClient.swift write one line, fail fast, never block the agent
EventServer.swift listen, frame by newline, deliver on main
NotchPalReport/ the hook binary
NotchPal/ the menu bar agent
AppModel.swift events → per-session state
NotchController.swift state → hidden / compact / expanded
Views/ the notch itself
Vector/ SVG path parser + the two marks
One case in ActivityDescriptor.describe, one test in
Tests/NotchPalCoreTests. The bar is that the result should be more useful than the
tool name alone — if it is not, leave it to the default.
If it can run a command on a lifecycle event and hand it JSON, it fits. Add a case
to Agent, teach HookPayload its event names, and give it a mark and a motion.
The Claude mark is a trademark of Anthropic. The OpenAI mark is a trademark of OpenAI. Neither company is affiliated with, sponsors, or endorses this project.
The marks appear here for one reason: to identify which of your agents is running. That is nominative use, and NotchPal does not use them as its own branding — the app's own mark is the notch glyph.
One thing to know if you fork this: the Claude path data comes from Simple Icons, whose icon files are CC0. The OpenAI path data came from an older Simple Icons release — it is not in current versions. Icons usually leave that set following a trademark request, so treat the OpenAI mark with more care than the Anthropic one.
If you would rather not carry that at all, BrandMark.data(for:) is a single
function and swapping in neutral geometric glyphs is a one-line change.
A live demo of the notch, the full feature tour, and the rest of the Mac apps live at mac.simonveprek.cz. The source for that site lives in its own repository.
Built on DynamicNotchKit by Kai Azim (MIT).
The desktop wallpaper used in the website's demo is AI-generated, and appears there only as set dressing for the screenshot.
Source-available: use, modify, fork, and share; do not sell.
NotchPal is licensed under the MIT License with the Commons Clause License Condition v1.0. The MIT permissions remain except for the right to sell the software as defined by the Commons Clause. This is source-available, not OSI-approved open source. See LICENSE for the binding terms.