Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
8f8b8e6
fix(conf): Replace stale "master" branch references with "main"
tony Mar 27, 2026
d1a07a8
docs(tools): Add behavioral guidance to 22 tool docstrings
tony Mar 27, 2026
88e80da
docs(server): Add tier-hidden context to agent safety instructions
tony Mar 27, 2026
0e681db
docs(quickstart): Add "How it works" section with tool sequence
tony Mar 27, 2026
7d7aa4b
docs(tools): Add "Which tool do I want?" decision guide
tony Mar 27, 2026
a854473
docs(tools): Add response examples to 15 tools that lacked them
tony Mar 27, 2026
1c69e6b
docs: Add recipes page with 6 composable workflow patterns
tony Mar 27, 2026
0b487ff
docs(topics): Add gotchas page — 7 things that will bite you
tony Mar 27, 2026
a31a376
docs(topics): Add agent prompting guide
tony Mar 27, 2026
87efa1d
feat(docs[_ext]): Add {toolref} role — code-linked tool refs without …
tony Mar 27, 2026
9054795
fix(docs[_ext]): Suppress mypy misc errors on docutils node subclasses
tony Mar 27, 2026
7fe9712
docs(recipes): Rewrite as scenario-driven workflows
tony Mar 27, 2026
90fe9c9
docs(recipes): Drop numbered prefixes from recipe titles
tony Mar 27, 2026
dd2e7f6
docs(recipes): Link tmuxp, generalize CV project to React
tony Mar 27, 2026
975f1fe
docs(glossary): Add SIGINT and SIGQUIT terms, link from recipes
tony Mar 27, 2026
521d2aa
docs: Add orphaned demo page showcasing badge system
tony Mar 28, 2026
b48987d
docs(glossary): Use {toolref} for send_keys in SIGINT/SIGQUIT definit…
tony Mar 28, 2026
43987a4
style(docs): Vertically align badge with code text in {tool} links
tony Mar 28, 2026
d78392b
style(docs): Add vertical-align middle to base badge rule
tony Mar 28, 2026
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
47 changes: 38 additions & 9 deletions docs/_ext/fastmcp_autodoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ def _extract_enum_values(type_str: str) -> list[str]:
return values


class _safety_badge_node(nodes.General, nodes.Inline, nodes.Element):
class _safety_badge_node(nodes.General, nodes.Inline, nodes.Element): # type: ignore[misc]
"""Custom node for safety badges with ARIA attributes in HTML output."""


