Sesh, the smart tmux session manager

tests goreport

[English](README.md) | [简体中文](README.zh-cn.md)
Sesh is a CLI that helps you create and manage tmux sessions quickly and easily using zoxide. - **Smart session creation** - automatically names sessions based on git repo, git remote, or directory - **Zoxide integration** - jump to your most-used projects instantly - **Session configuration** - define startup commands, windows, and preview commands per project in `sesh.toml` - **Wildcard configs** - apply settings to all projects matching a glob pattern - **Built-in picker** - interactive session selector, or integrate with fzf, television, or gum - **Clone and connect** - clone a git repo and start a session in one step - **Mkdir and connect** - create a new directory (relative or absolute path) and start a session in one step - **Last session switching** - seamlessly bounce between your two most recent sessions - **Root session navigation** - jump to the root of a git worktree or repository - **Nerd Font icons** - display session type icons in your picker - **Shell completions** - tab completion for Bash, Zsh, Fish, and PowerShell > [!NOTE] > ⭐ If you find sesh useful, please star the repo — it helps the project grow and reach more developers! > [!TIP] > Want to see more features and help sesh mature? Consider [sponsoring the project](https://github.com/sponsors/joshmedeski) to support ongoing development. ## Table of Contents
- [Videos](#videos) - [How to install](#how-to-install) - [Shell Completion](#shell-completion) - [Extensions](#extensions) - [How to use](#how-to-use) - [Recommended tmux Settings](#recommended-tmux-settings) - [Bonus](#bonus) - [Configuration](#configuration) - [Contributing](#contributing) - [Background (the "t" script)](#background-the-t-script) - [Contributors](#contributors) - [Star History](#star-history)
## Videos
Smart tmux sessions with sesh

Intro from Josh Medeski (sesh's creator)

DevOps Toolbox - sesh

Review from DevOps Toolbox

## How to install
Homebrew To install sesh, run the following [homebrew](https://brew.sh/) command: ```sh brew install sesh ```
Arch Linux AUR To install sesh, run the following [yay](https://aur.archlinux.org/packages/yay) command: ```sh yay -S sesh-bin ```
Go Alternatively, you can install Sesh using Go's go install command: ```sh go install github.com/joshmedeski/sesh/v2@latest ``` This will download and install the latest version of Sesh. Make sure that your Go environment is properly set up.
Conda To install sesh, run **one** of the following commands, depending on your setup: - Conda/(micro)mamba users ```sh # Replace with mamba/micromamba if required conda -c conda-forge install sesh ``` - Pixi users ```sh pixi global install sesh ```
Nix See the [nix package directory](https://search.nixos.org/packages?channel=unstable&show=sesh&from=0&size=50&sort=relevance&type=packages&query=sesh) for instructions on how to install sesh through the nix platform.
**Note:** Do you want this on another package manager? [Create an issue](https://github.com/joshmedeski/sesh/issues/new) and let me know! ## Shell Completion Sesh supports shell completion (tab completion) for Bash, Zsh, Fish, and PowerShell. This helps you discover commands, flags, and arguments by pressing the Tab key.
Bash ```sh # Generate completion script sesh completion bash > sesh-completion.bash # Install system-wide (recommended) sudo cp sesh-completion.bash /etc/bash_completion.d/ # Or install user-only mkdir -p ~/.local/share/bash-completion/completions cp sesh-completion.bash ~/.local/share/bash-completion/completions/sesh # Reload your shell source ~/.bashrc ```
Zsh ```sh # Generate completion script sesh completion zsh > _sesh # Install system-wide (recommended) sudo mkdir -p /usr/local/share/zsh/site-functions sudo cp _sesh /usr/local/share/zsh/site-functions/ # Or install user-only mkdir -p ~/.zsh/completions cp _sesh ~/.zsh/completions/ echo 'fpath=(~/.zsh/completions $fpath)' >> ~/.zshrc echo 'autoload -U compinit && compinit' >> ~/.zshrc # Reload your shell source ~/.zshrc ```
Fish ```sh # Generate and install completion sesh completion fish > ~/.config/fish/completions/sesh.fish # Reload fish configuration source ~/.config/fish/config.fish ```
PowerShell ```powershell # Generate completion script sesh completion powershell > sesh.ps1 # Create PowerShell profile directory if it doesn't exist mkdir -p (Split-Path $PROFILE) # Add to PowerShell profile Add-Content $PROFILE ". /path/to/sesh.ps1" # Reload PowerShell & $PROFILE ```
After setting up completion, you can press Tab while typing `sesh` to see available commands, flags, and arguments. ## Extensions ## Raycast Extension The [sesh companion extension](https://www.raycast.com/joshmedeski/sesh) for [Raycast](https://www.raycast.com/) makes it easy to use sesh outside of the terminal. Here are limitations to keep in mind: - tmux has to be running before you can use the extension - The extension caches results for a few seconds, so it may not always be up to date ## Ulauncher Extension For Linux users using [Ulauncher](https://ulauncher.io/) there are two extensions to use sesh outside the terminal: - [Sesh Session Manager](https://ext.ulauncher.io/-/github-jacostag-sesh-ulauncher) - [SESHion Manager](https://ext.ulauncher.io/-/github-mrinfinidy-seshion-manager) Here are limitations to keep in mind for Sesh Session Manager: - tmux has to be running before you can use the extension ## Walker launcher usage (Linux) Create an action directly on $XDG_CONFIG_HOME/config.toml ``` [[plugins]] name = "sesh" prefix = ";s " src_once = "sesh list -d -c -t -T" cmd = "sesh connect --switch %RESULT%" keep_sort = false recalculate_score = true show_icon_when_single = true switcher_only = true ``` ### For the dmenu mode you can use: #### Fish shell: set ssession $(sesh l -t -T -d -H | walker -d -f -k -p "Sesh sessions"); sesh cn --switch $ssession #### Bash/Zsh: ssession=$(sesh l -t -T -d -H | walker -d -f -k -p "Sesh sessions"); sesh cn --switch $ssession ##### For dmenu launchers replace walker -dfk with dmenu or rofi) ### How to use ### tmux for sessions [tmux](https://github.com/tmux/tmux) is a powerful terminal multiplexer that allows you to create and manage multiple terminal sessions. Sesh is designed to make managing tmux sessions easier. ### zoxide for directories [zoxide](https://github.com/ajeetdsouza/zoxide) is a blazing fast alternative to `cd` that tracks your most used directories. Sesh uses zoxide to manage your projects. You'll have to set up zoxide first, but once you do, you can use it to quickly jump to your most used directories. ### Basic usage Once tmux and zoxide are setup, `sesh list` will list all your tmux sessions and zoxide results, and `sesh connect {session}` will connect to a session (automatically creating it if it doesn't exist yet). It is best used by integrating it into your shell and tmux. #### fzf The easiest way to integrate sesh into your workflow is to use [fzf](https://github.com/junegunn/fzf). You can use it to select a session to connect to: ```sh sesh connect $(sesh list | fzf) ``` #### tmux + fzf In order to integrate with tmux, you can add a binding to your tmux config (`tmux.conf`). For example, the following will bind `ctrl-a T` to open a fzf prompt as a tmux popup (using `fzf-tmux`) and using different commands to list active sessions (`sesh list -t`), configured sessions (`sesh list -c`), zoxide directories (`sesh list -z`), and find directories (`fd...`). ```sh bind-key "T" run-shell "sesh connect \"$( sesh list --icons | fzf-tmux -p 80%,70% \ --no-sort --ansi --border-label ' sesh ' --prompt '⚡ ' \ --header ' ^a all ^t tmux ^g configs ^x zoxide ^d tmux kill ^f find' \ --bind 'tab:down,btab:up' \ --bind 'ctrl-a:change-prompt(⚡ )+reload(sesh list --icons)' \ --bind 'ctrl-t:change-prompt(🪟 )+reload(sesh list -t --icons)' \ --bind 'ctrl-g:change-prompt(⚙️ )+reload(sesh list -c --icons)' \ --bind 'ctrl-x:change-prompt(📁 )+reload(sesh list -z --icons)' \ --bind 'ctrl-f:change-prompt(🔎 )+reload(fd -H -d 2 -t d -E .Trash . ~)' \ --bind 'ctrl-d:execute(tmux kill-session -t {2..})+change-prompt(⚡ )+reload(sesh list --icons)' \ --preview-window 'right:55%' \ --preview 'sesh preview {}' )\"" ``` You can customize this however you want, see `man fzf` for more info on the different options. #### tmux + [television](https://github.com/alexpasmantier/television) If you prefer to use television instead of fzf, you can add a binding to your tmux config that opens the [sesh channel](https://alexpasmantier.github.io/television/community/channels-unix/#sesh) in a tmux popup. ```sh bind-key "T" display-popup -E -w 80% -h 70% -d '#{pane_current_path}' -T 'Sesh' tv sesh ``` Use `Ctrl-s` to cycle through the sources, and `Ctrl-d` to kill the highlighted session. ### Window management `sesh window` (alias `w`) lets you list, switch to, and create tmux windows within a session — similar to how `sesh list` and `sesh connect` work for sessions. #### List windows in the current session ```sh sesh window ``` #### Switch to an existing window by name ```sh sesh window editor ``` If a window named `editor` exists in the current session, sesh will switch to it. #### Create a new window at a directory ```sh sesh window ~/projects/my-app ``` If no window with that name exists, sesh will create a new window named after the directory (`my-app`) with its working directory set to the given path. #### Target a specific session Use `--session` / `-s` to manage windows in a session other than the one you're currently attached to: ```sh sesh window --session work sesh window ~/projects/my-app --session work ``` #### fzf integration You can combine `sesh window` with fzf to interactively switch windows: ```sh sesh window $(sesh window | fzf) ``` Or as a tmux keybind: ```sh bind-key "W" run-shell "sesh window \"$(sesh window | fzf-tmux -p 60%,50% --prompt '🪟 ')\"" ``` ### Create a directory and connect `sesh mkdir` (alias `md`) combines `mkdir` and `sesh connect` into a single step: it creates the directory if it doesn't already exist, then connects to it as a session — no need to `cd` into it first or wait for zoxide to pick it up. ```sh sesh mkdir my-new-project ``` `` accepts both **relative** paths (resolved against your current working directory) and **absolute** paths, as well as `~` for your home directory: ```sh sesh mkdir my-new-project # relative to the current directory sesh mkdir ~/projects/my-app # relative to your home directory sesh mkdir /Users/josh/dev/api # absolute path ``` ## gum + tmux If you prefer to use [charmbracelet's gum](https://github.com/charmbracelet/gum) then you can use the following command to connect to a session: ```sh bind-key "K" display-popup -E -w 40% "sesh connect \"$( sesh list -i | gum filter --limit 1 --no-sort --fuzzy --placeholder 'Pick a sesh' --height 50 --prompt='⚡' )\"" ``` **Note:** There are less features available with gum compared to fzf, but I found its matching algorithm is faster and it has a more modern feel. > [!WARNING] > As of [gum v0.15.0](https://github.com/charmbracelet/gum/releases/tag/v0.15.0) you have to add the `--no-strip-ansi` in order to display the icons correctly. See my video, [Top 4 Fuzzy CLIs](https://www.youtube.com/watch?v=T0O2qrOhauY) for more inspiration for tooling that can be integrated with sesh. #### sesh picker Sesh has a built-in picker that can be used as a tmux popup: ```sh bind-key "K" display-popup -h 90% -w 50% -E "sesh picker -i" ``` ## zsh keybind If you use zsh, you can add the following keybind to your `.zshrc` to connect to a session: ```sh function sesh-sessions() { { exec /dev/null 2>&1 || true [[ -z "$session" ]] && return sesh connect $session } } zle -N sesh-sessions bindkey -M emacs '\es' sesh-sessions bindkey -M vicmd '\es' sesh-sessions bindkey -M viins '\es' sesh-sessions ``` After adding this to your `.zshrc`, you can press `Alt-s` to open a fzf prompt to connect to a session. ## Recommended tmux Settings I recommend you add these settings to your `tmux.conf` to have a better experience with this plugin. ```sh bind-key x kill-pane # skip "kill-pane 1? (y/n)" prompt set -g detach-on-destroy off # don't exit from tmux when closing a session ``` ## Bonus ### Last The default `+L` command will "Switch the attached client back to the last session." However, if you close a session while `detach-on-destroy off` is set, the last session will not be found. To fix this, I have a `sesh last` command that will always switch the client to the second to last session that has been attached. Add the following to your `tmux.conf` to overwrite the default `last-session` command: ```sh bind -N "last-session (via sesh) " L run-shell "sesh last" ``` ### Enrich session names with the GitHub issue title `sesh` can rename a session to include its branch's GitHub issue title, e.g. `400-status` → `400-status — warm the status cache`. It parses the issue number from the branch name and looks it up with the `gh` CLI (which must be installed and authenticated). Add an opt-in tmux hook so every new session is enriched in the background: ```tmux set-hook -g session-created 'run-shell -b "sesh rename --enrich"' ``` The command is a no-op when the branch has no resolvable issue (the session keeps its plain name), and it self-heals: switching to a branch without an issue renames the session back to its base name. Reconnecting to the directory reattaches to the enriched session rather than creating a duplicate. ### Connect to root While working in a nested session, you may way to connect to the root session of a git worktree or git repository. To do this, you can use the `--root` flag with the `sesh connect` command. I recommend adding this to your `tmux.conf`: ```sh bind -N "switch to root session (via sesh) " 9 run-shell "sesh connect --root $(pwd)" ``` ### Filter by root If you want to filter your search by the root of the active project, you can modify your piker by using the `sesh root` command: ```sh bind-key "R" display-popup -E -w 40% "sesh connect \"$( sesh list -i -H | gum filter --value \"$(sesh root)\" --limit 1 --fuzzy --no-sort --placeholder 'Pick a sesh' --prompt='⚡'readme )\"" ``` I have this bound to `+R` so I can use an alternative binding. **Note:** This will only work if you are in a git worktree or git repository. For now, git worktrees expect a `.bare` folder. ## Configuration You can configure sesh by creating a `sesh.toml` file in your `$XDG_CONFIG_HOME/sesh` or `$HOME/.config/sesh` directory. ```sh mkdir -p ~/.config/sesh && touch ~/.config/sesh/sesh.toml ``` ### Custom Config Path You can specify a custom config file path using the `--config` (or `-C`) flag. This is useful for NixOS wrappers, maintaining separate work/private configurations, or testing. ```sh sesh -C /path/to/custom/sesh.toml list sesh --config /path/to/custom/sesh.toml connect my-session ``` The flag works with any subcommand. When specified, the file must exist or sesh will return an error. Without the flag, sesh uses the default config path. ### Custom Multiplexer (psmux) Sesh uses `tmux` as the default terminal multiplexer, but you can configure it to use any tmux-compatible multiplexer like [psmux](https://github.com/psmux/psmux) by setting `tmux_command` in your `sesh.toml`: ```toml tmux_command = "psmux" ``` This replaces the `tmux` binary in all commands sesh runs (session creation, switching, attaching, etc.). The configured multiplexer must support tmux's CLI interface. ### Custom Frecency Backend (fasd, autojump, etc.) Sesh uses [zoxide](https://github.com/ajeetdsouza/zoxide) as its default frecency directory-jumping backend, but you can point it at an alternative such as [fasd](https://github.com/clvv/fasd), [fasder](https://github.com/khwang0/fasder), [autojump](https://github.com/wting/autojump), or [memy](https://github.com/xvello/memy) by overriding the commands in a `[frecency]` table. This is useful for tools that track files _and_ directories, unlike zoxide which tracks directories only. ```toml [frecency] list_command = "fasd -d -l -R" # list all tracked entries query_command = "fasd -d {}" # resolve one input to a path add_command = "fasd -A {}" # record a path after connecting ``` - The `{}` placeholder is replaced with the query string (`query_command`) or the path (`add_command`), the same substitution used by `preview_command`. - `list_command` output is parsed one path per line, most-frecent first. A leading numeric score is detected automatically when present (as with zoxide's `--score`); otherwise the score is `0`. - Any command runs as a single binary (no shell), so pipes/redirects aren't supported. - Any field you omit falls back to its zoxide default (`zoxide query --list --score`, `zoxide query {}`, `zoxide add {}`), so an absent `[frecency]` table leaves behavior unchanged. - The source label in `sesh list` output stays `zoxide`, so existing integrations that read the `--json` output keep working. ### Schema (Editor Autocomplete) Sesh provides a [JSON Schema](https://json-schema.org/) for `sesh.toml` that enables autocomplete, validation, and documentation in your editor. This works with any editor that supports the [taplo](https://taplo.tamasfe.dev/) TOML language server. #### Quick Setup Add this line to the top of your `sesh.toml`: ```toml #:schema https://github.com/joshmedeski/sesh/raw/main/sesh.schema.json ``` That's it! Taplo will read the directive and apply the schema automatically. #### Neovim Make sure you have taplo installed: ```sh brew install taplo ``` If you're using [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig), taplo should work out of the box with the `#:schema` directive above. Alternatively, you can configure the schema association directly: ```lua require("lspconfig").taplo.setup({ settings = { taplo = { schema = { associations = { [".*sesh\\.toml$"] = "https://github.com/joshmedeski/sesh/raw/main/sesh.schema.json", }, }, }, }, }) ``` ### Blacklist You may want to blacklist certain tmux sessions from showing up in the results. For example, you may want to exclude your `scratch` directory from the results. ```sh blacklist = ["scratch"] ``` To surface blacklisted sessions for management, pass `-b` (or `--blacklisted`) to `sesh list`. It composes with source flags, so `sesh list -b -t` lists blacklisted tmux sessions only. ### Directory Length Control how many directory components are used for session names. Default is 1 (only the directory basename). ```toml dir_length = 2 # Uses last 2 directories: "projects/sesh" instead of just "sesh" ``` > [!NOTE] > Works great with [tmux-floax](https://github.com/omerxx/tmux-floax) ### Sorting If you'd like to change the order of the sessions shown, you can configure `sort_order` in your `sesh.toml` file ```toml sort_order = [ "tmuxinator", # show first "config", "tmux", "zoxide", # show last ] ``` The default order is `tmux`, `config`, `tmuxinator`, and then `zoxide`. You can omit session types if you only care about the order of specific ones. ```toml sort_order = [ "config", # resulting order: config, tmux, tmuxinator, zoxide ] ``` ### Cache > [!WARNING] > This feature is experimental and may not work as expected. Sesh can cache session lists to speed up repeated calls. Caching is opt-in and disabled by default. When enabled, sesh stores results at `$XDG_CACHE_HOME/sesh/sessions.gob` (default `~/.cache/sesh/sessions.gob`) and uses a stale-while-revalidate strategy with a 5-second TTL: - **Cold start**: no cache exists, fetches live data and writes the cache - **Fresh hit** (within 5s): returns cached data instantly - **Stale hit** (after 5s): returns cached data instantly and refreshes the cache in the background The cache is also refreshed automatically after `sesh connect`. ```toml cache = true ``` Sessions created or killed outside `sesh connect` (e.g. plain `tmux new-session`, closing a session's last window) are missing from the cache until the next stale-hit refresh. To keep the cache current, run `sesh cache refresh` when those events happen — it fetches live data and rewrites the cache, and does nothing when the cache is disabled. With tmux hooks this covers both events: ```sh set-hook -g session-created 'run-shell -b "sesh cache refresh"' set-hook -g session-closed 'run-shell -b "sesh cache refresh"' ``` If you use a kill binding in an fzf picker (like `ctrl-d` in the example above), refresh the cache before reloading the list so the killed session disappears immediately: ```sh --bind 'ctrl-d:execute(tmux kill-session -t {2..}; sesh cache refresh)+change-prompt(⚡ )+reload(sesh list --icons)' \ ``` ### Picker TUI The Picker TUI can be configured with some options that help you customize it's behaviour, this picker is a useful replacement to external fuzzy pickers. ```toml [tui] prompt = "> " placeholder = "Filter sessions... " show_icons = false show_windows = false alias_auto_connect_delay = "150ms" alias_filter_prefix = "/" preview = false preview_width = 60 preview_min_width = 100 preview_border = "line" ``` With `show_windows = true`, each row also lists the names of the windows inside that session, dimmed after the session name. Window names that don't fit are summarized as `+N`: ``` > sesh editor server logs dotfiles nvim shell my-project code server db +2 scratch ``` Window names are display-only: selecting a row still returns just the session name, and typing a window name does not match its session. The names for live tmux sessions are fetched in a single tmux call, so the option costs the same regardless of how many sessions you have. `alias_auto_connect_delay` and `alias_filter_prefix` tune aliases — see [Session Aliases](#session-aliases). #### Custom icons With `show_icons = true`, each row gets a glyph for where the session came from — tmux, config, zoxide, tmuxinator. That says where it was found, not what it is, so `[[session]]` and `[[wildcard]]` blocks can name their own icon instead. It can be any string: a nerd font glyph or an emoji. ```toml [[session]] name = "sesh" path = "~/c/sesh" icon = "" [[session]] name = "notes" path = "~/second-brain" icon = "📓" [[wildcard]] pattern = "~/c/work/*" icon = "🏠" ``` ``` >  sesh 📓 notes 🏠 work-api dotfiles ``` The most specific match wins: an exact `[[session]]` name, then its `path` — so the same directory listed by zoxide under a derived name still gets the icon — then a `[[wildcard]]` pattern. If several patterns match, the first in config order wins, as it does for `startup_command`. Anything with no icon of its own keeps its source glyph, and `icon = ""` counts as unset. Custom icons render unstyled: emoji bring their own color, and nerd font glyphs take the default foreground. The icon column is padded to the widest icon you configured, so a double-width emoji on one row doesn't push its name out of line with the rest. If one icon still sits a column off, add a trailing space to it: ```toml [[session]] name = "update" path = "~/c/update" icon = "⬆️ " # note the trailing space ``` Terminals disagree about how wide an emoji is, and nothing in a TUI can ask which way yours went. Emoji written with a variation selector — `⬆️` is `U+2B06` plus `U+FE0F`, as are `🖼️` and `🖥️` — measure as two cells but are drawn in one by WezTerm and others, which leaves that row short. A trailing space is counted into the row but not into the column width, so it fixes the one icon without shifting anything else. This is picker-only. `sesh list --icons` keeps the source glyphs, because the scripts and external pickers that parse its output trim a known-width glyph — see [#246](https://github.com/joshmedeski/sesh/issues/246). Custom icons are also suppressed entirely with `show_icons = false`. #### Starting with a filter `--query` (`-q`) opens the picker with its filter already typed out, which is handy for tmux keybinds that scope the list to one slice of your sessions: ```sh bind-key "P" display-popup -h 90% -w 50% -E "sesh picker -q work/" ``` The query goes in exactly as if you'd typed it, so the list is narrowed the moment sessions load, and backspacing widens it again. Sigils work too — `-q '#'` opens straight into [number jumping](#jumping-by-number) and `-q /` into [alias mode](#session-aliases). A query that matches a single session doesn't connect on its own; the picker opens with that row highlighted and waits for enter. Neither does a query that spells out an alias with `alias_auto_connect` on — auto-connect stays a reward for actually typing it, so a scripted `--query` never connects somewhere you didn't look. This is per-invocation only, with no `[tui]` equivalent: a default query would silently hide sessions on every launch. #### Jumping by number Typing `#` as the first character numbers the rows and turns the next digit into a jump — the fastest way to reach one of your first few sessions without reading their names: ``` filter: # > 1 sesh 2 dotfiles 3 my-project ``` Pressing 3 connects to `my-project` immediately. Only `1`–`9` jump, and only the first nine rows are numbered. The numbers follow the visible list, so anything typed after the sigil narrows it first and renumbers what's left — `#a` then 2 jumps to the second match for `a`. A digit with no row at that position does nothing rather than filtering. Only a leading `#` counts, so `feat#123` filters normally. If you configure `alias_filter_prefix = "#"`, alias mode wins and this mode is unreachable. #### Preview pane With `preview = true`, the highlighted session is previewed beside the list, using exactly the same output as `sesh preview` — live tmux panes via `capture-pane`, your `preview_command` for configured sessions, and a directory listing otherwise: ``` > sesh │ $ eza --icons dotfiles │ README.md main.go my-project │ picker/ previewer/ ``` The pane is off by default, and `ctrl+o` toggles it at any time. `preview_width` is the share of the terminal, in percent, guaranteed to the pane. The session list is capped at 60 columns, so on a wide terminal everything past that cap goes to the preview on top of this share; on a narrow one the list is never squeezed below 40 columns. `preview_border` picks the divider between the two panes: `line` (default), `thick`, `double`, or `none` for no divider at all. Turning it off gives the divider's column to the preview text: ```toml [tui] preview_border = "none" ``` ``` > sesh $ eza --icons dotfiles README.md main.go my-project picker/ previewer/ ``` `preview_min_width` (default `100`) is the narrowest terminal that gets a split at all. Below it the picker renders the list only, and the pane comes back on its own once the window has room — handy in small tmux popups. Preview commands run in the background as the cursor moves, so a slow one never blocks the list, and a failing one is reported in the pane instead of taking the picker down. ### Default Session The default session can be configured to run a command when connecting to a session. This is useful for running a dev server or starting a tmux plugin. Additionally, you can define a preview command that runs when previewing the session's directory. This can be handy for displaying files with tools like [eza](https://github.com/eza-community/eza) or [lsd](https://github.com/lsd-rs/lsd). Note: The `{}` will be automatically replaced with the session's path. ```toml [default_session] startup_command = "nvim -c ':Telescope find_files'" preview_command = "eza --all --git --icons --color=always {}" ``` If you want to disable the default start command on a specific session, you can set `disable_startup_command = true`. ### Session Configuration A startup command is a command that is run when a session is created. It is useful for setting up your environment for a given project. For example, you may want to run `npm run dev` to automatically start a dev server. **Note:** If you use the `--command/-c` flag, then the startup script will not be run. I like to use a command that opens nvim on session startup. You can also define a preview command to display the contents of a specific file using [bat](https://github.com/sharkdp/bat) or any another file previewer of your choice. ```toml [[session]] name = "Downloads 📥" path = "~/Downloads" startup_command = "ls" [[session]] name = "tmux config" path = "~/c/dotfiles/.config/tmux" startup_command = "nvim tmux.conf" preview_command = "bat --color=always ~/c/dotfiles/.config/tmux/tmux.conf" ``` A session can also set `icon` to replace the source glyph it gets in the picker — see [Custom icons](#custom-icons). ### Session Aliases Fuzzy matching is great for discovery, but the top result for `wp` shifts as sessions come and go, so muscle memory never quite forms. An alias gives a session a short, fixed name you can always count on: ```toml [[session]] name = "wallpaper" path = "~/c/wallpaper" alias = "wp" alias_auto_connect = true [[session]] name = "dotfiles" path = "~/.config" alias = "dot" ``` Aliases must be unique (case-insensitively) — sesh reports an error at startup if two sessions share one. On the command line, `sesh connect wp` behaves exactly like `sesh connect wallpaper`. In the picker, aliased sessions are tagged with a chip so they stay discoverable: ``` > wp wallpaper dot dotfiles my-project ``` Typing an alias exactly resolves to that session and nothing else, regardless of how fuzzy matching would have ranked it — the whole point being that the result never shifts: ``` filter: wp > wp wallpaper ``` Anything short of an exact alias is fuzzy-matched as usual, so `w` and `wpx` behave like any other query. An alias resolves even when its session isn't in the current list — say you're running `sesh picker --tmux` and it isn't started yet. With `alias_auto_connect = true`, typing the full alias connects immediately — no Enter. It is opt-in per session because it is only worth it for the handful you jump to constantly. Aliases that share a prefix (`w` and `wp`) are allowed. `[tui] alias_auto_connect_delay` (default `150ms`) is the grace period before auto-connect fires, which leaves room to finish typing the longer one. Raise it if you type slowly, or lower it to `"0s"` to fire the instant the alias is complete. To keep typing past an alias in a one-off invocation, run the picker with `--no-alias-auto`. #### Browsing aliases Typing `/` as the first character narrows the picker to aliased sessions only, which is how you go from "I know I set up a shortcut for this" to the session without remembering the shortcut: ``` filter: / > wp wallpaper dot dotfiles tc tmux config ``` What you type next narrows further, matching aliases by prefix (`/t` finds `tc`) and then falling back to session names (`/config` also finds `tc`, since aliases come first and names are matched anywhere). Aliases whose sessions aren't running still show up. Completing an alias in this mode connects immediately, whether or not it set `alias_auto_connect` — reaching for `/` says the next thing you type is a shortcut to jump to. `alias_auto_connect_delay` still applies, so `/w` leaves room to become `/wp`, and `--no-alias-auto` still holds it back. Only a leading `/` counts, so `code/app` filters normally. But a query that *starts* with a path — `/Users/you/code` — would enter alias mode instead, so pick a different sigil if you filter that way: ```toml [tui] alias_filter_prefix = "@" # or "" to turn the mode off ``` ### Path substitution If you want to use the path of the selected session in your startup or preview command, you can use the `{}` placeholder. This will be replaced with the session's path when the command is run. An example of this in use is the following, where the `tmuxinator` default_project uses the path as key/value pair using [ERB syntax](https://github.com/tmuxinator/tmuxinator?tab=readme-ov-file#erb): ```toml [default_session] startup_command = "tmuxinator start default_project path={}" preview_command = "eza --all --git --icons --color=always {}" ``` ### Multiple windows If you want your session to have multiple windows you can define windows in your configuration. You can then use these window layouts in your sessions. These windows can be reused as many times as you want and you can add as many windows to each session as you want. Note: If you do not specify a path in the window, it will use the session's path. ```toml [[session]] name = "Downloads 📥" path = "~/Downloads" startup_command = "ls" [[session]] name = "tmux config" path = "~/c/dotfiles/.config/tmux" startup_command = "nvim tmux.conf" preview_command = "bat --color=always ~/c/dotfiles/.config/tmux/tmux.conf" windows = [ "git" ] [[window]] name = "git" startup_script = "git pull" ``` ### Wildcard Configuration Wildcard configs let you define session settings that apply to any directory matching a glob pattern, instead of creating a `[[session]]` entry for each project. This is useful when you have a directory full of projects that should all use the same startup command. ```toml [[wildcard]] pattern = "~/projects/*" startup_command = "nvim" [[wildcard]] pattern = "~/work/*" startup_command = "make dev" preview_command = "ls -la" [[wildcard]] pattern = "~/repos/**" startup_command = "git status" ``` When you run `sesh connect ~/projects/myapp`, sesh matches the path against your wildcard patterns and automatically creates a session named from the directory (using git remote or folder name), runs the configured startup command, and adds the path to zoxide. Available fields: | Field | Description | |-------|-------------| | `pattern` | Glob pattern to match directories (e.g. `~/projects/*`) | | `startup_command` | Command to run on session creation (supports `{}` for path) | | `preview_command` | Command to run when previewing the session | | `disable_startup_command` | Set to `true` to suppress the startup command | | `windows` | Window layout to use (array of window names from `[[window]]` configs) | | `icon` | Icon shown for matching sessions in the picker — see [Custom icons](#custom-icons) | **Note:** Patterns use Go's `filepath.Match` syntax which supports `*` (any sequence), `?` (single character), and `[...]` (character classes). You can also use `/**` at the end of a pattern for recursive matching -- `~/projects/**` matches `~/projects/foo`, `~/projects/foo/bar`, and any deeper nesting. A single `*` only matches one level: `~/projects/*` matches `~/projects/foo` but not `~/projects/foo/bar`. Explicit `[[session]]` configs always take priority over wildcard matches. If multiple wildcards match, the first one in config order wins. ### Listing Configurations Session configurations will load by default if no flags are provided (the return after tmux sessions and before zoxide results). If you want to explicitly list them, you can use the `-c` flag. ```sh sesh list -c ``` ## Contributing Want to contribute? Check out our [Contributing Guide](CONTRIBUTING.md) to get started. ## Background (the "t" script) Sesh is the successor to my popular [t-smart-tmux-session-manager](https://github.com/joshmedeski/t-smart-tmux-session-manager) tmux plugin. After a year of development and over 250 stars, it's clear that people enjoy the idea of a smart session manager. However, I've always felt that the tmux plugin was a bit of a hack. It's a bash script that runs in the background and parses the output of tmux commands. It works, but it's not ideal and isn't flexible enough to support other terminal multiplexers. I've decided to start over and build a session manager from the ground up. This time, I'm using a language that's more suited for the task: Go. Go is a compiled language that's fast, statically typed, and has a great standard library. It's perfect for a project like this. I've also decided to make this session manager multiplexer agnostic. It will be able to work with any terminal multiplexer, including tmux, zellij, Wezterm, and more. The first step is to build a CLI that can interact with tmux and be a drop-in replacement for my previous tmux plugin. Once that's complete, I'll extend it to support other terminal multiplexers. ## StarMapper StarMapper ## Contributors Made with [contrib.rocks](https://contrib.rocks). ## Star History [![Star History Chart](https://api.star-history.com/svg?repos=joshmedeski/sesh&type=Date)](https://www.star-history.com/#joshmedeski/sesh&Date)