{"id":51532523,"url":"https://github.com/expelledboy/finch","last_synced_at":"2026-07-09T04:31:01.052Z","repository":{"id":355660902,"uuid":"1229031528","full_name":"expelledboy/finch","owner":"expelledboy","description":"Tiny, fast macOS browser router. ~700 LOC, ~5µs hot path, native JavaScriptCore.","archived":false,"fork":false,"pushed_at":"2026-05-04T17:36:55.000Z","size":35,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-04T18:37:18.358Z","etag":null,"topics":["browser","browser-picker","browser-router","default-browser","finicky-alternative","javascriptcore","macos","macos-app","swift","url-handler"],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/expelledboy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-04T16:20:07.000Z","updated_at":"2026-05-04T17:36:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/expelledboy/finch","commit_stats":null,"previous_names":["expelledboy/finch"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/expelledboy/finch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/expelledboy%2Ffinch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/expelledboy%2Ffinch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/expelledboy%2Ffinch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/expelledboy%2Ffinch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/expelledboy","download_url":"https://codeload.github.com/expelledboy/finch/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/expelledboy%2Ffinch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35287396,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-09T02:00:07.329Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["browser","browser-picker","browser-router","default-browser","finicky-alternative","javascriptcore","macos","macos-app","swift","url-handler"],"created_at":"2026-07-09T04:31:00.298Z","updated_at":"2026-07-09T04:31:01.038Z","avatar_url":"https://github.com/expelledboy.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Finch\n\nA tiny, fast macOS browser router. Set it as your default browser; it routes\neach URL to the right browser based on rules in `~/.finch.js`.\n\n- **~700 LOC** Swift + a 1.5KB embedded JS URL polyfill\n- **~5µs** hot-path latency on the common case (well under perceptible)\n- Native `JavaScriptCore`, no bundler, no transpiler, no Electron\n- Config is real JavaScript — simple cases look like data, full power available\n\n## Why\n\nExisting macOS browser pickers either lack power (Browserosaurus, Bumpr) or\ncarry significant complexity (Finicky ships ~2,900 LOC plus a Babel/esbuild/goja\nconfig pipeline). Finch keeps the powerful parts of Finicky's design (graduated\nmatch types, opener context, URL rewriting) but compiles config helpers to\nnative Swift code so the hot path stays in Swift, not JS.\n\n## How Finch compares\n\n|                          | **Finch**     | Finicky    | Velja  | Choosy | Browserosaurus |\n|--------------------------|---------------|------------|--------|--------|----------------|\n| Source LOC               | **~700**      | ~2,900     | closed | closed | ~4,200         |\n| Hot-path latency         | **~5 µs**     | unknown    | —      | —      | —              |\n| Memory footprint         | ~17 MB        | ~30 MB     | —      | —      | ~150 MB        |\n| Programmable rules       | ✅            | ✅         | 🚧 URL only | ❌     | ❌             |\n| Modern ES2020+ in config | ✅            | ❌ (ES5.1) | —      | —      | —              |\n| Caller app routing       | ✅            | ✅         | ✅     | ✅     | ❌             |\n| URL rewriting            | ✅            | ✅         | ✅     | ❌     | ❌             |\n| Tracking-strip helper    | ✅            | 🚧 manual  | ✅     | ❌     | ❌             |\n| Bundler / transpiler     | none          | Babel + esbuild + goja | — | — | Vite + Forge |\n| Open source              | ✅ MIT        | ✅ MIT     | ❌     | ❌     | ✅ (archived)  |\n| Free                     | ✅            | ✅         | $8     | $10    | ✅             |\n| Actively maintained      | ✅            | ✅         | ✅     | ✅     | ❌ (Aug 2025)  |\n\nFull feature matrix across all 18 alternatives we evaluated:\n[COMPARISON.md](COMPARISON.md). Finch's own implementation status and roadmap:\n[FEATURES.md](FEATURES.md).\n\n## Install\n\nRequires macOS 13+ and Xcode Command Line Tools (`xcode-select --install`).\n\n**Via Homebrew** (recommended):\n\n```sh\nbrew install --cask expelledboy/finch/finch\n```\n\nThe cask builds from source on your machine — no Developer ID needed, no\nGatekeeper warnings. See [docs/homebrew.md](docs/homebrew.md) for how the\ndistribution works and the release process.\n\n**From source:**\n\n```sh\ngit clone https://github.com/expelledboy/finch\ncd finch\nmake run\n```\n\nThen launch Finch (🐦 in your menu bar), open **System Settings → Desktop \u0026\nDock → Default web browser** and select Finch. Edit `~/.finch.js` to define\nyour rules — see [`examples/finch.example.js`](examples/finch.example.js).\n\nFinch handles `http`, `https`, and `mailto` URLs only — it deliberately does\nnot register as a viewer for local HTML files. `open foo.html` is a separate\nLaunchServices binding (`public.html`) that routes to whatever app you've\nchosen via Finder → Get Info → \"Open with… → Change All\", or programmatically\nwith [`duti`](https://github.com/moretension/duti):\n\n```sh\nbrew install duti\nduti -s app.zen-browser.zen public.html  all   # replace with your bundle id\nduti -s app.zen-browser.zen public.xhtml all\n```\n\n## Configuration\n\nDrop a JavaScript file at `~/.finch.js`. See [`examples/finch.example.js`](examples/finch.example.js).\n\n```js\nmodule.exports = {\n  default: \"zen\",\n\n  browsers: {\n    zen:    \"app.zen-browser.zen\",\n    prisma: \"com.talon-sec.Work\",\n    chrome: { id: \"com.google.Chrome\", args: [\"--profile-directory=Work\"] },\n  },\n\n  // All matching rewrites apply, in order\n  rewrite: [\n    strip(\"utm_*\", \"fbclid\", \"gclid\"),\n  ],\n\n  // First matching rule wins\n  rules: [\n    { match: domain(\"paymentology.atlassian.net\", \"datadoghq.com\", \"zoom.us\"),\n      open: \"prisma\" },\n\n    { match: /github\\.com\\/(paymentology|tutuka)\\//, open: \"prisma\" },\n\n    // Full power: any predicate function works\n    { match: (url, ctx) =\u003e ctx.modifiers.option, open: \"zen\" },\n  ],\n};\n```\n\n### Match types\n\n| Syntax | Matches | Notes |\n|---|---|---|\n| `\"example.com\"` | hostname, exactly or as subdomain | Most common — bare strings are hostname patterns, not full URLs |\n| `domain(\"a.com\", \"b.com\")` | any of these hostnames | Compiled to a single fast check |\n| `from(\"com.tinyspeck.slackmacgap\")` | URL was opened by this app | Caller bundle ID |\n| `running(\"us.zoom.xos\")` | this app is currently running | |\n| `/regex/` | regex against full URL | For path-specific rules |\n| `(url, ctx) =\u003e bool` | anything | Slow path (~5µs extra), full power |\n\nThe `ctx` argument has `ctx.url`, `ctx.opener.bundleId`, and `ctx.modifiers.{shift,option,command,control}`.\n\n### Browser targets\n\n| Syntax | Means |\n|---|---|\n| `\"zen\"` | Look up `zen` in `browsers` dict |\n| `\"app.zen-browser.zen\"` | Direct bundle ID |\n| `{ id: \"com.google.Chrome\", args: [\"--incognito\"] }` | Bundle ID with launch args |\n| `(url, ctx) =\u003e \"zen\"` | Dynamic target |\n| `null` | Suppress — do nothing |\n\n### Rewrite rules\n\n| Syntax | Effect |\n|---|---|\n| `strip(\"utm_*\", \"fbclid\")` | Remove these query params (supports `*` suffix) |\n| `{ match: ..., url: \"...\" }` | Replace URL when match hits |\n| `{ match: ..., url: (u, ctx) =\u003e ... }` | Transform URL via JS |\n\nA `URL` constructor is available inside predicates and rewrites for parsing.\n\n## Commands\n\n```sh\nmake build                          # build Finch.app\nmake run                            # build + register + launch\nmake test URL=\"https://...\"         # dry-run a URL through the rules\nmake clean\n```\n\nTo reload config after editing `~/.finch.js`:\n\n```sh\nkill -HUP $(pgrep -f Finch.app/Contents/MacOS/Finch)\n```\n\nOr use the menu bar icon → Reload Config.\n\nThe binary also has `--bench N \u003curl\u003e` for in-process resolve benchmarking.\n\n## Performance\n\nMeasured on Apple Silicon, macOS 15, release build, 100k iterations.\n\n| Path | Latency |\n|---|---|\n| Default fallback (no rule match) | 5.2µs |\n| `domain()` match | 5.4µs |\n| Subdomain match | 4.9µs |\n| Regex match | 4.5µs |\n| Tracking strip + match | 8.7µs |\n| User function predicate | ~10µs |\n\nFor comparison: macOS dispatching the Apple Event from the originating app to\nFinch takes ~1–5ms. Finch's contribution to click-to-browser latency is in the\nnoise.\n\nThe trick: `domain()`, `from()`, `strip()` etc. return marker objects like\n`{__type: \"domain\", hosts: [...]}` that Swift recognizes at config load and\ncompiles to native `NSRegularExpression` / `Set\u003cString\u003e`. The Swift→JS bridge\nis only crossed for user-written `(url, ctx) =\u003e ...` predicates.\n\n## Architecture\n\n| File | LOC | Responsibility |\n|---|---|---|\n| `Sources/Finch/main.swift` | 6 | Bootstrap |\n| `Sources/Finch/AppDelegate.swift` | 112 | Apple Event handler, hot path entry, `NSWorkspace.open` |\n| `Sources/Finch/Loader.swift` | 46 | Read `~/.finch.js`, evaluate via JSC, return module.exports |\n| `Sources/Finch/Helpers.swift` | 101 | Embedded JS prelude: URL polyfill + `domain`/`from`/`strip` helpers |\n| `Sources/Finch/Engine.swift` | 442 | Marker compilation + native hot-path resolver |\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexpelledboy%2Ffinch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fexpelledboy%2Ffinch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexpelledboy%2Ffinch/lists"}