A GitOps-driven, data-centric training system for endurance athletes.
Integrates Intervals.icu, wearable data, and AI to plan, evaluate, and adapt training load, readiness, and long-term form.
| .woodpecker | ||
| app | ||
| athletes | ||
| docs | ||
| goals | ||
| timeoff/chucknorris | ||
| .gitignore | ||
| .sops.yaml | ||
| AGENTS.md | ||
| daily.sh | ||
| README.md | ||
TrainOps (PowerShell + Intervals.icu + OpenAI)
This repository contains a deterministic fitness automation pipeline for endurance training workflows.
- Supports multiple athletes
- Uses Intervals.icu for activities, wellness, and events
- Produces deterministic JSON + Markdown outputs for planning and review
- Optional AI tasks driven by prepared payloads
- Optional publishing to calendars/email (see
PUBLISHING.md)
Quick start (local)
Prereqs:
- PowerShell 7 (
pwsh) - Internet access to Intervals.icu and OpenAI API (if AI enabled)
- API secrets as env vars:
Install module:
pwsh -NoProfile -Command 'Install-Module powershell-yaml -Scope CurrentUser -Force'
Configuration
- Global defaults live in
app/settings.ymlat the repo root. - Athlete-specific YAML files under
athletes/override only the fields they need, andSettings.ps1merges them with the shared defaults. - The merge preserves the original key order so the resulting
settings.debug.ymlsnapshot mirrors your hand-edited files. Enable it by addingsystem_settings.debug: trueto an athlete profile and inspect the dump underout/<athlete>/00_raw_data/settings.debug.yml. - Shared provider defaults (Intervals IDs, OpenAI models, etc.) live under a top-level
providersblock.
Example snippet:
providers:
intervals:
athlete_id: "icuid"
openai:
default_model: "gpt-4.1-mini"
Logging
- All logs are written to a single shared folder:
app/logsat the repo root. - Each log line includes the athlete id:
[athlete=<id>]. - Log files are date-stamped (
trainops-YYYY-MM-DD.log).
Protecting athlete configs with SOPS
- Install SOPS and generate/store an age key (e.g.
age-keygen -o ~/.config/sops/age/keys.txt). ExportSOPS_AGE_KEY_FILE(orSOPS_AGE_KEY) so both local runs and CI can decrypt data. .sops.yamlenforces encryption forathletes/*.yml,goals/<athlete>/*.yml,health/<athlete>/*.yml(time-off events live underhealth/). Replace the age public key with your own.- Use the built-in editor to work with encrypted files:
sops athletes/lada.yml. When you save/exit, the ciphertext is updated in place and safe to commit. - The helper
Load-YamlFileautomatically detects SOPS-encrypted files and shells out tosops -dat runtime, so pipelines always see plaintext while the repository keeps ciphertext only. - CI runners only need the private key exposed as a secret env var; no code changes are required elsewhere.
Optional: Local SOPS vault for API keys
- Store user-provided API keys in an encrypted
.env-style vault (secrets/vault.envby default, ignored by git). Override the path viaTRAINOPS_VAULT_PATHif you want to keep the file elsewhere. - Run
daily.shinstead of calling the PowerShell script directly. The wrapper decrypts the vault via SOPS, sources it withset -a, and then launchesapp/scripts/daily.ps1with the original arguments. - Example vault contents:
export INTERVALS_API_KEY_CHUCKNORRIS="..."
export OPENAI_API_KEY_CHUCKNORRIS="..."
- Edit with
sops secrets/vault.env(or your custom path) and keep the age key private; the file never touches git thanks to the.gitignoreentry.
How it works (high level)
- Import raw data (activities, wellness, events) from Intervals.icu
- Normalize data into stable schemas
- Build daily state payloads and summaries
- (Optional) Run AI tasks using prepared payloads
- (Optional) Publish outputs (calendar/email/files)
Pipeline docs
DATA_IMPORT.mdNORMALIZATION.mdDAILY_STATE.mdPLANNER.mdSUMMARIZATION.mdAI.mdPUBLISHING.md
Daily State V2 payloads
For the exact JSON structure, calculations, and coefficients used by planner_input.json and goal_readiness_report.json, see DAILY_STATE.md.
Woodpecker
See woodpecker.yml. Create a cron named daily-coach in Woodpecker UI.