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
Section titled “At a glance”- Control Shoji from the shell via
hs -c - Requires
hs.ipcloaded in Hammerspoon config - Commands return
successplusdataorerror - 14 commands: status, layout control, focus, swap, and more
Prerequisites
Section titled “Prerequisites”Load the hs.ipc module in Hammerspoon config:
-- ~/.hammerspoon/init.luarequire("hs.ipc")This enables the hs CLI tool. If hs is not in PATH, run
hs.ipc.cliInstall() in the Hammerspoon console.
Basic usage
Section titled “Basic usage”Send commands via the hs CLI:
# Get current statushs -c 'spoon.Shoji:cmd("status")'
# Cycle to next layouths -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
Section titled “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
Section titled “Available commands”list-commands
Section titled “list-commands”Returns all available command names.
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
Section titled “status”Returns the current state of the focused space.
hs -c 'spoon.Shoji:cmd("status")'Response:
{ success = true, data = { spaceID = 1, layout = "tall", mainRatio = 0.5, mainCount = 1, windowCount = 3 }}cycle-layout
Section titled “cycle-layout”Cycles to the next layout in the enabled layouts list.
hs -c 'spoon.Shoji:cmd("cycle-layout")'Response:
{ success = true, data = { cycled = true }}set-layout
Section titled “set-layout”Sets a specific layout by name.
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
Section titled “retile”Retiles the current space with current parameters. Preserves mainRatio,
mainCount, and layout state. Useful for re-applying the layout after manual
window moves.
hs -c 'spoon.Shoji:cmd("retile")'Response:
{ success = true, data = { retiled = true }}Resets layout parameters (mainRatio, mainCount, layoutState) to defaults
and retiles.
hs -c 'spoon.Shoji:cmd("reset")'Response:
{ success = true, data = { reset = true }}list-layouts
Section titled “list-layouts”Returns all available layout names.
hs -c 'spoon.Shoji:cmd("list-layouts")'get-layout
Section titled “get-layout”Returns the current layout for the focused space.
hs -c 'spoon.Shoji:cmd("get-layout")'bundle-id
Section titled “bundle-id”Returns the bundle ID of the focused window. Useful
for finding bundle IDs to add to filter_apps.
hs -c 'spoon.Shoji:cmd("bundle-id")'Response:
{ success = true, data = { bundleID = "com.apple.Terminal" }}toggle-float
Section titled “toggle-float”Toggles floating state for the focused window.
hs -c 'spoon.Shoji:cmd("toggle-float")'Focus window in a direction.
# Focus directions: left, right, up, down, next, prevhs -c 'spoon.Shoji:cmd("focus", "left")'hs -c 'spoon.Shoji:cmd("focus", "next")'Swap focused window with another.
# Swap directions: next, prev, mainhs -c 'spoon.Shoji:cmd("swap", "next")'hs -c 'spoon.Shoji:cmd("swap", "main")'Adjust the main area ratio.
# Increase ratio by 0.05hs -c 'spoon.Shoji:cmd("ratio", "0.05")'
# Decrease ratio by 0.1hs -c 'spoon.Shoji:cmd("ratio", "-0.1")'The ratio is clamped between 0.1 and 0.9.
main-count
Section titled “main-count”Adjust the number of main windows.
# Increase main-count by 1hs -c 'spoon.Shoji:cmd("main-count", "1")'
# Decrease main-count by 1hs -c 'spoon.Shoji:cmd("main-count", "-1")'The main-count is clamped to a minimum of 1.
Response format
Section titled “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|IPCErrorResultError handling
Section titled “Error handling”Commands fail gracefully with descriptive error messages. All error responses follow the same structure:
{ success = false, error = "error message" }Error reference
Section titled “Error reference”| Command | Error condition | Error message |
|---|---|---|
| (any) | Shoji not started | "Shoji is not started" |
| (any) | Unknown command | "Unknown command: <name>" |
| (any) | Handler exception | "Command failed: <message>" |
status |
No focused space | "Could not get focused space" |
get-layout |
No focused space | "Could not get focused space" |
set-layout |
Missing argument | "Missing layout name argument" |
set-layout |
Unknown layout | "Unknown layout: <name>" |
set-layout |
No focused space | "Could not get focused space" |
retile |
No focused space | "Could not get focused space" |
reset |
No focused space | "Could not get focused space" |
focus |
Missing direction | "Missing direction argument (left, right, up, down, next, prev)" |
focus |
Invalid direction | "Invalid direction: <dir> (use left, right, up, down, next, prev)" |
swap |
Missing direction | "Missing direction argument (next, prev, main)" |
swap |
Invalid direction | "Invalid direction: <dir> (use next, prev, main)" |
ratio |
Missing argument | "Missing delta argument (e.g., 0.05 or -0.05)" |
ratio |
Non-numeric value | "Invalid delta: <val> (must be a number)" |
ratio |
No focused space | "Could not get focused space" |
main-count |
Missing argument | "Missing delta argument (e.g., 1 or -1)" |
main-count |
Non-numeric value | "Invalid delta: <val> (must be a number)" |
main-count |
No focused space | "Could not get focused space" |
bundle-id |
No focused window | "No focused window" |
bundle-id |
No 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
Section titled “Scripting examples”Toggle between layouts
Section titled “Toggle between layouts”#!/bin/bashCURRENT=$(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")'fiGet window count
Section titled “Get window count”hs -c 'return spoon.Shoji:cmd("status").data.windowCount'Conditional retile
Section titled “Conditional retile”# Only retile if using BSP layoutLAYOUT=$(hs -c 'return spoon.Shoji:cmd("status").data.layout')if [ "$LAYOUT" = "bsp" ]; then hs -c 'spoon.Shoji:cmd("retile")'fitmux integration
Section titled “tmux integration”Bind Shoji commands to tmux keys:
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
Section titled “Lua API”Use the same commands from Lua:
-- In your Hammerspoon configlocal result = spoon.Shoji:cmd("status")if result.success then print("Current layout: " .. result.data.layout)endCommon mistakes
Section titled “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
Section titled “Common errors”“Shoji is not started”: Called a command before
start(). Ensure spoon.Shoji:start() runs before any
IPC commands.
“Unknown command: 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").