Your plan, in a text file you own.

Aski schedules running and strength on iPhone and Apple Watch. Hand the plan to ChatGPT, Claude, or Gemini — straight from the Today screen. The plan comes back as a text file in your iCloud.

No subscriptions, no accounts, no analytics — just big numbers, a loud rest timer, and the four metrics that matter.

A week, end to end

Four screens, in order.

Fig. 01 — 04
Aski Plan tab: today’s session at the top, a horizontal week strip below, running and strength side by side.
01 · Plan

Today’s session at the top. A six-day strip across the week. Strength and running, side by side. Tap to start.

Aski mid-set: large rep count and weight, previous set ghosted beneath, coral checkbox.
02 · Mid-set

Big numbers, readable from across the room. The previous set ghosts beneath, pre-filled. One tap confirms.

Aski rest timer: tight coral countdown filling most of the screen.
03 · Rest

A tight coral countdown visible from six feet. Gong at zero, audible tick on the last ten.

Aski session summary: time, sets, heart rate, and volume in a four-up grid.
04 · Summary

Time, sets, heart rate, volume — the four numbers that matter. No badges, no streaks.

  1. 01

    Logging must never interrupt a set.

    Tap the "previous" ghost. Tap the checkbox. Done. No modals, no confirmations, no four-step drawers between you and the next set.

  2. 02

    The rest timer survives everything.

    Live Activity on the Lock Screen, Dynamic Island pill, audible tick on the last 10 seconds, gong at zero. A silent notification with the phone 6 feet away is a failure.

  3. 03

    The watch does what wrists do best.

    Heart rate with zone colour. Session elapsed. Duration timers for timed exercises. Haptic feedback when a rest or duration timer ends. What the watch doesn’t do: log sets. The phone is already doing that, better.

  4. 04

    Onboarding must not block the first set.

    Every question is postponable. Strava, notifications, Watch setup — deferred to Settings until you actually need them. 90 seconds to the first logged rep.

  5. 05

    Track, don’t coach.

    No streaks, no badges, no leaderboards, no daily dopamine push. Aski records what you did; the coaching lives in your chatbot, where it belongs. The app’s job is the schedule and the receipt.

Private by construction

Everything stays on your device.

0 third-party SDKs
0 trackers or analytics
1 opt-in network call · Strava

Workouts live in your iCloud Drive. HealthKit writes stay on the phone. Strava is a single opt-in, one-way upload — with tokens stored in Keychain and revokable any time.

Read the privacy policy
How it works

Hand the plan to any chatbot.

The coach lives in your chatbot — Aski is the schedule and the receipt. Tap a chatbot inside the app, paste, send, and the plan comes back as a text file in your iCloud Drive. Portable, auditable, never locked to a subscription.

  1. 01
    Tap "Write one with my AI coach."

    In onboarding, or any time from Profile → Coach loop. Pick Claude, ChatGPT, or Gemini.

  2. 02
    Aski copies the prompt and opens the chat.

    Your goal and current plan state are on the clipboard. Paste, send, and the bot will interview you before writing a plan.

  3. 03
    Aski reads the plan back from iCloud.

    The chatbot saves the JSON to Aski/Documents/plans/. The Plan tab updates automatically — no file picker, no download step.

Manual route · self-contained prompt

No app yet? Copy the whole prompt.

Aski's in-app handoff just copies a longer version of the same prompt and opens your chatbot for you. If you haven't installed Aski yet, or you'd rather drive the chatbot by hand, the block below is everything: iCloud paths, plan schema, workout-template schema, and the interview instructions. Edit the Goal: line, copy the whole thing, paste into Claude, ChatGPT, or Gemini.

# Aski training-plan handoff

## Your job
You are helping me create a training plan for Aski, an iOS strength-and-running app I use solo. **Before you write any JSON, interview me.** Ask one question at a time, in plain language, and wait for each answer before moving on.

Cover at minimum:
1. Primary goal & timeline
2. What I'm doing now — sessions per week, current weights / paces
3. Experience level — lifts I know, races I've done
4. Available days, session length, equipment / gym access
5. Injuries, things to avoid, current physio constraints
6. Anything I hate doing (so we skip it)

