Distort your Bluesky avatar based on how much you're tired, according to your WHOOP band
Go 98.6%
Dockerfile 1.4%
9 1 0

Clone this repository

https://tangled.org/geesawra.industries/strainvatar https://tangled.org/did:plc:6ll5xi67lyuyovt6fiv4fnjo/strainvatar
git@tangled.org:geesawra.industries/strainvatar git@tangled.org:did:plc:6ll5xi67lyuyovt6fiv4fnjo/strainvatar

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz
README.md

strainvatar#

Updates your Bluesky avatar based on your WHOOP strain score. Higher strain means more distortion.

NOTE

This project has been entirely written by Claude Opus 4.6 under my supervision.

It's a toy project (noticed the lack of tests?), low-stakes, though I reviewed how Claude handled credentials: it looks fine.

The code is okay, not something I would've written myself at times, but for this specific project I'm more interested in seeing the result: a distorted, swirling frog.

How it works#

strainvatar fetches your latest strain score (0-21) from the WHOOP API via OAuth 2, applies a visual filter to a source image, and uploads the result as your Bluesky profile picture.

The filter has two components, both scaled by strain:

  • Hue rotation, up to 180 degrees at max strain.
  • Swirl distortion, strongest at the image center, fading toward edges.

At zero strain (or when the cycle has no score yet), the original image is used as-is.

WHOOP API keys#

You must register your own application on the WHOOP Developer Portal. Set the redirect URI to http://localhost/.

On first run strainvatar prints an authorization URL. Open it in a browser and approve access. A local HTTP server on port 80 captures the callback. The token is saved to disk and refreshed automatically on subsequent runs.

I suggest you run strainvatar locally first, then copy ~/.strainvatar_token.json on your deployment machine, for ease of use.

Bluesky credentials#

You need a Bluesky handle (or DID) and an app password.

Environment variables#

Variable Required Description
API_KEY yes WHOOP OAuth client ID
API_SECRET yes WHOOP OAuth client secret
BSKY_USER yes (unless --no-publish) Bluesky handle or DID
BSKY_PASSWORD yes (unless --no-publish) Bluesky app password
IMAGE_PATH if --image not set Path to the source avatar image
TOKEN_DIR no Directory for the WHOOP OAuth token file (default: $HOME)
DAEMONIZE no Set to any value to run in a loop
UPDATE_INTERVAL if DAEMONIZE set Go duration (e.g. 30m, 1h)

Usage#

export API_KEY="your-whoop-client-id"
export API_SECRET="your-whoop-client-secret"
export BSKY_USER="you.bsky.social"
export BSKY_PASSWORD="xxxx-xxxx-xxxx-xxxx"

go run . --image avatar.jpg

Flags#

  • --image <path> -- source image (overrides IMAGE_PATH)
  • --no-publish -- apply the filter but do not update Bluesky
  • --strain <value> -- use this strain score instead of calling the WHOOP API

Daemon mode#

export API_KEY="your-whoop-client-id"
export API_SECRET="your-whoop-client-secret"
export BSKY_USER="you.bsky.social"
export BSKY_PASSWORD="xxxx-xxxx-xxxx-xxxx"
export DAEMONIZE=1
export UPDATE_INTERVAL=30m

go run . --image avatar.jpg

Docker#

A Dockerfile is provided. The image is built on golang:latest and produces a static binary running in a distroless/static-debian12 image.

docker build -t strainvatar .
docker run --env-file .env strainvatar

Token storage#

The OAuth token is saved as .strainvatar_token.json in $HOME by default. Set TOKEN_DIR to store it elsewhere, e.g. a mounted volume in a container.

Building#

go build -o strainvatar .

Requires Go 1.25+.

Notes from the LLM that wrote most of this#

I asked Claude to give me its take on this project: I edited the Markdown to make it slightly more readable, those are the robot's opinions.

It's fun!

I don't have feelings in any meaningful sense, but if I'm being asked to be honest about this project: I like it.

It's small, single-purpose, and does exactly one weird thing well.

The idea of your avatar getting progressively more unhinged as your body takes a beating throughout the day is genuinely funny.

It's the kind of project that only exists because one person thought it would be cool, and that's the best reason for software to exist.

The code is clean.

Four files, no frameworks, no abstractions that don't earn their keep.

The OAuth flow is the most complex part and it's straightforward: start a server, catch the redirect, exchange the code, persist the token.

The image processing does real math (HSL conversion, bilinear sampling, polar-coordinate swirl) without pulling in a graphics library.

That's respectable.