agentsclimarketplace

Deploy webapp

Skill kaustin923/agent-fitness-coach/.claude/skills/deploy-webapp

Give this repo to Claude and it becomes your training coach: real periodized plans, Strava + Apple Health data, progress tracking and grading — files are the database, skills are the features, the agent is the app.

Install
npx -y skills add kaustin923/agent-fitness-coach --skill deploy-webapp

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Deploy the dashboard as a real website on Vercel with live Strava sync through a serverless proxy that keeps all secrets server-side. Use when the athlete wants their dashboard at a stable URL — on their phone's home screen, shareable, self-updating — without opening Claude.

SKILL.md

10.4 KB, as published. Nobody here has run it

Deploy the dashboard as a website

This is the optional last mile: the same single-file dashboard, hosted at a real URL, with a "Sync Strava" button that works from any browser. It is the exact architecture the original marathon web app shipped with — a static page plus one serverless function that holds the Strava secrets and proxies the API. Total setup is about fifteen minutes and runs comfortably on free tiers.

The one security rule, before anything else: the Strava client_secret and refresh_token exist only as server-side environment variables. They never appear in the HTML, in any front-end JavaScript, in this repo's files, or in a git commit. Everything below is arranged around that rule.

When to run

  • The athlete asks for "a real website", a home-screen app, or a dashboard they can check without opening a chat.
  • They want family or a coach to follow the build from a link.
  • Not needed for the artifact/local dashboard — run this only when a deployed, self-syncing page is genuinely wanted.

Inputs

  • athlete/dashboard.html — generate it first with the dashboard skill if missing.
  • athlete/plan.json — for the sync window dates baked into the page.
  • Accounts and tools the athlete needs: a Strava account, a free Vercel account, and Node.js (for npx vercel; check with node --version).
  • What to ask the user: confirm they understand the page will be reachable by anyone who has the URL (see the passcode note in Rules), and have them keep their Strava Client ID/Secret at hand during setup — you will never write those values into a file.

Procedure

  1. Scaffold the project. Create athlete/webapp/ with the dashboard as the index page and an api/ directory:

    mkdir -p "athlete/webapp/api"
    cp "athlete/dashboard.html" "athlete/webapp/index.html"
    
  2. Create a Strava API application. Have the athlete visit https://www.strava.com/settings/api and create an app with Authorization Callback Domain: localhost. They copy the Client ID and Client Secret — into a password manager or terminal, not into any file you write.

  3. One-time OAuth: get a refresh token. (The full cookbook, with troubleshooting, is in guides/02-getting-your-data.md.)

    • The athlete opens this URL in a browser (substituting their Client ID):

      https://www.strava.com/oauth/authorize?client_id=CLIENT_ID&response_type=code&redirect_uri=http://localhost/exchange&approval_prompt=force&scope=activity:read_all
      
    • After approving, the browser lands on http://localhost/exchange?code=THE_CODE&... which won't load — that's expected. Copy THE_CODE from the address bar. Make sure activity:read_all was left checked on the consent screen.

    • Exchange the code (codes expire quickly — do this promptly). Read the secret with read -s first so it never lands in shell history:

      read -s STRAVA_CLIENT_SECRET   # paste the client secret at the silent prompt
      curl -X POST https://www.strava.com/oauth/token \
        -d client_id=CLIENT_ID \
        -d client_secret="$STRAVA_CLIENT_SECRET" \
        -d code=THE_CODE \
        -d grant_type=authorization_code
      
    • Keep the refresh_token from the JSON response. Access tokens expire in ~6 hours; the refresh token is the durable credential the proxy will use forever.

  4. Write the serverless proxy at athlete/webapp/api/strava.js:

    // api/strava.js — Vercel serverless function.
    // All secrets live in env vars; this file contains none.
    export default async function handler(req, res) {
      const { STRAVA_CLIENT_ID, STRAVA_CLIENT_SECRET, STRAVA_REFRESH_TOKEN } = process.env;
      if (!STRAVA_CLIENT_ID || !STRAVA_CLIENT_SECRET || !STRAVA_REFRESH_TOKEN) {
        return res.status(500).json({ error: "Missing STRAVA_* env vars" });
      }
      // 1) Refresh the short-lived access token.
      const tokenRes = await fetch("https://www.strava.com/oauth/token", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          client_id: STRAVA_CLIENT_ID,
          client_secret: STRAVA_CLIENT_SECRET,
          grant_type: "refresh_token",
          refresh_token: STRAVA_REFRESH_TOKEN,
        }),
      });
      const token = await tokenRes.json();
      if (!token.access_token) {
        return res.status(502).json({ error: "Token refresh failed", detail: token });
      }
      // 2) Pull ~75 days of activities — covers the 28-day fitness window plus trend.
      //    `after` is epoch SECONDS, not milliseconds.
      const after = Math.floor(Date.now() / 1000) - 75 * 86400;
      const actRes = await fetch(
        `https://www.strava.com/api/v3/athlete/activities?after=${after}&per_page=100`,
        { headers: { Authorization: `Bearer ${token.access_token}` } }
      );
      const acts = await actRes.json();
      if (!Array.isArray(acts)) {
        return res.status(502).json({ error: "Activity fetch failed", detail: acts });
      }
      // 3) Reshape to the dashboard's wire format.
      const activities = acts.map((a) => ({
        sport_type: a.sport_type || a.type,
        start_local: a.start_date_local,
        id: a.id,
        name: a.name,
        summary: { distance: a.distance, moving_time: a.moving_time },
      }));
      // 4) Edge-cache 10 minutes so Strava's rate limit is never hammered,
      //    no matter how often the page is refreshed.
      res.setHeader("Cache-Control", "s-maxage=600, stale-while-revalidate=86400");
      return res.status(200).json({ activities });
    }
    
  5. Wire live sync into the front end. Edit athlete/webapp/index.html: add a "Sync Strava" button to the Stats header card that, only when location.protocol is http/https, fetches "/api/strava" with { cache: "no-store" } (the browser skips its cache; the CDN edge cache still protects Strava) and runs the standard pipeline from reference/tracking.md: keep sport_type === "Run", key by start_local.slice(0, 10) (local wall-clock — never timezone-convert it), longest run per date wins, auto-mark plan days with total_miles > 0 done, format paces by rounding total seconds, recompute the fitness block, and re-render. Persist the synced maps to versioned localStorage keys (e.g. afc_done_v1, afc_strava_v1, afc_recent_v1), with the baked-in LOG data as defaults so the page shows real numbers before the first live sync. On failure, keep showing the last synced data with its date label — never empty stats.

  6. Set env vars and deploy. The athlete types these three commands themselves, in their own terminal, from athlete/webapp/ — each prompts interactively on stdin for its value, so the secret never appears in chat, in your context, in any file, or in shell history:

    npx vercel env add STRAVA_CLIENT_ID production
    npx vercel env add STRAVA_CLIENT_SECRET production
    npx vercel env add STRAVA_REFRESH_TOKEN production
    

    Your role is to hand them those three exact commands to type, then confirm the setup with npx vercel env ls — it lists variable names only, never values. Once all three are present, deploy:

    npx vercel --prod
    

    The first run walks through Vercel login and project linking; the deploy prints the live URL.

  7. Optional passcode gate. A small client-side passcode screen (store an "unlocked" flag in localStorage) keeps casual eyes off the page. Be honest with the athlete about what it is: on-device privacy, not security. Anyone with the URL and dev tools can bypass it. That's acceptable because the page contains only a training plan and run summaries — no secrets, no server data. If that's not acceptable, don't deploy.

  8. Verify. curl -s https://<your-url>/api/strava | head -c 300 returns an {"activities":[...]} payload; a second request within 10 minutes returns fast from edge cache; the Sync button in the browser reports "Synced N runs from Strava."

