Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,9 +386,30 @@ Aliases
| `claude-yolo` | `claude --dangerously-skip-permissions` | Claude without prompts |
| `opencode-yolo` | `opencode --dangerously-skip-permissions` | OpenCode without prompts |

### Multiplexer Keybindings (Experimental)

Both tmux and zellij ship with Omarchy-inspired defaults and matching keybindings:
### Shared Keyboard Language (Experimental)

Squarebox builds on Omarchy's defaults with one portable rule: `Super` belongs
to the OS/window manager, while `Ctrl` acts immediately in the focused app.
Adding `Shift` modifies or moves the target. Herdr follows this language
directly; its `F12` prefix remains available as a compatibility fallback.

Herdr's generated default uses familiar app shortcuts:

| Feature | Herdr |
|---------|-------|
| New / close | `Ctrl+T` / `Ctrl+W` |
| Cycle / select tabs | `Ctrl+Tab`, `Ctrl+Shift+Tab` / `Ctrl+1-9` |
| Focus / swap pane | `Ctrl+Arrow` / `Ctrl+Shift+Arrow` |
| Sidebar / go to | `Ctrl+B` / `Ctrl+G` |
| Split right / down | `Ctrl+\\` / `Ctrl+Shift+\\` |
| Zoom / new workspace | `Ctrl+Shift+Z` / `Ctrl+Shift+N` |
| Compatibility prefix | `F12` |

These bindings intentionally take precedence over matching shell or nested-app
shortcuts while Herdr is focused. Existing `~/.config/herdr/config.toml` files
remain user-managed and are never replaced by setup.

Tmux and Zellij retain their earlier Omarchy-inspired matching defaults:

| Feature | Tmux | Zellij |
|---------|------|--------|
Expand Down
65 changes: 63 additions & 2 deletions setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1760,13 +1760,74 @@ install_zellij() {
}

_install_herdr_inner() {
local config_path="$HOME/.config/herdr/config.toml"
command -v herdr >/dev/null 2>&1 || sb_install herdr latest || return 1
command -v herdr >/dev/null 2>&1 || return 1
mkdir -p "$HOME/.config/herdr" || return 1
if [ -e "$config_path" ] && [ ! -L "$config_path" ] && [ ! -f "$config_path" ]; then
echo "ERROR: Herdr config path is not a regular file: $config_path" >&2
return 1
fi
if [ ! -e "$config_path" ] && [ ! -L "$config_path" ]; then
if ! (
config_tmp=""
cleanup_herdr_stage() {
[ -z "$config_tmp" ] || rm -f -- "$config_tmp"
}
trap cleanup_herdr_stage EXIT
trap 'exit 1' HUP INT TERM
config_tmp=$(mktemp "$HOME/.config/herdr/.config.toml.squarebox-tmp.XXXXXX") || exit 1
cat > "$config_tmp" <<-'HERDRCONF' || exit 1
[ui]
agent_panel_sort = "spaces"

# Super belongs to the OS/window manager. Ctrl acts immediately in Herdr;
# Shift modifies or moves. F12 keeps the upstream-style prefix fallbacks.
[keys]
prefix = "f12"
help = ["ctrl+?", "prefix+?"]
goto = ["ctrl+g", "prefix+g"]
workspace_picker = "prefix+w"
toggle_sidebar = ["ctrl+b", "prefix+b"]
detach = ["ctrl+shift+q", "prefix+q"]

new_tab = ["ctrl+t", "prefix+c"]
previous_tab = ["ctrl+shift+tab", "prefix+p"]
next_tab = ["ctrl+tab", "prefix+n"]
switch_tab = ["ctrl+1..9", "prefix+1..9"]
rename_tab = "prefix+shift+t"
close_tab = "prefix+shift+x"

new_workspace = ["ctrl+shift+n", "prefix+shift+n"]
rename_workspace = "prefix+shift+w"
close_workspace = "prefix+shift+d"

focus_pane_left = ["ctrl+left", "prefix+h"]
focus_pane_down = ["ctrl+down", "prefix+j"]
focus_pane_up = ["ctrl+up", "prefix+k"]
focus_pane_right = ["ctrl+right", "prefix+l"]
swap_pane_left = ["ctrl+shift+left", "prefix+shift+h"]
swap_pane_down = ["ctrl+shift+down", "prefix+shift+j"]
swap_pane_up = ["ctrl+shift+up", "prefix+shift+k"]
swap_pane_right = ["ctrl+shift+right", "prefix+shift+l"]

close_pane = ["ctrl+w", "prefix+x"]
split_vertical = ["ctrl+backslash", "prefix+v"]
split_horizontal = ["ctrl+shift+backslash", "prefix+minus"]
zoom = ["ctrl+shift+z", "prefix+z"]
resize_mode = "prefix+r"
HERDRCONF
mv -fT -- "$config_tmp" "$config_path" || exit 1
config_tmp=""
); then
return 1
fi
fi
}

