Drive tmux from PowerShell with native cmdlets. Sessions, windows and panes come back as typed objects that a pipeline can filter.
Needs PowerShell 7.4 or newer and tmux 3.3a or newer, on Linux or WSL. Some cmdlets need a newer tmux; compatibility lists them.
Contents — Quick start · Find things · Panes · Service readiness · Choose how to run · Workspaces · Packages · From source · Leaves out · Support · Documentation
Reference — Cmdlet reference · Compatibility · Troubleshooting · License
Install the module from the PowerShell Gallery with pwsh:
$ Install-PSResource -Name LibTmuxImport it:
$ Import-Module -Name LibTmux -ErrorAction StopNew-TmuxServer: a handle on a private server
It names a socket and an empty tmux configuration, and contacts nothing, so your default server stays untouched:
Import-Module -Name LibTmux -ErrorAction Stop
$server = LibTmux\New-TmuxServer `
-SocketName ('libtmux-readme-' + [Guid]::NewGuid().ToString('N')) `
-ConfigurationFile /dev/nullWithout a socket, tmux picks the server: the one in TMUX inside a pane,
otherwise its default. LIBTMUX_SOCKET_PATH or LIBTMUX_SOCKET chooses one.
More: read tmux objects.
Invoke-TmuxPaneCommand: run a command, read its exit status
It returns a LibTmux.PaneRunResult and needs tmux 3.4 or
newer. New-TmuxSession starts the shell, and the finally
block removes only that session with Remove-TmuxSession:
& {
$ErrorActionPreference = 'Stop'
$name = 'pane-run-' + [Guid]::NewGuid().ToString('N')
$session = $server | New-TmuxSession -Name $name -Command 'exec /bin/sh'
try {
$pane = $session | Get-TmuxPane
$pane |
Invoke-TmuxPaneCommand -Command 'exit 7' -Timeout 5 -Confirm:$false
} finally {
$session | Remove-TmuxSession -Confirm:$false
}
}It prints ExitStatus = 7 and TimedOut = False. A nonzero exit status
raises no error. On timeout the command may still run and the result has no
exit status.
More: command completion. Later
sections reuse $server.
Get-TmuxSnapshot: what tmux holds, in one read
One call returns every session, window and pane as native objects. This
example builds a tree with New-TmuxSession,
Split-TmuxPane and New-TmuxWindow, reads
it, and removes it. cat keeps the panes open:
Import-Module -Name LibTmux -ErrorAction Stop
$captured = & {
$ErrorActionPreference = 'Stop'
$session = $server |
New-TmuxSession -Name demo -WindowName editor -Command 'exec /bin/cat'
try {
$pane = $session | Get-TmuxPane
$null = $pane | Split-TmuxPane -Horizontal -Command 'exec /bin/cat'
$null = $session | New-TmuxWindow -Name logs -Command 'exec /bin/cat'
($server | Get-TmuxSnapshot).Sessions | Where-Object Name -CEQ demo
} finally {
$session | Remove-TmuxSession -Confirm:$false
}
}$captured is a LibTmux.Session that stays readable after
cleanup. Its Windows hold LibTmux.Window
objects, and their Panes hold LibTmux.Pane objects:
| Expression | Captured result |
|---|---|
$captured |
A Session named demo |
$captured.Windows |
Two Window objects: editor and logs |
$captured.Windows[0].Panes |
Two Pane objects in editor; logs has one |
A captured object keeps what tmux said when you read it. Call
Get-TmuxSnapshot again to see a later change.
More: snapshots and linked windows.
Where-Object reads the capture and starts no tmux client:
$captured.Windows |
Where-Object { $_.Panes.Count -gt 1 } |
Select-Object Name, @{ Name = 'PaneCount'; Expression = { $_.Panes.Count } }It prints editor with a pane count of 2. logs has one pane and drops out:
Name PaneCount
---- ---------
editor 2
Select-TmuxWindow: pick windows by criteria
Criteria select the one window with two or more panes; -ExactlyOne fails on
zero or several matches. It starts no tmux client either:
$captured.Windows |
Select-TmuxWindow -Criteria @{ 'Panes.Count' = @{ Ge = 2 } } -ExactlyOneIt returns editor, the same object as $captured.Windows[0].
More: filter and query.
Get-TmuxQueryPlan: see what runs in tmux
New-TmuxQuery builds criteria once. A plan shows which part
tmux evaluates and which part runs in PowerShell, and reads nothing:
$query = New-TmuxQuery -Target Window -Criteria ([ordered]@{
IsActive = $true
Name = 'editor'
})
$plan = $query | Get-TmuxQueryPlan -DaemonVersion '3.6'
'in tmux: {0}; here: {1}' -f
($null -ne $plan.PushedPredicate), ($null -ne $plan.ResidualPredicate)It prints in tmux: True; here: True: tmux evaluates IsActive and PowerShell
evaluates Name. The split depends on the tmux release the plan names.
-Pushdown Never keeps everything local, and Require fails if any part must
stay local.
Invoke-TmuxQuery runs the query against a server and
returns fresh objects:
& {
$ErrorActionPreference = 'Stop'
$session = $server |
New-TmuxSession -Name query -WindowName editor -Command 'exec /bin/cat'
try {
$server | Invoke-TmuxQuery -Query $query | ForEach-Object Name
} finally {
$session | Remove-TmuxSession -Confirm:$false
}
}It prints editor.
More: store a query or acquire a fresh result.
Update-TmuxPane: read a held pane again
It returns a fresh copy of a held Pane and leaves the original as it
was. This example retitles a pane with
Invoke-TmuxCommand after $held was read:
& {
$ErrorActionPreference = 'Stop'
$session = $server |
New-TmuxSession -Name refresh -Command 'exec /bin/cat'
try {
$id = ($session | Get-TmuxPane).Id.ToString()
$retitle = { param($text)
$null = $server | Invoke-TmuxCommand -Confirm:$false `
-Arguments 'select-pane', '-t', $id, '-T', $text
}
& $retitle editor
$held = $session | Get-TmuxPane
& $retitle build
$fresh = $held | Update-TmuxPane
'{0} {1}' -f $held.Title, $fresh.Title
} finally {
$session | Remove-TmuxSession -Confirm:$false
}
}It prints editor build. For a session or window, read again with
Get-TmuxSession or Get-TmuxWindow.
Split-TmuxPane: add a pane and work in it
Send-TmuxText and Send-TmuxKey type into
a pane, and Get-TmuxPaneContent reads its screen.
Set-TmuxWindowSize and
Set-TmuxPaneSize resize. cat echoes what it receives,
and Wait-TmuxPaneText waits for the echo:
Import-Module -Name LibTmux -ErrorAction Stop
& {
$ErrorActionPreference = 'Stop'
$session = $server | New-TmuxSession -Name panes -Command 'exec /bin/cat'
try {
$session | Get-TmuxWindow | Set-TmuxWindowSize -Width 100 -Height 30
$left = $session | Get-TmuxPane
$right = $left | Split-TmuxPane -Horizontal -Command 'exec /bin/cat'
$right | Send-TmuxText -Text hello
$right | Send-TmuxKey -Key Enter
$null = $right | Wait-TmuxPaneText -Pattern '^hello$' -Timeout 10
$right | Get-TmuxPaneContent | Select-Object -First 1
($left | Set-TmuxPaneSize -Width 30 -PassThru).Width
} finally {
$session | Remove-TmuxSession -Confirm:$false
}
}It prints hello, the first screen line, then 30, the left pane's width.
More: send text and keys, pane contents and arrange and resize.
Wait-TmuxPaneText: wait for a readiness line
It watches a pane's text and returns a
LibTmux.PaneWaitResult. It uses a control client on tmux 3.7
or newer and polls on older releases.
Send-TmuxText and Send-TmuxKey type into
the pane first, and Get-TmuxPaneContent reads its
screen. If the application can signal completion itself, use
Wait-TmuxChannel with
a channel.
With Python 3 on PATH, this example starts an HTTP server on a free loopback
port, waits for its readiness line, then requests a page:
Import-Module -Name LibTmux -ErrorAction Stop
& {
$ErrorActionPreference = 'Stop'
$session = $server | New-TmuxSession `
-Name ('http-' + [Guid]::NewGuid().ToString('N')) `
-Command 'exec python3 -u -m http.server 0 --bind 127.0.0.1'
try {
$pane = $session | Get-TmuxPane
$ready = $pane | Wait-TmuxPaneText `
-Pattern '^Serving HTTP on 127\.0\.0\.1 port [0-9]+' `
-CaseSensitive -Timeout 10 -TailLines 4 -Confirm:$false
if ($ready.Outcome -notin 'PresentAtEntry', 'Matched') {
throw "HTTP readiness ended with $($ready.Outcome)."
}
$tail = $ready.Tail -join "`n"
$port = [regex]::Match($tail, 'port ([0-9]+)').Groups[1].Value
$uri = "http://127.0.0.1:$port/"
$response = Invoke-WebRequest -Uri $uri -TimeoutSec 5 -NoProxy
$ready
$response.StatusCode
} finally {
$session | Remove-TmuxSession -Confirm:$false
}
}It prints the wait result, outcome PresentAtEntry or Matched, then HTTP
status 200.
More: pane-text waits.
Pick a mode by who owns the client and how failures are reported:
| Task | Use | Example |
|---|---|---|
| Read or change tmux state | Typed cmdlets and pipelines | Create sessions and panes |
| Run a shell command and check its exit status | Invoke-TmuxPaneCommand |
Run to completion |
| Wait for a running application's readiness line | Wait-TmuxPaneText |
Readiness and an HTTP health check |
| Enter a session interactively | Enter-TmuxSession |
Attach your foreground terminal |
| Run an ordered batch | New-TmuxCommand → Invoke-TmuxChain |
Compose commands |
| Reuse a connected client | Connect-TmuxControl → Invoke-TmuxControlCommand |
Control commands and cleanup |
| React to output and notifications | Watch-TmuxEvent |
Bounded event streams |
| Keep the prompt available | Start-ThreadJob |
Bounded background jobs |
| Run commands concurrently | ForEach-Object -Parallel |
Independent clients |
A cmdlet refuses before it changes anything when tmux is too old:
- tmux 3.4:
Invoke-TmuxPaneCommand. - tmux 3.7:
Connect-TmuxControlandWatch-TmuxEventwith a server.
Optional features lists every
refusal, and capabilities.json holds the list for a
script to read.
The raw-command cmdlets Invoke-TmuxCommand,
Invoke-TmuxChain and
Invoke-TmuxControlCommand check the command
they are given, not strings nested in it (if-shell, bind-key, braces).
What the checks do not read
lists the rest.
Invoke-TmuxChain: several commands, one tmux call
New-TmuxCommand builds each command with literal
arguments. A chain runs them in order and stops at the first failure; earlier
changes stay. This example renames a window and reads the new name:
& {
$ErrorActionPreference = 'Stop'
$session = $server |
New-TmuxSession -Name chain -Command 'exec /bin/cat'
try {
$rename = New-TmuxCommand -Name rename-window -Arguments @(
'-t', 'chain:0', 'build'
)
$show = New-TmuxCommand -Name display-message -Arguments @(
'-p', '-t', 'chain:0', '#{window_name}'
)
$result = $server | Invoke-TmuxChain -Command @($rename, $show)
$result.StandardOutputLines
} finally {
$session | Remove-TmuxSession -Confirm:$false
}
}It prints build.
More: execute an ordered chain.
Connect-TmuxControl: one client for repeated commands
It attaches one control client. Invoke-TmuxControlCommand
sends a command through it, and
Disconnect-TmuxControl ends it. This example reads a
session name, then disconnects and removes its session:
Import-Module -Name LibTmux -ErrorAction Stop
& {
$ErrorActionPreference = 'Stop'
$session = $server |
New-TmuxSession -Name control-demo -Command 'exec /bin/cat'
try {
$client = $server |
Connect-TmuxControl -Target 'control-demo' -ErrorAction Stop
try {
$command = New-TmuxCommand -Name display-message -Arguments @(
'-p', '#{session_name}'
)
$client |
Invoke-TmuxControlCommand -Command $command -ErrorAction Stop
} finally {
$client | Disconnect-TmuxControl -Confirm:$false
}
} finally {
$session | Remove-TmuxSession -Confirm:$false
}
}The reply is control-demo. The client stays connected for every command in
the inner try block.
More: reuse a control client.
Watch-TmuxEvent: react to notifications
It streams tmux notifications and pane output from a control client, with event-count and text-byte limits. A server handle needs tmux 3.7 or newer.
More: observe one owned change, a complete program that starts its own server and cleans up.
The other modes:
Enter-TmuxSessionattaches your terminal to a session.- Foreground cmdlets and
ForEach-Object -Paralleloccupy their pipeline. - Thread jobs return the prompt while they run.
More: commands and control clients.
Describe a session once in YAML or JSON, review the plan, then apply it.
Installing the workspace module also installs LibTmux:
$ Install-PSResource -Name LibTmux.WorkspaceImport both modules:
$ Import-Module -Name LibTmux, LibTmux.Workspace -ErrorAction StopImport-TmuxWorkspace: read a declaration
It parses YAML or JSON without contacting tmux or running its commands.
Get-TmuxWorkspace finds declaration files,
Resolve-TmuxWorkspace expands directories and
Test-TmuxWorkspace validates one. This declaration has
two panes in one window:
Import-Module -Name LibTmux, LibTmux.Workspace -ErrorAction Stop
$workspace = LibTmux.Workspace\Import-TmuxWorkspace -Yaml @'
session_name: readme-workspace-preview
windows:
- window_name: editor
panes:
- shell_command: exec /bin/sh
- shell_command: exec /bin/sh
'@It returns a LibTmux.Workspace.WorkspaceFile and prints
nothing.
Get-TmuxWorkspacePlan: review before anything runs
It plans against the same private server. Planning may read tmux and creates nothing:
$workspacePlan = $workspace | LibTmux.Workspace\Get-TmuxWorkspacePlan `
-Server $server -ServerStartup CreateOrJoin -ExistingSession ErrorReview the actions. The default view hides command text, option values, paths
and environment values; read an action's
Request only in a trusted terminal:
$workspacePlan.ActionsIt lists the actions in order, from
CreateSession to
CaptureResult, with [redacted] for
command text.
Invoke-TmuxWorkspace: preview, then apply
-WhatIf previews the plan without applying it:
$workspacePlan | LibTmux.Workspace\Invoke-TmuxWorkspace -WhatIfIt prints a What if: line and the plan's actions. Apply that plan, then remove
only the session it created. The result stays readable after cleanup:
$workspaceResult = & {
$result = $workspacePlan |
LibTmux.Workspace\Invoke-TmuxWorkspace -Confirm:$false -ErrorAction Stop
try { $result } finally {
$result.Session | Remove-TmuxSession -Confirm:$false
}
}The result is a LibTmux.Workspace.WorkspaceResult. Read its
windows:
$workspaceResult.Windows |
Select-Object Name, @{ Name = 'PaneCount'; Expression = { $_.Panes.Count } }It returns editor with two panes.
ConvertTo-TmuxWorkspace turns a captured session
into a starting declaration.
More: apply the reviewed plan.
| Product | Use it for |
|---|---|
| LibTmux | Typed cmdlets, snapshots, input, capture, control clients and event streams |
| LibTmux.Workspace |
Discover YAML/JSON declarations, resolve directories, review plans and create workspaces |
LibTmux: typed cmdlets for tmux
Read, create, arrange and remove sessions, windows and panes, send input and capture output as native objects.
Reference: every LibTmux cmdlet, PowerShell Gallery, changelog.
LibTmux.Workspace: workspaces from YAML or JSON
Describe a session once, review the plan, then apply it.
Install it at the same version as LibTmux.
Reference: every LibTmux.Workspace cmdlet, PowerShell Gallery, changelog.
Install PowerShell and the .NET SDK from .tool-versions with mise:
$ mise installInstall tmux with your package manager and check that it is on PATH:
$ tmux -VFrom the checkout root, restore the libtmux for .NET packages and build both modules:
$ pwsh -NoLogo -NoProfile -File eng/Build.ps1 \
-RestoreThe build stages both modules under build/Modules. For shared .NET changes,
see review package builds.
Start PowerShell with the staged module path:
$ LIBTMUX_REVIEW_MODULE_ROOT="$PWD/build/Modules" \
pwsh \
-NoLogo \
-NoProfileImport the staged manifests so an installed module cannot shadow the build:
$modules = $env:LIBTMUX_REVIEW_MODULE_ROOT
Import-Module -Name @(
"$modules/LibTmux/0.1.0/LibTmux.psd1",
"$modules/LibTmux.Workspace/0.1.0/LibTmux.Workspace.psd1"
) -ErrorAction Stopexamples/QuickStart.ps1: a private server and three panes
Run it from the checkout root. It starts a private server, splits the editor
window, adds logs, and returns a captured LibTmux.Session. Show
each window's pane IDs:
(./examples/QuickStart.ps1).Windows | Select-Object Name, @{
Name = 'PaneIds'
Expression = { $_.Panes.Id -join ', ' }
}It prints:
Name PaneIds
---- -------
editor %0, %1
logs %2
Assign the script's output to a variable to keep the session. Its
Windows and Panes stay readable after
the script removes its session. It uses a unique socket, so your default tmux
server stays untouched.
- Live objects. A captured object keeps what tmux said when you read it.
Get-TmuxSnapshotreads again; snapshots and linked windows list what a capture cannot know. - An implicit server. A handle from
New-TmuxServerstarts no daemon, and importing the module connects to none.TMUXandTMUX_PANEstay as they are (compatibility). - Shell quoting.
Send-TmuxTextsends its text literally; quote it for the receiving shell yourself (send input). - Native Windows. Run PowerShell and tmux inside WSL. Native Windows and psmux are outside this port (compatibility).
- Pre-1.0. Any release may change or remove exported identifiers; each module's changelog marks breaks. Pin an exact version.
- PowerShell. 7.4 or newer. Windows PowerShell 5.1 cannot load the modules.
- tmux. 3.3a or newer. Some cmdlets need more: optional features.
- Systems. Linux and WSL, with PowerShell and tmux in the same Linux environment. macOS is a trial; compatibility has the detail.
- Built on libtmux for .NET.
The cmdlets return its native objects and add parameter binding, help,
formatting and
-WhatIf/-Confirm.
- Read: snapshots and filtering, structured queries, pane contents and scrollback, attached clients.
- Automate: create, send text and keys, arrange and resize, link and move windows, remove sessions, windows and panes.
- Configure: options with
Set-TmuxOption, hooks withSet-TmuxHook, environment for new panes. - Coordinate: chains and control clients, event streams, jobs and parallel workers.
- Workspaces: load, review and apply, export a starting declaration, edit a declaration.
Browse cmdlets with Get-Command -Module LibTmux and examples with
Get-Help LibTmux\New-TmuxSession -Examples. Further reading:
- Compatibility: the support contract
- Troubleshooting: endpoint, package, input and stream failures
- Benchmarks: workloads and how to rerun them
- Changelog: changes across both modules, plus
LibTmuxandLibTmux.Workspace - Contributing: runners and development checks
- Example sources: the code behind every block here
Use these packages with AI tools as you like. When contributing, check what you submit and wait for a maintainer's invitation before preparing a pull request. See the AI policy, also kept as AI_POLICY.md.
MIT. See third-party notices for bundled dependencies.