Files
settled-reach/tooling/main.py
T
jpmschweitzerandClaude Opus 5 8d64800fe9 feat(config): T-1259 — reach is a real command, and Typer vendors Click
`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>
2026-08-23 14:01:32 +02:00

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."""