I've been a Keyboard Maestro user for ten years. Paid for every major upgrade. Built up a Global Macro Group with seventeen macros that my hands depend on: browser history on ⌘J/⌘L, tab switching on ⌘S/⌘D, hard refresh, dev tools, window sizing, a terminal toggle on backtick, forward delete, a text expansion. The kind of setup you stop thinking about because it's part of how you type.
Last week I replaced all of it with one Rebind script. I didn't even write the script — RebindGPT did.
The before state
If you've maintained a serious Keyboard Maestro setup, you know what it looks like. Every remap is a macro. Every macro is a stack of click-configured conditions: If any of the following are true: Google Chrome is at the front, Chromium is at the front... execute: Type the ⌘] Keystroke. Otherwise: No Action. Multiply that by every browser you use, then by every shortcut, then again for the apps that need a different keystroke for the same action.
My "browser history forward" macro alone was two condition blocks deep — one for Chrome-family browsers (⌘]) and one for Firefox-family (⌘→), each enumerating applications one dropdown at a time. Seventeen macros of that. It works, and Keyboard Maestro is genuinely good software — but the setup is opaque, it lives in a proprietary editor, you can't diff it, you can't grep it, and you rebuild it dropdown by dropdown on every new machine.
The old setup: one of seventeen macros — two condition blocks and four app dropdowns just to remap ⌘L per browser family.
The after state
One Luau file. My entire Global Macro Group, expressed as code I can read:
remap("J", "LeftBrace", function() return cmd() and CHROMISH[front()] end) -- Chrome → ⌘[
remap("J", "Left", function() return cmd() and FIREFOXISH[front()] end) -- Firefox → ⌘←
remap("L", "RightBrace", function() return cmd() and CHROMISH[front()] end) -- Chrome → ⌘]
remap("L", "Right", function() return cmd() and FIREFOXISH[front()] end) -- Firefox → ⌘→
That's the entire "browser history" pair — the thing that took four condition blocks across two macros in the old setup. App families are defined once as plain tables and reused everywhere:
BROWSERS = set { "Google Chrome", "Chromium", "Brave Browser", "Firefox", "LibreWolf", "Safari" }
ARROW_TABS = set { "Google Chrome", "Chromium", "Brave Browser", "Firefox", "LibreWolf", "Safari", "Code", "Visual Studio Code", "Cursor" }
BRACKET_TABS = set { "Ghostty", "Terminal", "Hyper", "iTerm2", "iTerm", "MacVim", term }
Add a browser to the table, every remap picks it up. Try doing that across seventeen macros of dropdowns.
I didn't write this script
Here's the part that actually surprised me. I described my Keyboard Maestro setup to RebindGPT — the shortcuts, which apps needed which variants, the terminal toggle behavior — and it wrote the whole thing. The app-family tables, the modifier helpers, even the subtle stuff I would have gotten wrong on the first pass, like waiting for physical modifiers to clear before injecting a chord that conflicts with them:
-- Emit `chord` after physical modifiers clear — used when the chord conflicts
-- with held ⌘/⇧ (macOS applies held modifiers to any injected event).
local function injectAfterMods(chord)
Run(function()
while cmd() or shift() do Sleep(5) end
HID.Combo(chord)
end)
end
That's a real macOS injection pitfall — held modifiers merge into synthetic events — and the generated script handled it without being asked.
It didn't stop at writing the script, either. When an early version polled System.Window() too aggressively in a timer, I pasted the runtime's warning into the same chat and RebindGPT diagnosed the tick-budget problem and rewrote the hot path — slower timer, expensive check moved behind the keypress that actually needs it.
The replacement: taky-km.luau running in the Rebind editor at 0µs/tick, with RebindGPT explaining its own performance fix.
The full script
Everything below is what runs on my machine today. The two configurable values (terminal app and the ⌘P text expansion) surface as UI controls in the Rebind panel, so the script itself stays generic:
--[[
rebind: name=taky-km
rebind: min_sdk=3.2.1
rebind: description=Keyboard Maestro parity — global, app-conditional chord remaps, app control, and configurable terminal/hash.
--]]
-- ═══════════════════════════════════════════════════════════════════════════════
-- Config
-- ═══════════════════════════════════════════════════════════════════════════════
local cfg = UI.Schema({
terminal = UI.Text("Terminal", {
label = "Terminal App",
tooltip = "Process name of your terminal (e.g. Terminal, Ghostty, iTerm2, Kitty).",
maxLength = 40,
}),
hash = UI.Text("your-snippet-here", {
label = "Hash String",
tooltip = "Text inserted by ⌘P (skipped in Cursor / MacVim).",
maxLength = 128,
}),
})
-- ═══════════════════════════════════════════════════════════════════════════════
-- Helpers
-- ═══════════════════════════════════════════════════════════════════════════════
local function set(t)
local s = {}
for _, n in ipairs(t) do s[n] = true end
return s
end
local function cmd() return Input.IsDown("LWin") or Input.IsDown("RWin") end
local function shift() return Input.IsDown("LShift") or Input.IsDown("RShift") end
local function ctrl() return Input.IsDown("LCtrl") or Input.IsDown("RCtrl") end
local function alt() return Input.IsDown("LAlt") or Input.IsDown("RAlt") end
local function bare() return not (cmd() or shift() or ctrl() or alt()) end
-- Evaluate the foreground process name on demand (not polled).
local function front()
local w = System.Window()
return w and w.process or ""
end
-- Emit `chord` immediately — used when adding modifiers to an already-held ⌘
-- (the physical ⌘ merges in, so we only emit the delta modifiers: ⌘R → "Shift+R").
local function remap(trigger, chord, pred)
Bind(trigger, { when = pred, action = function() HID.Combo(chord) end })
end
-- Emit `chord` after physical modifiers clear — used when the chord conflicts
-- with held ⌘/⇧ (macOS applies held modifiers to any injected event).
local function injectAfterMods(chord)
Run(function()
while cmd() or shift() do Sleep(5) end
HID.Combo(chord)
end)
end
-- ═══════════════════════════════════════════════════════════════════════════════
-- App groups (built once at load)
-- ═══════════════════════════════════════════════════════════════════════════════
local BROWSERS, ARROW_TABS, BRACKET_TABS, SHIFT_TABS, TERMINALS
local CHROMISH, FIREFOXISH, HASH_SKIP
function OnStart()
local term = cfg.terminal
BROWSERS = set { "Google Chrome", "Chromium", "Brave Browser", "Firefox", "LibreWolf", "Safari" }
ARROW_TABS = set { "Google Chrome", "Chromium", "Brave Browser", "Firefox", "LibreWolf", "Safari", "Code", "Visual Studio Code", "Cursor" }
BRACKET_TABS = set { "Ghostty", "Terminal", "Hyper", "iTerm2", "iTerm", "MacVim", term }
SHIFT_TABS = set { "Neovide" }
TERMINALS = set { "Terminal", "Hyper", "iTerm2", "iTerm", "Ghostty", term }
CHROMISH = set { "Google Chrome", "Chromium" }
FIREFOXISH = set { "Firefox", "Brave Browser", "LibreWolf" }
HASH_SKIP = set { "Cursor", "MacVim" }
Log.Info(string.format("taky-km ready — terminal=%s front=%s", term, front()))
end
-- ═══════════════════════════════════════════════════════════════════════════════
-- Browser chord remaps (⌘ held, in a browser)
-- ═══════════════════════════════════════════════════════════════════════════════
remap("R", "Shift+R", function() return cmd() and BROWSERS[front()] end) -- ⌘R → ⇧⌘R hard refresh
remap("U", "Opt+U", function() return cmd() and BROWSERS[front()] end) -- ⌘U → ⌥⌘U view source
remap("I", "Opt+I", function() return cmd() and BROWSERS[front()] end) -- ⌘I → ⌥⌘I dev tools
-- ═══════════════════════════════════════════════════════════════════════════════
-- Tab navigation (⌘S left / ⌘D right), per app family
-- ═══════════════════════════════════════════════════════════════════════════════
remap("S", "Opt+Left", function() return cmd() and ARROW_TABS[front()] end)
remap("S", "Shift+LeftBrace", function() return cmd() and BRACKET_TABS[front()] end)
remap("S", "Shift+S", function() return cmd() and SHIFT_TABS[front()] end)
remap("D", "Opt+Right", function() return cmd() and ARROW_TABS[front()] end)
remap("D", "Shift+RightBrace", function() return cmd() and BRACKET_TABS[front()] end)
remap("D", "Shift+D", function() return cmd() and SHIFT_TABS[front()] end)
-- ═══════════════════════════════════════════════════════════════════════════════
-- Browser history (⌘J back / ⌘L forward)
-- ═══════════════════════════════════════════════════════════════════════════════
remap("J", "LeftBrace", function() return cmd() and CHROMISH[front()] end) -- Chrome → ⌘[
remap("J", "Left", function() return cmd() and FIREFOXISH[front()] end) -- Firefox → ⌘←
remap("L", "RightBrace", function() return cmd() and CHROMISH[front()] end) -- Chrome → ⌘]
remap("L", "Right", function() return cmd() and FIREFOXISH[front()] end) -- Firefox → ⌘→
-- ═══════════════════════════════════════════════════════════════════════════════
-- App-specific single-key overrides
-- ═══════════════════════════════════════════════════════════════════════════════
remap("F", "Opt+F", function() return cmd() and front() == "Mail" end) -- ⌘F → ⌥⌘F find in Mail
remap("E", "M", cmd) -- ⌘E → ⌘M minimize
-- ⌘⌫ → ^C in terminals, else ⌘Delete (forward delete)
Bind("Backspace", {
when = cmd,
action = function()
if TERMINALS[front()] then
injectAfterMods("Ctrl+C")
else
HID.Combo("Delete")
end
return false
end,
})
-- ⌘P → configured snippet (skipped in Cursor / MacVim where ⌘P passes through)
Bind("P", {
when = function() return cmd() and not HASH_SKIP[front()] end,
action = function()
Run(function()
while cmd() or shift() do Sleep(5) end
HID.Type(cfg.hash)
end)
return false
end,
})
-- ⇧⌘' → literal backtick (drops the held ⇧⌘)
Bind("Apostrophe", {
when = function() return cmd() and shift() end,
action = function()
injectAfterMods("Grave")
return false
end,
})
-- ═══════════════════════════════════════════════════════════════════════════════
-- Window sizing
-- ═══════════════════════════════════════════════════════════════════════════════
-- ⇧⌘Enter → center-to-left (85% width)
Bind("Enter", {
when = function() return cmd() and shift() end,
action = function()
local sw, sh = System.Screen()
Window.Move(0, 0, 0, math.floor(sw * 0.85), math.floor((sh - 22) * 0.90))
return false
end,
})
-- ⌘Enter → fullscreen-ish with margin
Bind("Enter", {
when = function() return cmd() and not shift() end,
action = function()
local sw, sh = System.Screen()
Window.Move(0, 0, 25, sw, sh - 25)
return false
end,
})
-- ═══════════════════════════════════════════════════════════════════════════════
-- Terminal toggle: bare backtick activates/hides the configured terminal
-- ═══════════════════════════════════════════════════════════════════════════════
Bind("Grave", {
when = function() return bare() and not App.IsRunning("Hyper") end,
action = function()
if front() == cfg.terminal then
HID.Combo("Cmd+H")
else
Run(function() App.Activate(cfg.terminal) end)
end
return false
end,
})
Bind.Remap("CapsLock", "Escape")
What carried over, one for one
- Browser history — ⌘J back / ⌘L forward, with the correct native chord per browser family
- Tab navigation — ⌘S / ⌘D everywhere: arrow-style in browsers and editors, bracket-style in terminals, app-specific in Neovide
- Hard refresh, view source, dev tools — ⌘R / ⌘U / ⌘I in any browser
- Window management — ⌘Enter near-fullscreen, ⇧⌘Enter 85%-width layout
- Terminal toggle — bare backtick summons or hides my terminal, whichever app is in front
- Forward delete, minimize, Mail find, text expansion, backtick literal — all the small ones that add up
- CapsLock → Escape — one line
Why this beats the old setup
It's text. The whole configuration diffs in git, greps, copies to a new machine as one file, and reads top to bottom. My macro setup finally has a paper trail.
It's one namespace. Modifier state, foreground app, window geometry, app activation, typing — one SDK instead of a chain of conditions, actions, and helper macros wired together in a GUI.
It runs through hardware. Rebind's output replays through the device, so the OS sees a standard keyboard. No accessibility-permission roulette after every macOS update.
It's conversational to extend. Yesterday I wanted ⌘F remapped only in Mail. That was one sentence to RebindGPT and one pasted line — not a new macro, a new group, and four dropdowns.
Ten years of license fees, seventeen macros, one editor I could never search — down to a single file an AI wrote in one sitting. If you have a Keyboard Maestro setup you've been maintaining out of inertia, describe it to RebindGPT and see what comes back.