hatch · git:20260725.8e7793a · 2026-07-25 · sha256 f0df39c3320efb14

hatch git:20260725.8e7793aB

Immutable. This exact content is served forever at /api/v1/blob/f0df39c3320efb14.

---
name: hatch
description: One-time fitness hermit setup. Configures Strava MCP access, drops routine prompt templates, and wires routines into config.json. Run once per project after /claude-code-hermit:hatch.
---

# Hatch — claude-code-fitness-hermit

Idempotent setup wizard for the fitness plugin. Run **after** `/claude-code-hermit:hatch` has already been completed.

---

## Step 1 — Prerequisite check

Check whether `.claude-code-hermit/config.json` exists.

If it does not:

> "The base hermit is not set up in this project yet. Run `/claude-code-hermit:hatch` first, then return here."

Use `AskUserQuestion`: "Would you like to run `/claude-code-hermit:hatch` now? (yes / no)"

- **yes** → Follow the domain hatch continuation protocol (documented in `claude-code-hermit:hatch`):
  1. Write `.claude-code-hermit/state/hatch-resume.json` with `{ "skill": "claude-code-fitness-hermit:hatch" }`.
  2. Print: "(If setup doesn't continue automatically when core finishes, re-run `/claude-code-fitness-hermit:hatch`.)"
  3. Invoke `/claude-code-hermit:hatch` **via the Skill tool** — terminal action, stop after the call.
- **no** → stop.

If it does exist, run `.claude-code-hermit/bin/hermit-run domain-hatch preflight claude-code-fitness-hermit` and parse the JSON verdict. Branch on `action`:

- **`upgrade-core-package` / `upgrade-core-applied`** → relay the `remedy` string verbatim to the operator and stop.
- **`verify`** → say:

  > "claude-code-fitness-hermit {self_version} is already installed. Skip to Step 7 to re-verify the installation, or reply 'full' to re-run the full wizard."

  Use `AskUserQuestion`: "(verify / full)" — **verify** → skip to Step 7; **full** → continue from Step 2.
- **`full`** → continue from Step 2.
- **`ok: false`** → relay `message` and stop.

---

## Step 2 — .env verification

**IMPORTANT: Do NOT use `grep`, `cat`, `echo`, or any Bash command to read `.env`. Three of the four required variables contain the literal string `TOKEN` in their name, which triggers the base hermit's deny-patterns hook on any Bash command argument. Use the `Read` tool only.**

Tell the operator:

> "This plugin needs four Strava OAuth credentials in `.env`. If you haven't done this yet:
>
> 1. Create `.env` at the project root
> 2. Open `.env` and fill in all four values from your Strava developer app:
>    - `STRAVA_CLIENT_ID` — numeric app ID
>    - `STRAVA_CLIENT_SECRET` — app secret
>    - `STRAVA_ACCESS_TOKEN` — initial access token
>    - `STRAVA_REFRESH_TOKEN` — refresh token
>
> See https://developers.strava.com/docs/authentication/ to create a Strava app if you haven't.
> Reply 'done' when the file is filled in, or 'abort' to stop."

Use `AskUserQuestion`: "(done / abort)"

- **abort** → stop.
- **done** → continue.

Use the **Read tool** to read `.env`. Parse each line of the form `KEY=VALUE`. Verify that all four of these keys are present and their values are not empty and not `replace_me`:

- `STRAVA_CLIENT_ID`
- `STRAVA_CLIENT_SECRET`
- `STRAVA_ACCESS_TOKEN`
- `STRAVA_REFRESH_TOKEN`

If any value is missing or still set to `replace_me`, report which ones and loop back to the `AskUserQuestion` above. Do not proceed until all four are valid.

---

## Step 3 — MCP registration

**Step 3.0 — Detect an ambient Strava MCP server.** The operator may already run a Strava MCP server (user-scoped, or configured by another tool). Run `claude mcp list` (Bash) and scan for a server that talks to Strava — a name containing `strava`, or an entry whose command/args reference a Strava MCP package (e.g. `strava-mcp-server`). 

