Hammerspoon configuration skill
Overview
Manage Hammerspoon configuration for macOS automation including app launching, window switching, emoji/symbol pickers, and leader modal. Window management is handled by AeroSpace.
Configuration locations
- •Config directory:
~/.config/hammerspoon/ - •Symlink:
~/.hammerspoon→~/.config/hammerspoon/ - •CLI tool:
hs(installed viahs.ipc.cliInstall()in init.lua)
Current modules
Core infrastructure
hyper-key.lua - Inline hyper key implementation
- •No spoon dependencies
- •Provides
HyperKey.new(mods)constructor - •Binding methods:
bind(key):toFunction(name, fn)andbind(key):toApplication(app) - •Tracks bindings in
self.bindingstable
init.lua - Main entry point
- •Calls
hs.ipc.cliInstall()to enable CLI access - •Defines
hypermodifier:{cmd, ctrl, alt, shift} - •Loads window-switcher and sets up cmd+space and cmd+tab bindings
- •Loads notch-clock with 4-minute offset
- •Loads leader modal with emoji and symbol pickers
Leader modal system
leader-modal.lua - Modal keybinding system
- •Timeout-based modal (default 3000ms)
- •Shows on-screen hints for available bindings
- •Supports nested leader keys
- •Sticky mode for repeated actions
leader-dsl.lua - DSL for defining leader bindings
- •
Leader(key, description, bindings)- Define leader menu - •
Bind(key, description, action, options)- Define action - •Actions can be functions, shell commands, or mode transitions
- •Automatically registers clues from
~/.config/hammerspoon/clues/directory
clues/*.lua - Leader binding definitions
- •
windows.lua- Window management (moved to AeroSpace) - •
hammerspoon.lua- Reload config, console, update apps, switcher - •
apps.lua- App launching - •
system.lua- System operations - •
cleanshot.lua- CleanShot integration - •
emoji.lua- Emoji picker - •
superwhisper.lua- SuperWhisper integration
Pickers
emoji-picker.lua - Emoji chooser
- •Fuzzy-searchable emoji picker
- •Categories and descriptions
- •Inserts selected emoji via keystroke
symbol-picker.lua - Symbol chooser
- •Special characters and symbols
- •Categories: arrows, math, punctuation, currency, etc.
- •Inserts selected symbol via keystroke
chooser-style.lua - Shared chooser styling
- •Dark theme
- •Consistent width and row count
- •Applied to all choosers
Window switcher
window-switcher.lua - Unified launcher/dispatcher modal
- •Uses
hs.chooserAPI with fuzzy matching - •Shows windows, apps, and commands
- •Keybindings:
cmd+spaceandcmd+tab - •Dark theme with 15 visible rows
fuzzy.lua - Dynamic programming fuzzy matching
- •Subsequence matching with scoring bonuses
- •Type priority: window (3), app (2), command (1)
notch-clock.lua - Clock in notch area
- •Shows time in MacBook notch
- •4-minute offset configuration
Common operations
Using the hs CLI
The hs command-line tool provides direct interaction with Hammerspoon. It requires hs.ipc.cliInstall() to be called in init.lua (already configured).
Check for errors and module status:
echo "
local status = {modules_loaded = {}, errors = {}}
local modules = {'hyper-key', 'config-watch', 'window-hotkeys', 'quick-switch', 'window-switcher'}
for _, mod in ipairs(modules) do
local ok, result = pcall(require, mod)
if ok then
table.insert(status.modules_loaded, mod)
else
table.insert(status.errors, mod .. ': ' .. tostring(result))
end
end
return hs.json.encode(status, true)
" | hs -c ''
View live console output:
hs -C
Execute Hammerspoon commands:
echo "hs.alert.show('test')" | hs -c ''
echo 'hs.reload()' | hs -c ''
Interactive Lua REPL:
hs
Mirror prints to console:
hs -P
Run a script:
hs /path/to/script.lua
Common flags:
- •
-A- Auto-launch Hammerspoon if not running - •
-C- Clone console prints to this terminal (best for checking errors) - •
-P- Mirror prints to Hammerspoon console - •
-c- Execute command and return result - •
-i- Force interactive mode - •
-n- Disable colorized output - •
-N- Force colorized output - •
-q- Quiet mode (errors and results only)
Other operations
Reload Hammerspoon:
echo 'hs.reload()' | hs -c '' # or via URL: open -g hammerspoon://reload
Check if running:
ps aux | rg -i hammerspoon
Open console window:
open "hammerspoon://consoleWindow"
Test configuration:
Edit any .lua file in ~/.config/hammerspoon/ to trigger auto-reload
Development patterns
Adding new leader bindings
Create a file in ~/.config/hammerspoon/clues/:
return Leader("key", "Description", {
Bind("a", "Action 1", { fn = function() ... end }),
Bind("b", "Action 2", { shell = "/path/to/script" }),
Bind("c", "Action 3", { mode = "mode_name" }),
})
Creating new modules
- •Create
~/.config/hammerspoon/module-name.lua - •Return module table or functionality
- •Require in
init.lua:local module = require("module-name")
App replacement status
Current Hammerspoon setup replaces:
- •AltTab: Window switching via window-switcher.lua (cmd+space, cmd+tab)
Other macOS automation tools:
- •Karabiner-Elements: Key remapping (caps lock to ctrl, etc.)
- •AeroSpace: Window management (tiling, workspaces, monitor assignment)
- •CleanShot: Screenshots (integrated via leader modal)
Troubleshooting
Check for errors
Use the hs CLI to check for module loading errors:
echo "
local status = {modules_loaded = {}, errors = {}}
local modules = {'hyper-key', 'config-watch', 'window-hotkeys', 'quick-switch', 'window-switcher'}
for _, mod in ipairs(modules) do
local ok, result = pcall(require, mod)
if ok then
table.insert(status.modules_loaded, mod)
else
table.insert(status.errors, mod .. ': ' .. tostring(result))
end
end
return hs.json.encode(status, true)
" | hs -c ''
Or view live console output: hs -C
Config not loading
- •Check symlink:
ls -la ~/.hammerspoon - •Verify Hammerspoon is running:
ps aux | rg Hammerspoon - •Reload manually:
echo 'hs.reload()' | hs -c '' - •Check for errors:
hs -Coropen "hammerspoon://consoleWindow"
Keybindings not working
- •Check for conflicts with app shortcuts
- •Verify modifier keys are correct
- •Test with:
echo "hs.alert.show('test')" | hs -c ''
Auto-reload not triggering
- •Verify
config-watch.luais loaded ininit.lua - •Check file extension is
.lua - •Restart Hammerspoon app
CLI not working
If hs command not found, ensure hs.ipc.cliInstall() is called in init.lua
Philosophy
Zero spoons approach
- •Inline functionality instead of external dependencies
- •Only use spoons if absolutely necessary
- •Keep implementation simple and maintainable
Decision criteria for using spoons:
- •Must provide significant value that's hard to inline
- •Must be actively maintained
- •Must be worth the workflow complexity
Future spoon management (not yet implemented): If spoons are needed, they will be managed via GitHub workflow:
- •Add spoon commit hash to
.github/workflows/versions.lua - •Create
.github/workflows/hammerspoon.ymlbuild workflow - •Workflow clones spoons at specified revisions, bundles into tarball
- •Release as
hammerspoon-YYYY.MM.DD-darwin-arm64.tar.gz - •Only add spoons to config after workflow builds successfully
This ensures pinned, reproducible spoon dependencies similar to other tools in the repo.
File organization
- •One module per file
- •Clear, descriptive names
- •Require modules in
init.lua - •Use XDG-style config directory
Resources
- •Hammerspoon API: https://www.hammerspoon.org/docs/
- •Plan document:
~/.claude/plans/hammerspoon-consolidation.md - •dbalatero dotfiles: https://github.com/dbalatero/dotfiles/tree/main/hammerspoon (reference only)