{"_id":"@armsteadj1/strava-sync","_rev":"2-c798eb327e7e7a644cc1b8e3623b4126","name":"@armsteadj1/strava-sync","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"@armsteadj1/strava-sync","version":"1.0.0","_id":"@armsteadj1/strava-sync@1.0.0","maintainers":[{"name":"armsteadj1","email":"armsteadj1@gmail.com"}],"bin":{"strava-sync":"dist/index.js"},"dist":{"shasum":"bc98eb50249a30a7c7fd42c28b61d7b7e49912fe","tarball":"https://registry.npmjs.org/@armsteadj1/strava-sync/-/strava-sync-1.0.0.tgz","fileCount":14,"integrity":"sha512-acMffZH7ujeQ1HkFBBrqL18R448eTJcW4lIc/UIGAUIxeWKrjeEczkJp4M7PHcNUsfykzc8jz9JIN2DrxCykng==","signatures":[{"sig":"MEYCIQDYh4//2QGk4/g6tsqwp6UkaUu2f+S+rUdWM2sECmfKFAIhALZ6+Htui0PPkqnSpvHsAotGGpGG3gy1OlSnQB/c29BO","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":25981},"gitHead":"a4c2aa76ed2b35ef5e22cb6ad9bd386d4d8c7b44","scripts":{"sync":"ts-node src/index.ts","build":"tsc","start":"ts-node src/index.ts","prepublishOnly":"npm run build"},"_npmUser":{"name":"armsteadj1","email":"armsteadj1@gmail.com"},"_npmVersion":"10.8.2","description":"Syncs Strava activities to a local SQLite database (`fitness.db`).","directories":{},"_nodeVersion":"20.20.0","dependencies":{"ts-node":"^10.9.2","typescript":"^5.4.2","better-sqlite3":"^9.4.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@types/node":"^20.11.30","@types/better-sqlite3":"^7.6.8"},"_npmOperationalInternal":{"tmp":"tmp/strava-sync_1.0.0_1773018136131_0.16896894210969338","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@armsteadj1/strava-sync","version":"1.1.0","bin":{"strava-sync":"dist/index.js"},"scripts":{"build":"tsc","start":"ts-node src/index.ts","sync":"ts-node src/index.ts","prepublishOnly":"npm run build"},"publishConfig":{"access":"public"},"dependencies":{"better-sqlite3":"^9.4.3","ts-node":"^10.9.2","typescript":"^5.4.2"},"devDependencies":{"@types/better-sqlite3":"^7.6.8","@types/node":"^20.11.30"},"_id":"@armsteadj1/strava-sync@1.1.0","gitHead":"1b4b085bbf6bf0322970272932f2940ec1c3e7c0","description":"Syncs Strava activities to a local SQLite database with full time-series data — power, HR, cadence, speed — plus private notes and rich activity metadata.","_nodeVersion":"20.20.1","_npmVersion":"10.8.2","dist":{"integrity":"sha512-WsFyCh7Zi5tTK+GpGXF1u+LaAMaeWD2QIM1f1pIezavJgFagGdLVEX0zHhLqb5RLyXucHx2vjKlLqRRS90tCvA==","shasum":"18317b11d5304a92080d6635cb13bed3104bc979","tarball":"https://registry.npmjs.org/@armsteadj1/strava-sync/-/strava-sync-1.1.0.tgz","fileCount":14,"unpackedSize":46961,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIAyQ6oqC5W9mh7ZswwkA9oIoCV8ANC/t2WglxPq9Vf3uAiEA/y4gAzMjluGvPxpWgRhXKcjd5cimeJB2Hbl1jefaUGM="}]},"_npmUser":{"name":"armsteadj1","email":"armsteadj1@gmail.com"},"directories":{},"maintainers":[{"name":"armsteadj1","email":"armsteadj1@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/strava-sync_1.1.0_1774732583103_0.526940455167271"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-09T01:02:16.015Z","modified":"2026-03-28T21:16:23.400Z","1.0.0":"2026-03-09T01:02:16.287Z","1.1.0":"2026-03-28T21:16:23.272Z"},"description":"Syncs Strava activities to a local SQLite database with full time-series data — power, HR, cadence, speed — plus private notes and rich activity metadata.","maintainers":[{"name":"armsteadj1","email":"armsteadj1@gmail.com"}],"readme":"# strava-sync\n\nSyncs Strava activities to a local SQLite database with full time-series data — power, HR, cadence, speed — plus private notes and rich activity metadata.\n\n## Installation\n\n```bash\nnpm install -g @armsteadj1/strava-sync\n```\n\nOr run from source:\n```bash\ngit clone https://github.com/armsteadj1/strava-sync\ncd strava-sync\nnpm install\n```\n\n## Setup\n\n### 1. Create a Strava API App\n\n1. Go to [strava.com/settings/api](https://www.strava.com/settings/api)\n2. Create an app — set **Authorization Callback Domain** to `localhost`\n3. Note your **Client ID** and **Client Secret**\n\n### 2. Configure\n\n```bash\ncp config.example.json config.json\n```\n\nFill in `client_id` and `client_secret`. Leave the token fields as-is — they'll be filled by the auth step.\n\n### 3. Authorize\n\n```bash\nstrava-sync auth\n```\n\nThis prints an OAuth URL. Open it in your browser, authorize, then paste the redirect URL back:\n\n```bash\nstrava-sync auth --code \"http://localhost:8888/callback/strava?code=abc123...\"\n```\n\nTokens are saved to `config.json` and auto-refresh on each sync.\n\n### 4. Initial Sync\n\n```bash\n# Pull all activities (detail + streams for rides/runs):\nstrava-sync sync --full\n\n# Backfill streams/detail on existing activities (50 at a time, ~2 req/sec):\nstrava-sync enrich\n```\n\n## Commands\n\n| Command | Description |\n|---|---|\n| `strava-sync sync` | Incremental sync — new activities only, auto-enriches them |\n| `strava-sync sync --full` | Full re-download from epoch 0 |\n| `strava-sync enrich` | Fetch detail + streams for up to 50 activities missing them |\n| `strava-sync enrich --limit=N` | Same, custom limit |\n| `strava-sync status` | Show DB stats (activity count, stream coverage, last sync) |\n| `strava-sync auth` | Start OAuth flow |\n| `strava-sync auth --code <url>` | Complete OAuth flow |\n\n## Database\n\nSQLite file: `fitness.db` (gitignored — never committed)\n\n### Tables\n\n**`activities`** — one row per activity\n\n| Column | Description |\n|---|---|\n| `id` | Strava activity ID |\n| `name` | Activity name |\n| `sport_type` | Ride, VirtualRide, Run, WeightTraining, etc. |\n| `start_date` | UTC ISO timestamp |\n| `moving_time_sec` | Duration in seconds |\n| `distance_m` | Distance in meters |\n| `avg_hr` / `max_hr` | Heart rate |\n| `avg_watts` / `max_watts` | Raw power averages |\n| `weighted_avg_watts` | Normalized Power (NP) — best effort metric |\n| `description` | Route/map name (e.g. \"Figure 8 in Watopia\") |\n| `private_notes` | Your own post-activity notes |\n| `has_streams` | 1 if time-series data is loaded |\n| `total_elevation_gain` | Elevation in meters |\n\n**`activity_streams`** — per-second time-series data for rides/runs\n\n| Column | Description |\n|---|---|\n| `activity_id` | Foreign key → activities.id |\n| `time_offset` | Seconds from activity start |\n| `watts` | Power output |\n| `heartrate` | Heart rate |\n| `cadence` | Pedal cadence (rpm) |\n| `velocity_ms` | Speed in m/s |\n| `altitude_m` | Elevation in meters |\n\n### Example Queries\n\n```bash\n# Recent activities with power data\nsqlite3 fitness.db \"\nSELECT name, datetime(start_date,'localtime'), moving_time_sec/60 as min,\n       weighted_avg_watts as np, avg_hr\nFROM activities WHERE start_date >= datetime('now','-7 days')\nORDER BY start_date DESC;\"\n\n# Best 20-minute power from a specific ride\nsqlite3 fitness.db \"\nSELECT MAX(avg_w) as best_20min_watts FROM (\n  SELECT AVG(watts) OVER (ORDER BY time_offset ROWS BETWEEN 0 PRECEDING AND 1199 FOLLOWING) as avg_w\n  FROM activity_streams WHERE activity_id = <ID> AND watts IS NOT NULL\n);\"\n\n# Power zone distribution (FTP = 230w)\nsqlite3 fitness.db \"\nSELECT\n  SUM(CASE WHEN watts < 138 THEN 1 ELSE 0 END) as z1_sec,\n  SUM(CASE WHEN watts >= 207 AND watts < 230 THEN 1 ELSE 0 END) as z4_sweet_spot_sec\nFROM activity_streams WHERE activity_id = <ID>;\"\n```\n\n## Auto-Sync (macOS)\n\nSet up a LaunchAgent to sync every hour:\n\n```bash\n# ~/Library/LaunchAgents/com.strava-sync.plist\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"...\">\n<plist version=\"1.0\">\n<dict>\n  <key>Label</key><string>com.strava-sync</string>\n  <key>ProgramArguments</key>\n  <array>\n    <string>/usr/local/bin/strava-sync</string>\n    <string>sync</string>\n  </array>\n  <key>WorkingDirectory</key><string>/path/to/your/strava-sync/dir</string>\n  <key>StartInterval</key><integer>3600</integer>\n</dict>\n</plist>\n```\n\n## Rate Limits\n\nStrava allows 100 requests/15 minutes and 1,000/day. The enrich command paces at ~1 req/second (2 requests per activity — detail + streams), so 50 activities ≈ 100 requests ≈ 100 seconds. Safe for daily use.\n","readmeFilename":"README.md"}