Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: lint

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

jobs:
shell:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install shellcheck
run: sudo apt-get update && sudo apt-get install -y shellcheck

# The plugins and the install script. SC1091 is "can't follow source",
# which is expected: they source $HOME/.config/sketchybar/*.sh, a path that
# only exists once stow has run.
- name: shellcheck
run: |
shellcheck -S warning -e SC1091 \
sketchybar/.config/sketchybar/*.sh \
sketchybar/.config/sketchybar/plugins/*.sh \
raycast/.config/raycast/scripts/*.sh \
scripts/*.sh

# install is zsh, not bash — shellcheck cannot read it, so just parse it.
- name: zsh parse install
run: |
sudo apt-get install -y zsh
zsh -n install

sketchybar:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

# Catches the SketchyBar-specific failures that are invisible at runtime:
# a plugin without its execute bit, a self-hiding item missing updates=on,
# and a comment pointing at a section that no longer exists. None of these
# produce any error on the host — the item simply never updates.
- name: sketchybar semantics
run: ./scripts/lint-sketchybar.sh
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# Anchored with a leading slash so it matches only the repo root, never the
# tracked `claude/` stow package.
/.claude
CLAUDE.local.md


# ============================================================
Expand Down
2 changes: 2 additions & 0 deletions Brewfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ brew "eza"
brew "fzf"
brew "jq"
brew "zoxide"
brew "media-control"

# --- Utilities ---
brew "fastfetch"
Expand All @@ -48,6 +49,7 @@ brew "gh"
brew "git-delta"
brew "lazygit"
brew "lazydocker"
brew "shellcheck"

# --- Languages & runtimes ---
brew "direnv"
Expand Down
151 changes: 105 additions & 46 deletions CLAUDE.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ each declared tap trust as it adds it.

| Package | Contents |
|---------|----------|
| `aerospace` | [AeroSpace](https://github.com/nikitabobko/AeroSpace) tiling WM. Numeric workspaces on `alt-1`–`alt-9`, focus/move on `alt-hjkl`. Needs Accessibility permission; does **not** start at login |
| `aerospace` | [AeroSpace](https://github.com/nikitabobko/AeroSpace) tiling WM. Numeric workspaces on `ctrl-1`–`ctrl-9` (**not** `alt` — that is where a Danish layout keeps `[ ] { } \`), focus/move on `alt-hjkl`. Needs Accessibility permission; does **not** start at login |
| `bat` | bat config (Catppuccin Frappé theme) |
| `btop` | btop Catppuccin Frappé theme (the install script seeds `color_theme = "catppuccin_frappe"` for you) |
| `claude` | Claude Code settings and statusline |
Expand Down
248 changes: 88 additions & 160 deletions aerospace/.config/aerospace/aerospace.toml

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions claude/.claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
"type": "command",
"command": "bash ~/.claude/statusline.sh"
},
"enabledPlugins": {
"playwright@claude-plugins-official": true,
"skill-creator@claude-plugins-official": true
},
"effortLevel": "xhigh",
"tui": "fullscreen",
"theme": "auto",
Expand Down
37 changes: 27 additions & 10 deletions install
Original file line number Diff line number Diff line change
Expand Up @@ -444,20 +444,37 @@ fi
# ============================================================
# Post-stow seeding
# ============================================================
# --- sketchybar mic helper ---
# --- sketchybar compiled helpers ---
# Compiled, not interpreted: `swift <file>` on a trivial script measured 1.25s,
# which is not pollable. The binary is written next to the stowed source, which
# which is not pollable. Binaries are written next to the stowed source, which
# lands in $HOME rather than in the repo because stow runs --no-folding (helpers/
# is a real directory containing one symlink). mic.sh rebuilds it on demand too,
# so a `git pull` that changes the source doesn't need a re-run of this script.
mic_src="${HOME}/.config/sketchybar/helpers/mic.swift"
if [[ -r $mic_src ]] && command -v swiftc &>/dev/null; then
if swiftc -O -o "${mic_src:h}/mic" "$mic_src" 2>/dev/null; then
ok "sketchybar mic helper built."
# is a real directory containing symlinks). Both callers (mic.sh, system.sh)
# rebuild on demand too, so a `git pull` that changes a source doesn't need a
# re-run of this script.
#
# THREE-WAY GUARD, DELIBERATELY. An earlier version tested
# `[[ -r $src ]] && command -v swiftc` in one condition, which meant a MISSING
# SOURCE was indistinguishable from a missing toolchain and both exited silently
# — no ok, no warn, nothing. thermal.swift was untracked in git for a while, and
# a fresh clone would have produced a bar with no temperature and no explanation.
# Distinguish the three outcomes so absence is loud.
build_sb_helper() {
local name=$1 what=$2
local src="${HOME}/.config/sketchybar/helpers/${name}.swift"

if [[ ! -r $src ]]; then
warn "sketchybar ${name} helper source missing at ${src}; ${what}"
elif ! command -v swiftc &>/dev/null; then
warn "swiftc not found, so the sketchybar ${name} helper was not built; ${what}"
elif swiftc -O -o "${src:h}/${name}" "$src" 2>/dev/null; then
ok "sketchybar ${name} helper built."
else
warn "sketchybar mic helper failed to build; the mic item will stay hidden."
warn "sketchybar ${name} helper failed to build; ${what}"
fi
fi
}

build_sb_helper mic "the mic item will stay hidden."
build_sb_helper thermal "the temperature will be omitted from the system island."

# --- btop theme ---
# btop.conf is machine-local (btop rewrites it on every exit) so it isn't tracked.
Expand Down
2 changes: 1 addition & 1 deletion raycast/.config/raycast/scripts/reload-sketchybar.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
# the same repair sketchybarrc makes at the top of itself.
# USER the client resolves the running bar's mach port through it and aborts
# with "sketchybar-msg: 'env USER' not set! abort.." if it is missing.
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/bin:/bin:$PATH"
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:$PATH"
export USER="${USER:-$(id -un)}"

# --reload re-executes the config in place, so the bar keeps its PID and nothing
Expand Down
108 changes: 108 additions & 0 deletions scripts/lint-aerospace.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/usr/bin/env bash
# Catches the AeroSpace failure classes that are INVISIBLE when they happen —
# the sibling of scripts/lint-sketchybar.sh, same philosophy.
#
# AeroSpace's own validator (`reload-config --dry-run --warnings-as-errors`)
# already covers syntax and unknown commands, so this script does NOT re-check
# those. What it adds is the two CROSS-FILE couplings the validator cannot see,
# because each half is individually valid — they are only wrong together:
#
# * gaps.outer.top must track SketchyBar's bar height. Get it wrong and windows
# tuck under the bar or leave a band of wallpaper; nothing errors.
# * the border colour AeroSpace settles on after its focus pulse must match the
# resting colour in bordersrc. Get it wrong and every focus change "settles"
# on the old colour; nothing errors.
#
# Plus the ~/.aerospace.toml ambiguity trap, and — when the AeroSpace app is
# actually running — the built-in validator as a bonus.
#
# Run by hand or from CI. Exits non-zero on any failure. The static checks need
# no binary and no running app, so they are CI-safe; the dry-run self-skips.

set -uo pipefail
cd "$(dirname "$0")/.." || exit 1

TOML="aerospace/.config/aerospace/aerospace.toml"
RC="sketchybar/.config/sketchybar/sketchybarrc"
BORDERS="borders/.config/borders/bordersrc"
CLEARANCE=5 # gaps.outer.top - bar height, by design
fail=0

note() { printf ' %s\n' "$*"; }
bad() { printf 'FAIL %s\n' "$*"; fail=1; }

# ---------------------------------------------------------------
# 0. The files must exist where we expect them.
for f in "$TOML" "$RC" "$BORDERS"; do
[ -f "$f" ] || { bad "missing file: $f"; }
done
[ "$fail" -eq 0 ] || { printf '\nFAIL — repo layout unexpected\n'; exit 1; }

# ---------------------------------------------------------------
# 1. Top gap must equal the SketchyBar bar height + clearance.
#
# aerospace.toml `gaps.outer.top` and sketchybarrc `--bar height` are coupled;
# the comments in both files spell out the +5. The height= match is anchored so
# it does not pick up `background.height=`.
printf '\n== bar height <-> top gap coupling ==\n'
height="$(grep -oE '^[[:space:]]+height=[0-9]+' "$RC" | grep -oE '[0-9]+' | head -1)"
top="$(grep -E '^gaps\.outer\.top' "$TOML" | grep -oE '[0-9]+' | head -1)"
if [ -z "$height" ] || [ -z "$top" ]; then
bad "could not read height ('$height') or top gap ('$top')"
elif [ "$top" -eq "$((height + CLEARANCE))" ]; then
note "ok top=$top == height=$height + $CLEARANCE"
else
bad "top gap $top != bar height $height + $CLEARANCE (=$((height + CLEARANCE))) — windows will tuck under the bar or leave a band"
fi

# ---------------------------------------------------------------
# 2. Border settle colour must match bordersrc's resting active colour.
#
# on-focus-changed pulses to lavender then back to a mauve; that second (settle)
# colour is the one that must equal bordersrc active_color. Take the LAST
# active_color on the pulse line as the settle value.
printf '\n== border settle colour <-> bordersrc ==\n'
settle="$(grep -E 'active_color=0x' "$TOML" | grep -oE 'active_color=0x[0-9a-fA-F]+' | tail -1 | cut -d= -f2)"
resting="$(grep -oE 'active_color=0x[0-9a-fA-F]+' "$BORDERS" | head -1 | cut -d= -f2)"
if [ -z "$settle" ] || [ -z "$resting" ]; then
bad "could not read settle ('$settle') or bordersrc active ('$resting')"
elif [ "$settle" = "$resting" ]; then
note "ok settle=$settle == bordersrc active=$resting"
else
bad "on-focus-changed settles on $settle but bordersrc rests at $resting — every focus change will settle on the wrong colour"
fi

# ---------------------------------------------------------------
# 3. No stray ~/.aerospace.toml.
#
# AeroSpace reads EITHER ~/.aerospace.toml OR the XDG path; having both is a
# hard error and the app refuses to start. The repo config lives only at the
# XDG path, so the home-dir file must not exist.
printf '\n== no ambiguous ~/.aerospace.toml ==\n'
if [ -e "$HOME/.aerospace.toml" ]; then
bad "$HOME/.aerospace.toml exists — AeroSpace will error on the ambiguity with the XDG config"
else
note "ok none present"
fi

# ---------------------------------------------------------------
# 4. AeroSpace's own validator, when the app is reachable.
#
# reload-config is a client->server command: it validates the INSTALLED (stowed)
# config and needs the app running. Skip cleanly when the binary is absent (CI)
# or the server is down, so this stays a bonus rather than a false failure.
printf '\n== aerospace validator ==\n'
if ! command -v aerospace >/dev/null 2>&1; then
note "skip aerospace not installed"
elif ! aerospace list-workspaces --all >/dev/null 2>&1; then
note "skip AeroSpace app not running (validator needs the server)"
elif aerospace reload-config --dry-run --warnings-as-errors >/dev/null 2>&1; then
note "ok reload-config --dry-run --warnings-as-errors"
else
bad "aerospace reload-config --dry-run --warnings-as-errors reported problems"
fi

printf '\n'
if [ "$fail" -eq 0 ]; then printf 'PASS — no silent-failure patterns found\n'
else printf 'FAIL — see above\n'; fi
exit "$fail"
124 changes: 124 additions & 0 deletions scripts/lint-sketchybar.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env bash
# Catches the SketchyBar failure classes that are INVISIBLE when they happen.
#
# Every check here exists because the failure it detects looks exactly like
# "working, with nothing to report". That is the property that makes them
# expensive: the bar comes up, the item is simply absent or stale, and nothing
# anywhere says why.
#
# Run by hand, or from CI. Exits non-zero on any failure.
#
# NOT a general shell linter — it defers to shellcheck for that, and runs it if
# present. These are the checks shellcheck cannot make because they are about
# SketchyBar's semantics, not the shell's.

set -uo pipefail
cd "$(dirname "$0")/.." || exit 1

PLUGINS="sketchybar/.config/sketchybar/plugins"
RC="sketchybar/.config/sketchybar/sketchybarrc"
fail=0

note() { printf ' %s\n' "$*"; }
bad() { printf 'FAIL %s\n' "$*"; fail=1; }

# ---------------------------------------------------------------
# 1. Every plugin must be executable.
#
# SketchyBar fork_execs its plugins. When the bit is missing it reports NOTHING
# AT ALL — the item just never updates, which is indistinguishable from a script
# that runs and decides to do nothing. Editors and `git apply` both create files
# without it.
printf '\n== executable bits ==\n'
for f in "$PLUGINS"/*.sh; do
if [ -x "$f" ]; then note "ok $(basename "$f")"
else bad "$(basename "$f") is not executable — sketchybar will never run it"; fi
done

# ---------------------------------------------------------------
# 2. Any item whose plugin can hide it must declare updates=on.
#
# Under the config-wide `updates=when_shown` default a hidden item stops being
# updated ENTIRELY: bar_item_update() gates timed and event runs behind
# `updates_only_when_shown ? is_shown : true`, and bar_draw() clears the bar
# association of anything it does not draw. So the item works right after a
# reload, hides itself when there is nothing to show, and never runs again.
# Eight items were found in that state at once.
printf '\n== self-hiding items declare updates=on ==\n'

# ITEM-level drawing=off only. `label.drawing=off`, `background.drawing=off` and
# `popup.drawing=off` hide a COMPONENT and leave the item drawn and updating —
# amphetamine and pomodoro both dim their label this way and are correctly on the
# when_shown default. Requiring a preceding space excludes the dotted forms.
# Calling hide() or require() counts too: both end in an item-level drawing=off
# inside lib.sh.
can_hide() {
grep -qE '(^|[[:space:]])drawing=off' "$1" && return 0
grep -qE '^[[:space:]]*(hide|require)([[:space:]]|$)|\|\|[[:space:]]*hide|&&[[:space:]]*hide' "$1"
}

# Which items a plugin writes. Usually itself, but system.sh writes three.
items_for() {
case "$(basename "$1" .sh)" in
system) printf 'cpu memory thermals' ;;
workspaces) printf '' ;; # drives space.N; none of them ever hide
*) basename "$1" .sh ;;
esac
}

for f in "$PLUGINS"/*.sh; do
can_hide "$f" || continue
for item in $(items_for "$f"); do
block="$(awk -v it="$item" '
$0 ~ ("--add item[[:space:]]+" it "[[:space:]]") {found=1}
found {print}
found && /script=/ {exit}' "$RC")"
# PASSIVE ITEMS ARE NOT AT RISK. cpu and memory carry no script of their
# own — they are written by thermals' script, which has updates=on and
# therefore un-hides them on its next run. Only an item that must run its
# OWN script to recover can be trapped by when_shown.
if [ -z "$block" ]; then
bad "item '$item' hides itself but has no --add item block in sketchybarrc"
elif ! printf '%s' "$block" | grep -q 'script='; then
note "skip $item (passive — driven by another item's script)"
elif printf '%s' "$block" | grep -q 'updates=on'; then
note "ok $item"
else
bad "item '$item' can set drawing=off but does not declare updates=on"
fi
done
done

# ---------------------------------------------------------------
# 3. Cross-references must resolve.
#
# The comments in this repo carry the measurements, so a comment that points at
# a section which no longer exists is a defect, not untidiness. Three were live
# at once: a deleted WIDTH BUDGET section, a privacy note that was never written,
# and a claim that an installed app was not installed.
printf '\n== cross-references resolve ==\n'
while IFS= read -r ref; do
[ -z "$ref" ] && continue
target="${ref#see the }"; target="${target% *}"
if grep -qF "$target" "$RC" "$PLUGINS"/*.sh 2>/dev/null; then
note "ok -> $target"
else
bad "dangling reference: '$ref' has no target"
fi
done < <(grep -ohE 'see the [A-Z][A-Z ]{3,}' "$RC" "$PLUGINS"/*.sh 2>/dev/null | sort -u)

# ---------------------------------------------------------------
# 4. shellcheck, if available.
printf '\n== shellcheck ==\n'
if command -v shellcheck >/dev/null 2>&1; then
shellcheck -S warning -e SC1091 "$PLUGINS"/*.sh \
sketchybar/.config/sketchybar/lib.sh || fail=1
note "shellcheck done"
else
note "skip shellcheck not installed (brew install shellcheck)"
fi

printf '\n'
if [ "$fail" -eq 0 ]; then printf 'PASS — no silent-failure patterns found\n'
else printf 'FAIL — see above\n'; fi
exit "$fail"
11 changes: 0 additions & 11 deletions sketchybar/.config/sketchybar/colors.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,5 @@ export LAVENDER=0xffbabbf1

export TRANSPARENT=0x00000000

# Island backgrounds. The bar itself is fully transparent (color=0x0 in
# sketchybarrc) — these pills are the only thing drawn, so their alpha byte is
# the single knob for how see-through the whole bar looks.
# 0xe6 90% 0xcc 80% 0xb3 70% (current) 0x99 60% 0x80 50%
# Below about 60% the Catppuccin text starts losing contrast on a busy wallpaper.
# There is no blur to fall back on: blur_radius is a BAR-level property and there
# is no background.blur_radius, so enabling it would frost the entire bar
# rectangle including the gaps between islands — a blurred band across the whole
# screen top, which is exactly the effect the islands exist to avoid.
export ISLAND=0xb3232634
# Kept fully opaque on purpose: as the fill gets more transparent the border is
# what keeps the pill's edge crisp and its shape readable against the desktop.
export ISLAND_BORDER=0xff414559
Loading
Loading