Neovim plugin for executing commands in tmux windows with smart window management and completion notifications.
https://github.com/dwainm/tmux-commander.nvim
- π― Smart Window Selection - automatically finds idle tmux windows or creates new ones
- π Completion Notifications - get notified when long-running commands finish (success/failure)
- π Command History - persistent history with Snacks.nvim picker to re-run commands
- π Live Window Preview - browse all tmux windows with real-time content preview
- π€ Auto-Adopt Commands - automatically monitor all running tmux commands with one keypress
- β¨οΈ Simple API - just one function:
run_prompt(cmd)- pass command or prompt user - π¨ Customizable - define your own keymaps, configure notifications and behavior
- π Auto-Prompt for Input - cursor-based detection automatically prompts for passwords, confirmations, and interactive input
Install the plugin with lazy.nvim:
{
"dwainm/tmux-commander.nvim",
dependencies = {
"folke/snacks.nvim", -- for picker UI
},
keys = {
-- Window commands - runs in separate tmux windows
{ "<leader>rd", function() require("tmux-commander").run_prompt("kamal deploy") end, desc = "Deploy with Kamal" },
{ "<leader>rt", function() require("tmux-commander").run_prompt("bin/rails test") end, desc = "Run Rails tests" },
{ "<leader>rC", function() require("tmux-commander").run_prompt("bin/rails console") end, desc = "Rails console" },
{ "<leader>rc", function() require("tmux-commander").run_prompt() end, desc = "Run custom command" },
-- Pane commands - runs in panes within current window
{ "<leader>pd", function() require("tmux-commander").run_panel_prompt("npm run dev") end, desc = "Dev server" },
{ "<leader>pt", function() require("tmux-commander").run_panel_prompt("npm run test:watch") end, desc = "Test watcher" },
{ "<leader>pc", function() require("tmux-commander").run_panel_prompt() end, desc = "Run custom command in pane" },
-- Built-in utilities
{ "<leader>ra", function() require("tmux-commander").adopt() end, desc = "Adopt running command" },
{ "<leader>rh", function() require("tmux-commander").show_history() end, desc = "Command history" },
{ "<leader>rr", function() require("tmux-commander").repeat_last() end, desc = "Repeat last command" },
{ "<leader>ri", function() require("tmux-commander").inspect() end, desc = "Jump to runner window" },
{ "<leader>rx", function() require("tmux-commander").kill() end, desc = "Kill running command" },
{ "<leader>rl", function() require("tmux-commander").list_windows() end, desc = "List runner windows" },
},
}- Neovim >= 0.9.0
- tmux >= 3.0
- snacks.nvim - for picker UI with tmux preview
- nvim-notify or noice.nvim (recommended for notifications)
- Install the plugin with your package manager
- Define keymaps that call
run_prompt("your command") - Press your keymap to run the command in a tmux window
- Get notified when the command completes
Example workflow:
-- Press <leader>rd to deploy
-- β Plugin finds idle tmux window or creates new one
-- β Sends "kamal deploy" to the window
-- β Shows: "π kamal deploy started in window 2"
-- β Monitors command execution every 2 seconds
-- β When finished: "β
kamal deploy completed (5m 32s)"Default configuration
{
"dwainm/tmux-commander.nvim",
opts = {
notification = true,
monitor_interval = 2000, -- Check command status every 2 seconds
idle_shells = { "zsh", "bash", "sh", "fish" }, -- What counts as idle
target_session = "", -- Target specific tmux session, or "" for current session
run_in_cwd = true, -- Prepend 'cd <cwd> && ' to commands (run in Neovim's working directory)
notify_on = {
start = true, -- "π Command started in window 2"
success = true, -- "β
Command completed (5m 32s)"
failure = true, -- "β Command failed (exit code 1)"
},
history = {
enabled = true,
max_entries = 100,
},
input_detection = {
enabled = true, -- Auto-prompt for input when command needs it
-- How long to wait for output to stabilize before detecting (ms)
stability_timeout = 2000,
-- Commands that wait for stdin without showing a prompt
-- These will trigger a notification instead of an input prompt
stdin_waiters = {
action = "notify", -- "notify" | "ignore" | "prompt"
commands = {
"cat", "grep", "sort", "wc", "tee", "tr",
"sed", "awk", "head", "tail", "less", "more",
"uniq", "cut", "paste"
}
},
-- Patterns that indicate a password prompt (for hidden input)
password_patterns = {
"password",
"passphrase",
}
},
},
keys = {
-- Your keymaps here...
},
}run_prompt(cmd?)
Runs a command in a tmux window (creates new windows as needed).
- With command:
run_prompt("bin/rails test")- runs the command directly - Without command:
run_prompt()- prompts user for input
-- Direct command execution in a window
vim.keymap.set("n", "<leader>rd", function()
require("tmux-commander").run_prompt("kamal deploy")
end)
-- Prompt for command in a window
vim.keymap.set("n", "<leader>rc", function()
require("tmux-commander").run_prompt()
end)run_panel_prompt(cmd?)
Runs a command in a tmux pane within the current window (splits panes as needed).
- With command:
run_panel_prompt("npm run dev")- runs the command directly - Without command:
run_panel_prompt()- prompts user for input
-- Direct command execution in a pane
vim.keymap.set("n", "<leader>pd", function()
require("tmux-commander").run_panel_prompt("npm run dev")
end)
-- Prompt for command in a pane
vim.keymap.set("n", "<leader>pc", function()
require("tmux-commander").run_panel_prompt()
end)When to use windows vs panes?
- Windows (
run_prompt): For long-running tasks you want isolated (deploys, test suites) - Panes (
run_panel_prompt): For side-by-side work in same window (dev server + logs)
adopt() - Automatically adopt all running tmux commands in the current session for monitoring. Scans all windows, adopts non-idle commands, and shows a summary notification. Already monitored and idle windows are skipped.
show_history() - Show command history in Snacks.nvim picker with formatted entries (β/β status icons, timestamps). Select an entry to re-run that command.
repeat_last() - Re-run the last executed command
inspect() - Jump to the active runner window (useful for interactive commands)
kill() - Send Ctrl-C to running command and kill it
list_windows() - Show all tmux windows in Snacks.nvim picker with live content preview. Browse windows, preview their output in real-time, and press Enter to switch to the selected window.
- Lists all tmux windows:
tmux list-windows - Finds first window running an idle shell (
zsh,bash,sh,fish) - If found β sends command to that window
- If not found β creates new window with
tmux new-window
- Lists all tmux panes in current window:
tmux list-panes - Finds first pane running an idle shell (
zsh,bash,sh,fish) - If found β sends command to that pane
- If not found β creates new pane with
tmux split-window -h(vertical split)
- Starts async timer (checks every
monitor_intervalms) - Checks if window is back to idle shell
- Checks for input prompts (password, confirmations, etc.)
- When command finishes:
- Shows completion notification
- Saves to command history
- Stops monitoring
When a monitored command needs user input, the plugin automatically detects it using cursor position and prompts you in Neovim:
Detection method:
- Monitors tmux cursor position (
#{cursor_x}) during command execution - When cursor is mid-line (cursor_x > 0) and output is stable β prompt is waiting for input
- Captures the last line to show as prompt text in Neovim
Smart handling:
-
Visible prompts - Auto-prompt in Neovim:
sudopassword promptscp -i,rm -i,mv -iconfirmations- Python
input(), shellread -p - Any command showing a prompt
-
Silent stdin waiters - Notify only:
cat,grep(no args) waiting for input- Configurable list in
stdin_waiters.commands - Shows: "
β οΈ cat is waiting for input. Press ri to interact"
-
Password detection - Hidden input:
- Checks prompt text against
password_patterns - Automatically hides characters for password prompts
- Checks prompt text against
Example workflow:
1. Press <leader>rd to run "kamal deploy"
2. Command hits password prompt in tmux
3. Detection: cursor_x=10, last_line="password:"
4. Neovim shows: "Input for kamal (window 2): password:"
5. Type password (hidden), sent to tmux
6. Command hits confirmation: "Deploy to production? (y/n)"
7. Neovim shows: "Input for kamal (window 2): Deploy to production? (y/n)"
8. Type "y", sent to tmux
9. Deploy continues...
Benefits:
- β No pattern configuration needed - works with any prompt
- β Detects 90%+ of real-world input scenarios
- β Smart handling of edge cases (stdin waiters)
- β
Fallback:
<leader>ristill works for any case
You can customize stdin waiters or disable this feature in configuration.
Commands are saved to ~/.local/share/nvim/tmux-commander-history.json:
[
{
"command": "kamal deploy",
"timestamp": 1729512345,
"window": 2,
"exit_code": 0,
"duration": 332
}
]Adopt existing commands to get notifications:
Already have commands running in tmux? Use adopt() to automatically monitor them all! The plugin will scan all windows, adopt running commands, and notify you when they complete.
-- Press <leader>ta to adopt all running commands
-- Shows: "β Adopted 2 windows, 3 idle, 1 already monitored"
-- Get notified when they finish!
{ "<leader>ta", function() require("tmux-commander").adopt() end }Keep dev servers in panes, tests in windows:
-- Dev server runs in a pane (always visible alongside editor)
{ "<leader>pd", function() require("tmux-commander").run_panel_prompt("npm run dev") end }
-- Tests run in separate window (don't clutter current workspace)
{ "<leader>rt", function() require("tmux-commander").run_prompt("npm test") end }Auto-prompt handles password and confirmation prompts automatically:
Commands that need input (passwords, confirmations) are automatically detected. Just type the input in Neovim when prompted - no need to jump to tmux!
-- The plugin will detect password prompts automatically
{ "<leader>rd", function() require("tmux-commander").run_prompt("kamal deploy") end }
-- When password is needed, you'll see: "Input needed in tmux window 2: password:"
-- You can still manually jump to window if needed
{ "<leader>ri", function() require("tmux-commander").inspect() end }For interactive commands (consoles, REPLs), use inspect() to jump to the window:
-- Run console
{ "<leader>rC", function() require("tmux-commander").run_prompt("bin/rails console") end }
-- Jump to it immediately
{ "<leader>ri", function() require("tmux-commander").inspect() end }Use with which-key for discoverable commands:
When you press <leader>r or <leader>p, which-key will show all your defined runner commands.
Multiple projects? Define different keymaps per filetype:
vim.api.nvim_create_autocmd("FileType", {
pattern = "ruby",
callback = function()
vim.keymap.set("n", "<leader>rt", function()
require("tmux-commander").run_prompt("bin/rails test")
end, { buffer = true, desc = "Run Rails tests" })
end,
})This plugin is in active development. Recent improvements:
- β Snacks.nvim integration - Full picker support with live window previews
- β Auto-adopt - Automatically monitor all running commands
- β Live preview - Real-time tmux window content in picker
- β Auto-prompt for input - Automatically detect and prompt for passwords and confirmations
Current limitations:
- Exit code detection assumes success (monitors shell return, not actual exit code)
- Single runner window per session (TODO: support multiple runners)
See SPEC.md for planned features and detailed specification.
MIT