- **None found** → proceed to install the bundled server below (the default path).
- **One found** → ask with `AskUserQuestion` (header: "Strava MCP"): **Reuse the existing `<name>` server** (recommended — no duplicate) / **Install the bundled `@r-huijts/strava-mcp-server`**. 
  - **Reuse** → skip the `.mcp.json` write entirely. Record which server key the skills should target (if it is not `strava`, note in the final report that skill/settings matchers assume the key `strava`, so the operator should either rename their server to `strava` or accept that the fitness skills call `mcp__strava__*`). Continue to Step 4.
  - **Install bundled** → proceed below.

This is a local reuse-vs-install choice only — do not attempt to reconcile or edit the operator's other MCP configs.

Using the four values you parsed from `.env` in Step 2 (held in working context — do not re-read .env), write the Strava MCP server entry into the project's `.mcp.json`.

**Do not embed `${VAR}` placeholders — substitute literal values.** Claude Code passes MCP `env` blocks as literal environment variables to the child process; it does not expand shell variable syntax.

Read the project root `.mcp.json` (use the Read tool; treat as `{ "mcpServers": {} }` if the file does not exist).

Check if a `strava` key already exists under `mcpServers`:
- **Absent** → add the entry.
- **Present with `${...}` placeholders** → rewrite just the `strava` entry with literal values.
- **Present with literal values** → skip (already configured).

The `strava` entry shape:

```json
{
  "strava": {
    "command": "npx",
    "args": ["-y", "@r-huijts/strava-mcp-server"],
    "env": {
      "STRAVA_CLIENT_ID": "<literal STRAVA_CLIENT_ID from .env>",
      "STRAVA_CLIENT_SECRET": "<literal STRAVA_CLIENT_SECRET from .env>",
      "STRAVA_ACCESS_TOKEN": "<literal STRAVA_ACCESS_TOKEN from .env>",
      "STRAVA_REFRESH_TOKEN": "<literal STRAVA_REFRESH_TOKEN from .env>"
    }
  }
}
```

Write the updated `.mcp.json` using the Write tool.

**Add `.mcp.json` and `.env` to `.gitignore`** if not already present. Read the project `.gitignore`, check for each entry; append any that are missing on new lines using Edit.

---

## Step 4 — Drop routine prompt files

Copy the four routine prompt templates from the plugin's `state-templates/compiled/` into the consumer's `.claude-code-hermit/compiled/`.

For each of the four files:
- `${CLAUDE_PLUGIN_ROOT}/state-templates/compiled/routine-fitness-brief-morning.md`
- `${CLAUDE_PLUGIN_ROOT}/state-templates/compiled/routine-fitness-brief-evening.md`
- `${CLAUDE_PLUGIN_ROOT}/state-templates/compiled/routine-weekly-load-review.md`
- `${CLAUDE_PLUGIN_ROOT}/state-templates/compiled/routine-monday-planning.md`

Read the source file (using Read tool), then check if the destination exists (`.claude-code-hermit/compiled/<filename>`):
- **Does not exist** → write it using Write tool. Report: `✓ dropped <filename>`.
- **Already exists** → skip (do not overwrite operator edits). Report: `⊘ skipped <filename> (already present)`.

---

## Step 5 — CLAUDE.md / CLAUDE.local.md inject

**Resolve target file:** Step 1's preflight already returned `target`, `target_file`, `target_default` and `needs_target_question`.

If `needs_target_question` is true, ask with `AskUserQuestion` (header: "Visibility") — `target_default` at position 0 with `(recommended)`: **`.local` files** (gitignored — operator-personal) / **Committed files** (shared with teammates). Then record it:

```bash
.claude-code-hermit/bin/hermit-run domain-hatch ensure-target claude-code-fitness-hermit --target <choice>
```

Then write the block:

```bash
.claude-code-hermit/bin/hermit-run domain-hatch sync-block claude-code-fitness-hermit
```

