Skip to content

Scripting

Automate Shoji through IPC and hooks. External scripts can query and control window layouts.

Prerequisites

Enable IPC in the Hammerspoon config:

require("hs.ipc")

This enables the hs command-line tool. Verify:

Terminal window
hs -c 'return "hello"'
# Should output: hello

Shell script examples

Cycle through layouts

#!/bin/bash
# Cycle: tall -> wide -> bsp -> tall
CURRENT=$(hs -c 'return spoon.Shoji:cmd("status").data.layout')
case "$CURRENT" in
"tall") hs -c 'spoon.Shoji:cmd("set-layout", "wide")' ;;
"wide") hs -c 'spoon.Shoji:cmd("set-layout", "bsp")' ;;
*) hs -c 'spoon.Shoji:cmd("set-layout", "tall")' ;;
esac

Context-aware layout

Adjust layout based on window count:

#!/bin/bash
COUNT=$(hs -c 'return spoon.Shoji:cmd("status").data.windowCount')
if [ "$COUNT" -le 1 ]; then
hs -c 'spoon.Shoji:cmd("set-layout", "fullscreen")'
elif [ "$COUNT" -le 3 ]; then
hs -c 'spoon.Shoji:cmd("set-layout", "tall")'
else
hs -c 'spoon.Shoji:cmd("set-layout", "bsp")'
fi

Status bar integration

For tmux, sketchybar, or similar status bars:

#!/bin/bash
# Output format: [tall 3]
LAYOUT=$(hs -c 'return spoon.Shoji:cmd("status").data.layout')
COUNT=$(hs -c 'return spoon.Shoji:cmd("status").data.windowCount')
echo "[$LAYOUT $COUNT]"

tmux integration

Bind Shoji commands to tmux keys:

~/.tmux.conf
bind-key W run-shell "hs -c 'spoon.Shoji:cmd(\"cycle-layout\")'"
bind-key T run-shell "hs -c 'spoon.Shoji:cmd(\"set-layout\", \"tall\")'"
bind-key B run-shell "hs -c 'spoon.Shoji:cmd(\"set-layout\", \"bsp\")'"

Alfred and Raycast integration

Scripts for launcher workflows:

~/scripts/shoji-tall.sh
#!/bin/bash
hs -c 'spoon.Shoji:cmd("set-layout", "tall")'

In Alfred, create a workflow with a keyword trigger. In Raycast, create a Script Command.

Hook-based automation

Auto-layout by app

Switch layouts based on the focused app:

local appLayouts = {
["com.apple.Safari"] = "wide",
["com.microsoft.VSCode"] = "tall",
["com.apple.Terminal"] = "bsp",
}
spoon.Shoji:configure({
hooks = {
window_focused = function(windowID)
local win = hs.window.get(windowID)
if not win then return end
local app = win:application()
if not app then return end
local layout = appLayouts[app:bundleID()]
if layout then
spoon.Shoji.tiling.setLayout(layout)
end
end,
},
})

Time-based layout

Set the default layout based on time of day. Work hours get a focused layout; evenings get fullscreen:

local function timeBasedLayout()
local hour = tonumber(os.date("%H"))
if hour >= 9 and hour < 17 then
return "tall"
else
return "fullscreen"
end
end
spoon.Shoji:configure({
default_layout = timeBasedLayout(),
})

Window count trigger

Switch layouts based on tiled window count:

local lastCount = 0
spoon.Shoji:configure({
hooks = {
after_tile = function(_spaceID)
local status = spoon.Shoji:cmd("status")
if not status.success then return end
local count = status.data.windowCount
if count ~= lastCount then
lastCount = count
-- Auto-switch layout based on window count
if count == 1 then
spoon.Shoji.tiling.setLayout("fullscreen")
elseif count <= 3 then
spoon.Shoji.tiling.setLayout("tall")
else
spoon.Shoji.tiling.setLayout("bsp")
end
end
end,
},
})

Debugging scripts

Test IPC commands in the Hammerspoon console (Cmd+Option+C):

-- In the console:
spoon.Shoji:cmd("status")
spoon.Shoji:cmd("list-commands")

Or from the shell:

Terminal window
# Get current status
hs -c 'return hs.inspect(spoon.Shoji:cmd("status"))'
# List all available commands
hs -c 'return hs.inspect(spoon.Shoji:cmd("list-commands"))'

Hook errors appear in the Hammerspoon console.

Cron-style automation

Use launchd to run Shoji commands on a schedule. This example switches to tall at 9 AM daily:

~/Library/LaunchAgents/com.user.shoji-morning.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.shoji-morning</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/hs</string>
<string>-c</string>
<string>spoon.Shoji:cmd("set-layout", "tall")</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>9</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
</dict>
</plist>

Load the job:

Terminal window
launchctl load ~/Library/LaunchAgents/com.user.shoji-morning.plist

The hs path depends on installation method. On Apple Silicon with Homebrew, use /opt/homebrew/bin/hs instead of /usr/local/bin/hs.