What cycling things have you vibe-coded?

During an endurance workout today, I got curious about whether I could control my Wahoo KICKR HEADWIND directly from my Mac. I was sick of the annoying iOS app for Wahoo.

The agent started by scanning its Bluetooth services, found the community-documented BLE commands, and vibe coded a tiny native macOS app while I continued to ride. It connects automatically, shows the current fan speed, and provides a slider plus 25%, 50%, 75%, 100% and off controls. I also gave it a proper icon and installed it so I can launch it through Raycast.

A surprisingly productive use of Zone 2!

Has anyone done anything similar?

I (Claude) built a calendar web app for tracking my season racing/riding/off time plans in exactly the way I wanted.

Shortly after I built it, I saw a Thread making fun of vibecoded website style tropes and virtually all of them are present. Don’t care, I got exactly what I wanted.

I built a dashboard that pulls all my metrics in from strava and my oura ring and let’s me compare different periods. It also has an Ai chat tab that let’s me ask it questions about any of my data. It’s pretty neat.

Everything:

  • Custom Training Load
  • Recovery Panel
  • Health Markers database
  • Race Analysis tools
  • Knowledge base
  • KOM pacing tool
  • And more

Not cycling but I built a workout library to help me remember different kettlebell and bodyweight routines I see on Instagram as I had tonnes of notes.

Also included a player if I’ve downloaded the routine video.

It’s very basic docker image but useful.

Using claude+mcp server+intervals.icu:

Dimensional load:

takes your trailing 7 days of rides from intervals.icu and, instead of just reporting a single TSS number per ride, splits each ride’s load into four physiological buckets — FTP (aerobic/threshold), MAP (VO2max), AC (anaerobic capacity), and NM (neuromuscular/sprint). It does this by pulling your actual power-zone time distribution for each ride, weighting each zone by (intensity relative to your zone midpoint)— the same math TSS itself uses — but referencing the MAP/AC/NM weighting against your own rolling 6-month best 5-min/1-min/5-sec power, so the split reflects your personal ceilings rather than generic zone bands. The output always shows raw TSS side by side with the dimensional breakdown — TSS/CTL/ATL/TSB stay your primary numbers, this is a supplementary lens on what kind of fatigue a week carried, since two rides at the same TSS (a steady road ride vs. a punchy technical MTB ride) can load you very differently.

Just something I am experimenting with for analysing unstructured rides, not taking too seriously.

What MCP are you using?

Oh, on the what you made note, I have a plan for next year: work runs a month long “move it” competition for walking, running, riding and swimming where you get points for amount done. This year one of the devs built something to look at the scoreboards and rules then each day give a suggestion on what to focus on to get the most points in a Money Ball style. Certainly more efficient than my method of just working like crazy.

So I plan to learn how to build one next year.

I made some DIY smart rollers with my own code, then eventually reverse engineered the PCB on some InsideRide rollers and tweaked my code to work with it, then used Claude to add a bunch of features, web server, better calibration stuff, and organize all the code. GitHub - acedeuce802/InsideRideRollers_Qubo_to_FTMS: Reprogramming the original InsideRide Smart Rollers with Qubo Smart Unit to be updated to FTMS protocol · GitHub

Thanks for starting this thread, I’ve been super curious to see what other people are doing. My setup sounds similar to @XCO_mtb 's using the intervals.icu MCP server to handle all the load and power numbers with write permissions so it can directly add and update workouts to the calendar. Then I’ve got a RAG setup with a pretty wide-ranging batch of research for cycling training, strength training, and general health/fitness for 50+ athletes.

Historically I’ve been terrible at sticking with strength training because of some shoulder issues and this has been super helpful for keeping me on track with an emphasis on shoulder work for a longer period before I start going heavier.

Using the dimensional load analysis I mentioned above (and with help and cooperation from the ICUSync developer), Claude can give me something like this for 2 similar TSS unstructured mountain bike rides:

Tuesday 18 Aug Sunday 23 Aug
TSS 147 151
RPE 8 7
MAP+AC+NM share of load 16.6% 6.9%
Burst blocks / time 9 / 6.2 min 13 / 6.6 min
W’bal — lowest point 66% remaining 82% remaining

TSS said these were basically the same ride. Tuesday actually pushed much deeper into anaerobic reserve and carried more than double the high-end load share — and it matched how it actually felt and how long it took to recover.

Some of the logic being applied here:

Coasting breakdown - coasting is broken down into short and long coasts (>= 8 seconds, <=10 watts, fairly arbitrary numbers). Long coasts are subtracted from the Z1 total before computing load.

Short coasts are considered part of MTB riding on technical, rolling terrain - gaps between pedal strokes and are not subtracted from the Z1 total.

This allows a better insight than the zone buckets that intervals uses which can end up with a huge amount of Z1 time.

The FTP/MAP/AC/NM composite is computed from the corrected zone-seconds.

The next step will be to evaluate unstructured rides as low, moderate or high cost and have something like yellow light/red light for moderate and high …

Low cost if MAP+AC+NM combined stays under roughly 10% of load and W’bal never drops much past 75% remaining,

Moderate cost, somewhere around 10-20% and 45-70% remaining.

High cost — 30%+ and reserve dropping below 30% remaining

