Skip to content

Commit fa2ab1d

Browse files
authored
docs: DX improvements — docstrings, recipes, badges, roles (#3)
why: Brainstorm-and-refine sessions (9 originals from Claude/Gemini/GPT, 3 refinement passes each) identified that 22/27 tool docstrings lacked behavioral guidance agents see at runtime, recipes were tool-centric JSON instead of real scenarios, and cross-reference tooling was incomplete. what: Runtime guidance (code): - Enrich 22 tool docstrings with behavioral guidance (use when, next step, side effects) — agents see these via FastMCP at runtime - Add safety tier refusal context to server instructions - Fix 3 stale "master" → "main" refs in conf.py + 1 in __about__.py Sphinx extension (docs/_ext/fastmcp_autodoc.py): - Add {toolref} role: code-formatted linked tool names without badge - Add {badge} role: standalone safety tier badges - Add {tool} role enhancements: proper vertical alignment with code text - Custom _safety_badge_node with ARIA attributes for accessibility - Return type annotations link to model docs via pending_xref - Section badges on Inspect/Act/Destroy headings (tools/index only) - Sidebar TOC badges compress to colored dots New documentation pages: - docs/recipes.md: 6 scenario-driven workflows (Discover/Decide/Act) - docs/topics/gotchas.md: 7 things that will bite you - docs/topics/prompting.md: agent prompting guide with _BASE_INSTRUCTIONS - docs/demo.md: orphaned badge/role showcase page Improved existing pages: - quickstart: "How it works" section with send→wait→capture pattern - tools/index: "Which tool do I want?" intent-based decision guide - 15 tools: response examples added (now 27/27 coverage) - glossary: SIGINT, SIGQUIT terms with {toolref} cross-references - configuration: {envvar} directives for linkable env vars - safety: {ref}/{tool}/{badge} cross-references throughout Visual polish: - Matte badge palette (forest green, smoky amber, matte crimson) - Emoji icons (🔍 readonly, ✏️ mutating, 💣 destructive) - Badge underline-on-hover for code only (gap and badge excluded) - Context-aware badge sizing (heading vs inline vs table) - Sidebar dot compression for badges in TOC
2 parents cefd25e + d78392b commit fa2ab1d

24 files changed

Lines changed: 1128 additions & 15 deletions

docs/_ext/fastmcp_autodoc.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ def _extract_enum_values(type_str: str) -> list[str]:
421421
return values
422422

423423

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

427427

@@ -868,16 +868,23 @@ def _add_section_badges(
868868
title_node += _safety_badge(tier)
869869

870870

871-
class _tool_ref_placeholder(nodes.General, nodes.Inline, nodes.Element):
872-
"""Placeholder node for ``{tool}`` role, resolved at doctree-resolved."""
871+
class _tool_ref_placeholder(nodes.General, nodes.Inline, nodes.Element): # type: ignore[misc]
872+
"""Placeholder node for ``{tool}`` and ``{toolref}`` roles.
873+
874+
Resolved at ``doctree-resolved`` by ``_resolve_tool_refs``.
875+
The ``show_badge`` attribute controls whether the safety badge is appended.
876+
"""
873877

874878

875879
def _resolve_tool_refs(
876880
app: Sphinx,
877881
doctree: nodes.document,
878882
fromdocname: str,
879883
) -> None:
880-
"""Resolve ``{tool}`` placeholders into links with safety badges.
884+
"""Resolve ``{tool}`` and ``{toolref}`` placeholders into links.
885+
886+
``{tool}`` renders as ``code`` + safety badge.
887+
``{toolref}`` renders as ``code`` only (no badge).
881888
882889
Runs at ``doctree-resolved`` — after all labels are registered and
883890
standard ``{ref}`` resolution is done.
@@ -888,6 +895,7 @@ def _resolve_tool_refs(
888895

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

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

911-
tool_info = tool_data.get(tool_name)
912-
if tool_info:
913-
newnode += nodes.Text(" ")
914-
newnode += _safety_badge(tool_info.safety)
919+
if show_badge:
920+
tool_info = tool_data.get(tool_name)
921+
if tool_info:
922+
newnode += nodes.Text(" ")
923+
newnode += _safety_badge(tool_info.safety)
915924

916925
node.replace_self(newnode)
917926

@@ -930,7 +939,26 @@ def _tool_role(
930939
Creates a placeholder node resolved later by ``_resolve_tool_refs``.
931940
"""
932941
target = text.strip().replace("_", "-")
933-
node = _tool_ref_placeholder(rawtext, reftarget=target)
942+
node = _tool_ref_placeholder(rawtext, reftarget=target, show_badge=True)
943+
return [node], []
944+
945+
946+
def _toolref_role(
947+
name: str,
948+
rawtext: str,
949+
text: str,
950+
lineno: int,
951+
inliner: object,
952+
options: dict[str, object] | None = None,
953+
content: list[str] | None = None,
954+
) -> tuple[list[nodes.Node], list[nodes.system_message]]:
955+
"""Inline role ``:toolref:`capture-pane``` → code-linked tool name, no badge.
956+
957+
Like ``{tool}`` but without the safety badge. Use in dense contexts
958+
(tables, inline prose) where badges would be too heavy.
959+
"""
960+
target = text.strip().replace("_", "-")
961+
node = _tool_ref_placeholder(rawtext, reftarget=target, show_badge=False)
934962
return [node], []
935963

936964

@@ -958,6 +986,7 @@ def setup(app: Sphinx) -> ExtensionMetadata:
958986
app.connect("doctree-resolved", _add_section_badges)
959987
app.connect("doctree-resolved", _resolve_tool_refs)
960988
app.add_role("tool", _tool_role)
989+
app.add_role("toolref", _toolref_role)
961990
app.add_role("badge", _badge_role)
962991
app.add_directive("fastmcp-tool", FastMCPToolDirective)
963992
app.add_directive("fastmcp-tool-input", FastMCPToolInputDirective)

docs/_static/css/custom.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ h4:has(> .sd-badge) {
258258
.sd-badge {
259259
display: inline-flex !important;
260260
align-items: center;
261+
vertical-align: middle;
261262
gap: 0.28rem;
262263
font-size: 0.67rem;
263264
font-weight: 650;
@@ -332,6 +333,11 @@ code.docutils + .sd-badge,
332333
/* ── Link behavior: underline code only, on hover ───────── */
333334
a.reference .sd-badge {
334335
text-decoration: none;
336+
vertical-align: middle;
337+
}
338+
339+
a.reference:has(.sd-badge) code {
340+
vertical-align: middle;
335341
}
336342

337343
a.reference:has(.sd-badge) {

docs/conf.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
},
9696
],
9797
"source_repository": f"{about['__repository__']}/",
98-
"source_branch": "master",
98+
"source_branch": "main",
9999
"source_directory": "docs/",
100100
"announcement": "<em>Pre-alpha.</em> APIs may change. <a href='https://github.com/tmux-python/libtmux-mcp/issues'>Feedback welcome</a>.",
101101
}
@@ -140,7 +140,7 @@
140140

141141
# sphinxext-rediraffe
142142
rediraffe_redirects = "redirects.txt"
143-
rediraffe_branch = "master~1"
143+
rediraffe_branch = "main~1"
144144

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

261261
if "dev" in about["__version__"]:
262-
return "{}/blob/master/{}/{}/{}{}".format(
262+
return "{}/blob/main/{}/{}/{}{}".format(
263263
about["__repository__"],
264264
"src",
265265
about["__package_name__"],

docs/demo.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
---
2+
orphan: true
3+
---
4+
5+
# Badge & Role Demo
6+
7+
A showcase of the custom Sphinx roles and visual elements available in libtmux-mcp documentation.
8+
9+
## Safety badges
10+
11+
Standalone badges via `{badge}`:
12+
13+
- {badge}`readonly` — green, read-only operations
14+
- {badge}`mutating` — amber, state-changing operations
15+
- {badge}`destructive` — red, irreversible operations
16+
17+
## Tool references
18+
19+
### `{tool}` — code-linked with badge
20+
21+
{tool}`capture-pane` · {tool}`send-keys` · {tool}`search-panes` · {tool}`wait-for-text` · {tool}`kill-pane` · {tool}`create-session` · {tool}`split-window`
22+
23+
### `{toolref}` — code-linked, no badge
24+
25+
{toolref}`capture-pane` · {toolref}`send-keys` · {toolref}`search-panes` · {toolref}`wait-for-text` · {toolref}`kill-pane` · {toolref}`create-session` · {toolref}`split-window`
26+
27+
### `{ref}` — plain text link
28+
29+
{ref}`capture-pane` · {ref}`send-keys` · {ref}`search-panes` · {ref}`wait-for-text` · {ref}`kill-pane` · {ref}`create-session` · {ref}`split-window`
30+
31+
## Badges in context
32+
33+
### In a heading
34+
35+
These are the actual tool headings as they render on tool pages:
36+
37+
> `capture_pane` {badge}`readonly`
38+
39+
> `split_window` {badge}`mutating`
40+
41+
> `kill_session` {badge}`destructive`
42+
43+
### In a table
44+
45+
| Tool | Tier | Description |
46+
|------|------|-------------|
47+
| {toolref}`list-sessions` | {badge}`readonly` | List all sessions |
48+
| {toolref}`send-keys` | {badge}`mutating` | Send commands to a pane |
49+
| {toolref}`kill-pane` | {badge}`destructive` | Destroy a pane |
50+
51+
### In prose
52+
53+
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.
54+
55+
### Dense inline (toolref, no badges)
56+
57+
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`.
58+
59+
## Environment variable references
60+
61+
{envvar}`LIBTMUX_SOCKET` · {envvar}`LIBTMUX_SAFETY` · {envvar}`LIBTMUX_SOCKET_PATH` · {envvar}`LIBTMUX_TMUX_BIN`
62+
63+
## Glossary terms
64+
65+
{term}`SIGINT` · {term}`SIGQUIT` · {term}`MCP` · {term}`Safety tier` · {term}`Pane` · {term}`Session`
66+
67+
## Admonitions
68+
69+
```{tip}
70+
Use {tool}`search-panes` before {tool}`capture-pane` when you don't know which pane has the output you need.
71+
```
72+
73+
```{warning}
74+
Do not call {toolref}`capture-pane` immediately after {toolref}`send-keys` — there is a race condition. Use {toolref}`wait-for-text` between them.
75+
```
76+
77+
```{note}
78+
All tools accept an optional `socket_name` parameter for multi-server support.
79+
```
80+
81+
## Badge anatomy
82+
83+
Each badge renders as:
84+
85+
```html
86+
<span class="sd-badge sd-bg-success"
87+
role="note"
88+
aria-label="Safety tier: readonly">
89+
🔍 readonly
90+
</span>
91+
```
92+
93+
Features:
94+
- **Emoji icon** — 🔍 readonly, ✏️ mutating, 💣 destructive (native system emoji, no filters)
95+
- **Matte colors** — forest green, smoky amber, matte crimson with 1px border
96+
- **Accessible**`role="note"` + `aria-label` for screen readers
97+
- **Non-selectable**`user-select: none` so copying tool names skips badge text
98+
- **Context-aware sizing** — slightly larger in headings, smaller inline
99+
- **Sidebar compression** — badges collapse to colored dots in the right-side TOC
100+
- **Heading flex**`h2/h3/h4:has(.sd-badge)` centers badge against cap-height

docs/glossary.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,10 @@ Safety tier
3333
3434
Socket
3535
The Unix socket used to communicate with a tmux server. Can be specified by name (`-L`) or path (`-S`).
36+
37+
SIGINT
38+
Interrupt signal (Ctrl-C). Sent via {toolref}`send-keys` with `keys: "C-c"` and `enter: false`. Most processes terminate gracefully on SIGINT.
39+
40+
SIGQUIT
41+
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.
3642
```

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ clients
9393
:caption: Use it
9494
9595
tools/index
96+
recipes
9697
configuration
9798
```
9899

docs/quickstart.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ Here are a few things to try:
3030
3131
> Search all my panes for the word "error".
3232
33+
## How it works
34+
35+
When you say "run `make test` and show me the output", the agent executes a three-step pattern:
36+
37+
1. {ref}`send-keys` — send the command to a tmux pane
38+
2. {ref}`wait-for-text` — wait for the shell prompt to return (command finished)
39+
3. {ref}`capture-pane` — read the terminal output
40+
41+
This **send → wait → capture** sequence is the fundamental workflow. Most agent interactions with tmux follow this pattern or a variation of it.
42+
3343
## Next steps
3444

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

0 commit comments

Comments
 (0)