Hammerspoon - macOS automation in Lua

Free, open-source macOS automation scripted in Lua. The glue behind my keyboard-first Mac: dictation, copy and paste keys, audio, screens and window rules.

13 Oct 2026

Hammerspoon is a free, open-source automation tool for macOS. It connects macOS system APIs to Lua: windows, screens, keyboard and mouse events, audio devices, the clipboard, mounted volumes, sleep and wake. You write the logic in 1 file, ~/.hammerspoon/init.lua, and it runs in the background with no window.

9 months after my first note, it has become the layer that holds my Mac together. It started with 1 function to move the mouse. Today it's 534 lines of config plus an 868-line module for my dictation app, and it does 15 jobs.

Key facts

What macOS automation app, scripted in Lua
Origin Forked from Mjolnir in October 2014, for a more integrated experience
Latest 1.1.1, 26th February 2026
Licence MIT, free
Code Objective-C, about 16,000 stars on GitHub
Needs Accessibility permission, plus whatever a module uses (microphone)
Extensions Spoons: ready-made Lua plug-ins

What I use it for

Typing and the clipboard

  • Dictation on Caps Lock. Caps Lock starts and stops Dictee, my local dictation app. Hammerspoon catches the key, draws the panel with the live text, and pastes the result into the app that had focus. It also starts the Dictee daemon: a launchd agent gets no microphone access, but a process started by Hammerspoon inherits its permission. See Dictee: my own local dictation app. Since 26th September 2026.
  • Copy on Cmd+D, Paste on Cmd+F, in every app. macOS menu remaps only work where the app lets the menu see the key first. Browsers, web apps, Electron apps and Ghostty don't. An event tap takes both keys first, presses the app's Edit > Copy or Edit > Paste, and falls back to a real Cmd+C or Cmd+V. Word and TextMate keep their own remaps. Since 30th September 2026.
  • Synthetic pastes that work. With Paste remapped, a Cmd+V sent by the Stream Deck or espanso matched no menu item and failed silently. Hammerspoon swallows any Cmd+V that doesn't come from a keyboard and types the clipboard text instead. A real Cmd+V passes through. Since 10th August 2026.
  • Paths for Claude. A clipboard watcher rewrites 2 local folder paths to the paths Claude sees in its sandbox, so a copied path works when I paste it into a chat.

Screens and windows

  • Demo screen rules. TextMate, Terminal and Ghostty windows move to my 1080p demo screen and fill it as soon as they open. The screen lost its name after a re-plug in August 2026, so the script now finds it as the only 1920x1080 screen.
  • Recording window. Ctrl+Opt+Cmd+R opens a new Chrome window in my "Record" profile and parks it on the demo screen. The Alfred keyword cr and the URL hammerspoon://record-window do the same. Since 10th October 2026.
  • Amethyst after sleep. Windows come back from sleep in the wrong layout. Hammerspoon quits and reopens Amethyst 5 seconds after the last wake, unlock or screensaver event, and the fresh launch applies my default layout. See Amethyst. Since 27th September 2026.
  • Dell monitor inputs. Ctrl+Opt+Cmd+1, 2 or 3, the Mod keys on my Advantage360, switch the left Dell between the Mac Studio, the Mac mini and the MacBook Pro. Hammerspoon asks the Mac mini to send the DDC command, because the mini's DisplayPort link stays up even when the Dell shows another input. See Kinesis Advantage keyboard.
  • Mouse to the focused window. The function from my January notes, still in daily use with 4 screens. Alfred now calls it through AppleScript, not through the hs command-line tool in the January steps. See Move mouse to active window via keyboard shortcut.

Audio and devices

  • The MOTU M2 stays the output. macOS switches the audio output whenever a new device appears. A watcher switches it back to the MOTU M2 at once, and a 5-second timer catches what the watcher misses after sleep or a monitor reconnect.
  • Voice recorders. When one of my voice recorders mounts, Hammerspoon runs a Python script that copies the new recordings to my voice memos folder, checks each copy and clears the recorder. See Voice Recorders.

Apps

  • Finder windows open in Filee. "Reveal in Finder" already goes to Filee, my own file manager. But macOS won't let another app handle folders, so open <folder>, Dock folders and the Desktop still create Finder windows. Hammerspoon closes each new Finder window and opens the same folder, with its selection, in Filee. hammerspoon://finder-redirect turns it on and off. See Filee: my own file manager for macOS. Since 13th October 2026.
  • Ghostty and herdr. A double-click on a file path in Ghostty opens the file in its default app, which herdr can't do itself. Cmd+Opt+R marks the focused herdr agent for later, in Ghostty only. See Herdr.
  • TextMate character count. A label on the TextMate status bar shows the character count, or the selection and the total. It's still in the config, but Textee replaced TextMate, see Textee: my own text editor for macOS.

