Moving from Finicky

Grinch is inspired by Finicky and supports many Finicky v4 configs. Copy yours across, validate it, and adjust the few places listed below.

Move your config

  1. Install Grinch and add the Grinch alias.
  2. Copy your Finicky config to a location Grinch reads. If yours lives somewhere other than ~/.finicky.js, copy that file instead:
    Terminal
    mkdir -p ~/.config
    cp ~/.finicky.js ~/.config/grinch.js
  3. Run Grinch --validate. If it reports an error, check what to change.
  4. Quit Finicky, open Grinch, and choose Grinch under System Settings → Desktop & Dock → Default web browser.
  5. Check a few links with Grinch --test <url>. Bare-string matchers behave differently; see the first item under what to change.

If you use Finicky's Chrome or Firefox add-on, point it at grinch://open/….

What works as-is

  • export default { … } as well as module.exports.
  • Finicky's key names: defaultBrowser, handlers, and a rule's browser are aliases for default, rules, and open.
  • Wildcard and regex matchers, arrays of matchers, null to open nothing, and combined {match, url, browser} entries.
  • Rewrite functions, including the legacy URL-object return shape, and undefined as "leave unchanged".
  • ctx.opener with the same meaning as Finicky v4's options.opener.
  • appType: "appName", "bundleId", "path", and "none". When you leave it out, Grinch recognizes app paths first, then looks up the value as an installed bundle ID or app name. If neither lookup succeeds, it uses the value as a bundle ID.
  • Option keys such as hideIcon and logRequests. urlShorteners and keepRunning are accepted but have no effect.

What to change

Bare-string matchers match hostnames

In Grinch, match: "github.com" matches the host and its subdomains. In Finicky v4, the same string is compared with === against the full URL, so it rarely fires. If your config relied on exact hostnames, use finicky.matchHostnames("github.com"). See bare-string matchers.

No import or named exports

Grinch turns export default <expr> into module.exports = <expr> when it loads the config, so pasting a v4 config works. import lines and named exports such as export const or export function fail with a load error that points at module.exports: JavaScriptCore runs the file as a script, so an import has nothing to resolve against. Inline what you need, or pre-process the file.

No await fetch()

Grinch decides synchronously, so Finicky's shortenerExpander pattern can't run. Expand shortened links outside Grinch instead; see URL shorteners.

Three finicky methods are missing

finicky.notify, finicky.getPowerInfo, and finicky.getBattery don't exist. Calling one throws a TypeError. At the top level of the config, the load fails; inside a matcher or open function, the rule is skipped and a runtime_js_error event is logged.

windowTitle needs Accessibility permission

Grinch asks for Accessibility permission when it launches. Until you grant it, opener.windowTitle is "" and rules that depend on it don't match.

The finicky namespace

Five Finicky v4 methods are present, plus one Grinch helper:

finicky.matchHostnames(matchers)
An exact-hostname matcher, compatible with Finicky. For subdomains, use Grinch's domain(…).
finicky.matchDomains(matchers)
A deprecated alias. It warns and calls matchHostnames.
finicky.getModifierKeys()
The real state of shift, option, command, control, caps lock, and fn.
finicky.isAppRunning(id)
Matches a bundle ID or a localized app name.
finicky.getSystemInfo()
{localizedName, name} for this Mac.
finicky.getRunningBrowsers() (Grinch only)
The bundle IDs of known browsers that are running, in family-table order. Combine it with Array.prototype.find to prefer the first running browser: prefs.find(b => running.includes(b)) || "com.apple.Safari".

Finicky v3 configs

Port v3 configs with Finicky's migration guide first. Grinch handles the most common v3 leftovers:

  • url.urlString, url.url, and url.opener still return usable values and log a one-line warning naming the v4 equivalent. urlString returns url.href, url.url returns the legacy {protocol, hostname, …} object, and url.opener returns the same opener object as ctx.opener, or null. URL-only callbacks do not request opener details; declare (url, ctx) and use ctx.opener.
  • url.keys throws, with a message pointing at ctx.modifiers and finicky.getModifierKeys(). Its v3 shape differs from v4's, and returning the wrong shape silently would send links to the wrong place.

Memory

Finicky bundles a WebKit instance for its config window. In a historical measurement, Grinch used about 15.5 MB of resident memory and Finicky about 142.5 MB. The footprint table has the full comparison.