TL;DR: faf-cli now fires native desktop notifications for the moments that matter — Trophy unlocks at 100%, tier upgrades, and long-running command completion. Inspired by Ghostty's notification model. Silent on terminals that don't support it. Opt out with one env var.
The Moment That Started It
You're running faf auto on a real project. The scan takes 15 seconds. You glance at Slack, answer a quick message, look up — was that done already? Did it work? You scroll back through the terminal to find the score.
This release fixes that. When faf auto finishes a long scan, your OS notification center pops up with the result. When you cross 100%, you get a 🏆 Trophy notification regardless of how long the run took. When a tier upgrade happens (Bronze → Silver, Silver → Gold), you know.
The mental model: one command, one notification, at the moment of payoff.
How It Works
faf-cli emits an OSC 9 escape sequence — a tiny terminal control code that supported terminals forward to the OS notification center. Ghostty does it. iTerm2 does it. Wezterm and Kitty do it. Everywhere else, the sequence is silently ignored. No native bindings, no platform-specific code paths, no extra dependencies.
This is how Claude Code's "waiting for your input" notifications appear in Ghostty. Same mechanism, same elegance. We borrowed the model.
The Three Rules
Notifications are easy to ship and easy to ruin. Three rules guided every decision:
- Valuable — only fires when there's something worth interrupting a human for. Not every step. Not progress. Not errors that are already visible in the terminal.
- Timely — fires at the moment of value, not before, not after. Quick commands (under 5 seconds) don't notify because you were probably watching.
- One-click gone — never repeats for the same event. Dismissal is the OS's job, not ours.
The 5-second duration guard is the key UX choice. It's calibrated for faf-cli's command profile — not the 10-second build-tool norm. faf auto on a small cached project finishes in 1–3 seconds, you see it, no notification needed. On a real project that takes 10–30 seconds, attention has drifted, the notification earns its space.
What Notifies, And When
| Trigger | When |
|---|---|
faf auto Trophy | Always — reaching 100% is a celebration, not a duration question |
faf auto tier upgrade | Always, only on score improvement (e.g. Silver → Gold) |
faf auto long scan | If the run took 5 seconds or more |
faf go completion | Always — interactive command, you almost certainly drifted between phases |
faf sync completion | If the sync took 5 seconds or more |
Click-To-Dismiss On macOS
By default macOS treats OSC 9 notifications as banners — they auto-dismiss in a few seconds. Some users (myself included) prefer the click-to-dismiss model, where notifications stay top-right until you actually look at them.
If terminal-notifier is on your PATH, faf-cli routes through it instead:
brew install terminal-notifier Then in System Settings → Notifications → terminal-notifier, switch to "Alerts". Every faf-cli notification now stays until you click. Optional — OSC 9 still works without it.
Opt Out
One env var. No config file. No flag.
export FAF_NO_NOTIFY=1 For users who want the OSC 9 path even when terminal-notifier is installed, FAF_NOTIFY_OSC9=1 forces it.
Try It
npm install -g faf-cli@6.3.0 Run faf auto in a real project from Ghostty or iTerm2. Watch your notification center.
The Numbers
- v6.3.0 — released April 27, 2026
- 409/409 — tests passing
- 100% — 🏆 Trophy score on project.faf
- 10 lines — the entire
notify()helper - 5 seconds — duration guard, calibrated for faf-cli's command profile
- Zero new dependencies — OSC 9 is just an escape sequence
Why "Ghostty-Inspired"
Ghostty is the terminal that made me notice the notification model in the first place. Watching Claude Code emit "waiting for your input" pop-ups while I was in another window — that was the moment. Same mechanism, different consumer.
Crediting the inspiration matters. Same pattern as F1-inspired engineering — we borrow what works, name where it came from, and keep building.
