OpenAthlete
AGPLv3 endurance training platform: EU-oriented hosting, transparent CTL/ATL/TSB logic in code, self-hosting, and full export. Built in Grenoble.
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
Running-club PWA for the Madregot club. Coaches parse training plans from text or
images with AI and push structured workouts to athletes' Garmin watches; athletes
get a Hebrew-first mobile app with their program, a Strava-style activity feed,
attendance, academy reports and push notifications.查看完整 README ↗Running-club PWA for the Madregot club. Coaches parse training plans from text or images with AI and push structured workouts to athletes' Garmin watches; athletes get a Hebrew-first mobile app with their program, a Strava-style activity feed, attendance, academy reports and push notifications.
next-intl (he/en, RTL)garmin-connect packagemaxDuration = 300)Requires Node >= 22.12.
npm install
Create a project at supabase.com, then run the SQL in
this order in the SQL Editor. Both steps are required — schema.sql creates
the base tables (coaches, groups, athletes, weekly_plans,
workout_deliveries, athlete_activities) and the migrations build everything
else on top of them. Running only one of the two leaves the app unable to boot.
supabase/schema.sql — the base tables.supabase/migrations/ in numeric order, from
000_extensions.sql through the highest-numbered file (currently
076_notification_image.sql, 72 files).Migrations are applied by hand in the SQL Editor; there is no supabase db push
wired up in this repo. To concatenate them in order for a single paste:
for f in supabase/migrations/*.sql; do echo "-- $f"; cat "$f"; echo; done > /tmp/all-migrations.sql
Then copy your project URL and API keys from Settings → API.
cp .env.local.example .env.local
.env.local.example documents every variable the app reads, grouped by the
feature it unlocks and annotated with what breaks when it's missing. Only the
first block ("Core") plus ENCRYPTION_KEY is needed to boot; the rest are
per-feature and degrade gracefully when blank.
Two that catch people out:
ENCRYPTION_KEY must be 64 hex characters, and is not rotatable in
place — a new key cannot decrypt existing Garmin/Strava connections. Generate
a fresh one only for an empty database.NEXT_PUBLIC_VAPID_PUBLIC_KEY /
VAPID_PRIVATE_KEY pair (npx web-push generate-vapid-keys). With either
missing, sending silently no-ops rather than erroring.npm run dev
Open http://localhost:3000.
npx vercel
Add the same environment variables in Vercel → Settings → Environment
Variables. The cron schedules in vercel.json are registered automatically on
deploy.
When pulling Vercel env vars locally, always target a temp path —
vercel env pull /tmp/mc-vercel.env --environment=production. The default destination is.env.local, which overwrites your own file.
npm run check # typecheck + tests + lint + build (run before pushing)
npm run typecheck
npm run test # vitest
npm run lint
Bump src/lib/version.ts and package.json together for each change (patch
bumps by default).
This trips up every change that touches a timestamp, so it's documented in
src/lib/utils.ts as well:
athlete_activities.start_time) store Garmin's
startTimeLocal wall-clock in a TIMESTAMPTZ column that Postgres reads as
UTC. The athlete's real local time therefore is the timestamp's UTC
wall-clock — accessors must read UTC parts, and comparing these to Date.now()
silently loses Israel's offset.israelNow() / israelToday() / israelDateAnchor().Do not try to fix this by setting a global TZ — that was tried and
reverted; TZ is deliberately unset outside of individual tests.
Further design notes live in docs/ (academy-feature.md, feed-plan.md,
run-chat-architecture.md, competitor-research.md).