Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,27 @@ $ uvx --from 'libtmux' --prerelease allow python
_Notes on the upcoming release will go here._
<!-- END PLACEHOLDER - ADD NEW CHANGELOG ENTRIES BELOW THIS LINE -->

libtmux 0.62.x adds pure Python workspace archive helpers inspired by
tmux-resurrect and tmux-continuum. Projects can capture, restore, rotate,
autosave, and startup-restore tmux workspaces through libtmux APIs without
installing TPM plugins.

### What's new

#### Headless workspace archives and autosave (#701, #702)

The new {mod}`libtmux.resurrect` package captures tmux workspaces into typed
JSON archives and restores them without shelling through tmux plugin scripts.
Archives include session, window, pane, layout, focus, grouped-session,
pane-title, zoom, and `automatic-rename` state, with idempotent reuse behavior
for existing sessions.

The package also includes conservative process restore policies, optional full
process command capture providers, tmux-resurrect tab-file import/export,
timestamped snapshot rotation with a portable `last.json` pointer, and
tmux-continuum-style autosave and startup-restore guards. See
{doc}`topics/resurrect` for usage.

## libtmux 0.61.0 (2026-07-04)

libtmux 0.61.0 hardens support for the tmux 3.7 patch line. It fixes
Expand Down
7 changes: 7 additions & 0 deletions docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ Format strings and constants.
Exception hierarchy.
:::

:::{grid-item-card} Resurrect
:link: libtmux.resurrect
:link-type: doc
Headless workspace archive, restore, autosave, and startup helpers.
:::

::::

## Testing
Expand Down Expand Up @@ -178,4 +184,5 @@ Options <libtmux.options>
Hooks <libtmux.hooks>
Constants <libtmux.constants>
Exceptions <libtmux.exc>
Resurrect <libtmux.resurrect>
```
21 changes: 21 additions & 0 deletions docs/api/libtmux.resurrect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Resurrect

```{eval-rst}
.. automodule:: libtmux.resurrect
:members:

.. automodule:: libtmux.resurrect.archives
:members:

.. automodule:: libtmux.resurrect.continuum
:members:

.. automodule:: libtmux.resurrect.processes
:members:

.. automodule:: libtmux.resurrect.resurrect_file
:members:

.. automodule:: libtmux.resurrect.storage
:members:
```
7 changes: 7 additions & 0 deletions docs/topics/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ Create and position floating (overlay) panes on tmux 3.7+.
Create sessions, windows, and panes programmatically.
:::

:::{grid-item-card} Workspace Archives
:link: resurrect
:link-type: doc
Capture, restore, rotate, and autosave tmux workspaces headlessly.
:::

:::{grid-item-card} Automation Patterns
:link: automation_patterns
:link-type: doc
Expand Down Expand Up @@ -86,6 +92,7 @@ filtering
pane_interaction
floating_panes
workspace_setup
resurrect
automation_patterns
context_managers
options_and_hooks
Expand Down
183 changes: 183 additions & 0 deletions docs/topics/resurrect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# Workspace Archives

libtmux includes pure Python helpers for capturing and restoring tmux
workspaces without installing tmux plugins. The API is modeled on
tmux-resurrect and tmux-continuum, but it uses libtmux calls and JSON archives
so it can run in tests, background jobs, and MCP servers.

## Capture and Restore

Capture the current server into a typed archive:

```python
from pathlib import Path

from libtmux import Server
from libtmux.resurrect import capture_archive, write_archive

server = Server()
archive = capture_archive(server)
write_archive(archive, Path("workspace.json"))
```

Restore an archive into a fresh or reusable server:

```python
from pathlib import Path

from libtmux import Server
from libtmux.resurrect import restore_archive

server = Server()
restore_archive(Path("workspace.json"), server, on_exists="reuse")
```

Archives preserve sessions, windows, panes, working directories, layouts,
active panes, active and alternate windows, grouped sessions, pane titles,
zoom flags, `automatic-rename`, and attached-client session focus when tmux can
replay it.

## Process Commands

By default, process restore uses a conservative tmux-resurrect-style allow-list
for interactive commands such as `vim`, `less`, `tail`, and `top`. Shell panes
are recreated at their saved working directory without replaying a command.

Use a policy when you want to add commands or restore everything:

```python
from pathlib import Path

from libtmux import Server
from libtmux.resurrect import ProcessRestorePolicy, restore_archive

server = Server()
policy = ProcessRestorePolicy.from_options("'python->uv run python *' 'git log'")
restore_archive(Path("workspace.json"), server, process_policy=policy)
```

Full command capture is explicit because command lines can contain sensitive
arguments. Pass a provider when you want to save process arguments:

```python
from libtmux import Server
from libtmux.resurrect import capture_archive, default_process_command_provider

server = Server()
archive = capture_archive(server, process_provider=default_process_command_provider())
```

The default provider reads Linux procfs and leaves `full_command` empty when
procfs cannot resolve the foreground process. Use
{class}`~libtmux.resurrect.PsProcessCommandProvider` explicitly when you want a
`ps`-based provider.

## tmux-resurrect Files

Existing tmux-resurrect save files can be imported without running TPM or the
plugin scripts:

```python
from pathlib import Path

from libtmux.resurrect import archive_from_resurrect_file, write_archive

