homebridge-peloton 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,29 @@
2
2
 
3
3
  All notable changes to homebridge-peloton are listed here. The format follows Keep a Changelog, and the project follows semantic versioning from 0.1.0-beta.1. The release notes of a version are its section here and nothing else.
4
4
 
5
+ ## 1.0.2 (2026-10-18)
6
+
7
+ A packaging and copy release, prepared as the plugin was verified by Homebridge (homebridge/plugins#1239). The developer auth probe stays out of the npm package, and nothing in the package description or the banner promises heart-rate zone triggers, which 1.0.x does not offer. No runtime code changes.
8
+
9
+ ### Changed
10
+
11
+ - The developer auth probe is no longer published to npm, at the suggestion of the Homebridge verification review (homebridge/plugins#1239). `"!dist/scripts"` in the `files` array of package.json keeps `auth-probe.mjs` and its `.d.ts` and `.map` files out of the package. The source stays in `src/scripts/` and `npm run build` still compiles it, so the probe runs from a clone. A package suite (`test/package.test.mjs`) runs `npm pack --dry-run --json` and fails if any path under `dist/scripts/` is in the package, if `dist/index.js`, `config.schema.json`, `homebridge-ui/server.js`, `homebridge-ui/public/index.html`, `README.md`, `LICENSE` or `CHANGELOG.md` is missing, or if any path is outside the files list.
12
+ - README, Auth probe: the section opens by saying the probe is not part of the npm package and runs only from a clone of this repository.
13
+ - Copy clarifications so nothing promises heart-rate zone triggers, which 1.0.x does not offer. The package.json description reads "Peloton workouts as HomeKit sensors for Homebridge automations: run scenes when a workout starts or ends." The banner tagline reads "HomeKit sensors driven by Peloton workouts: run scenes when a workout starts or ends."; `assets/peloton-banner.png` and `homebridge-ui/public/peloton-banner.png` are regenerated (1280 × 320) on the same layout, colours and type, and `bannerAlt` in copy.js, the README banner alt text and the banner line in design/README.md match. The Daily check-in caption keeps "heart-rate zones", since the check-in does refresh them.
14
+ - SPEC section 14 records the Homebridge verification (October 18, 2026, homebridge/plugins#1239): the Verified by Homebridge badge first in the README badge row, the icon in Homebridge's central plugin list, and a standing rule that the verification requirements stay met in every release. CLAUDE.md carries the same rule.
15
+ - Version 1.0.2 in package.json and the version literals in the tests and the bug report form. SPEC sections 5, 13, 14 and 16 and design/README.md carry the 1.0.2 clarifications.
16
+ - The package suite reads `npm pack --json` from npm 10 through 12. npm 12 prints an object keyed by package name where npm 10 and 11 print an array, which failed the first 1.0.2 release run before anything was published. The build workflow now installs npm@latest as the release workflow does, on the Node 22 and 24 legs; npm 12 refuses to install on Node 20, so that leg keeps its bundled npm 10 and the suite runs against both shapes.
17
+
18
+ ## 1.0.1 (2026-09-17)
19
+
20
+ A copy and asset release. The settings page intro matches the README and the 1.0.0 release notes: Heart-rate zone triggers are not offered, so the page no longer says they are. No code changes.
21
+
22
+ ### Changed
23
+
24
+ - Settings page intro paragraph one reads "Turns Peloton workouts into HomeKit sensors so automations can run when a workout starts or ends.", in the page copy and in the `headerDisplay` of `config.schema.json`. The banner tagline stays.
25
+ - `assets/screenshots/trigger-workout.png` and `settings.png` are 1.0.0 captures, moved from the repository root where the upload landed into `assets/screenshots/` where the README points. The README's settings caption names the Keep fast polling field and the Devices seen block the capture shows.
26
+ - Version 1.0.1 in package.json and the version literals in the tests and the bug report form. SPEC section 16 and design/README.md carry the 1.0.1 clarifications.
27
+
5
28
  ## 1.0.0 (2026-09-17)
6
29
 
7
30
  The first general release, to the npm latest tag. Every device the test membership can produce is named, the Device filter covers the apps, the plugin records the device codes it sees so a Row or Android report is a copy and paste, and a second workout after the first is caught quickly whether or not the fast polling switch is on.
package/README.md CHANGED
@@ -1,11 +1,6 @@
1
- <img src="assets/peloton-banner.png" alt="Peloton: HomeKit sensors driven by Peloton workouts" width="100%">
2
-
3
- <!--
4
- verified-by-homebridge: this plugin has not been through Homebridge verification yet.
5
- Do not claim it. Once the plugin is verified, replace this comment with the badge:
6
- [![verified-by-homebridge](https://badgen.net/badge/homebridge/verified/purple)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
7
- -->
1
+ <img src="assets/peloton-banner.png" alt="Peloton. HomeKit sensors driven by Peloton workouts: run scenes when a workout starts or ends." width="100%">
8
2
 
3
+ [![verified-by-homebridge](https://img.shields.io/badge/_-verified-blueviolet?color=%23491F59&style=flat&logoColor=%23FFFFFF&logo=homebridge)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
9
4
  [![npm version](https://img.shields.io/npm/v/homebridge-peloton)](https://www.npmjs.com/package/homebridge-peloton)
10
5
  [![npm downloads](https://img.shields.io/npm/dt/homebridge-peloton)](https://www.npmjs.com/package/homebridge-peloton)
11
6
  [![License](https://img.shields.io/github/license/arodbuilds/homebridge-peloton)](LICENSE)
@@ -89,7 +84,7 @@ Any trigger that means "a workout is about to start" works in place of the light
89
84
 
90
85
  ### 4. Settings and Advanced
91
86
 
92
- ![The Settings section with Advanced open: the auto-off period, the Attention needed sensor, the daily check-in time, and Restore from backup](assets/screenshots/settings.png)
87
+ ![The Settings section with Advanced open: the auto-off period, Keep fast polling after a workout ends, the Attention needed sensor, the daily check-in time, Devices seen, and Restore from backup](assets/screenshots/settings.png)
93
88
 
94
89
  Settings holds one Advanced disclosure, collapsed until something inside it is changed:
95
90
 
@@ -182,7 +177,7 @@ The network is always mocked in tests. Fixtures live under `fixtures/`, sanitise
182
177
 
183
178
  ### Auth probe
184
179
 
185
- The probe runs the headless sign-in on its own, without Homebridge, which is the first thing to try when sign-in stops working. Build first, then run from a directory where a token file may be written:
180
+ The probe is not part of the npm package and runs only from a clone of this repository. It runs the headless sign-in on its own, without Homebridge, which is the first thing to try when sign-in stops working. Build first, then run from a directory where a token file may be written:
186
181
 
187
182
  ```shell
188
183
  npm run build
@@ -5,7 +5,7 @@
5
5
  "customUi": true,
6
6
  "customUiPath": "./homebridge-ui",
7
7
  "strictValidation": false,
8
- "headerDisplay": "Turns Peloton workouts into HomeKit sensors so automations can run when a workout starts, ends, or reaches a heart-rate zone. Set up in two steps: connect your Peloton account, then add a trigger for what you want to automate. Save, restart Homebridge, and use the sensor in a HomeKit automation. Not affiliated with or endorsed by Peloton Interactive. Uses Peloton's undocumented member API, which can change without notice.",
8
+ "headerDisplay": "Turns Peloton workouts into HomeKit sensors so automations can run when a workout starts or ends. Set up in two steps: connect your Peloton account, then add a trigger for what you want to automate. Save, restart Homebridge, and use the sensor in a HomeKit automation. Not affiliated with or endorsed by Peloton Interactive. Uses Peloton's undocumented member API, which can change without notice.",
9
9
  "footerDisplay": "After saving, restart Homebridge. Your sensors appear in the Home app. Open Automations, choose a sensor as the trigger, and pick what should happen when a workout starts.",
10
10
  "schema": {
11
11
  "type": "object",
@@ -35,8 +35,8 @@ export const FIELD_LABELS = {
35
35
  };
36
36
 
37
37
  export const PAGE = {
38
- bannerAlt: 'Peloton. HomeKit sensors driven by Peloton workouts: workout in progress and heart-rate zones.',
39
- lead1: 'Turns Peloton workouts into HomeKit sensors so automations can run when a workout starts, ends, or reaches a heart-rate zone.',
38
+ bannerAlt: 'Peloton. HomeKit sensors driven by Peloton workouts: run scenes when a workout starts or ends.',
39
+ lead1: 'Turns Peloton workouts into HomeKit sensors so automations can run when a workout starts or ends.',
40
40
  lead2: 'Set up in two steps: connect your Peloton account, then add a trigger for what you want to automate. '
41
41
  + 'Save, restart Homebridge, and use the sensor in a HomeKit automation.',
42
42
  leadNote: 'Not affiliated with or endorsed by Peloton Interactive. Uses Peloton\'s undocumented member API, which can change without notice.',
package/package.json CHANGED
@@ -2,8 +2,8 @@
2
2
  "name": "homebridge-peloton",
3
3
  "displayName": "Peloton",
4
4
  "type": "module",
5
- "version": "1.0.0",
6
- "description": "Peloton workouts as HomeKit sensors for Homebridge automations: workout in progress and heart-rate zones.",
5
+ "version": "1.0.2",
6
+ "description": "Peloton workouts as HomeKit sensors for Homebridge automations: run scenes when a workout starts or ends.",
7
7
  "author": "Alex Rodriguez (https://alex-rodriguez.com)",
8
8
  "license": "Apache-2.0",
9
9
  "homepage": "https://github.com/arodbuilds/homebridge-peloton#readme",
@@ -28,6 +28,7 @@
28
28
  "types": "dist/index.d.ts",
29
29
  "files": [
30
30
  "dist",
31
+ "!dist/scripts",
31
32
  "homebridge-ui/public",
32
33
  "homebridge-ui/server.js",
33
34
  "config.schema.json",
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Standalone auth probe for the Pi. This is how the headless flow gets its first real run.
4
- *
5
- * node dist/scripts/auth-probe.mjs login <email> [--dump <dir>] prompts for the password without echo
6
- * node dist/scripts/auth-probe.mjs refresh
7
- * node dist/scripts/auth-probe.mjs browser prints the URL, waits for the pasted callback URL
8
- * node dist/scripts/auth-probe.mjs me
9
- * node dist/scripts/auth-probe.mjs workout [--keys] --keys also prints the raw field names of the workout
10
- * node dist/scripts/auth-probe.mjs graph <workoutId> prints metric slugs, sample counts, and heart-rate zone bounds
11
- *
12
- * Tokens live in ./probe-tokens.json with mode 0600. Nothing here prints a token.
13
- * On failure the probe prints the stage and HTTP status only.
14
- */
15
- export {};
@@ -1,381 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Standalone auth probe for the Pi. This is how the headless flow gets its first real run.
4
- *
5
- * node dist/scripts/auth-probe.mjs login <email> [--dump <dir>] prompts for the password without echo
6
- * node dist/scripts/auth-probe.mjs refresh
7
- * node dist/scripts/auth-probe.mjs browser prints the URL, waits for the pasted callback URL
8
- * node dist/scripts/auth-probe.mjs me
9
- * node dist/scripts/auth-probe.mjs workout [--keys] --keys also prints the raw field names of the workout
10
- * node dist/scripts/auth-probe.mjs graph <workoutId> prints metric slugs, sample counts, and heart-rate zone bounds
11
- *
12
- * Tokens live in ./probe-tokens.json with mode 0600. Nothing here prints a token.
13
- * On failure the probe prints the stage and HTTP status only.
14
- */
15
- import { chmod, readFile, writeFile } from 'node:fs/promises';
16
- import { createInterface } from 'node:readline/promises';
17
- import { ApiError, PELOTON_API_BASE, getLatestWorkout, getMe, getPerformanceGraph } from '../api/peloton-api.js';
18
- import { AuthError, browserFinish, browserStart, login, refresh } from '../auth/peloton-auth.js';
19
- const TOKEN_FILE = './probe-tokens.json';
20
- const REFRESH_AHEAD_MS = 6 * 60 * 60 * 1000;
21
- const CTRL_C = String.fromCharCode(3);
22
- const DELETE = String.fromCharCode(127);
23
- const BACKSPACE = String.fromCharCode(8);
24
- function parseArgs(argv) {
25
- const positional = [];
26
- let dump;
27
- let keys = false;
28
- for (let index = 0; index < argv.length; index += 1) {
29
- const arg = argv[index];
30
- if (arg === '--dump') {
31
- dump = argv[index + 1];
32
- index += 1;
33
- }
34
- else if (arg === '--keys') {
35
- keys = true;
36
- }
37
- else if (arg !== undefined) {
38
- positional.push(arg);
39
- }
40
- }
41
- return { command: positional.shift(), positional, dump, keys };
42
- }
43
- function usage() {
44
- console.error([
45
- 'Usage:',
46
- ' node dist/scripts/auth-probe.mjs login <email> [--dump <dir>]',
47
- ' node dist/scripts/auth-probe.mjs refresh',
48
- ' node dist/scripts/auth-probe.mjs browser',
49
- ' node dist/scripts/auth-probe.mjs me',
50
- ' node dist/scripts/auth-probe.mjs workout [--keys]',
51
- ' node dist/scripts/auth-probe.mjs graph <workoutId>',
52
- ].join('\n'));
53
- process.exit(2);
54
- }
55
- /* ------------------------------------------------------------------------------------------------
56
- * Token file
57
- * ---------------------------------------------------------------------------------------------- */
58
- async function loadTokens() {
59
- let text;
60
- try {
61
- text = await readFile(TOKEN_FILE, 'utf8');
62
- }
63
- catch {
64
- throw new Error(`No ${TOKEN_FILE} yet. Run login or browser first.`);
65
- }
66
- const parsed = JSON.parse(text);
67
- if (typeof parsed.accessToken !== 'string' || typeof parsed.refreshToken !== 'string' || typeof parsed.expiresAt !== 'number') {
68
- throw new Error(`${TOKEN_FILE} is malformed. Run login or browser again.`);
69
- }
70
- return parsed;
71
- }
72
- async function saveTokens(tokens) {
73
- await writeFile(TOKEN_FILE, JSON.stringify(tokens, null, 2) + '\n', { mode: 0o600 });
74
- await chmod(TOKEN_FILE, 0o600);
75
- }
76
- function describeExpiry(tokens) {
77
- const hours = Math.round((tokens.expiresAt - Date.now()) / 36000) / 100;
78
- return `access token expires ${new Date(tokens.expiresAt).toISOString()} (in ${hours} h)`;
79
- }
80
- /* ------------------------------------------------------------------------------------------------
81
- * Prompts
82
- * ---------------------------------------------------------------------------------------------- */
83
- async function promptVisible(question) {
84
- const rl = createInterface({ input: process.stdin, output: process.stdout });
85
- try {
86
- return (await rl.question(question)).trim();
87
- }
88
- finally {
89
- rl.close();
90
- }
91
- }
92
- /** Reads a line from the terminal without echoing it. Falls back to a plain read when not a TTY. */
93
- function promptHidden(question) {
94
- const stdin = process.stdin;
95
- process.stdout.write(question);
96
- if (!stdin.isTTY) {
97
- return new Promise((resolve) => {
98
- let buffer = '';
99
- stdin.setEncoding('utf8');
100
- stdin.on('data', (chunk) => {
101
- buffer += chunk;
102
- const newline = buffer.indexOf('\n');
103
- if (newline >= 0) {
104
- stdin.pause();
105
- resolve(buffer.slice(0, newline).replace(/\r$/, ''));
106
- }
107
- });
108
- stdin.on('end', () => resolve(buffer.replace(/\r?\n$/, '')));
109
- });
110
- }
111
- return new Promise((resolve, reject) => {
112
- let value = '';
113
- let onData = () => undefined;
114
- const finish = () => {
115
- stdin.removeListener('data', onData);
116
- stdin.setRawMode(false);
117
- stdin.pause();
118
- process.stdout.write('\n');
119
- };
120
- onData = (chunk) => {
121
- for (const char of chunk) {
122
- if (char === '\r' || char === '\n') {
123
- finish();
124
- resolve(value);
125
- return;
126
- }
127
- if (char === CTRL_C) {
128
- finish();
129
- reject(new Error('Cancelled'));
130
- return;
131
- }
132
- if (char === DELETE || char === BACKSPACE) {
133
- value = value.slice(0, -1);
134
- continue;
135
- }
136
- value += char;
137
- }
138
- };
139
- stdin.setRawMode(true);
140
- stdin.resume();
141
- stdin.setEncoding('utf8');
142
- stdin.on('data', onData);
143
- });
144
- }
145
- /* ------------------------------------------------------------------------------------------------
146
- * Commands
147
- * ---------------------------------------------------------------------------------------------- */
148
- async function commandLogin(args) {
149
- const email = args.positional[0];
150
- if (email === undefined) {
151
- usage();
152
- }
153
- const password = await promptHidden('Peloton password: ');
154
- if (password.length === 0) {
155
- throw new Error('Empty password');
156
- }
157
- if (args.dump !== undefined) {
158
- console.log(`Writing redacted HTML dumps to ${args.dump}`);
159
- }
160
- const tokens = await login(email, password, fetch, {
161
- debugDump: args.dump,
162
- onLoginPage: (target) => {
163
- console.log(`Login page parsed: domain ${target.auth0Domain}, tenant ${target.auth0Tenant}, connection ${target.connection}`);
164
- },
165
- });
166
- await saveTokens(tokens);
167
- console.log(`Login ok, ${describeExpiry(tokens)}. Tokens saved to ${TOKEN_FILE}.`);
168
- }
169
- async function commandRefresh() {
170
- const stored = await loadTokens();
171
- const tokens = await refresh(stored.refreshToken);
172
- await saveTokens({ ...tokens, userId: stored.userId });
173
- const rotated = tokens.refreshToken !== stored.refreshToken;
174
- console.log(`Refresh ok, ${describeExpiry(tokens)}. Refresh token ${rotated ? 'rotated' : 'unchanged'}.`);
175
- }
176
- async function commandBrowser() {
177
- const start = browserStart();
178
- console.log('Open this URL in a browser, sign in, then paste the full address the browser lands on:');
179
- console.log('');
180
- console.log(start.authorizeUrl);
181
- console.log('');
182
- const callbackUrl = await promptVisible('Callback URL: ');
183
- const tokens = await browserFinish(callbackUrl, start.verifier, start.state);
184
- await saveTokens(tokens);
185
- console.log(`Browser sign-in ok, ${describeExpiry(tokens)}. Tokens saved to ${TOKEN_FILE}.`);
186
- }
187
- /** Runs fn with a valid access token, refreshing when close to expiry or on 401, like the account store. */
188
- async function withToken(fn) {
189
- let stored = await loadTokens();
190
- if (stored.expiresAt - Date.now() <= REFRESH_AHEAD_MS) {
191
- console.log('Access token is within 6 hours of expiry, refreshing first.');
192
- stored = await refreshStored(stored);
193
- }
194
- try {
195
- return await fn(stored.accessToken);
196
- }
197
- catch (error) {
198
- if (!(error instanceof ApiError) || error.status !== 401) {
199
- throw error;
200
- }
201
- console.log('HTTP 401, refreshing and retrying once.');
202
- }
203
- stored = await refreshStored(stored);
204
- return fn(stored.accessToken);
205
- }
206
- async function refreshStored(stored) {
207
- const tokens = await refresh(stored.refreshToken);
208
- const updated = { ...tokens, userId: stored.userId };
209
- await saveTokens(updated);
210
- return updated;
211
- }
212
- async function resolveUserId(accessToken) {
213
- const stored = await loadTokens();
214
- if (stored.userId !== undefined) {
215
- return stored.userId;
216
- }
217
- const me = await getMe(accessToken);
218
- await saveTokens({ ...stored, userId: me.id });
219
- return me.id;
220
- }
221
- async function commandMe() {
222
- await withToken(async (accessToken) => {
223
- const me = await getMe(accessToken);
224
- const stored = await loadTokens();
225
- await saveTokens({ ...stored, userId: me.id });
226
- console.log(`User id: ${me.id}`);
227
- console.log(`Username: ${me.username}`);
228
- console.log(`Name: ${me.firstName} ${me.lastName}`);
229
- console.log(`Default avatar: ${me.isProfileImageDefault}`);
230
- console.log(`Customised heart rate zones: ${me.customizedHeartRateZones.length}`);
231
- for (const zone of me.customizedHeartRateZones) {
232
- console.log(` zone ${zone.zone} (${zone.slug}): ${zone.minValue} to ${zone.maxValue}`);
233
- }
234
- console.log(`Max heart rate: default ${me.defaultMaxHeartRate ?? 'none'}, customised ${me.customizedMaxHeartRate ?? 'none'}`);
235
- console.log(`Paired devices: ${me.pairedDevices.length}`);
236
- const lastWorkoutAt = me.lastWorkoutAt === null ? 'none' : new Date(me.lastWorkoutAt * 1000).toISOString();
237
- console.log(`last_workout_at on /api/me (stale, not used by the plugin): ${lastWorkoutAt}`);
238
- });
239
- }
240
- async function commandWorkout(args) {
241
- await withToken(async (accessToken) => {
242
- const userId = await resolveUserId(accessToken);
243
- const workout = await getLatestWorkout(userId, accessToken);
244
- if (workout === null) {
245
- console.log('No workouts yet.');
246
- return;
247
- }
248
- console.log(`Workout id: ${workout.id}`);
249
- console.log(`Status: ${workout.status}`);
250
- console.log(`Discipline: ${workout.fitnessDiscipline}`);
251
- console.log(`Device type: ${workout.deviceType}`);
252
- console.log(`Workout type: ${workout.workoutType}`);
253
- console.log(`Title: ${workout.title}`);
254
- console.log(`Name: ${workout.name}`);
255
- console.log(`Platform: ${workout.platform}`);
256
- console.log(`Peloton originated: ${workout.isPelotonOriginatedWorkout}, third-party fit feed import: ${workout.is3pFitFeedWorkout}`);
257
- if (workout.ride !== null) {
258
- console.log(`Ride: ${workout.ride.title} (${workout.ride.duration ?? 'unknown'} s, instructor ${workout.ride.instructorId ?? 'none'})`);
259
- }
260
- console.log(`Start: ${formatEpoch(workout.startTime)}, end: ${formatEpoch(workout.endTime)}, created: ${formatEpoch(workout.createdAt)}`);
261
- console.log('Last workout at (from created_at): ' + formatEpoch(workout.createdAt));
262
- if (args.keys) {
263
- const keys = await rawWorkoutKeys(userId, accessToken);
264
- console.log(`Raw workout fields (names only): ${keys.join(', ')}`);
265
- }
266
- if (workout.status !== 'IN_PROGRESS') {
267
- return;
268
- }
269
- const graph = await getPerformanceGraph(workout.id, 5, accessToken);
270
- console.log(`Samples: ${graph.secondsSincePedalingStart.length}, latest at ${graph.secondsSincePedalingStart.at(-1) ?? 'none'} s`);
271
- if (graph.heartRate === null) {
272
- console.log('Heart rate metric: absent');
273
- return;
274
- }
275
- console.log(`Heart rate: latest ${graph.heartRate.latestSample ?? 'none'} bpm, ${graph.heartRate.zones.length} zone bounds`);
276
- for (const zone of graph.heartRate.zones) {
277
- console.log(` zone ${zone.zone} (${zone.slug}): ${zone.minValue} to ${zone.maxValue}`);
278
- }
279
- });
280
- }
281
- /**
282
- * Prints what the performance graph of one workout carries: every metric slug with its sample
283
- * count, the heart-rate zone bounds, and the latest heart-rate sample. Settles SPEC items 3 and 8
284
- * for any workout type by id. Nothing here prints a token.
285
- */
286
- async function commandGraph(args) {
287
- const workoutId = args.positional[0];
288
- if (workoutId === undefined) {
289
- usage();
290
- }
291
- await withToken(async (accessToken) => {
292
- const metrics = await rawMetricSummary(workoutId, accessToken);
293
- console.log(`Metrics: ${metrics.length}`);
294
- for (const metric of metrics) {
295
- console.log(` ${metric.slug}: ${metric.samples} samples${metric.zones === null ? '' : `, ${metric.zones} zone bounds`}`);
296
- }
297
- const graph = await getPerformanceGraph(workoutId, 5, accessToken);
298
- console.log(`Sample offsets: ${graph.secondsSincePedalingStart.length}, latest at ${graph.secondsSincePedalingStart.at(-1) ?? 'none'} s`);
299
- if (graph.heartRate === null) {
300
- console.log('Heart rate metric: absent');
301
- return;
302
- }
303
- console.log(`Heart rate: latest ${graph.heartRate.latestSample ?? 'none'} bpm, ${graph.heartRate.zones.length} zone bounds`);
304
- for (const zone of graph.heartRate.zones) {
305
- console.log(` ${zone.slug}: min_value ${zone.minValue}, max_value ${zone.maxValue}`);
306
- }
307
- });
308
- }
309
- /** Fetches the performance graph raw and returns only each metric's slug, sample count, and zone count. */
310
- async function rawMetricSummary(workoutId, accessToken) {
311
- const response = await fetch(`${PELOTON_API_BASE}/api/workout/${encodeURIComponent(workoutId)}/performance_graph?every_n=5`, {
312
- headers: { authorization: `Bearer ${accessToken}`, 'peloton-platform': 'web', accept: 'application/json' },
313
- });
314
- if (!response.ok) {
315
- throw new ApiError(response.status, '/api/workout/{id}/performance_graph');
316
- }
317
- const body = await response.json();
318
- return (body.metrics ?? []).map((entry) => {
319
- const metric = entry !== null && typeof entry === 'object' ? entry : {};
320
- return {
321
- slug: typeof metric.slug === 'string' ? metric.slug : '(no slug)',
322
- samples: Array.isArray(metric.values) ? metric.values.length : 0,
323
- zones: Array.isArray(metric.zones) ? metric.zones.length : null,
324
- };
325
- });
326
- }
327
- /** Fetches the latest workout again and returns only the top-level field names. This is what settled SPEC open item 1: no device id field. */
328
- async function rawWorkoutKeys(userId, accessToken) {
329
- const response = await fetch(`${PELOTON_API_BASE}/api/user/${encodeURIComponent(userId)}/workouts?limit=1&sort_by=-created`, {
330
- headers: { authorization: `Bearer ${accessToken}`, 'peloton-platform': 'web', accept: 'application/json' },
331
- });
332
- if (!response.ok) {
333
- throw new ApiError(response.status, '/api/user/{id}/workouts');
334
- }
335
- const body = await response.json();
336
- const first = body.data?.[0];
337
- return first !== null && typeof first === 'object' ? Object.keys(first).sort() : [];
338
- }
339
- function formatEpoch(seconds) {
340
- return seconds === null ? 'none' : new Date(seconds * 1000).toISOString();
341
- }
342
- /* ------------------------------------------------------------------------------------------------
343
- * Entry
344
- * ---------------------------------------------------------------------------------------------- */
345
- async function main() {
346
- const args = parseArgs(process.argv.slice(2));
347
- switch (args.command) {
348
- case 'login':
349
- return commandLogin(args);
350
- case 'refresh':
351
- return commandRefresh();
352
- case 'browser':
353
- return commandBrowser();
354
- case 'me':
355
- return commandMe();
356
- case 'workout':
357
- return commandWorkout(args);
358
- case 'graph':
359
- return commandGraph(args);
360
- default:
361
- usage();
362
- }
363
- }
364
- main().catch((error) => {
365
- if (error instanceof AuthError) {
366
- const status = error.status === undefined ? 'no HTTP status' : `HTTP ${error.status}`;
367
- const code = error.code === undefined ? '' : `, code ${error.code}`;
368
- console.error(`Auth failed at stage ${error.stage} (${status}${code})`);
369
- }
370
- else if (error instanceof ApiError) {
371
- console.error(`API call failed: HTTP ${error.status} on ${error.path}`);
372
- }
373
- else if (error instanceof Error) {
374
- console.error(`Failed: ${error.message}`);
375
- }
376
- else {
377
- console.error('Failed');
378
- }
379
- process.exit(1);
380
- });
381
- //# sourceMappingURL=auth-probe.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth-probe.mjs","sourceRoot":"","sources":["../../src/scripts/auth-probe.mts"],"names":[],"mappings":"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"}