A typing coach that already knows what you're bad at. Watches how you really type, drills what actually costs you time. macOS, on-device.
Swift
0
57 commits
updated Oct 1, 2026
A typing coach that already knows what you're bad at.
Typing tutors make you grind synthetic drills for hours before they learn anything about you. Fumble watches how you type all day — in your editor, your terminal, your email — and works out which keys and which transitions actually cost you time. No cold start, and no pretending that pseudo-random letter soup resembles the things you really type.
macOS menu bar. Free. Open source. Entirely on-device.


keybr is good, and this exists because of two things it can't do:
git commit -m, const, =>, ];, snake_case identifiers.
The transitions that actually cost you time barely appear in its lessons.Fumble measures the real thing.
]; and -> cost real time while the
individual keys look fine.The headline metric is seconds lost today: how much slower than your own baseline a key is, multiplied by how often you hit it.
This matters. A key you hit 50 times at +220ms costs you 11 seconds. A key you hit 2,000 times
at +40ms costs you 80 seconds. The second one is the problem, and "slowest key" ranking buries
it. "; cost you 80 seconds today" is also a claim you can accept or reject — which a composite
score with tuned weights would not be.
Your baseline is the median of your own per-key p95 latencies. A 40 WPM typist and a 110 WPM typist have completely different distributions; against a fixed threshold the slower typist's entire keyboard reads as "weak", which is useless advice.
Per-key medians rather than all your keystrokes pooled, deliberately: pooling weights each key by how often you press it, so a frequent slow key drags the baseline toward itself and hides its own slowness. Those are exactly the keys worth fixing.
ed is slow for everyone — it's one finger doing two jobs. Fumble shows these but keeps them out
of drills. Telling you to grind a same-finger bigram is telling you to fix your hand.
Most typing trackers will happily report 400 WPM when you paste a paragraph. Fumble excludes:
osascript), remote-control software, AI tools that type into the frontmost app. Hardware
events carry kCGEventSourceStateHIDSystemState; anything posted by a process doesn't.
(Pastes and most editor/LLM completions never generate keystrokes at all, so they can't
inflate the count in the first place.)Every one of these exclusions is counted and shown in the dropdown under "What was excluded". The number should be auditable, not magic.
Fumble records which key and when — never the characters. What lands on disk is counters and latency histograms: no keystroke sequence exists to replay your text. (Per-key-pair counters do retain adjacent-pair frequencies — that residue is deliberately disclosed and bounded; see PRIVACY.md.) Nothing is recorded while a password field has focus. There is no network code, no account, no telemetry.
The full account, including the part that is a residual risk and what's done about it, is in PRIVACY.md — along with the commands to verify all of it yourself.
Requires macOS 14+.
Homebrew:
brew install --cask alexiscodingbits/fumble/fumble
Direct download: grab the DMG from the latest release and drag Fumble to Applications.
Build from source:
git clone https://github.com/alexiscodingbits/Fumble.git
cd Fumble
bash scripts/bundle-app.sh
cp -R dist/Fumble.app /Applications/
open /Applications/Fumble.app
Grant Input Monitoring when prompted (System Settings → Privacy & Security → Input Monitoring). Fumble cannot work without it, and cannot ship on the Mac App Store because of it: the store mandates sandboxing, and sandboxing blocks this API.
Release builds are Developer ID signed and notarized — the DMG opens on any Mac without Gatekeeper warnings. Self-built copies sign with whatever identity you have (see
scripts/bundle-app.sh); with none, macOS re-asks for Input Monitoring after each rebuild.
cd FumbleCore
swift run fumble-cli # today, summarised
swift run fumble-cli --all # every day on record
swift run fumble-cli --json # raw stored data, verbatim
Fumble is a coach, not just a tracker. The practice app (menu bar → Practice) has five modes:
Typing assists (stop-until-correct or advance-through), whitespace dots, cursor styles, optional key/error sounds, and a daily goal with streaks. Practice typing is excluded from your daily stats so drills can't distort the model that generates them.
useEffect or
provenmetal specifically. The plan is to build that vocabulary from your own repos and
shell history and rank it with the timing data — so the word list comes from files already
on your disk, and the keyboard tap never needs to see words at all.cd FumbleCore
swift build # must be 0 warnings
swift test # 190+ tests
Layout: FumbleCore is a pure Foundation model — no AppKit, no SwiftUI. FumbleUI is pure
presentation logic with no SwiftUI import, so the UI's content is unit-testable. FumbleApp is
the only target that touches SwiftUI/AppKit/CoreGraphics.
The virtual-keycode table follows the Carbon kVK_* constants. Thanks to
keyStats (MIT) — read as prior art while working
out how to do system-wide capture on macOS.
MIT — see LICENSE.
Swift
86.6%
HTML
9.9%
Shell
2.2%
A typing coach that already knows what you're bad at. Watches how you really type, drills what actually costs you time. macOS, on-device.
Swift
0
57 commits
updated Oct 1, 2026
A typing coach that already knows what you're bad at.
Typing tutors make you grind synthetic drills for hours before they learn anything about you. Fumble watches how you type all day — in your editor, your terminal, your email — and works out which keys and which transitions actually cost you time. No cold start, and no pretending that pseudo-random letter soup resembles the things you really type.
macOS menu bar. Free. Open source. Entirely on-device.


