Skip to content

Development

Baidak.D edited this page Sep 15, 2026 · 8 revisions

English · Русский

Hacking on familiar

The Homebrew build is for everyday use. To work on familiar, clone the repo and point your config at the checkout, then restore the released build when you're done — all on your working ~/.config/kitty:

brew tap denoby/familiar https://github.com/DenoBY/familiar
brew install denoby/familiar/familiar
familiar enable --all          # everyday use — the released build

git clone https://github.com/DenoBY/familiar && cd familiar
./bin/familiar enable --all    # switch your live config to this checkout
# edit plugins/**, reload kitty (Cmd+Ctrl+,) to see your changes

familiar enable --all          # switch back to the Homebrew build

Both the Homebrew familiar and the repo ./bin/familiar write the same ~/.config/kitty/familiar.conf, so switching is just re-running the other one — no duplication, nothing to clean up. familiar bakes absolute paths from wherever it runs: the brew build points at /opt/homebrew/opt/familiar/libexec, the checkout at your clone. familiar status prints wired root: — the installation kitty actually runs — next to the app root: of the copy you invoked, and warns when the two differ; familiar disable removes it entirely.

Tests

Standard-library unittest, no external dependencies, run outside kitty:

python3 -m unittest discover -s tests -t tests

What's covered is in tests/README.md in the repo. Style is checked with ruff check . (configured in pyproject.toml).

The app icon

config/icon/familiar.svg is the source; kitty.app.icns (what familiar enable copies into the kitty config directory) and familiar.png (the logo in the README) are built from it:

brew install librsvg          # provides rsvg-convert; iconutil ships with macOS
python3 tools/build_icon.py

The icns is not the plain artwork: macOS icons draw their squircle in 824 px of a 1024 px canvas, and an icon filling the whole canvas looks bigger than its neighbours in the Dock. build_icon.py wraps the art in those margins (the measured result matches the system icons: content 0.805 of the canvas, 25 px margins at 256×256); the README png keeps the full canvas.

disable recognises its own icon by comparing the bytes with the source, so regenerate and commit both files together — an .icns that no longer matches what was installed is left in place as "the user's own".

The demo stand

Every screenshot in this wiki is taken on a reproducible demo stand — a generated git repo with photogenic history and uncommitted changes (for review and log), a folder of three independent repositories (for their multi-repository mode) plus a fake Claude Code storage with live sessions and a rich transcript (for session). Regenerate it any time the UI changes and retake the shots:

python3 tools/demo_stand.py --dir /tmp/familiar-demo    # --wipe to recreate

Then launch a dedicated kitty instance pointed at the fake storage:

FAMILIAR_THEME=ghostty CLAUDE_CONFIG_DIR=/tmp/familiar-demo/claude \
  /Applications/kitty.app/Contents/MacOS/kitty --detach \
  --config <checkout>/config/kitty.conf --directory /tmp/familiar-demo/repo

(The environment variables must reach the kitty process — the kittens inherit them from there, so launching from a shell is the simplest way. --config keeps the instance off your live ~/.config/kitty: shots are taken in the default ghostty theme, and a personal env FAMILIAR_THEME=darcula baked into familiar.conf would otherwise repaint every frame.)

All wiki frames are shot at a window of 150×45 cells (the gif is stitched from equally sized ones). The darcula frame is the exception: a second instance with FAMILIAR_THEME=darcula and one more --config <checkout>/config/look/darcula.conf after the first.

Inside that window:

  • Cmd+Shift+S — session: three projects; the repo project has a busy and a waiting live session, a ◆ background agent, a custom-titled session, and a rich transcript ("Fix sunset rounding bug") for the preview.
  • Cmd+Shift+R — review: the tree shows every status (staged utils.py, modified core.py / test_core.py, renamed app.py, untracked notify.py); the core.py diff has word-diff lines (int(raw_minutes) → round(raw_minutes)) and symbols for go-to-definition (normalize_degrees, minutes_to_time live in utils.py).
  • Cmd+Shift+L — log: ~18 commits, a --no-ff merge of feature/moon-phase, and an unmerged feature/timezones (press a for all branches, i for the details panel).

Clone this wiki locally