This is really interesting, I do something similar. However I think it misses the interactions and the sequences. Example, doing tempo after high intensity has a higher cost than doing it before

I built Athmex.com after getting fed up with rebuilding the same athlete context about myself whenever I wanted to use ChatGPT or Claude for a training question. It was driving me mad sending it loads of screenshots and data before using it to design the next training block or make changes.

It’s not that similar to what you’ve done but the curiosity is the same - there has to be a better way to do this!

you might want to check what an .md file is.

Yea a MD file is definitely a perfectly good DIY solution for storing static context. The bit I wanted to solve was making that context structured, editable and available to both Claude and ChatGPT without a non technical user having to maintain files or think about any of the plumbing. Probably overkill for people who enjoy building this stuff themselves, though :smiley:

Fun thread - thanks for sharing everyone.

I’ve built:

  1. A Strava KOM finder that looks at the top 400 (rate limit on strava API for free plan) segments that start around a given address and my liklihood to take the crown. It used my recent power PBs to align with the time/power of the top riders. Its not perfect but its been fun to star a few low hanging segments and go grab them on otherwise standard rides. Another easy filter criteria for finding is sorting recent efforts from low to high, this proves the route is still viable but nobody rides it and the crown is likely quite easy. Does it make me feel good to get fake internet points? yes. it really does.
  2. I’ve attempted to build an injury prevention monitor with the strava MCP. Taking all my history and looking for large periods of time where i stopped training, then giving me input about what happened going into those pauses. Increase in intensity, volume, etc. So far its always the few weeks after a big event and not actually flagging real injuries.
  3. Last was my broken (there is a theme with these apps not working lol) interval playlist generator. Using Spotify API you can pull the meta data of different sections of a song, including the intensity, the theme, the BPM, etc. - combining that with a prescribed 4x4 on with 2 min rest, ive gotten pretty close to having 4 mins of intense music followed by 2 minutes of easier listening or intro/outro. The struggle thus far - it really likes to just take the same song that has 4 minutes of intensity and repeat it over and over again.

Are you saying, it is just picking up the natural training down time after a big event. You were not injured, just having a reset after committing so many physical and mental resources into yoyr big aim of the year?

Oh yes! Fun thread!

I sometimes :sweat_smile: play around with Di2. I have a Mac. Shimano’s E-Tube Project does not run on Macs.

The E-Tube Project software is used to run diagnostics and update ‘wireless’ shifters.. and it’s also used to change certain settings. See https://bike.shimano.com/products/apps/e-tube-project-professional.html

For quite a while I played around with emulators and virtual machines to get E-Tube Project to work, but it wasn’t great. And you can’t really dual boot windows on an Apple sillicon mac.

So… I rebuilt E-Tube Project for Mac. It started out as a Python project:

It connects to bikes, performs firmware updates, lets you shift derailleurs, and worked well.

Sure, it sometimes left components in bootloader mode, but I could always get my own app to fix that :).

But then a colleague said: why don’t you try WebUSB? I never thought I could get a browser and the SM-PCE02 tool to talk to a bike reliably, but why not give it a shot? The Python version works, but looks awful. So… I instructed Claude to take the Python version of the app, turn it into a web version.

It can connect to an entire bike, with the SM-PCE02 in slave mode. It’ll then detect the connected components, their current state and firmware versions.

I gave it the Shimano API urls and format, and if/when a new firmware version is released, it can install those. It can also downgrade firmware, something the Shimano tool doesn’t let you do.

I can’t redistribute any of the Shimano files, so the browser downloads the relevant firmware file when the user wants to flash that to a component.

Flashing firmware when the PCE02 is connected in ‘host’ mode (only the PCE02 and a couple of components) works really well. It’s pretty safe and I trust it.

Flashing firmware with the PCE02 in ‘slave’ mode - when there’s a Junction A / RD + battery connected is a lot more risky. The E-Tube Project timings are subtle.. I can no longer count the number of times the web app left components in bootloader mode or the entire bike unresponsive - definitely more than 50 times so far :slight_smile:

Sometimes it would flash correctly, but then release components in the wrong order.. sometimes it would not release them at all, or flood the PCE02 with messages every 1ms instead of waiting 11-15ms like the Shimano tool does.. and the browser being async meant the Python project wasn’t a single ‘apply this to the web’ conversion.

When it works though, it’s great:

And if you’re on an android phone, you can even connect the SM-PCE02 to your phone and use the web app that way :).

Here’s a short demo: https://www.youtube.com/shorts/TfNfWIIpxUo and https://youtube.com/shorts/S6I1l9oE84M

What’s next…? Well.. first I’ll stop it from bricking bikes all the time :slight_smile: :slight_smile: :slight_smile:. Then I’ll make it look better and remove the AI looks.. I’ll also see if I can get it to support the older SM-PCE1 and the SM-BCR2 (charger).

I’ll also have to review the code at some point.. at least I do speak JavaScript/Next/React (…I don’t know Python). It’s hooked up to Sentry, so that every connect I get an ‘info’ event with the connected bike parts and the log. Also, when things go wrong, that logs an error to sentry with the log as an attachment… so that I can fix that specific case.

It’s a fun project, but involves bricking a lot of Di2 parts… quite often. Good times!

Exactly.