keybr is good, and this exists because of two things it can't do:
git commit -m, const, =>, ];, snake_case identifiers.
The transitions that actually cost you time barely appear in its lessons.Fumble measures the real thing.
]; and -> cost real time while the
individual keys look fine.The headline metric is seconds lost today: how much slower than your own baseline a key is, multiplied by how often you hit it.
This matters. A key you hit 50 times at +220ms costs you 11 seconds. A key you hit 2,000 times
at +40ms costs you 80 seconds. The second one is the problem, and "slowest key" ranking buries
it. "; cost you 80 seconds today" is also a claim you can accept or reject — which a composite
score with tuned weights would not be.
Your baseline is the median of your own per-key p95 latencies. A 40 WPM typist and a 110 WPM typist have completely different distributions; against a fixed threshold the slower typist's entire keyboard reads as "weak", which is useless advice.
Per-key medians rather than all your keystrokes pooled, deliberately: pooling weights each key by how often you press it, so a frequent slow key drags the baseline toward itself and hides its own slowness. Those are exactly the keys worth fixing.
ed is slow for everyone — it's one finger doing two jobs. Fumble shows these but keeps them out
of drills. Telling you to grind a same-finger bigram is telling you to fix your hand.
Most typing trackers will happily report 400 WPM when you paste a paragraph. Fumble excludes:
osascript), remote-control software, AI tools that type into the frontmost app. Hardware
events carry kCGEventSourceStateHIDSystemState; anything posted by a process doesn't.
(Pastes and most editor/LLM completions never generate keystrokes at all, so they can't
inflate the count in the first place.)Every one of these exclusions is counted and shown in the dropdown under "What was excluded". The number should be auditable, not magic.
Fumble records which key and when — never the characters. What lands on disk is counters and latency histograms: no keystroke sequence exists to replay your text. (Per-key-pair counters do retain adjacent-pair frequencies — that residue is deliberately disclosed and bounded; see PRIVACY.md.) Nothing is recorded while a password field has focus. There is no network code, no account, no telemetry.
The full account, including the part that is a residual risk and what's done about it, is in PRIVACY.md — along with the commands to verify all of it yourself.
Requires macOS 14+.
Homebrew:
brew install --cask alexiscodingbits/fumble/fumble
Direct download: grab the DMG from the latest release and drag Fumble to Applications.
Build from source:
git clone https://github.com/alexiscodingbits/Fumble.git
cd Fumble
bash scripts/bundle-app.sh
cp -R dist/Fumble.app /Applications/
open /Applications/Fumble.app
Grant Input Monitoring when prompted (System Settings → Privacy & Security → Input Monitoring). Fumble cannot work without it, and cannot ship on the Mac App Store because of it: the store mandates sandboxing, and sandboxing blocks this API.
Release builds are Developer ID signed and notarized — the DMG opens on any Mac without Gatekeeper warnings. Self-built copies sign with whatever identity you have (see
scripts/bundle-app.sh); with none, macOS re-asks for Input Monitoring after each rebuild.
cd FumbleCore
swift run fumble-cli # today, summarised
swift run fumble-cli --all # every day on record
swift run fumble-cli --json # raw stored data, verbatim
Fumble is a coach, not just a tracker. The practice app (menu bar → Practice) has five modes:
Typing assists (stop-until-correct or advance-through), whitespace dots, cursor styles, optional key/error sounds, and a daily goal with streaks. Practice typing is excluded from your daily stats so drills can't distort the model that generates them.
useEffect or
provenmetal specifically. The plan is to build that vocabulary from your own repos and
shell history and rank it with the timing data — so the word list comes from files already
on your disk, and the keyboard tap never needs to see words at all.cd FumbleCore
swift build # must be 0 warnings
swift test # 190+ tests
Layout: FumbleCore is a pure Foundation model — no AppKit, no SwiftUI. FumbleUI is pure
presentation logic with no SwiftUI import, so the UI's content is unit-testable. FumbleApp is
the only target that touches SwiftUI/AppKit/CoreGraphics.
The virtual-keycode table follows the Carbon kVK_* constants. Thanks to
keyStats (MIT) — read as prior art while working
out how to do system-wide capture on macOS.
MIT — see LICENSE.
Swift
86.6%
HTML
9.9%
Shell
2.2%