@wattsonclub/wattson-cli 0.1.0 → 0.1.1

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/dist/bin.js CHANGED
@@ -5,8 +5,8 @@ var __export = (target, all) => {
5
5
  __defProp(target, name, { get: all[name], enumerable: true });
6
6
  };
7
7
 
8
- // src/cli.ts
9
- import { readFile as readFile2 } from "node:fs/promises";
8
+ // src/tool-command.ts
9
+ import { readFile } from "node:fs/promises";
10
10
 
11
11
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
12
12
  var external_exports = {};
@@ -6169,13 +6169,13 @@ function createPlannerClient(options = {}) {
6169
6169
  try {
6170
6170
  return await fetchImpl(buildUrl(baseUrl, input.path, input.query), {
6171
6171
  method,
6172
+ redirect: "error",
6172
6173
  headers,
6173
6174
  ...body === void 0 ? {} : { body }
6174
6175
  });
6175
6176
  } catch (error) {
6176
6177
  throw new PlannerClientError({
6177
- message: error instanceof Error ? error.message : "Planner request failed.",
6178
- cause: error
6178
+ message: "Unable to reach the planner API. Check the connection and selected API URL."
6179
6179
  });
6180
6180
  }
6181
6181
  }
@@ -6193,18 +6193,33 @@ function createPlannerClient(options = {}) {
6193
6193
  const parsedError = PlannerApiErrorSchema.safeParse(payload);
6194
6194
  if (parsedError.success) {
6195
6195
  throw new PlannerClientError({
6196
- message: parsedError.data.error.message,
6196
+ message: {
6197
+ bad_request: "Invalid request. Check the command input schema.",
6198
+ unauthorized: "Authentication required. Sign in again.",
6199
+ forbidden: "This operation is not permitted for this account or access mode.",
6200
+ not_found: "Requested resource was not found.",
6201
+ conflict: "The resource changed or conflicts with this request. Fetch the latest version and retry.",
6202
+ unprocessable: "The request could not be processed. Check its inputs and validation result.",
6203
+ too_many_requests: "Too many requests. Try again later.",
6204
+ service_unavailable: "The planner service is temporarily unavailable.",
6205
+ internal: "The planner service encountered an error."
6206
+ }[parsedError.data.error.code],
6197
6207
  statusCode: response.status,
6198
6208
  code: parsedError.data.error.code,
6199
- details: parsedError.data.error.details
6209
+ // Never forward server diagnostic text or echoed sensitive input.
6210
+ details: []
6200
6211
  });
6201
6212
  }
6202
6213
  throw new PlannerClientError({
6203
- message: typeof payload === "string" && payload.length > 0 ? payload : `Planner request failed with HTTP ${response.status}.`,
6214
+ message: `Planner request failed with HTTP ${response.status}.`,
6204
6215
  statusCode: response.status
6205
6216
  });
6206
6217
  }
6207
- return input.outputSchema.parse(payload);
6218
+ const parsedOutput = input.outputSchema.safeParse(payload);
6219
+ if (!parsedOutput.success) {
6220
+ throw new PlannerClientError({ message: "The planner API returned an unexpected response.", statusCode: response.status });
6221
+ }
6222
+ return parsedOutput.data;
6208
6223
  }
6209
6224
  return {
6210
6225
  baseUrl,
@@ -6658,6 +6673,16 @@ function createPlannerClient(options = {}) {
6658
6673
  outputSchema: ListMySavedWorkoutsOutputSchema
6659
6674
  });
6660
6675
  },