When you have enough, summarise the plan you intend to build (week count, weekly shape, progression model) in plain English and ask me to confirm. **Only after I confirm** — emit the JSON.

## Where Aski stores plans
- iCloud container: `iCloud.com.casvanderhoven.aski`
- Plan files live in: **iCloud Drive → Aski → `plans/`**
  - Full Mac path: `~/Library/Mobile Documents/iCloud~com~casvanderhoven~aski/Documents/plans/`
- Reusable workout templates (optional) live in: **iCloud Drive → Aski → `workouts/`**
  - Full Mac path: `~/Library/Mobile Documents/iCloud~com~casvanderhoven~aski/Documents/workouts/`
- Files are plain UTF-8 JSON. Filename convention: `<uuid>.json` matching the document's `id`.
- Aski auto-imports new `.json` files via NSMetadataQuery — no app restart needed.

## What I'm giving you (edit these lines before sending)
**Goal:** [e.g. Sub-3:30 marathon, or hypertrophy upper body 3 days/week, or return-to-running after knee rehab]
**Race / event date:** [optional ISO date — e.g. 2026-09-12 — or omit if no event]
**Experience:** [beginner / intermediate / advanced]
**Equipment:** [bodyweight / dumbbells / full gym / running only / mix]
**Sessions per week:** [3 / 4 / 5 / 6]
**Anything I should know:** [injuries, time-of-day, climate, equipment quirks]

## Output format
- After I confirm: emit one fenced ```json``` block per file, with the filename on a comment line above each block.
- Always emit one plan-document file. Strength/mobility sessions may either reference a separate workout-template file (via `templateId`) or inline a short description in `summary` / `coachNotes`.
- After the JSON, briefly list the filenames I need to save and where.

## Plan schema (save as `<plan-uuid>.json` in `plans/`)

```json
{
  "id": "UUID — same id to revise an existing plan, fresh id for a new one",
  "planSpecVersion": 1,
  "name": "Marathon — build phase",
  "goal": "Sub-3:30 marathon, 2026-09-12",
  "raceDate": "2026-09-12T00:00:00Z",
  "startDate": "2026-05-04T00:00:00Z",
  "weekCount": 18,
  "disciplines": ["running", "strength", "mobility"],
  "coachAttribution": "Claude (Pfitzinger 18/55-style)",
  "weeks": [
    {
      "weekNumber": 1,
      "label": "Endurance build",
      "totalPlannedKilometers": 50,
      "days": [
        { "dayOfWeek": 1, "items": [{ "discipline": "rest", "title": "Rest" }] },
        {
          "dayOfWeek": 2,
          "items": [
            {
              "discipline": "running",
              "title": "Easy 8 km",
              "summary": "Conversational. HR zone 2.",
              "estimatedMinutes": 48,
              "runningSpec": {
                "id": "fresh-uuid-here",
                "version": 1,
                "name": "Easy 8 km",
                "totalTarget": { "distanceMeters": 8000 },
                "steps": [
                  {
                    "kind": "single",
                    "spec": {
                      "stepType": "active",
                      "label": "Easy",
                      "durationTrigger": { "kind": "distance", "meters": 8000 },
                      "intensity": { "kind": "heartRateZoneIndex", "index": 2 }
                    }
                  }
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}
```

### Field reference

- **DurationTrigger.kind**: `"time"` (+seconds) | `"distance"` (+meters) | `"openLap"` | `"heartRateBelow"` (+bpm)
- **IntensityTarget.kind**: `"paceZone"` (+low,high in sec/km) | `"heartRateZone"` (+low,high in bpm) | `"heartRateZoneIndex"` (+index 1..5) | `"rpe"` (+low,high 1..10) | `"openIntensity"`
- **RunningStep.kind**: `"single"` (+spec) | `"repeat"` (+count, +steps for nested intervals)
- **stepType**: `warmup` | `active` | `recovery` | `rest` | `cooldown`
- **discipline**: `running` | `strength` | `mobility` | `rest`
- **dayOfWeek**: 1=Mon ... 7=Sun