What I stopped using it for

  • Moving windows between screens. From 10th August to 11th September 2026 a Hammerspoon tap moved windows between displays, after an Edge bug closed web-app windows on the Amethyst shortcuts. Amethyst's own shortcuts work again, so the tap is gone.
  • Elgato Key Lights. The Stream Deck now runs the light script directly, with no hotkey and no Accessibility permission. See Elgato Key Light — Overview & Wi-Fi Troubleshooting.

How I run it

  • 1 file, 1 module. Everything lives in init.lua, except Dictee. Its module lives in the Dictee project and is symlinked into ~/.hammerspoon.
  • Claude Code writes the Lua. I describe the behaviour, Claude Code edits the config and reloads it. I almost never write Lua myself, which removes the main trade-off in my January notes.
  • A backup before each change. ~/.hammerspoon/backups/ holds a dated copy of init.lua from before each change: 16 since 11th September 2026.
  • URL events as an API. hammerspoon://record-window, hammerspoon://voice-import and hammerspoon://finder-redirect let Alfred, the Stream Deck and scripts call Hammerspoon without a shortcut.
  • Event taps over hotkeys. An hs.eventtap sees the key before the app, and can act in 1 app only, such as Cmd+Opt+R in Ghostty. An hs.hotkey takes the key everywhere.
  • Real keys versus synthetic keys. A physical key press has source process ID 0. The taps check it, so a script or the Stream Deck can't trigger them by accident. A test script once cancelled 4 Dictee recordings with a synthetic Esc, before Dictee checked it.
local pid = ev:getProperty(hs.eventtap.event.properties.eventSourceUnixProcessID)
if pid ~= 0 then return false end -- synthetic key: let it through untouched

The January wish list, revisited

My January notes ended with 5 "high-ROI" uses. Where they stand:

January idea October 2026
Cursor follows focus Done, on a hotkey through Alfred
Audio device enforcement Done, the MOTU M2 rule
Demo and meeting mode toggles Not built
App launch and window rules Done: demo screen, recording window, Amethyst after sleep
A "fix everything" panic hotkey Not built. The Amethyst relaunch covers the most common fix

The January idea of Caps Lock for the mouse warp didn't happen either: Caps Lock went to Dictee.


22 Jan 2026 my original notes.

Overview

Hammerspoon is a lightweight macOS automation tool built on Lua. It exposes low-level macOS APIs for windows, screens, mouse, keyboard, and system events, making it ideal for deterministic, keyboard-driven workflows.

Why it's useful:

  • Full control over the mouse cursor (e.g. move to focused window)
  • Precise window and screen awareness (multi-monitor safe)
  • Instant execution (no UI lag)
  • Declarative, reproducible configuration (dotfile-friendly)

Typical use cases:

  • Warp mouse to the focused window after app switching
  • Keyboard-driven window positioning across monitors
  • Custom app switchers that fix Cmd+Tab shortcomings
  • Context-aware shortcuts (screen, app, window state)

Strengths:

  • Free and open-source
  • Extremely reliable
  • Composable (small functions, clear intent)
  • Excellent fit as a "logic layer" behind hardware keyboards and launchers

Trade-offs:

  • Requires basic Lua
  • No GUI for building automations
  • You own the config (feature, not a bug)

Ideal for me because, best paired with:

  • Alfred (intent + discovery)
  • Kinesis Advantage360 (hardware triggers / layers)
  • Keyboard-only workflows on multi-monitor setups

Mental model:

Hardware key → Launcher (optional) → Hammerspoon → macOS APIs

In practice, Hammerspoon is the missing glue that makes macOS behave predictably when you refuse to touch the mouse.

How do I use it?

Out of the box, Hammerspoon does nothing.

You will need to create a Lua script in ~/.hammerspoon/init.lua using our APIs and standard Lua APIs.

If you are new to Hammerspoon, read the:

Why using it (with Alfred and Amethyst already in use)

Short answer: logic, state, and system hooks.
Amethyst and Alfred are great at actions. Hammerspoon excels at decisions.

Here's what Hammerspoon can do that they fundamentally can't (or only very awkwardly).

1. Conditional, Context-Aware Automation

Example

Same hotkey, different behavior:

  • If Teams focused → mute mic
  • If browser focused → mute tab
  • Else → mute system audio

Amethyst: ❌
Alfred: ⚠️ (hacky, brittle)
Hammerspoon: ✅ native

This is where Lua + app/window state matters.

2. Reacting to System Events (not user-triggered)

Things that happen to your Mac:

  • Display connected / disconnected
  • Wi-Fi network changed
  • Sleep / wake
  • Audio device appeared
  • App crashed or relaunched

