@dofek/trainerroad 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Asher Cohen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # @dofek/trainerroad
2
+
3
+ Unofficial TypeScript client for private TrainerRoad web endpoints. It signs in
4
+ through the observed HTML form flow and retrieves member information,
5
+ activities, and career data.
6
+
7
+ This package is not affiliated with, endorsed by, or supported by TrainerRoad.
8
+ Its authentication page and undocumented endpoints may change without notice.
9
+
10
+ ## Requirements and installation
11
+
12
+ Requires Node.js 22.14 or newer and its built-in
13
+ [`fetch`](https://nodejs.org/api/globals.html#fetch) implementation.
14
+
15
+ ```sh
16
+ npm install @dofek/trainerroad
17
+ ```
18
+
19
+ ## Quick start
20
+
21
+ Save this as `example.mjs`, set `TRAINERROAD_USERNAME` and
22
+ `TRAINERROAD_PASSWORD`, then run `node example.mjs`.
23
+
24
+ ```js
25
+ import { TrainerRoadClient } from "@dofek/trainerroad";
26
+
27
+ const login = process.env.TRAINERROAD_USERNAME;
28
+ const password = process.env.TRAINERROAD_PASSWORD;
29
+ if (!login || !password) {
30
+ throw new Error("Set TRAINERROAD_USERNAME and TRAINERROAD_PASSWORD");
31
+ }
32
+
33
+ const { authCookie, username } = await TrainerRoadClient.signIn(
34
+ login,
35
+ password,
36
+ );
37
+ const client = new TrainerRoadClient(authCookie);
38
+ const activities = await client.getActivities(
39
+ username,
40
+ "2026-07-01",
41
+ "2026-07-07",
42
+ );
43
+
44
+ console.log(activities);
45
+ ```
46
+
47
+ Dates passed to `getActivities` use `YYYY-MM-DD`.
48
+
49
+ ## Public API
50
+
51
+ - `TrainerRoadClient.signIn(username, password, fetch?)` returns the observed
52
+ `SharedTrainerRoadAuth` cookie value and canonical account username.
53
+ - `new TrainerRoadClient(authCookie, fetch?)` creates a session-backed client.
54
+ - `client.getMemberInfo()` retrieves the current member's ID and username.
55
+ - `client.getActivities(username, startDate, endDate)` retrieves scheduled and
56
+ completed calendar activities.
57
+ - `client.getCareer(username)` retrieves career data such as FTP and weight.
58
+
59
+ Supported deep imports:
60
+
61
+ - `@dofek/trainerroad/client` — client class.
62
+ - `@dofek/trainerroad/parsing` — activity type mapping and
63
+ `parseTrainerRoadActivity`.
64
+ - `@dofek/trainerroad/types` — raw member, activity, and career interfaces.
65
+
66
+ ```ts
67
+ import { parseTrainerRoadActivity } from "@dofek/trainerroad/parsing";
68
+ import type { TrainerRoadActivity } from "@dofek/trainerroad/types";
69
+ ```
70
+
71
+ ## Authentication and persistence
72
+
73
+ The [current implementation](https://github.com/Asherlc/dofek/blob/main/packages/trainerroad-client/src/client.ts)
74
+ loads `/app/login`, extracts the `__RequestVerificationToken` CSRF field and
75
+ initial cookies, submits the login form, and returns the
76
+ `SharedTrainerRoadAuth` cookie set by the response. It does not use embedded
77
+ application credentials.
78
+
79
+ Treat `authCookie` as a password-equivalent session secret and keep it in
80
+ encrypted storage. The package cannot inspect its expiry and has no cookie
81
+ refresh endpoint. If an authenticated request begins failing, call `signIn`
82
+ again and replace the stored cookie. Never log or commit the cookie or account
83
+ password.
84
+
85
+ ## Rate limits and errors
86
+
87
+ The shared
88
+ [rate-limit wrapper](https://github.com/Asherlc/dofek/blob/main/packages/provider-http/src/rate-limit.ts)
89
+ throws `ProviderRateLimitError` for `429` and
90
+ `ProviderServiceUnavailableError` for `502`, `503`, and `504`. Both expose
91
+ `providerId`, `statusCode`, `responseBody`, and `retryAfterSeconds`. The latter
92
+ follows the HTTP
93
+ [`Retry-After`](https://www.rfc-editor.org/rfc/rfc9110.html#name-retry-after)
94
+ header when present. Other unsuccessful API responses throw a regular `Error`;
95
+ a failed login without the expected session cookie also throws `Error`.
96
+
97
+ If your application handles these error classes directly, declare
98
+ `@dofek/provider-http` as a direct dependency:
99
+
100
+ ```sh
101
+ npm install @dofek/provider-http
102
+ ```
103
+
104
+ ```ts
105
+ import {
106
+ ProviderRateLimitError,
107
+ ProviderServiceUnavailableError,
108
+ } from "@dofek/provider-http/rate-limit";
109
+
110
+ try {
111
+ await client.getCareer(username);
112
+ } catch (error) {
113
+ if (
114
+ error instanceof ProviderRateLimitError ||
115
+ error instanceof ProviderServiceUnavailableError
116
+ ) {
117
+ console.error(error.providerId, error.statusCode, error.retryAfterSeconds);
118
+ }
119
+ throw error;
120
+ }
121
+ ```
122
+
123
+ ## Parsing behavior
124
+
125
+ `parseTrainerRoadActivity` maps the currently observed activity names and
126
+ `IsOutside` flag to canonical activity types. It treats `CompletedDate` as the
127
+ end time and derives the start by subtracting `Duration`.
128
+
129
+ ## Project
130
+
131
+ - [Source](https://github.com/Asherlc/dofek/tree/main/packages/trainerroad-client)
132
+ - [Report an issue](https://github.com/Asherlc/dofek/issues)
133
+ - [Contribute a pull request](https://github.com/Asherlc/dofek/pulls)
134
+ - License: [MIT](./LICENSE)
@@ -0,0 +1,13 @@
1
+ import type { TrainerRoadActivity, TrainerRoadCareer, TrainerRoadMemberInfo } from "./types.ts";
2
+ export declare class TrainerRoadClient {
3
+ #private;
4
+ constructor(authCookie: string, fetchFn?: typeof globalThis.fetch);
5
+ getMemberInfo(): Promise<TrainerRoadMemberInfo>;
6
+ getActivities(username: string, startDate: string, endDate: string): Promise<TrainerRoadActivity[]>;
7
+ getCareer(username: string): Promise<TrainerRoadCareer>;
8
+ static signIn(username: string, password: string, fetchFn?: typeof globalThis.fetch): Promise<{
9
+ authCookie: string;
10
+ username: string;
11
+ }>;
12
+ }
13
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAIhG,qBAAa,iBAAiB;;gBAIhB,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB;IAsB7E,aAAa,IAAI,OAAO,CAAC,qBAAqB,CAAC;IAI/C,aAAa,CACjB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAM3B,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;WAIhD,MAAM,CACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB,GAClD,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAkDrD"}
package/dist/client.js ADDED
@@ -0,0 +1,77 @@
1
+ import { createRateLimitAwareFetch } from "@dofek/provider-http/rate-limit";
2
+ const TRAINERROAD_BASE = "https://www.trainerroad.com";
3
+ export class TrainerRoadClient {
4
+ #authCookie;
5
+ #fetchFn;
6
+ constructor(authCookie, fetchFn = globalThis.fetch) {
7
+ this.#authCookie = authCookie;
8
+ this.#fetchFn = createRateLimitAwareFetch(fetchFn, { providerId: "trainerroad" });
9
+ }
10
+ async #get(path) {
11
+ const url = `${TRAINERROAD_BASE}${path}`;
12
+ const response = await this.#fetchFn(url, {
13
+ headers: {
14
+ Cookie: `SharedTrainerRoadAuth=${this.#authCookie}`,
15
+ Accept: "application/json",
16
+ },
17
+ });
18
+ if (!response.ok) {
19
+ const text = await response.text();
20
+ throw new Error(`TrainerRoad API error (${response.status}): ${text}`);
21
+ }
22
+ return response.json();
23
+ }
24
+ async getMemberInfo() {
25
+ return this.#get("/app/api/member-info");
26
+ }
27
+ async getActivities(username, startDate, endDate) {
28
+ return this.#get(`/app/api/calendar/activities/${username}?startDate=${startDate}&endDate=${endDate}`);
29
+ }
30
+ async getCareer(username) {
31
+ return this.#get(`/app/api/career/${username}/new`);
32
+ }
33
+ static async signIn(username, password, fetchFn = globalThis.fetch) {
34
+ const rateLimitFetchFn = createRateLimitAwareFetch(fetchFn, { providerId: "trainerroad" });
35
+ // First, get the CSRF token from the login page
36
+ const loginPageResponse = await rateLimitFetchFn(`${TRAINERROAD_BASE}/app/login`, {
37
+ redirect: "manual",
38
+ });
39
+ const loginPageHtml = await loginPageResponse.text();
40
+ // Extract __RequestVerificationToken from the page
41
+ const tokenMatch = loginPageHtml.match(/name="__RequestVerificationToken"\s+value="([^"]+)"/);
42
+ const csrfToken = tokenMatch?.[1] ?? "";
43
+ // Extract cookies from the login page response
44
+ const pageCookies = loginPageResponse.headers.getSetCookie?.() ?? [];
45
+ const cookieHeader = pageCookies.map((c) => c.split(";")[0]).join("; ");
46
+ // Submit login form
47
+ const loginResponse = await rateLimitFetchFn(`${TRAINERROAD_BASE}/app/login`, {
48
+ method: "POST",
49
+ headers: {
50
+ "Content-Type": "application/x-www-form-urlencoded",
51
+ Cookie: cookieHeader,
52
+ },
53
+ body: new URLSearchParams({
54
+ Username: username,
55
+ Password: password,
56
+ __RequestVerificationToken: csrfToken,
57
+ }),
58
+ redirect: "manual",
59
+ });
60
+ // Extract auth cookie from response
61
+ const responseCookies = loginResponse.headers.getSetCookie?.() ?? [];
62
+ const authCookieEntry = responseCookies.find((c) => c.startsWith("SharedTrainerRoadAuth="));
63
+ if (!authCookieEntry) {
64
+ throw new Error("TrainerRoad login failed — no auth cookie returned");
65
+ }
66
+ // authCookieEntry was matched by startsWith("SharedTrainerRoadAuth="), so it
67
+ // always contains "=" — take everything after the first one (the value may
68
+ // itself contain "=") up to the first attribute separator.
69
+ const firstEqualsIndex = authCookieEntry.indexOf("=");
70
+ const authCookieValue = authCookieEntry.slice(firstEqualsIndex + 1).split(";")[0] ?? "";
71
+ // Get username from member info
72
+ const client = new TrainerRoadClient(authCookieValue, fetchFn);
73
+ const memberInfo = await client.getMemberInfo();
74
+ return { authCookie: authCookieValue, username: memberInfo.Username };
75
+ }
76
+ }
77
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { CanonicalActivityType } from "@dofek/training/training";
2
+ import type { TrainerRoadActivity } from "./types.ts";
3
+ export interface ParsedTrainerRoadActivity {
4
+ externalId: string;
5
+ activityType: CanonicalActivityType;
6
+ name: string;
7
+ startedAt: Date;
8
+ endedAt: Date;
9
+ raw: Record<string, unknown>;
10
+ }
11
+ export declare function mapTrainerRoadActivityType(activityType: string, isOutside: boolean): CanonicalActivityType;
12
+ export declare function parseTrainerRoadActivity(act: TrainerRoadActivity): ParsedTrainerRoadActivity;
13
+ //# sourceMappingURL=parsing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parsing.d.ts","sourceRoot":"","sources":["../src/parsing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,qBAAqB,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,EAAE,IAAI,CAAC;IACd,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED,wBAAgB,0BAA0B,CACxC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,OAAO,GACjB,qBAAqB,CAQvB;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,mBAAmB,GAAG,yBAAyB,CA4B5F"}
@@ -0,0 +1,40 @@
1
+ export function mapTrainerRoadActivityType(activityType, isOutside) {
2
+ const type = activityType.toLowerCase();
3
+ if (type.includes("ride") || type.includes("cycling")) {
4
+ return isOutside ? "cycling" : "virtual_cycling";
5
+ }
6
+ if (type.includes("run"))
7
+ return "running";
8
+ if (type.includes("swim"))
9
+ return "swimming";
10
+ return "other";
11
+ }
12
+ export function parseTrainerRoadActivity(act) {
13
+ const completedAt = new Date(act.CompletedDate);
14
+ const startedAt = new Date(completedAt.getTime() - act.Duration * 1000);
15
+ return {
16
+ externalId: String(act.Id),
17
+ activityType: mapTrainerRoadActivityType(act.ActivityType, act.IsOutside),
18
+ name: act.WorkoutName,
19
+ startedAt,
20
+ endedAt: completedAt,
21
+ raw: {
22
+ tss: act.Tss,
23
+ distanceMeters: act.DistanceInMeters,
24
+ normalizedPower: act.NormalizedPower,
25
+ avgPower: act.AveragePower,
26
+ maxPower: act.MaxPower,
27
+ avgHeartRate: act.AverageHeartRate,
28
+ maxHeartRate: act.MaxHeartRate,
29
+ avgCadence: act.AverageCadence,
30
+ maxCadence: act.MaxCadence,
31
+ calories: act.Calories,
32
+ elevationGain: act.ElevationGainInMeters,
33
+ avgSpeed: act.AverageSpeed,
34
+ maxSpeed: act.MaxSpeed,
35
+ intensityFactor: act.IfFactor,
36
+ isOutside: act.IsOutside,
37
+ },
38
+ };
39
+ }
40
+ //# sourceMappingURL=parsing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parsing.js","sourceRoot":"","sources":["../src/parsing.ts"],"names":[],"mappings":"AAYA,MAAM,UAAU,0BAA0B,CACxC,YAAoB,EACpB,SAAkB;IAElB,MAAM,IAAI,GAAG,YAAY,CAAC,WAAW,EAAE,CAAC;IACxC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACtD,OAAO,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC;IACnD,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC3C,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,UAAU,CAAC;IAC7C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,GAAwB;IAC/D,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IAExE,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,YAAY,EAAE,0BAA0B,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,SAAS,CAAC;QACzE,IAAI,EAAE,GAAG,CAAC,WAAW;QACrB,SAAS;QACT,OAAO,EAAE,WAAW;QACpB,GAAG,EAAE;YACH,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,cAAc,EAAE,GAAG,CAAC,gBAAgB;YACpC,eAAe,EAAE,GAAG,CAAC,eAAe;YACpC,QAAQ,EAAE,GAAG,CAAC,YAAY;YAC1B,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,YAAY,EAAE,GAAG,CAAC,gBAAgB;YAClC,YAAY,EAAE,GAAG,CAAC,YAAY;YAC9B,UAAU,EAAE,GAAG,CAAC,cAAc;YAC9B,UAAU,EAAE,GAAG,CAAC,UAAU;YAC1B,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,aAAa,EAAE,GAAG,CAAC,qBAAqB;YACxC,QAAQ,EAAE,GAAG,CAAC,YAAY;YAC1B,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,eAAe,EAAE,GAAG,CAAC,QAAQ;YAC7B,SAAS,EAAE,GAAG,CAAC,SAAS;SACzB;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,31 @@
1
+ export interface TrainerRoadMemberInfo {
2
+ MemberId: number;
3
+ Username: string;
4
+ }
5
+ export interface TrainerRoadActivity {
6
+ Id: number;
7
+ WorkoutName: string;
8
+ CompletedDate: string;
9
+ Duration: number;
10
+ Tss: number;
11
+ DistanceInMeters: number;
12
+ IsOutside: boolean;
13
+ ActivityType: string;
14
+ IfFactor: number;
15
+ NormalizedPower: number;
16
+ AveragePower: number;
17
+ MaxPower: number;
18
+ AverageHeartRate: number;
19
+ MaxHeartRate: number;
20
+ AverageCadence: number;
21
+ MaxCadence: number;
22
+ Calories: number;
23
+ ElevationGainInMeters: number;
24
+ AverageSpeed: number;
25
+ MaxSpeed: number;
26
+ }
27
+ export interface TrainerRoadCareer {
28
+ Ftp: number;
29
+ Weight: number;
30
+ }
31
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@dofek/trainerroad",
3
+ "version": "0.1.0",
4
+ "description": "Unofficial TrainerRoad API client using reverse-engineered cookie-based authentication",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Asherlc/dofek.git",
10
+ "directory": "packages/trainerroad-client"
11
+ },
12
+ "homepage": "https://github.com/Asherlc/dofek/tree/main/packages/trainerroad-client#readme",
13
+ "bugs": "https://github.com/Asherlc/dofek/issues",
14
+ "keywords": [
15
+ "trainerroad",
16
+ "cycling",
17
+ "api-client",
18
+ "unofficial",
19
+ "typescript"
20
+ ],
21
+ "engines": {
22
+ "node": ">=22.14.0"
23
+ },
24
+ "sideEffects": false,
25
+ "files": [
26
+ "dist",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/client.d.ts",
33
+ "import": "./dist/client.js"
34
+ },
35
+ "./*": {
36
+ "types": "./dist/*.d.ts",
37
+ "import": "./dist/*.js"
38
+ }
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "scripts": {
44
+ "test": "vitest run",
45
+ "test:watch": "vitest --watch",
46
+ "build": "tsc",
47
+ "prepack": "pnpm run build",
48
+ "lint": "biome check .",
49
+ "typecheck": "tsc --noEmit"
50
+ },
51
+ "dependencies": {
52
+ "@dofek/provider-http": "0.1.0",
53
+ "@dofek/training": "0.1.0"
54
+ },
55
+ "devDependencies": {
56
+ "typescript": "6.0.3",
57
+ "vitest": "3.2.6"
58
+ },
59
+ "gitHead": "ac95258c753c7e75b57da7ae47426d22b78e8c01"
60
+ }