6676
+ async createMyWorkoutShare(input) {
6677
+ const parsed = CreateWorkoutShareInputSchema.parse(input);
6678
+ return request({ method: "POST", path: "/me/workout-shares", body: parsed, outputSchema: CreateWorkoutShareOutputSchema });
6679
+ },
6680
+ async getMyWorkoutSharePreview(shareId) {
6681
+ return request({ method: "GET", path: `/me/workout-shares/${encodeURIComponent(shareId)}`, outputSchema: ImportWorkoutShareOutputSchema.omit({ alreadyImported: true }) });
6682
+ },
6683
+ async importMyWorkoutShare(shareId) {
6684
+ return request({ method: "POST", path: `/me/workout-shares/${encodeURIComponent(shareId)}/import`, outputSchema: ImportWorkoutShareOutputSchema });
6685
+ },
6661
6686
  async uploadMyCompletedRide(input) {
6662
6687
  const { rideId, ...body } = UploadCompletedRideInputSchema.parse(input);
6663
6688
  return request({
@@ -6850,10 +6875,273 @@ function createPlannerClient(options = {}) {
6850
6875
  };
6851
6876
  }
6852
6877
 
6878
+ // ../planner-client/src/research.ts
6879
+ function scoreResearchDocument(document, query) {
6880
+ const tokens = query.toLowerCase().split(/[^a-z0-9]+/i).map((token) => token.trim()).filter((token) => token.length > 0);
6881
+ if (tokens.length === 0) {
6882
+ return 1;
6883
+ }
6884
+ const haystack = [
6885
+ document.title,
6886
+ document.url,
6887
+ document.text,
6888
+ JSON.stringify(document.metadata ?? {})
6889
+ ].join("\n").toLowerCase();
6890
+ return tokens.reduce((score, token) => score + (haystack.includes(token) ? 1 : 0), 0);
6891
+ }
6892
+ async function buildResearchDocuments(client) {
6893
+ const documents = [];
6894
+ const [profileResult, sessionsResult, workoutsResult] = await Promise.allSettled([
6895
+ client.getMyProfile(),
6896
+ client.getMyRecentSessions({ limit: 10 }),
6897
+ client.listMySavedWorkouts()
6898
+ ]);
6899
+ if (profileResult.status === "fulfilled") {
6900
+ documents.push({
6901
+ id: `profile:${profileResult.value.profile.athleteId}`,
6902
+ title: `${profileResult.value.profile.athleteId} athlete profile`,
6903
+ url: `${client.baseUrl}/me/profile`,
6904
+ text: JSON.stringify(profileResult.value.profile, null, 2),
6905
+ metadata: {
6906
+ kind: "athlete_profile",
6907
+ athleteId: profileResult.value.profile.athleteId
6908
+ }
6909
+ });
6910
+ }
6911
+ if (sessionsResult.status === "fulfilled") {
6912
+ for (const session of sessionsResult.value.sessions) {
6913
+ documents.push({
6914
+ id: `session:${session.id}`,
6915
+ title: `Workout session ${session.id}`,
6916
+ url: `${client.baseUrl}/me/runtime/sessions/${encodeURIComponent(session.id)}/summary`,
6917
+ text: JSON.stringify(session, null, 2),
6918
+ metadata: {
6919
+ kind: "workout_session",
6920
+ athleteId: session.athleteId,
6921
+ sessionId: session.id,
6922
+ workoutId: session.workoutId,
6923
+ status: session.status
6924
+ }
6925
+ });
6926
+ }
6927
+ }
6928
+ if (workoutsResult.status === "fulfilled") {
6929
+ for (const record of workoutsResult.value.workouts) {
6930
+ documents.push({
6931
+ id: `workout:${record.workout.id}:v${record.workout.version}`,
6932
+ title: record.workout.title ?? record.runtimePlan.title,
6933
+ url: `${client.baseUrl}/me/workouts/${encodeURIComponent(record.workout.id)}?workoutVersion=${record.workout.version}`,
6934
+ text: JSON.stringify(record, null, 2),
6935
+ metadata: {
6936
+ kind: "saved_workout",
6937
+ athleteId: record.workout.athleteId,
6938
+ workoutId: record.workout.id,
6939
+ workoutVersion: record.workout.version
6940
+ }
6941
+ });
6942
+ }
6943
+ }
6944
+ return documents;
6945
+ }
6946
+ async function searchPlannerResearch(client, query) {
6947
+ const documents = await buildResearchDocuments(client);
6948
+ return { results: documents.map((document) => ({ document, score: scoreResearchDocument(document, query) })).filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score || a.document.title.localeCompare(b.document.title)).slice(0, 8).map(({ document, score }) => ({ id: document.id, title: document.title, url: document.url, score })) };
6949
+ }
6950
+ async function fetchPlannerResearch(client, id) {
6951
+ const document = (await buildResearchDocuments(client)).find((candidate) => candidate.id === id);
6952
+ if (!document) throw new Error(`Unknown research document ${id}.`);
6953
+ return document;
6954
+ }
6955
+
6956
+ // src/parity.ts
6957
+ var shareSchema = external_exports.object({ shareId: external_exports.string().uuid() });
6958
+ var emptySchema = external_exports.object({});
6959
+ var toolRoutes = [
6960
+ { tool: "list_saved_workouts", command: "workout list", internal: "listSavedWorkouts", public: "listMySavedWorkouts", schema: ListSavedWorkoutsInputSchema, publicSchema: ListMySavedWorkoutsInputSchema, extended: false, signedInOnly: false, limited: false },
6961
+ { tool: "health", command: "health", internal: "health", public: "health", schema: emptySchema, publicSchema: emptySchema, extended: false, signedInOnly: false, limited: false },
6962
+ { tool: "get_athlete_profile", command: "athlete profile", internal: "getAthleteProfile", public: "getMyProfile", schema: GetAthleteProfileInputSchema, publicSchema: emptySchema, extended: false, signedInOnly: false, limited: false },
6963
+ { tool: "get_readiness_snapshot", command: "athlete readiness latest", internal: "getReadinessSnapshot", public: "getMyReadinessSnapshot", schema: GetReadinessSnapshotInputSchema, publicSchema: GetMyReadinessSnapshotInputSchema, extended: false, signedInOnly: false, limited: false },
6964
+ { tool: "get_recent_sessions", command: "athlete sessions", internal: "getRecentSessions", public: "getMyRecentSessions", schema: GetRecentSessionsInputSchema, publicSchema: GetMyRecentSessionsInputSchema, extended: false, signedInOnly: false, limited: false },
6965
+ { tool: "get_saved_workout", command: "workout get", internal: "getSavedWorkout", public: "getMySavedWorkout", schema: GetSavedWorkoutInputSchema, publicSchema: GetSavedWorkoutInputSchema, extended: false, signedInOnly: false, limited: false },
6966
+ { tool: "get_workout_runtime_plan", command: "workout runtime-plan", internal: "getWorkoutRuntimePlan", public: "getMyWorkoutRuntimePlan", schema: GetWorkoutRuntimePlanInputSchema, publicSchema: GetWorkoutRuntimePlanInputSchema, extended: false, signedInOnly: false, limited: false },
6967
+ { tool: "list_saved_workout_revisions", command: "workout revisions", internal: "listSavedWorkoutRevisions", public: "listMySavedWorkoutRevisions", schema: ListSavedWorkoutRevisionsInputSchema, publicSchema: ListSavedWorkoutRevisionsInputSchema, extended: false, signedInOnly: false, limited: false },
6968
+ { tool: "summarize_session", command: "session summary", internal: "summarizeSession", public: "summarizeMySession", schema: SummarizeSessionInputSchema, publicSchema: SummarizeSessionInputSchema, extended: false, signedInOnly: false, limited: false },
6969
+ { tool: "get_session_telemetry", command: "session telemetry", internal: "getSessionTelemetry", public: "getMySessionTelemetry", schema: GetSessionTelemetryInputSchema, publicSchema: GetSessionTelemetryInputSchema, extended: false, signedInOnly: false, limited: false },
6970
+ { tool: "get_session_artifacts", command: "session artifacts", internal: "getSessionArtifacts", public: "getMySessionArtifacts", schema: GetSessionArtifactsInputSchema, publicSchema: GetSessionArtifactsInputSchema, extended: false, signedInOnly: false, limited: false },
6971
+ { tool: "get_session_fit_export", command: "session fit", internal: "getSessionFitExport", public: "getMySessionFitExport", schema: GetSessionFitExportInputSchema, publicSchema: GetSessionFitExportInputSchema, extended: false, signedInOnly: false, limited: false },
6972
+ { tool: "lookup_climb", command: "course lookup", internal: "lookupClimb", public: "lookupMyClimb", schema: LookupClimbInputSchema, publicSchema: LookupMyClimbInputSchema, extended: true, signedInOnly: false, limited: false, positional: "query" },
6973
+ { tool: "find_my_strava_segments", command: "strava find-segments", internal: "findMyStravaSegments", public: "findMyStravaSegments", schema: FindStravaSegmentsInputSchema, publicSchema: FindStravaSegmentsInputSchema, extended: true, signedInOnly: true, limited: false, positional: "query" },
6974
+ { tool: "import_strava_segment", command: "course import-strava-segment", internal: "importMyStravaSegment", public: "importMyStravaSegment", schema: ImportStravaSegmentInputSchema, publicSchema: ImportStravaSegmentInputSchema, extended: true, signedInOnly: true, limited: false, positional: "segment" },
6975
+ { tool: "import_gpx_course", command: "course import-gpx", internal: "importGpxCourse", public: "importMyGpxCourse", schema: ImportGpxCourseInputSchema, publicSchema: ImportMyGpxCourseInputSchema, extended: true, signedInOnly: false, limited: false },
6976
+ { tool: "generate_terrain_course", command: "course generate-terrain", internal: "generateTerrainCourse", public: "generateMyTerrainCourse", schema: GenerateTerrainCourseInputSchema, publicSchema: GenerateMyTerrainCourseInputSchema, extended: true, signedInOnly: false, limited: false },
6977
+ { tool: "import_strava_activity_course", command: "course import-strava-activity", internal: "importMyStravaActivityCourse", public: "importMyStravaActivityCourse", schema: ImportStravaActivityCourseInputSchema, publicSchema: ImportStravaActivityCourseInputSchema, extended: true, signedInOnly: true, limited: true, positional: "activity" },
6978
+ { tool: "build_road_course", command: "course build-road", internal: "buildRoadCourse", public: "buildMyRoadCourse", schema: BuildRoadCourseInputSchema, publicSchema: BuildMyRoadCourseInputSchema, extended: true, signedInOnly: false, limited: false },
6979
+ { tool: "import_climb_page", command: "course import-climb-page", internal: "importClimbPage", public: "importMyClimbPage", schema: ImportClimbPageInputSchema, publicSchema: ImportMyClimbPageInputSchema, extended: true, signedInOnly: false, limited: false, positional: "url" },
6980
+ { tool: "get_my_climb_history", command: "course history", internal: "getClimbHistory", public: "getMyClimbHistory", schema: GetClimbHistoryInputSchema, publicSchema: GetMyClimbHistoryInputSchema, extended: true, signedInOnly: false, limited: false },
6981
+ { tool: "explore_strava_segments", command: "strava explore", internal: "exploreMyStravaSegments", public: "exploreMyStravaSegments", schema: ExploreStravaSegmentsInputSchema, publicSchema: ExploreStravaSegmentsInputSchema, extended: true, signedInOnly: true, limited: true },
6982
+ { tool: "get_my_strava_segment_efforts", command: "strava segment-efforts", internal: "getMyStravaSegmentEfforts", public: "getMyStravaSegmentEfforts", schema: ListStravaSegmentEffortsInputSchema, publicSchema: ListStravaSegmentEffortsInputSchema, extended: true, signedInOnly: true, limited: true, positional: "segment" },
6983
+ { tool: "list_my_strava_starred_segments", command: "strava starred-segments", internal: "listMyStravaStarredSegments", public: "listMyStravaStarredSegments", schema: ListStravaStarredSegmentsInputSchema, publicSchema: ListStravaStarredSegmentsInputSchema, extended: true, signedInOnly: true, limited: true },
6984
+ { tool: "list_my_favourite_strava_climbs", command: "strava favourite-climbs", internal: "listMyFavouriteStravaClimbs", public: "listMyFavouriteStravaClimbs", schema: ListFavouriteStravaClimbsInputSchema, publicSchema: ListFavouriteStravaClimbsInputSchema, extended: true, signedInOnly: true, limited: true },
6985
+ { tool: "recommend_first_workout", command: "recommend first", internal: "recommendMyOnboardingWorkout", public: "recommendMyOnboardingWorkout", schema: RecommendOnboardingWorkoutInputSchema, publicSchema: RecommendOnboardingWorkoutInputSchema, extended: true, signedInOnly: true, limited: false },
6986
+ { tool: "list_my_strava_routes", command: "strava routes", internal: "listMyStravaRoutes", public: "listMyStravaRoutes", schema: ListStravaRoutesInputSchema, publicSchema: ListStravaRoutesInputSchema, extended: true, signedInOnly: true, limited: true },
6987
+ { tool: "import_strava_route", command: "course import-strava-route", internal: "importMyStravaRoute", public: "importMyStravaRoute", schema: ImportStravaRouteInputSchema, publicSchema: ImportStravaRouteInputSchema, extended: true, signedInOnly: true, limited: true, positional: "route" },
6988
+ { tool: "draft_workout", command: "workout draft", internal: "draftWorkout", public: "draftMyWorkout", schema: DraftWorkoutInputSchema, publicSchema: DraftMyWorkoutInputSchema, extended: false, signedInOnly: false, limited: false },
6989
+ { tool: "revise_workout", command: "workout revise", internal: "reviseWorkout", public: "reviseMyWorkout", schema: ReviseWorkoutInputSchema, publicSchema: ReviseWorkoutInputSchema, extended: false, signedInOnly: false, limited: false },
6990
+ { tool: "validate_workout", command: "workout validate", internal: "validateWorkout", public: "validateWorkout", schema: ValidateWorkoutInputSchema, publicSchema: ValidateWorkoutInputSchema, extended: false, signedInOnly: false, limited: false },
6991
+ { tool: "save_workout", command: "workout save", internal: "saveWorkout", public: "saveMyWorkout", schema: SaveWorkoutInputSchema, publicSchema: SaveWorkoutInputSchema, extended: false, signedInOnly: false, limited: false },
6992
+ { tool: "schedule_saved_workout", command: "workout schedule", internal: "scheduleSavedWorkout", public: "scheduleMySavedWorkout", schema: ScheduleSavedWorkoutInputSchema, publicSchema: ScheduleSavedWorkoutInputSchema, extended: false, signedInOnly: false, limited: false },
6993
+ { tool: "retire_saved_workout", command: "workout retire", internal: "retireSavedWorkout", public: "retireMySavedWorkout", schema: RetireSavedWorkoutInputSchema, publicSchema: RetireSavedWorkoutInputSchema, extended: false, signedInOnly: false, limited: false },
6994
+ { tool: "trigger_session_strava_sync", command: "session strava-sync", internal: "triggerSessionStravaSync", public: "triggerMySessionStravaSync", schema: TriggerSessionStravaSyncInputSchema, publicSchema: TriggerSessionStravaSyncInputSchema, extended: false, signedInOnly: false, limited: false },
6995
+ { tool: "create_workout_share", command: "share create", internal: "createMyWorkoutShare", public: "createMyWorkoutShare", schema: CreateWorkoutShareInputSchema, publicSchema: CreateWorkoutShareInputSchema, extended: true, signedInOnly: true, limited: false, positional: "workoutId" },
6996
+ { tool: "preview_workout_share", command: "share preview", internal: "getMyWorkoutSharePreview", public: "getMyWorkoutSharePreview", schema: shareSchema, publicSchema: shareSchema, extended: true, signedInOnly: true, limited: false, positional: "shareId", scalar: "shareId" },
6997
+ { tool: "import_workout_share", command: "share import", internal: "importMyWorkoutShare", public: "importMyWorkoutShare", schema: shareSchema, publicSchema: shareSchema, extended: true, signedInOnly: true, limited: false, positional: "shareId", scalar: "shareId" }
6998
+ ];
6999
+
7000
+ // src/tool-command.ts
7001
+ var researchRoutes = [
7002
+ { tool: "search", command: "research search", schema: external_exports.object({ query: external_exports.string().min(1) }) },
7003
+ { tool: "fetch", command: "research fetch", schema: external_exports.object({ id: external_exports.string().min(1) }) }
7004
+ ];
7005
+ function unwrap(schema) {
7006
+ if (schema instanceof external_exports.ZodEffects) return unwrap(schema.innerType());
7007
+ if (schema instanceof external_exports.ZodOptional || schema instanceof external_exports.ZodNullable) return unwrap(schema.unwrap());
7008
+ if (schema instanceof external_exports.ZodDefault) return unwrap(schema.removeDefault());
7009
+ return schema;
7010
+ }
7011
+ var kebab = (s) => s.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
7012
+ function describeSchema(schema) {
7013
+ const meta = schema.description ? { description: schema.description } : {};
7014
+ if (schema instanceof external_exports.ZodEffects) return { ...describeSchema(schema.innerType()), ...meta };
7015
+ if (schema instanceof external_exports.ZodOptional) return { ...describeSchema(schema.unwrap()), ...meta };
7016
+ if (schema instanceof external_exports.ZodNullable) return { ...meta, anyOf: [describeSchema(schema.unwrap()), { type: "null" }] };
7017
+ if (schema instanceof external_exports.ZodDefault) return { ...describeSchema(schema.removeDefault()), ...meta, default: schema._def.defaultValue() };
7018
+ if (schema instanceof external_exports.ZodObject) return { ...meta, type: "object", properties: Object.fromEntries(Object.entries(schema.shape).map(([key, value]) => [key, describeSchema(value)])), required: Object.entries(schema.shape).filter(([, value]) => !value.isOptional()).map(([key]) => key), additionalProperties: schema._def.unknownKeys === "passthrough" };
7019
+ if (schema instanceof external_exports.ZodArray) return { ...meta, type: "array", items: describeSchema(schema.element), ...schema._def.minLength ? { minItems: schema._def.minLength.value } : {}, ...schema._def.maxLength ? { maxItems: schema._def.maxLength.value } : {}, ...schema._def.exactLength ? { minItems: schema._def.exactLength.value, maxItems: schema._def.exactLength.value } : {} };
7020
+ if (schema instanceof external_exports.ZodEnum) return { ...meta, type: "string", enum: schema.options };
7021
+ if (schema instanceof external_exports.ZodUnion || schema instanceof external_exports.ZodDiscriminatedUnion) return { ...meta, anyOf: schema.options.map(describeSchema) };
7022
+ if (schema instanceof external_exports.ZodLiteral) return { ...meta, const: schema.value };
7023
+ if (schema instanceof external_exports.ZodRecord) return { ...meta, type: "object", additionalProperties: describeSchema(schema.valueSchema) };
7024
+ if (schema instanceof external_exports.ZodNull) return { ...meta, type: "null" };
7025
+ if (schema instanceof external_exports.ZodBoolean) return { ...meta, type: "boolean" };
7026
+ if (schema instanceof external_exports.ZodUnknown || schema instanceof external_exports.ZodAny) return meta;
7027
+ if (schema instanceof external_exports.ZodLazy) return describeSchema(schema.schema);
7028
+ const result = { ...meta, type: schema instanceof external_exports.ZodNumber ? "number" : "string" };
7029
+ for (const check of schema._def.checks ?? []) {
7030
+ if (check.kind === "int") result.type = "integer";
7031
+ if (check.kind === "min") result[schema instanceof external_exports.ZodString ? "minLength" : check.inclusive ? "minimum" : "exclusiveMinimum"] = check.value;
7032
+ if (check.kind === "max") result[schema instanceof external_exports.ZodString ? "maxLength" : check.inclusive ? "maximum" : "exclusiveMaximum"] = check.value;
7033
+ if (check.kind === "length") {
7034
+ result.minLength = check.value;
7035
+ result.maxLength = check.value;
7036
+ }
7037
+ if (check.kind === "multipleOf") result.multipleOf = check.value;
7038
+ if (check.kind === "regex") result.pattern = check.regex.source;
7039
+ if (["uuid", "email", "url", "datetime"].includes(check.kind)) result.format = { url: "uri", datetime: "date-time" }[check.kind] ?? check.kind;
7040
+ }
7041
+ return result;
7042
+ }
7043
+ function decode(raw, schema) {
7044
+ const inner = unwrap(schema);
7045
+ if (raw === "null" && schema.isNullable()) return null;
7046
+ if (inner instanceof external_exports.ZodNumber) {
7047
+ if (raw.trim() === "" || !Number.isFinite(Number(raw))) throw new Error(`Invalid number: ${raw}`);
7048
+ return Number(raw);
7049
+ }
7050
+ if (inner instanceof external_exports.ZodBoolean) {
7051
+ if (!["true", "false"].includes(raw)) throw new Error("Boolean values must be true or false.");
7052
+ return raw === "true";
7053
+ }
7054
+ if (inner instanceof external_exports.ZodObject || inner instanceof external_exports.ZodArray || inner instanceof external_exports.ZodUnion) return JSON.parse(raw);
7055
+ return raw;
7056
+ }
7057
+ function field(schema, name) {
7058
+ const inner = unwrap(schema);
7059
+ if (!(inner instanceof external_exports.ZodObject)) throw new Error(`Not an object: ${name}`);
7060
+ const key = Object.keys(inner.shape).find((key2) => kebab(key2) === name || key2 === name);
7061
+ if (!key) throw new Error(`Unknown option: --${name}`);
7062
+ return [key, inner.shape[key]];
7063
+ }
7064
+ async function parseToolArgs(args, schema, positional) {
7065
+ let input = {};
7066
+ const jsonIndex = args.indexOf("--input-json"), fileIndex = args.indexOf("--input-file");
7067
+ if (jsonIndex >= 0 && fileIndex >= 0) throw new Error("Use only one of --input-json or --input-file.");
7068
+ const index = Math.max(jsonIndex, fileIndex);
7069
+ if (index >= 0) {
7070
+ const value = args[index + 1];
7071
+ if (!value || value.startsWith("--")) throw new Error("Missing input value.");
7072
+ input = JSON.parse(fileIndex >= 0 ? await readFile(value, "utf8") : value);
7073
+ args.splice(index, 2);
7074
+ if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error("Input must be a JSON object.");
7075
+ if (args.length) throw new Error("Do not combine JSON input with other arguments.");
7076
+ return input;
7077
+ }
7078
+ if (positional && args[0] && !args[0].startsWith("--")) input[positional] = args.shift();
7079
+ while (args.length) {
7080
+ const option = args.shift();
7081
+ if (!option.startsWith("--")) throw new Error(`Unexpected argument: ${option}`);
7082
+ const raw = args.shift();
7083
+ if (raw === void 0 || raw.startsWith("--")) throw new Error(`Missing value for ${option}.`);
7084
+ if (option === "--gpx-file") {
7085
+ field(schema, "gpx");
7086
+ if ("gpx" in input) throw new Error("Duplicate GPX input.");
7087
+ input.gpx = await readFile(raw, "utf8");
7088
+ continue;
7089
+ }
7090
+ let current = input, currentSchema = schema;
7091
+ const parts = option.slice(2).split(".");
7092
+ for (const [i, part] of parts.entries()) {
7093
+ const [key, valueSchema] = field(currentSchema, part);
7094
+ if (i < parts.length - 1) {
7095
+ current[key] ??= {};
7096
+ current = current[key];
7097
+ currentSchema = valueSchema;
7098
+ } else {
7099
+ const inner = unwrap(valueSchema);
7100
+ if (inner instanceof external_exports.ZodArray && !raw.startsWith("[")) {
7101
+ current[key] = [...current[key] ?? [], decode(raw, inner.element)];
7102
+ } else {
7103
+ if (key in current) throw new Error(`Duplicate option: ${option}`);
7104
+ current[key] = decode(raw, valueSchema);
7105
+ }
7106
+ }
7107
+ }
7108
+ }
7109
+ return input;
7110
+ }
7111
+ async function runToolCommand(args, client, signedIn, limited) {
7112
+ const generic = args[0] === "tool";
7113
+ if (generic && args[1] === "list" && args.length !== 2) throw new Error("tool list does not accept arguments.");
7114
+ if (generic && args[1] === "schema" && args.length !== 3) throw new Error("Use tool schema <MCP-name>.");
7115
+ if (generic && args[1] === "list") return { result: [...toolRoutes.filter((r) => !limited || !r.limited).map(({ tool, command }) => ({ tool, command })), ...researchRoutes.map(({ tool, command }) => ({ tool, command }))] };
7116
+ const schemaOnly = generic && args[1] === "schema";
7117
+ const toolName = generic ? args[schemaOnly ? 2 : 1] : void 0;
7118
+ const route = toolRoutes.find((r) => generic ? r.tool === toolName : r.extended && r.command.split(" ").every((s, i) => args[i] === s));
7119
+ const research = researchRoutes.find((r) => generic ? r.tool === toolName : r.command.split(" ").every((s, i) => args[i] === s));
7120
+ if (!route && !research) {
7121
+ if (generic) throw new Error(`Unknown tool: ${toolName}`);
7122
+ return void 0;
7123
+ }
7124
+ if (route?.limited && limited) throw new Error("This command is unavailable in Strava limited-access mode.");
7125
+ const schema = research?.schema ?? (signedIn ? route.publicSchema : route.schema);
7126
+ if (schemaOnly) return { result: { tool: toolName, command: (route ?? research).command, inputSchema: describeSchema(schema), note: "Contract refinements are validated on invocation. Flags use kebab-case; nested fields use dots; arrays accept JSON or repeated values." } };
7127
+ if ((research || route?.signedInOnly) && !signedIn) throw new Error("Sign in with `wattson auth login` to use this command.");
7128
+ args.splice(0, generic ? 2 : (route ?? research).command.split(" ").length);
7129
+ const input = await parseToolArgs(args, schema, generic ? void 0 : research ? research.tool === "search" ? "query" : "id" : route.positional);
7130
+ if (signedIn && "athleteId" in input && !unwrap(schema).shape.athleteId) throw new Error("Authenticated commands target the signed-in athlete; athleteId is not accepted.");
7131
+ const parsed = schema.parse(input);
7132
+ if (research) return { result: research.tool === "search" ? await searchPlannerResearch(client, parsed.query) : await fetchPlannerResearch(client, parsed.id) };
7133
+ const selected = route;
7134
+ const method = client[signedIn ? selected.public : selected.internal];
7135
+ return { result: await method(selected.scalar ? parsed[selected.scalar] : parsed) };
7136
+ }
7137
+
7138
+ // src/cli.ts
7139
+ import { readFile as readFile3 } from "node:fs/promises";
7140
+
6853
7141
  // src/auth.ts
6854
7142
  import { spawn } from "node:child_process";
6855
7143
  import { createHash, randomBytes } from "node:crypto";
6856
- import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
7144
+ import { chmod, mkdir, readFile as readFile2, rename, rm, writeFile } from "node:fs/promises";
6857
7145
  import { createServer } from "node:http";
6858
7146
  import os from "node:os";
6859
7147
  import path from "node:path";
@@ -6967,7 +7255,7 @@ function parseJsonSafely2(raw) {
6967
7255
  }
6968
7256
  async function readStoredAuthState(authFilePath) {
6969
7257
  try {
6970
- const raw = await readFile(authFilePath, "utf8");
7258
+ const raw = await readFile2(authFilePath, "utf8");
6971
7259
  const trimmed = raw.trim();
6972
7260
  if (trimmed.length === 0) {
6973
7261
  return void 0;
@@ -6977,7 +7265,7 @@ async function readStoredAuthState(authFilePath) {
6977
7265
  if (error.code === "ENOENT") {
6978
7266
  return void 0;
6979
7267
  }
6980
- throw error;
7268
+ throw new Error("Unable to read saved login. Repair or remove the credential file, then sign in again.");
6981
7269
  }
6982
7270
  }
6983
7271
  async function writeStoredAuthState(authFilePath, state) {
@@ -6995,6 +7283,7 @@ async function writeStoredAuthState(authFilePath, state) {
6995
7283
  async function postTokenRequest(baseUrl, payload, fetchImpl) {
6996
7284
  const response = await fetchImpl(`${normalizeBaseUrl2(baseUrl)}/token`, {
6997
7285
  method: "POST",
7286
+ redirect: "error",
6998
7287
  headers: {
6999
7288
  "content-type": "application/x-www-form-urlencoded"
7000
7289
  },
@@ -7006,7 +7295,7 @@ async function postTokenRequest(baseUrl, payload, fetchImpl) {
7006
7295
  throw new Error(`OAuth token endpoint returned an unexpected response (HTTP ${response.status}).`);
7007
7296
  }
7008
7297
  if (!response.ok || parsed.data.error) {
7009
- throw new Error(parsed.data.error_description ?? parsed.data.error ?? `OAuth token request failed with HTTP ${response.status}.`);
7298
+ throw new Error(`OAuth token request failed with HTTP ${response.status}.`);
7010
7299
  }
7011
7300
  return parsed.data;
7012
7301
  }
@@ -7264,6 +7553,7 @@ async function loginWithDeviceFlow(options) {
7264
7553
  if (remaining <= 0) throw new Error("Device login timed out. Run login again.");
7265
7554
  return await fetchImpl(url, {
7266
7555
  ...init,
7556
+ redirect: "error",
7267
7557
  signal: AbortSignal.timeout(Math.max(1, Math.ceil(Math.min(3e4, remaining))))
7268
7558
  });
7269
7559
  };
@@ -7358,34 +7648,41 @@ function usage() {
7358
7648
  " auth status",
7359
7649
  " auth logout",
7360
7650
  "",
7361
- "Inspection/runtime commands:",
7651
+ "Inspection commands:",
7362
7652
  " health",
7363
7653
  " athlete profile [athleteId]",
7364
7654
  " athlete readiness latest [athleteId] [--as-of <isoDateTime>]",
7365
7655
  " athlete sessions [athleteId] [--from <date>] [--to <date>] [--limit <n>]",
7366
- " plan current [athleteId]",
7367
- " runtime-plan today [athleteId] [--date <date>]",
7656
+ " workout list [athleteId] [--limit <n>]",
7368
7657
  " workout get <workoutId> [--version <n>]",
7369
7658
  " workout revisions <workoutId>",
7370
7659
  " workout runtime-plan <workoutId> --version <n>",
7371
- " session load-workout [--athlete-id <athleteId>] --workout-id <id> --version <n>",
7372
- " session complete <sessionId> [--status <status>] [--elapsed-sec <n>] [--completion-kind <kind>] [--started-at <iso>] [--ended-at <iso>] [--execution-mode <mode>] [--avg-power <n>] [--avg-hr-bpm <n>] [--max-hr-bpm <n>] [--compliance-pct <n>] [--completed-duration-sec <n>] [--completed-tss <n>] [--note <text> ...]",
7373
- " session complete <sessionId> --input-json <json> | --input-file <path>",
7374
7660
  " session summary <sessionId>",
7375
7661
  " session telemetry <sessionId>",
7376
7662
  " session artifacts <sessionId>",
7377
7663
  " session fit <sessionId>",
7378
7664
  " session strava-sync <sessionId>",
7665
+ " strava routes [--limit <n>]",
7666
+ " strava starred-segments [--limit <n>]",
7667
+ " course import-strava-route --input-json <json> | --input-file <path>",
7668
+ " course import-strava-segment --input-json <json> | --input-file <path>",
7669
+ "",
7670
+ "Extended MCP commands (all accept --input-json or --input-file):",
7671
+ ...toolRoutes.filter((route) => route.extended).map((route) => ` ${route.command}${route.positional ? ` <${route.positional}>` : ""}`),
7672
+ " research search <query> | research fetch <documentId>",
7673
+ " tool list | tool schema <MCP-name> | tool <MCP-name> [--input-json <json>]",
7674
+ " Use tool schema for fields; flags use --kebab-case, nested --field.child, repeated arrays.",
7379
7675
  "",
7380
- "Authoring/adaptation commands:",
7676
+ "Authoring/library commands:",
7381
7677
  " recommend daily [athleteId] --available-time-min <n> [--date <date>] [--prefer-objective <objective>] [--hard-constraint <text> ...]",
7382
7678
  " workout draft [athleteId] --objective <objective> --duration-min <n> [--execution-mode <mode> ...] [--max-pct-ftp <n>] [--style-hint <text> ...] [--readiness-snapshot-id <id>]",
7383
7679
  " workout draft --input-json <json> | --input-file <path>",
7384
7680
  " workout revise --input-json <json> | --input-file <path>",
7385
7681
  " workout validate --input-json <json> | --input-file <path>",
7386
7682
  " workout save --input-json <json> | --input-file <path>",
7387
- " plan schedule [athleteId] --workout-id <id> --version <n> --date <date>",
7388
- " session adapt <sessionId> --op <op> --reason <text> [--payload-json <json> | --payload-file <path>]"
7683
+ " workout schedule <workoutId> --date <YYYY-MM-DD>",
7684
+ " workout unschedule <workoutId>",
7685
+ " workout retire <workoutId>"
7389
7686
  ].join("\n");
7390
7687
  }
7391
7688
  function shiftRequired(args, label) {
@@ -7463,8 +7760,8 @@ function parseIntegerOption(raw, label) {
7463
7760
  if (raw === void 0) {
7464
7761
  return void 0;
7465
7762
  }
7466
- const parsed = Number.parseInt(raw, 10);
7467
- if (!Number.isFinite(parsed)) {
7763
+ const parsed = Number(raw);
7764
+ if (raw.trim() === "" || !Number.isInteger(parsed)) {
7468
7765
  throw new Error(`Invalid integer for ${label}: ${raw}.`);
7469
7766
  }
7470
7767
  return parsed;
@@ -7496,7 +7793,7 @@ async function readJsonInput(args, jsonFlag = "--input-json", fileFlag = "--inpu
7496
7793
  return parseJsonValue(inlineJson, jsonFlag);
7497
7794
  }
7498
7795
  if (filePath !== void 0) {
7499
- const raw = await readFile2(filePath, "utf8");
7796
+ const raw = await readFile3(filePath, "utf8");
7500
7797
  return parseJsonValue(raw, fileFlag);
7501
7798
  }
7502
7799
  return void 0;
@@ -7671,24 +7968,6 @@ function summarizeWorkoutRuntimePlan(result) {
7671
7968
  `validation: ${result.validation.status}`
7672
7969
  ].join("\n");
7673
7970
  }
7674
- function summarizeQueuedSession(result) {
7675
- return [
7676
- "session queued",
7677
- `sessionId: ${result.sessionId}`,
7678
- `status: ${result.status}`,
7679
- ...result.runtimePlanId ? [`runtimePlanId: ${result.runtimePlanId}`] : []
7680
- ].join("\n");
7681
- }
7682
- function summarizeCompletedSession(result) {
7683
- return [
7684
- "session completed",
7685
- `sessionId: ${result.session.id}`,
7686
- `status: ${result.session.status}`,
7687
- ...result.session.completionKind ? [`completionKind: ${result.session.completionKind}`] : [],
7688
- ...result.session.actualMetrics?.completedDurationSec === void 0 ? [] : [`completedDurationSec: ${result.session.actualMetrics.completedDurationSec}`],
7689
- ...result.session.actualMetrics?.compliancePct === void 0 ? [] : [`compliancePct: ${result.session.actualMetrics.compliancePct}`]
7690
- ].join("\n");
7691
- }
7692
7971
  function summarizeStravaSync(result) {
7693
7972
  const strava = result.session.externalSync?.strava;
7694
7973
  return [
@@ -7710,15 +7989,6 @@ function summarizeRecommendation(result) {
7710
7989
  ...alternatives.length === 0 ? [] : ["alternatives:", ...alternatives.map((alt) => `- ${alt.objective} (${alt.durationMin} min)`)]
7711
7990
  ].join("\n");
7712
7991
  }
7713
- function summarizeAdaptedSession(result) {
7714
- const adaptations = result.session.adaptations ?? [];
7715
- return [
7716
- "session adapted",
7717
- `sessionId: ${result.session.id}`,
7718
- `status: ${result.session.status}`,
7719
- `adaptations: ${adaptations.length}`
7720
- ].join("\n");
7721
- }
7722
7992
  function summarizeAuthStatus(state) {
7723
7993
  return [
7724
7994
  "auth status",
@@ -7736,6 +8006,14 @@ function formatDefaultResult(commandKey, result) {
7736
8006
  return summarizeRecentSessions(result);
7737
8007
  case "recommend_daily":
7738
8008
  return summarizeRecommendation(result);
8009
+ case "workout_list": {
8010
+ const workouts = result.workouts;
8011
+ return [
8012
+ "saved workouts",
8013
+ `count: ${workouts.length}`,
8014
+ ...workouts.map(({ workout }) => `- ${workout.id} | v${workout.version} | ${workout.title} | ${workout.status} | ${workout.scheduledDate ?? "unscheduled"}`)
8015
+ ].join("\n");
8016
+ }
7739
8017
  case "workout_get":
7740
8018
  return summarizeSavedWorkout(result);
7741
8019
  case "workout_draft":
@@ -7748,10 +8026,6 @@ function formatDefaultResult(commandKey, result) {
7748
8026
  return summarizeWorkoutRevisions(result);
7749
8027
  case "workout_runtime_plan":
7750
8028
  return summarizeWorkoutRuntimePlan(result);
7751
- case "session_load_workout":
7752
- return summarizeQueuedSession(result);
7753
- case "session_complete":
7754
- return summarizeCompletedSession(result);
7755
8029
  case "session_summary":
7756
8030
  return summarizeSessionSummary(result);
7757
8031
  case "session_telemetry":
@@ -7762,8 +8036,6 @@ function formatDefaultResult(commandKey, result) {
7762
8036
  return summarizeFit(result);
7763
8037
  case "session_strava_sync":
7764
8038
  return summarizeStravaSync(result);
7765
- case "session_adapt":
7766
- return summarizeAdaptedSession(result);
7767
8039
  default:
7768
8040
  return JSON.stringify(result, null, 2);
7769
8041
  }
@@ -7888,6 +8160,8 @@ async function runAuthCommand(context) {
7888
8160
  throw new Error(`Unsupported auth subcommand: ${subcommand}.`);
7889
8161
  }
7890
8162
  async function runCommand(context) {
8163
+ const extended = await runToolCommand(context.args, context.client, context.authState !== void 0, context.stravaLimitedAccess ?? false);
8164
+ if (extended) return writeResult(context, "tool", extended.result);
7891
8165
  const top = shiftRequired(context.args, "command");
7892
8166
  if (top === "health") {
7893
8167
  return writeResult(context, "health", await context.client.health());
@@ -7957,52 +8231,28 @@ async function runCommand(context) {
7957
8231
  context: contextPayload
7958
8232
  }));
7959
8233
  }
7960
- if (top === "plan") {
7961
- const subcommand = shiftRequired(context.args, "plan subcommand");
7962
- if (subcommand === "current") {
7963
- const target = resolveAthleteTarget(context);
7964
- return target.useMyRoutes ? writeResult(context, "plan_current", await context.client.getMyCurrentPlan()) : writeResult(context, "plan_current", await context.client.getCurrentPlan({ athleteId: target.athleteId }));
8234
+ if (top === "strava") {
8235
+ const subcommand = shiftRequired(context.args, "strava subcommand");
8236
+ requireSignedInAuth(context);
8237
+ if (subcommand === "routes") {
8238
+ const limit = parseIntegerOption(takeOption(context.args, "--limit"), "--limit");
8239
+ return writeResult(context, "strava_routes", await context.client.listMyStravaRoutes({ ...limit === void 0 ? {} : { limit } }));
7965
8240
  }
7966
- if (subcommand === "schedule") {
7967
- const target = resolveAthleteTarget(context);
7968
- const workoutId = takeOption(context.args, "--workout-id");
7969
- const workoutVersion = parseIntegerOption(takeOption(context.args, "--version"), "--version");
7970
- const date = takeOption(context.args, "--date");
7971
- if (!workoutId) {
7972
- throw new Error("Missing required argument: --workout-id.");
7973
- }
7974
- if (workoutVersion === void 0) {
7975
- throw new Error("Missing required argument: --version.");
7976
- }
7977
- if (!date) {
7978
- throw new Error("Missing required argument: --date.");
7979
- }
7980
- return target.useMyRoutes ? writeResult(context, "plan_schedule", await context.client.scheduleMyWorkout({
7981
- workoutId,
7982
- workoutVersion,
7983
- date
7984
- })) : writeResult(context, "plan_schedule", await context.client.scheduleWorkout({
7985
- athleteId: target.athleteId,
7986
- workoutId,
7987
- workoutVersion,
7988
- date
7989
- }));
8241
+ if (subcommand === "starred-segments") {
8242
+ const limit = parseIntegerOption(takeOption(context.args, "--limit"), "--limit");
8243
+ return writeResult(context, "strava_starred", await context.client.listMyStravaStarredSegments({ ...limit === void 0 ? {} : { limit } }));
7990
8244
  }
7991
- throw new Error(`Unsupported plan subcommand: ${subcommand}.`);
8245
+ throw new Error(`Unsupported strava subcommand: ${subcommand}.`);
7992
8246
  }
7993
- if (top === "runtime-plan") {
7994
- const subcommand = shiftRequired(context.args, "runtime-plan subcommand");
7995
- if (subcommand !== "today") {
7996
- throw new Error(`Unsupported runtime-plan subcommand: ${subcommand}.`);
8247
+ if (top === "course") {
8248
+ const subcommand = shiftRequired(context.args, "course subcommand");
8249
+ requireSignedInAuth(context);
8250
+ if (subcommand === "import-strava-route" || subcommand === "import-strava-segment") {
8251
+ const input = await readJsonInput(context.args);
8252
+ if (input === void 0) throw new Error(`course ${subcommand} requires --input-json or --input-file.`);
8253
+ return subcommand === "import-strava-route" ? writeResult(context, "course_import", await context.client.importMyStravaRoute(input)) : writeResult(context, "course_import", await context.client.importMyStravaSegment(input));
7997
8254
  }
7998
- const target = resolveAthleteTarget(context);
7999
- const date = takeOption(context.args, "--date");
8000
- return target.useMyRoutes ? writeResult(context, "runtime_plan_today", await context.client.getMyTodayRuntimePlan({
8001
- ...date === void 0 ? {} : { date }
8002
- })) : writeResult(context, "runtime_plan_today", await context.client.getTodayRuntimePlan({
8003
- athleteId: target.athleteId,
8004
- ...date === void 0 ? {} : { date }
8005
- }));
8255
+ throw new Error(`Unsupported course subcommand: ${subcommand}.`);
8006
8256
  }
8007
8257
  if (top === "workout") {
8008
8258
  const subcommand = shiftRequired(context.args, "workout subcommand");
@@ -8093,7 +8343,22 @@ async function runCommand(context) {
8093
8343
  structuredInput
8094
8344
  ));
8095
8345
  }
8346
+ if (subcommand === "list") {
8347
+ const limit = parseIntegerOption(takeOption(context.args, "--limit"), "--limit");
8348
+ const target = resolveAthleteTarget(context);
8349
+ return target.useMyRoutes ? writeResult(context, "workout_list", await context.client.listMySavedWorkouts({ ...limit === void 0 ? {} : { limit } })) : writeResult(context, "workout_list", await context.client.listSavedWorkouts({ athleteId: target.athleteId, ...limit === void 0 ? {} : { limit } }));
8350
+ }
8096
8351
  const workoutId = shiftRequired(context.args, "workoutId");
8352
+ if (subcommand === "schedule" || subcommand === "unschedule") {
8353
+ const date = takeOption(context.args, "--date");
8354
+ if (subcommand === "schedule" && !date) throw new Error("Missing required argument: --date.");
8355
+ if (subcommand === "unschedule" && date !== void 0) throw new Error("workout unschedule does not accept --date.");
8356
+ const input = { workoutId, scheduledDate: subcommand === "schedule" ? date : null };
8357
+ return context.authState ? writeResult(context, "workout_schedule", await context.client.scheduleMySavedWorkout(input)) : writeResult(context, "workout_schedule", await context.client.scheduleSavedWorkout(input));
8358
+ }
8359
+ if (subcommand === "retire") {
8360
+ return context.authState ? writeResult(context, "workout_retire", await context.client.retireMySavedWorkout({ workoutId })) : writeResult(context, "workout_retire", await context.client.retireSavedWorkout({ workoutId }));
8361
+ }
8097
8362
  if (subcommand === "get") {
8098
8363
  const workoutVersion = parseIntegerOption(takeOption(context.args, "--version"), "--version");
8099
8364
  return context.authState ? writeResult(context, "workout_get", await context.client.getMySavedWorkout({
@@ -8124,99 +8389,7 @@ async function runCommand(context) {
8124
8389
  }
8125
8390
  if (top === "session") {
8126
8391
  const subcommand = shiftRequired(context.args, "session subcommand");
8127
- if (subcommand === "load-workout") {
8128
- const athleteId = takeOption(context.args, "--athlete-id");
8129
- const workoutId = takeOption(context.args, "--workout-id");
8130
- const workoutVersion = parseIntegerOption(takeOption(context.args, "--version"), "--version");
8131
- if (!workoutId) {
8132
- throw new Error("Missing required argument: --workout-id.");
8133
- }
8134
- if (workoutVersion === void 0) {
8135
- throw new Error("Missing required argument: --version.");
8136
- }
8137
- if (!athleteId && context.authState) {
8138
- return writeResult(context, "session_load_workout", await context.client.sendMyWorkoutToActiveTrainerSession({
8139
- workoutId,
8140
- workoutVersion
8141
- }));
8142
- }
8143
- if (!athleteId) {
8144
- throw new Error("Missing required argument: --athlete-id.");
8145
- }
8146
- return writeResult(context, "session_load_workout", await context.client.sendToActiveTrainerSession({
8147
- athleteId,
8148
- workoutId,
8149
- workoutVersion
8150
- }));
8151
- }
8152
8392
  const sessionId = shiftRequired(context.args, "sessionId");
8153
- if (subcommand === "complete") {
8154
- const structuredInput = await readJsonInput(context.args);
8155
- if (structuredInput !== void 0) {
8156
- return context.authState ? writeResult(context, "session_complete", await context.client.ingestMyCompletedSession({
8157
- sessionId,
8158
- ...structuredInput
8159
- })) : writeResult(context, "session_complete", await context.client.ingestCompletedSession({
8160
- sessionId,
8161
- ...structuredInput
8162
- }));
8163
- }
8164
- const status = takeOption(context.args, "--status");
8165
- const completionKind = takeOption(context.args, "--completion-kind");
8166
- const startedAt = takeOption(context.args, "--started-at");
8167
- const endedAt = takeOption(context.args, "--ended-at");
8168
- const executionMode = takeOption(context.args, "--execution-mode");
8169
- const elapsedSec = parseIntegerOption(takeOption(context.args, "--elapsed-sec"), "--elapsed-sec");
8170
- const avgPower = parseNumberOption(takeOption(context.args, "--avg-power"), "--avg-power");
8171
- const avgHrBpm = parseNumberOption(takeOption(context.args, "--avg-hr-bpm"), "--avg-hr-bpm");
8172
- const maxHrBpm = parseNumberOption(takeOption(context.args, "--max-hr-bpm"), "--max-hr-bpm");
8173
- const compliancePct = parseNumberOption(takeOption(context.args, "--compliance-pct"), "--compliance-pct");
8174
- const completedDurationSec = parseNumberOption(takeOption(context.args, "--completed-duration-sec"), "--completed-duration-sec");
8175
- const completedTss = parseNumberOption(takeOption(context.args, "--completed-tss"), "--completed-tss");
8176
- const notes = takeOptions(context.args, "--note");
8177
- if (!status) {
8178
- throw new Error("session complete requires either --input-json/--input-file or at least --status.");
8179
- }
8180
- const completion = {
8181
- sessionId,
8182
- status,
8183
- ...completionKind === void 0 ? {} : { completionKind },
8184
- ...startedAt === void 0 ? {} : { startedAt },
8185
- ...endedAt === void 0 ? {} : { endedAt },
8186
- ...executionMode === void 0 ? {} : { executionMode },
8187
- ...elapsedSec === void 0 ? {} : { liveSummary: { elapsedSec } },
8188
- ...avgPower === void 0 && avgHrBpm === void 0 && maxHrBpm === void 0 && compliancePct === void 0 && completedDurationSec === void 0 && completedTss === void 0 ? {} : {
8189
- actualMetrics: {
8190
- ...avgPower === void 0 ? {} : { avgPower },
8191
- ...avgHrBpm === void 0 ? {} : { avgHrBpm },
8192
- ...maxHrBpm === void 0 ? {} : { maxHrBpm },
8193
- ...compliancePct === void 0 ? {} : { compliancePct },
8194
- ...completedDurationSec === void 0 ? {} : { completedDurationSec },
8195
- ...completedTss === void 0 ? {} : { completedTss }
8196
- }
8197
- },
8198
- ...notes.length === 0 ? {} : { notes }
8199
- };
8200
- return context.authState ? writeResult(context, "session_complete", await context.client.ingestMyCompletedSession(completion)) : writeResult(context, "session_complete", await context.client.ingestCompletedSession(completion));
8201
- }
8202
- if (subcommand === "adapt") {
8203
- const op = takeOption(context.args, "--op");
8204
- const reason = takeOption(context.args, "--reason");
8205
- const payload = await readJsonInput(context.args, "--payload-json", "--payload-file");
8206
- if (!op) {
8207
- throw new Error("Missing required argument: --op.");
8208
- }
8209
- if (!reason) {
8210
- throw new Error("Missing required argument: --reason.");
8211
- }
8212
- const adaptation = {
8213
- sessionId,
8214
- op,
8215
- reason,
8216
- ...payload === void 0 ? {} : { payload }
8217
- };
8218
- return context.authState ? writeResult(context, "session_adapt", await context.client.adaptMyActiveSession(adaptation)) : writeResult(context, "session_adapt", await context.client.adaptActiveSession(adaptation));
8219
- }
8220
8393
  if (subcommand === "summary") {
8221
8394
  return context.authState ? writeResult(context, "session_summary", await context.client.summarizeMySession({ sessionId })) : writeResult(context, "session_summary", await context.client.summarizeSession({ sessionId }));
8222
8395
  }
@@ -8284,7 +8457,8 @@ async function runPlannerCli(options) {
8284
8457
  stdout,
8285
8458
  stderr,
8286
8459
  client,
8287
- authState: await authController.loadState()
8460
+ authState: await authController.loadState(),
8461
+ stravaLimitedAccess: ["1", "true", "yes", "on"].includes((env.WATTSON_STRAVA_LIMITED_ACCESS ?? env.PLANNER_MCP_STRAVA_LIMITED_ACCESS ?? "").trim().toLowerCase())
8288
8462
  });
8289
8463
  } catch (error) {
8290
8464
  writeError(stderr, error);