dev-test-hammerspoon
This skill should be used when the user asks to "debug macOS app", "test native app", "automate macOS workflow", "test native macOS application", or needs desktop automation for testing macOS applications with Hammerspoon. Use for application launch/control, window management, keyboard/mouse simulation, and visual verification.
What this skill does
**Announce:** "I'm using dev-test-hammerspoon for macOS desktop automation."
<EXTREMELY-IMPORTANT>
## Gate Reminder
Before taking screenshots or running E2E tests, you MUST complete all 6 gates from dev-tdd:
```
GATE 1: BUILD
GATE 2: LAUNCH (with file-based logging)
GATE 3: WAIT
GATE 4: CHECK PROCESS
GATE 5: READ LOGS ← MANDATORY, CANNOT SKIP
GATE 6: VERIFY LOGS
THEN: E2E tests/screenshots
```
**You loaded dev-tdd earlier. Follow the gates now.**
</EXTREMELY-IMPORTANT>
## Contents
- [Tool Availability Gate](#tool-availability-gate)
- [When to Use Hammerspoon](#when-to-use-hammerspoon)
- [Hammerspoon Setup](#hammerspoon-setup)
- [Input Simulation](#input-simulation)
- [Application Control](#application-control)
- [Window Management](#window-management)
- [Screenshots](#screenshots)
- [Complete E2E Example](#complete-e2e-example)
- [Alternative: cliclick](#alternative-cliclick)
# macOS Desktop Automation
<EXTREMELY-IMPORTANT>
## Tool Availability Gate
**Verify Hammerspoon is installed before proceeding.**
```bash
# Check Hammerspoon installation (both CLI and app)
which hs || echo "MISSING: hs CLI"
ls /Applications/Hammerspoon.app 2>/dev/null || echo "MISSING: Hammerspoon.app"
```
**If missing:**
```
STOP: Cannot proceed with macOS automation.
Missing tool: Hammerspoon (required for macOS E2E testing)
Install with:
# Install via nix-darwin, or verify: hammerspoon -c "print('ok')"
After installing:
1. Open Hammerspoon.app
2. Grant Accessibility permissions in System Preferences
3. In Hammerspoon console, run: hs.ipc.cliInstall()
4. Add to ~/.hammerspoon/init.lua: require("hs.ipc")
Reply when installed and I'll continue testing.
```
**This gate is non-negotiable. Missing tools = full stop.**
</EXTREMELY-IMPORTANT>
<EXTREMELY-IMPORTANT>
## When to Use Hammerspoon
**Use Hammerspoon for:**
- macOS native application automation
- System-wide keyboard shortcuts
- Window management and positioning
- Menu item automation
- Clipboard verification
- Multi-app workflows on macOS
**Do not use Hammerspoon for:**
- Testing web applications (use Chrome MCP or Playwright)
- Cross-platform testing needed
- Linux desktop automation (use dev-test-linux)
**For web testing, discover and read the relevant skill:**
Related skills:
- Read `${CLAUDE_SKILL_DIR}/../../skills/dev-test-chrome/SKILL.md` and follow its instructions.
- Read `${CLAUDE_SKILL_DIR}/../../skills/dev-test-playwright/SKILL.md` and follow its instructions.
- Chrome MCP skill - debugging
- Playwright skill - CI/CD
### Rationalization Prevention
| Thought | Reality |
|---------|---------|
| "I can use AppleScript instead" | Hammerspoon is more reliable for automation |
| "I'll test the app manually" | AUTOMATE IT with Hammerspoon |
| "Web testing tools work for desktop apps" | NO. Use Hammerspoon for native apps |
| "Accessibility permissions are too hard" | One-time setup. Do it. |
| "The app is too complex to automate" | Break it into testable steps |
</EXTREMELY-IMPORTANT>
## Hammerspoon Setup
**One-time setup in `~/.hammerspoon/init.lua`:**
```lua
require("hs.ipc") -- Enables CLI
```
**Reload config after changes:**
```bash
hs -c 'hs.reload()' # Reload Hammerspoon configuration
```
## Input Simulation
### hs.eventtap - Keyboard/Mouse
```lua
-- Type text (simulates keystrokes)
hs.eventtap.keyStrokes("hello world")
-- Key press with modifiers
hs.eventtap.keyStroke({"cmd"}, "c") -- Cmd+C
hs.eventtap.keyStroke({"cmd", "shift"}, "s") -- Cmd+Shift+S
hs.eventtap.keyStroke({"ctrl", "alt"}, "t") -- Ctrl+Alt+T
hs.eventtap.keyStroke({}, "return") -- Enter key
hs.eventtap.keyStroke({}, "escape") -- Escape key
-- Function keys
hs.eventtap.keyStroke({}, "f1")
hs.eventtap.keyStroke({"cmd"}, "f5")
-- Mouse clicks
hs.eventtap.leftClick({x=100, y=200})
hs.eventtap.rightClick({x=100, y=200})
hs.eventtap.middleClick({x=100, y=200})
hs.eventtap.doubleClick({x=100, y=200})
-- Mouse movement
hs.mouse.absolutePosition({x=500, y=300})
-- Scroll
hs.eventtap.scrollWheel({0, -5}, {}) -- Scroll down
hs.eventtap.scrollWheel({0, 5}, {}) -- Scroll up
```
### Running from CLI
```bash
# Execute Lua code directly
hs -c 'hs.eventtap.keyStroke({"cmd"}, "c")' # Run inline Lua code via CLI
# Execute a script file
hs /path/to/test_script.lua # Run Hammerspoon script from file
# Pipe script via stdin
echo 'hs.eventtap.keyStrokes("test")' | hs -s # Run script piped through stdin
```
## Application Control
### hs.application
```lua
-- Launch or focus app by name
local app = hs.application.launchOrFocus("Safari")
-- Launch app by bundle ID
hs.application.launchOrFocusByBundleID("com.apple.Safari")
-- Get running app
local app = hs.application.get("Safari")
if app then
app:activate() -- Bring to front
app:hide() -- Hide
app:unhide() -- Unhide
app:kill() -- Terminate gracefully
app:kill9() -- Force kill
end
-- Get frontmost app
local front = hs.application.frontmostApplication()
print(front:name())
print(front:bundleID())
-- List all running apps
for _, app in ipairs(hs.application.runningApplications()) do
print(app:name())
end
-- Wait for app to launch
hs.timer.waitUntil(
function() return hs.application.get("MyApp") ~= nil end,
function() print("App launched") end,
0.5 -- Check every 0.5 seconds
)
```
### Menu Items
```lua
-- Click menu item
local app = hs.application.get("Safari")
app:selectMenuItem({"File", "New Window"})
app:selectMenuItem({"Edit", "Paste"})
-- Check if menu item exists
local menuItem = app:findMenuItem({"File", "Save"})
if menuItem then
print("Save is available, enabled:", menuItem.enabled)
end
```
## Window Management
### hs.window
```lua
-- Get focused window
local win = hs.window.focusedWindow()
print(win:title())
print(win:frame()) -- {x, y, w, h}
-- Get app's windows
local app = hs.application.get("Safari")
local wins = app:allWindows()
for _, win in ipairs(wins) do
print(win:title())
end
-- Get window by title (partial match)
local win = hs.window.get("My Document")
-- Window actions
win:focus() -- Focus window
win:maximize() -- Maximize
win:minimize() -- Minimize to dock
win:close() -- Close window
-- Move/resize
win:setFrame({x=100, y=100, w=800, h=600})
win:move({100, 0}) -- Move relative
win:setSize({800, 600})
win:centerOnScreen()
-- Get window position and size
local frame = win:frame()
print("Position:", frame.x, frame.y)
print("Size:", frame.w, frame.h)
```
## Screenshots
<EXTREMELY-IMPORTANT>
### The Iron Law of Visual Verification
**Every E2E test MUST include screenshot evidence.**
After completing a workflow, capture a screenshot to prove success.
</EXTREMELY-IMPORTANT>
### screencapture (CLI)
```bash
# Full screen (all displays)
screencapture /tmp/screenshot.png # Capture entire screen to file
# Main screen only
screencapture -m /tmp/main_screen.png # Capture primary screen only
# Specific window (interactive - click to select)
screencapture -w /tmp/window.png # Interactively select window to capture
# Specific region
screencapture -R 100,200,800,600 /tmp/region.png # Capture rectangular region (x,y,w,h)
# Without window shadow
screencapture -o /tmp/no_shadow.png # Capture without window shadows
# Silent (no camera sound)
screencapture -x /tmp/silent.png # Capture silently without shutter sound
# To clipboard instead of file
screencapture -c # Capture to clipboard
# Combined: silent, no shadow, specific region
screencapture -x -o -R 0,0,1920,1080 /tmp/clean.png # Capture region silently without shadows
```
### hs.screen (Hammerspoon)
```lua
-- Capture focused window
local win = hs.window.focusedWindow()
if win then
local img = win:snapshot()
img:saveToFile("/tmp/window.png")
end
-- Capture entire screen
local screen = hs.screen.mainScreen()
local img = screen:snapshot()
img:saveToFile("/tmp/screen.png")
-- Capture specific region
local img = hs.sRelated in Code Review
gstack
IncludedFast headless browser for QA testing and site dogfooding. Navigate pages, interact with elements, verify state, diff before/after, take annotated screenshots, test responsive layouts, forms, uploads, dialogs, and capture bug evidence. Use when asked to open or test a site, verify a deployment, dogfood a user flow, or file a bug with screenshots. (gstack)
startup-due-diligence
IncludedLegal due diligence review for seed-stage and Series A startups (US, Delaware C-Corp focus). Supports both investor and founder perspectives. Capabilities include: (1) Interactive document review and issue spotting; (2) Document request list generation; (3) Cap table and SAFE/convertible note analysis; (4) Red flag identification with severity ratings; (5) Diligence report generation. TRIGGERS: due diligence, DD, startup investment, cap table review, Series A, seed round, investor diligence, legal review startup, SAFE analysis, convertible note, 409A, founder vesting.
interview-master
IncludedThis skill should be used when the user asks to "generate interview questions", "prepare for interview", "optimize resume", "conduct mock interview", "analyze git commits for resume", "generate resume from code", "review my resume", or mentions interview preparation, career assistance, or extracting project experience from git history. Provides comprehensive interview and career development guidance for both job seekers and interviewers.
fix-issue
IncludedFixes GitHub issues using parallel analysis agents for root cause investigation, code exploration, and regression detection. Reads issue context from gh CLI, searches codebase and memory for related patterns, generates a fix with tests, and links the resolution back to the issue via PR. Includes prevention analysis to avoid recurrence. Use when debugging errors, resolving regressions, fixing bugs, or triaging issues.
sf-apex
IncludedGenerates and reviews Salesforce Apex code with 150-point scoring. TRIGGER when: user writes, reviews, or fixes Apex classes, triggers, test classes, batch/queueable/schedulable jobs, or touches .cls/.trigger files. DO NOT TRIGGER when: LWC JavaScript (use sf-lwc), Flow XML (use sf-flow), SOQL-only queries (use sf-soql), or non-Salesforce code.
swift-development
IncludedComprehensive Swift development for building, testing, and deploying iOS/macOS applications. Use when Claude needs to: (1) Build Swift packages or Xcode projects from command line, (2) Run tests with XCTest or Swift Testing framework, (3) Manage iOS simulators with simctl, (4) Handle code signing, provisioning profiles, and app distribution, (5) Format or lint Swift code with SwiftFormat/SwiftLint, (6) Work with Swift Package Manager (SPM), (7) Implement Swift 6 concurrency patterns (async/await, actors, Sendable), (8) Create SwiftUI views with MVVM architecture, (9) Set up Core Data or SwiftData persistence, or any other Swift/iOS/macOS development tasks.