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 via hs.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) and bind(key):toApplication(app)
-
Tracks bindings in self.bindings table
init.lua - Main entry point
-
Calls hs.ipc.cliInstall() to enable CLI access
-
Defines hyper modifier: {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.chooser API with fuzzy matching
-
Shows windows, apps, and commands
-
Keybindings: cmd+space and cmd+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 -C or open "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.lua is loaded in init.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.yml build 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)