Sessionizer is a Herdr plugin that uses fuzzy pickers to open projects and Git worktrees into configured workspaces.
- Sessionizer — focus an existing workspace or create a new project workspace
- Worktree — create or reopen a Git worktree workspace
Platform: macOS only for now. Tested on macOS; Linux support is planned.
Inspired by ThePrimeagen's tmux-sessionizer: fuzzy-find a project, land in the right dev environment — but for Herdr workspaces instead of tmux sessions.
| tmux-sessionizer | Sessionizer |
|---|---|
fzf over project roots |
fzf over projects.roots |
| tmux session | Herdr workspace |
| tmux windows/panes | Sessionizer tab/pane layout |
| tmux-only | Herdr-native + worktree picker |
Sessionizer does not install system tools for you.
- macOS (Linux planned; not validated yet)
- Herdr
>= 0.7.0 - Bun — plugin build and runtime
- fzf — interactive pickers
curl -fsSL https://bun.com/install | bash
brew install fzfOptional: bat for richer README.md previews (brew install bat).
herdr plugin install andrewchng/herdr-sessionizer --yes
herdr plugin config-dir sessionizerWire keybindings in your Herdr config (see Example keybindings).
bun install
herdr plugin link /path/to/herdr-sessionizerAfter manifest or pane/action changes:
herdr plugin unlink sessionizer || true
herdr plugin link /path/to/herdr-sessionizer| Flow | Action |
|---|---|
| Project picker | sessionizer.open |
| Worktree picker | sessionizer.worktree-open |
herdr plugin action invoke sessionizer.open
herdr plugin action invoke sessionizer.worktree-openUX flow:
Sessionizer: existing workspace picker ──Enter──> focus
existing workspace picker ──Esc────> project picker ──Enter──> create workspace + layout
Worktree: project picker ──Enter──> branch/worktree picker ──Enter──> open existing or create worktree + layout
project picker ──Enter──> branch/worktree picker ──Esc────> new branch prompt
project picker ──Enter──> no candidates ─────────> new branch prompt
Add these to your Herdr config, for example:
~/.config/herdr/config.toml
[[keys.command]]
key = "prefix+f"
type = "plugin_action"
command = "sessionizer.open"
description = "open project workspace"
[[keys.command]]
key = "prefix+up"
type = "plugin_action"
command = "sessionizer.worktree-open"
description = "open worktree workspace"Sessionizer lists existing workspaces plus repos under projects.roots. Pick a workspace to focus it, or pick a project to create a new workspace with your configured layout.
Worktree lists base repos under projects.roots, then shows a branch/worktree picker with previews when there are existing choices:
| Selection | Result |
|---|---|
| Existing workspace/checkout | Reopen as-is |
| Local branch | Create a worktree workspace for that branch |
| Remote branch | Create a local worktree from that remote branch |
| Esc / no choices | Prompt for a new branch, then create the worktree |
When Sessionizer creates a new project or worktree workspace, it applies the layout from config.toml. Existing workspaces are only focused — the layout is not reapplied.
~/.config/herdr/plugins/config/sessionizer/config.toml
Created automatically on first run if missing. It controls:
[projects]— parent folders thefzfpickers scan for repos[layout],[tabs.*]+[[tabs.*.panes]]— the tabs, splits, per-split ratios, commands, and final focus for newly created workspaces
If you want an agent to help edit either the global config or a repo-local override, see Agent skill.
[projects]
roots = ["~/Projects", "~/Workspace"]
[layout]
placement = "overlay"
focus = "editor"
[tabs.dev]
label = "dev"
[[tabs.dev.panes]]
id = "editor"
title = "nvim"
command = "nvim"
[[tabs.dev.panes]]
id = "agent"
from = "editor"
title = "agent"
split = "right"
ratio = 0.3
command = "opencode"
[[tabs.dev.panes]]
id = "git"
from = "editor"
title = "lazygit"
split = "down"
command = "lazygit"
[tabs.server]
label = "server"
[[tabs.server.panes]]
id = "server"
title = "server"
command = "npm run dev"First tab shape:
dev
┌────────────────┬───────┐
│ │ agent │
│ nvim │ │
├────────────────┤ │
│ lazygit │ │
└────────────────┴───────┘
These diagrams show pane titles, not commands. Here, ratio = 0.3 gives the new right-side agent pane 30% of the split width, leaving the editor side with the remaining 70%.
Second tab shape:
server
┌──────────────┐
│ │
│ server │
│ │
└──────────────┘
[projects].roots— parent folders scanned by both pickers[layout].placement— how plugin panes open (overlayorsplit)[layout].focus— which tab or pane to focus after layout bootstrap[tabs.<name>]— one Herdr tab to create per section[[tabs.<name>.panes]]— panes inside the tab;from+split(rightordown) define the split treeratio— optional share for the newly created pane on the split axiscommand— exact command a pane runs (nvim,pi,claude,opencode, etc.)
Rules for ratio:
- only split-created panes may set it; the first/root pane in a tab cannot
- it must be a number greater than
0and less than1 - it is local to that split at creation time, not a percentage of the whole tab
- if omitted, Herdr's default split sizing is used
- it applies only when the workspace is first bootstrapped, never when an existing workspace is reopened
If you launch a worktree with --command, exactly one pane in that layout must opt in with accept_command_override = true. The generated default config leaves this off until you choose which pane should receive the raw command.
A repository can override the layout for new workspace bootstrap. Put a repo-local layout config at:
<project>/.sessionizer/config.toml
When Sessionizer or Worktree creates a new workspace at cwd, Sessionizer checks in this order:
<cwd>/.sessionizer/config.toml— if present, use its[layout].focusand[tabs.*](full replacement; no merge with global tabs)- Global
config.toml— default layout
[projects].roots and [layout].placement always come from the global config. Repo-local files may include those sections, but they are ignored. Invalid repo-local config fails with an error that names the file path.
| Event | Layout source |
|---|---|
| Sessionizer creates a new project workspace | Repo override at picked cwd, else global default |
| Worktree creates a new workspace | Repo override at checkout cwd, else global default |
| Focus or reopen an existing workspace | No relayout |
A docs repo might skip the global nvim + agent + lazygit layout and open lazygit with an agent instead:
# my-docs-repo/.sessionizer/config.toml
[layout]
focus = "docs"
[tabs.docs]
label = "docs"
[[tabs.docs.panes]]
id = "git"
title = "lazygit"
command = "lazygit"
[[tabs.docs.panes]]
id = "agent"
from = "git"
title = "agent"
split = "right"
ratio = 0.3
command = "pi" docs
┌────────────────┬───────┐
│ │ agent │
│ lazygit │ │
│ │ │
└────────────────┴───────┘
Check .sessionizer/config.toml into the repo if you want the layout to travel with the project. Repos without it keep the global default.
This repo also ships a sessionizer-layout-editor skill for agents that support the skills ecosystem. It helps agents update:
- global Sessionizer config
projects.roots- repo-local
.sessionizer/config.tomloverrides
Install it from this repo:
npx skills add andrewchng/herdr-sessionizer --skill sessionizer-layout-editorList available skills in this repo:
npx skills add andrewchng/herdr-sessionizer --listExample requests:
- "Add
~/Workto my Sessionizer project roots" - "Create a repo-local override for this repo with
lazygiton the left andcopiloton the right" - "Update my global Sessionizer layout to focus the git pane"
See CHANGELOG.md for release history.
bun run typecheck
bun run test
bun run release -- 0.2.1 --dry-run
bun run release:tag -- 0.2.1 --dry-run
bun run release:notes -- 0.2.1
bun run sessionizerUse bun run release -- <version> on the release-prep branch to update version files, then run bun run release:tag -- <version> from merged main to create and push the annotated v<version> release tag.
