gtm-sdk
Go-To-Market SDK + CLI for account research, enrichment, CRM sync, and outreach. Layered architecture: thin CLI → workflow orchestration → single-SDK adapters. Deployable as Modal serverless functions; consumable as an editable Python package or git submodule.
- Package names:
gtm-sdk(library) andgtm-cli(entrypoint script:gtm = cli.main:run) - Python:
>=3.13,<3.14 - Package manager:
uvonly (never barepip) - License: MIT
- Docs:
docs/
Layout
gtm-sdk/
├── cli/ # Thin command surface (Typer). Parses flags, preflight, calls src/.
├── src/ # Workflow orchestration. Chains libs/ adapters. Modal endpoints register here.
├── libs/ # Single-SDK adapters. One folder per external service. NO cross-lib imports.
├── data-gen/ # Reusable data generation/enrichment pipelines (independent, composable).
├── webhooks/ # Standalone Modal webhook handlers (Attio, GCS raw/ETL, Slack).
├── api/
│ ├── specs/ # External API OpenAPI specs (e.g. caldotcom, sanity)
│ └── samples/ # Sample payloads (rb2b, caldotcom, fathom, octolens)
├── tests/ # pytest, importlib mode. Mirrors src/, libs/, cli/.
├── tmp/ # Gitignored scratch. ALL temporary files go here.
├── worktrees/ # Gitignored. All git worktrees under this dir.
├── deploy.py # Modal deploy entrypoint (must stay at root — avoids `attio` pkg shadowing).
├── pyproject.toml
└── uv.lock
Layer rules
The authoritative placement and boundary rules for contributors are in
AGENTS.md, with directory-specific guidance in
webhooks/AGENTS.md, docs/AGENTS.md,
and tests/AGENTS.md.
Adapters (libs/)
One directory per external service (Attio, Apollo, Exa, Parallel, Fathom, Granola, …) plus a few internal utility libs. The list is discoverable — this README does not mirror it:
ls libs/
Every adapter follows the same pattern: get_client() with three-tier API-key resolution —
explicit api_key= argument → api_key_scope contextvar → env var.
Orchestration (src/)
src/app.py— endpoint-module registration (_ENDPOINT_MODULES); re-exportsapp,imagefromsrc.modal_runtime. Edit here when adding new Modal endpoints.src/modal_runtime.py— ModalAppdefinition and image build.src/modal_app.py—MODAL_APPname (env-overridable viaMODAL_APP, defaultgtm-sdk).src/secrets_bootstrap.py— Infisical-backed secret hydration for Modal functions (KEY_SCOPES,@with_secrets,bootstrap_secret()).- One package per domain (
src/accounts/,src/attio/,src/apollo/, …) — discoverable vials src/.
CLI surface
Run via uv run gtm <group> <command> (or uv run python -m cli.main). The command tree is
discoverable — this README does not mirror it:
uv run gtm --help # list command groups
uv run gtm <group> --help # list commands in a group
Contract: success data is JSON on stdout, errors/logs on stderr; mutating commands preview by default and require an explicit flag to execute.
CLI helpers: cli/json_encoder.py, cli/json_validation.py. CLI emits OTEL events (cli.usage_error on exit code 2).
Install
As an editable submodule (preferred when consumed from another repo)
git submodule add git@github.com:elviskahoro/gtm-sdk.git gtm-sdk
In the parent pyproject.toml:
[tool.uv.sources]
gtm-sdk = { path = "gtm-sdk", editable = true }
gtm-cli = { path = "gtm-sdk/cli", editable = true }
Then uv sync. All cli, src, libs packages become importable.
Standalone
git clone git@github.com:elviskahoro/gtm-sdk.git
cd gtm-sdk
uv sync
Enable Entire session capture (per clone)
Git hooks aren't committed, so after cloning on a new device wire up Entire (agent-session checkpoints) plus the anti-AI-co-author enforcement in one step:
scripts/entire-hooks-setup.py
Install the Entire CLI (curl -fsSL https://entire.io/install.sh | bash) and run
entire login first. The script is idempotent — safe to re-run.
Common commands
uv sync # install/lock deps
uv run gtm --help # CLI help
uv run gtm <group> <cmd> --help # subcommand help
uv run pytest # full test suite (importlib mode)
uv run pytest tests/cli # subset
trunk check --all # lint + typecheck (ruff, etc.)
For local Bazel iteration, run one target or package rather than the whole
graph through the committed Flox toolchain, for example
flox activate -- bazel test //tests/libs/attio:TARGET --config=ci or
flox activate -- bazel test //tests/libs/attio/... --config=ci.
The required Unit tests CI check is the ARM64 Dagger Bazel graph with
--test_tag_filters=-manual; Bazel's pytest launcher also excludes tests marked
integration. The optional impacted-target job registers Trunk graph metadata
for same-repository pull requests and is not a replacement for the full suite.
To reproduce the pull-request Bazel job in its Linux ARM64 Dagger container,
run uv run scripts/bazel-dagger-validate.py. It compares HEAD with
origin/main by default; use --base <ref> to select another comparison base.
Modal deployment
uv run modal deploy deploy.py
Deployment constraints and Modal-specific gotchas are maintained in
AGENTS.md.
Build env vars baked into the image: AI_BUILD_GIT_SHA, AI_DEPLOYED_AT.
Webhooks
Standalone Modal apps under webhooks/ — one app per (handler, source) pair via the
WebhookModelToReplace placeholder: export_to_attio.py, export_to_gcp_etl.py,
export_to_gcp_raw.py, export_to_slack.py.
Deploy with scripts/webhooks-handlers-redeploy.py <handler> <source> (or --all) — never
modal deploy webhooks/<file>.py directly (it fails on the placeholder). Full runbook:
webhooks/README.md; rules for agents working there:
webhooks/AGENTS.md.
Telemetry
OTEL traces and logs are provided by libs/telemetry.py; see the
telemetry documentation for configuration and the
authoritative contributor guidance in AGENTS.md.
Setup guides: docs/telemetry/ (Dash0, Grafana Cloud).
Conventions, testing, and agent guidance
Repository conventions, testing requirements, and contributor workflow are
maintained in AGENTS.md and the scoped guidance files linked
there.