wrongmove/docs/plans/2026-02-22-debug-cli-design.md
2026-02-22 15:05:18 +00:00

4.3 KiB

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

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 <id> 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 <id> <liked|disliked> decision_service.set_decision() PUT /api/decisions/{id}
list decision_service.get_user_decisions() GET /api/decisions
remove <id> 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 <id> poi_service.update_poi() PUT /api/poi/{id}
delete <id> poi_service.delete_poi() DELETE /api/poi/{id}
calculate <id> 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 <id> task_service.get_task_status() GET /api/task_status
list task_service.get_user_tasks() GET /api/tasks_for_user
cancel <id> 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.