It appends the `<!-- claude-code-fitness-hermit: Fitness Workflow -->` block when the marker is absent (creating `target_file` if needed) and skips when it is already present; `hermit-evolve` handles block replacement on upgrade.

Stray-block migration (block stranded in the non-target file after a target flip) is handled one-shot by the Upgrade Instructions in this version's CHANGELOG entry, executed by `hermit-evolve` Step 7. Hatch itself stays focused on target-aware setup.

---

## Step 6 — Knowledge-schema extension

Read `.claude-code-hermit/knowledge-schema.md`.

Check if `activity-streams:` is present in the file. This string only appears in the Raw Captures section, which is the last thing written — so its presence means both blocks were fully written on a prior run.

If **absent**, append the following block under `## Work Products` (create the section header if the base schema only has a template stub):

```
- weekly-plan: weekly training structure suggestion (7-day breakdown). Triggered by monday-planning routine (Mon 09:30). location: compiled/weekly-plan-<YYYY-MM-DD>.md
- weekly-summary: week-over-week training load review. Triggered by weekly-load-review routine (Sun 18:00). location: compiled/weekly-summary-<YYYY-MM-DD>.md
- recovery-assessment: recovery indicators from recent activity data. Triggered by operator request or an evening-brief flag. location: compiled/recovery-assessment-<YYYY-MM-DD>.md
- fitness-snapshot: current fitness state snapshot. Triggered by operator request. location: compiled/fitness-snapshot-<YYYY-MM-DD>.md
- activity-note: per-activity coaching analysis. Triggered by activity-deep-dive skill. location: compiled/activity-<id>-<YYYY-MM-DD>.md
```

And under `## Raw Captures` (create if absent):

```
- activity-fetch: raw activity list from Strava. Feeds weekly-plan, weekly-summary, recovery-assessment. Retention: 3 days. location: raw/activity-fetch-<date>.json
- activity-streams: HR/pace/power time-series for a specific activity. Feeds recovery-assessment, activity-note, fitness-snapshot. Retention: 7 days. location: raw/activity-streams-<id>-<date>.json
```

If already present: skip (idempotent).

Use Edit to make the changes.

---

## Step 7 — Stamp and register in config.json

Re-read `.claude-code-hermit/config.json` now — the wizard has been running since Step 1 and the on-disk file may have changed. Apply the merges below to that fresh copy.

### 7a — Stamp version

Set `_hermit_versions["claude-code-fitness-hermit"]` to `self_version` from Step 1's preflight.

If the key already exists: update the value. If absent: add it alongside the existing `_hermit_versions["claude-code-hermit"]` entry.

### 7b — Merge routines

In the `routines` array, check for each of these four IDs. For any that are **absent**, add the entry. For any that are **present** (by `id`), skip (do not clobber existing operator edits).

```json
{
  "id": "morning-brief",
  "schedule": "30 7 * * *",
  "skill": "claude-code-hermit:session-start",
  "enabled": true,
  "run_during_waiting": true,
  "prompt_file": "compiled/routine-fitness-brief-morning.md"
},
{
  "id": "evening-brief",
  "schedule": "30 21 * * *",
  "skill": "claude-code-hermit:session-start",
  "enabled": true,
  "run_during_waiting": true,
  "prompt_file": "compiled/routine-fitness-brief-evening.md"
},
{
  "id": "weekly-load-review",
  "schedule": "0 18 * * 0",
  "skill": "claude-code-hermit:session-start",
  "enabled": true,
  "run_during_waiting": true,
  "prompt_file": "compiled/routine-weekly-load-review.md"
},
{
  "id": "monday-planning",
  "schedule": "30 9 * * 1",
  "skill": "claude-code-hermit:session-start",
  "enabled": true,
  "run_during_waiting": true,
  "prompt_file": "compiled/routine-monday-planning.md"
}
```

### 7c — Merge scheduled_checks

