Skip to main content

Add your description here

Project description

termhelp

An interactive Markdown help browser for Python CLI applications.

Write your help docs as Markdown, ship them inside your package, then add an -i / --interactive flag to your help command. Your users get a keyboard-driven, scrollable, searchable, cross-referenced help system instead of a wall of --help text that scrolls off-screen.

No Rich, no Textual — just markdown-it-py and prompt_toolkit.

What it looks like

A single full-screen page with a title bar, the rendered topic, and a status line. Cross-topic and anchor links are highlighted; pressing / walks between them, Enter follows. Press / to search, p for a fuzzy topic picker, ? for the in-app keymap.

 termhelp · Configuration  [3/4]  L1-22/45
 ─────────────────────────────────────────────────
  # Configuration

  Back to the index — see also Getting started.

  myapp is configured via a TOML file. Every key
  is optional; defaults are shown below.

  ## Top-level keys

  ┌─────────┬───────┬───────────┬───────────────┐
  │ Key     │ Type  │ Default   │ Description   │
  ├─────────┼───────┼───────────┼───────────────┤
  │ profile │ table │ {}        │ Named conn... │
  │ log     │ table │ see below │ Logging conf. │
  └─────────┴───────┴───────────┴───────────────┘

 ─────────────────────────────────────────────────
  ↑/↓ link · ←/→ back/follow · / search · p pick

The model is Lynx + :help: arrows walk between hyperlinks, goes back in history, / thread through your topics like web pages, / searches inside the current page, and p jumps to any topic by name.

Quick start

Clone the repo and try the bundled demo docs:

git clone https://github.com/.../termhelp
cd termhelp
uv sync
uv run python -m termhelp tests/fixtures/docs

Move around with the arrow keys; press ? for the full keymap.

Add it to your CLI

The whole public API is one function:

from termhelp import browse

browse("path/to/docs")               # open the first topic (index.md if present)
browse("path/to/docs", entry="config")  # open a specific topic

A typical wiring with argparse (the same pattern works with Click and Typer):

# myapp/help.py
from importlib.resources import files
from termhelp import browse

def help_interactive() -> None:
    docs_dir = files("myapp") / "docs"
    browse(docs_dir, entry="index")
# myapp/cli.py
parser.add_argument("-i", "--interactive", action="store_true")
# ...
if args.interactive:
    from myapp.help import help_interactive
    help_interactive()

You can also invoke termhelp directly during authoring:

python -m termhelp ./docs              # browse a docs dir
python -m termhelp ./docs -e config    # open straight on the 'config' topic

Writing your help docs

Drop a directory of .md files anywhere in your package. Each file is one topic; the filename (without .md) is the topic slug; the first # heading becomes the topic title shown in the browser.

myapp/docs/
├── index.md
├── getting-started.md
├── config.md
└── commands.md

index.md, if present, is always listed first. Other topics are sorted alphabetically.

Cross-references

Standard Markdown links power navigation between topics:

