A Windows 11 app that registers as the default web browser and routes each clicked URL to a specific Chrome profile by regex rule. One binary, three modes; a system-tray UI edits the rules. See SPEC.md for the full design.
When you click a link anywhere in Windows, the shell hands the URL to
guise.exe. It matches the URL against an ordered list of regex rules and
launches Chrome with the profile bound to the first matching rule. If
nothing matches, it launches Chrome with no --profile-directory flag, letting
Chrome do its normal thing.
chrome.exe --profile-directory="Profile 3" https://github.com/foo
| Invocation | Mode | Does |
|---|---|---|
guise.exe <url> |
ROUTE | match, launch Chrome, exit (this is what Windows runs per click) |
guise.exe --setup |
SETUP | one-command onboarding: register + start-at-login + launch tray + open Default Apps, then exit |
guise.exe --tray |
TRAY | tray icon + rule editor (autostart this at login) |
guise.exe --register |
SETUP | write HKCU registry entries so the app is an eligible browser |
guise.exe --unregister |
SETUP | remove those entries |
guise.exe --version |
— | print the embedded build version and exit (-v works too) |
Every mode writes only to HKEY_CURRENT_USER, so nothing ever needs admin
rights — no UAC, no elevation.
Requires Go 1.26+ on Windows (amd64).
go generate ./... # regenerate rsrc_windows_amd64.syso from the manifest + icon (optional; committed)
./scripts/build.ps1 # builds guise.exe with the version stamped in from gitscripts/build.ps1 is the canonical build: it derives the version from the
nearest git tag (git describe) and stamps it, the commit, and the build date
into the binary via -ldflags -X, always with -H windowsgui. A plain
go build -ldflags "-H windowsgui" -o guise.exe . also works but reports its
version as dev.
-H windowsgui is essential — without it every link click flashes a console
window. go generate needs rsrc on PATH: go install github.com/akavel/rsrc@latest.
Versions are semver and the git tag is the source of truth. To cut a release, push a tag:
git tag v1.2.3
git push origin v1.2.3The Release workflow (.github/workflows/release.yml) then builds guise.exe
with that version stamped in and publishes a GitHub Release with the binary and
its guise.exe.sha256 checksum. Tags with a hyphen (e.g. v1.2.3-rc1) are
published as pre-releases. Between tags, builds report a git describe version
like v1.2.3-5-gabc1234; with no tags yet, v0.0.0-dev+<sha>.
Check a binary's version with guise.exe --version; the tray menu's header also
shows it.
The tray keeps itself current (§14). At startup and once a day it checks the
latest GitHub release; if a
newer stable tag exists, it downloads guise.exe, verifies it against the
release's guise.exe.sha256, and reveals an Install update vX.Y.Z menu item.
Clicking it (with a confirm) swaps the binary in place — keeping the registered
path stable so the default-browser registration still points at it — and
restarts the tray.
- Toggle: Check for updates automatically (tray checkbox, on by default;
persisted as
"auto_update"inconfig.json). Check for updates now… runs on demand regardless of the toggle. - Only stable releases trigger an update — pre-release tags (
v1.2.3-rc1) are ignored. Development builds (no clean release tag) never auto-update. - The check runs only in the tray, never on the routing hot path, and fails soft: a network error or checksum mismatch is logged and the tray keeps running. The binary is replaced only when you click Install.
No installer or admin rights needed — everything writes to HKEY_CURRENT_USER.
winget install jjshanks.guiseThis is a portable install:
it drops guise.exe and puts a guise command on your PATH, but it does not
register guise as a browser. Finish setup with one command:
guise --setupThis registers guise as a browser, enables start-at-login, launches the tray, and opens the Default Apps settings page. It's idempotent, so re-running it is safe. One manual step remains — Windows 11 forbids silent default-browser changes, so in the Settings window that opens, pick Guise and choose Set default. (The tray's "Default browser: No — click to fix" item also deep-links there.)
Prefer the individual steps?
- Run
guise --register(or open the tray withguise --trayand let it register). - Run
guise --trayand toggle Start at login in the tray menu. - Set the default browser: Settings → Apps → Default apps → Guise → Set default.
winget-installed copies update through winget upgrade jjshanks.guise — the built-in
updater detects the winget install and steps aside (see SPEC §14).
irm https://raw.githubusercontent.com/jjshanks/guise/main/scripts/install.ps1 | iexIt downloads the latest guise.exe, verifies it against the release's
published SHA-256, installs it to %LOCALAPPDATA%\Programs\Guise\, and runs
guise --setup (register + start-at-login + launch tray + open Default Apps).
Overrides (set before the pipe, since iex can't take parameters):
# Pin a version instead of latest:
$env:GUISE_VERSION='v1.2.3'; irm https://raw.githubusercontent.com/jjshanks/guise/main/scripts/install.ps1 | iex
# Install elsewhere:
$env:GUISE_INSTALL_DIR='D:\Apps\Guise'; irm https://raw.githubusercontent.com/jjshanks/guise/main/scripts/install.ps1 | iexOne manual step remains afterward (Windows 11 forbids automating it): set the default browser in the Settings window that opens — Settings → Apps → Default apps → Guise → Set default (the tray's "Default browser: No — click to fix" item also deep-links there).
If you'd rather not pipe a script, grab guise.exe from the
latest release and:
- Copy
guise.exeto%LOCALAPPDATA%\Programs\Guise\. - Run
guise.exe --setuponce — it registers guise, enables start-at-login, launches the tray, and opens the Default Apps page. - Set the default browser in the window that opens: Settings → Apps → Default apps → Guise → Set default.
Or run the steps individually
- Run
guise.exe --registeronce. - Run
guise.exe --trayand toggle Start at login in the tray menu. - Set the default browser as above.
The install-script and manual paths keep themselves current via the in-app
updater (§14); only the winget path defers updates to winget upgrade.
irm https://raw.githubusercontent.com/jjshanks/guise/main/scripts/uninstall.ps1 | iexThis unregisters guise, removes autostart, and deletes the install directory.
Your rules and log under %APPDATA%\Guise are kept (the script prints how to
remove them too). Or do it by hand: guise.exe --unregister, untoggle Start
at login, then delete the install folder. (winget installs: winget uninstall jjshanks.guise, after guise --unregister.)
Rules live in %APPDATA%\Guise\config.json and are edited from the tray
("Edit rules…"). Order is evaluation order: first match wins.
{
"version": 1,
"chrome_path": "",
"rules": [
{ "id": "r1", "enabled": true, "pattern": "github\\.com/foo(/|$)", "profile_directory": "Profile 3", "comment": "GitHub foo" },
{ "id": "r2", "enabled": true, "pattern": "mail\\.google\\.com", "profile_directory": "Profile 1", "comment": "Gmail → Work" }
]
}patternis a Go RE2 regex (theregexppackage). RE2 has no backreferences — don't paste PCRE.- Matching is unanchored:
github\.com/fooalso matchesgithub.com/foobar. Anchor a boundary with^…$or(/|$). The editor's "Test URL" field exists to catch over-broad patterns before they surprise you. - Matching is case-sensitive; prefix
(?i)for case-insensitive. profile_directoryis the on-disk folder name (Default,Profile 1, …), not the friendly Chrome name. The editor's dropdown bridges the two.profile_match(optional) binds the rule to a profile by Google account instead of by directory — an accountemail("joe@acme.com") or a Workspacehosted_domain("acme.com"). It's resolved to the current directory at route time, so it survives Chrome renumbering profiles across machines. When set it overridesprofile_directory; an account that matches no current profile falls back to Chrome's default, just like a vanished directory. The editor's "Match by account" dropdown sets it. Example:{ "id": "r3", "enabled": true, "pattern": ".", "profile_match": { "email": "joe@acme.com" } }source(optional) matches the app that produced the click — a case-insensitive substring of the process image name, e.g.slackmatchesSlack.exe. A rule with bothpatternandsourceneeds both to match; asource-only rule matches any URL from that app. The lookup walks the process tree (skipping OS brokers likeexplorer.exe) and is best-effort: if the source can't be determined the rule is simply skipped and the click still routes. The editor's "from app" field next to "Test URL" previews it.chrome_pathempty = auto-detect.
%APPDATA%\Guise\guise.log records one line per click: input URL, the
rule that won (or "default"), the resolved profile, and the launch result. When
a link opens in the "wrong" profile, the log shows exactly which rule won.
main_windows.go mode dispatch (ROUTE / TRAY / SETUP)
internal/config config schema, load, atomic save (§5)
internal/router ordered RE2 matching + ROUTE-mode launch (§5.3, §12)
internal/chrome profile discovery + chrome.exe resolution (§4)
internal/source originating-app lookup: process-tree walk (§5.4)
internal/winreg HKCU registration, default detection, autostart (§3, §7)
internal/tray systray menu + GUI thread dispatch (§6.1) + update check (§14)
internal/editor walk rule editor + test dialog (§6.2)
internal/updater GitHub-release check + verified download + self-update (§14)
internal/applog log file + rotation (§9)
internal/notify Windows message-box notifications (§10)
internal/winutil shell-open helper (§3.3, §6.1)
internal/version build version stamped from git tags via -ldflags
internal/assets embedded tray icon
scripts/build.ps1 version-stamping build (used by CI + the release workflow)
go test ./...
# Registry round-trip against real HKCU (writes + cleans up; opt-in):
$env:GUISE_REGISTRY_IT=1; go test ./internal/winreg/ -run RoundTripContributions are welcome — see CONTRIBUTING.md for the build and test workflow, the platform-split convention, and the design invariants to preserve. Please also read SPEC.md, the authoritative design document.
- Code of conduct: CODE_OF_CONDUCT.md
- Reporting a vulnerability: SECURITY.md (please report privately, not via a public issue)
- Changelog: CHANGELOG.md
MIT © Joshua Shanks
