Yojam: a macOS default-browser shim that routes URLs through a rule engine
59 points by fluffypony
59 points by fluffypony
Author here. Yojam replaces the default browser on macOS so every URL - clicks, Finder .webloc/.inetloc/.url files, Handoff, AirDrop, Share and Services menus, Safari/Chromium/Firefox extensions, and a yojam:// scheme - runs through one pipeline: global regex rewrites, tracking-parameter scrubbing, rule matching, per-browser rewrites, then open-or-pick.
Technically interesting bits if you want to pick them apart:
Rules can target specific browser profiles, not just bundle IDs. For Chromium this means shelling out with --profile-directory; for Firefox, -P with the profile name; for Safari/Orion, AppleScript because there's no profile CLI (because of course there isn't).
Source-app filtering uses real originating bundle IDs where macOS actually gives them to us, and synthetic sentinels (com.yojam.source.handoff and friends) where it doesn't. Rules can key on either.
The Share Extension, Safari Web Extension, and native messaging host are separate Xcode targets sharing an App Group container, which is why swift build alone can't produce a functioning bundle - the default-browser role requires a proper Info.plist with URL scheme registration. Don't ask me how long that took to figure out.
Private/incognito is per-target: Chromium --incognito, Firefox --private-window, Safari/Orion via AppleScript.
No telemetry, no network I/O beyond optional iCloud KV sync and Sparkle. Your URLs are your business.
Source layout, rule semantics, and yojam:// scheme parameters are all in the README. Feedback on the rule model and the native-messaging install/repair flow especially welcome - those two areas took the most iteration and I'm least confident they're right yet.
Oh, finally plumber (9) on Mac!
This functionality does feel like a gap in the OS itself.
Ha, yes - plumber is exactly the mental model, and it's wild that nothing in macOS itself fills that gap. LaunchServices gives you one default handler per scheme and that's it; no routing, no context, no source-app awareness. Everything interesting has to live in a shim that registers as the default browser and then dispatches from there.
The nice side-effect of building it that way is that every ingress path (clicks, Finder, Handoff, AirDrop, Share, Services, extensions, yojam://) funnels through the same pipeline, so rules and tracker scrubbing apply uniformly. The less-nice side is that private-window support for Safari and Orion has to go through AppleScript because there is no CLI flag for it, which is about as plumber-esque as it gets.
What is the number between brackets?
Unix / Linux / BSD manual section number. Man pages are grouped into numbered sections by category, and you cite a page as name(section) to disambiguate - so plumber(9) means "the plumber page in section 9". Run man 9 plumber to read it (on a Plan 9 system, macOS doesn't ship it so there is no man entry for plumber).
Classic Unix sections are 1 (user commands), 2 (syscalls), 3 (library functions), 5 (file formats), 7 (miscellaneous/conventions), 8 (sysadmin). Plan 9 reshuffled the numbering a bit, which is why plumber lives at 9 there and why the plumb rules file the other commenter linked is plumb(7).
It reminded me of plumb (see the example): https://9fans.github.io/plan9port/man/man7/plumb.html
Yup, plumb(7) really is the canonical reference for this pattern. The rules file syntax (match on source, type, data; apply actions) maps almost one-to-one onto what Yojam ended up doing, just with a GUI bolted on and macOS ingress paths instead of Plan 9 ones. Source-app matching in particular is basically the src field wearing a different hat.
If I had been braver I would have shipped a plaintext rules file as the source of truth and treated the UI as a view over it. Maybe in a future version; the App Group storage is JSON under the hood, so it is not a huge leap.
Neat. Very similar to choosy.app
A basic version can also be created with hammerspoon.
Indeed! Choosy is the closest neighbour and it is a solid app - I used it for a bit (although Bumpr was my go-to more recently). All of these are similar in that they intercept the default-browser role and route based on rules.
Where Yojam goes further are a few things, eg. browser profiles as first-class rule targets (not just bundle IDs), source-app matching with synthetic sentinels for Handoff/AirDrop/Share/Services/each extension, tracker parameter scrubbing before the target browser ever sees the URL, global regex URL rewrites, custom CLI args with a $URL placeholder for non-browser targets (or if you want to use Lynx / Links etc), and a yojam:// scheme for Shortcuts/Raycast/Alfred automation. The URL tester on the Pipeline tab is also something I missed in Choosy - paste a URL and watch exactly which rewrites fire, what gets stripped, and which rule wins.
If any finicky users are able to offer a comparison I'd be interested to hear your thoughts.
I've been thinking about my need of something like this on Android.
Happy to see this explored.
I had a request for a windows version on X - I’ll do a little digging and see how feasible it is on Android too!
Rules can target specific browser profiles, not just bundle IDs.
I'm curious, can it target Firefox container tabs? If not directly I think https://addons.mozilla.org/en-US/firefox/addon/open-url-in-container/ might be able to do the trick.
I'm not sure if it can intrinsically (will research), but Yojam has a FF addon (rough and a bit untested atm) for opening a link in FF via Yojam - I could extend the addon to handle that similarly to that addon. Would you mind opening a GitHub issue for this so I can keep track?
1.7k followers on Github profile but dozens of stars on repos, wut?
Most of my followers are because I was the lead maintainer on Monero from the outset till 2019 when I stepped back. I remained a member of the Monero Core Team for a long time after that.
I have posted about some of my newer tools on X but I'm not sure AI tools and a URL click handler appeal much to my GitHub followers:-P
interesting idea. I may just borrow that concept for my particular system.
Would love to make a version for *nix, and happy to target your district as a first class citizen if it’s useful!
an unreleased Linux distro that I am working on, so there are some opportunities to do things different. This concept seems interesting to build into the OS itself. Still early days though, so I wont bother you with this, for now at least :-)
I've been using Choosy for a long time but will give Yojam a try! I daily drive Safari, but Choosy isn't able to target profiles which I use to keep a vague separation between church and state.
In the OP you mention that you support profiles in Safari/Orian via AppleScript, but I didn't see anything in the UI for Safari, nor is there any mention of Safari profiles on the homepage. Is this upcoming functionality or something I can get from a prerelease build or build from source?
Sorry that's bad writing on my part - the AppleScript path for Safari and Orion is for private/incognito windows, not profile targeting - I shouldn't have clumped that all together and missed noting that. Safari profiles don't expose any scripting or CLI surface for "open this URL in profile X" - AppleScript can't do it, open -a Safari --args can't do it, and there's no URL scheme variant for it. As far as I can tell Apple simply hasn't shipped the hook.
So it isn't upcoming in a prerelease or hiding behind a build flag; it's blocked on Apple. The moment they ship a scripting dictionary entry or a CLI flag for profile selection, I'll wire it up the same day - the rule model already supports profile targets, it's purely an ingress problem on Safari's side.
afaik Orion does support profiles, but I only switched to it as my daily a couple of weeks ago so haven't had a chance to even try that and properly bake it in. For anything weird like that, you can always add it as a custom app, and set the launch args with $URL for the url.
If you find a trick I've missed, please shout - I would genuinely love to be wrong about this!
That makes sense, I tried to work around the Safari limitations you mentioned and always came up short. Right now on my work Mac I'm using Safari for work and Orion for personal as a workaround.
Elsewhere on this thread we're discussing using the Firefox addon to create a custom URL scheme that lets Yojam handle opening in a specific container...I'll do a little research and see if there is a way I can do it via a Safari extension. Knowing my luck, I'll come up with some insane hacky thing that works, and then Apple will ship a CLI arg for it in macOS 26.5 or something:-P
One thing I'd like – though it may not be possible – is to be able to assign domains to specific displays.
I'm working somewhere that uses Google Meet, so all my meetings are in-browser. I do most of my browsing on Display 2 (my big external monitor), but meetings should be on Display 1 (the built-in Macbook screen) so it looks like I'm paying attention to everyone. I wish I could have a rule like "If there's already browser window on Display 1, open a new tab; if not, open a new window on that display".
Someone tried to hide their AGENTS.MD here… (See .gitignore)
Using LLMs is unethical and trying to hide LLM usage even more so. If you use them, please at least be transparent about it so I can avoid your software.
I absolutely do use AI coding assistants to a greater or lesser degree, but there is no AGENTS.md in the source tree and I’m not trying to hide anything
The reason I’ve started putting that in there is because I’ve had to deal with a great many PRs that include the submitter’s AGENTS.md - it’s beyond frustrating. I have no problem with AI assisted PRs, I just don’t see why I also need to read the garbage analysis your agent wrote about the source code or your personal preferences.
I encourage everyone to start doing the same.
It's right there in the commit history: https://github.com/fluffypony/yojam/blob/709d6ba1c3a8d957d52f04335ab953c422561e8d/AGENTS.md
I'd like to especially highlight this line:
Always invoke the humanizer skill for every piece of user-facing text.
You’re misunderstanding what I said. The very first line in my reply was “I absolutely do use AI coding assistants to a greater or lesser degree”. I nuked the AGENTS.md AND added it to gitignore not because I don’t use AI coding tools, but because (1) they keep getting included in PRs on various projects I work on; (2) where I do use AI coding agents my workflow uses mcp-code-indexer and not AGENTS.md. My previous reply stands - I’m sorry if you misunderstood it to mean I don’t use AI or something along those lines.
The "humanizer" skill is not very good, more aggravating than anything.
Sure but writing docs (even a reasme) is the bane of my existence. If it was just something for me to use I wouldn’t bother, but I want others to benefit from this, which necessitates some UI/UX focus. I don’t use the stock skill, I have a modified version (happy to share) and then I still go through the textual changes and manually tweak bits (eg. it still sometimes shoves emdashes in even when I explicitly tell it not to)
I had implemented something similar to use on my work laptop to keep personal and work related browsing segregated across browsers.
Currently using Better Touch Tool as a replacement with more keyboard shortcuts.
I had a similar driving force - personal gmail logged in in Safari, and work G Workspace in Chrome (plus all the oauth stuff that stems from that).