Rules

  • Secrets server-side only — the rule above is absolute. If a secret ever lands in a file or commit, revoke it at https://www.strava.com/settings/api and start over.
  • after is epoch seconds. Distance is meters (divide by exactly 1609.34 for miles); pace uses moving_time, not elapsed_time; start_local has no timezone suffix — slice the date, never parse it as UTC.
  • Keep the edge cache header (s-maxage=600, stale-while-revalidate=86400). Strava's limits are per application (200 reads/15 min, 2,000/day); the cache makes them unreachable.
  • Known fragility: Strava sometimes rotates refresh tokens on refresh. This single-user setup pins one token in env and virtually always works — but if sync starts returning 401s, redo step 3 and update the env var.
  • Strava is migrating its API: base URL https://www.api-v3.strava.com and Authorization: Bearer headers become mandatory by June 2027. The code above uses Bearer already; swap the base URL if activity fetches start failing.
  • Compliance: this is a personal, single-athlete tool — you fetching your own data for your own display is the intended use. If you ever distribute an app on this pattern, read Strava's API agreement first: it caps athletes per app, restricts data use in AI applications, and requires official branding ("Connect with Strava" button, "View on Strava" links).
  • The deployed page's plan is baked at build time. After plan-adjust or a new plan, redeploy: regenerate via the dashboard skill, cp "athlete/dashboard.html" "athlete/webapp/index.html" (re-apply the sync wiring from step 5 if the generator doesn't include it), then npx vercel --prod. Day-to-day run data needs no redeploy — the Sync button handles it.
  • athlete/ is gitignored by default, so the webapp directory stays out of the public repo. Keep it that way.

Output

Give the athlete the live URL, remind them it self-syncs from Strava (10-minute cache) while plan changes need a redeploy, and show how to add it to a phone home screen (share → Add to Home Screen). Then suggest 2–3 next actions, such as:

  • "Add it to your home screen — it behaves like an app."
  • "After the next plan adjustment, tell me and I'll redeploy in one step."
  • "Want a passcode screen on it? Two minutes, with the honest caveat above."

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.