Amethyst: ❌
Alfred: ❌ (mostly pull-based)
Hammerspoon: ✅ push-based

Example:

  • HDMI monitor appears → move demo windows + switch audio + warp mouse

3. True Mouse Control (not just triggers)

You already hit this.

  • Move cursor to focused window
  • Jump cursor between monitors
  • Cursor follows Cmd+Tab focus
  • Eliminate mouse entirely

Alfred: ❌
Amethyst: ❌
Hammerspoon: ✅ (one of its killer features)

4. Stateful Automations (memory across actions)

Hammerspoon can remember things:

  • Last audio device
  • Last window layout
  • Current "mode" (Demo / Meeting / Focus)
  • Toggle cycles beyond on/off

Example:

  • Press once → setup demo
  • Press again → restore previous state

Alfred workflows reset every run.
Hammerspoon keeps state alive.

5. App-Lifecycle Enforcement

Rules like:

  • "Teams must never open on monitor 1"
  • "Safari popups always float + move"
  • "If Zoom launches, kill Music"

This happens automatically, not via shortcuts.

Amethyst: limited, layout-only
Alfred: manual
Hammerspoon: declarative + automatic

6. Deep Audio & Device Logic

You're in audio hell already 😄

Hammerspoon can:

  • Detect audio devices appearing/disappearing
  • Enforce preferred output
  • Auto-fix macOS "helpful" switching
  • Per-context audio rules

Alfred: ❌
Amethyst: ❌
Hammerspoon: ✅

7. Accessibility-Level UI Automation

When apps don't expose APIs:

  • Click buttons
  • Navigate menus
  • Read UI state
  • Build robust macros

More reliable than AppleScript, more programmable than Alfred.

8. Glue Between Tools (this is the real win)

Hammerspoon doesn't replace:

  • Amethyst → window tiling engine
  • Alfred → launcher + UI

It orchestrates them.

Example:

  • Hammerspoon detects context
  • Calls Amethyst layout change
  • Triggers Alfred workflow
  • Fixes mouse + audio after

Alfred alone cannot observe system state.
Amethyst cannot decide.

Mental Model (important)

  • Amethyst: "Where should windows go?"
  • Alfred: "Run this now"
  • Hammerspoon: "Given what's happening, what should my system do?"

If you're happy with:

  • Static shortcuts
  • Manual triggers
  • Stateless workflows

→ You don't need Hammerspoon.

If you want:

  • Deterministic behavior
  • Zero-click corrections
  • Keyboard-only, multi-monitor sanity

→ Hammerspoon becomes the control plane.

High-ROI Uses for you specifically

Given your setup, the biggest wins would be:

  1. Cursor-follows-focus (you already felt it)
  2. Audio device enforcement
  3. Demo / Meeting mode toggles
  4. App launch + window correction rules
  5. "Fix everything" panic hotkey

Use cases & code

Position mouse (using Kinesis + Alfred)

Goal: Move the mouse cursor to the center of the currently focused window after switching apps (e.g. via Cmd+Tab), using:

  • Kinesis Advantage360 (hardware trigger)
  • Alfred (launcher / glue)
  • Hammerspoon (window + mouse logic)

Step 1: Choose a dedicated shortcut
Pick a shortcut that:

  • Is easy to hit from the Advantage360 (thumb cluster is ideal)
  • Does not conflict with system shortcuts

Recommended:

  • Hyper + M
    (Hyper = Ctrl + Alt + Cmd + Shift)

You can change this later, but pick one and stick to it.

My choice = CAPS key?

Step 2: Program the Kinesis shortcut
In SmartSet:

  • Assign a key (or thumb key) to emit:
    Cmd Shift 3
  • Keep it as a simple key combo (no delays, no sequences)

Result:
Pressing that key sends a single, clean shortcut to macOS.

Step 3: Create an Alfred workflow

  1. Open Alfred Preferences
  2. Go to Workflows → New Workflow
  3. Name it something like: "Warp Mouse to Window"

Add a trigger:

  • Hotkey Trigger
  • Shortcut: Cmd Shift 3
  • Set it to "Pass through to workflow"

Add an action:

  • Run Script
  • Language: /bin/bash
  • Script:
    hs -c 'warpToFocusedWindow()'

Connect:
Hotkey → Run Script

Step 4: Add the Hammerspoon function
Open ~/.hammerspoon/init.lua and add:

function warpToFocusedWindow()
  local win = hs.window.focusedWindow()
  if not win then return end

  local f = win:frame()
  hs.mouse.absolutePosition({
    x = f.x + f.w / 2,
    y = f.y + f.h / 2
  })
end

Reload Hammerspoon config.

Further reading