### Strength / mobility items

Running plan items carry their full structure inline (`runningSpec`). **Strength and mobility plan items don't** — instead they point at a separate workout-template file that holds the typed structure (blocks → exercises → sets with `reps` / `load` / `rest` / `tempo`; see the next schema). Pick one of two shapes per strength/mobility item:

1. **Templated (recommended for repeating sessions).** Set `templateId` to a workout-template UUID. The template lives in `workouts/` and gives the runner full set-by-set logging on the watch / phone.
2. **Free-text only (lightweight, for one-off or mobility flows).** Leave `templateId` as `null` and describe the session in `summary` (one line) + `coachNotes` (multi-line: sets × reps, %1RM or RPE, rest, tempo, equipment). The session still appears on the plan but won't have typed sets to log against.

Inline text and `templateId` are mutually exclusive on the same item.

```json
{
  "discipline": "strength",
  "title": "Squat day",
  "summary": "5×5 Squat + 3×8 Bench + 3×10 Row",
  "estimatedMinutes": 60,
  "coachNotes": "Squat: 5×5 @ RPE 7-8, 3 min rest. Bench: 3×8 @ 70% 1RM. Row: 3×10 across.",
  "templateId": null
}
```

## Workout-template schema (optional, one per `templateId`, save as `<uuid>.json` in `workouts/`)

```json
{
  "id": "UUID — same as the templateId referenced from the plan",
  "version": 1,
  "name": "Squat day — heavy",
  "createdBy": "Claude (5/3/1-style)",
  "createdAt": "2026-05-04T09:00:00Z",
  "tags": ["lower", "strength"],
  "notes": "Focus on depth and bar speed.",
  "blocks": [
    {
      "type": "warmup",
      "name": "Warm-up",
      "exercises": [
        {
          "exerciseId": "goblet-squat",
          "name": "Goblet Squat",
          "muscleGroups": ["quads", "glutes"],
          "coachNotes": "Slow eccentric. 2× through.",
          "sets": [
            { "reps": 8, "load": "bodyweight", "rest": 60 }
          ]
        }
      ]
    },
    {
      "type": "main",
      "name": "Main work",
      "exercises": [
        {
          "exerciseId": "back-squat",
          "name": "Back Squat",
          "muscleGroups": ["quads", "glutes", "hamstrings"],
          "coachNotes": "Top set AMRAP.",
          "sets": [
            { "reps": 5, "load": "80 kg", "rest": 180 },
            { "reps": 3, "load": "90 kg", "rest": 180 },
            { "reps": 1, "load": "100 kg", "rest": 240, "tempo": "3-1-1-0" }
          ]
        }
      ]
    }
  ]
}
```

### Set variants

- **`{ "reps": N, ... }`** — `reps` (Int), optional `tempo` ("3-1-1-0" = eccentric-bottom-concentric-top), optional `load` ("bodyweight" / "80 kg" / "RPE 7" / "60% 1RM"), optional `rest` (seconds).
- **`{ "durationSeconds": N, ... }`** — for holds and timed work. Optional `load`, `rest`.

### WorkoutBlock.type

`"warmup"` | `"main"` | `"cooldown"`

### MuscleGroup (closed vocabulary)

`chest`, `upper-back`, `lats`, `lower-back`, `shoulders-front`, `shoulders-side`, `shoulders-rear`, `biceps`, `triceps`, `forearms`, `abs`, `obliques`, `glutes`, `quads`, `hamstrings`, `calves`, `hip-flexors`, `adductors`, `abductors`, `neck`. Unknown values are dropped on decode.

### Rehab tag

If I have an active injury, add `"rehab"` to the workout-template `tags` array. Aski activates a session-end pain gate + next-morning reactivity check for any session whose template is tagged `rehab`. Document stop / abort conditions in the template's `notes` field.

Prefer a formal JSON Schema for validation? The single-workout template schema is also available as a downloadable workout.schema.json for tooling that needs a strict $schema reference.