archive = archive_from_resurrect_file(Path("last").read_text(encoding="utf-8"))
write_archive(archive, Path("workspace.json"))
```

You can also export a libtmux archive back to tmux-resurrect tab rows:

```python
from pathlib import Path

from libtmux.resurrect import archive_to_resurrect_file, read_archive

archive = read_archive(Path("workspace.json"))
Path("last").write_text(archive_to_resurrect_file(archive), encoding="utf-8")
```

## Autosave and Rotation

Autosave helpers provide tmux-continuum-style interval checks and socket-aware
paths. Snapshot storage adds timestamped archives, retention rotation, and a
`last.json` pointer:

```python
from pathlib import Path

from libtmux import Server
from libtmux.resurrect import autosave_once, default_autosave_paths

server = Server(socket_name="main")
paths = default_autosave_paths(server, Path("archives"))
autosave_once(server, archive_path=paths.archive_path, state_path=paths.state_path)
```

```python
from pathlib import Path

from libtmux.resurrect import capture_archive, write_archive_snapshot

archive = capture_archive(server)
write_archive_snapshot(archive, Path("archives"), keep=10, portable_last=True)
```

Use `portable_last=True` when a filesystem or operating system does not allow
symlink creation. Without it, libtmux writes a relative symlink when possible
and falls back to a copy when symlink creation fails.

## Startup Restore

Startup restore is split into a decision helper and a one-shot restore helper.
This makes downstream service wrappers explicit about when restore is allowed:

```python
from pathlib import Path

from libtmux import Server
from libtmux.resurrect import startup_restore_once

server = Server(socket_name="main")
result = startup_restore_once(
server,
Path("archives/last.json"),
enabled=True,
halt_file=Path("archives/restore-halt"),
another_server_running=False,
)
```

The helper skips restore when disabled, when a halt file exists, when sessions
already exist, when another server owns the restore window, or when the startup
grace period has elapsed. `result.reason` contains a stable reason string for
service logs.

For a systemd user service, run a small Python wrapper that calls
`startup_restore_once()` after starting tmux:

```ini
[Unit]
Description=Restore tmux workspace

[Service]
Type=oneshot
ExecStart=python -m your_restore_module

[Install]
WantedBy=default.target
```

For launchd, use the same wrapper from a `ProgramArguments` entry:

```xml
<key>ProgramArguments</key>
<array>
<string>python</string>
<string>-m</string>
<string>your_restore_module</string>
</array>
```

The wrapper should compute `another_server_running` using the service manager
or deployment environment that owns tmux startup.
101 changes: 101 additions & 0 deletions src/libtmux/resurrect/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""Headless tmux workspace archive helpers."""

from __future__ import annotations

from .archives import (
CAPTURED_CAPABILITIES,
DEFAULT_SHELL_COMMANDS,
FORMAT_VERSION,
PaneArchive,
RestorePolicy,
SessionArchive,
WindowArchive,
WorkspaceArchive,
capture_archive,
read_archive,
restore_archive,
write_archive,
)
from .continuum import (
DEFAULT_AUTOSAVE_INTERVAL,
DEFAULT_STARTUP_RESTORE_GRACE,
STATE_FORMAT_VERSION,
AutosavePaths,
AutosaveResult,
AutosaveState,
StartupRestoreDecision,
StartupRestoreResult,
autosave_once,
default_autosave_paths,
next_autosave_at,
read_autosave_state,
should_autosave,
should_restore_on_startup,
startup_restore_once,
write_autosave_state,
)
from .processes import (
DEFAULT_PROCESS_RESTORE_POLICY,
DEFAULT_RESTORE_PROGRAMS,
CompositeProcessCommandProvider,
ProcessCommandProvider,
ProcessRestorePolicy,
ProcessRestoreRule,
ProcfsProcessCommandProvider,
PsProcessCommandProvider,
default_process_command_provider,
)
from .resurrect_file import (
archive_from_resurrect_file,
archive_to_resurrect_file,
)
from .storage import (
ArchiveSnapshot,
LastPointerKind,
write_archive_snapshot,
)

__all__ = (
"CAPTURED_CAPABILITIES",
"DEFAULT_AUTOSAVE_INTERVAL",
"DEFAULT_PROCESS_RESTORE_POLICY",
"DEFAULT_RESTORE_PROGRAMS",
"DEFAULT_SHELL_COMMANDS",
"DEFAULT_STARTUP_RESTORE_GRACE",
"FORMAT_VERSION",
"STATE_FORMAT_VERSION",
"ArchiveSnapshot",
"AutosavePaths",
"AutosaveResult",
"AutosaveState",
"CompositeProcessCommandProvider",
"LastPointerKind",
"PaneArchive",
"ProcessCommandProvider",
"ProcessRestorePolicy",
"ProcessRestoreRule",
"ProcfsProcessCommandProvider",
"PsProcessCommandProvider",
"RestorePolicy",
"SessionArchive",
"StartupRestoreDecision",
"StartupRestoreResult",
"WindowArchive",
"WorkspaceArchive",
"archive_from_resurrect_file",
"archive_to_resurrect_file",
"autosave_once",
"capture_archive",
"default_autosave_paths",
"default_process_command_provider",
"next_autosave_at",
"read_archive",
"read_autosave_state",
"restore_archive",
"should_autosave",
"should_restore_on_startup",
"startup_restore_once",
"write_archive",
"write_archive_snapshot",
"write_autosave_state",
)
Loading
Loading