Skip to content

IPC commands

Control Shoji from the command line through Hammerspoon’s IPC mechanism. Use it for shell scripts, keyboard shortcuts from other tools, and automation.

At a glance

  • Control Shoji from the shell via hs -c
  • Requires hs.ipc loaded in Hammerspoon config
  • Commands return success plus data or error
  • 14 commands: status, layout control, focus, swap, and more

Prerequisites

Load the hs.ipc module in Hammerspoon config:

-- ~/.hammerspoon/init.lua
require("hs.ipc")

This enables the hs CLI tool. If hs is not in PATH, run hs.ipc.cliInstall() in the Hammerspoon console.

Basic usage

Send commands via the hs CLI:

Terminal window
# Get current status
hs -c 'spoon.Shoji:cmd("status")'
# Cycle to next layout
hs -c 'spoon.Shoji:cmd("cycle-layout")'
# Set specific layout (varargs style)
hs -c 'spoon.Shoji:cmd("set-layout", "wide")'
# Set specific layout (array style - also supported)
hs -c 'spoon.Shoji:cmd("set-layout", {"wide"})'

Type aliases

These docs use the following IPC-specific type aliases.

---@alias IPCFocusDirection
---| "left"
---| "right"
---| "up"
---| "down"
---| "next"
---| "prev"
---@alias IPCSwapDirection
---| "next"
---| "prev"
---| "main"

Available commands

list-commands

Returns all available command names.

Terminal window
hs -c 'spoon.Shoji:cmd("list-commands")'

The response includes all commands: bundle-id, cycle-layout, focus, get-layout, list-commands, list-layouts, main-count, ratio, reset, retile, set-layout, status, swap, toggle-float.

status

Returns the current state of the focused space.

Terminal window
hs -c 'spoon.Shoji:cmd("status")'

Response:

{
success = true,
data = {
spaceID = 1,
layout = "tall",
mainRatio = 0.5,
mainCount = 1,
windowCount = 3
}
}

cycle-layout

Cycles to the next layout in the enabled layouts list.

Terminal window
hs -c 'spoon.Shoji:cmd("cycle-layout")'

Response:

{
success = true,
data = { cycled = true }
}

set-layout

Sets a specific layout by name.

Terminal window
hs -c 'spoon.Shoji:cmd("set-layout", "bsp")'

Arguments:

  • layoutName (required): Name of the layout to set

Response:

{
success = true,
data = { layout = "bsp" }
}

Error (unknown layout):

{
success = false,
error = "Unknown layout: nonexistent"
}

retile

Retiles the current space with current parameters. Preserves mainRatio, mainCount, and layout state. Useful for re-applying the layout after manual window moves.

Terminal window
hs -c 'spoon.Shoji:cmd("retile")'

Response:

{
success = true,
data = { retiled = true }
}

reset

Resets layout parameters (mainRatio, mainCount, layoutState) to defaults and retiles.

Terminal window
hs -c 'spoon.Shoji:cmd("reset")'

Response:

{
success = true,
data = { reset = true }
}

list-layouts

Returns all available layout names.

Terminal window
hs -c 'spoon.Shoji:cmd("list-layouts")'

get-layout

Returns the current layout for the focused space.

Terminal window
hs -c 'spoon.Shoji:cmd("get-layout")'

bundle-id

Returns the bundle ID of the focused window. Useful for finding bundle IDs to add to filter_apps.

Terminal window
hs -c 'spoon.Shoji:cmd("bundle-id")'

Response:

{
success = true,
data = { bundleID = "com.apple.Terminal" }
}

toggle-float

Toggles floating state for the focused window.

Terminal window
hs -c 'spoon.Shoji:cmd("toggle-float")'

focus

Focus window in a direction.

Terminal window
# Focus directions: left, right, up, down, next, prev
hs -c 'spoon.Shoji:cmd("focus", "left")'
hs -c 'spoon.Shoji:cmd("focus", "next")'

swap

Swap focused window with another.

Terminal window
# Swap directions: next, prev, main
hs -c 'spoon.Shoji:cmd("swap", "next")'
hs -c 'spoon.Shoji:cmd("swap", "main")'

ratio

Adjust the main area ratio.

Terminal window
# Increase ratio by 0.05
hs -c 'spoon.Shoji:cmd("ratio", "0.05")'
# Decrease ratio by 0.1
hs -c 'spoon.Shoji:cmd("ratio", "-0.1")'

The ratio is clamped between 0.1 and 0.9.

main-count

Adjust the number of main windows.

Terminal window
# Increase main-count by 1
hs -c 'spoon.Shoji:cmd("main-count", "1")'
# Decrease main-count by 1
hs -c 'spoon.Shoji:cmd("main-count", "-1")'

The main-count is clamped to a minimum of 1.

Response format

All commands return a table with:

---@class IPCSuccessResult
---@field success true -- Whether the command succeeded
---@field data table -- Result data (on success)
---@class IPCErrorResult
---@field success false -- Whether the command succeeded
---@field error string -- Error message (on failure)
---@alias IPCResult IPCSuccessResult|IPCErrorResult

Error handling

Commands fail gracefully with descriptive error messages. All error responses follow the same structure:

{ success = false, error = "error message" }

Error reference

CommandError conditionError message
(any)Shoji not started"Shoji is not started"
(any)Unknown command"Unknown command: <name>"
(any)Handler exception"Command failed: <message>"
statusNo focused space"Could not get focused space"
get-layoutNo focused space"Could not get focused space"
set-layoutMissing argument"Missing layout name argument"
set-layoutUnknown layout"Unknown layout: <name>"
set-layoutNo focused space"Could not get focused space"
retileNo focused space"Could not get focused space"
resetNo focused space"Could not get focused space"
focusMissing direction"Missing direction argument (left, right, up, down, next, prev)"
focusInvalid direction"Invalid direction: <dir> (use left, right, up, down, next, prev)"
swapMissing direction"Missing direction argument (next, prev, main)"
swapInvalid direction"Invalid direction: <dir> (use next, prev, main)"
ratioMissing argument"Missing delta argument (e.g., 0.05 or -0.05)"
ratioNon-numeric value"Invalid delta: <val> (must be a number)"
ratioNo focused space"Could not get focused space"
main-countMissing argument"Missing delta argument (e.g., 1 or -1)"
main-countNon-numeric value"Invalid delta: <val> (must be a number)"
main-countNo focused space"Could not get focused space"
bundle-idNo focused window"No focused window"
bundle-idNo bundle ID"Could not determine bundle ID for focused window"

Commands that operate on the focused window (focus, swap, toggle-float) succeed silently when there are no tiling windows — focus and swap actions no-op by design. The error responses above are for argument validation failures.

Scripting examples

Toggle between layouts

#!/bin/bash
CURRENT=$(hs -c 'return spoon.Shoji:cmd("status").data.layout')
if [ "$CURRENT" = "tall" ]; then
hs -c 'spoon.Shoji:cmd("set-layout", "wide")'
else
hs -c 'spoon.Shoji:cmd("set-layout", "tall")'
fi

Get window count

Terminal window
hs -c 'return spoon.Shoji:cmd("status").data.windowCount'

Conditional retile

Terminal window
# Only retile if using BSP layout
LAYOUT=$(hs -c 'return spoon.Shoji:cmd("status").data.layout')
if [ "$LAYOUT" = "bsp" ]; then
hs -c 'spoon.Shoji:cmd("retile")'
fi

tmux integration

Bind Shoji commands to tmux keys:

~/.tmux.conf
bind-key C-l run-shell "hs -c 'spoon.Shoji:cmd(\"cycle-layout\")'"
bind-key C-t run-shell "hs -c 'spoon.Shoji:cmd(\"set-layout\", \"tall\")'"

Lua API

Use the same commands from Lua:

-- In your Hammerspoon config
local result = spoon.Shoji:cmd("status")
if result.success then
print("Current layout: " .. result.data.layout)
end

Common mistakes

Forgetting require("hs.ipc") in init.lua: Without hs.ipc, the hs CLI tool cannot communicate with Hammerspoon. Add require("hs.ipc") before any Shoji configuration.

Using print() instead of return in hs -c: print() sends output to the Hammerspoon console, not to stdout. Use return to get output in the terminal: hs -c 'return spoon.Shoji:cmd("status").data.layout'.

Nested quote escaping in bash/tmux bindings: Shell and Lua quoting interact. In tmux run-shell, use backslash-escaped inner quotes: run-shell "hs -c 'spoon.Shoji:cmd(\"cycle-layout\")'"

Common errors

“Shoji is not started”: Called a command before start(). Ensure spoon.Shoji:start() runs before any IPC commands.

“Unknown command: : Typo in the command name. Run list-commands to see all available commands.

“Missing layout name argument”: set-layout requires a layout name. Pass it as a second argument: spoon.Shoji:cmd("set-layout", "tall").