# Debug CLI Design **Date:** 2026-02-22 **Status:** Approved ## Goal Add a debug CLI that mirrors all web UI interactions, with superuser access to impersonate any user. Purpose: debug flows without authorization overhead. ## Decisions - **Scope:** All API endpoints (listings, decisions, POIs, tasks, districts) - **Execution mode:** Switchable — direct service calls (default) or HTTP to running API (`--http`) - **User identity:** `--user-email` flag to impersonate any user - **Location:** New `cli/` package with submodules, registered under a `debug` group in `main.py` - **Output:** Pretty-printed by default, `--json` flag for machine-readable output ## Structure ``` cli/ __init__.py # Exports all groups for registration in main.py _context.py # Shared context: user identity, HTTP vs direct, output formatting listings.py # list, detail, stream, refresh decisions.py # set, list, remove pois.py # create, list, update, delete, calculate, distances tasks.py # status, list, cancel, clear districts.py # list ``` ### Usage ```bash python main.py debug --user-email user@example.com listings list --type RENT --min-bedrooms 2 python main.py debug --user-email user@example.com --http decisions set 12345 liked --type RENT python main.py debug --user-email user@example.com --json pois list ``` ## Shared Context (`_context.py`) - **`CliContext` dataclass:** Holds `user_email`, `use_http`, `json_output`, `api_base_url` - **`get_user()`:** Creates `User(sub=email, email=email, name=email)` from provided email — no token validation - **Direct mode:** Instantiates repositories and calls service functions, passing the fake `User` - **HTTP mode:** Mints a self-signed JWT using `JWT_SECRET` from env (passkey-style token), makes requests via `httpx` - **Output helpers:** `output(data, json_mode)` — prints JSON or pretty-formatted text ## Command Mapping ### `listings` group | Command | Direct mode | HTTP mode | |---------|------------|-----------| | `list` | `listing_service.get_listings()` | `GET /api/listing` | | `detail ` | Repository + poi_service | `GET /api/listing/{id}/detail` | | `stream` | `export_service.export_to_geojson()` | `GET /api/listing_geojson/stream` | | `refresh` | `listing_service.refresh_listings()` | `POST /api/refresh_listings` | ### `decisions` group | Command | Direct mode | HTTP mode | |---------|------------|-----------| | `set ` | `decision_service.set_decision()` | `PUT /api/decisions/{id}` | | `list` | `decision_service.get_user_decisions()` | `GET /api/decisions` | | `remove ` | `decision_service.remove_decision()` | `DELETE /api/decisions/{id}` | ### `pois` group | Command | Direct mode | HTTP mode | |---------|------------|-----------| | `list` | `poi_service.get_user_pois()` | `GET /api/poi` | | `create` | `poi_service.create_poi()` | `POST /api/poi` | | `update ` | `poi_service.update_poi()` | `PUT /api/poi/{id}` | | `delete ` | `poi_service.delete_poi()` | `DELETE /api/poi/{id}` | | `calculate ` | `poi_service.trigger_calculation()` | `POST /api/poi/{id}/calculate` | | `distances` | `poi_service.get_distances_for_listing()` | `GET /api/poi/distances` | ### `tasks` group | Command | Direct mode | HTTP mode | |---------|------------|-----------| | `status ` | `task_service.get_task_status()` | `GET /api/task_status` | | `list` | `task_service.get_user_tasks()` | `GET /api/tasks_for_user` | | `cancel ` | `task_service.cancel_task()` | `POST /api/cancel_task` | | `clear` | `task_service.clear_all_tasks()` | `POST /api/clear_all_tasks` | ### `districts` group | Command | Direct mode | HTTP mode | |---------|------------|-----------| | `list` | `district_service.get_all_districts()` | `GET /api/get_districts` | ## Error Handling - **Direct mode:** Catch service exceptions, print readable messages (or `{"error": "..."}` in JSON mode) - **HTTP mode:** Print status code + response body on non-2xx - **Missing services:** Fail fast with clear message about which service (DB, Redis) is unavailable ## Testing - Unit tests for `_context.py` (JWT minting, user creation, output formatting) - Integration tests for representative commands in direct mode (mocked repositories) - No HTTP-mode tests (that's API testing, already covered) ## Dependencies No new dependencies. Uses existing: `click`, `httpx`, `pyjwt`.