install_herdr() {
if command -v herdr &>/dev/null; then
echo "Herdr already installed, skipping."
if command -v herdr &>/dev/null && [ -f "$HOME/.config/herdr/config.toml" ]; then
echo "Herdr already installed and configured, skipping."
return 0
fi
run_with_spinner "Installing Herdr..." _install_herdr_inner
Expand Down
31 changes: 31 additions & 0 deletions tests/test-provision-state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,37 @@ assert_true "[ -L '$ZELLIJ_CONF' ] && grep -Fqx 'on_force_close \"quit\"' '$ZELL
"Zellij migration does not rewrite a symlinked config"
rm -f "$ZELLIJ_CONF"

# Herdr uses the shared immediate-app keyboard language for a fresh Managed
# home, while an existing config remains user-controlled.
printf '#!/usr/bin/env bash\nexit 0\n' > "$BIN/herdr"
chmod +x "$BIN/herdr"
printf 'herdr\n' > "$STATE/multiplexer"
rm -rf "$HOME_DIR/.config/herdr"
HOME="$HOME_DIR" SQUAREBOX_STATE_DIR="$STATE" \
SQUAREBOX_TOOL_LIB="$FIXTURE_LIB" SQUAREBOX_TOOLS_YAML=/dev/null \
PATH="$BIN:$PATH" bash "$ROOT/setup.sh" --rerun multiplexers >"$TMP/herdr-fresh.out" 2>"$TMP/herdr-fresh.err"
HERDR_CONF="$HOME_DIR/.config/herdr/config.toml"
assert_true "grep -Fqx 'prefix = \"f12\"' '$HERDR_CONF' && grep -Fqx 'new_tab = [\"ctrl+t\", \"prefix+c\"]' '$HERDR_CONF' && grep -Fqx 'focus_pane_left = [\"ctrl+left\", \"prefix+h\"]' '$HERDR_CONF'" \
"fresh Herdr config uses immediate Ctrl actions with F12 fallbacks"
printf '# user-owned Herdr config\n' > "$HERDR_CONF"
HOME="$HOME_DIR" SQUAREBOX_STATE_DIR="$STATE" \
SQUAREBOX_TOOL_LIB="$FIXTURE_LIB" SQUAREBOX_TOOLS_YAML=/dev/null \
PATH="$BIN:$PATH" bash "$ROOT/setup.sh" --rerun multiplexers >"$TMP/herdr-user.out" 2>"$TMP/herdr-user.err"
assert_true "grep -Fqx '# user-owned Herdr config' '$HERDR_CONF'" \
"Herdr setup preserves an existing user config"

rm -rf "$HERDR_CONF"
mkdir -p "$HERDR_CONF"
if HOME="$HOME_DIR" SQUAREBOX_STATE_DIR="$STATE" \
SQUAREBOX_TOOL_LIB="$FIXTURE_LIB" SQUAREBOX_TOOLS_YAML=/dev/null \
PATH="$BIN:$PATH" bash "$ROOT/setup.sh" --rerun multiplexers >"$TMP/herdr-directory.out" 2>"$TMP/herdr-directory.err"; then
HERDR_DIRECTORY_RC=0
else
HERDR_DIRECTORY_RC=$?
fi
assert_true "[ \"$HERDR_DIRECTORY_RC\" -ne 0 ] && [ -d '$HERDR_CONF' ] && grep -Fq 'Herdr config path is not a regular file' '$TMP/herdr-directory.err'" \
"Herdr rejects a directory at the config path"

# A gum cancellation and a confirmed empty multi-select are different state
# transitions: cancel preserves prior files; empty intentionally clears them.
if command -v script >/dev/null 2>&1; then
Expand Down