Repo layout
Layer rules
libs/<service>/wraps one external SDK or API with idiomatic Python types and functions. Nolibs/<x>may import fromlibs/<y>— if two adapters need to coordinate, that belongs insrc/.src/chains adapters into workflows and owns side effects. Modal@app.functionand@modal.fastapi_endpointdecorators live here, never inlibs/.cli/is Typer-only: parse arguments → preflight → call intosrc/→ render output. No business logic.data-gen/products are independent; they do not depend on each other.
libs/, business logic inside
cli/, cross-lib imports.
Where new code goes
- External SDK call? → New file in
libs/<service>/. Wrap one SDK only. - Multi-step flow or Modal endpoint? →
src/<service>/. If it defines endpoints, register the module in_ENDPOINT_MODULESinsrc/app.py, or its decorators never run. - User-facing command? →
cli/<group>/as a Typer subapp, wired intocli/main.pyviaapp.add_typer(...). - Standalone data product? →
data-gen/<product>/, self-contained. - Webhook handler? →
webhooks/<name>.py, an independent Modal app (never registered insrc/app.py).
Adding a new top-level package also means updating
[tool.setuptools.packages.find] in pyproject.toml (currently cli*, libs*,
scripts*, src*).The adapter pattern
Every keyed adapter exposes the same client shape: aget_client() (or module-level
functions that build one) with a three-tier key resolution — explicit api_key=
argument, then the api_key_scope contextvar, then the adapter’s environment variable.
Secrets and API keys covers why.
Execution model
The CLI is a thin client: provider-backed commands invoke functions deployed on Modal by name rather than calling provider APIs from your machine. Local-only commands (gtm version, gmail url decode, granola export, sanity blog download,
webhook list) run in-process. The CLI contract page describes
the I/O and mutation-gating conventions shared by every command.
Related directories
api/specs/andapi/samples/are read-only reference: OpenAPI specs for external services and redacted real webhook payloads used as test fixtures. Webhook models are validated against captured payloads, not hand-authored fixtures.data-gen/currently ships one product (marketplace, behinduv sync --extra marketplace).tests/mirrors the source layout; see Development.