6.0 KiB
fx — automated, selectable fix sources for f()
fx automates the [LEARN] ... Enter a fix: prompt in f() by gathering
candidate fixes from a directory of small, pluggable scripts and letting
you pick one (or auto-picking) before it's piped into f's stdin.
Zero changes to ../../src/f.sh. f()'s read -r n already doesn't
care whether that line comes from a human or a program — fx just
decides what to type there.
Usage
. ../../src/f.sh
. fx.sh
fx some-command --that --might --fail
Set FX_AUTO=1 to skip the picker and take the top-ranked candidate
automatically. Without it, fx uses fzf if installed, or a plain
numbered prompt otherwise.
Default sources (sources.d/, run in filename order)
| File | What it offers |
|---|---|
10-known.sh |
Fixes already taught for a command that starts the same way, since f()'s own lookup only matches the exact string. |
20-history.sh |
The closest-looking command you've actually run before, read from $HISTFILE. |
30-selfdiag.sh |
Re-runs the command once, and if the tool's own error output suggests a corrected invocation (git's --set-upstream hint, etc.), emits a function that re-derives the suggestion live on every future call — not a frozen snapshot. This distinction matters: an earlier version of this source cached the literal suggested line and it silently broke on the second branch it saw (git ls-remote showed the second branch never got pushed). Re-diagnosing on every call is what makes one taught fix generalize correctly. |
40-pathfuzzy.sh |
If the first word isn't a real command, offers a function (not an alias — aliases don't expand in non-interactive shells without shopt -s expand_aliases) wrapping the closest-spelled real command, using transposition-aware edit distance so gti scores closer to git than to unrelated same-length commands like ftp. |
50-thefuck.sh |
Bridges to thefuck --yes, if installed, as one more opinion. Note --yes executes its own suggestion, so this candidate is often already-applied by the time you see it. Wrapped in timeout 5 — fuzz-testing found it hanging past 3s on a majority of unmatched inputs with no fast-fail path of its own. |
70-local-llm.sh |
Off by default (no-ops unless a local Ollama or LM Studio server responds on its usual port). The one source with real reasoning capability, for failures none of the deterministic sources above can characterize — see "Known gaps" below for a concrete real-world case. |
80-remote-api.sh |
Off by default (no-ops unless FX_REMOTE_API_URL/KEY/MODEL are all set). Same reasoning capability as 70-local-llm.sh, over a paid remote API instead — the last-resort tier, both in cost and in filename order. |
90-team-shared.sh |
Off by default (no-ops unless FX_TEAM_URL is set) — see below, this one is the extension example. |
Adding a custom source
Drop a new executable file into sources.d/. Nothing else to register.
Contract:
- invoked as
sourcefile "$CMD"where$CMDis the whole failing command line - may read
$FX_OUTPUT— captured stdout+stderr of one real attempt (empty ifFX_NO_PROBEis set) - prints zero or more candidate fix lines to stdout, one per line
- non-zero exit or no output = "no opinion," silently skipped
- filename prefix (
NN-name) sets consideration/display order — lower runs first
90-team-shared.sh is a real, working example of this contract: it pulls
a shared p-format corrections file from $FX_TEAM_URL and offers
exact-key matches. It's a template, not a dependency — treat a URL you
point it at the same way you'd treat an rc file you source: a match
becomes eval'd code the moment you select it.
Known gaps
Every deterministic/heuristic source (10-50) fixes one of two shapes:
the command name is wrong (40-pathfuzzy.sh), or the tool itself
already said what the right invocation is (30-selfdiag.sh). Neither
covers the shape where the command name and structure are entirely
correct, but one argument value is wrong for reasons that require
outside knowledge to diagnose.
Concrete, real case, not hypothetical: testing against a live third-party
system (an independently-built local service stack) from WSL,
curl http://127.0.0.1:7778/... failed — not because curl or the URL
syntax was wrong, but because WSL2's NAT networking means 127.0.0.1
from inside WSL doesn't reach the Windows host; the actual gateway IP
(cat /etc/resolv.conf's nameserver line) does. No source here fixes
this: pathfuzzy only touches the command name, selfdiag needs the
tool's own output to suggest the correction (a bare "Connection refused"
doesn't), and known/history need this exact case taught before it can
help. This is precisely what 70-local-llm.sh/80-remote-api.sh exist
for — handed the command plus $FX_OUTPUT, a model that knows WSL2's
networking quirks could plausibly reason its way to the fix. Neither tier
was configured during that test, so fx correctly stayed silent instead
of guessing.
A related, categorically different case from the same session: three of
that stack's five services turned out to be bound to 127.0.0.1 only
(reachable from the Windows host itself, confirmed working there;
unreachable from WSL at any IP, connection times out rather than
refuses). No source in this directory — including the LLM tiers — can fix
that, because the packet never reaches the process at all. That's a
server-side bind-address decision, not a client-side command to correct.
Worth naming as its own category: not every "why did this fail" question
is a fx question. Some are infrastructure decisions to leave alone
(this one was, deliberately) and document, not to work around.
Known cost
fx runs the command once itself before consulting sources (so
30-selfdiag.sh has real output to read). That's one extra execution
beyond what plain f() does. Set FX_NO_PROBE=1 to skip it for
commands with side effects you don't want repeated — the output-aware
source just has no opinion in that case, and the others still work.