8.4 KiB
Phase 2 Probe Testing
Install dependencies:
uv sync
Optional environment setup:
cp .env.example .env
Fill GARMIN_EMAIL and GARMIN_PASSWORD in .env, or leave them blank and the scripts will prompt. Tokens are stored in .garmin-tokens by default.
Garmin Coach cycling detail currently comes from this Connect API endpoint:
/workout-service/fbt-adaptive/<workoutUuid>
The browser network analyzer shows the same route under /gc-api/..., but the CLI should use the Connect API route with normal python-garminconnect tokens. If you are debugging the browser-only /gc-api route specifically, you can copy the Cookie request header into .env:
GARMIN_GC_API_COOKIE=JWT_WEB=...; ...
Treat that cookie like a password. Do not commit .env, screenshots containing the cookie, or debug logs with request headers.
Local Checkout Validation
uv run python scripts/local_check.py
Expected:
PASS: ruff check .PASS: ty checkPASS: pytestPASS: ... scripts/probe_garmin.py dummy --date tomorrow --dry-rundebug/local_checks.jsonis written.
This command does not log in to Garmin or upload anything.
Login
uv run python scripts/probe_garmin.py login
Expected:
- If cached tokens work, it prints account/device summary.
- If credentials are needed, it prompts for email/password.
- If MFA is required, it prompts for the MFA code.
- It must not print the Garmin password or MFA code.
Discovery
Preferred one-pass read-only report:
uv run python scripts/probe_garmin.py report --dump-json
This writes debug/probe_report.json. It logs in once and collects account summary, training plans, workout type metadata, normal workouts, today/tomorrow calendar entries, and today/tomorrow Coach/adaptive match summaries. It continues through partial API failures and records errors in the report.
Individual discovery commands:
uv run python scripts/probe_garmin.py plans
uv run python scripts/probe_garmin.py types
uv run python scripts/probe_garmin.py calendar --date today --dump-json
uv run python scripts/probe_garmin.py calendar --date tomorrow --dump-json
uv run python scripts/probe_garmin.py workouts --limit 25 --dump-json
uv run python scripts/probe_garmin.py generated --prefix GCClone
uv run python scripts/probe_garmin.py generated-calendar --date today --prefix GCClone
Check:
- Training plans are listed.
- Any plan marked
FBT_ADAPTIVE, Coach, or adaptive is shown as a candidate. - Workout type metadata is written to
debug/workout_types.json. - Calendar JSON dumps are written under
debug/. - Normal workouts are visible.
- Generated probe/clone workout templates are listed separately by prefix, if any exist.
- Generated scheduled calendar entries are listed separately by prefix for the inspected month, if any exist.
If you have a normal cycling workout that already syncs to the Edge 1030, inspect it:
uv run python scripts/probe_garmin.py workout <workout-id> --dump-json
This writes debug/workout_<workout-id>.json, which is useful as a known-good schema reference.
debug/ is gitignored because dumps can contain private Garmin account data even after redaction.
Coach Workout Inspection
uv run python scripts/probe_garmin.py coach --date today --dump-json
uv run python scripts/probe_garmin.py coach --date tomorrow --dump-json
Useful output:
Workout-like objects with workoutSegments/workoutSteps: N- A readable step summary with warmup/interval/recovery/cooldown/repeat steps.
debug/coach_workout_YYYY-MM-DD.jsonwhen a matched workout is found.debug/coach_workout_today.jsonwhen today's matched workout is found.- The probe should fetch the full
fbt-adaptivedetail fromworkoutUuid.
Blocking output:
No parseable workoutSegments/workoutSteps found for this date.
If blocked, report the command output and keep the redacted JSON in debug/ for inspection.
If coach finds dated Coach task summaries but no step arrays, run the read-only endpoint sweep:
uv run python scripts/probe_garmin.py coach-endpoints --date today --dump-json
Expected:
- It prints the plan ID, workout name,
workoutUuid, and calendar item ID. - It tries candidate private endpoints and continues through 404/errors.
- It writes
debug/coach_endpoint_probe_YYYY-MM-DD.json.
Useful output:
At least one candidate endpoint returned workout-like step data.
Blocking output:
No candidate endpoint returned workoutSegments/workoutSteps.
For the June 16, 2026 account probe, /workout-service/fbt-adaptive/<workoutUuid> returned step arrays. /workout-service/workout/uuid/<workoutUuid> returned an empty list, and other candidate detail endpoints returned errors.
Offline Dump Analysis
Analyze a saved dump without logging in to Garmin:
uv run python scripts/analyze_dump.py debug/coach_workout_today.json --date today --clone-dry-run --dump-json
Expected, if the dump contains a dated workout:
- Date-containing object count is shown.
- Workout-like object count is shown.
- A matched workout summary is printed.
Local clone payload passed validation.is printed if conversion is structurally safe.
If the dump does not contain a dated workout-like object, the command exits non-zero and prints:
No dated workout-like object was found.
Dummy Upload And Schedule Test
Dry run:
uv run python scripts/probe_garmin.py dummy --date tomorrow --dry-run
Expected:
Dummy workout payload passed local validation.- The generated JSON is printed.
- Nothing is uploaded.
Upload and schedule:
uv run python scripts/probe_garmin.py dummy --date tomorrow --schedule
After upload/schedule succeeds:
- Sync the Edge 1030 with Garmin Connect / Garmin Express.
- Check
Training > Workouts. - Check
Training > Training Plan > calendar icon > tomorrow's date. - Start normal route/course navigation, then start the dummy workout.
- Confirm whether workout prompts and navigation can run together.
Report:
- Did the dummy workout appear in Garmin Connect?
- Did it appear on the Edge 1030?
- Did it appear in the calendar/date view?
- Could it start while navigation was active?
Clone Test
Dry run:
uv run python scripts/clone_today_workout.py --date today --dry-run --dump-json
Expected:
Converted clone payload passed local validation.- The converted JSON is printed.
- Nothing is uploaded.
Upload and schedule only if the dry run shows plausible steps:
uv run python scripts/clone_today_workout.py --date today --schedule
The clone is named:
GCClone YYYY-MM-DD <original workout name>
Repeated runs skip an existing clone for the same date unless --force is passed.
Report:
- Did the cloned workout appear in Garmin Connect?
- Did it appear on the Edge 1030 under workouts/calendar?
- Can it be started on the Edge?
- Can it run together with normal Edge navigation?
- After completion, did Garmin Coach mark the original Coach workout complete?
Generated Workout Cleanup
List generated workout templates:
uv run python scripts/probe_garmin.py generated --prefix GCClone
List generated scheduled calendar entries for the month containing --date:
uv run python scripts/probe_garmin.py generated-calendar --date today --prefix GCClone
Unschedule a generated calendar entry only if you are sure the scheduled ID is from the generated-prefix calendar list:
uv run python scripts/probe_garmin.py generated-calendar --date today --prefix GCClone --unschedule-id <scheduled-id> --confirm
Delete a generated workout template only if you are sure the ID is from the generated-prefix list:
uv run python scripts/probe_garmin.py generated --prefix GCClone --delete-id <workout-id> --confirm
Expected:
- Without
--delete-idor--unschedule-id, these commands are read-only. - With
--delete-idor--unschedule-id, the command refuses to run unless--confirmis also passed. - The commands refuse IDs that are not in the generated-prefix list.
Unknown:
- Whether deleting the workout template also removes any scheduled calendar entry. Prefer unscheduling first, then check Garmin Connect calendar afterward and report what happened.
Current Acceptance Gate
Phase 3 should not start until the dummy scheduled workout works on the Edge 1030 and the Coach inspection either exposes parseable steps or we explicitly choose a fallback architecture.