feat(strava): add Strava athlete, activity, social & club adapter#2005
Open
yixin-1024 wants to merge 5 commits into
Open
feat(strava): add Strava athlete, activity, social & club adapter#2005yixin-1024 wants to merge 5 commits into
yixin-1024 wants to merge 5 commits into
Conversation
Strava serves authenticated athlete/activity pages as server-rendered HTML (`opencli browser analyze` -> Pattern C: no JSON XHR, no SSR state), so this adapter scrapes the rendered DOM through the logged-in Chrome session (Strategy.COOKIE). Login is verified by detecting Strava's /login redirect for signed-out requests. Commands: - strava profile <athlete> name, location, following/followers counts - strava activities <athlete> recent activities (type, name, id, url), --limit - strava activity <id> distance, moving time, elevation, date Pure parsing helpers live in utils.js with unit tests in utils.test.js. Accepts bare ids, /athletes|/activities paths, or full URLs as input. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Expands the Strava adapter from a React-props + SSR-HTML scrape of the
logged-in session (Strategy.COOKIE):
- whoami / login identity via window.currentAthlete + nav (shared site-auth)
- kudos <activity> kudos/comment counts + can_kudo + owner, read from the
<ADPKudosAndComments> react-props blob on the activity page
- feed [--limit] dashboard following feed (athlete_id, activity_id, title)
- segments your starred segments (type, name, distance, elevation)
- clubs <athlete> clubs an athlete belongs to (id, name from logo alt)
Split into auth.js / social.js / feed.js / segments.js / clubs.js; id
normalizers (club/segment) added to utils.js with unit tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ation
Adds the interactive command set, all verified against the live logged-in session:
Reads:
- comments <activity> comment list (modal scrape: author, text, time, id)
- club <id> club name, sport, location, member count
- club-activities <id> recent club member activities
- prs <athlete> personal records / best efforts (label, value)
- map <activity> route export URLs (GPX / original)
Writes (guarded by --execute, since they notify other athletes):
- kudo <activity> give kudos via the ADP button; verified via live DOM
(react-props are SSR-only and don't reflect the click)
- comment <activity> <text> post a comment through the ADP modal
- comment-delete <act> <id> delete your own comment (open modal → delete → confirm)
- join <club> join a club via its Join CTA
whoami now reads the full name off the profile h1 (window.currentAthlete only
carries the id). requireExecute() guard added to utils.js.
Athlete free-text search was intentionally dropped: Strava's web /athletes/search
only returns find-friends suggestions (no query-matched results, no search XHR),
so a `search` command would surface silently-wrong data.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ries
The `activity` command only surfaced distance / moving_time / elevation /
date — but the activity page carries the full ride/run breakdown. Scrape it
all from the logged-in session:
- both inline-stats blocks: the primary (distance/time/elevation) and the
ride-only secondary-stats (weighted_avg_power / total_work)
- the "More Stats" table (identified by its Avg/Max header so we never grab
an unrelated unstyled table): avg/max speed, pace, heart rate, cadence,
power, plus single-valued calories / temperature / elapsed_time
- device (head unit) and gear (bike/shoe); Strava's lone em-dash "no gear"
placeholder is normalized to empty
New pure helper parseMoreStats() flattens the table into avg_<k>/max_<k> and
single keys; parseInlineStats() learns weighted_avg_power / total_work. Both
covered by vitest. Unknown labels are dropped rather than guessed.
Verified live against a ride (full power/HR/cadence) and a run (pace only,
no stats table) — empty fields stay empty, no silently-wrong data.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two PR CI gates flagged the new adapter; both are satisfied without
touching adapter behaviour:
- docs/adapters/browser/strava.md: the doc-coverage --strict gate requires
every clis/* adapter to have a doc page. Documents all 18 commands
(read/write grouping, examples, --execute notes, login prerequisite).
- silent-column-drop baseline: the gate's heuristic scans every object
literal in a command's source file. It flags the page.evaluate DOM-scrape
intermediates (e.g. {iconClass, typeText, name, href}) and rows from other
commands that share the same file, but every row this adapter actually
returns matches its declared columns exactly. Adopt the 17 false-positive
signatures into the baseline, matching the existing entries for the
twitter / xiaohongshu / bilibili / weibo / zhihu scrapers. Purely additive.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Hi @jackwener 👋 friendly ping on this one whenever you have a moment. Why it's worth having: Strava is the largest online platform for endurance sports (running / cycling / triathlon) with tens of millions of active athletes. An OpenCLI adapter exposes a huge amount of personal training data to agent workflows — athlete profile, activity history, kudos/comments (social), and clubs. The PR is mergeable with no conflicts and adds the adapter behind the logged-in browser session, consistent with the project's model. Happy to address any feedback. Thanks for maintaining OpenCLI! 🙏 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Strava adapter
Adds a Strava adapter (
clis/strava/) covering athlete, activity, social and club data. Strava serves authenticated pages as server-rendered HTML / React micro-frontends (opencli browser analyze→ Pattern C, no JSON API), so this works as aStrategy.COOKIEadapter that reads structured data off the logged-in session — Reactdata-react-propsblobs,__NEXT_DATA__, SSR tables — and drives the ADP UI for writes. Login is detected via Strava's/loginredirect.Commands (18)
Read
profile <athlete>activities <athlete>activity <id>whoami/loginwindow.currentAthlete+ profile h1 (sharedsite-auth)kudos <id><ADPKudosAndComments>react-props (counts, can_kudo, owner)comments <id>feed [--limit]segments [--limit]clubs <athlete>club <id>club-activities <id>prs <athlete>map <id>Write (guarded by
--execute, since they notify other athletes)kudo <id>comment <id> <text>comment-delete <id> <comment_id>join <club>All commands accept bare ids, paths, or full URLs. Pure parsers live in
utils.jswith unit tests (utils.test.js).Notes
/athletes/searchonly returns find-friends suggestions (no query-matched results, no search XHR), so asearchcommand would surface silently-wrong data.prs/clubparsing is best-effort over the rendered page.Testing
opencli validate strava→ PASS (18 commands, 0 errors / 0 warnings)vitest run clis/strava/utils.test.js→ 10 passing🤖 Generated with Claude Code