`reach --help` runs from the console entrypoint in 80 ms. typer 0.27.1 and pydantic 2.13.4 join the dependencies, both CVE-checked against NVD, OSV and the GitHub Advisory Database. The design in the ticket did not survive contact. It specified a click.Group root, on the reasoning that it would keep typer off the --help path — but typer vendors Click as of 0.26.0, so there is no top-level click package to import and no supported way to extract typer's internal one. A click.Group root hosting Typer sub-apps would put two Click implementations in one process. The root is therefore a typer.Typer, and lazy registration will go through the supported typer.Typer(cls=...) surface with a TyperGroup subclass. T-1260 is corrected to match. The callback is not decoration: a Typer root with no commands AND no callback raises at build time, and lazy registration means no command is ever eager. The ticket claimed a zero-command root always raises — half right, and the half that matters is that a callback makes it legal. rich_markup_mode=None is load-bearing rather than cosmetic. It takes an empty --help from 168 ms to 74 ms, and keeps rich and pygments off the import path entirely rather than merely skipping the render. It also stops typer drawing box-art help, which it does even when stdout is a pipe — that would have put box-drawing characters into every hook log and agent capture. typer-slim was considered and rejected: deprecated since 0.22.0, now a shallow wrapper that installs all of typer. D-263 amended: the feels-instant ceiling goes from 250 ms to 500 ms. A ceiling is not a typical and most invocations sit far below it; the tighter number was buying discipline that the import-graph assertion enforces better. Stay smart about what loads, stop worrying about tightness. Security, checked 2026-08-23. typer has no advisories on record. pydantic 2.13.4 clears PYSEC-2026-1812 (email-regex ReDoS, fixed in 2.4.0) — and the 2026 SSRF advisories CVE-2026-25580 and CVE-2026-54249 are against pydantic-ai, a different package that is not a dependency here, recorded in pyproject so the next sweep does not re-panic. Transitively, pygments 2.21.0 clears CVE-2026-4539. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
51 lines
2.2 KiB
Python
51 lines
2.2 KiB
Python
"""`reach` — one command for every repo tool (D-263).
|
|
|
|
**This file is a router and nothing else.** No logic, no I/O, no pydantic, no
|
|
domain imports at module level. It is the file most likely to accumulate "just
|
|
one small thing", and the only defence is that it stays short enough that an
|
|
addition is obvious in review.
|
|
|
|
The root is a `typer.Typer`, and the reason is worth recording because the
|
|
original plan was different. T-1259 specified a `click.Group` root, on the
|
|
theory that it would keep typer off the `reach --help` path. **That is no longer
|
|
possible: typer vendors click as of 0.26.0** — there is no top-level `click`
|
|
package to import, and the docs are explicit that "extracting the internal Click
|
|
app" is unsupported. Mixing a real `click.Group` root with typer sub-apps would
|
|
mean two different Click implementations in one process.
|
|
|
|
So the customisation surface is `typer.Typer(cls=...)` with a `TyperGroup`
|
|
subclass, which is the supported path and is what T-1260 uses to register
|
|
domains lazily.
|
|
|
|
`rich_markup_mode=None` is not a style preference — it is worth 94 ms of the
|
|
168 ms an empty `--help` otherwise costs, and it keeps `rich` and `pygments`
|
|
off the import path entirely (verified absent from `sys.modules`). It also
|
|
stops typer drawing box-art help, which it does **even when stdout is a pipe**,
|
|
so hook logs and agent output stay readable. One line to revert if the boxes
|
|
are ever wanted more than the milliseconds.
|
|
|
|
The callback below is not decoration. A `typer.Typer` with **no commands and no
|
|
callback** raises `RuntimeError: Could not get a command for this Typer
|
|
instance` at build time; with a callback it builds fine and prints help. Since
|
|
domains are registered lazily and none are eager, the callback is what makes an
|
|
empty root legal.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import typer
|
|
|
|
cli = typer.Typer(
|
|
name="reach",
|
|
help="Repo tooling for The Settled Reach.\n\nRun `reach <domain> --help` to see what a domain can do.",
|
|
no_args_is_help=True,
|
|
add_completion=False,
|
|
rich_markup_mode=None,
|
|
context_settings={"help_option_names": ["-h", "--help"], "max_content_width": 100},
|
|
)
|
|
|
|
|
|
@cli.callback()
|
|
def root() -> None:
|
|
"""Present so an empty root is legal — see the module docstring."""
|