plugins/claude-code-fitness-hermit/skills/hatch/SKILL.md
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.
npx skillsauth add gtapps/claude-code-hermit hatchInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Idempotent setup wizard for the fitness plugin. Run after /claude-code-hermit:hatch has already been completed.
Read .claude-code-hermit/config.json.
If the file does not exist or _hermit_versions["claude-code-hermit"] is absent or empty:
"The base hermit is not set up in this project yet. Run
/claude-code-hermit:hatchfirst, then return here."
Use AskUserQuestion: "Would you like to run /claude-code-hermit:hatch now? (yes / no)"
/claude-code-hermit:hatch, wait for completion, then continue to Step 2.If _hermit_versions["claude-code-hermit"] is present but the version string is earlier than 1.0.26 (compare major.minor.patch numerically), warn:
"Base hermit version is {version}; this plugin requires ≥1.0.26. Run
/claude-code-hermit:hermit-evolveto upgrade, then re-run this hatch."
Stop.
Read _hermit_versions["claude-code-fitness-hermit"] from .claude-code-hermit/config.json.
Read version from ${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json.
If the versions match, say:
"claude-code-fitness-hermit {version} is already installed. Skip to Step 8 to re-verify the installation, or reply 'full' to re-run the full wizard."
Use AskUserQuestion: "(verify / full)"
If absent or stale: continue from Step 3.
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:
cp .env.example .env(or copy the file manually)- Open
.envand fill in all four values from your Strava developer app:
STRAVA_CLIENT_ID— numeric app IDSTRAVA_CLIENT_SECRET— app secretSTRAVA_ACCESS_TOKEN— initial access tokenSTRAVA_REFRESH_TOKEN— refresh tokenSee 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)"
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_IDSTRAVA_CLIENT_SECRETSTRAVA_ACCESS_TOKENSTRAVA_REFRESH_TOKENIf 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.
Using the four values you parsed from .env in Step 3 (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:
${...} placeholders → rewrite just the strava entry with literal values.The strava entry shape:
{
"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.
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-strava-sync.md${CLAUDE_PLUGIN_ROOT}/state-templates/compiled/routine-strava-health-check.md${CLAUDE_PLUGIN_ROOT}/state-templates/compiled/routine-weekly-load-review.md${CLAUDE_PLUGIN_ROOT}/state-templates/compiled/routine-monday-planning.mdRead the source file (using Read tool), then check if the destination exists (.claude-code-hermit/compiled/<filename>):
✓ dropped <filename>.⊘ skipped <filename> (already present).Resolve target file: Read .claude-code-hermit/state/hatch-options.json. Use the "target" field:
"local" → target_file = CLAUDE.local.md
"committed" or absent → target_file = CLAUDE.md
If the file doesn't exist (no hatch-options.json yet — operator's core hermit predates 1.1.1): detect core_install_scope from claude plugin list --json using the same precedence rules as core hatch Step 1.5 item 2 (filter entries where plugin name is claude-code-hermit and enabled == true; precedence local > project (both require projectPath == project root) > user (any projectPath) > null; map project → committed, local/user/null → local). Ask with AskUserQuestion (header: "Visibility") — scope-derived default at position 0 with (recommended): .local files (gitignored — operator-personal) / Committed files (shared with teammates). Write the canonical 5-field schema to .claude-code-hermit/state/hatch-options.json:
{
"target": "<choice>",
"core_install_scope": "<project|local|user|null>",
"stamped_at": "<current ISO 8601 timestamp with timezone offset>",
"stamped_by": "claude-code-fitness-hermit:hatch",
"version": "<current fitness-hermit plugin version from ${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json>"
}
Read target_file. Search for the opening marker <!-- claude-code-fitness-hermit: Fitness Workflow -->. The matching closing marker is <!-- /claude-code-fitness-hermit: Fitness Workflow -->.
target_file does not exist (greenfield CLAUDE.local.md is common) → treat as marker-absent and proceed to the append branch; Edit will create the file.${CLAUDE_PLUGIN_ROOT}/state-templates/CLAUDE-APPEND.md to target_file using Edit.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.
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 strava-sync 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.
Use the config.json content already loaded in Step 1. (Do not re-read the file.)
Set _hermit_versions["claude-code-fitness-hermit"] to the plugin version retrieved in Step 2.
If the key already exists: update the value. If absent: add it alongside the existing _hermit_versions["claude-code-hermit"] entry.
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).
{
"id": "strava-sync",
"schedule": "30 21 * * *",
"skill": "claude-code-hermit:session-start",
"enabled": true,
"run_during_waiting": true,
"prompt_file": "compiled/routine-strava-sync.md"
},
{
"id": "strava-health-check",
"schedule": "5 8 * * *",
"skill": "claude-code-hermit:session-start",
"enabled": true,
"run_during_waiting": true,
"prompt_file": "compiled/routine-strava-health-check.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"
}
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.
{"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).
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: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.
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.
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.
(none — Strava is a cloud service)
development
Use when the user asks about cost structure or composition: which token types cost the most, how cache reads compare to output costs, which sessions are draining budget, or what cold starts add up to. This is the 'why is my bill high' skill. It audits where spend goes across token categories (cache_read, cache_write, output, input) and per-session attribution. Invoke for questions like 'cost breakdown', 'which sessions are expensive', 'what's driving spend', or 'cold start costs'. Not for week-over-week trend lines (use hermit-evolution) or simple total-spend summaries.
tools
Presence history & tracker-health report — current home/away state, reliability, recent arrival/departure transitions, and activity patterns for person/device_tracker entities. Use when the operator asks about presence history or when a presence-dependent automation (locks, alarm, vacuum, climate) misbehaves.
development
Evening house brief — end-of-day security check, device status, and energy snapshot. Runs as a daily routine at 22:30 or on demand.
tools
Browse and explain the hermit's Home Assistant automations — list by topic, filter by keyword with plain-language YAML explanations, or sort by last-fired. Read-only. Use when the operator asks "what automations do I have / what does this one do / which haven't fired."