Skip to content

About

Native PowerShell cmdlets for tmux sessions, windows and panes, with workspace planning. Built on LibTmux for .NET.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

libtmux for PowerShell

libtmux for PowerShell

powershell LibTmux libtmux.org license

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

Quick start

Install the module from the PowerShell Gallery with pwsh:

$ Install-PSResource -Name LibTmux

Import it:

$ Import-Module -Name LibTmux -ErrorAction Stop

New-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/null

Without 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.

Find sessions, windows, and panes

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.

Filter the captured graph

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 } } -ExactlyOne

It 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, resize, type into and read panes

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 for service readiness

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.

Choose how to run

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:

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-TmuxSession attaches your terminal to a session.
  • Foreground cmdlets and ForEach-Object -Parallel occupy their pipeline.
  • Thread jobs return the prompt while they run.

More: commands and control clients.

Build a workspace from a file

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.Workspace

Import both modules:

$ Import-Module -Name LibTmux, LibTmux.Workspace -ErrorAction Stop

Import-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 Error

Review 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.Actions

It 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 -WhatIf

It 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.

Packages

Product Use it for
LibTmux Typed cmdlets, snapshots, input, capture, control clients and event streams
LibTmux.Workspace 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 from source

Install PowerShell and the .NET SDK from .tool-versions with mise:

$ mise install

Install tmux with your package manager and check that it is on PATH:

$ tmux -V

From the checkout root, restore the libtmux for .NET packages and build both modules:

$ pwsh -NoLogo -NoProfile -File eng/Build.ps1 \
    -Restore

The 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 \
    -NoProfile

Import 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 Stop

examples/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.

What it leaves out

  • Live objects. A captured object keeps what tmux said when you read it. Get-TmuxSnapshot reads again; snapshots and linked windows list what a capture cannot know.
  • An implicit server. A handle from New-TmuxServer starts no daemon, and importing the module connects to none. TMUX and TMUX_PANE stay as they are (compatibility).
  • Shell quoting. Send-TmuxText sends 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).

Support and status

  • 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.

Documentation

Browse cmdlets with Get-Command -Module LibTmux and examples with Get-Help LibTmux\New-TmuxSession -Examples. Further reading:

AI policy

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.

License

MIT. See third-party notices for bundled dependencies.

About

Native PowerShell cmdlets for tmux sessions, windows and panes, with workspace planning. Built on LibTmux for .NET.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages