Skip to content

hapticasensorics/bee-computer-reps

Repository files navigation

Bee Reps

A phone-browser workout logger that turns manual or Bee transcripts into pending set candidates, confirmed sets, and owner-hosted workout history.

Run

React dev UI:

npm install
npm run dev -- --host 0.0.0.0 --port 5176

Then open http://localhost:5176/static/ on the Mac.

For the real iPhone workflow, keep that server running and open the Mac's LAN URL on the phone:

LAN_IP="$(ipconfig getifaddr en0 || ipconfig getifaddr en1)"
echo "http://$LAN_IP:5176/static/"

Use that URL in Safari on the iPhone. This is the fastest local proof path because the phone is exercising the same browser/PWA surface that goes to Fly later.

Build the React app into the FastAPI-served static/ directory:

npm run build

FastAPI/static preview:

uv run python main.py

Then open http://localhost:8080. The runtime binds to 0.0.0.0:$PORT and defaults to PORT=8080.

Health check:

curl http://localhost:8080/health

Deploy Quickly To Fly.io

Self-serve guided path:

scripts/setup-demo.sh --install-tools

The script checks local tools, logs into Fly, creates or selects a Fly app, creates a bee_data volume mounted at /data, asks for your OpenAI API key, enables Bee QR linking, deploys, waits for /health, opens the app, and prints the Bee Developer Mode steps.

It also generates the private owner/setup URL and stores it in gitignored local state:

.gymscribe/private-setup-url.txt
.gymscribe/private-setup-qr.png
.gymscribe/private-setup-qr.svg
.gymscribe/setup.env

Use it later from the computer with:

open "$(cat .gymscribe/private-setup-url.txt)"

Use it later from a phone by opening the saved QR and scanning it:

open .gymscribe/private-setup-qr.png

That URL and QR are secrets. They contain the generated owner key and Bee setup key. The same keys are stored as Fly secrets, while .gymscribe/ is only for this local clone and is ignored by git. The OpenAI API key is different: the script sends it to Fly as OPENAI_API_KEY but does not save the key in .gymscribe/.

Private Setup URL And Key Rotation

The private setup URL is not just a bookmark. It contains two secret values in the fragment:

APP_ACCESS_KEY       unlocks the owner session, workout history, and Bee sync
BEE_LINK_ADMIN_KEY   unlocks Bee QR linking and unlinking

Those values are also stored as Fly secrets. If either value changes on Fly, every older private setup URL that contains the previous value is stale. This is why an old chat-pasted URL can stop working even when the Fly Machine is healthy.

The local source of truth for this clone is the URL plus QR stored under .gymscribe/:

open "$(cat .gymscribe/private-setup-url.txt)"
open .gymscribe/private-setup-qr.png

By default, scripts/setup-demo.sh reuses .gymscribe/setup.env so it does not casually rotate keys. To intentionally rotate owner/setup access:

scripts/setup-demo.sh --rotate-keys --app bee-computer-reps

That command replaces the Fly APP_ACCESS_KEY, BEE_LINK_ADMIN_KEY, and SESSION_SIGNING_KEY, then rewrites .gymscribe/private-setup-url.txt, .gymscribe/private-setup-qr.png, .gymscribe/private-setup-qr.svg, and .gymscribe/setup.env. Afterward, old private setup URLs and QR codes stop working and existing browser owner sessions need to open or scan the new stored URL/QR.

If a QR or private URL is stale because Fly secrets changed elsewhere, run the setup script again without rotation first:

scripts/setup-demo.sh --app bee-computer-reps

That reuses the local .gymscribe/setup.env keys, pushes them back to Fly secrets, and rewrites the URL/QR artifacts so the phone QR stays current for this clone. Use --rotate-keys only when you intentionally want old URLs and QR codes to stop working.

Do not rotate BEE_TOKEN_ENCRYPTION_KEY during normal access-key cleanup. That key protects the encrypted Bee token file on /data; changing it can require relinking Bee. Only rotate it when you intentionally want to invalidate the stored Bee token:

scripts/setup-demo.sh --rotate-keys --rotate-token-encryption-key --app bee-computer-reps

Optional environment knobs:

FLY_APP_NAME=my-bee-reps FLY_REGION=sjc FLY_ORG=my-org scripts/setup-demo.sh

Install and log in:

brew install flyctl
flyctl auth login

Create the Fly app once. Public Fly app names are global, so change app = "bee-computer-reps" in fly.toml first if this name is already taken:

flyctl apps create bee-computer-reps

Use QR linking from the app UI for the product path. For a private one-user backend fallback, you can still set a server-side Bee token:

flyctl secrets set BEE_API_TOKEN="..."

Deploy:

flyctl deploy --remote-only

The Dockerfile is self-contained for Fly's remote builder. It runs npm ci, builds the Vite app into static/, installs Python dependencies with uv, and serves the built assets from FastAPI. You do not need to commit prebuilt static/ assets for deploys.

The app stores QR-linked Bee credentials at /data/bee-token.enc and saved workout history at /data/gymscribe.sqlite when a Fly volume is mounted. The fly.toml includes:

[mounts]
  source = "bee_data"
  destination = "/data"
  initial_size = "1gb"

Open:

flyctl open

Workout History Storage

For this MVP, every owner runs their own Fly app. Saved workout history is stored in that owner's SQLite database at /data/gymscribe.sqlite. The phone app still uses browser storage for the active draft and Bee sync cursor, but completed workouts are posted to the owner backend.

The backend does not provide accounts, teams, global sync, or shared cloud history. Treat each Fly deployment as a private one-user backend.

Fly Cost Controls

The default fly.toml is configured to conserve compute:

[http_service]
  auto_stop_machines = "stop"
  auto_start_machines = true
  min_machines_running = 0

  [http_service.concurrency]
    type = "requests"
    soft_limit = 20

That lets Fly stop the web Machine when it is idle and start it again on the next request. The tradeoff is a cold-start pause when opening the app after it has been idle.

Important: stopped Machines can still have tiny root filesystem charges, and the bee_data Fly volume is charged while it exists because it stores SQLite history and the encrypted Bee token. To pause compute without losing data:

flyctl machine stop --app bee-computer-reps e826247c0d0d38

To remove ongoing storage charges, destroy the app or volume only after you are willing to lose the stored Bee token and workout database:

flyctl apps destroy bee-computer-reps

If the app name is already taken, choose a unique name in fly.toml and use that same name with flyctl apps create.

For the owner-ready QR path, let the helper create the app if needed, generate the owner/setup keys, set Fly secrets, deploy with the remote builder, and print both the public app URL and the private setup URL:

scripts/phone-demo.sh --install-tools --enable-linking

Open the private setup URL on the owner phone, tap Link Bee, then scan the generated QR from Bee. The setup URL lives in a URL fragment (#appKey=...&setupKey=...), so those keys are not sent as a URL path or query string. The app exchanges them once for an HttpOnly owner session cookie and then strips the fragment from the browser address bar. The setup screen also reports whether the backend has an OpenAI key configured for draft workout extraction.

Useful deploy checks:

flyctl status
flyctl logs
curl https://bee-computer-reps.fly.dev/health

Launch On A Real iPhone

The fastest local demo workflow is browser-first:

  1. Start the dev server for LAN access:
npm run dev -- --host 0.0.0.0 --port 5176
  1. Print the phone URL:
LAN_IP="$(ipconfig getifaddr en0 || ipconfig getifaddr en1)"
echo "http://$LAN_IP:5176/static/"
  1. Open that URL in Safari on the iPhone.

The fastest public demo workflow is the same product surface on Fly:

  1. Deploy:
flyctl deploy --remote-only

The Dockerfile builds the React app inside Fly's remote builder, so a local npm run build is optional for previewing but not required before deploy.

  1. Verify:
curl https://bee-computer-reps.fly.dev/health
  1. Open:
https://bee-computer-reps.fly.dev/

For live demos, show or scan a QR code for that URL. The phone should land directly on the workout logger. Bee can be unlinked and manual transcript should still complete the golden path: transcript -> pending candidate -> confirm -> saved workout history.

Bee App Linking Flow

Bee linking should be part of the app UI, not a hidden ops-only setup. For public publishing, each person should deploy their own Fly app and link their own Bee. Do not enter a Bee token into someone else's hosted URL.

The privacy model:

flowchart LR
  Owner["Owner phone PWA<br/>normal HTTPS"] --> Backend["Own Fly backend<br/>setup-key-gated QR linking<br/>server-side Bee token"]
  Backend --> Bee["Bee API<br/>private CA plus bearer token"]
  Visitor["Other visitor<br/>no setup key"] -. "manual demo only" .-> Backend
Loading

The desired product flow is one simple loop:

  1. The workout logger shows Bee not connected and a Link Bee action near Sync Bee.
  2. The app backend creates a Bee app-pairing request and returns a QR/link such as https://bee.computer/connect#<requestId>.
  3. The user opens the Bee iOS app and enables Developer Mode if needed.
  4. In Bee, the user taps Developer -> Connect App.
  5. Bee opens a scanner and the user scans the QR shown by the workout logger.
  6. The backend polls the pairing request, decrypts the returned token server-side, and keeps it out of browser localStorage.
  7. The workout logger changes to Bee connected, and Sync Bee starts returning normalized transcript events from /api/sync-now.
  8. Saved workouts are written to this Fly app's SQLite database, not to a shared service.

The Bee QR scanner is the product clue: our app should generate the QR, and the Bee app should scan/approve it. Users should not paste API tokens into a phone browser.

Enable the in-app QR path locally with:

BEE_LINKING_ENABLED=true uv run python main.py

On Fly, production QR linking requires the generated owner/setup secrets. The setup script creates them, stores them as Fly secrets, and saves the private URL locally:

scripts/setup-demo.sh --install-tools

Then open the stored private URL:

open "$(cat .gymscribe/private-setup-url.txt)"

The app exchanges the URL fragment keys for an HttpOnly owner session cookie, then removes the fragment from the address bar. The Bee token stays on the backend.

The QR path is intentionally backend-owned:

  • POST /api/bee/link/start creates the Bee app-pairing request.
  • GET /api/bee/link/status/:requestId polls until Bee approval.
  • DELETE /api/bee/link clears the private demo token.
  • The browser receives only the QR/link and status, never the Bee token.

Pairing sequence:

sequenceDiagram
  participant Phone as Owner phone PWA
  participant Fly as Fly backend
  participant BeeApp as Bee iOS app
  participant BeeAPI as Bee pairing API
  Phone->>Fly: POST /api/bee/link/start with owner session + CSRF
  Fly->>BeeAPI: Create pairing request with public key
  BeeAPI-->>Fly: requestId and expiry
  Fly-->>Phone: QR payload
  BeeApp->>Phone: Scan QR from Link Bee sheet
  BeeApp->>BeeAPI: Approve app pairing
  Phone->>Fly: Poll /api/bee/link/status
  Fly->>BeeAPI: Poll pairing status
  BeeAPI-->>Fly: Encrypted Bee token
  Fly->>Fly: Decrypt and store token server-side
  Fly-->>Phone: Bee connected
Loading

Screens from the Bee app:

Step Screenshot
Open Bee settings and find Developer / About. Bee settings showing Developer and About
Tap the Bee app version five times in About to enable Developer Mode. Bee About screen with app version
Open Developer, then tap Connect App. Bee Developer screen with Connect App
Scan the QR code generated by this app. Bee QR scanner

One-Command Demo Script

For public-repo self-serve setup, use:

scripts/setup-demo.sh --install-tools

For local phone iteration, use:

scripts/phone-demo.sh

Optional deploy path:

scripts/phone-demo.sh --install-tools --deploy

scripts/setup-demo.sh behavior:

  1. Check for uv, npm, and flyctl; install common missing tools when --install-tools is passed.
  2. Prompt for a unique Fly app name.
  3. Verify Fly auth or open flyctl auth login.
  4. Install Python and Node dependencies.
  5. Run uv run pytest and npm run build.
  6. Create the Fly app if needed.
  7. Create the bee_data Fly volume in the selected region for Bee credentials and SQLite workout history.
  8. Generate or reuse APP_ACCESS_KEY, BEE_LINK_ADMIN_KEY, SESSION_SIGNING_KEY, and BEE_TOKEN_ENCRYPTION_KEY.
  9. Prompt for OPENAI_API_KEY or reuse the existing Fly secret if this clone already marked it configured.
  10. Set those values as Fly secrets with BEE_LINKING_ENABLED=true, OPENAI_MODEL=gpt-5.5, and OPENAI_REASONING_EFFORT=low.
  11. Save the private setup URL, QR code, and local secret state to .gymscribe/private-setup-url.txt, .gymscribe/private-setup-qr.png, .gymscribe/private-setup-qr.svg, and .gymscribe/setup.env.
  12. Deploy with flyctl deploy --remote-only --app <app-name>.
  13. Wait for https://<app-name>.fly.dev/health.
  14. Open the private setup URL https://<app-name>.fly.dev/setup#appKey=<key>&setupKey=<key> and print the Bee 5-tap / QR scan steps.

scripts/phone-demo.sh behavior:

  1. Check for flyctl; install with brew install flyctl when missing and --install-tools is passed.
  2. Check for Bee CLI; install with npm install -g @beeai/cli when missing and --install-tools is passed.
  3. Verify Fly auth with flyctl auth whoami.
  4. Run npm ci if dependencies are missing.
  5. Create the Fly app and bee_data volume when deploying.
  6. Prompt for OPENAI_API_KEY and set it as a Fly secret when deploying.
  7. Deploy with flyctl deploy --remote-only when --deploy is passed.
  8. Start npm run dev -- --host 0.0.0.0 --port 5176 for the local phone path, or leave the existing server alone if it is already running.
  9. Print the Mac LAN URL for opening the workout logger on the phone.
  10. Print the Bee taps:
Bee app -> Settings -> About -> tap App version 5 times
Bee app -> Developer -> Connect App -> scan this app's QR

The script should not ask for or persist a Bee token in browser storage. It prints setup/app URLs only; Bee approval happens through the app QR sheet.

Runtime Gotchas

  • flyctl secrets set OPENAI_API_KEY=... --app <app-name> creates a new Fly release and restarts machines. Open the private setup URL afterward; the setup screen should show AI extraction ready. Do not put the OpenAI key in .gymscribe/, browser storage, URLs, screenshots, or frontend logs.
  • flyctl secrets set BEE_API_TOKEN=... creates a new Fly release and restarts machines. Re-run the /health check after setting it; beeConfigured should become true.
  • The Bee token must stay server-side. Do not put it in localStorage, query params, screenshots, or frontend logs.
  • Do not share the private setup URL, private setup QR, or .gymscribe/ files. They unlock owner access for that Fly app.
  • The backend already owns Bee private-CA trust for the PWA path. The phone talks only to this app over normal HTTPS; the backend talks to Bee with the bundled CA, or an override from BEE_CA_CERT.
  • App-pairing tokens are written to an encrypted server-side token file with owner-only permissions. Saved workouts are written to SQLite at /data/gymscribe.sqlite.
  • For a durable Fly setup, keep the bee_data volume mounted at /data; without it, token and workout history durability depend on the machine filesystem.
  • The LAN iPhone URL requires the Mac and phone to be on the same network, and the macOS firewall must allow the Vite server. If that is flaky, use the Fly URL.

Bee Setup

Enable Bee Developer Mode before trying to link or sync real Bee data:

  1. Open the Bee iOS app.
  2. Go to Settings and find the app version.
  3. Tap the app version 5 times to enable Developer Mode.
  4. Verify from your terminal:
bee login
bee status

bee status should confirm Bee Developer Mode is available before the demo tries to use Bee sync.

The product path is app-owned QR linking: tap Link Bee in the workout logger, open Bee Developer Mode, choose Connect App, and scan the QR shown by the workout logger.

For a private local fallback before durable QR-link storage exists, set a server-side Bee token before running the app:

export BEE_API_TOKEN="..."
uv run python main.py

Without a linked Bee token, the demo still runs: manual transcript, parsing, active session state, and saved workout history work. Bee sync pulls normalized transcript events from /api/sync-now when Bee is linked; it does not write workouts to Bee.

Try Saying

  • "Bench press 135 for 8"
  • "Squat one thirty five for five"
  • "8 reps of curls at 30"
  • "Add 5 pounds"
  • "Remember that my right knee felt tight"
  • "Remind me to stretch after legs"
  • "Sync to Bee"

Test

uv run pytest

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors