SketchyBar for yabai. Inspired by simple-bar.
A native, opinionated macOS status bar for yabai: one 35 pt strip over the hidden system menu bar, spaces with app icons, the focused space's windows, and a fixed row of data widgets that only show up when something is worth looking at. One Swift binary, no Electron, no WebView, no Node, no polling scripts.
obi (帯) is the paper band wrapped around a Japanese book. It pairs with yabai.
simple-bar on Übersicht looks great, but it costs a
WebView, a Node server, a second Node process for events, and shell scripts polling top,
netstat, system_profiler and defaults read every few hundred milliseconds. obi keeps the
look and the workflow and replaces the machinery with AppKit and system APIs:
| simple-bar + Übersicht | obi | |
|---|---|---|
| Processes | Übersicht, its Node server, simple-bar-server | one obi |
| yabai events | HTTP → WebSocket → JS re-query | HTTP → debounced re-query |
| CPU / memory / network | top, memory_pressure, netstat -w1 |
host_processor_info, sysctl, getifaddrs |
| Volume | osascript every 20 s |
CoreAudio |
| Keyboard layout | defaults read every 200 ms |
TIS notification |
| Idle cost | a WebKit process | < 1 % CPU, ~60 MB |
Spaces. Every space of every display, filtered by label. Each pill shows the label (or index) and one icon per app with windows there; the focused app's icon is brighter. Focused, visible and fullscreen states. Click focuses the space, Alt+click renames its label inline.
Process. Windows of the space that has focus, on any display. Layout badge (bsp /
stack / float) and a mode badge fed by Hammerspoon or skhd ({"mode","color"}). Click
focuses the window.
Data widgets (order is fixed):
| Widget | Shows | Hidden when | Interval | Click |
|---|---|---|---|---|
| zoom | mic / camera state during a zoom.us meeting | not in a meeting | 5 s | |
| netstats | ↓ ↑ rates | both below threshold (1500 kb/s) | 2 s | |
| cpu | user CPU % | below threshold (50 %) | 2 s | Activity Monitor |
| memory | used % (red above 70) | below threshold (50 %) | 4 s | |
| mic | input volume, unfolding slider on hover | 20 s | drag | |
| sound | output volume, unfolding slider on hover | 20 s | drag | |
| wifi | SSID, max_length chars then …, full name on hover |
Wi‑Fi off | 20 s | right click → Network settings |
| keyboard | layout name, 3 chars | on change | next layout | |
| date | Wed 23 Sep |
30 s | Calendar | |
| time | HH:mm |
minute boundary |
Thresholds are the point: cpu, memory and network widgets stay out of the way until they are hot. Data keeps being collected while a widget is hidden.
- macOS 14+, Apple Silicon or Intel
- yabai with the scripting addition (SIP partially disabled)
- The system menu bar set to auto-hide (
System Settings → Control Center → Automatically hide and show the menu bar) and yabai configured withexternal_bar main:0:0. obi draws in the strip the hidden menu bar leaves free and does not reserve space.
brew tap hmepas/formulae
brew install obi # builds from source, needs Xcode
brew services start obi # launchd agent sh.brew.obibrew services restart obi after brew upgrade obi. Do not mix with obi install below:
that registers a second agent pointing at a versioned Cellar path.
git clone https://github.com/hmepas/obi.git && cd obi
scripts/deploy.sh # release build → ~/Applications/obi.app → launchd agent com.hmepas.obiobi install writes ~/Library/LaunchAgents/com.hmepas.obi.plist and bootstraps it;
obi uninstall removes it. Both are idempotent. Re-run scripts/deploy.sh after every
change: it recreates the bundle, which matters because overwriting a running
executable in place makes macOS kill the next launch.
Since macOS 27.2 the SSID is only readable by apps with Location access, and macOS shows the
permission prompt for app bundles only. Both install paths therefore run obi from an ad-hoc
signed obi.app (scripts/bundle.sh); allow the prompt on first start, or enable obi in
System Settings → Privacy & Security → Location Services. Without it the wifi widget shows —.
The grant is tied to the ad-hoc signature, so macOS asks again after every rebuild or
brew upgrade obi.
A bare .build/debug/obi gets no prompt: bundle it first
(scripts/bundle.sh .build/debug/obi .build/obi.app) and start .build/obi.app/Contents/MacOS/obi.
Point yabai at obi from ~/.yabairc. Endpoints are simple-bar-server compatible, so an
existing simple-bar-conf.sh works as is once the Übersicht osascript refresh line is gone.
for ev in window_focused window_created window_destroyed window_resized window_minimized \
window_deminimized window_title_changed application_activated; do
yabai -m signal --add event=$ev action="curl -s localhost:7776/yabai/windows/refresh"
done
for ev in space_changed space_created space_destroyed display_changed; do
yabai -m signal --add event=$ev action="curl -s localhost:7776/yabai/spaces/refresh"
done
for ev in display_added display_removed display_moved; do
yabai -m signal --add event=$ev action="curl -s localhost:7776/yabai/displays/refresh"
doneWrite {"mode": "app_launch", "color": "red"} to the mode file (default
~/Library/Caches/obi/mode). obi watches the file, so the
badge updates without any request; curl localhost:7776/skhd/mode/refresh also works. An
empty mode hides the badge. Colors are palette names: red, green, yellow, orange,
blue, magenta, cyan, white, foreground.
One palette (Material Ocean), three shapes. Switch with theme = ... in the config or obi theme <name> at runtime.
| Theme | Look |
|---|---|
pills |
simple-bar: transparent 35 pt strip, dark rounded groups, a grey pill per item |
band |
native menu bar: black band the full height of the notch strip, system font, text on black; only the focused space / window is highlighted |
segmented |
black band; spaces and the process row are segmented controls, data widgets are text split by hairlines |
~/.config/obi/config.toml. Missing file means defaults; a parse error is logged and the bar
starts with defaults anyway.
theme = "pills" # pills | band | segmented
yabai = "/opt/homebrew/bin/yabai"
port = 7776
mode_file = "~/Library/Caches/obi/mode"
[spaces]
exclude = ["s16", "sC", "sM"] # exact label match
apps_exclude = ["Wispr Flow"] # case-insensitive; floating panels are always hidden
[icons] # app → glyph instead of the app icon
iTerm2 = "svg:terminal" # svg:<builtin> built-ins: terminal, ghostty, safari
Ghostty = "svg:ghostty" # svg:<path.svg> your own monochrome SVG
Safari = "safari" # anything else is an SF Symbol name
[widgets.cpu]
threshold = 50
[widgets.memory]
threshold = 50
[widgets.netstats]
threshold = 1500 # kb/s
[widgets.keyboard]
max_length = 3
[widgets.wifi]
device = "en0"
max_length = 12 # longer SSIDs end in …, hover for the full nameReload without restarting: obi reload, or kill -HUP $(pgrep -x obi).
obi [--port N] [--offset PX] [--config PATH] run the bar (what launchd calls)
obi install | uninstall manage the launchd agent (source installs)
obi reload re-read the config, rebuild widgets
obi theme <name> switch theme at runtime
obi refresh spaces|windows|displays|mode poke a refresh
GET 127.0.0.1:7776 |
|
|---|---|
/yabai/{spaces,windows,displays}/refresh |
re-query yabai (debounced 20 ms) |
/skhd/mode/refresh |
re-read the mode file |
/config/reload |
reload config |
/theme/<name> |
switch theme |
/health |
ok |
obi --port 7778 --offset 28 --config ./dev-config.toml # 28 px lower, own port
scripts/dev-signals.sh add 7778 # yabai signals for this instance
scripts/dev-signals.sh remove- Swift 5 language mode, AppKit, SwiftPM, no dependencies, no Xcode project. Build with
swift build(Xcode toolchain). - A widget is a class with a view and
start/stop. Data widgets subclassPollingWidget:fetch()on a utility queue,render()on main. Adding one is a file plus a line inWidgetRegistry. - Theme is a value (palette, roles, font, metrics). Widgets never see color literals.
- Config and theme changes rebuild every widget; nothing hot-patches itself.
- HTTP is
NWListenerplus a hand-parsed request line. TOML is a strict subset with its own parser, but the file is valid TOML.
Palettes beyond Material Ocean, light mode, floating or bottom bars, multiple bars, graphs, media / weather / battery / GitHub widgets, a settings UI, plugins, AeroSpace or skhd key handling, SIP-enabled setups.
Layout, palette and the terminal / ghost / compass glyphs come from simple-bar by Jean Tinland (MIT). SketchyBar was the reference for talking to macOS.
