So I made a snap decision and enabled the voluntary OASIS Fitness Lockout software on my rig. […] If I didn’t meet my daily exercise requirements, the system prevented me from logging into my OASIS account. […] If I wanted to log in, I had no choice but to exercise first. This proved to be the only motivation I needed.

— Ready Player One, chapter 19

I wanted the same thing for Claude Code: no prompts until today’s workout shows up from my Fitbit.

So I built a mod that does that. Like Wade’s, it’s voluntary (it’s off until you opt in). Unlike Wade’s, there’s no two-month lock: anyone at the keyboard can turn it off, so it adds friction rather than enforcement. But friction is the point.

Mods are plugins

A Claude Code mod is delivered as a regular plugin. What makes it a mod is the hooks module: instead of shell scripts, hooks/hooks.json points at a TypeScript file that registers handlers on events like session.start, prompt.submit, and command.run.

fitness-protocol-mod/
├── .claude-plugin/marketplace.json   lists the mod (this folder is a local marketplace)
└── fitness-protocol/
    ├── .claude-plugin/plugin.json    manifest and user options
    ├── hooks/hooks.json              { "modules": ["./register.ts"] }
    └── hooks/register.ts             the gate

The source lives in my project folder. When you install it, Claude Code copies it into ~/.claude/plugins/cache/, and ~/.claude/plugins/known_marketplaces.json remembers where the marketplace came from. Edits in the project reach a running session after /reload-plugins.

claude plugin marketplace add ~/Projects/fitness-protocol-mod
claude plugin install fitness-protocol@wsmoak

The gate

The core of it is one prompt.submit handler. If the protocol is on and today’s goal isn’t met, the prompt is dropped with a message instead of going to the model:

on('prompt.submit', async ($, e, next) => {
  if (!options.optIn || e.command) return next(e)
  try {
    const s = await checkStatus($, options)
    if (!s.ok) return { drop: s.text }
  } catch (err) {
    return { drop: `Fitness Protocol: could not verify your workout (${(err as Error).message}). Locked until it can.` }
  }
  return next(e)
})

A few design choices:

  • Opt-in. optIn defaults to false in plugin.json’s userConfig, along with minMinutes (default 20) and minSteps (default off). Either goal unlocks.
  • Slash commands always pass, so /fitness (which shows your status) and /reload-plugins still work when you’re locked out.
  • Unlock once per day. When a goal is met, the date and goals go into the mod’s $.store, so it doesn’t call the API on every prompt. Changing a goal forces a fresh check.
  • Fail closed. If the API or token refresh fails, you stay locked. The way out is claude plugin disable from a regular terminal.

Fitbit data, via Google Health

The legacy Fitbit Web API is being shut down this fall, so the mod reads from the Google Health API (health.googleapis.com/v4) instead. It asks for exercise data points whose civil start time is today and adds up activeDuration.

Auth is the fiddly part. Google classifies all the Health API scopes as Restricted, so I created my own OAuth client in Google Cloud, added myself as a test user, and wrote a small get-refresh-token.mjs script that runs the consent flow once and saves the refresh token to ~/.config/fitness-protocol/credentials.json (mode 600). The mod exchanges that for short-lived access tokens and keeps them in memory.

After this morning’s workout synced:

> /fitness
Workout logged (20 min). Welcome to the OASIS.

A terminal to match

If I’m entering the OASIS, my terminal should look the part. I had Claude build a macOS Terminal profile: a deep-space purple background, grid-cyan text, neon pink for bold, a blinking magenta block cursor, and a synthwave ANSI palette. The window title reads OASIS // ONTOLOGICALLY ANTHROPOCENTRIC SENSORY IMMERSIVE SIMULATION.

A .terminal file is a plist with colors and the font stored as NSKeyedArchiver data, so the profile is generated by a small JXA script (osascript -l JavaScript make_oasis.js). One gotcha: SF Mono ships inside Terminal.app rather than being installed system-wide, so the script has to register the font for its own process before it can archive it. The first attempt saved an empty font, and Terminal rejected the file as corrupt.

261111_53846-fitness-protocol-unlocked

Making it harder to turn off

Wade couldn’t turn his lockout off for two months. Mine is a toggle in /config, which is a lot easier to talk myself out of.

Claude Code also reads a managed settings file, /Library/Application Support/ClaudeCode/managed-settings.json on macOS, which takes precedence over user settings. It’s meant for organizations rolling out policy with MDM, but nothing stops me from managing my own Mac. My machine is already configured with nix-darwin, so the idea is to have the flake write that file with the marketplace, the mod enabled, and my goals:

{
  "extraKnownMarketplaces": {
    "wsmoak": { "source": { "source": "github", "repo": "wsmoak/fitness-protocol-mod" } }
  },
  "enabledPlugins": { "fitness-protocol@wsmoak": true },
  "pluginConfigs": {
    "fitness-protocol@wsmoak": { "options": { "optIn": true, "minMinutes": 20, "minSteps": 2500 } }
  }
}

The file is owned by root, so turning the protocol off would mean editing the flake and running sudo darwin-rebuild switch on purpose. That’s not two months, but it’s more than a toggle. I haven’t tried it yet, and I still need to confirm which of these settings Claude Code actually enforces from the managed file.

It’s still not real enforcement: the installed plugin code is in my home directory where I can edit it, and claude.ai in a browser doesn’t care about any of this. Wade’s lockout was bound to his OASIS account; this one is bound to one install. But the goal was friction, and every extra step helps.

What’s next

  • The steps goal uses the same filter style as exercise but hasn’t been verified against the API yet.
  • It checks that a workout happened today, not that it happened in the morning. A cutoff hour would be easy to add.
  • Publishing it to GitHub so others can install it, with the caveat that everyone needs their own Google Cloud OAuth client.

Claude Code and Opus 5.5 were instrumental in writing the mod, researching the move from the Fitbit Web API to Google Health, building the OASIS Terminal profile (and debugging why Terminal said it was corrupt), and drafting this blog post. Fittingly, it was Claude Code helping me build the thing that locks me out of Claude Code.

Copyright 2026 Wendy Smoak - This post first appeared on wsmoak.net and is CC BY-NC licensed.