CLI (components/cli/)
Development CLI for the Polo monorepo. Single entry point for local dev, testing, building, and deploying.
Design Decisions
Why a CLI instead of a Makefile:
- Subcommand groups (
polo test unit,polo deploy worker) are more discoverable than flat make targets - Click gives us
--helpfor free at every level - Options with defaults and validation (e.g.
--env staging,--days 30) - Python — same language as the rest of the backend, easy to extend with logic when a command outgrows a shell one-liner
- Makefile syntax is arcane for anything beyond trivial recipes (eval/shell timing, escaping, no real control flow)
Thin wrapper, not a framework:
- Commands delegate to existing tools (pytest, wrangler, docker compose) via
subprocess.run - No abstraction layer over the underlying commands — if pytest args change, the CLI command changes
_run()is the only helper: runs a command, exits with its return code- No config files, no plugins, no registries
Subcommand structure:
- Top-level commands for daily-driver operations:
up,down,migrate,seed,sync,health testgroup for test suites:schema,unit,integration,perf,e2e,allbuildgroup for artifacts:uideploygroup for deployment:worker- Groups exist where there are (or will be) multiple related subcommands
Command Reference
Local Development
| Command | Description |
|---|---|
polo up | Start ClickHouse via Docker Compose |
polo down | Stop ClickHouse |
polo migrate | Apply schema migrations to local ClickHouse |
polo migrate-remote <id> | Run migrations against remote CH via SSM port forwarding |
polo seed | Seed test data into local ClickHouse |
polo sync | Snapshot production data into local instance |
polo health | Run repo health checks |
Testing
| Command | Description |
|---|---|
polo test schema | Schema tests (requires CH) |
polo test unit | Unit tests (no CH needed) |
polo test integration | Integration tests (requires CH) |
polo test perf | Performance benchmarks |
polo test e2e | E2E tests (requires AWS + CH) |
polo test all | All of the above in order |
Build & Deploy
| Command | Description |
|---|---|
polo build ui | Build the React SPA |
polo deploy worker [--env staging] | Deploy the Cloudflare Worker |
Adding Commands
New commands go in components/cli/commands.py. Pattern:
@polo.command()
@click.option("--flag", default="value", help="Description.")
def my_command(flag: str) -> None:
"""One-line description shown in --help."""
_run(["some-tool", "--flag", flag])
For a new subcommand under an existing group:
@deploy.command("infra")
@click.option("--env", default="staging")
def deploy_infra(env: str) -> None:
"""Deploy AWS infrastructure via CDK."""
_run(["cdk", "deploy", "--all"], env={"ENV": env})
Guidelines:
- Keep commands thin — delegate to the underlying tool
- Use
_run()for subprocess execution (handles exit codes) - Use
cwdparameter when a command must run from a specific directory - Use
envparameter for environment variable overrides