Expand Down Expand Up @@ -868,16 +868,23 @@ def _add_section_badges(
title_node += _safety_badge(tier)


class _tool_ref_placeholder(nodes.General, nodes.Inline, nodes.Element):
"""Placeholder node for ``{tool}`` role, resolved at doctree-resolved."""
class _tool_ref_placeholder(nodes.General, nodes.Inline, nodes.Element): # type: ignore[misc]
"""Placeholder node for ``{tool}`` and ``{toolref}`` roles.

Resolved at ``doctree-resolved`` by ``_resolve_tool_refs``.
The ``show_badge`` attribute controls whether the safety badge is appended.
"""


def _resolve_tool_refs(
app: Sphinx,
doctree: nodes.document,
fromdocname: str,
) -> None:
"""Resolve ``{tool}`` placeholders into links with safety badges.
"""Resolve ``{tool}`` and ``{toolref}`` placeholders into links.

``{tool}`` renders as ``code`` + safety badge.
``{toolref}`` renders as ``code`` only (no badge).

Runs at ``doctree-resolved`` — after all labels are registered and
standard ``{ref}`` resolution is done.
Expand All @@ -888,6 +895,7 @@ def _resolve_tool_refs(

for node in list(doctree.findall(_tool_ref_placeholder)):
target = node.get("reftarget", "")
show_badge = node.get("show_badge", True)
label_info = domain.labels.get(target)
if label_info is None:
node.replace_self(nodes.literal("", target.replace("-", "_")))
Expand All @@ -908,10 +916,11 @@ def _resolve_tool_refs(

newnode += nodes.literal("", tool_name)

tool_info = tool_data.get(tool_name)
if tool_info:
newnode += nodes.Text(" ")
newnode += _safety_badge(tool_info.safety)
if show_badge:
tool_info = tool_data.get(tool_name)
if tool_info:
newnode += nodes.Text(" ")
newnode += _safety_badge(tool_info.safety)

node.replace_self(newnode)

Expand All @@ -930,7 +939,26 @@ def _tool_role(
Creates a placeholder node resolved later by ``_resolve_tool_refs``.
"""
target = text.strip().replace("_", "-")
node = _tool_ref_placeholder(rawtext, reftarget=target)
node = _tool_ref_placeholder(rawtext, reftarget=target, show_badge=True)
return [node], []


def _toolref_role(
name: str,
rawtext: str,
text: str,
lineno: int,
inliner: object,
options: dict[str, object] | None = None,
content: list[str] | None = None,
) -> tuple[list[nodes.Node], list[nodes.system_message]]:
"""Inline role ``:toolref:`capture-pane``` → code-linked tool name, no badge.

Like ``{tool}`` but without the safety badge. Use in dense contexts
(tables, inline prose) where badges would be too heavy.
"""
target = text.strip().replace("_", "-")
node = _tool_ref_placeholder(rawtext, reftarget=target, show_badge=False)
return [node], []


Expand Down Expand Up @@ -958,6 +986,7 @@ def setup(app: Sphinx) -> ExtensionMetadata:
app.connect("doctree-resolved", _add_section_badges)
app.connect("doctree-resolved", _resolve_tool_refs)
app.add_role("tool", _tool_role)
app.add_role("toolref", _toolref_role)
app.add_role("badge", _badge_role)
app.add_directive("fastmcp-tool", FastMCPToolDirective)
app.add_directive("fastmcp-tool-input", FastMCPToolInputDirective)
Expand Down
6 changes: 6 additions & 0 deletions docs/_static/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ h4:has(> .sd-badge) {
.sd-badge {
display: inline-flex !important;
align-items: center;
vertical-align: middle;
gap: 0.28rem;
font-size: 0.67rem;
font-weight: 650;
Expand Down Expand Up @@ -332,6 +333,11 @@ code.docutils + .sd-badge,
/* ── Link behavior: underline code only, on hover ───────── */
a.reference .sd-badge {
text-decoration: none;
vertical-align: middle;
}

a.reference:has(.sd-badge) code {
vertical-align: middle;
}

a.reference:has(.sd-badge) {
Expand Down
6 changes: 3 additions & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
},
],
"source_repository": f"{about['__repository__']}/",
"source_branch": "master",
"source_branch": "main",
"source_directory": "docs/",
"announcement": "<em>Pre-alpha.</em> APIs may change. <a href='https://github.com/tmux-python/libtmux-mcp/issues'>Feedback welcome</a>.",
}
Expand Down Expand Up @@ -140,7 +140,7 @@

# sphinxext-rediraffe
rediraffe_redirects = "redirects.txt"
rediraffe_branch = "master~1"
rediraffe_branch = "main~1"

# sphinxext.opengraph
ogp_site_url = about["__url__"]
Expand Down Expand Up @@ -259,7 +259,7 @@ def linkcode_resolve(domain: str, info: dict[str, str]) -> None | str:
fn = relpath(fn, start=pathlib.Path(libtmux_mcp.__file__).parent)

if "dev" in about["__version__"]:
return "{}/blob/master/{}/{}/{}{}".format(
return "{}/blob/main/{}/{}/{}{}".format(
about["__repository__"],
"src",
about["__package_name__"],
Expand Down
100 changes: 100 additions & 0 deletions docs/demo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
orphan: true
---

# Badge & Role Demo

A showcase of the custom Sphinx roles and visual elements available in libtmux-mcp documentation.

## Safety badges

Standalone badges via `{badge}`:

- {badge}`readonly` — green, read-only operations
- {badge}`mutating` — amber, state-changing operations
- {badge}`destructive` — red, irreversible operations

## Tool references

### `{tool}` — code-linked with badge

{tool}`capture-pane` · {tool}`send-keys` · {tool}`search-panes` · {tool}`wait-for-text` · {tool}`kill-pane` · {tool}`create-session` · {tool}`split-window`

### `{toolref}` — code-linked, no badge

{toolref}`capture-pane` · {toolref}`send-keys` · {toolref}`search-panes` · {toolref}`wait-for-text` · {toolref}`kill-pane` · {toolref}`create-session` · {toolref}`split-window`

### `{ref}` — plain text link

{ref}`capture-pane` · {ref}`send-keys` · {ref}`search-panes` · {ref}`wait-for-text` · {ref}`kill-pane` · {ref}`create-session` · {ref}`split-window`

## Badges in context

### In a heading

These are the actual tool headings as they render on tool pages:

> `capture_pane` {badge}`readonly`

> `split_window` {badge}`mutating`

> `kill_session` {badge}`destructive`

### In a table

| Tool | Tier | Description |
|------|------|-------------|
| {toolref}`list-sessions` | {badge}`readonly` | List all sessions |
| {toolref}`send-keys` | {badge}`mutating` | Send commands to a pane |
| {toolref}`kill-pane` | {badge}`destructive` | Destroy a pane |

### In prose

Use {tool}`search-panes` to find text across all panes. If you know which pane, use {tool}`capture-pane` instead. After running a command with {tool}`send-keys`, always {tool}`wait-for-text` before capturing.

### Dense inline (toolref, no badges)

The fundamental pattern: {toolref}`send-keys` → {toolref}`wait-for-text` → {toolref}`capture-pane`. For discovery: {toolref}`list-sessions` → {toolref}`list-panes` → {toolref}`get-pane-info`.

## Environment variable references

{envvar}`LIBTMUX_SOCKET` · {envvar}`LIBTMUX_SAFETY` · {envvar}`LIBTMUX_SOCKET_PATH` · {envvar}`LIBTMUX_TMUX_BIN`

## Glossary terms

{term}`SIGINT` · {term}`SIGQUIT` · {term}`MCP` · {term}`Safety tier` · {term}`Pane` · {term}`Session`

## Admonitions

```{tip}
Use {tool}`search-panes` before {tool}`capture-pane` when you don't know which pane has the output you need.
```

```{warning}
Do not call {toolref}`capture-pane` immediately after {toolref}`send-keys` — there is a race condition. Use {toolref}`wait-for-text` between them.
```

```{note}
All tools accept an optional `socket_name` parameter for multi-server support.
```

## Badge anatomy

Each badge renders as:

```html
<span class="sd-badge sd-bg-success"
role="note"
aria-label="Safety tier: readonly">
🔍 readonly
</span>
```

Features:
- **Emoji icon** — 🔍 readonly, ✏️ mutating, 💣 destructive (native system emoji, no filters)
- **Matte colors** — forest green, smoky amber, matte crimson with 1px border
- **Accessible** — `role="note"` + `aria-label` for screen readers
- **Non-selectable** — `user-select: none` so copying tool names skips badge text
- **Context-aware sizing** — slightly larger in headings, smaller inline
- **Sidebar compression** — badges collapse to colored dots in the right-side TOC
- **Heading flex** — `h2/h3/h4:has(.sd-badge)` centers badge against cap-height
6 changes: 6 additions & 0 deletions docs/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,10 @@ Safety tier

Socket
The Unix socket used to communicate with a tmux server. Can be specified by name (`-L`) or path (`-S`).

SIGINT
Interrupt signal (Ctrl-C). Sent via {toolref}`send-keys` with `keys: "C-c"` and `enter: false`. Most processes terminate gracefully on SIGINT.

SIGQUIT
Quit signal (Ctrl-\\). Sent via {toolref}`send-keys` with `keys: "C-\\"` and `enter: false`. Stronger than {term}`SIGINT` — may produce a core dump on Unix. Use as an escalation when SIGINT is ignored.
```
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ clients
:caption: Use it

tools/index
recipes
configuration
```

Expand Down
10 changes: 10 additions & 0 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ Here are a few things to try:

> Search all my panes for the word "error".

## How it works

When you say "run `make test` and show me the output", the agent executes a three-step pattern:

1. {ref}`send-keys` — send the command to a tmux pane
2. {ref}`wait-for-text` — wait for the shell prompt to return (command finished)
3. {ref}`capture-pane` — read the terminal output

This **send → wait → capture** sequence is the fundamental workflow. Most agent interactions with tmux follow this pattern or a variation of it.

## Next steps

- {ref}`concepts` — Understand the tmux hierarchy and how tools target panes
Expand Down
Loading
Loading