Skip to content

pkajaba/junction

Repository files navigation

Junction

A native, open-source browser router for macOS. Click a link anywhere — Junction decides which browser (and which profile) opens it, from rules you define. No rule? A fast keyboard-driven picker lets you choose.

Status: feature-complete, in dogfooding. Not yet signed/notarized, so there's no one-click installer — build from source for now (it's quick). A signed Homebrew cask is the last milestone before v0.1.

The Junction picker


What it does

You probably use more than one browser — or more than one Chrome profile (work vs. personal). macOS only lets you pick one default browser, so every link is a small papercut: wrong browser, wrong profile, drag the URL across, sign in again.

Junction sits in that gap. It registers as your default browser, then for every link:

  1. Rules match first. *.zoom.us → Zoom app. github.com → work Chrome. news.ycombinator.com → Safari. Silent, instant.
  2. No match? The picker appears — a keyboard-driven panel. Pick a browser, or pin it to create a rule on the spot.
  3. Native apps win where it matters. A Zoom link can open the Zoom app directly, skipping the browser entirely.

Why another one of these?

The browser-router niche exists, but every option has a catch in 2026:

App The catch
Browserosaurus Discontinued. Picker only, no rules.
Velja Closed-source, paid.
Finicky Open-source, but JS-config only — no GUI, no picker.
Choosy Closed-source, paid.
Browserino Open-source and native — closest sibling. No URL rewriting, no app-origin rules, no native-app handoff yet (see issues #13, #42, #49).

Junction is the combination that didn't exist: open source (MIT), native (Swift/SwiftUI — no Electron, ~10 MB), with both a rules engine and a picker, plus a real settings UI.

A handful of recurring Browserino feature requests are first-class in Junction by design — app-origin rules (#13), native-app handoff for Zoom/Slack/etc. (#42), URL rewriting (#49), hiding non-browser apps that register as http handlers (#36), and promoting unrecognized browsers the allowlist missed (#24). If you found your way here from one of those threads, that's why.

Features

  • Rule engine — match on host (+ subdomains), host regex, URL substring, or URL prefix (path-aware) — and optionally narrow by the app the link came from (e.g. links from Slack → work Chrome). Route to any browser + profile + launch args.
  • Keyboard-driven picker — for unmatched URLs. Number keys, arrows, Return. Pin a row to turn the choice into a saved rule.
  • Per-browser profiles — Chrome/Brave/Edge/Vivaldi/Opera (--profile-directory) and Firefox (-P). Real profile names are detected and shown in the rule editor — no guessing Default vs Profile 1.
  • Native-app handoff — Zoom, Teams, Slack, Notion, Linear, Spotify, Discord. A matching URL opens the native app instead of a browser.
  • URL rewriting — strips utm_*, fbclid, gclid, and other tracking params before routing. Pure, local, no network.
  • Visual rule editor — a two-pane Settings tab: grouped rule list + inline editor with a host-chip matcher and a live "test this URL" field.
  • Live config reloadrules.json is plain text; edit it in any editor and Junction picks it up instantly (it watches the file).
  • Native picker chrome — a system blur material (NSVisualEffectView) that the OS renders with Liquid Glass on macOS 26 and a standard vibrant material below.

Screenshots

The picker

No rule matched, so Junction asks — keyboard-first (number keys, arrows, ), or pin a row (⌥↩) to turn the choice into a saved rule.

The picker

Rules

A two-pane editor: grouped rules on the left, an inline host-chip matcher + live URL tester on the right.

Settings — Rules

Native-app handoff

Send matching links straight to a native app instead of a browser — each toggle enabled only when the app is installed.

Settings — Handoff

Advanced

Launch at login, theme, Activity-log retention (secrets are always redacted before anything is logged), and the tracking-parameter strip list.

Settings — Advanced

More — Browsers & Activity

Browsers

Toggle which browsers show in the picker; add ones Junction didn't auto-detect.

Settings — Browsers

Activity

Every link Junction has handled, with the outcome (rule match / picker / handoff) and one-click "make it a rule."

Settings — Activity

Install

No signed build yet — build from source. It takes about a minute.

Requirements

  • macOS 14 Sonoma or newer
  • Xcode 16 or newer
  • XcodeGenbrew install xcodegen

Build & run

git clone https://github.com/pkajaba/junction.git
cd junction
xcodegen generate          # writes Junction.xcodeproj from project.yml
open Junction.xcodeproj
# ⌘R in Xcode

Junction.xcodeproj is generated from project.yml and is not committed — that sidesteps the merge-conflict pain of binary Xcode projects. Re-run xcodegen generate after pulling changes to project.yml.

Set it as your default browser

System Settings → Desktop & Dock → Default web browser → Junction. (An unsigned build may not appear in that dropdown; design/set_default_browser.swift is a small helper that sets it via the same API the dropdown uses.)

How it works

Rules

Rules live in ~/Library/Application Support/Junction/rules.json — plain JSON, hand-editable, git-friendly. Junction watches the file and reloads on save. Or use Settings → Rules (⌘,): a grouped list on the left, an inline editor on the right with a visual host-chip matcher and a live URL tester.

{
  "schemaVersion": 1,
  "rules": [
    {
      "name": "GitHub → work Chrome",
      "enabled": true,
      "match": { "type": "host", "value": "github.com" },
      "target": {
        "browserBundleID": "com.google.Chrome",
        "profile": "Default"
      }
    }
  ]
}

Matchers: host (exact or subdomain — github.com matches api.github.com but not notgithub.com), hostRegex (case-insensitive regex on the host), urlContains (case-insensitive substring on the whole URL), urlPrefix (anchored, case-insensitive prefix on the whole URL — e.g. https://github.com/acme/ to route just one org's pages), and any (matches every URL, for rules that key off the source app alone). A rule can additionally require the link to come from specific apps (sourceApps, by bundle ID). Rules evaluate top to bottom; first enabled match wins.

The picker

When no rule matches, the picker appears — centered, keyboard-first:

  • 19 — open in that browser
  • — move selection · — open · esc — cancel
  • ⌥↩, or click a row's pin — open and save a rule for that host

URL rewriting

Tracking parameters (utm_*, fbclid, gclid, msclkid, …) are stripped before matching and opening, so the browser gets a clean URL. Configurable in Settings → Advanced. Pure value transform — no network, no logging.

Native-app handoff

If you have the native app, a matching link can open it directly:

Link Opens in
*.zoom.us/j/… Zoom
teams.microsoft.com/l/… Microsoft Teams
app.slack.com/client/… Slack
notion.so/… Notion
linear.app/… Linear
open.spotify.com/… Spotify
discord.com/channels/… Discord

Each is opt-in per app in Settings → Advanced (only enabled when the app is installed). Handoff takes priority over rules.

Profiles

Browser family How
Chromium (Chrome, Brave, Edge, Vivaldi, Opera, all channels) --profile-directory=<dir> — profile names read from the browser's Local State
Firefox (incl. Developer Edition, Nightly) -P <name> — read from profiles.ini
Safari / Arc Not supported — Apple/Arc expose no external profile API (#13)

Status & roadmap

Core (URL routing, rules, picker, profiles, rewriter) ✅ Done
Settings UI, native-app handoff, redesigned picker ✅ Done
Unit tests, SwiftLint, CodeQL, CI ✅ Done
Sign + notarize + Homebrew cask → v0.1 ⏳ Last step (needs an Apple Developer ID)

See SPEC.md for the architecture and CLAUDE.md for a contributor's orientation to the codebase.

Contributing

Issues and PRs welcome. The codebase is small, dependency-free, and documented in CLAUDE.md. See CONTRIBUTING.md and the issue templates. Tests run on every PR via GitHub Actions; swiftlint runs on every build.

License

MIT — same as Browserosaurus and Finicky. Fork freely.

Acknowledgements

Junction stands on the shoulders of Browserosaurus (Will Stone) and Finicky (Johannes Stenmark) — both proved this kind of tool can be loved. Browserino (Alex Strnik) is the nearest contemporary MIT-licensed native option; reading through its issue tracker shaped several of Junction's defaults. Junction's goal is to keep that experience alive, natively and openly.

About

A native, open-source browser router for macOS. Pick the right browser for every link.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages