Guide
Build an fzf Project Switcher (and When to Use son Instead)
fzf is one of the most powerful tools in a developer's terminal toolkit. In this guide, we will build a project picker from scratch, progressively adding features until we hit the limits of what a shell script can comfortably handle.
What Is fzf?
fzf (short for fuzzy finder) is a general-purpose command-line fuzzy finder written in Go. You pipe a list of items into it, and it gives you an interactive search interface where you can type fragments of what you are looking for and see matches instantly.
fzf is a building block, not a solution. It knows nothing about projects, git repos, or terminal workspaces. That is your job. Let us build something with it.
Version 1: Basic Project Picker (5 Lines)
The simplest fzf project picker finds all git repos under a directory and lets you pick one:
This works. It is simple and fast enough for small project collections. But it has problems: it rescans the filesystem every time, the results are unsorted (alphabetical at best), and all you get is a cd into the directory.
Version 2: Adding Git Timestamps (15 Lines)
Let us make the list more useful by showing the last commit date, so you can see which projects are active:
Better. Active projects now appear at the top. But there is a noticeable lag: the script runs git log inside every repo sequentially. With 50 repos, you might wait 2-3 seconds. With 200 repos, it could take 10+ seconds. You can optimize this with parallel execution, but the script is getting complex.
Version 3: Adding Frecency (40+ Lines)
Git timestamps tell you when code was last committed, but not when you last worked on a project. You might access a repo for code review without committing. To track actual usage, you need a frequency/recency database:
Now we have a proper frecency-sorted project picker. Projects you use frequently and recently appear at the top. It works. But look at how much code this is, and it is still just a directory jumper.
Where the DIY Script Hits Its Limits
Even with Version 3, there are several things your script cannot do without becoming a full application:
- 1.No workspace launch. The script gets you into a directory. It does not open your editor, start dev servers, or split your terminal into panes. Adding that means shelling out to tmux, iTerm2 AppleScript, or WezTerm CLI -- each with its own API.
- 2.No multi-terminal support. Writing code that works in tmux, iTerm2, and WezTerm requires handling three different APIs. That is a lot of bash.
- 3.No per-project config. Different projects need different layouts. Your API needs a 3-pane setup; your blog needs a single pane. Encoding this in bash gets ugly fast.
- 4.Caching is manual. The find+git scan is slow. You could cache results to a file, but then you need cache invalidation logic, background refresh, etc.
- 5.Maintenance burden. Shell scripts are fragile. Edge cases accumulate: repos with spaces in paths, repos without commits, submodules, nested git directories. Each is a bug waiting to happen.
At some point, you cross the line from "handy shell function" to "application I need to maintain." That is the point where a dedicated tool pays for itself.
When to Use son Instead
son is essentially what Version 3 of the script above would become if you kept adding features and rewrote it in a compiled language. It handles all the things the DIY script cannot:
| Feature | DIY fzf Script | son |
|---|---|---|
| Fuzzy project search | Yes | Yes |
| Frecency sorting | 40+ lines of awk | Built-in |
| Cached project index | DIY or slow | Automatic |
| Split pane workspace | No | Yes |
| Multi-terminal (tmux/iTerm/WezTerm) | Pick one or write 3x code | All supported |
| Per-project config | No | .son.toml |
| Editor integration | Manual | Built-in |
| Startup hooks | No | Yes |
| Single binary, no deps | Needs bash + fzf + git | Yes (fzf included) |
That said, keep the DIY script if:
- +You have fewer than 10 projects and do not need workspace automation.
- +You enjoy customizing shell scripts and want full control.
- +You only need to
cdinto a directory and nothing else.
Getting Started with son
If you are ready to move beyond the script, here is the setup:
No config files. No database setup. No dependencies to install. It discovers your projects automatically from common dev directories, and the frecency ranking starts learning your patterns from the first use.
For more on workspace layouts, see the tmux split panes guide. For tips on managing large numbers of repos, see managing multiple git repos.
From 40 lines of bash to one binary.
Keep your fzf skills. Lose the maintenance burden.