Drive tmux from Elixir: capture sessions, windows and panes as an immutable snapshot, filter it with plain data, then rename, split and type through domain actions.
Contents — Installation · Try the examples · Find things · Rename · Control · Errors · Guides · Support · Leaves out
Add the package to your Mix dependencies:
{:libtmux, "~> 0.1"}Fetch dependencies:
$ mix deps.getIt needs:
- Elixir 1.20 or newer, on OTP 29 or newer
- tmux 3.3 or newer
- Linux and a C compiler, which builds a small helper
- no third-party runtime packages and no NIF
The exact floors and tested releases are in Packages and support.
The snippets below form one executable program. From this checkout, run it on a private tmux server:
$ python3 examples/owned_server.py \
--run "$(command -v tmux)" \
mix run examples/readme.exs- The runner starts the server, passes in the executable, socket and session ID, and stops the server afterward.
- Your own tmux sessions are untouched. The runner needs Python 3.9+.
- Starting and stopping the server is the host's job; the library never starts one.
No checkout? The standalone demo installs the package from Hex. To read an existing server, use the capture-only program.
LibTmux.Server.new/1 picks the socket in this order:
:socket(a path) or:socket_nameLIBTMUX_SOCKET_PATH, thenLIBTMUX_SOCKET- neither: tmux's choice, which is the server
TMUXnames inside tmux and the default socket outside it
A Server with a chosen socket removes TMUX.
Server.snapshot!/2: what tmux has now
One call captures every session, window and pane as an immutable graph.
Snapshot.sessions/1,
Snapshot.windows/1 and
Snapshot.panes/1 pick a type, and
Selection.where/2 filters it. None of them calls tmux again.
alias LibTmux.{Selection, Server, Snapshot}
[executable, socket, session_id] = System.argv()
server =
Server.new(
executable: executable,
socket: socket,
env: Map.drop(System.get_env(), ["TMUX", "TMUX_PANE"])
)
snapshot = Server.snapshot!(server)
active_ids =
snapshot
|> Snapshot.panes()
|> Selection.where(active: true)
|> Enum.map(& &1.id)active_ids is ["%0"]: the demo server starts with one pane. Capture again
when you need fresh state.
More: Captured state.
Selection.where/2: filter by data
Criteria are keywords or maps, and can test related panes.
Filter.from_json/3 reads the same criteria from JSON, so a
filter can come from a file or a request. Selection.filter/2 takes
an Elixir function instead.
alias LibTmux.{Filter, Pane, Selection, Snapshot}
windows =
snapshot
|> Snapshot.windows()
|> Selection.where(panes: [some: [active: true, current_command: "cat"]])
|> Enum.map(& &1.id)
json = """
{"version":1,"entity":"pane",
"where":{"active":true,"currentCommand":{"in":["cat","nvim"]}}}
"""
{:ok, filter} = Filter.from_json(Pane, json)
pane_ids =
snapshot
|> Snapshot.panes()
|> Selection.where(filter)
|> Enum.map(& &1.id)windows is ["@0"] and pane_ids is ["%0"]. One pane must meet both
conditions inside some.
More: Filtering.
Window.rename!/4: change a window's name
Continue with the same snapshot and
server. Selection.one!/2 needs exactly one match,
Session.active_window/2 reads the captured link, and
Ref.from_observation/1 turns an observation into an action target.
alias LibTmux.{Ref, Selection, Session, Snapshot, Window}
session = snapshot |> Snapshot.sessions() |> Selection.one!(id: session_id)
window = Session.active_window(snapshot, session)
{:ok, session_ref} = Ref.from_observation(session)
{:ok, window_ref} = Ref.from_observation(window)
Window.rename!(server, window_ref, "editor", session: session_ref)It returns :ok once tmux acknowledges the command. session: names the
session whose link to the window to use. The old snapshot keeps the old name.
More: One-shot actions.
Control.with_connection/2: one connection for many commands
It attaches a control client to an existing session, runs your callback, and
closes the client. Pane.split!/3 returns the new pane's ID and a
bound reference. Pane.send_text!/4 sends literal text and
Pane.send_keys!/4 sends named keys. After the scope closes,
Pane.capture!/3 reads the new pane's screen through the server.
alias LibTmux.{Control, Pane, Ref, Selection, Snapshot}
pane = snapshot |> Snapshot.panes() |> Selection.one!(window_id: window.id, active: true)
{:ok, pane_ref} = Ref.from_observation(pane)
{:ok, {created_id, created}} =
Control.with_connection(
[server: server, session: {:existing, session.id}, lane: :host_qualified],
fn control ->
%{id: id, ref: created} =
Pane.split!(control, pane_ref, axis: :horizontal, command: {:argv, ["/bin/cat"]})
Pane.send_text!(control, created, "hello")
Pane.send_keys!(control, created, ["Enter"])
{id, created}
end
)
screen = Pane.capture!(server, created, max_bytes: 4096)The program prints:
{"window":"editor","active_panes":["%0"],"created_pane":"%1","connection_retired":true}
{:argv, ["/bin/cat"]}runs a program directly;{:shell, "command"}goes through a shell.- An acknowledgement does not mean the program has read its input, so
screenmay not showhelloyet. Pane.capture!/3runs one command through theserver; a control connection refuses it.lane: :host_qualifiedsays you control this server's hooks and aliases. The demo runner does. Read the managed-control guide before using another host.
More: Managed control.
Action.CompletionError: what went wrong
Actions return {:ok, result} only when the command completes; the bang forms
raise instead. A command that times out may still have taken effect, so
inspect it before you retry.
More: Recovery, Mental model.
- Filtering: predicates, relations and finding one match.
- One-shot actions: rename, split, input and screen capture.
- Managed control: one connection, many commands.
- Mental model: snapshots, references and results.
- API reference: functions and types.
- Alpha. Releases are numbered
0.1.N. Any release may change or remove exported functions; the changelog marks each break. Pin an exact version to avoid one. - Linux. The helper that owns tmux processes needs Linux.
- tmux. 3.3 or newer.
- Server lifecycle. It never starts a server and has no stop call; send
kill-serverthroughServer.run/3. - A query planner. Selections run locally over a snapshot;
Query,BatchandPlanare planned. - Change notifications.
ObservationandTrackerare planned.
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.
See Contributing for development setup and checks, and Writing for documentation conventions.
Related projects: libtmux for Python, libtmux ports, and tmux. Artwork. Licensed under the MIT License.