You write... What happens when followed
[Config](config.md) or [Config](config) Switch to the config topic.
[Top](#top-level-keys) Scroll to that heading in the current topic.
[Env overrides](config.md#environment-overrides) Switch topic and scroll to the anchor.
[Project](https://example.com) Show the URL in the status bar (so the user can copy it). termhelp does not open URLs on the user's behalf.

Anchor slugs are generated by lowercasing the heading and replacing whitespace with -, matching the convention used by most Markdown renderers and static-site generators.

Key bindings

The full reference is always one keystroke away (press ? in the app).

Navigation

Key Action
/ Move between links on the page. Past the first or last link, scrolls one line. On link-free pages, scrolls directly.
j / k Scroll one line down / up.
Space / f / PageDown Page down.
b / PageUp Page up.
g / G Jump to top / bottom of the page.
h Go to the home (first) topic. If already there, scroll to top.

Links and history

Key Action
/ Enter Follow the selected link.
Back in history.
Esc Clear search highlights / deselect the current link.

Search

Key Action
/ Open the search prompt (Enter confirms, Esc cancels).
n / N Next / previous match.

Topic picker

Key Action
p Open the topic picker (a filterable list of every topic).
type Filter by slug or title (case-insensitive).
/ Move selection.
Enter Open the selected topic.
Esc Close the picker.

Help and quit

Key Action
? Toggle the in-app help overlay.
q Quit the app (in browse view) or close the help overlay (in help view).
Ctrl-C Quit, anywhere.

Supported Markdown

Construct Status
Headings ####### Yes; anchor slugs auto-generated
Paragraphs (width-aware word wrap) Yes
Bullet and ordered lists Yes (basic nesting)
Fenced code blocks (```) Yes; no syntax highlighting
Inline code, bold, italic, strike Yes
Links (internal, anchor, external) Yes
Tables (GFM pipe syntax) Yes; box-drawn borders, bold headers
Block quotes (>) Yes; rendered with a margin
Horizontal rules (---) Yes
Images Ignored
HTML passthrough Ignored

Search is case-insensitive substring matching on the rendered plain text.

Status and stability

This is the first public release (0.1.0). The public API — browse(docs_dir, entry=None) — is intentionally minimal and not expected to change. Cosmetic details (key bindings, status-bar wording, color choices) may still evolve before 1.0. See CHANGES.md for the release log.

Limitations and non-goals

  • No mouse support — keyboard-first by design.
  • No syntax highlighting in code blocks (would pull in pygments; out of scope).
  • No HTML rendering or image display.
  • Search is substring, not regex, and not incremental.
  • Anchor-slug generation is heuristic; collisions are theoretically possible in long docs.
  • termhelp shows external URLs in the status bar but does not launch them in a web browser.

How it works (for the curious)

Three modules, roughly 700 lines total:

  • loader.py — scans a directory of *.md files, parses each with markdown-it-py, extracts the title from the first # heading.
  • renderer.py — walks the Markdown token stream and produces a Document containing:
    • lines — pre-wrapped styled lines ready for prompt_toolkit
    • links — target, kind (internal / anchor / external), and the line indices the link spans
    • anchors — heading slug → line index, used for in-page jumps
  • browser.py — a prompt_toolkit Application with a title bar, content pane, and status/search bar. State (current topic, scroll, selected link, history stack, search query, picker filter, mode) lives on a single Browser object. Four UI modes share the same three windows: browse (the rendered page), search (the / prompt), picker (the p topic list), help (the ? overlay).

The renderer re-runs whenever the terminal width changes, so wrapping always matches the current window. Search highlights and selected-link styling are layered on top of the rendered segments at draw time.

Development

uv sync
make test     # pytest
make lint     # ruff + ty + pyrefly + mypy
make format   # ruff format & fix

Test layout:

  • tests/a_unit/ — unit tests for loader, renderer, and browser state transitions
  • tests/fixtures/docs/ — the sample help directory used by tests and the manual demo

Documentation site

The longer-form documentation is in docs/, built with Zensical. Configuration lives in zensical.toml at the repo root. To preview locally:

uv tool install zensical          # one-off
zensical serve                    # http://127.0.0.1:8000

License

MIT (placeholder — see LICENSE once added).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

termhelp-0.1.0.tar.gz (15.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

termhelp-0.1.0-py3-none-any.whl (18.0 kB view details)

Uploaded Python 3

File details

Details for the file termhelp-0.1.0.tar.gz.

File metadata

  • Download URL: termhelp-0.1.0.tar.gz
  • Upload date:
  • Size: 15.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for termhelp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 27759d1d5e23bdb50e9138f77c7da87cb58a71eccdef80647d51be9f9e2e68fc
MD5 694437a62901b549006c6aac0e084d81
BLAKE2b-256 e9993f25717195249ec29789fa9a8bb259a094c6f4ef8641ec5b70c7c49e1321

See more details on using hashes here.

File details

Details for the file termhelp-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: termhelp-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 18.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for termhelp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2d330f5b729f60b10c656b74a9c729e1dbd6c8f8b85c72215de088e48541b061
MD5 19ff086375835bad5f95eff68f97c46d
BLAKE2b-256 1409ac484aae6e06c0f76b27444574b9a09905c5a3a43b1d399f9b61a0c218b7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page