A themeable, fully interactive macOS menu bar built on SketchyBar. Free forever, source visible to everyone — and it replaces a small pile of paid menu bar apps.
curl -fsSL https://himanshu007-creator.github.io/sketchetc/install.sh | bashRead the installer before you run it (recommended — it is 191 lines, no obfuscation)
#!/bin/bash
# sketchetc installer · curl -fsSL https://himanshu007-creator.github.io/sketchetc/install.sh | bash
# Idempotent: first run installs, later runs upgrade in place.
set -euo pipefail
REPO_URL="https://github.com/himanshu007-creator/sketchetc.git"
BRANCH="${SKETCHETC_CHANNEL:-production}"
APP="$HOME/.local/share/sketchetc/app"
DRY=0
COUNT=1
LOCAL=0
for a in "$@"; do
[ "$a" = "--dry-run" ] && DRY=1
[ "$a" = "--no-count" ] && COUNT=0
[ "$a" = "--local" ] && LOCAL=1
done
[ -n "${SKETCHETC_NO_TELEMETRY:-}" ] && COUNT=0
# --local installs from the checkout this script sits in rather than cloning, so
# the repo's own install.sh can be a one line wrapper around this file. Two
# installers drifting apart is what let the same bug ship twice.
if [ "$LOCAL" = 1 ]; then
APP="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
[ -d "$APP/config" ] || { echo "--local: no config/ next to $APP, is this a sketchetc checkout?"; exit 1; }
fi
say() { printf '�[38;5;213m==>�[0m %s
' "$1"; }
warn() { printf '�[38;5;215m ! �[0m%s
' "$1"; }
run() { if [ "$DRY" = 1 ]; then printf ' would run: %s
' "$*"; else "$@"; fi }
[ "$(uname)" = "Darwin" ] || { echo "sketchetc is macOS only."; exit 1; }
# ---------- dependencies ----------
if ! command -v brew >/dev/null; then
say "Homebrew is required: https://brew.sh"
exit 1
fi
say "Installing dependencies"
run brew tap FelixKratz/formulae
run brew tap koekeishiya/formulae
brew trust felixkratz/formulae 2>/dev/null || true
brew trust koekeishiya/formulae 2>/dev/null || true
for f in sketchybar macmon pngpaste switchaudio-osx blueutil cliclick; do
if brew list "$f" &>/dev/null; then printf ' have %s
' "$f"; else run brew install "$f"; fi
done
brew list skhd &>/dev/null || run brew install koekeishiya/formulae/skhd
brew list --cask font-jetbrains-mono-nerd-font &>/dev/null || run brew install --cask font-jetbrains-mono-nerd-font
if [ ! -f "$HOME/Library/Fonts/sketchybar-app-font.ttf" ]; then
# a fresh macOS account has no ~/Library/Fonts yet, and curl will not create
# it. Under set -e that aborted the whole install before the bar ever started.
run mkdir -p "$HOME/Library/Fonts"
run curl -fsSL "https://github.com/kvndrsslr/sketchybar-app-font/releases/latest/download/sketchybar-app-font.ttf" \
-o "$HOME/Library/Fonts/sketchybar-app-font.ttf" \
|| warn "app icon font download failed, app icons will fall back"
fi
# ---------- legacy config migration ----------
# Older installs keep settings.conf and widgets.conf inside the checkout with
# local edits. Any release that also touches those files makes --ff-only refuse,
# which is how an upgrade could fail outright. The values are what matter, not
# the files: lift them into ~/.config/sketchetc, then let the checkout go clean.
if [ -d "$APP/.git" ] && [ "$DRY" = 0 ]; then
say "Preserving your settings"
mkdir -p "$HOME/.config/sketchetc/themes" 2>/dev/null || true
if [ ! -f "$HOME/.config/sketchetc/settings.conf" ] && [ -f "$APP/config/settings.conf" ]; then
cp "$APP/config/settings.conf" "$HOME/.config/sketchetc/settings.conf" || true
fi
if [ ! -f "$HOME/.config/sketchetc/widgets.conf" ] && [ -f "$APP/config/widgets.conf" ]; then
cp "$APP/config/widgets.conf" "$HOME/.config/sketchetc/widgets.conf" || true
fi
for f in .theme .iconset .notify_sound .extras_collapsed; do
[ -f "$APP/config/$f" ] && cp "$APP/config/$f" "$HOME/.config/sketchetc/$f" 2>/dev/null || true
done
for f in "$APP"/config/themes/*.sh; do
[ -e "$f" ] || continue
case "$(basename "$f")" in vice-city.sh|cyberpunk.sh|matrix.sh|catppuccin.sh|miami-sunset.sh) continue ;; esac
cp "$f" "$HOME/.config/sketchetc/themes/" 2>/dev/null || true
done
git -C "$APP" checkout -- config/settings.conf config/widgets.conf 2>/dev/null || true
fi
# ---------- code ----------
if [ "$LOCAL" = 1 ]; then
say "Using this checkout ($APP)"
elif [ -d "$APP/.git" ]; then
say "Updating sketchetc ($BRANCH)"
run git -C "$APP" fetch --quiet origin "$BRANCH"
run git -C "$APP" checkout --quiet "$BRANCH"
run git -C "$APP" pull --ff-only --quiet origin "$BRANCH"
else
say "Fetching sketchetc ($BRANCH)"
run mkdir -p "$(dirname "$APP")"
run git clone --quiet --branch "$BRANCH" "$REPO_URL" "$APP"
fi
# ---------- link config ----------
say "Linking config into ~/.config/sketchybar"
if [ -e "$HOME/.config/sketchybar" ] && [ ! -L "$HOME/.config/sketchybar" ]; then
BK="$HOME/.config/sketchybar.bak.$(date +%s)"
warn "existing config moved to $BK"
run mv "$HOME/.config/sketchybar" "$BK"
fi
run mkdir -p "$HOME/.config"
run ln -sfn "$APP/config" "$HOME/.config/sketchybar"
[ "$DRY" = 1 ] || chmod +x "$APP/config/sketchybarrc" "$APP/config"/*.sh "$APP/config/items/"* "$APP/config/plugins/"*.sh 2>/dev/null || true
# ---------- helpers ----------
if command -v swiftc >/dev/null; then
say "Compiling helpers"
run env CONFIG_DIR="$APP/config" "$APP/config/plugins/build.sh"
else
warn "swiftc missing (install Xcode command line tools) — windows and pickers will be unavailable"
fi
# ---------- user config ----------
# Settings live in ~/.config/sketchetc, never in the checkout: a tracked config
# file means every clone carries its own, and switching checkouts silently moved
# people's data folder.
say "Seeding user config"
run mkdir -p "$HOME/.config/sketchetc/themes"
# non-fatal: the bar seeds these itself on first run, and an install must never
# die on a step that only saves it a second of work later
if [ ! -f "$HOME/.config/sketchetc/settings.conf" ] && [ -f "$APP/config/settings.default.conf" ]; then
run cp "$APP/config/settings.default.conf" "$HOME/.config/sketchetc/settings.conf" || true
fi
if [ ! -f "$HOME/.config/sketchetc/widgets.conf" ] && [ -f "$APP/config/widgets.default.conf" ]; then
run cp "$APP/config/widgets.default.conf" "$HOME/.config/sketchetc/widgets.conf" || true
fi
# ---------- macOS bits ----------
say "System setup"
run defaults write NSGlobalDomain _HIHideMenuBar -bool false
if [ "$DRY" = 0 ]; then
hotkeys() {
TMP=$(mktemp -d); defaults export com.apple.symbolichotkeys "$TMP/shk.plist"
python3 - "$TMP/shk.plist" <<'PY'
import plistlib, sys
p = sys.argv[1]
with open(p, 'rb') as f: d = plistlib.load(f)
hk = d.setdefault('AppleSymbolicHotKeys', {})
for i, key in enumerate([118, 119, 120, 121]): # Switch to Desktop 1-4
hk[str(key)] = {'enabled': True, 'value': {'parameters': [65535, 18 + i, 262144], 'type': 'standard'}}
with open(p, 'wb') as f: plistlib.dump(d, f)
PY
defaults import com.apple.symbolichotkeys "$TMP/shk.plist"
/System/Library/PrivateFrameworks/SystemAdministration.framework/Resources/activateSettings -u
killall Finder 2>/dev/null || true
}
hotkeys || warn "could not set the ctrl+1..4 desktop hotkeys, everything else is fine"
fi
if [ "$DRY" = 0 ]; then
mkdir -p "$HOME/.config/skhd" 2>/dev/null || true
grep -q clipboard_choose "$HOME/.config/skhd/skhdrc" 2>/dev/null || \
echo 'alt - v : CONFIG_DIR=$HOME/.config/sketchybar $HOME/.config/sketchybar/plugins/clipboard_choose.sh' >> "$HOME/.config/skhd/skhdrc"
grep -q prompts.sh "$HOME/.config/skhd/skhdrc" 2>/dev/null || \
echo 'alt - p : CONFIG_DIR=$HOME/.config/sketchybar $HOME/.config/sketchybar/plugins/prompts.sh' >> "$HOME/.config/skhd/skhdrc"
fi
# both are already-done-is-fine: skhd exits non-zero when the service file
# exists, which under set -e used to abort every reinstall part way through
run skhd --install-service || true
run skhd --restart-service 2>/dev/null || run skhd --start-service || true
say "Starting sketchetc"
run brew services restart sketchybar
# anonymous install counter: one hit on a public tally, nothing about you is
# sent (no IP logging on our side, no id, no phone home afterwards).
# Skip with --no-count or SKETCHETC_NO_TELEMETRY=1
if [ "$COUNT" = 1 ] && [ "$DRY" = 0 ]; then
curl -s --max-time 3 "https://api.counterapi.dev/v1/sketchetc/installs/up" >/dev/null 2>&1 || true
printf ' counted this install on a public tally · skip with --no-count
'
elif [ "$COUNT" = 0 ]; then
printf ' install counter skipped
'
fi
cat <<EOF
sketchetc $( [ -f "$APP/VERSION" ] && cat "$APP/VERSION" ) is running.
Grant these when macOS asks (everything degrades gracefully if you don't):
Accessibility → window snapping, desktop switching, paste
Automation → media controls, app switching
Calendar → the meeting widget
Screen Recording → the screenshot widget
Press Option+V for clipboard history · click to toggle widgets
Click → studio for themes · → Settings for notifications
Updates appear as a pill in the bar. Uninstall: $APP/uninstall.sh
EOFVerify it instead of trusting us:
curl -fsSLO https://himanshu007-creator.github.io/sketchetc/install.sh
shasum -a 256 install.sh # expect b195ac61dae16451af90d2bd0db47780cdbc06852ce8a572763eb373d4b45b0f
less install.sh # read it
bash install.shPin to a release (immutable — this exact commit, forever):
curl -fsSL https://raw.githubusercontent.com/himanshu007-creator/sketchetc/v1.3.2/docs/install.sh -o install.sh
shasum -a 256 install.sh && bash install.shRe-running the same command upgrades in place. Prefer git? git clone -b production … then ./install.sh.
Skip the anonymous install counter with --no-count or SKETCHETC_NO_TELEMETRY=1.
| Widget | What it does | Replaces |
|---|---|---|
| Network speed | live ↓/↑, click → top-5 bandwidth hogs | iStat Menus ($12) |
| Temps | CPU °C + fan RPM (auto-hides RPM on fanless Macs) | TG Pro ($10) |
| Caffeinate | one-click keep-awake toggle | Lungo ($10) |
| Speedtest | click → Apple's networkQuality, result in the bar |
Speedtest app |
| Pomodoro | click → 25:00 countdown, spoken finish | Flow ($) |
| Meetings | "Standup in 8m" appears <60 min out, click joins the Zoom/Meet | Meeter ($) |
| GitHub | PRs awaiting your review · your open PRs, click to open | — |
| Dev ports | running dev servers (3000, 5173, 8080…), click to kill | — |
| Window snapping | halves, thirds, maximize, center the frontmost window | Magnet ($8) |
| Quick switches | dark mode, hide desktop icons, empty Trash, screensaver | One Switch ($10) |
| Screenshots | area, window, full screen, 5s timer — every shot also lands on the clipboard, ready to paste | CleanShot ($29) |
| Bluetooth | paired devices + battery, click to connect | AirBuddy ($10) |
| Audio switching | pick output device right in the volume popup | SoundSource ($47) |
| AI agents | running claude/codex/gemini sessions with uptime | — |
| Clear RAM | reaps orphaned helpers + purge, then speaks the result | CleanMyMac-ish |
| Weather | temp + AQI, color-coded | paid weather widgets |
| + | spaces, front-app switcher, media controls + progress, volume slider, battery health, calendar | — |
| Clipboard history | Option+V anywhere → native picker of your last 5 copies (text + image thumbnails), arrow keys + Enter or click to paste — works in terminals too | Paste ($30/yr) / Maccy | | Aura points | pomodoros scored on real activity (keys/clicks/agents/PRs) → spoken awards, shareable PNG cards (how) | — | | Journal | tamper-evident daily work log: immutable files + hash chain, export any timeframe (how) | — |
Everything is toggleable from the popover — turn off what you don't want, it vanishes from the bar. Icons come in three sets (nerd / minimal / emoji), switchable next to the theme dots in the popover.
Vice City ![]() |
Cyberpunk ![]() |
Matrix ![]() |
Catppuccin ![]() |
Miami Sunset ![]() |
add yours → |
Theme Studio ( → studio) gives every color role a native picker. Built-in
themes are read only: Duplicate one, then edit and Apply. The icon set is a
global setting (six of them: nerd, minimal, outline, retro, emoji, plain) and
lives in the studio footer, so it applies across every theme. A theme is a 12-line file in
config/themes/, and every widget uses palette roles, so any theme restyles the
whole bar including popups.
- One popup at a time, closes on outside click / app switch / mouse-out
- Reserved space — windows tile below the bar, never behind it
- Fullscreen guard — fullscreening an app converts it to fill-below-the-bar (toggleable)
- Hover glows, bounce animations, all in the active theme's colors
- Fixed widget widths — changing numbers never reflow the bar
- Settings ( → Settings…) — silence any notification category, turn sounds
or spoken announcements off, control screenshot-to-clipboard. Stored in
config/settings.conf, editable by hand too. - Autostarts on login (brew services / launchd)
The agents widget lists what is running with uptime and CPU, stops a runaway one on click, and tells you when a long run finishes so you can walk away from it. It also shows today's token totals per project, read only from the session files Claude Code already writes on your machine. Tokens, never a cost: the rates are not in those files, so any dollar figure would be invented.
Docker, Cursor, Dropbox and anything else that installs a menu bar icon are mirrored into the bar, with a chevron that collapses them into a tray. Needs Screen Recording permission (System Settings → Privacy & Security → Screen Recording → sketchybar), which is what macOS requires to draw them.
Grant the permissions macOS asks for (Accessibility → space switching + fullscreen guard; Calendar → meetings widget). Everything degrades gracefully if you decline.
- menu → "Revert to macOS bar" — confirmation dialog, then the native bar
is back instantly (
brew services start sketchybarreturns you). ./uninstall.sh— full clean removal.
WIDGETS.md — a widget is ~30 lines of bash in two files. ROADMAP.md — what's
next (album-art tinting, per-agent token burn, more themes — PRs welcome).
Built on FelixKratz/SketchyBar · app icons from sketchybar-app-font · temps via macmon.
CC BY-NC-ND 4.0 — plain English version: LICENSE-SUMMARY.md. Free to use forever, and it will never be sold. Share it and credit it freely; do not charge for it, and do not publish a modified copy as your own release. Configuring your own bar, writing your own themes, and forking to open a pull request here are all fine.





