Metadata-Version: 2.3
Name: termhelp
Version: 0.1.0
Summary: Add your description here
Author: Stefane Fermigier
Author-email: Stefane Fermigier <sf@abilian.com>
Requires-Dist: markdown-it-py>=4.2.0
Requires-Dist: prompt-toolkit>=3.0.52
Requires-Python: >=3.12
Description-Content-Type: text/markdown

# 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`](https://markdown-it-py.readthedocs.io/) and [`prompt_toolkit`](https://python-prompt-toolkit.readthedocs.io/).

## 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:

```bash
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:

```python
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):

```python
# 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")
```

```python
# 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:

```bash
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`](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

```bash
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](https://zensical.org/). Configuration lives in `zensical.toml` at the repo root. To preview locally:

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

## License

MIT (placeholder — see `LICENSE` once added).
