This file provides guidance to AI agents (including Claude Code, Cursor, and other LLM-powered tools) when working with code in this repository.
- ALL tests MUST pass for code to be considered complete and working
- Never describe code as "working as expected" if there are ANY failing tests
- Even if specific feature tests pass, failing tests elsewhere indicate broken functionality
- Changes that break existing tests must be fixed before considering implementation complete
- A successful implementation must pass linting, type checking, AND all existing tests
libtmux is a typed Python library that provides an Object-Relational Mapping (ORM) wrapper for interacting programmatically with tmux, a terminal multiplexer.
Key features:
- Manage tmux servers, sessions, windows, and panes programmatically
- Typed Python API with full type hints
- Built on tmux's target and formats system
- Powers tmuxp, a tmux workspace manager
- Provides pytest fixtures for testing with tmux
This project uses:
- Python 3.10+
- uv for dependency management
- ruff for linting and formatting
- mypy for type checking
- pytest for testing
- pytest-watcher for continuous testing
# Install dependencies
uv pip install --editable .
uv pip sync
# Install with development dependencies
uv pip install --editable . -G dev# Run all tests
just test
# or directly with pytest
uv run pytest
# Run a single test file
uv run pytest tests/test_pane.py
# Run a specific test
uv run pytest tests/test_pane.py::test_send_keys
# Run tests with test watcher
just start
# or
uv run ptw .
# Run tests with doctests
uv run ptw . --now --doctest-modules# Run ruff for linting
just ruff
# or directly
uv run ruff check .
# Format code with ruff
just ruff-format
# or directly
uv run ruff format .
# Run ruff linting with auto-fixes
uv run ruff check . --fix --show-fixes
# Run mypy for type checking
just mypy
# or directly
uv run mypy src tests
# Watch mode for linting (using entr)
just watch-ruff
just watch-mypyFollow this workflow for code changes:
- Format First:
uv run ruff format . - Run Tests:
uv run pytest - Run Linting:
uv run ruff check . --fix --show-fixes - Check Types:
uv run mypy - Verify Tests Again:
uv run pytest
# Build documentation
just build-docs
# Start documentation server with auto-reload
just start-docs
# Update documentation CSS/JS
just design-docslibtmux follows an object-oriented design that mirrors tmux's hierarchy:
Server (tmux server instance)
└─ Session (tmux session)
└─ Window (tmux window)
└─ Pane (tmux pane)
-
Server (
src/libtmux/server.py)- Represents a tmux server instance
- Manages sessions
- Executes tmux commands via
tmux()method - Entry point for most libtmux interactions
-
Session (
src/libtmux/session.py)- Represents a tmux session
- Manages windows within the session
- Provides session-level operations (attach, kill, rename, etc.)
-
Window (
src/libtmux/window.py)- Represents a tmux window
- Manages panes within the window
- Provides window-level operations (split, rename, move, etc.)
-
Pane (
src/libtmux/pane.py)- Represents a tmux pane (terminal instance)
- Provides pane-level operations (send-keys, capture, resize, etc.)
- Core unit for command execution and output capture
-
Common (
src/libtmux/common.py)- Base classes and shared functionality
TmuxRelationalObjectandTmuxMappingObjectbase classes- Format handling and command execution
-
Formats (
src/libtmux/formats.py)- Tmux format string constants
- Used for querying tmux state
-
Neo (
src/libtmux/neo.py)- Modern query interface and dataclass-based objects
- Alternative to traditional ORM-style objects
-
pytest Plugin (
src/libtmux/pytest_plugin.py)- Provides fixtures for testing with tmux
- Creates temporary tmux sessions/windows/panes
libtmux uses pytest for testing with custom fixtures. The pytest plugin (pytest_plugin.py) defines fixtures for creating temporary tmux objects for testing. These include:
server: A tmux server instance for testingsession: A tmux session for testingwindow: A tmux window for testingpane: A tmux pane for testing
These fixtures handle setup and teardown automatically, creating isolated test environments.
-
Use functional tests only: Write tests as standalone functions, not classes. Avoid
class TestFoo:groupings - use descriptive function names and file organization instead. -
Use existing fixtures over mocks
- Use fixtures from conftest.py instead of
monkeypatchandMagicMockwhen available - For libtmux, use provided fixtures:
server,session,window, andpane - Document in test docstrings why standard fixtures weren't used for exceptional cases
- Use fixtures from conftest.py instead of
-
Preferred pytest patterns
- Use
tmp_path(pathlib.Path) fixture over Python'stempfile - Use
monkeypatchfixture overunittest.mock
- Use
-
Running tests continuously
- Use pytest-watcher during development:
uv run ptw . - For doctests:
uv run ptw . --now --doctest-modules
- Use pytest-watcher during development:
def test_window_rename(window):
"""Test renaming a window."""
# window is already a Window instance with a live tmux window
window.rename_window('new_name')
assert window.window_name == 'new_name'Key highlights:
- Use namespace imports for standard library modules:
import enuminstead offrom enum import Enum- Exception:
dataclassesmodule may usefrom dataclasses import dataclass, fieldfor cleaner decorator syntax - This rule applies to Python standard library only; third-party packages may use
from X import Y
- Exception:
- For typing, use
import typing as tand access via namespace:t.NamedTuple, etc. - Use
from __future__ import annotationsat the top of all Python files
Follow NumPy docstring style for all functions and methods:
"""Short description of the function or class.
Detailed description using reStructuredText format.
Parameters
----------
param1 : type
Description of param1
param2 : type
Description of param2
Returns
-------
type
Description of return value
"""All functions and methods MUST have working doctests. Doctests serve as both documentation and tests.
CRITICAL RULES:
- Doctests MUST actually execute - never comment out function calls or similar
- Doctests MUST NOT be converted to
.. code-block::as a workaround (code-blocks don't run) - If you cannot create a working doctest, STOP and ask for help
Available tools for doctests:
doctest_namespacefixtures:server,session,window,pane,Server,Session,Window,Pane,request- Ellipsis for variable output:
# doctest: +ELLIPSIS - Update
conftest.pyto add new fixtures todoctest_namespace
# doctest: +SKIP is NOT permitted - it's just another workaround that doesn't test anything. Use the fixtures properly - tmux is required to run tests anyway.
Using fixtures in doctests:
>>> server.new_session(session_name='my_session') # server from doctest_namespace
Session($... my_session)
>>> session.new_window(window_name='my_window') # session from doctest_namespace
Window(@... ...:my_window, Session($... ...))
>>> pane.send_keys('echo hello') # pane from doctest_namespace
>>> pane.capture_pane() # doctest: +ELLIPSIS
[...'echo hello'...]When output varies, use ellipsis:
>>> window.window_id # doctest: +ELLIPSIS
'@...'
>>> session.session_id # doctest: +ELLIPSIS
'$...'Additional guidelines:
- Use narrative descriptions for test sections rather than inline comments
- Move complex examples to dedicated test files at
tests/examples/<path_to_module>/test_<example>.py - Keep doctests simple and focused on demonstrating usage
- Add blank lines between test sections for improved readability
Format commit messages as:
Scope(type[detail]): concise description
why: Explanation of necessity or impact.
what:
- Specific technical changes made
- Focused on a single topic
Common commit types:
- feat: New features or enhancements
- fix: Bug fixes
- refactor: Code restructuring without functional change
- docs: Documentation updates
- chore: Maintenance (dependencies, tooling, config)
- test: Test-related updates
- style: Code style and formatting
- py(deps): Dependencies
- py(deps[dev]): Dev Dependencies
- ai(rules[AGENTS]): AI rule updates
- ai(claude[rules]): Claude Code rules (CLAUDE.md)
- ai(claude[command]): Claude Code command changes
Example:
Pane(feat[send_keys]): Add support for literal flag
why: Enable sending literal characters without tmux interpretation
what:
- Add literal parameter to send_keys method
- Update send_keys to pass -l flag when literal=True
- Add tests for literal key sending
For multi-line commits, use heredoc to preserve formatting:
git commit -m "$(cat <<'EOF'
feat(Component[method]) add feature description
why: Explanation of the change.
what:
- First change
- Second change
EOF
)"When writing documentation (README, CHANGES, docs/), follow these rules for code blocks:
One command per code block. This makes commands individually copyable.
Put explanations outside the code block, not as comments inside.
Good:
Run the tests:
$ uv run pytestRun with coverage:
$ uv run pytest --covBad:
# Run the tests
$ uv run pytest
# Run with coverage
$ uv run pytest --covWhen stuck in debugging loops:
- Pause and acknowledge the loop
- Minimize to MVP: Remove all debugging cruft and experimental code
- Document the issue comprehensively for a fresh approach
- Format for portability (using quadruple backticks)
- All tmux commands go through the
cmd()method on Server/Session/Window/Pane objects - Commands return a
CommandResultobject withstdoutandstderr - Use tmux format strings to query object state (see
formats.py)
libtmux uses tmux's format system extensively:
- Defined in
src/libtmux/formats.py - Used to query session_id, window_id, pane_id, etc.
- Format:
#{format_name}(e.g.,#{session_id},#{window_name})
- Objects can become stale if tmux state changes externally
- Use refresh methods (e.g.,
session.refresh()) to update object state - Alternative: use
neo.pyquery interface for fresh data
- Documentation: https://libtmux.git-pull.com/
- API Reference: https://libtmux.git-pull.com/api.html
- Architecture: https://libtmux.git-pull.com/about.html
- tmux man page: http://man.openbsd.org/OpenBSD-current/man1/tmux.1
- tmuxp (workspace manager): https://tmuxp.git-pull.com/