Named, managed tmux sessions: create-or-reuse by name, drive by sid
fastmux’s handle API assumes you can hold a Pane between calls. A background terminal often outlives that: it is created in one tool call and driven from another, shared with a user who attaches by name, and referred to by nothing but that name. This module provides that functionality.
start_session creates-or-reuses a named session, and every verb takes a sid — a session name, a %pane_id, a Session or Pane handle, or None for the current pane.
Managed sessions
A managed session differs from a bare new_session in the ways background work needs: it is found again by name instead of erroring as a duplicate; remain is set (with the dead-pane banner cleared) so a finished command’s output and exit status stay inspectable; and the created pane’s id is recorded in the @fastmux_pane_id session option, so later splits don’t change which pane the sid addresses. cmd is started with respawn-pane only after those options are in place, since a fast-failing command could otherwise die before they land.
def start_session( sid:NoneType=None, # Session name; default: a generated `fastmux-`-prefixed name cmd:NoneType=None, # Command to run (str or argv list); default: the user's shell cwd:NoneType=None, # Working directory env:NoneType=None, # Extra environment vars as a dict width:NoneType=None, # Terminal width in columns height:NoneType=None, # Terminal height in rows):
The session named sid, created detached (managed, with remain set) if it doesn’t exist
s = start_session('fastmux-test-bg', width=60, height=8)test_eq(s.name, 'fastmux-test-bg')test_eq(start_session('fastmux-test-bg').id, s.id)s
Addressing by sid
pane turns any sid form into the Pane it addresses, and is the bridge to the full handle API: everything this module doesn’t wrap (wait, search, splitting, transcript slicing) is a method on its result. For a session name, the managed pane recorded at creation wins over the currently-active one:
The pane sid addresses: a session name (its managed pane), a %pane_id, a Session or Pane handle, or None for the current pane
p = pane('fastmux-test-bg')test_eq(p.id, pane(s).id)test_eq(p.id, pane(p.id).id)
Bare names pin to the session exactly (= prefix, and =sid: as the pane target), so a window that happens to share the name can never shadow it — tmux’s loose target-pane resolution searches windows too, and before this pin a name collision once resolved close to the wrong session entirely.
decoy = new_session(name='fastmux-test-decoy', width=40, height=5)w = decoy.new_window(name='fastmux-test-bg') # a window named like our sessiontest_eq(pane('fastmux-test-bg').id, p.id) # ...cannot shadow itdecoy.kill()
Splitting the managed session doesn’t move the sid’s target:
The verbs mirror Pane’s, with a sid in front: each sends (or just waits), polls until the pane differs from its last-seen state, and returns a Capture. Output that arrived between calls satisfies the next poll immediately.
Wait for sid’s pane to differ from its last-seen state (or to match until, or to settle), then capture the last lines (the viewport instead with screen=True)
c = send('fastmux-test-bg', 'echo $((6*7)) apples\n', wait_ms=2000)assert'42 apples'in c.textc
Lifecycle
close kills the whole session owning the sid’s pane — including when the sid is a %pane_id — and managed_sessions lists every session start_session created, whatever it was named: