Fast fuzzy switcher for Chrome and Brave: open tabs, bookmarks, recent history, and web search — all from one overlay, with Alt+1–Alt+9 quick jump for tiling-WM-style tab switching.
Replaces an earlier Raycast TypeScript extension that was bottlenecked by osascript and shell sqlite3. This version uses native chrome.* APIs, so everything stays local and instant.
Coming soon. Until then, install unpacked.
bun install
bun run buildThen in Chrome or Brave:
- Open
chrome://extensions(orbrave://extensions). - Toggle Developer mode on.
- Click Load unpacked → pick
dist.
Default hotkey: Alt+Space. Change it at chrome://extensions/shortcuts. Scope stays In Chrome — the only option MV3 exposes on macOS.
The overlay opens in jump mode. Eighteen keys jump directly to results; space enters search mode for fuzzy filtering.
| Key | Action |
|---|---|
f j d k s l a ; |
jump to result 1–8 (home row, strongest fingers first) |
1 2 3 4 5 6 7 8 9 0 |
jump to result 9–18 |
space |
enter search mode |
↵ |
open the top result |
⌘↵ |
open top result in new tab |
↑ / ↓ or ^p / ^n |
move selection |
esc |
close |
| Key | Action |
|---|---|
| type | fuzzy search across tabs + bookmarks + history |
↵ |
open selected (or open URL) |
⌘↵ |
force open in new tab |
⌥1 – ⌥9 |
jump to result 1–9 while typing |
↑ / ↓ or ^p / ^n |
move selection |
esc |
clear search, back to jump mode |
bun install # one-off
bun run dev # vite HMR for the popup UI
bun test # vitest unit + component tests
bun run test:e2e # playwright extension e2e
bun run check # biome lint + format check + tsc --noEmit
bun run build # production build to dist/Reload the extension in chrome://extensions after service-worker changes.
src/
├─ background.ts service worker: commands, open popup, message router
├─ switcher/
│ ├─ index.html
│ ├─ main.tsx Preact UI, fuse ranking, keyboard handling
│ └─ styles.css
├─ lib/
│ ├─ sources.ts getTabs, getHistory, getBookmarks
│ ├─ ranker.ts Fuse.js index + kind-aware ranking
│ └─ actions.ts activateTab, openUrl, searchSuggestion
└─ types.ts
Ranking: Fuse.js with weighted keys (title 0.6 / url 0.25 / subtitle 0.15), threshold: 0.4, ignoreLocation: true. Tabs get a zero boost, bookmarks +0.1, history +0.15 (lower is better in Fuse). Empty query falls back to tabs → bookmarks → history (recency).
Reads tabs, history, and bookmarks locally; sends nothing anywhere. Favicons are fetched from google.com/s2/favicons the same way Chromium itself does. No analytics, no telemetry. Full policy in docs/privacy.md.
See CONTRIBUTING.md. Conventional commits required; bun run check and bun test must pass.
Features intentionally left out of v1:
MV3's chrome.commands only fires when Chrome is frontmost. To trigger from anywhere:
- Option A — Swift menubar helper. ~100 LoC
NSStatusItemapp owning a global hotkey viaNSEvent.addGlobalMonitorForEvents. Launches Chrome (open -a "Google Chrome") then sends{"action":"open"}to the extension via a Native Messaging host. - Option B — macOS Shortcut. User builds a Shortcut that activates Chrome and triggers
Alt+Space. No binary to ship; less polished.
chrome.sessions.getDevices returns synced tabs from other devices signed into the same browser sync account. Adds one permission (sessions), one source file (getSessions() in sources.ts).
Currently hardcoded to Google. Add a simple options page (chrome.storage.sync.get/set) and read the value in actions.ts:searchSuggestion.
Inject an overlay into the active tab via a content script instead of chrome.windows.create. Feels more seamless but breaks on chrome://, chrome-extension://, and the new tab page.
MIT © Adam Bosnjakovic