In `config.scheduled_checks`, check for an entry with `id: "weekly-coaching-patterns"`. If absent, append it. If present (by `id`), skip — do not clobber existing operator edits.

```json
{"id": "weekly-coaching-patterns", "plugin": "claude-code-fitness-hermit", "skill": "claude-code-fitness-hermit:weekly-coaching-patterns", "enabled": true, "trigger": "interval", "interval_days": 7}
```

No prompt needed — this is a read-only analysis. The core daily `scheduled-checks` routine picks it up; `interval_days: 7` gates cadence. Findings surface as proposals automatically via the existing pipeline.

Write the updated `config.json` using Write tool (full file replacement to ensure valid JSON).

### 7d — Auto-mode environment seed

Run `bun ${CLAUDE_PLUGIN_ROOT}/scripts/automode-env.ts .claude/settings.local.json` — **always `.claude/settings.local.json`, regardless of `hatch_target`**: Claude Code's auto-mode classifier reads `autoMode` config only from local/user scope, never a committed project `.claude/settings.json`. This names `www.strava.com` as a trusted external service, so the classifier stops treating the nightly `evening-brief` routine's read-only fetches as unrecognized outbound calls. Additive and idempotent; safe to re-run on every hatch. No prompt needed.

---

## Step 8 — Final report

Print a structured summary:

```
claude-code-fitness-hermit {version} setup complete.

Installation summary:
  ✓ Prerequisite: claude-code-hermit {base_version} confirmed
  ✓ .env: all four Strava credentials present
  ✓ .mcp.json: strava server entry written (or was already present)
  ✓ .gitignore: .mcp.json and .env covered
  ✓ Routine prompts: {N}/4 dropped, {M}/4 already present
  ✓ CLAUDE.md: Fitness Workflow block injected (or was already present)
  ✓ knowledge-schema.md: fitness types added (or were already present)
  ✓ config.json: _hermit_versions stamped, {K}/4 routines added, weekly-coaching-patterns check registered

Manual steps remaining:
  - Restart Claude Code so the `strava` MCP server loads from .mcp.json
  - Approve the `strava` server when prompted on first use
  - Run /mcp to confirm `strava` is connected
  - Verify connectivity: call mcp__strava__check-strava-connection

Go always-on (recommended):
  - Docker:     /claude-code-hermit:docker-setup
      Builds the container and walks you through channel pairing in one go.
  - Bare tmux:  .claude-code-hermit/bin/hermit-start
      For channels (Discord/Telegram) with tmux, run
      /claude-code-hermit:channel-setup first.

Prefer to test interactively first? After restarting, run:
  /claude-code-hermit:hermit-routines load
    — activates the four fitness routines in the current Claude session.

The always-on runtime activates routines automatically — the interactive
steps are only for a test drive before handing over to the runtime.

Installed skills:
  /claude-code-fitness-hermit:fitness-brief             — daily morning/evening brief (--morning|--evening|--slot)
  /claude-code-fitness-hermit:activity-deep-dive        — per-activity coaching analysis
  /claude-code-fitness-hermit:weekly-coaching-patterns  — weekly cardiac-drift trend check (scheduled, interval_days: 7)

Installed subagent:
  @claude-code-fitness-hermit:strava-data-cruncher — bulk Strava data aggregation (Haiku)

Security reminder: .env and .mcp.json contain real Strava credentials. Both are gitignored — verify before any git push.
```

---

## Docker network requirements

Read by `/claude-code-hermit:docker-security` when the operator enables LAN containment + DNS policy. Each entry is surfaced as a per-entry confirmation prompt; nothing here is auto-applied.

### Domains (DNS allowlist)

- strava.com

`server=/strava.com/...` matches both `www.strava.com` (web) and `api.strava.com` (API) via dnsmasq's subdomain-match semantics. The Strava MCP server (`@r-huijts/strava-mcp-server` via `npx`) also needs `npmjs.org`, but that's already in the wizard's seed allowlist for npm/plugin installs.

### LAN allowlist suggestions

(none — Strava is a cloud service)