hevy-mcp 4.1.3 → 5.0.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.
@@ -4,9 +4,9 @@
4
4
  (function() {
5
5
  try {
6
6
  var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
7
- e.SENTRY_RELEASE = { id: "hevy-mcp@4.1.3" };
7
+ e.SENTRY_RELEASE = { id: "hevy-mcp@5.0.0" };
8
8
  var n = new e.Error().stack;
9
- n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "112c763b-1652-4fb5-8237-94b6a8ef1122", e._sentryDebugIdIdentifier = "sentry-dbid-112c763b-1652-4fb5-8237-94b6a8ef1122");
9
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "8ea46bdf-fa98-4e73-b4d5-a25044e016df", e._sentryDebugIdIdentifier = "sentry-dbid-8ea46bdf-fa98-4e73-b4d5-a25044e016df");
10
10
  } catch (e) {}
11
11
  })();
12
12
  import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
@@ -80,11 +80,11 @@ function sanitizeSentryMcpSpan(span) {
80
80
  * OTel Collector → Honeycomb: performance traces, metrics
81
81
  */
82
82
  const name$1 = "hevy-mcp";
83
- const version$1 = "4.1.3";
83
+ const version$1 = "5.0.0";
84
84
  const telemetryEnabled = process.env.HEVY_MCP_TELEMETRY !== "0";
85
85
  const collectorToken = "NH9vOela-HYreQxAbJa68cjEmORoEKM57EvneUDVcOo";
86
86
  const COLLECTOR_ENDPOINT = "https://otel.chrisdoc.dev/v1";
87
- const sentryRelease = process.env.SENTRY_RELEASE ?? `hevy-mcp@4.1.3`;
87
+ const sentryRelease = process.env.SENTRY_RELEASE ?? `hevy-mcp@5.0.0`;
88
88
  const resource = resourceFromAttributes({
89
89
  "service.name": name$1,
90
90
  "service.version": version$1
@@ -226,8 +226,8 @@ const repRangeShape = {
226
226
  function optionalRepRangeSchema(schema) {
227
227
  return z.preprocess((value) => value === null ? void 0 : value, schema);
228
228
  }
229
- const zOptionalRepRange = optionalRepRangeSchema(z.object(repRangeShape).optional());
230
- optionalRepRangeSchema(z.object(repRangeShape).strict().optional());
229
+ optionalRepRangeSchema(z.object(repRangeShape).optional());
230
+ const zStrictOptionalRepRange = optionalRepRangeSchema(z.object(repRangeShape).strict().optional());
231
231
  const setTypeEnum = z.enum([
232
232
  "warmup",
233
233
  "normal",
@@ -1468,8 +1468,8 @@ function withPromptObservation(name, observer, handler) {
1468
1468
  scope = memoizeObservationScope(observer?.start({
1469
1469
  name,
1470
1470
  kind: "prompt",
1471
- argumentKeys: Object.keys(args).filter((key) => key === "routineId"),
1472
- argumentPresence: args.routineId ? { routineId: true } : {},
1471
+ argumentKeys: Object.keys(args).filter((key) => key === "routine_id"),
1472
+ argumentPresence: args.routine_id ? { routine_id: true } : {},
1473
1473
  argumentKeyCountBucket: bucketCount(Object.keys(args).length)
1474
1474
  }));
1475
1475
  } catch {
@@ -1524,22 +1524,22 @@ function registerWorkoutPrompts(server, observer) {
1524
1524
  title: "Create Workout From Routine",
1525
1525
  description: "Create a completed workout from an existing routine.",
1526
1526
  argsSchema: z.object({
1527
- routineId: z.string().min(1).optional().describe("Routine ID to use as a guide."),
1528
- startTime: utcSecondTimestamp.optional().describe("Workout start time in UTC as YYYY-MM-DDTHH:mm:ssZ.")
1527
+ routine_id: z.string().min(1).optional().describe("Routine ID to use as a guide."),
1528
+ start_time: utcSecondTimestamp.optional().describe("Workout start time in UTC as YYYY-MM-DDTHH:mm:ssZ.")
1529
1529
  })
1530
- }, withPromptObservation("create-workout-from-routine", observer, ({ routineId, startTime }) => ({ messages: [{
1530
+ }, withPromptObservation("create-workout-from-routine", observer, ({ routine_id, start_time }) => ({ messages: [{
1531
1531
  role: "user",
1532
1532
  content: {
1533
1533
  type: "text",
1534
- text: routineId && startTime ? [
1535
- `Create a workout from routine ${routineId}, starting at ${startTime}.`,
1536
- "First call get-routine with the routineId and map supported plan fields: routine title to workout title, plus each exerciseTemplateId, supersetId, exercise notes, and set type.",
1537
- "Do not copy routine-only restSeconds or repRange fields into create-workout.",
1538
- "Before calling create-workout, confirm or collect the user's actual completed set data for every set, including applicable weight, reps, distance, duration, RPE, or custom metric values.",
1539
- "Also collect the required endTime in strict UTC YYYY-MM-DDTHH:mm:ssZ format and confirm any other missing required workout fields.",
1540
- "Never invent completion data. If the actual results or endTime are unavailable, ask the user for them instead of creating the workout.",
1534
+ text: routine_id && start_time ? [
1535
+ `Create a workout from routine ${routine_id}, starting at ${start_time}.`,
1536
+ ["First call get-routine with the routine_id and map supported plan fields:", "routine title to workout title, plus each exercise_template_id, superset_id, exercise notes, and set type."].join(" "),
1537
+ ["Do not copy routine-only rest_seconds or rep_range fields into", "create-workout."].join(" "),
1538
+ ["Before calling create-workout, confirm or collect the user's actual completed set data for every set,", "including applicable weight_kg, reps, distance_meters, duration_seconds, rpe, or custom_metric values."].join(" "),
1539
+ ["Also collect the required end_time in strict UTC YYYY-MM-DDTHH:mm:ssZ format and confirm", "any other missing required workout fields."].join(" "),
1540
+ ["Never invent completion data. If the actual results or end_time are unavailable, ask the user for them", "instead of creating the workout."].join(" "),
1541
1541
  "Once confirmed, call create-workout with only fields supported by that tool."
1542
- ].join("\n") : "Provide a routineId and startTime to generate the full prompt."
1542
+ ].join("\n") : "Provide a routine_id and start_time to generate the full prompt."
1543
1543
  }
1544
1544
  }] })));
1545
1545
  }
@@ -1845,7 +1845,7 @@ const getV1WorkoutsWorkoutid200Schema = z$1.lazy(() => workoutSchema);
1845
1845
  z$1.any();
1846
1846
  z$1.lazy(() => getV1WorkoutsWorkoutid200Schema);
1847
1847
  //#endregion
1848
- //#region ../core/src/utils/response-formatter.ts
1848
+ //#region ../core/src/utils/response-contracts.ts
1849
1849
  function jsonText(data) {
1850
1850
  return JSON.stringify(data, null, 2) ?? "null";
1851
1851
  }
@@ -1883,146 +1883,150 @@ function defineJsonResponseContract(present, telemetry) {
1883
1883
  function respond(contract, data) {
1884
1884
  return contract.render(data);
1885
1885
  }
1886
- const optionalNullableNumber = z.number().nullable().optional();
1886
+ const optionalNumber = z.number().optional();
1887
1887
  const formattedWorkoutSetSchema = z.object({
1888
1888
  index: z.number().optional(),
1889
1889
  type: z.string().optional(),
1890
- weight: optionalNullableNumber,
1891
- reps: optionalNullableNumber,
1892
- distance: optionalNullableNumber,
1893
- duration: optionalNullableNumber,
1894
- rpe: optionalNullableNumber,
1895
- customMetric: optionalNullableNumber
1890
+ weight_kg: optionalNumber,
1891
+ reps: optionalNumber,
1892
+ distance_meters: optionalNumber,
1893
+ duration_seconds: optionalNumber,
1894
+ rpe: optionalNumber,
1895
+ custom_metric: optionalNumber
1896
1896
  });
1897
1897
  const formattedWorkoutExerciseSchema = z.object({
1898
1898
  index: z.number().optional(),
1899
- name: z.string().optional(),
1900
- exerciseTemplateId: z.string().optional(),
1901
- notes: z.string().nullable().optional(),
1902
- supersetsId: optionalNullableNumber,
1899
+ title: z.string().optional(),
1900
+ exercise_template_id: z.string().optional(),
1901
+ notes: z.string().optional(),
1902
+ supersets_id: optionalNumber,
1903
1903
  sets: z.array(formattedWorkoutSetSchema).optional()
1904
1904
  });
1905
1905
  const formattedWorkoutSchema = z.object({
1906
1906
  id: z.string().optional(),
1907
+ routine_id: z.string().optional(),
1907
1908
  title: z.string().optional(),
1908
- description: z.string().nullable().optional(),
1909
- startTime: z.union([z.string(), z.number()]).optional(),
1910
- endTime: z.union([z.string(), z.number()]).optional(),
1911
- createdAt: z.string().optional(),
1912
- updatedAt: z.string().optional(),
1909
+ description: z.string().optional(),
1910
+ start_time: z.string().optional(),
1911
+ end_time: z.string().optional(),
1912
+ created_at: z.string().optional(),
1913
+ updated_at: z.string().optional(),
1913
1914
  duration: z.string(),
1914
1915
  exercises: z.array(formattedWorkoutExerciseSchema).optional()
1915
1916
  });
1916
1917
  const formattedRoutineSetSchema = z.object({
1917
1918
  index: z.number().optional(),
1918
1919
  type: z.string().optional(),
1919
- weight: optionalNullableNumber,
1920
- reps: optionalNullableNumber,
1921
- distance: optionalNullableNumber,
1922
- duration: optionalNullableNumber,
1923
- customMetric: optionalNullableNumber,
1924
- repRange: z.object({
1925
- start: z.number().nullable().optional(),
1926
- end: z.number().nullable().optional()
1927
- }).nullable().optional(),
1928
- rpe: optionalNullableNumber
1920
+ weight_kg: optionalNumber,
1921
+ reps: optionalNumber,
1922
+ distance_meters: optionalNumber,
1923
+ duration_seconds: optionalNumber,
1924
+ custom_metric: optionalNumber,
1925
+ rep_range: z.object({
1926
+ start: z.number().optional(),
1927
+ end: z.number().optional()
1928
+ }).optional(),
1929
+ rpe: optionalNumber
1929
1930
  });
1930
1931
  const formattedRoutineExerciseSchema = z.object({
1931
- name: z.string().optional(),
1932
+ title: z.string().optional(),
1932
1933
  index: z.number().optional(),
1933
- exerciseTemplateId: z.string().optional(),
1934
- notes: z.string().nullable().optional(),
1935
- supersetId: optionalNullableNumber,
1936
- restSeconds: z.union([z.string(), z.number()]).nullable().optional(),
1934
+ exercise_template_id: z.string().optional(),
1935
+ notes: z.string().optional(),
1936
+ supersets_id: optionalNumber,
1937
+ rest_seconds: z.string().optional(),
1937
1938
  sets: z.array(formattedRoutineSetSchema).optional()
1938
1939
  });
1939
1940
  const formattedRoutineSchema = z.object({
1940
1941
  id: z.string().optional(),
1941
1942
  title: z.string().optional(),
1942
- folderId: z.number().nullable().optional(),
1943
- createdAt: z.string().optional(),
1944
- updatedAt: z.string().optional(),
1943
+ folder_id: z.number().optional(),
1944
+ created_at: z.string().optional(),
1945
+ updated_at: z.string().optional(),
1945
1946
  exercises: z.array(formattedRoutineExerciseSchema).optional()
1946
1947
  });
1947
1948
  const formattedRoutineFolderSchema = z.object({
1948
1949
  id: z.number().optional(),
1949
1950
  title: z.string().optional(),
1950
- createdAt: z.string().optional(),
1951
- updatedAt: z.string().optional()
1951
+ created_at: z.string().optional(),
1952
+ updated_at: z.string().optional()
1952
1953
  });
1953
1954
  const formattedExerciseTemplateSchema = z.object({
1954
1955
  id: z.string().optional(),
1955
1956
  title: z.string().optional(),
1956
1957
  type: z.string().optional(),
1957
- primaryMuscleGroup: z.string().optional(),
1958
- secondaryMuscleGroups: z.array(z.string()).optional(),
1959
- isCustom: z.boolean().optional()
1958
+ primary_muscle_group: z.string().optional(),
1959
+ secondary_muscle_groups: z.array(z.string()).optional(),
1960
+ is_custom: z.boolean().optional()
1960
1961
  });
1961
1962
  const formattedExerciseHistoryEntrySchema = z.object({
1962
- workoutId: z.string().optional(),
1963
- workoutTitle: z.string().optional(),
1964
- workoutStartTime: z.string().optional(),
1965
- workoutEndTime: z.string().optional(),
1966
- exerciseTemplateId: z.string().optional(),
1967
- weight: optionalNullableNumber,
1968
- reps: optionalNullableNumber,
1969
- distance: optionalNullableNumber,
1970
- duration: optionalNullableNumber,
1971
- rpe: optionalNullableNumber,
1972
- customMetric: optionalNullableNumber,
1973
- setType: z.string().optional()
1963
+ workout_id: z.string().optional(),
1964
+ workout_title: z.string().optional(),
1965
+ workout_start_time: z.string().optional(),
1966
+ workout_end_time: z.string().optional(),
1967
+ exercise_template_id: z.string().optional(),
1968
+ weight_kg: optionalNumber,
1969
+ reps: optionalNumber,
1970
+ distance_meters: optionalNumber,
1971
+ duration_seconds: optionalNumber,
1972
+ rpe: optionalNumber,
1973
+ custom_metric: optionalNumber,
1974
+ set_type: z.string().optional()
1974
1975
  });
1975
1976
  const formattedBodyMeasurementSchema = z.object({
1976
1977
  date: z.string(),
1977
- weightKg: z.number().nullable(),
1978
- leanMassKg: z.number().nullable(),
1979
- fatPercent: z.number().nullable(),
1980
- neckCm: z.number().nullable(),
1981
- shoulderCm: z.number().nullable(),
1982
- chestCm: z.number().nullable(),
1983
- leftBicepCm: z.number().nullable(),
1984
- rightBicepCm: z.number().nullable(),
1985
- leftForearmCm: z.number().nullable(),
1986
- rightForearmCm: z.number().nullable(),
1987
- abdomen: z.number().nullable(),
1988
- waist: z.number().nullable(),
1989
- hips: z.number().nullable(),
1990
- leftThigh: z.number().nullable(),
1991
- rightThigh: z.number().nullable(),
1992
- leftCalf: z.number().nullable(),
1993
- rightCalf: z.number().nullable()
1978
+ weight_kg: optionalNumber,
1979
+ lean_mass_kg: optionalNumber,
1980
+ fat_percent: optionalNumber,
1981
+ neck_cm: optionalNumber,
1982
+ shoulder_cm: optionalNumber,
1983
+ chest_cm: optionalNumber,
1984
+ left_bicep_cm: optionalNumber,
1985
+ right_bicep_cm: optionalNumber,
1986
+ left_forearm_cm: optionalNumber,
1987
+ right_forearm_cm: optionalNumber,
1988
+ abdomen: optionalNumber,
1989
+ waist: optionalNumber,
1990
+ hips: optionalNumber,
1991
+ left_thigh_cm: optionalNumber,
1992
+ right_thigh_cm: optionalNumber,
1993
+ left_calf_cm: optionalNumber,
1994
+ right_calf_cm: optionalNumber
1994
1995
  });
1995
- const workflowTelemetrySchema = z.object({
1996
- name: z.string(),
1997
- pagination: z.record(z.string(), z.number().int().nonnegative()),
1998
- cacheStatus: z.enum([
1999
- "hit",
2000
- "miss",
2001
- "not-used"
2002
- ]),
2003
- itemsScanned: z.number().int().nonnegative()
1996
+ const scanSchema = z.object({
1997
+ pages: z.record(z.string(), z.number().int().nonnegative()),
1998
+ items: z.number().int().nonnegative()
2004
1999
  });
2005
2000
  const trainingSummarySessionSchema = z.object({
2006
2001
  id: z.string().optional(),
2007
2002
  title: z.string().optional(),
2008
- startTime: z.string().optional(),
2009
- endTime: z.string().optional(),
2010
- durationSeconds: z.number().int().nonnegative().nullable(),
2011
- exerciseCount: z.number().int().nonnegative(),
2012
- setCount: z.number().int().nonnegative()
2003
+ start_time: z.string().optional(),
2004
+ end_time: z.string().optional(),
2005
+ duration_seconds: z.number().int().nonnegative().optional(),
2006
+ exercise_count: z.number().int().nonnegative(),
2007
+ set_count: z.number().int().nonnegative()
2013
2008
  });
2014
2009
  const compactRoutineSchema = z.object({
2015
2010
  id: z.string().optional(),
2016
2011
  title: z.string().optional(),
2017
- folderId: z.number().nullable(),
2018
- updatedAt: z.string().optional(),
2019
- exerciseCount: z.number().int().nonnegative(),
2020
- setCount: z.number().int().nonnegative()
2012
+ folder_id: z.number().optional(),
2013
+ updated_at: z.string().optional(),
2014
+ exercise_count: z.number().int().nonnegative(),
2015
+ set_count: z.number().int().nonnegative()
2016
+ });
2017
+ const formattedWorkoutSummarySchema = z.object({
2018
+ id: z.string().optional(),
2019
+ title: z.string().optional(),
2020
+ start_time: z.string().optional(),
2021
+ end_time: z.string().optional(),
2022
+ duration: z.string(),
2023
+ exercise_count: z.number().int().nonnegative(),
2024
+ set_count: z.number().int().nonnegative()
2021
2025
  });
2022
2026
  const paginationOutputSchema = {
2023
2027
  page: z.number().int().positive(),
2024
- pageCount: z.number().int().nonnegative().optional(),
2025
- hasNextPage: z.boolean().optional()
2028
+ page_count: z.number().int().nonnegative().optional(),
2029
+ has_next_page: z.boolean().optional()
2026
2030
  };
2027
2031
  function normalizePaginatedInput(data) {
2028
2032
  if (data === void 0) return {
@@ -2036,11 +2040,11 @@ function normalizePaginatedInput(data) {
2036
2040
  };
2037
2041
  }
2038
2042
  const workoutsOutputSchema = {
2039
- workouts: z.array(formattedWorkoutSchema),
2043
+ workouts: z.array(formattedWorkoutSummarySchema),
2040
2044
  ...paginationOutputSchema
2041
2045
  };
2042
2046
  const workoutOutputSchema = { workout: formattedWorkoutSchema.nullable() };
2043
- const workoutCountOutputSchema = { count: z.number().int() };
2047
+ const workoutCountOutputSchema = { workout_count: z.number().int() };
2044
2048
  const formattedUpdatedWorkoutSchema = z.object({
2045
2049
  type: z.literal("updated"),
2046
2050
  workout: formattedWorkoutSchema
@@ -2048,70 +2052,70 @@ const formattedUpdatedWorkoutSchema = z.object({
2048
2052
  const formattedDeletedWorkoutSchema = z.object({
2049
2053
  type: z.literal("deleted"),
2050
2054
  id: z.string(),
2051
- deletedAt: z.string().optional()
2055
+ deleted_at: z.string().optional()
2052
2056
  });
2053
2057
  const workoutEventsOutputSchema = {
2054
2058
  events: z.array(z.union([formattedUpdatedWorkoutSchema, formattedDeletedWorkoutSchema])),
2055
2059
  ...paginationOutputSchema
2056
2060
  };
2057
2061
  const routinesOutputSchema = {
2058
- routines: z.array(formattedRoutineSchema),
2062
+ routines: z.array(compactRoutineSchema),
2059
2063
  ...paginationOutputSchema
2060
2064
  };
2061
2065
  const routineOutputSchema = { routine: formattedRoutineSchema.nullable() };
2062
2066
  const exerciseTemplatesOutputSchema = {
2063
- exerciseTemplates: z.array(formattedExerciseTemplateSchema),
2067
+ exercise_templates: z.array(formattedExerciseTemplateSchema),
2064
2068
  ...paginationOutputSchema
2065
2069
  };
2066
- const exerciseTemplateSearchOutputSchema = { exerciseTemplates: z.array(formattedExerciseTemplateSchema) };
2067
- const exerciseTemplateOutputSchema = { exerciseTemplate: formattedExerciseTemplateSchema.nullable() };
2068
- const exerciseHistoryOutputSchema = { exerciseHistory: z.array(formattedExerciseHistoryEntrySchema) };
2070
+ const exerciseTemplateSearchOutputSchema = { exercise_templates: z.array(formattedExerciseTemplateSchema) };
2071
+ const exerciseTemplateOutputSchema = { exercise_template: formattedExerciseTemplateSchema.nullable() };
2072
+ const exerciseHistoryOutputSchema = { exercise_history: z.array(formattedExerciseHistoryEntrySchema) };
2069
2073
  const routineFoldersOutputSchema = {
2070
- routineFolders: z.array(formattedRoutineFolderSchema),
2074
+ routine_folders: z.array(formattedRoutineFolderSchema),
2071
2075
  ...paginationOutputSchema
2072
2076
  };
2073
- const routineFolderOutputSchema = { routineFolder: formattedRoutineFolderSchema.nullable() };
2077
+ const routineFolderOutputSchema = { routine_folder: formattedRoutineFolderSchema.nullable() };
2074
2078
  const bodyMeasurementsOutputSchema = {
2075
- bodyMeasurements: z.array(formattedBodyMeasurementSchema),
2079
+ body_measurements: z.array(formattedBodyMeasurementSchema),
2076
2080
  ...paginationOutputSchema
2077
2081
  };
2078
- const bodyMeasurementOutputSchema = { bodyMeasurement: formattedBodyMeasurementSchema.nullable() };
2082
+ const bodyMeasurementOutputSchema = { body_measurement: formattedBodyMeasurementSchema.nullable() };
2079
2083
  const userOutputSchema = { user: userInfoSchema.nullable() };
2080
2084
  const trainingSummaryOutputSchema = {
2081
2085
  period: z.object({
2082
- startDate: z.string(),
2083
- endDate: z.string(),
2086
+ start_date: z.string(),
2087
+ end_date: z.string(),
2084
2088
  weeks: z.number().int().positive()
2085
2089
  }),
2086
2090
  workouts: z.object({
2087
2091
  count: z.number().int().nonnegative(),
2088
- totalDurationSeconds: z.number().int().nonnegative(),
2089
- exerciseCount: z.number().int().nonnegative(),
2090
- setCount: z.number().int().nonnegative(),
2091
- uniqueExerciseTemplateIds: z.array(z.string()),
2092
+ total_duration_seconds: z.number().int().nonnegative(),
2093
+ exercise_count: z.number().int().nonnegative(),
2094
+ set_count: z.number().int().nonnegative(),
2095
+ unique_exercise_template_ids: z.array(z.string()),
2092
2096
  sessions: z.array(trainingSummarySessionSchema)
2093
2097
  }),
2094
- bodyMeasurements: z.object({
2098
+ body_measurements: z.object({
2095
2099
  count: z.number().int().nonnegative(),
2096
2100
  latest: z.object({
2097
2101
  date: z.string(),
2098
- weightKg: z.number().nullable(),
2099
- leanMassKg: z.number().nullable(),
2100
- fatPercent: z.number().nullable()
2101
- }).nullable(),
2102
+ weight_kg: optionalNumber,
2103
+ lean_mass_kg: optionalNumber,
2104
+ fat_percent: optionalNumber
2105
+ }).optional(),
2102
2106
  earliest: z.object({
2103
2107
  date: z.string(),
2104
- weightKg: z.number().nullable(),
2105
- leanMassKg: z.number().nullable(),
2106
- fatPercent: z.number().nullable()
2107
- }).nullable(),
2108
- weightChangeKg: z.number().nullable()
2108
+ weight_kg: optionalNumber,
2109
+ lean_mass_kg: optionalNumber,
2110
+ fat_percent: optionalNumber
2111
+ }).optional(),
2112
+ weight_change_kg: optionalNumber
2109
2113
  }),
2110
- workflow: workflowTelemetrySchema
2114
+ scan: scanSchema
2111
2115
  };
2112
2116
  const compactRoutinesOutputSchema = {
2113
2117
  routines: z.array(compactRoutineSchema),
2114
- workflow: workflowTelemetrySchema
2118
+ scan: scanSchema
2115
2119
  };
2116
2120
  function getSupersetId(exercise) {
2117
2121
  if (exercise.superset_id !== void 0) return exercise.superset_id;
@@ -2119,99 +2123,104 @@ function getSupersetId(exercise) {
2119
2123
  return null;
2120
2124
  }
2121
2125
  /**
2122
- * Format a workout object for consistent presentation
2123
- *
2124
- * @param workout - The workout object from the API
2125
- * @returns A formatted workout object with standardized properties
2126
+ * Project a workout into the sparse snake_case MCP contract.
2126
2127
  */
2127
- function formatWorkout(workout) {
2128
+ function projectWorkout(workout) {
2128
2129
  return {
2129
- id: workout.id,
2130
- title: workout.title,
2131
- description: workout.description,
2132
- startTime: workout.start_time,
2133
- endTime: workout.end_time,
2134
- createdAt: workout.created_at,
2135
- updatedAt: workout.updated_at,
2130
+ ...workout.id == null ? {} : { id: workout.id },
2131
+ ...workout.routine_id == null ? {} : { routine_id: workout.routine_id },
2132
+ ...workout.title == null ? {} : { title: workout.title },
2133
+ ...workout.description == null ? {} : { description: workout.description },
2134
+ ...workout.start_time == null ? {} : { start_time: workout.start_time },
2135
+ ...workout.end_time == null ? {} : { end_time: workout.end_time },
2136
+ ...workout.created_at == null ? {} : { created_at: workout.created_at },
2137
+ ...workout.updated_at == null ? {} : { updated_at: workout.updated_at },
2136
2138
  duration: calculateDuration(workout.start_time, workout.end_time),
2137
- exercises: workout.exercises?.map((exercise) => {
2138
- return {
2139
- index: exercise.index,
2140
- name: exercise.title,
2141
- exerciseTemplateId: exercise.exercise_template_id,
2142
- notes: exercise.notes,
2143
- supersetsId: getSupersetId(exercise),
2144
- sets: exercise.sets?.map((set) => ({
2145
- index: set.index,
2146
- type: set.type,
2147
- weight: set.weight_kg,
2148
- reps: set.reps,
2149
- distance: set.distance_meters,
2150
- duration: set.duration_seconds,
2151
- rpe: set.rpe,
2152
- customMetric: set.custom_metric
2153
- }))
2154
- };
2155
- })
2139
+ ...workout.exercises ? { exercises: workout.exercises.map((exercise) => ({
2140
+ ...exercise.index === void 0 ? {} : { index: exercise.index },
2141
+ ...exercise.title == null ? {} : { title: exercise.title },
2142
+ ...exercise.exercise_template_id == null ? {} : { exercise_template_id: exercise.exercise_template_id },
2143
+ ...exercise.notes == null ? {} : { notes: exercise.notes },
2144
+ ...getSupersetId(exercise) == null ? {} : { supersets_id: getSupersetId(exercise) ?? void 0 },
2145
+ ...exercise.sets ? { sets: exercise.sets.map((set) => ({
2146
+ ...set.index === void 0 ? {} : { index: set.index },
2147
+ ...set.type == null ? {} : { type: set.type },
2148
+ ...set.weight_kg == null ? {} : { weight_kg: set.weight_kg },
2149
+ ...set.reps == null ? {} : { reps: set.reps },
2150
+ ...set.distance_meters == null ? {} : { distance_meters: set.distance_meters },
2151
+ ...set.duration_seconds == null ? {} : { duration_seconds: set.duration_seconds },
2152
+ ...set.rpe == null ? {} : { rpe: set.rpe },
2153
+ ...set.custom_metric == null ? {} : { custom_metric: set.custom_metric }
2154
+ })) } : {}
2155
+ })) } : {}
2156
2156
  };
2157
2157
  }
2158
- /**
2159
- * Format a routine object for consistent presentation
2160
- *
2161
- * @param routine - The routine object from the API
2162
- * @returns A formatted routine object with standardized properties
2163
- */
2164
- function formatRoutine(routine) {
2158
+ function summarizeWorkout(workout) {
2159
+ const exercises = workout.exercises ?? [];
2165
2160
  return {
2166
- id: routine.id,
2167
- title: routine.title,
2168
- folderId: routine.folder_id,
2169
- createdAt: routine.created_at,
2170
- updatedAt: routine.updated_at,
2171
- exercises: routine.exercises?.map((exercise) => {
2172
- return {
2173
- name: exercise.title,
2174
- index: exercise.index,
2175
- exerciseTemplateId: exercise.exercise_template_id,
2176
- notes: exercise.notes,
2177
- supersetId: getSupersetId(exercise),
2178
- restSeconds: exercise.rest_seconds,
2179
- sets: exercise.sets?.map((set) => ({
2180
- index: set.index,
2181
- type: set.type,
2182
- weight: set.weight_kg,
2183
- reps: set.reps,
2184
- ...set.rep_range !== void 0 && { repRange: set.rep_range },
2185
- distance: set.distance_meters,
2186
- duration: set.duration_seconds,
2187
- ...set.rpe !== void 0 && { rpe: set.rpe },
2188
- customMetric: set.custom_metric
2189
- }))
2190
- };
2191
- })
2161
+ ...workout.id == null ? {} : { id: workout.id },
2162
+ ...workout.title == null ? {} : { title: workout.title },
2163
+ ...workout.start_time == null ? {} : { start_time: workout.start_time },
2164
+ ...workout.end_time == null ? {} : { end_time: workout.end_time },
2165
+ duration: calculateDuration(workout.start_time, workout.end_time),
2166
+ exercise_count: exercises.length,
2167
+ set_count: exercises.reduce((total, exercise) => total + (exercise.sets?.length ?? 0), 0)
2192
2168
  };
2193
2169
  }
2194
- /**
2195
- * Format a routine folder object for consistent presentation
2196
- *
2197
- * @param folder - The routine folder object from the API
2198
- * @returns A formatted routine folder object with standardized properties
2199
- */
2200
- function formatRoutineFolder(folder) {
2170
+ function projectRoutine(routine) {
2171
+ return {
2172
+ ...routine.id == null ? {} : { id: routine.id },
2173
+ ...routine.title == null ? {} : { title: routine.title },
2174
+ ...routine.folder_id == null ? {} : { folder_id: routine.folder_id },
2175
+ ...routine.created_at == null ? {} : { created_at: routine.created_at },
2176
+ ...routine.updated_at == null ? {} : { updated_at: routine.updated_at },
2177
+ ...routine.exercises ? { exercises: routine.exercises.map((exercise) => ({
2178
+ ...exercise.title == null ? {} : { title: exercise.title },
2179
+ ...exercise.index === void 0 ? {} : { index: exercise.index },
2180
+ ...exercise.exercise_template_id == null ? {} : { exercise_template_id: exercise.exercise_template_id },
2181
+ ...exercise.notes == null ? {} : { notes: exercise.notes },
2182
+ ...getSupersetId(exercise) == null ? {} : { supersets_id: getSupersetId(exercise) ?? void 0 },
2183
+ ...exercise.rest_seconds == null ? {} : { rest_seconds: exercise.rest_seconds },
2184
+ ...exercise.sets ? { sets: exercise.sets.map((set) => {
2185
+ const repRange = set.rep_range;
2186
+ const normalizedRepRange = repRange && (repRange.start != null || repRange.end != null) ? {
2187
+ ...repRange.start == null ? {} : { start: repRange.start },
2188
+ ...repRange.end == null ? {} : { end: repRange.end }
2189
+ } : void 0;
2190
+ return {
2191
+ ...set.index === void 0 ? {} : { index: set.index },
2192
+ ...set.type == null ? {} : { type: set.type },
2193
+ ...set.weight_kg == null ? {} : { weight_kg: set.weight_kg },
2194
+ ...set.reps == null ? {} : { reps: set.reps },
2195
+ ...normalizedRepRange ? { rep_range: normalizedRepRange } : {},
2196
+ ...set.distance_meters == null ? {} : { distance_meters: set.distance_meters },
2197
+ ...set.duration_seconds == null ? {} : { duration_seconds: set.duration_seconds },
2198
+ ...set.rpe == null ? {} : { rpe: set.rpe },
2199
+ ...set.custom_metric == null ? {} : { custom_metric: set.custom_metric }
2200
+ };
2201
+ }) } : {}
2202
+ })) } : {}
2203
+ };
2204
+ }
2205
+ function summarizeRoutine(routine) {
2206
+ const exercises = routine.exercises ?? [];
2207
+ return {
2208
+ ...routine.id == null ? {} : { id: routine.id },
2209
+ ...routine.title == null ? {} : { title: routine.title },
2210
+ ...routine.folder_id == null ? {} : { folder_id: routine.folder_id },
2211
+ ...routine.updated_at == null ? { updated_at: void 0 } : { updated_at: routine.updated_at },
2212
+ exercise_count: exercises.length,
2213
+ set_count: exercises.reduce((total, exercise) => total + (exercise.sets?.length ?? 0), 0)
2214
+ };
2215
+ }
2216
+ function projectRoutineFolder(folder) {
2201
2217
  return {
2202
2218
  id: folder.id,
2203
2219
  title: folder.title,
2204
- createdAt: folder.created_at,
2205
- updatedAt: folder.updated_at
2220
+ created_at: folder.created_at,
2221
+ updated_at: folder.updated_at
2206
2222
  };
2207
2223
  }
2208
- /**
2209
- * Calculate duration between two ISO timestamp strings
2210
- *
2211
- * @param startTime - The start time as ISO string or timestamp
2212
- * @param endTime - The end time as ISO string or timestamp
2213
- * @returns A formatted duration string (e.g. "1h 30m 45s") or "Unknown duration" if inputs are invalid
2214
- */
2215
2224
  function calculateDuration(startTime, endTime) {
2216
2225
  if (!startTime || !endTime) return "Unknown duration";
2217
2226
  try {
@@ -2226,69 +2235,53 @@ function calculateDuration(startTime, endTime) {
2226
2235
  return "Unknown duration";
2227
2236
  }
2228
2237
  }
2229
- /**
2230
- * Format an exercise template object for consistent presentation
2231
- *
2232
- * @param template - The exercise template object from the API
2233
- * @returns A formatted exercise template object with standardized properties
2234
- */
2235
- function formatExerciseTemplate(template) {
2236
- return {
2237
- id: template.id,
2238
- title: template.title,
2239
- type: template.type,
2240
- primaryMuscleGroup: template.primary_muscle_group,
2241
- secondaryMuscleGroups: template.secondary_muscle_groups,
2242
- isCustom: template.is_custom
2243
- };
2244
- }
2245
- function formatExerciseHistoryEntry(entry) {
2238
+ function normalizeExerciseHistoryEntry(entry) {
2246
2239
  return {
2247
- workoutId: entry.workout_id,
2248
- workoutTitle: entry.workout_title,
2249
- workoutStartTime: entry.workout_start_time,
2250
- workoutEndTime: entry.workout_end_time,
2251
- exerciseTemplateId: entry.exercise_template_id,
2252
- weight: entry.weight_kg,
2253
- reps: entry.reps,
2254
- distance: entry.distance_meters,
2255
- duration: entry.duration_seconds,
2256
- rpe: entry.rpe,
2257
- customMetric: entry.custom_metric,
2258
- setType: entry.set_type
2240
+ ...entry.workout_id == null ? {} : { workout_id: entry.workout_id },
2241
+ ...entry.workout_title == null ? {} : { workout_title: entry.workout_title },
2242
+ ...entry.workout_start_time == null ? {} : { workout_start_time: entry.workout_start_time },
2243
+ ...entry.workout_end_time == null ? {} : { workout_end_time: entry.workout_end_time },
2244
+ ...entry.exercise_template_id == null ? {} : { exercise_template_id: entry.exercise_template_id },
2245
+ ...entry.weight_kg == null ? {} : { weight_kg: entry.weight_kg },
2246
+ ...entry.reps == null ? {} : { reps: entry.reps },
2247
+ ...entry.distance_meters == null ? {} : { distance_meters: entry.distance_meters },
2248
+ ...entry.duration_seconds == null ? {} : { duration_seconds: entry.duration_seconds },
2249
+ ...entry.rpe == null ? {} : { rpe: entry.rpe },
2250
+ ...entry.custom_metric == null ? {} : { custom_metric: entry.custom_metric },
2251
+ ...entry.set_type == null ? {} : { set_type: entry.set_type }
2259
2252
  };
2260
2253
  }
2261
- function formatBodyMeasurement(measurement) {
2254
+ function normalizeBodyMeasurement(measurement) {
2262
2255
  return {
2263
2256
  date: measurement.date,
2264
- weightKg: measurement.weight_kg ?? null,
2265
- leanMassKg: measurement.lean_mass_kg ?? null,
2266
- fatPercent: measurement.fat_percent ?? null,
2267
- neckCm: measurement.neck_cm ?? null,
2268
- shoulderCm: measurement.shoulder_cm ?? null,
2269
- chestCm: measurement.chest_cm ?? null,
2270
- leftBicepCm: measurement.left_bicep_cm ?? null,
2271
- rightBicepCm: measurement.right_bicep_cm ?? null,
2272
- leftForearmCm: measurement.left_forearm_cm ?? null,
2273
- rightForearmCm: measurement.right_forearm_cm ?? null,
2274
- abdomen: measurement.abdomen ?? null,
2275
- waist: measurement.waist ?? null,
2276
- hips: measurement.hips ?? null,
2277
- leftThigh: measurement.left_thigh ?? null,
2278
- rightThigh: measurement.right_thigh ?? null,
2279
- leftCalf: measurement.left_calf ?? null,
2280
- rightCalf: measurement.right_calf ?? null
2257
+ ...measurement.weight_kg == null ? {} : { weight_kg: measurement.weight_kg },
2258
+ ...measurement.lean_mass_kg == null ? {} : { lean_mass_kg: measurement.lean_mass_kg },
2259
+ ...measurement.fat_percent == null ? {} : { fat_percent: measurement.fat_percent },
2260
+ ...measurement.neck_cm == null ? {} : { neck_cm: measurement.neck_cm },
2261
+ ...measurement.shoulder_cm == null ? {} : { shoulder_cm: measurement.shoulder_cm },
2262
+ ...measurement.chest_cm == null ? {} : { chest_cm: measurement.chest_cm },
2263
+ ...measurement.left_bicep_cm == null ? {} : { left_bicep_cm: measurement.left_bicep_cm },
2264
+ ...measurement.right_bicep_cm == null ? {} : { right_bicep_cm: measurement.right_bicep_cm },
2265
+ ...measurement.left_forearm_cm == null ? {} : { left_forearm_cm: measurement.left_forearm_cm },
2266
+ ...measurement.right_forearm_cm == null ? {} : { right_forearm_cm: measurement.right_forearm_cm },
2267
+ ...measurement.abdomen == null ? {} : { abdomen: measurement.abdomen },
2268
+ ...measurement.waist == null ? {} : { waist: measurement.waist },
2269
+ ...measurement.hips == null ? {} : { hips: measurement.hips },
2270
+ ...measurement.left_thigh == null ? {} : { left_thigh_cm: measurement.left_thigh },
2271
+ ...measurement.right_thigh == null ? {} : { right_thigh_cm: measurement.right_thigh },
2272
+ ...measurement.left_calf == null ? {} : { left_calf_cm: measurement.left_calf },
2273
+ ...measurement.right_calf == null ? {} : { right_calf_cm: measurement.right_calf }
2281
2274
  };
2282
2275
  }
2283
- function formatWorkoutEvent(event) {
2276
+ function projectWorkoutEvent(event) {
2284
2277
  if (event.type === "updated" && "workout" in event) return {
2285
2278
  type: "updated",
2286
- workout: formatWorkout(event.workout)
2279
+ workout: projectWorkout(event.workout)
2287
2280
  };
2288
2281
  if (event.type === "deleted" && "id" in event) return {
2289
2282
  type: "deleted",
2290
2283
  id: event.id,
2291
- deletedAt: event.deleted_at
2284
+ deleted_at: event.deleted_at
2292
2285
  };
2293
2286
  throw new Error(`Unsupported workout event type: ${event.type}`);
2294
2287
  }
@@ -2332,10 +2325,10 @@ const workoutsResponse = defineStructuredResponseContract({
2332
2325
  normalize: (input) => {
2333
2326
  const data = normalizePaginatedInput(input);
2334
2327
  return {
2335
- workouts: data.items.map(formatWorkout),
2328
+ workouts: data.items.map(summarizeWorkout),
2336
2329
  page: data.page,
2337
- pageCount: data.pageCount,
2338
- hasNextPage: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2330
+ page_count: data.pageCount,
2331
+ has_next_page: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2339
2332
  };
2340
2333
  },
2341
2334
  legacyJson: ({ workouts }) => workouts,
@@ -2347,9 +2340,9 @@ const workoutsResponse = defineStructuredResponseContract({
2347
2340
  });
2348
2341
  const workoutResponse = defineStructuredResponseContract({
2349
2342
  outputSchema: workoutOutputSchema,
2350
- normalize: (data) => ({ workout: data.workout ? formatWorkout(data.workout) : null }),
2343
+ normalize: (data) => ({ workout: data.workout ? projectWorkout(data.workout) : null }),
2351
2344
  legacyJson: ({ workout }) => workout,
2352
- text: ({ workoutId }, { workout }) => workout === null ? `Workout with ID ${workoutId} not found` : void 0,
2345
+ text: ({ workout_id }, { workout }) => workout === null ? `Workout with ID ${workout_id} not found` : void 0,
2353
2346
  telemetry: ({ workout, expected404Outcome }) => ({
2354
2347
  ...workoutResultTelemetry(workout),
2355
2348
  expected404Outcome
@@ -2357,17 +2350,17 @@ const workoutResponse = defineStructuredResponseContract({
2357
2350
  });
2358
2351
  const workoutCountResponse = defineStructuredResponseContract({
2359
2352
  outputSchema: workoutCountOutputSchema,
2360
- normalize: (count) => ({ count }),
2353
+ normalize: (count) => ({ workout_count: count }),
2361
2354
  legacyJson: (output) => output,
2362
2355
  telemetry: (count) => ({ itemCountBucket: bucketCount(count) })
2363
2356
  });
2364
2357
  const workoutEventsResponse = defineStructuredResponseContract({
2365
2358
  outputSchema: workoutEventsOutputSchema,
2366
2359
  normalize: (data) => ({
2367
- events: data.events?.map(formatWorkoutEvent) ?? [],
2360
+ events: data.events?.map(projectWorkoutEvent) ?? [],
2368
2361
  page: data.page,
2369
- pageCount: data.pageCount,
2370
- hasNextPage: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2362
+ page_count: data.pageCount,
2363
+ has_next_page: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2371
2364
  }),
2372
2365
  legacyJson: ({ events }) => events,
2373
2366
  text: ({ since }, { events }) => events.length === 0 ? `No workout events found for the specified parameters since ${since}` : void 0,
@@ -2381,10 +2374,10 @@ const routinesResponse = defineStructuredResponseContract({
2381
2374
  normalize: (input) => {
2382
2375
  const data = normalizePaginatedInput(input);
2383
2376
  return {
2384
- routines: data.items.map(formatRoutine),
2377
+ routines: data.items.map(summarizeRoutine),
2385
2378
  page: data.page,
2386
- pageCount: data.pageCount,
2387
- hasNextPage: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2379
+ page_count: data.pageCount,
2380
+ has_next_page: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2388
2381
  };
2389
2382
  },
2390
2383
  legacyJson: ({ routines }) => routines,
@@ -2396,9 +2389,9 @@ const routinesResponse = defineStructuredResponseContract({
2396
2389
  });
2397
2390
  const routineResponse = defineStructuredResponseContract({
2398
2391
  outputSchema: routineOutputSchema,
2399
- normalize: (data) => ({ routine: data.routine ? formatRoutine(data.routine) : null }),
2392
+ normalize: (data) => ({ routine: data.routine ? projectRoutine(data.routine) : null }),
2400
2393
  legacyJson: ({ routine }) => routine,
2401
- text: ({ routineId }, { routine }) => routine === null ? `Routine with ID ${routineId} not found` : void 0,
2394
+ text: ({ routine_id }, { routine }) => routine === null ? `Routine with ID ${routine_id} not found` : void 0,
2402
2395
  telemetry: ({ routine, expected404Outcome }) => ({
2403
2396
  ...routineResultTelemetry(routine),
2404
2397
  expected404Outcome
@@ -2409,14 +2402,14 @@ const exerciseTemplatesResponse = defineStructuredResponseContract({
2409
2402
  normalize: (input) => {
2410
2403
  const data = normalizePaginatedInput(input);
2411
2404
  return {
2412
- exerciseTemplates: data.items.map(formatExerciseTemplate),
2405
+ exercise_templates: data.items,
2413
2406
  page: data.page,
2414
- pageCount: data.pageCount,
2415
- hasNextPage: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2407
+ page_count: data.pageCount,
2408
+ has_next_page: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2416
2409
  };
2417
2410
  },
2418
- legacyJson: ({ exerciseTemplates }) => exerciseTemplates,
2419
- text: (_data, { exerciseTemplates }) => exerciseTemplates.length === 0 ? "No exercise templates found for the specified parameters" : void 0,
2411
+ legacyJson: ({ exercise_templates }) => exercise_templates,
2412
+ text: (_data, { exercise_templates }) => exercise_templates.length === 0 ? "No exercise templates found for the specified parameters" : void 0,
2420
2413
  telemetry: (templates) => ({
2421
2414
  itemCountBucket: bucketCount(normalizePaginatedInput(templates).items.length),
2422
2415
  expected404Outcome: normalizePaginatedInput(templates).expected404Outcome
@@ -2424,26 +2417,26 @@ const exerciseTemplatesResponse = defineStructuredResponseContract({
2424
2417
  });
2425
2418
  const exerciseTemplateResponse = defineStructuredResponseContract({
2426
2419
  outputSchema: exerciseTemplateOutputSchema,
2427
- normalize: (data) => ({ exerciseTemplate: data.exerciseTemplate ? formatExerciseTemplate(data.exerciseTemplate) : null }),
2428
- legacyJson: ({ exerciseTemplate }) => exerciseTemplate,
2429
- text: ({ exerciseTemplateId }, { exerciseTemplate }) => exerciseTemplate === null ? `Exercise template with ID ${exerciseTemplateId} not found` : void 0,
2430
- telemetry: ({ exerciseTemplate, expected404Outcome }) => ({
2431
- itemCountBucket: bucketCount(exerciseTemplate ? 1 : 0),
2420
+ normalize: (data) => ({ exercise_template: data.exercise_template ?? null }),
2421
+ legacyJson: ({ exercise_template }) => exercise_template,
2422
+ text: ({ exercise_template_id }, { exercise_template }) => exercise_template === null ? `Exercise template with ID ${exercise_template_id} not found` : void 0,
2423
+ telemetry: ({ exercise_template, expected404Outcome }) => ({
2424
+ itemCountBucket: bucketCount(exercise_template ? 1 : 0),
2432
2425
  expected404Outcome
2433
2426
  })
2434
2427
  });
2435
2428
  const exerciseHistoryResponse = defineStructuredResponseContract({
2436
2429
  outputSchema: exerciseHistoryOutputSchema,
2437
- normalize: (data) => ({ exerciseHistory: data.history?.map(formatExerciseHistoryEntry) ?? [] }),
2438
- legacyJson: ({ exerciseHistory }) => exerciseHistory,
2439
- text: ({ exerciseTemplateId }, { exerciseHistory }) => exerciseHistory.length === 0 ? `No exercise history found for template ${exerciseTemplateId}` : void 0,
2440
- telemetry: (data) => ({ itemCountBucket: bucketCount(data.history?.length ?? 0) })
2430
+ normalize: (data) => ({ exercise_history: data.exercise_history?.map(normalizeExerciseHistoryEntry) ?? [] }),
2431
+ legacyJson: ({ exercise_history }) => exercise_history,
2432
+ text: ({ exercise_template_id }, { exercise_history }) => exercise_history.length === 0 ? `No exercise history found for template ${exercise_template_id}` : void 0,
2433
+ telemetry: (data) => ({ itemCountBucket: bucketCount(data.exercise_history?.length ?? 0) })
2441
2434
  });
2442
2435
  const searchExerciseTemplatesResponse = defineStructuredResponseContract({
2443
2436
  outputSchema: exerciseTemplateSearchOutputSchema,
2444
- normalize: (data) => ({ exerciseTemplates: data.results.map(formatExerciseTemplate) }),
2445
- legacyJson: ({ exerciseTemplates }) => exerciseTemplates,
2446
- text: ({ query, primaryMuscleGroup }, { exerciseTemplates }) => exerciseTemplates.length === 0 ? `No exercise templates found matching "${query}"${primaryMuscleGroup ? ` with primary muscle group "${primaryMuscleGroup}"` : ""}` : void 0,
2437
+ normalize: (data) => ({ exercise_templates: data.results }),
2438
+ legacyJson: ({ exercise_templates }) => exercise_templates,
2439
+ text: ({ query, primary_muscle_group }, { exercise_templates }) => exercise_templates.length === 0 ? `No exercise templates found matching "${query}"${primary_muscle_group ? ` with primary muscle group "${primary_muscle_group}"` : ""}` : void 0,
2447
2440
  telemetry: (data) => ({ itemCountBucket: bucketCount(data.results.length) })
2448
2441
  });
2449
2442
  const routineFoldersResponse = defineStructuredResponseContract({
@@ -2451,14 +2444,14 @@ const routineFoldersResponse = defineStructuredResponseContract({
2451
2444
  normalize: (input) => {
2452
2445
  const data = normalizePaginatedInput(input);
2453
2446
  return {
2454
- routineFolders: data.items.map(formatRoutineFolder),
2447
+ routine_folders: data.items.map(projectRoutineFolder),
2455
2448
  page: data.page,
2456
- pageCount: data.pageCount,
2457
- hasNextPage: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2449
+ page_count: data.pageCount,
2450
+ has_next_page: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2458
2451
  };
2459
2452
  },
2460
- legacyJson: ({ routineFolders }) => routineFolders,
2461
- text: (_data, { routineFolders }) => routineFolders.length === 0 ? "No routine folders found for the specified parameters" : void 0,
2453
+ legacyJson: ({ routine_folders }) => routine_folders,
2454
+ text: (_data, { routine_folders }) => routine_folders.length === 0 ? "No routine folders found for the specified parameters" : void 0,
2462
2455
  telemetry: (folders) => ({
2463
2456
  itemCountBucket: bucketCount(normalizePaginatedInput(folders).items.length),
2464
2457
  expected404Outcome: normalizePaginatedInput(folders).expected404Outcome
@@ -2466,11 +2459,11 @@ const routineFoldersResponse = defineStructuredResponseContract({
2466
2459
  });
2467
2460
  const routineFolderResponse = defineStructuredResponseContract({
2468
2461
  outputSchema: routineFolderOutputSchema,
2469
- normalize: (data) => ({ routineFolder: data.routineFolder ? formatRoutineFolder(data.routineFolder) : null }),
2470
- legacyJson: ({ routineFolder }) => routineFolder,
2471
- text: ({ folderId }, { routineFolder }) => routineFolder === null ? `Routine folder with ID ${folderId} not found` : void 0,
2472
- telemetry: ({ routineFolder, expected404Outcome }) => ({
2473
- itemCountBucket: bucketCount(routineFolder ? 1 : 0),
2462
+ normalize: (data) => ({ routine_folder: data.routine_folder ? projectRoutineFolder(data.routine_folder) : null }),
2463
+ legacyJson: ({ routine_folder }) => routine_folder,
2464
+ text: ({ folder_id }, { routine_folder }) => routine_folder === null ? `Routine folder with ID ${folder_id} not found` : void 0,
2465
+ telemetry: ({ routine_folder, expected404Outcome }) => ({
2466
+ itemCountBucket: bucketCount(routine_folder ? 1 : 0),
2474
2467
  expected404Outcome
2475
2468
  })
2476
2469
  });
@@ -2479,14 +2472,14 @@ const bodyMeasurementsResponse = defineStructuredResponseContract({
2479
2472
  normalize: (input) => {
2480
2473
  const data = normalizePaginatedInput(input);
2481
2474
  return {
2482
- bodyMeasurements: data.items.map(formatBodyMeasurement),
2475
+ body_measurements: data.items.map(normalizeBodyMeasurement),
2483
2476
  page: data.page,
2484
- pageCount: data.pageCount,
2485
- hasNextPage: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2477
+ page_count: data.pageCount,
2478
+ has_next_page: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2486
2479
  };
2487
2480
  },
2488
- legacyJson: ({ bodyMeasurements }) => bodyMeasurements,
2489
- text: (_data, { bodyMeasurements }) => bodyMeasurements.length === 0 ? "No body measurements found for the specified parameters" : void 0,
2481
+ legacyJson: ({ body_measurements }) => body_measurements,
2482
+ text: (_data, { body_measurements }) => body_measurements.length === 0 ? "No body measurements found for the specified parameters" : void 0,
2490
2483
  telemetry: (measurements) => ({
2491
2484
  itemCountBucket: bucketCount(normalizePaginatedInput(measurements).items.length),
2492
2485
  expected404Outcome: normalizePaginatedInput(measurements).expected404Outcome
@@ -2494,11 +2487,11 @@ const bodyMeasurementsResponse = defineStructuredResponseContract({
2494
2487
  });
2495
2488
  const bodyMeasurementResponse = defineStructuredResponseContract({
2496
2489
  outputSchema: bodyMeasurementOutputSchema,
2497
- normalize: (data) => ({ bodyMeasurement: data.bodyMeasurement ? formatBodyMeasurement(data.bodyMeasurement) : null }),
2498
- legacyJson: ({ bodyMeasurement }) => bodyMeasurement,
2499
- text: ({ date }, { bodyMeasurement }) => bodyMeasurement === null ? `No body measurement found for date ${date}` : void 0,
2500
- telemetry: ({ bodyMeasurement, expected404Outcome }) => ({
2501
- itemCountBucket: bucketCount(bodyMeasurement ? 1 : 0),
2490
+ normalize: (data) => ({ body_measurement: data.body_measurement ? normalizeBodyMeasurement(data.body_measurement) : null }),
2491
+ legacyJson: ({ body_measurement }) => body_measurement,
2492
+ text: ({ date }, { body_measurement }) => body_measurement === null ? `No body measurement found for date ${date}` : void 0,
2493
+ telemetry: ({ body_measurement, expected404Outcome }) => ({
2494
+ itemCountBucket: bucketCount(body_measurement ? 1 : 0),
2502
2495
  expected404Outcome
2503
2496
  })
2504
2497
  });
@@ -2511,17 +2504,46 @@ const userResponse = defineStructuredResponseContract({
2511
2504
  });
2512
2505
  const trainingSummaryResponse = defineStructuredResponseContract({
2513
2506
  outputSchema: trainingSummaryOutputSchema,
2514
- normalize: (data) => data,
2507
+ normalize: (data) => {
2508
+ const { workflow, ...result } = data;
2509
+ const { latest, earliest, weight_change_kg, ...body_measurements } = result.body_measurements;
2510
+ return {
2511
+ ...result,
2512
+ body_measurements: {
2513
+ ...body_measurements,
2514
+ ...latest ? { latest } : {},
2515
+ ...earliest ? { earliest } : {},
2516
+ ...weight_change_kg == null ? {} : { weight_change_kg }
2517
+ },
2518
+ scan: {
2519
+ pages: workflow.pagination,
2520
+ items: workflow.itemsScanned
2521
+ }
2522
+ };
2523
+ },
2515
2524
  legacyJson: (output) => output,
2516
- text: (data) => data.workouts.count === 0 && data.bodyMeasurements.count === 0 ? "No workouts or body measurements found for the specified period" : void 0,
2525
+ text: (data) => data.workouts.count === 0 && data.body_measurements.count === 0 ? "No workouts or body measurements found for the specified period" : void 0,
2517
2526
  telemetry: (data) => ({
2518
- itemCountBucket: bucketCount(data.workouts.count + data.bodyMeasurements.count),
2527
+ itemCountBucket: bucketCount(data.workouts.count + data.body_measurements.count),
2519
2528
  workflow: workflowResultTelemetry(data.workflow)
2520
2529
  })
2521
2530
  });
2522
2531
  const compactRoutinesResponse = defineStructuredResponseContract({
2523
2532
  outputSchema: compactRoutinesOutputSchema,
2524
- normalize: (data) => data,
2533
+ normalize: (data) => {
2534
+ const { workflow, ...result } = data;
2535
+ return {
2536
+ ...result,
2537
+ routines: result.routines.map((routine) => ({
2538
+ ...routine,
2539
+ ...routine.folder_id == null ? { folder_id: void 0 } : {}
2540
+ })),
2541
+ scan: {
2542
+ pages: workflow.pagination,
2543
+ items: workflow.itemsScanned
2544
+ }
2545
+ };
2546
+ },
2525
2547
  legacyJson: ({ routines }) => routines,
2526
2548
  text: (_data, { routines }) => routines.length === 0 ? "No routines found matching the query" : void 0,
2527
2549
  telemetry: (data) => ({
@@ -2529,22 +2551,22 @@ const compactRoutinesResponse = defineStructuredResponseContract({
2529
2551
  workflow: workflowResultTelemetry(data.workflow)
2530
2552
  })
2531
2553
  });
2532
- const createWorkoutResponse = defineJsonResponseContract((workout) => workout ? { json: formatWorkout(workout) } : { text: "Failed to create workout: Server returned no data" }, (workout) => workoutResultTelemetry(workout));
2533
- const updateWorkoutResponse = defineJsonResponseContract((data) => data.workout ? { json: formatWorkout(data.workout) } : { text: `Failed to update workout with ID ${data.workoutId}` }, (data) => workoutResultTelemetry(data.workout));
2554
+ const createWorkoutResponse = defineJsonResponseContract((workout) => workout ? { json: projectWorkout(workout) } : { text: "Failed to create workout: Server returned no data" }, (workout) => workoutResultTelemetry(workout));
2555
+ const updateWorkoutResponse = defineJsonResponseContract((data) => data.workout ? { json: projectWorkout(data.workout) } : { text: `Failed to update workout with ID ${data.workout_id}` }, (data) => workoutResultTelemetry(data.workout));
2534
2556
  const repRangeDisplayWarningText = "Note: Hevy's public API stores rep ranges (rep_range), but the Hevy apps may not display them because they rely on an internal-only exercise field (input_modifier). See https://github.com/chrisdoc/hevy-mcp/issues/261 for details/workarounds.";
2535
2557
  const createRoutineResponse = defineJsonResponseContract((data) => data.routine ? {
2536
- json: formatRoutine(data.routine),
2558
+ json: projectRoutine(data.routine),
2537
2559
  additionalText: data.usesRepRanges ? [repRangeDisplayWarningText] : []
2538
2560
  } : { text: "Failed to create routine: Server returned no data" }, (data) => routineResultTelemetry(data.routine));
2539
2561
  const updateRoutineResponse = defineJsonResponseContract((data) => data.routine ? {
2540
- json: formatRoutine(data.routine),
2562
+ json: projectRoutine(data.routine),
2541
2563
  additionalText: data.usesRepRanges ? [repRangeDisplayWarningText] : []
2542
- } : { text: `Failed to update routine with ID ${data.routineId}` }, (data) => routineResultTelemetry(data.routine));
2564
+ } : { text: `Failed to update routine with ID ${data.routine_id}` }, (data) => routineResultTelemetry(data.routine));
2543
2565
  const createExerciseTemplateResponse = defineJsonResponseContract((response) => ({ json: {
2544
2566
  id: response?.id,
2545
2567
  message: "Exercise template created successfully"
2546
2568
  } }));
2547
- const createRoutineFolderResponse = defineJsonResponseContract((folder) => folder ? { json: formatRoutineFolder(folder) } : { text: "Failed to create routine folder: Server returned no data" }, (folder) => ({ itemCountBucket: bucketCount(folder ? 1 : 0) }));
2569
+ const createRoutineFolderResponse = defineJsonResponseContract((folder) => folder ? { json: projectRoutineFolder(folder) } : { text: "Failed to create routine folder: Server returned no data" }, (folder) => ({ itemCountBucket: bucketCount(folder ? 1 : 0) }));
2548
2570
  const createBodyMeasurementResponse = defineJsonResponseContract((date) => ({ text: `Body measurement for ${date} created successfully.` }), () => ({ itemCountBucket: "1" }));
2549
2571
  const updateBodyMeasurementResponse = defineJsonResponseContract((date) => ({ text: `Body measurement for ${date} updated successfully.` }), () => ({ itemCountBucket: "1" }));
2550
2572
  //#endregion
@@ -2581,33 +2603,103 @@ function registerHevyResources(server, runtime) {
2581
2603
  description: "Total number of workouts in the Hevy account",
2582
2604
  mimeType: JSON_MIME_TYPE
2583
2605
  }, async (uri) => {
2584
- return createJsonResourceResult(uri, { count: (await runtime.getClient().getWorkoutCount())?.workout_count ?? 0 });
2606
+ return createJsonResourceResult(uri, { workout_count: (await runtime.getClient().getWorkoutCount())?.workout_count ?? 0 });
2585
2607
  });
2586
2608
  server.registerResource("exercise-templates", "hevy://exercise-templates", {
2587
2609
  description: "Full formatted Hevy exercise template catalog",
2588
2610
  mimeType: JSON_MIME_TYPE
2589
2611
  }, async (uri) => {
2590
- return createJsonResourceResult(uri, (await runtime.catalog.get()).map(formatExerciseTemplate));
2612
+ return createJsonResourceResult(uri, await runtime.catalog.get());
2591
2613
  });
2592
2614
  server.registerResource("routine-folders", "hevy://routine-folders", {
2593
2615
  description: "Full formatted list of Hevy routine folders",
2594
2616
  mimeType: JSON_MIME_TYPE
2595
2617
  }, async (uri) => {
2596
- return createJsonResourceResult(uri, (await fetchAllRoutineFolders(runtime)).map(formatRoutineFolder));
2618
+ return createJsonResourceResult(uri, (await fetchAllRoutineFolders(runtime)).map(projectRoutineFolder));
2597
2619
  });
2598
2620
  }
2599
2621
  //#endregion
2600
2622
  //#region ../core/src/server-metadata.ts
2601
2623
  const SERVER_NAME = "hevy-mcp";
2602
- const SERVER_VERSION = "4.1.3";
2624
+ const SERVER_VERSION = "5.0.0";
2603
2625
  const SERVER_INSTRUCTIONS = [
2604
2626
  "Hevy MCP connects clients to the authenticated user's Hevy workout-tracking data, including workouts, routines, exercise templates, routine folders, body measurements, and profile information. HEVY_API_KEY must contain a valid Hevy API key for local stdio use.",
2605
2627
  "Safety: all get-* and search-* tools are read-only. create-* and update-* tools mutate Hevy data. Creates are additive and non-idempotent, so repeating one can create duplicates. Updates can overwrite existing data. Delete operations are not available.",
2606
2628
  "Workflow: search exercise templates first, then use the returned template IDs when creating workouts or routines. To create a completed workout from a routine, fetch the routine as a plan, then obtain the actual completed sets and end time from the user; never invent completion data.",
2607
- "Pagination: start at page 1 and fetch only the pages needed. Most list tools allow pageSize up to 10; get-exercise-templates allows up to 100.",
2629
+ "Pagination: start at page 1 and fetch only the pages needed. Most list tools allow page_size up to 10; get-exercise-templates allows page_size up to 100.",
2608
2630
  "Rate limits and retries: minimize repeated calls. If Hevy returns HTTP 429, follow its retry guidance. Transient read requests retry automatically, but write requests do not; confirm uncertain write outcomes before trying again."
2609
2631
  ].join("\n\n");
2610
2632
  //#endregion
2633
+ //#region ../core/src/utils/compact-json-schema.ts
2634
+ function isRecord$1(value) {
2635
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2636
+ }
2637
+ function isNullSchema(value) {
2638
+ return isRecord$1(value) && value.type === "null" && Object.keys(value).length === 1;
2639
+ }
2640
+ function compactNullableSchema(value) {
2641
+ if (Array.isArray(value)) return value.map(compactNullableSchema);
2642
+ if (!isRecord$1(value)) return value;
2643
+ const anyOf = value.anyOf;
2644
+ if (Array.isArray(anyOf) && anyOf.length === 2) {
2645
+ const nullableBranch = anyOf.find(isNullSchema);
2646
+ const valueBranch = anyOf.find((branch) => !isNullSchema(branch));
2647
+ if (nullableBranch !== void 0 && isRecord$1(valueBranch) && typeof valueBranch.type === "string") {
2648
+ const { anyOf: _anyOf, ...metadata } = value;
2649
+ return compactNullableSchema({
2650
+ ...valueBranch,
2651
+ ...metadata,
2652
+ type: [valueBranch.type, "null"]
2653
+ });
2654
+ }
2655
+ }
2656
+ return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, compactNullableSchema(nested)]));
2657
+ }
2658
+ function omitOutputObjectRestrictions(value) {
2659
+ if (Array.isArray(value)) return value.map(omitOutputObjectRestrictions);
2660
+ if (!isRecord$1(value)) return value;
2661
+ return Object.fromEntries(Object.entries(value).filter(([key, nested]) => key !== "additionalProperties" || nested !== false).map(([key, nested]) => [key, omitOutputObjectRestrictions(nested)]));
2662
+ }
2663
+ function convertSchema(schema, io) {
2664
+ const converted = z.toJSONSchema(schema, {
2665
+ target: "draft-2020-12",
2666
+ io
2667
+ });
2668
+ if (!isRecord$1(converted)) throw new Error("Zod emitted a non-object root schema");
2669
+ const compacted = compactNullableSchema(converted);
2670
+ if (!isRecord$1(compacted)) throw new Error("Nullable schema compaction changed the root schema");
2671
+ const output = io === "output" ? omitOutputObjectRestrictions(compacted) : compacted;
2672
+ if (!isRecord$1(output)) throw new Error("Output schema compaction changed the root schema");
2673
+ delete output.$schema;
2674
+ return output;
2675
+ }
2676
+ /**
2677
+ * Keep Zod validation while advertising a compact JSON Schema.
2678
+ *
2679
+ * Zod emits nullable values as `anyOf`; draft 2020-12 also permits a type
2680
+ * array. The MCP wire envelope does not need a repeated `$schema` marker, and
2681
+ * structured output is validated by the original Zod schema before sending.
2682
+ */
2683
+ function compactJsonSchema(schema) {
2684
+ let inputSchema;
2685
+ let outputSchema;
2686
+ Object.defineProperty(schema["~standard"], "jsonSchema", {
2687
+ configurable: true,
2688
+ value: {
2689
+ input: () => {
2690
+ inputSchema ??= convertSchema(schema, "input");
2691
+ return inputSchema;
2692
+ },
2693
+ output: () => {
2694
+ outputSchema ??= convertSchema(schema, "output");
2695
+ return outputSchema;
2696
+ }
2697
+ },
2698
+ writable: true
2699
+ });
2700
+ return schema;
2701
+ }
2702
+ //#endregion
2611
2703
  //#region ../core/src/utils/tool-helpers.ts
2612
2704
  /**
2613
2705
  * Type-safe utilities for MCP tool handlers
@@ -2645,7 +2737,7 @@ function requireClient(client) {
2645
2737
  * ```
2646
2738
  */
2647
2739
  function createTypedToolHandler(schema, handler) {
2648
- const zodSchema = z.object(schema);
2740
+ const zodSchema = z.strictObject(schema);
2649
2741
  return async (args) => {
2650
2742
  return handler(zodSchema.parse(args));
2651
2743
  };
@@ -2659,11 +2751,12 @@ function registerToolDefinition(server, runtime, definition) {
2659
2751
  kind: definition.kind,
2660
2752
  operation: definition.operation
2661
2753
  });
2754
+ const inputSchema = compactJsonSchema(z.strictObject(definition.inputSchema));
2662
2755
  const config = {
2663
2756
  description: definition.description,
2664
- inputSchema: z.object(definition.inputSchema),
2757
+ inputSchema,
2665
2758
  annotations: definition.annotations,
2666
- ...definition.kind === "read" ? { outputSchema: z.object(definition.outputSchema) } : {}
2759
+ ...definition.kind === "read" ? { outputSchema: compactJsonSchema(z.object(definition.outputSchema)) } : {}
2667
2760
  };
2668
2761
  server.registerTool(definition.name, config, callback);
2669
2762
  }
@@ -2701,16 +2794,6 @@ function updateAnnotations(title) {
2701
2794
  };
2702
2795
  }
2703
2796
  //#endregion
2704
- //#region ../core/src/utils/tool-descriptions.ts
2705
- function describeTool({ summary, aliases, useCase, importantNotes }) {
2706
- return [
2707
- summary,
2708
- `Aliases: ${aliases.join(", ")}.`,
2709
- `<use_case>${useCase}</use_case>`,
2710
- `<important_notes>${importantNotes}</important_notes>`
2711
- ].join(" ");
2712
- }
2713
- //#endregion
2714
2797
  //#region ../core/src/utils/json-parser.ts
2715
2798
  /**
2716
2799
  * Preprocessor to handle MCP clients that send JSON-stringified arrays
@@ -2732,143 +2815,156 @@ function parseJsonArray(val) {
2732
2815
  }
2733
2816
  //#endregion
2734
2817
  //#region ../core/src/tools/input-schemas.ts
2735
- /** Build the page and pageSize fields shared by paginated tools. */
2818
+ /** Build the page and page_size fields shared by paginated tools. */
2736
2819
  function paginationShape({ defaultPageSize, maxPageSize, integerPage = true }) {
2737
2820
  const pageNumber = z.coerce.number().gte(1);
2738
2821
  return {
2739
2822
  page: integerPage ? pageNumber.int() : pageNumber,
2740
- pageSize: z.coerce.number().int().gte(1).lte(maxPageSize).default(defaultPageSize)
2823
+ page_size: z.coerce.number().int().gte(1).lte(maxPageSize).default(defaultPageSize)
2741
2824
  };
2742
2825
  }
2743
2826
  const nonEmptyId = z.string().min(1);
2744
- const exerciseTemplateInputShape = {
2827
+ const exerciseTemplatePayloadShape = {
2745
2828
  title: z.string().min(1),
2746
- exerciseType: exerciseTypeEnum,
2747
- equipmentCategory: equipmentCategoryEnum,
2748
- muscleGroup: muscleGroupEnum,
2749
- otherMuscles: z.array(muscleGroupEnum).default([])
2829
+ exercise_type: exerciseTypeEnum,
2830
+ equipment_category: equipmentCategoryEnum,
2831
+ muscle_group: muscleGroupEnum,
2832
+ other_muscles: z.array(muscleGroupEnum).default([])
2750
2833
  };
2751
- const routineFolderInputShape = { name: z.string().min(1) };
2834
+ const exerciseTemplateInputShape = z.strictObject({ exercise: z.strictObject(exerciseTemplatePayloadShape) }).shape;
2835
+ const routineFolderPayloadShape = { title: z.string().min(1) };
2836
+ const routineFolderInputShape = z.strictObject({ routine_folder: z.strictObject(routineFolderPayloadShape) }).shape;
2752
2837
  const CALENDAR_DATE_MESSAGE = "Date must be in YYYY-MM-DD format";
2753
2838
  const calendarDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, CALENDAR_DATE_MESSAGE).refine((value) => {
2754
2839
  const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
2755
2840
  return !Number.isNaN(parsed.getTime()) && parsed.toISOString().startsWith(value);
2756
2841
  }, CALENDAR_DATE_MESSAGE);
2842
+ const rpeEnum = z.union([
2843
+ z.literal(6),
2844
+ z.literal(7),
2845
+ z.literal(7.5),
2846
+ z.literal(8),
2847
+ z.literal(8.5),
2848
+ z.literal(9),
2849
+ z.literal(9.5),
2850
+ z.literal(10)
2851
+ ]);
2757
2852
  const workoutSetShape = {
2758
2853
  type: setTypeEnum,
2759
- weight: z.coerce.number().optional().nullable(),
2760
- weightKg: z.coerce.number().optional().nullable(),
2854
+ weight_kg: z.coerce.number().optional().nullable(),
2761
2855
  reps: z.coerce.number().int().optional().nullable(),
2762
- distance: z.coerce.number().int().optional().nullable(),
2763
- distanceMeters: z.coerce.number().int().optional().nullable(),
2764
- duration: z.coerce.number().int().optional().nullable(),
2765
- durationSeconds: z.coerce.number().int().optional().nullable(),
2766
- rpe: z.coerce.number().optional().nullable(),
2767
- customMetric: z.coerce.number().optional().nullable()
2856
+ distance_meters: z.coerce.number().int().optional().nullable(),
2857
+ duration_seconds: z.coerce.number().int().optional().nullable(),
2858
+ rpe: rpeEnum.optional().nullable(),
2859
+ custom_metric: z.coerce.number().optional().nullable()
2768
2860
  };
2861
+ const workoutSetSchema = z.strictObject(workoutSetShape);
2769
2862
  const workoutExerciseShape = {
2770
- exerciseTemplateId: nonEmptyId,
2771
- supersetId: z.coerce.number().nullable().optional(),
2863
+ exercise_template_id: nonEmptyId,
2864
+ superset_id: z.coerce.number().nullable().optional(),
2772
2865
  notes: z.string().optional().nullable(),
2773
- sets: z.array(z.object(workoutSetShape))
2866
+ sets: z.array(workoutSetSchema)
2774
2867
  };
2775
- const workoutPayloadShape = {
2868
+ const workoutExerciseSchema = z.strictObject(workoutExerciseShape);
2869
+ const workoutExercisesSchema = z.preprocess(parseJsonArray, z.array(workoutExerciseSchema));
2870
+ const replaceWorkoutPayloadShape = {
2776
2871
  title: z.string().min(1),
2777
2872
  description: z.string().optional().nullable(),
2778
- startTime: utcSecondTimestamp,
2779
- endTime: utcSecondTimestamp,
2780
- isPrivate: z.boolean().default(false),
2781
- exercises: z.preprocess(parseJsonArray, z.array(z.object(workoutExerciseShape)))
2873
+ start_time: utcSecondTimestamp,
2874
+ end_time: utcSecondTimestamp,
2875
+ is_private: z.boolean().default(false),
2876
+ exercises: workoutExercisesSchema
2782
2877
  };
2878
+ const replaceWorkoutPayloadSchema = z.strictObject(replaceWorkoutPayloadShape);
2879
+ const workoutInputShape = z.strictObject({ workout: replaceWorkoutPayloadSchema }).shape;
2880
+ z.strictObject({
2881
+ workout_id: nonEmptyId,
2882
+ workout: replaceWorkoutPayloadSchema
2883
+ }).shape;
2884
+ const workoutMetadataPatchSchema = z.strictObject({
2885
+ title: z.string().min(1).optional(),
2886
+ description: z.string().nullable().optional(),
2887
+ start_time: utcSecondTimestamp.optional(),
2888
+ end_time: utcSecondTimestamp.optional(),
2889
+ is_private: z.boolean().optional()
2890
+ }).refine((patch) => Object.values(patch).some((value) => value !== void 0), "Include at least one workout metadata field").meta({ minProperties: 1 });
2891
+ const updateWorkoutInputShape = z.strictObject({
2892
+ workout_id: nonEmptyId,
2893
+ workout: workoutMetadataPatchSchema
2894
+ }).shape;
2895
+ const replaceWorkoutExercisesInputShape = z.strictObject({
2896
+ workout_id: nonEmptyId,
2897
+ workout: z.strictObject({ exercises: workoutExercisesSchema })
2898
+ }).shape;
2783
2899
  const routineSetShape = {
2784
2900
  type: setTypeEnum,
2785
- weight: z.coerce.number().optional(),
2786
- weightKg: z.coerce.number().optional(),
2901
+ weight_kg: z.coerce.number().optional(),
2787
2902
  reps: zNullableInt,
2788
- distance: z.coerce.number().int().optional(),
2789
- distanceMeters: z.coerce.number().int().optional(),
2790
- duration: z.coerce.number().int().optional(),
2791
- durationSeconds: z.coerce.number().int().optional(),
2792
- customMetric: z.coerce.number().optional(),
2793
- repRange: zOptionalRepRange
2903
+ distance_meters: z.coerce.number().int().optional(),
2904
+ duration_seconds: z.coerce.number().int().optional(),
2905
+ rep_range: zStrictOptionalRepRange,
2906
+ custom_metric: z.coerce.number().optional()
2794
2907
  };
2908
+ const routineSetSchema = z.strictObject(routineSetShape);
2795
2909
  const routineExerciseShape = {
2796
- exerciseTemplateId: nonEmptyId,
2797
- supersetId: z.coerce.number().nullable().optional(),
2798
- restSeconds: z.coerce.number().int().min(0).optional(),
2910
+ exercise_template_id: nonEmptyId,
2911
+ superset_id: z.coerce.number().nullable().optional(),
2912
+ rest_seconds: z.coerce.number().int().min(0).optional(),
2799
2913
  notes: z.string().optional(),
2800
- sets: z.array(z.object(routineSetShape))
2914
+ sets: z.array(routineSetSchema)
2801
2915
  };
2916
+ const routineExerciseSchema = z.strictObject(routineExerciseShape);
2917
+ const routineExercisesSchema = z.preprocess(parseJsonArray, z.array(routineExerciseSchema));
2802
2918
  const routinePayloadShape = {
2803
2919
  title: z.string().min(1),
2804
- folderId: z.coerce.number().nullable().optional(),
2920
+ folder_id: z.coerce.number().nullable().optional(),
2805
2921
  notes: z.string().optional(),
2806
- exercises: z.preprocess(parseJsonArray, z.array(z.object(routineExerciseShape)))
2922
+ exercises: routineExercisesSchema
2807
2923
  };
2808
- const bodyMeasurementFieldsSchema = {
2809
- weightKg: zNullableNumber.describe("Body weight in kilograms"),
2810
- leanMassKg: zNullableNumber.describe("Lean body mass in kilograms"),
2811
- fatPercent: zNullableNumber.describe("Body fat percentage"),
2812
- neckCm: zNullableNumber.describe("Neck circumference in centimeters"),
2813
- shoulderCm: zNullableNumber.describe("Shoulder circumference in centimeters"),
2814
- chestCm: zNullableNumber.describe("Chest circumference in centimeters"),
2815
- leftBicepCm: zNullableNumber.describe("Left bicep circumference in centimeters"),
2816
- rightBicepCm: zNullableNumber.describe("Right bicep circumference in centimeters"),
2817
- leftForearmCm: zNullableNumber.describe("Left forearm circumference in centimeters"),
2818
- rightForearmCm: zNullableNumber.describe("Right forearm circumference in centimeters"),
2819
- abdomen: zNullableNumber.describe("Abdomen circumference in centimeters"),
2820
- waist: zNullableNumber.describe("Waist circumference in centimeters"),
2821
- hips: zNullableNumber.describe("Hips circumference in centimeters"),
2822
- leftThigh: zNullableNumber.describe("Left thigh circumference in centimeters"),
2823
- rightThigh: zNullableNumber.describe("Right thigh circumference in centimeters"),
2824
- leftCalf: zNullableNumber.describe("Left calf circumference in centimeters"),
2825
- rightCalf: zNullableNumber.describe("Right calf circumference in centimeters")
2924
+ const routinePayloadSchema = z.strictObject(routinePayloadShape);
2925
+ const createRoutineInputShape = z.strictObject({ routine: routinePayloadSchema }).shape;
2926
+ const routineUpdatePayloadShape = {
2927
+ title: z.string().min(1),
2928
+ notes: z.string().optional(),
2929
+ exercises: routineExercisesSchema
2826
2930
  };
2827
- const measurementFieldToApiKey = {
2828
- weightKg: "weight_kg",
2829
- leanMassKg: "lean_mass_kg",
2830
- fatPercent: "fat_percent",
2831
- neckCm: "neck_cm",
2832
- shoulderCm: "shoulder_cm",
2833
- chestCm: "chest_cm",
2834
- leftBicepCm: "left_bicep_cm",
2835
- rightBicepCm: "right_bicep_cm",
2836
- leftForearmCm: "left_forearm_cm",
2837
- rightForearmCm: "right_forearm_cm",
2838
- abdomen: "abdomen",
2839
- waist: "waist",
2840
- hips: "hips",
2841
- leftThigh: "left_thigh",
2842
- rightThigh: "right_thigh",
2843
- leftCalf: "left_calf",
2844
- rightCalf: "right_calf"
2931
+ const routineUpdatePayloadSchema = z.strictObject(routineUpdatePayloadShape);
2932
+ const updateRoutineInputShape = z.strictObject({
2933
+ routine_id: nonEmptyId,
2934
+ routine: routineUpdatePayloadSchema
2935
+ }).shape;
2936
+ const bodyMeasurementFieldsSchema = {
2937
+ weight_kg: zNullableNumber,
2938
+ lean_mass_kg: zNullableNumber,
2939
+ fat_percent: zNullableNumber,
2940
+ neck_cm: zNullableNumber,
2941
+ shoulder_cm: zNullableNumber,
2942
+ chest_cm: zNullableNumber,
2943
+ left_bicep_cm: zNullableNumber,
2944
+ right_bicep_cm: zNullableNumber,
2945
+ left_forearm_cm: zNullableNumber,
2946
+ right_forearm_cm: zNullableNumber,
2947
+ abdomen: zNullableNumber.describe("Circumference in centimeters."),
2948
+ waist: zNullableNumber.describe("Circumference in centimeters."),
2949
+ hips: zNullableNumber.describe("Circumference in centimeters."),
2950
+ left_thigh: zNullableNumber,
2951
+ right_thigh: zNullableNumber,
2952
+ left_calf: zNullableNumber,
2953
+ right_calf: zNullableNumber
2845
2954
  };
2955
+ z.strictObject(bodyMeasurementFieldsSchema);
2956
+ const createBodyMeasurementInputSchema = z.strictObject({
2957
+ date: calendarDate,
2958
+ ...bodyMeasurementFieldsSchema
2959
+ });
2960
+ const updateBodyMeasurementInputSchema = z.strictObject({
2961
+ date: calendarDate,
2962
+ ...bodyMeasurementFieldsSchema
2963
+ });
2964
+ const createBodyMeasurementInputShape = createBodyMeasurementInputSchema.shape;
2965
+ const updateBodyMeasurementInputShape = updateBodyMeasurementInputSchema.shape;
2846
2966
  //#endregion
2847
- //#region ../core/src/tools/payload-mappers.ts
2848
- /** Map the public camelCase workout input to the API's snake_case payload. */
2849
- function buildWorkoutPayload(input) {
2850
- return {
2851
- title: input.title,
2852
- description: input.description ?? null,
2853
- start_time: input.startTime,
2854
- end_time: input.endTime,
2855
- is_private: input.isPrivate,
2856
- exercises: input.exercises.map((exercise) => ({
2857
- exercise_template_id: exercise.exerciseTemplateId,
2858
- superset_id: exercise.supersetId ?? null,
2859
- notes: exercise.notes ?? null,
2860
- sets: exercise.sets.map((set) => ({
2861
- type: set.type,
2862
- weight_kg: set.weight ?? set.weightKg ?? null,
2863
- reps: set.reps ?? null,
2864
- distance_meters: set.distance ?? set.distanceMeters ?? null,
2865
- duration_seconds: set.duration ?? set.durationSeconds ?? null,
2866
- rpe: set.rpe ?? null,
2867
- custom_metric: set.customMetric ?? null
2868
- }))
2869
- }))
2870
- };
2871
- }
2967
+ //#region ../core/src/tools/mutation-semantics.ts
2872
2968
  function buildRepRange(repRange) {
2873
2969
  if (!repRange) return null;
2874
2970
  const start = repRange.start ?? void 0;
@@ -2888,14 +2984,14 @@ function getFixedRepsFromRepRange(repRange) {
2888
2984
  }
2889
2985
  function buildRoutineSets(sets, mode) {
2890
2986
  return sets.map((set) => {
2891
- const repRange = buildRepRange(set.repRange);
2987
+ const repRange = buildRepRange(set.rep_range);
2892
2988
  const reps = typeof set.reps === "number" ? set.reps : getFixedRepsFromRepRange(repRange);
2893
2989
  const common = {
2894
- weight_kg: set.weight ?? set.weightKg ?? null,
2990
+ weight_kg: set.weight_kg ?? null,
2895
2991
  reps: reps ?? null,
2896
- distance_meters: set.distance ?? set.distanceMeters ?? null,
2897
- duration_seconds: set.duration ?? set.durationSeconds ?? null,
2898
- custom_metric: set.customMetric ?? null
2992
+ distance_meters: set.distance_meters ?? null,
2993
+ duration_seconds: set.duration_seconds ?? null,
2994
+ custom_metric: set.custom_metric ?? null
2899
2995
  };
2900
2996
  if (mode === "create") return {
2901
2997
  ...common,
@@ -2915,9 +3011,9 @@ function buildRoutinePayload(input, mode) {
2915
3011
  const sets = buildRoutineSets(exercise.sets, mode);
2916
3012
  if (sets.some((set) => set.rep_range != null && getFixedRepsFromRepRange(set.rep_range) === null)) usesRepRanges = true;
2917
3013
  return {
2918
- exercise_template_id: exercise.exerciseTemplateId,
2919
- superset_id: exercise.supersetId ?? null,
2920
- rest_seconds: exercise.restSeconds ?? null,
3014
+ exercise_template_id: exercise.exercise_template_id,
3015
+ superset_id: exercise.superset_id ?? null,
3016
+ rest_seconds: exercise.rest_seconds ?? null,
2921
3017
  notes: exercise.notes ?? null,
2922
3018
  sets
2923
3019
  };
@@ -2925,7 +3021,7 @@ function buildRoutinePayload(input, mode) {
2925
3021
  if (mode === "create") return {
2926
3022
  payload: {
2927
3023
  title: input.title,
2928
- folder_id: input.folderId ?? null,
3024
+ folder_id: input.folder_id ?? null,
2929
3025
  notes: input.notes ?? "",
2930
3026
  exercises
2931
3027
  },
@@ -2940,27 +3036,71 @@ function buildRoutinePayload(input, mode) {
2940
3036
  usesRepRanges
2941
3037
  };
2942
3038
  }
3039
+ const workoutUpdateMetadataSchema = z.object({
3040
+ title: z.string().min(1),
3041
+ start_time: utcSecondTimestamp,
3042
+ end_time: utcSecondTimestamp
3043
+ });
3044
+ /**
3045
+ * Map fetched API exercises to the update shape without validating legacy data.
3046
+ * Caller-supplied replacement exercises are still validated by their input schema.
3047
+ */
3048
+ function preserveWorkoutExercises(current) {
3049
+ return current.exercises?.map((exercise) => ({
3050
+ exercise_template_id: exercise.exercise_template_id,
3051
+ superset_id: exercise.supersets_id ?? null,
3052
+ notes: exercise.notes ?? null,
3053
+ sets: exercise.sets?.map((set) => ({
3054
+ ...set.type === void 0 ? {} : { type: set.type },
3055
+ weight_kg: set.weight_kg ?? null,
3056
+ reps: set.reps ?? null,
3057
+ distance_meters: set.distance_meters ?? null,
3058
+ duration_seconds: set.duration_seconds ?? null,
3059
+ rpe: set.rpe,
3060
+ custom_metric: set.custom_metric ?? null
3061
+ })) ?? []
3062
+ })) ?? [];
3063
+ }
3064
+ function buildWorkoutUpdatePayload(current, patch, replacementExercises) {
3065
+ return {
3066
+ ...workoutUpdateMetadataSchema.parse({
3067
+ title: patch.title !== void 0 ? patch.title : current.title,
3068
+ start_time: patch.start_time !== void 0 ? patch.start_time : current.start_time,
3069
+ end_time: patch.end_time !== void 0 ? patch.end_time : current.end_time
3070
+ }),
3071
+ description: patch.description !== void 0 ? patch.description : current.description ?? null,
3072
+ exercises: replacementExercises === void 0 ? preserveWorkoutExercises(current) : replacementExercises,
3073
+ ...patch.is_private !== void 0 ? { is_private: patch.is_private } : {}
3074
+ };
3075
+ }
3076
+ const measurementKeys = [
3077
+ "weight_kg",
3078
+ "lean_mass_kg",
3079
+ "fat_percent",
3080
+ "neck_cm",
3081
+ "shoulder_cm",
3082
+ "chest_cm",
3083
+ "left_bicep_cm",
3084
+ "right_bicep_cm",
3085
+ "left_forearm_cm",
3086
+ "right_forearm_cm",
3087
+ "abdomen",
3088
+ "waist",
3089
+ "hips",
3090
+ "left_thigh",
3091
+ "right_thigh",
3092
+ "left_calf",
3093
+ "right_calf"
3094
+ ];
2943
3095
  /** Omit nullish measurement values because the API rejects explicit nulls. */
2944
3096
  function buildMeasurementPayload(fields) {
2945
3097
  const payload = {};
2946
- for (const [camelKey, apiKey] of Object.entries(measurementFieldToApiKey)) {
2947
- const value = fields[camelKey];
2948
- if (value != null) payload[apiKey] = value;
3098
+ for (const key of measurementKeys) {
3099
+ const value = fields[key];
3100
+ if (value != null) payload[key] = value;
2949
3101
  }
2950
3102
  return payload;
2951
3103
  }
2952
- function buildExerciseTemplateRequest(input) {
2953
- return { exercise: {
2954
- title: input.title,
2955
- exercise_type: input.exerciseType,
2956
- equipment_category: input.equipmentCategory,
2957
- muscle_group: input.muscleGroup,
2958
- other_muscles: input.otherMuscles
2959
- } };
2960
- }
2961
- function buildRoutineFolderRequest(input) {
2962
- return { routine_folder: { title: input.name } };
2963
- }
2964
3104
  //#endregion
2965
3105
  //#region ../core/src/utils/hevy-error-policy.ts
2966
3106
  const READ_RESOURCE_ENDPOINTS = /* @__PURE__ */ new Set([
@@ -2992,41 +3132,26 @@ const getBodyMeasurementsSchema = { ...paginationShape({
2992
3132
  defaultPageSize: 10,
2993
3133
  maxPageSize: 10
2994
3134
  }) };
2995
- const getBodyMeasurementSchema = { date: calendarDate.describe("The date of the body measurement (YYYY-MM-DD)") };
2996
- const createBodyMeasurementSchema = {
2997
- date: calendarDate.describe("The date of the body measurement (YYYY-MM-DD). Must be unique — returns 409 if an entry already exists for this date."),
2998
- ...bodyMeasurementFieldsSchema
2999
- };
3000
- const updateBodyMeasurementSchema = {
3001
- date: calendarDate.describe("The date of the body measurement to update (YYYY-MM-DD). Must already exist — returns 404 otherwise."),
3002
- ...bodyMeasurementFieldsSchema
3003
- };
3135
+ const getBodyMeasurementSchema = { date: calendarDate };
3136
+ const createBodyMeasurementSchema = createBodyMeasurementInputShape;
3137
+ const updateBodyMeasurementSchema = updateBodyMeasurementInputShape;
3004
3138
  const bodyMeasurementToolDefinitions = [
3005
3139
  {
3006
3140
  name: "get-body-measurements",
3007
3141
  feature: "measurements",
3008
3142
  operation: "list",
3009
- description: describeTool({
3010
- summary: "Read-only. Lists dated body measurements for the account.",
3011
- aliases: [
3012
- "body stats history",
3013
- "list weigh-ins",
3014
- "measurement log"
3015
- ],
3016
- useCase: "Use to browse measurement history; use get-body-measurement for one exact date.",
3017
- importantNotes: "Results are paginated; page starts at 1 and pageSize is limited to 10."
3018
- }),
3143
+ description: "Read-only. Lists dated body measurements; results are paginated. Use get-body-measurement for one date.",
3019
3144
  inputSchema: getBodyMeasurementsSchema,
3020
3145
  outputSchema: bodyMeasurementsResponse.outputSchema,
3021
3146
  annotations: readOnlyAnnotations("Get Body Measurements"),
3022
3147
  kind: "read",
3023
3148
  responseContract: bodyMeasurementsResponse,
3024
3149
  execute: async (runtime, args) => {
3025
- const { page, pageSize } = args;
3150
+ const { page, page_size } = args;
3026
3151
  try {
3027
3152
  const data = await runtime.getClient().getBodyMeasurements({
3028
3153
  page,
3029
- pageSize
3154
+ pageSize: page_size
3030
3155
  });
3031
3156
  return {
3032
3157
  items: data?.body_measurements ?? [],
@@ -3047,16 +3172,7 @@ const bodyMeasurementToolDefinitions = [
3047
3172
  name: "get-body-measurement",
3048
3173
  feature: "measurements",
3049
3174
  operation: "get",
3050
- description: describeTool({
3051
- summary: "Read-only. Retrieves the body measurement entry for one date.",
3052
- aliases: [
3053
- "get weigh-in",
3054
- "show body stats",
3055
- "measurement by date"
3056
- ],
3057
- useCase: "Use when the exact measurement date is known; use get-body-measurements to browse dates.",
3058
- importantNotes: "date must use YYYY-MM-DD; at most one entry exists per date."
3059
- }),
3175
+ description: "Read-only. Gets the body measurement for one YYYY-MM-DD date. Use get-body-measurements to browse dates.",
3060
3176
  inputSchema: getBodyMeasurementSchema,
3061
3177
  outputSchema: bodyMeasurementResponse.outputSchema,
3062
3178
  annotations: readOnlyAnnotations("Get Body Measurement"),
@@ -3066,12 +3182,12 @@ const bodyMeasurementToolDefinitions = [
3066
3182
  const { date } = args;
3067
3183
  try {
3068
3184
  return {
3069
- bodyMeasurement: await runtime.getClient().getBodyMeasurement(date),
3185
+ body_measurement: await runtime.getClient().getBodyMeasurement(date),
3070
3186
  date
3071
3187
  };
3072
3188
  } catch (error) {
3073
3189
  if (isExpectedReadNotFound(error)) return {
3074
- bodyMeasurement: null,
3190
+ body_measurement: null,
3075
3191
  date,
3076
3192
  expected404Outcome: "not_found"
3077
3193
  };
@@ -3083,16 +3199,7 @@ const bodyMeasurementToolDefinitions = [
3083
3199
  name: "create-body-measurement",
3084
3200
  feature: "measurements",
3085
3201
  operation: "create",
3086
- description: describeTool({
3087
- summary: "Writes to the Hevy account by creating a dated body measurement.",
3088
- aliases: [
3089
- "log weigh-in",
3090
- "add body stats",
3091
- "record measurements"
3092
- ],
3093
- useCase: "Use for a date without an entry; use update-body-measurement when that date already exists.",
3094
- importantNotes: "date must use YYYY-MM-DD and be unique. Null fields are omitted and cannot clear values; an existing date returns 409."
3095
- }),
3202
+ description: "Writes a body measurement for a new YYYY-MM-DD date. Existing dates conflict; retries are not idempotent.",
3096
3203
  inputSchema: createBodyMeasurementSchema,
3097
3204
  annotations: createAnnotations("Create Body Measurement"),
3098
3205
  kind: "write",
@@ -3110,16 +3217,7 @@ const bodyMeasurementToolDefinitions = [
3110
3217
  name: "update-body-measurement",
3111
3218
  feature: "measurements",
3112
3219
  operation: "update",
3113
- description: describeTool({
3114
- summary: "Mutates the Hevy account by updating a body measurement for a date.",
3115
- aliases: [
3116
- "edit weigh-in",
3117
- "correct body stats",
3118
- "change measurements"
3119
- ],
3120
- useCase: "Use to change fields on an existing date; use create-body-measurement for a new date.",
3121
- importantNotes: "date must use YYYY-MM-DD and already exist. Provide at least one numeric field; nulls are omitted and cannot clear stored values."
3122
- }),
3220
+ description: "Mutates numeric fields on an existing YYYY-MM-DD measurement. Omitted fields remain unchanged; values cannot be cleared.",
3123
3221
  inputSchema: updateBodyMeasurementSchema,
3124
3222
  annotations: updateAnnotations("Update Body Measurement"),
3125
3223
  kind: "write",
@@ -3127,7 +3225,7 @@ const bodyMeasurementToolDefinitions = [
3127
3225
  execute: async (runtime, args) => {
3128
3226
  const { date, ...fields } = args;
3129
3227
  const payload = buildMeasurementPayload(fields);
3130
- if (Object.keys(payload).length === 0) throw new Error("No measurement fields provided. Include at least one numeric measurement field (e.g. weightKg) to update.");
3228
+ if (Object.keys(payload).length === 0) throw new Error("No measurement fields provided. Include at least one numeric measurement field (e.g. weight_kg) to update.");
3131
3229
  await runtime.getClient().updateBodyMeasurement(date, payload);
3132
3230
  return date;
3133
3231
  }
@@ -3139,34 +3237,25 @@ const getRoutineFoldersSchema = paginationShape({
3139
3237
  defaultPageSize: 5,
3140
3238
  maxPageSize: 10
3141
3239
  });
3142
- const getRoutineFolderSchema = { folderId: nonEmptyId };
3240
+ const getRoutineFolderSchema = { folder_id: nonEmptyId };
3143
3241
  const createRoutineFolderSchema = routineFolderInputShape;
3144
3242
  const folderToolDefinitions = [
3145
3243
  {
3146
3244
  name: "get-routine-folders",
3147
3245
  feature: "folders",
3148
3246
  operation: "list",
3149
- description: describeTool({
3150
- summary: "Read-only. Lists default and custom routine folders.",
3151
- aliases: [
3152
- "list folders",
3153
- "browse routine groups",
3154
- "show plan folders"
3155
- ],
3156
- useCase: "Use to browse folder organization or discover folder IDs for routine creation.",
3157
- importantNotes: "Results are paginated; page starts at 1 and pageSize is limited to 10."
3158
- }),
3247
+ description: "Read-only. Lists routine folders and IDs; results are paginated.",
3159
3248
  inputSchema: getRoutineFoldersSchema,
3160
3249
  outputSchema: routineFoldersResponse.outputSchema,
3161
3250
  annotations: readOnlyAnnotations("Get Routine Folders"),
3162
3251
  kind: "read",
3163
3252
  responseContract: routineFoldersResponse,
3164
3253
  execute: async (runtime, args) => {
3165
- const { page, pageSize } = args;
3254
+ const { page, page_size } = args;
3166
3255
  try {
3167
3256
  const data = await runtime.getClient().getRoutineFolders({
3168
3257
  page,
3169
- pageSize
3258
+ pageSize: page_size
3170
3259
  });
3171
3260
  return {
3172
3261
  items: data?.routine_folders ?? [],
@@ -3187,32 +3276,23 @@ const folderToolDefinitions = [
3187
3276
  name: "get-routine-folder",
3188
3277
  feature: "folders",
3189
3278
  operation: "get",
3190
- description: describeTool({
3191
- summary: "Read-only. Retrieves one routine folder's metadata by ID.",
3192
- aliases: [
3193
- "show folder",
3194
- "folder details",
3195
- "routine folder metadata"
3196
- ],
3197
- useCase: "Use for one known folder; use get-routine-folders to browse or discover folder IDs.",
3198
- importantNotes: "Requires a folderId from get-routine-folders or a prior create response."
3199
- }),
3279
+ description: "Read-only. Gets one routine folder by folder_id. Use get-routine-folders to discover IDs.",
3200
3280
  inputSchema: getRoutineFolderSchema,
3201
3281
  outputSchema: routineFolderResponse.outputSchema,
3202
3282
  annotations: readOnlyAnnotations("Get Routine Folder"),
3203
3283
  kind: "read",
3204
3284
  responseContract: routineFolderResponse,
3205
3285
  execute: async (runtime, args) => {
3206
- const { folderId } = args;
3286
+ const { folder_id } = args;
3207
3287
  try {
3208
3288
  return {
3209
- routineFolder: await runtime.getClient().getRoutineFolder(folderId),
3210
- folderId
3289
+ routine_folder: await runtime.getClient().getRoutineFolder(folder_id),
3290
+ folder_id
3211
3291
  };
3212
3292
  } catch (error) {
3213
3293
  if (isExpectedReadNotFound(error)) return {
3214
- routineFolder: null,
3215
- folderId,
3294
+ routine_folder: null,
3295
+ folder_id,
3216
3296
  expected404Outcome: "not_found"
3217
3297
  };
3218
3298
  throw error;
@@ -3223,169 +3303,109 @@ const folderToolDefinitions = [
3223
3303
  name: "create-routine-folder",
3224
3304
  feature: "folders",
3225
3305
  operation: "create",
3226
- description: describeTool({
3227
- summary: "Writes to the Hevy account by creating a new routine folder.",
3228
- aliases: [
3229
- "add folder",
3230
- "organize routines",
3231
- "create plan group"
3232
- ],
3233
- useCase: "Use to create an organizational destination before assigning new routines to a folderId.",
3234
- importantNotes: "Requires a non-empty name. Retrying or reusing a name can create duplicate folders."
3235
- }),
3306
+ description: "Writes a routine folder. Retries or reused titles can create duplicates.",
3236
3307
  inputSchema: createRoutineFolderSchema,
3237
3308
  annotations: createAnnotations("Create Routine Folder"),
3238
3309
  kind: "write",
3239
3310
  responseContract: createRoutineFolderResponse,
3240
3311
  execute: async (runtime, args) => {
3241
- return runtime.getClient().createRoutineFolder(buildRoutineFolderRequest(args));
3312
+ return runtime.getClient().createRoutineFolder(args);
3242
3313
  }
3243
3314
  }
3244
3315
  ];
3245
- //#endregion
3246
- //#region ../core/src/tools/routines.ts
3247
- const getRoutinesSchema = paginationShape({
3248
- defaultPageSize: 5,
3249
- maxPageSize: 10
3250
- });
3251
- const getRoutinesDefinition = {
3252
- name: "get-routines",
3253
- feature: "routines",
3254
- operation: "list",
3255
- description: describeTool({
3256
- summary: "Read-only. Lists custom and default workout routines.",
3257
- aliases: [
3258
- "list routines",
3259
- "show workout plans",
3260
- "browse saved routines"
3261
- ],
3262
- useCase: "Use to browse routines or discover a routine ID; use get-routine for one known routine.",
3263
- importantNotes: "Results are paginated; page starts at 1 and pageSize is limited to 10."
3264
- }),
3265
- inputSchema: getRoutinesSchema,
3266
- kind: "read",
3267
- outputSchema: routinesResponse.outputSchema,
3268
- annotations: readOnlyAnnotations("Get Routines"),
3269
- responseContract: routinesResponse,
3270
- execute: async (runtime, { page, pageSize }) => {
3271
- try {
3272
- const data = await runtime.getClient().getRoutines({
3273
- page,
3274
- pageSize
3275
- });
3276
- return {
3277
- items: data?.routines ?? [],
3278
- page,
3279
- pageCount: data?.page_count
3280
- };
3281
- } catch (error) {
3282
- if (isExpectedListPageNotFound(error, page)) return {
3283
- items: [],
3284
- page,
3285
- expected404Outcome: "end_of_list"
3286
- };
3287
- throw error;
3316
+ const routineToolDefinitions = [
3317
+ {
3318
+ name: "get-routines",
3319
+ feature: "routines",
3320
+ operation: "list",
3321
+ description: "Read-only. Lists compact routine summaries. Use get-routine for exercises and sets; results are paginated.",
3322
+ inputSchema: paginationShape({
3323
+ defaultPageSize: 5,
3324
+ maxPageSize: 10
3325
+ }),
3326
+ kind: "read",
3327
+ outputSchema: routinesResponse.outputSchema,
3328
+ annotations: readOnlyAnnotations("Get Routines"),
3329
+ responseContract: routinesResponse,
3330
+ execute: async (runtime, { page, page_size }) => {
3331
+ try {
3332
+ const data = await runtime.getClient().getRoutines({
3333
+ page,
3334
+ pageSize: page_size
3335
+ });
3336
+ return {
3337
+ items: data?.routines ?? [],
3338
+ page,
3339
+ pageCount: data?.page_count
3340
+ };
3341
+ } catch (error) {
3342
+ if (isExpectedListPageNotFound(error, page)) return {
3343
+ items: [],
3344
+ page,
3345
+ expected404Outcome: "end_of_list"
3346
+ };
3347
+ throw error;
3348
+ }
3288
3349
  }
3289
- }
3290
- };
3291
- const getRoutineSchema = { routineId: nonEmptyId };
3292
- const getRoutineDefinition = {
3293
- name: "get-routine",
3294
- feature: "routines",
3295
- operation: "get",
3296
- description: describeTool({
3297
- summary: "Read-only. Retrieves one routine and its exercise configuration by ID.",
3298
- aliases: [
3299
- "show routine",
3300
- "fetch workout plan",
3301
- "routine details"
3302
- ],
3303
- useCase: "Use when the routineId is known; use get-routines to browse or discover IDs.",
3304
- importantNotes: "Requires a routineId from get-routines or a prior create response."
3305
- }),
3306
- inputSchema: getRoutineSchema,
3307
- kind: "read",
3308
- outputSchema: routineResponse.outputSchema,
3309
- annotations: readOnlyAnnotations("Get Routine"),
3310
- responseContract: routineResponse,
3311
- execute: async (runtime, { routineId }) => {
3312
- try {
3350
+ },
3351
+ {
3352
+ name: "get-routine",
3353
+ feature: "routines",
3354
+ operation: "get",
3355
+ description: "Read-only. Gets one routine with exercises and sets by routine_id. Use search-routines to discover IDs.",
3356
+ inputSchema: { routine_id: nonEmptyId },
3357
+ kind: "read",
3358
+ outputSchema: routineResponse.outputSchema,
3359
+ annotations: readOnlyAnnotations("Get Routine"),
3360
+ responseContract: routineResponse,
3361
+ execute: async (runtime, { routine_id }) => {
3362
+ try {
3363
+ return {
3364
+ routine: (await runtime.getClient().getRoutineById(String(routine_id)))?.routine,
3365
+ routine_id
3366
+ };
3367
+ } catch (error) {
3368
+ if (isExpectedReadNotFound(error)) return {
3369
+ routine: null,
3370
+ routine_id,
3371
+ expected404Outcome: "not_found"
3372
+ };
3373
+ throw error;
3374
+ }
3375
+ }
3376
+ },
3377
+ {
3378
+ name: "create-routine",
3379
+ feature: "routines",
3380
+ operation: "create",
3381
+ description: "Writes a reusable routine; use create-workout for completed sessions. Retries can create duplicates.",
3382
+ inputSchema: createRoutineInputShape,
3383
+ kind: "write",
3384
+ annotations: createAnnotations("Create Routine"),
3385
+ responseContract: createRoutineResponse,
3386
+ execute: async (runtime, args) => {
3387
+ const { payload, usesRepRanges } = buildRoutinePayload(args.routine, "create");
3313
3388
  return {
3314
- routine: (await runtime.getClient().getRoutineById(String(routineId)))?.routine,
3315
- routineId
3316
- };
3317
- } catch (error) {
3318
- if (isExpectedReadNotFound(error)) return {
3319
- routine: null,
3320
- routineId,
3321
- expected404Outcome: "not_found"
3389
+ routine: await runtime.getClient().createRoutine({ routine: payload }),
3390
+ usesRepRanges
3322
3391
  };
3323
- throw error;
3324
3392
  }
3325
- }
3326
- };
3327
- const routineExercisesSchema = z.preprocess(parseJsonArray, z.array(z.object(routineExerciseShape)));
3328
- const createRoutineSchema = routinePayloadShape;
3329
- const createRoutineDefinition = {
3330
- name: "create-routine",
3331
- feature: "routines",
3332
- operation: "create",
3333
- description: describeTool({
3334
- summary: "Writes to the Hevy account by creating a new workout routine.",
3335
- aliases: [
3336
- "add routine",
3337
- "build workout plan",
3338
- "save training template"
3339
- ],
3340
- useCase: "Use to create a reusable plan; use create-workout to log a completed session.",
3341
- importantNotes: "Requires exercise template IDs; folderId is optional. Retrying can create duplicates, and non-fixed rep ranges may not display in Hevy apps."
3342
- }),
3343
- inputSchema: createRoutineSchema,
3344
- kind: "write",
3345
- annotations: createAnnotations("Create Routine"),
3346
- responseContract: createRoutineResponse,
3347
- execute: async (runtime, args) => {
3348
- const { payload, usesRepRanges } = buildRoutinePayload(args, "create");
3349
- return {
3350
- routine: await runtime.getClient().createRoutine({ routine: payload }),
3351
- usesRepRanges
3352
- };
3353
- }
3354
- };
3355
- const updateRoutineSchema = {
3356
- routineId: nonEmptyId,
3357
- title: z.string().min(1),
3358
- notes: z.string().optional(),
3359
- exercises: routineExercisesSchema
3360
- };
3361
- const routineToolDefinitions = [
3362
- getRoutinesDefinition,
3363
- getRoutineDefinition,
3364
- createRoutineDefinition,
3393
+ },
3365
3394
  {
3366
3395
  name: "update-routine",
3367
3396
  feature: "routines",
3368
3397
  operation: "update",
3369
- description: describeTool({
3370
- summary: "Mutates the Hevy account by replacing an existing routine's content.",
3371
- aliases: [
3372
- "edit routine",
3373
- "revise workout plan",
3374
- "replace routine exercises"
3375
- ],
3376
- useCase: "Use to change a known routine; use create-routine for a separate new plan.",
3377
- importantNotes: "Requires routineId and the complete title and exercises payload; omitted exercises are removed. Non-fixed rep ranges may not display in Hevy apps."
3378
- }),
3379
- inputSchema: updateRoutineSchema,
3398
+ description: "Mutates a routine by replacing its title and exercises. Omitted exercises are removed.",
3399
+ inputSchema: updateRoutineInputShape,
3380
3400
  kind: "write",
3381
3401
  annotations: updateAnnotations("Update Routine"),
3382
3402
  responseContract: updateRoutineResponse,
3383
3403
  execute: async (runtime, args) => {
3384
- const { routineId } = args;
3385
- const { payload, usesRepRanges } = buildRoutinePayload(args, "update");
3404
+ const { routine_id } = args;
3405
+ const { payload, usesRepRanges } = buildRoutinePayload(args.routine, "update");
3386
3406
  return {
3387
- routine: await runtime.getClient().updateRoutine(routineId, { routine: payload }),
3388
- routineId,
3407
+ routine: await runtime.getClient().updateRoutine(routine_id, { routine: payload }),
3408
+ routine_id,
3389
3409
  usesRepRanges
3390
3410
  };
3391
3411
  }
@@ -3397,17 +3417,19 @@ const getExerciseTemplatesSchema = paginationShape({
3397
3417
  defaultPageSize: 5,
3398
3418
  maxPageSize: 100
3399
3419
  });
3400
- const getExerciseTemplateSchema = { exerciseTemplateId: nonEmptyId };
3420
+ const getExerciseTemplateSchema = { exercise_template_id: nonEmptyId };
3421
+ const isoDateTimeBase = z.iso.datetime({ offset: true });
3422
+ const isoDateTimeWithOffset = z.string().refine((value) => isoDateTimeBase.safeParse(value).success, "Must be an ISO 8601 timestamp with an offset").meta({ format: "date-time" });
3401
3423
  const getExerciseHistorySchema = {
3402
- exerciseTemplateId: nonEmptyId,
3403
- startDate: z.string().datetime({ offset: true }).describe("ISO 8601 start date for filtering history").optional(),
3404
- endDate: z.string().datetime({ offset: true }).describe("ISO 8601 end date for filtering history").optional()
3424
+ exercise_template_id: nonEmptyId,
3425
+ start_date: isoDateTimeWithOffset.optional(),
3426
+ end_date: isoDateTimeWithOffset.optional()
3405
3427
  };
3406
3428
  const createExerciseTemplateSchema = exerciseTemplateInputShape;
3407
3429
  const searchExerciseTemplatesSchema = {
3408
- query: z.string().min(1).describe("Case-insensitive substring to match against exercise template titles"),
3409
- primaryMuscleGroup: muscleGroupEnum.optional().describe("Optional filter to restrict results to a specific primary muscle group"),
3410
- refresh: z.boolean().optional().default(false).describe("Set to true to invalidate the catalog cache and re-fetch all templates from the API")
3430
+ query: z.string().min(1),
3431
+ primary_muscle_group: muscleGroupEnum.optional(),
3432
+ refresh: z.boolean().optional().default(false)
3411
3433
  };
3412
3434
  /** Ordered exercise-template tools for composition by the shared server. */
3413
3435
  const templateToolDefinitions = [
@@ -3415,27 +3437,18 @@ const templateToolDefinitions = [
3415
3437
  name: "get-exercise-templates",
3416
3438
  feature: "templates",
3417
3439
  operation: "list",
3418
- description: describeTool({
3419
- summary: "Read-only. Lists default and custom exercise templates with equipment and muscle metadata.",
3420
- aliases: [
3421
- "browse exercises",
3422
- "list exercise catalog",
3423
- "show movements"
3424
- ],
3425
- useCase: "Use for page-by-page catalog browsing; use search-exercise-templates for a name lookup across the full catalog.",
3426
- importantNotes: "Results are paginated; page starts at 1 and pageSize is limited to 100."
3427
- }),
3440
+ description: "Read-only. Pages through exercise templates. Use search-exercise-templates when a title is known.",
3428
3441
  inputSchema: getExerciseTemplatesSchema,
3429
3442
  outputSchema: exerciseTemplatesResponse.outputSchema,
3430
3443
  annotations: readOnlyAnnotations("Get Exercise Templates"),
3431
3444
  kind: "read",
3432
3445
  responseContract: exerciseTemplatesResponse,
3433
3446
  execute: async (runtime, args) => {
3434
- const { page, pageSize } = args;
3447
+ const { page, page_size } = args;
3435
3448
  try {
3436
3449
  const data = await runtime.getClient().getExerciseTemplates({
3437
3450
  page,
3438
- pageSize
3451
+ pageSize: page_size
3439
3452
  });
3440
3453
  return {
3441
3454
  items: data?.exercise_templates ?? [],
@@ -3456,32 +3469,23 @@ const templateToolDefinitions = [
3456
3469
  name: "get-exercise-template",
3457
3470
  feature: "templates",
3458
3471
  operation: "get",
3459
- description: describeTool({
3460
- summary: "Read-only. Retrieves complete metadata for one exercise template by ID.",
3461
- aliases: [
3462
- "show exercise details",
3463
- "fetch movement",
3464
- "exercise template info"
3465
- ],
3466
- useCase: "Use after locating an exact template; use search-exercise-templates when only a name is known.",
3467
- importantNotes: "Requires an exerciseTemplateId from a template list, search, routine, or workout."
3468
- }),
3472
+ description: "Read-only. Gets one exercise template by exercise_template_id. Use search-exercise-templates to discover IDs.",
3469
3473
  inputSchema: getExerciseTemplateSchema,
3470
3474
  outputSchema: exerciseTemplateResponse.outputSchema,
3471
3475
  annotations: readOnlyAnnotations("Get Exercise Template"),
3472
3476
  kind: "read",
3473
3477
  responseContract: exerciseTemplateResponse,
3474
3478
  execute: async (runtime, args) => {
3475
- const { exerciseTemplateId } = args;
3479
+ const { exercise_template_id } = args;
3476
3480
  try {
3477
3481
  return {
3478
- exerciseTemplate: await runtime.getClient().getExerciseTemplate(exerciseTemplateId),
3479
- exerciseTemplateId
3482
+ exercise_template: await runtime.getClient().getExerciseTemplate(exercise_template_id),
3483
+ exercise_template_id
3480
3484
  };
3481
3485
  } catch (error) {
3482
3486
  if (isExpectedReadNotFound(error)) return {
3483
- exerciseTemplate: null,
3484
- exerciseTemplateId,
3487
+ exercise_template: null,
3488
+ exercise_template_id,
3485
3489
  expected404Outcome: "not_found"
3486
3490
  };
3487
3491
  throw error;
@@ -3492,29 +3496,20 @@ const templateToolDefinitions = [
3492
3496
  name: "get-exercise-history",
3493
3497
  feature: "templates",
3494
3498
  operation: "get",
3495
- description: describeTool({
3496
- summary: "Read-only. Retrieves past performed sets for one exercise template.",
3497
- aliases: [
3498
- "exercise progress",
3499
- "past sets",
3500
- "movement history"
3501
- ],
3502
- useCase: "Use to analyze performance for one movement; use get-workouts for complete sessions.",
3503
- importantNotes: "Requires an exerciseTemplateId. Optional startDate and endDate must be ISO 8601 datetimes with an offset."
3504
- }),
3499
+ description: "Read-only. Returns performed sets for one exercise-template ID, optionally bounded by ISO 8601 timestamps.",
3505
3500
  inputSchema: getExerciseHistorySchema,
3506
3501
  outputSchema: exerciseHistoryResponse.outputSchema,
3507
3502
  annotations: readOnlyAnnotations("Get Exercise History"),
3508
3503
  kind: "read",
3509
3504
  responseContract: exerciseHistoryResponse,
3510
3505
  execute: async (runtime, args) => {
3511
- const { exerciseTemplateId, startDate, endDate } = args;
3506
+ const { exercise_template_id, start_date, end_date } = args;
3512
3507
  return {
3513
- history: (await runtime.getClient().getExerciseHistory(exerciseTemplateId, {
3514
- ...startDate ? { start_date: startDate } : {},
3515
- ...endDate ? { end_date: endDate } : {}
3508
+ exercise_history: (await runtime.getClient().getExerciseHistory(exercise_template_id, {
3509
+ ...start_date ? { start_date } : {},
3510
+ ...end_date ? { end_date } : {}
3516
3511
  }))?.exercise_history,
3517
- exerciseTemplateId
3512
+ exercise_template_id
3518
3513
  };
3519
3514
  }
3520
3515
  },
@@ -3522,38 +3517,20 @@ const templateToolDefinitions = [
3522
3517
  name: "create-exercise-template",
3523
3518
  feature: "templates",
3524
3519
  operation: "create",
3525
- description: describeTool({
3526
- summary: "Writes to the Hevy account by creating a custom exercise template.",
3527
- aliases: [
3528
- "add custom exercise",
3529
- "create movement",
3530
- "define exercise"
3531
- ],
3532
- useCase: "Use only when the needed movement is absent; search-exercise-templates should check existing templates first.",
3533
- importantNotes: "Requires title, exercise type, equipment category, and primary muscle group. Retrying or reusing a title can create duplicates."
3534
- }),
3520
+ description: "Writes a custom exercise template. Search first; retries or reused titles can create duplicates.",
3535
3521
  inputSchema: createExerciseTemplateSchema,
3536
3522
  annotations: createAnnotations("Create Exercise Template"),
3537
3523
  kind: "write",
3538
3524
  responseContract: createExerciseTemplateResponse,
3539
3525
  execute: async (runtime, args) => {
3540
- return runtime.getClient().createExerciseTemplate(buildExerciseTemplateRequest(args));
3526
+ return runtime.getClient().createExerciseTemplate(args);
3541
3527
  }
3542
3528
  },
3543
3529
  {
3544
3530
  name: "search-exercise-templates",
3545
3531
  feature: "templates",
3546
3532
  operation: "search",
3547
- description: describeTool({
3548
- summary: "Read-only for the Hevy account. Searches the full exercise template catalog by title substring.",
3549
- aliases: [
3550
- "find exercise",
3551
- "look up movement",
3552
- "search exercise IDs"
3553
- ],
3554
- useCase: "Use when a name or partial name is known, especially to discover IDs for workouts and routines; use get-exercise-templates for page browsing.",
3555
- importantNotes: "Matching is case-insensitive. The catalog is cached locally for 5 minutes; refresh:true re-fetches all pages and changes only local cache state."
3556
- }),
3533
+ description: "Read-only. Searches template titles case-insensitively and returns IDs. refresh reloads the five-minute catalog cache.",
3557
3534
  inputSchema: searchExerciseTemplatesSchema,
3558
3535
  outputSchema: searchExerciseTemplatesResponse.outputSchema,
3559
3536
  annotations: readOnlyAnnotations("Search Exercise Templates"),
@@ -3561,7 +3538,7 @@ const templateToolDefinitions = [
3561
3538
  responseContract: searchExerciseTemplatesResponse,
3562
3539
  execute: async (runtime, args) => {
3563
3540
  runtime.getClient();
3564
- const { query, primaryMuscleGroup, refresh } = args;
3541
+ const { query, primary_muscle_group, refresh } = args;
3565
3542
  const templates = await runtime.catalog.get({
3566
3543
  refresh,
3567
3544
  onRefreshed: (refreshedCatalog, reason) => {
@@ -3582,11 +3559,11 @@ const templateToolDefinitions = [
3582
3559
  });
3583
3560
  const queryLower = query.toLowerCase();
3584
3561
  let results = templates.filter((t) => (t.title ?? "").toLowerCase().includes(queryLower));
3585
- if (primaryMuscleGroup !== void 0) results = results.filter((t) => t.primary_muscle_group === primaryMuscleGroup);
3562
+ if (primary_muscle_group !== void 0) results = results.filter((t) => t.primary_muscle_group === primary_muscle_group);
3586
3563
  return {
3587
3564
  results,
3588
3565
  query,
3589
- primaryMuscleGroup
3566
+ primary_muscle_group
3590
3567
  };
3591
3568
  }
3592
3569
  }
@@ -3595,16 +3572,7 @@ const userToolDefinitions = [{
3595
3572
  name: "get-user-info",
3596
3573
  feature: "profile",
3597
3574
  operation: "get",
3598
- description: describeTool({
3599
- summary: "Read-only. Returns the authenticated account's user ID, display name, and public profile URL.",
3600
- aliases: [
3601
- "who am I",
3602
- "account info",
3603
- "verify Hevy user"
3604
- ],
3605
- useCase: "Use to confirm which Hevy account is connected before reading or writing account data.",
3606
- importantNotes: "Accepts no inputs and reports only the account associated with the configured credentials."
3607
- }),
3575
+ description: "Read-only. Returns the authenticated Hevy user ID, display name, and public profile URL.",
3608
3576
  inputSchema: {},
3609
3577
  kind: "read",
3610
3578
  outputSchema: userResponse.outputSchema,
@@ -3617,8 +3585,8 @@ const userToolDefinitions = [{
3617
3585
  //#endregion
3618
3586
  //#region ../core/src/tools/routine-discovery.ts
3619
3587
  const routineDiscoverySchema = {
3620
- query: z.string().min(1).optional().describe("Optional case-insensitive substring to match routine titles."),
3621
- limit: z.coerce.number().int().min(1).max(100).default(20).describe("Maximum compact routines to return (1-100).")
3588
+ query: z.string().min(1).optional(),
3589
+ limit: z.coerce.number().int().min(1).max(100).default(20)
3622
3590
  };
3623
3591
  async function discoverRoutines(runtime, { query, limit }) {
3624
3592
  const normalizedQuery = query?.toLocaleLowerCase();
@@ -3645,14 +3613,7 @@ async function discoverRoutines(runtime, { query, limit }) {
3645
3613
  page += 1;
3646
3614
  }
3647
3615
  return {
3648
- routines: routines.slice(0, limit).map((routine) => ({
3649
- ...routine.id ? { id: routine.id } : {},
3650
- ...routine.title ? { title: routine.title } : {},
3651
- folderId: routine.folder_id ?? null,
3652
- ...routine.updated_at ? { updatedAt: routine.updated_at } : {},
3653
- exerciseCount: routine.exercises?.length ?? 0,
3654
- setCount: routine.exercises?.reduce((total, exercise) => total + (exercise.sets?.length ?? 0), 0) ?? 0
3655
- })),
3616
+ routines: routines.slice(0, limit).map(summarizeRoutine),
3656
3617
  workflow: {
3657
3618
  name: "routine-discovery",
3658
3619
  pagination: { routines: pages },
@@ -3665,16 +3626,7 @@ const routineDiscoveryToolDefinitions = [{
3665
3626
  name: "search-routines",
3666
3627
  feature: "workflows",
3667
3628
  operation: "search",
3668
- description: describeTool({
3669
- summary: "Read-only. Discovers routines by title and returns compact metadata without full set payloads.",
3670
- aliases: [
3671
- "find routine",
3672
- "browse routine names",
3673
- "compact routine list"
3674
- ],
3675
- useCase: "Use to find a routine ID or shortlist plans before calling get-routine for full exercise details.",
3676
- importantNotes: "Search scans routine pages at pageSize 10 and returns only IDs, titles, folder metadata, and exercise/set counts."
3677
- }),
3629
+ description: "Read-only. Searches routine titles and returns compact IDs and counts. Use get-routine for full exercises and sets.",
3678
3630
  inputSchema: routineDiscoverySchema,
3679
3631
  outputSchema: compactRoutinesResponse.outputSchema,
3680
3632
  annotations: readOnlyAnnotations("Search Routines"),
@@ -3684,7 +3636,7 @@ const routineDiscoveryToolDefinitions = [{
3684
3636
  }];
3685
3637
  //#endregion
3686
3638
  //#region ../core/src/tools/workflows.ts
3687
- const trainingSummarySchema = { weeks: z.coerce.number().int().min(1).max(12).default(4).describe("Number of recent weeks to summarize (1-12).") };
3639
+ const trainingSummarySchema = { weeks: z.coerce.number().int().min(1).max(12).default(4) };
3688
3640
  const MILLISECONDS_PER_DAY = 1440 * 60 * 1e3;
3689
3641
  function parseUtcDate(value) {
3690
3642
  const normalized = value.length === 10 ? `${value}T00:00:00.000Z` : value;
@@ -3738,28 +3690,29 @@ function getPeriod(weeks) {
3738
3690
  };
3739
3691
  }
3740
3692
  function durationSeconds(workout) {
3741
- if (!workout.start_time || !workout.end_time) return null;
3693
+ if (!workout.start_time || !workout.end_time) return void 0;
3742
3694
  const duration = new Date(workout.end_time).getTime() - new Date(workout.start_time).getTime();
3743
- return Number.isFinite(duration) && duration >= 0 ? Math.floor(duration / 1e3) : null;
3695
+ return Number.isFinite(duration) && duration >= 0 ? Math.floor(duration / 1e3) : void 0;
3744
3696
  }
3745
3697
  function compactSession(workout) {
3746
3698
  const exercises = workout.exercises ?? [];
3699
+ const elapsedSeconds = durationSeconds(workout);
3747
3700
  return {
3748
3701
  ...workout.id ? { id: workout.id } : {},
3749
3702
  ...workout.title ? { title: workout.title } : {},
3750
- ...workout.start_time ? { startTime: workout.start_time } : {},
3751
- ...workout.end_time ? { endTime: workout.end_time } : {},
3752
- durationSeconds: durationSeconds(workout),
3753
- exerciseCount: exercises.length,
3754
- setCount: exercises.reduce((total, exercise) => total + (exercise.sets?.length ?? 0), 0)
3703
+ ...workout.start_time ? { start_time: workout.start_time } : {},
3704
+ ...workout.end_time ? { end_time: workout.end_time } : {},
3705
+ ...elapsedSeconds === void 0 ? {} : { duration_seconds: elapsedSeconds },
3706
+ exercise_count: exercises.length,
3707
+ set_count: exercises.reduce((total, exercise) => total + (exercise.sets?.length ?? 0), 0)
3755
3708
  };
3756
3709
  }
3757
3710
  function compactMeasurement(measurement) {
3758
3711
  return {
3759
3712
  date: measurement.date,
3760
- weightKg: measurement.weight_kg ?? null,
3761
- leanMassKg: measurement.lean_mass_kg ?? null,
3762
- fatPercent: measurement.fat_percent ?? null
3713
+ ...measurement.weight_kg == null ? {} : { weight_kg: measurement.weight_kg },
3714
+ ...measurement.lean_mass_kg == null ? {} : { lean_mass_kg: measurement.lean_mass_kg },
3715
+ ...measurement.fat_percent == null ? {} : { fat_percent: measurement.fat_percent }
3763
3716
  };
3764
3717
  }
3765
3718
  async function getTrainingSummary(runtime, weeks) {
@@ -3787,37 +3740,38 @@ async function getTrainingSummary(runtime, weeks) {
3787
3740
  }, pageSize, period.startDate, period.endDate, (measurement) => measurement.date)]);
3788
3741
  const workouts = workoutPages.items;
3789
3742
  const sessions = workouts.map(compactSession);
3790
- const uniqueExerciseTemplateIds = [...new Set(workouts.flatMap((workout) => (workout.exercises ?? []).map((exercise) => exercise.exercise_template_id).filter((id) => Boolean(id))))];
3743
+ const unique_exercise_template_ids = [...new Set(workouts.flatMap((workout) => (workout.exercises ?? []).map((exercise) => exercise.exercise_template_id).filter((id) => Boolean(id))))];
3791
3744
  const measurements = [...measurementPages.items].sort((a, b) => a.date.localeCompare(b.date));
3792
3745
  const earliestMeasurement = measurements[0];
3793
3746
  const latestMeasurement = measurements.at(-1);
3794
- const earliest = earliestMeasurement ? compactMeasurement(earliestMeasurement) : null;
3795
- const latest = latestMeasurement ? compactMeasurement(latestMeasurement) : null;
3796
- const weightChangeKg = latest?.weightKg !== null && latest?.weightKg !== void 0 && earliest?.weightKg !== null && earliest?.weightKg !== void 0 ? latest.weightKg - earliest.weightKg : null;
3747
+ const earliest = earliestMeasurement ? compactMeasurement(earliestMeasurement) : void 0;
3748
+ const latest = latestMeasurement ? compactMeasurement(latestMeasurement) : void 0;
3749
+ const weight_change_kg = latest?.weight_kg !== void 0 && earliest?.weight_kg !== void 0 ? latest.weight_kg - earliest.weight_kg : void 0;
3797
3750
  return {
3798
3751
  period: {
3799
- ...period,
3752
+ start_date: period.startDate,
3753
+ end_date: period.endDate,
3800
3754
  weeks
3801
3755
  },
3802
3756
  workouts: {
3803
3757
  count: workouts.length,
3804
- totalDurationSeconds: sessions.reduce((total, session) => total + (session.durationSeconds ?? 0), 0),
3805
- exerciseCount: sessions.reduce((total, session) => total + session.exerciseCount, 0),
3806
- setCount: sessions.reduce((total, session) => total + session.setCount, 0),
3807
- uniqueExerciseTemplateIds,
3758
+ total_duration_seconds: sessions.reduce((total, session) => total + (session.duration_seconds ?? 0), 0),
3759
+ exercise_count: sessions.reduce((total, session) => total + session.exercise_count, 0),
3760
+ set_count: sessions.reduce((total, session) => total + session.set_count, 0),
3761
+ unique_exercise_template_ids,
3808
3762
  sessions
3809
3763
  },
3810
- bodyMeasurements: {
3764
+ body_measurements: {
3811
3765
  count: measurements.length,
3812
- latest,
3813
- earliest,
3814
- weightChangeKg
3766
+ ...latest ? { latest } : {},
3767
+ ...earliest ? { earliest } : {},
3768
+ ...weight_change_kg === void 0 ? {} : { weight_change_kg }
3815
3769
  },
3816
3770
  workflow: {
3817
3771
  name: "training-summary",
3818
3772
  pagination: {
3819
3773
  workouts: workoutPages.pages,
3820
- bodyMeasurements: measurementPages.pages
3774
+ body_measurements: measurementPages.pages
3821
3775
  },
3822
3776
  cacheStatus: "not-used",
3823
3777
  itemsScanned: workoutPages.itemsScanned + measurementPages.itemsScanned
@@ -3828,16 +3782,7 @@ const workflowToolDefinitions = [{
3828
3782
  name: "get-training-summary",
3829
3783
  feature: "workflows",
3830
3784
  operation: "get",
3831
- description: describeTool({
3832
- summary: "Read-only. Summarizes recent workout activity and body-measurement trends in one call.",
3833
- aliases: [
3834
- "training progress",
3835
- "progress summary",
3836
- "recent training overview"
3837
- ],
3838
- useCase: "Use for a bounded progress review instead of separately counting and paging through workouts and body measurements.",
3839
- importantNotes: "The summary covers the most recent 1-12 weeks, returns compact session evidence, and reports the pages and items scanned."
3840
- }),
3785
+ description: "Read-only. Summarizes workouts and body-measurement trends for the last 1–12 weeks, including compact session and scan evidence.",
3841
3786
  inputSchema: trainingSummarySchema,
3842
3787
  outputSchema: trainingSummaryResponse.outputSchema,
3843
3788
  annotations: readOnlyAnnotations("Get Training Summary"),
@@ -3852,7 +3797,7 @@ const getWorkoutsSchema = paginationShape({
3852
3797
  maxPageSize: 10,
3853
3798
  integerPage: false
3854
3799
  });
3855
- const getWorkoutSchema = { workoutId: nonEmptyId };
3800
+ const getWorkoutSchema = { workout_id: nonEmptyId };
3856
3801
  const getWorkoutEventsSchema = {
3857
3802
  ...paginationShape({
3858
3803
  defaultPageSize: 5,
@@ -3860,11 +3805,9 @@ const getWorkoutEventsSchema = {
3860
3805
  }),
3861
3806
  since: z.string().default("1970-01-01T00:00:00Z")
3862
3807
  };
3863
- const createWorkoutSchema = workoutPayloadShape;
3864
- const updateWorkoutSchema = {
3865
- workoutId: nonEmptyId,
3866
- ...workoutPayloadShape
3867
- };
3808
+ const createWorkoutSchema = workoutInputShape;
3809
+ const updateWorkoutSchema = updateWorkoutInputShape;
3810
+ const replaceWorkoutExercisesSchema = replaceWorkoutExercisesInputShape;
3868
3811
  //#endregion
3869
3812
  //#region ../core/src/tools/register.ts
3870
3813
  const hevyToolDefinitions = [
@@ -3873,16 +3816,7 @@ const hevyToolDefinitions = [
3873
3816
  name: "get-workouts",
3874
3817
  feature: "workouts",
3875
3818
  operation: "list",
3876
- description: describeTool({
3877
- summary: "Read-only. Lists workouts from newest to oldest with exercise and timing details.",
3878
- aliases: [
3879
- "list workout history",
3880
- "show recent workouts",
3881
- "browse logs"
3882
- ],
3883
- useCase: "Use to browse or page through workout history; use get-workout when a workout ID is already known.",
3884
- importantNotes: "Results are paginated; page starts at 1 and pageSize is limited to 10."
3885
- }),
3819
+ description: "Read-only. Lists compact workout summaries newest first. Use get-workout for exercises and sets; results are paginated.",
3886
3820
  inputSchema: getWorkoutsSchema,
3887
3821
  outputSchema: workoutsResponse.outputSchema,
3888
3822
  annotations: readOnlyAnnotations("Get Workouts"),
@@ -3892,7 +3826,7 @@ const hevyToolDefinitions = [
3892
3826
  try {
3893
3827
  const data = await runtime.getClient().getWorkouts({
3894
3828
  page: args.page,
3895
- pageSize: args.pageSize
3829
+ pageSize: args.page_size
3896
3830
  });
3897
3831
  return {
3898
3832
  items: data?.workouts ?? [],
@@ -3913,16 +3847,7 @@ const hevyToolDefinitions = [
3913
3847
  name: "get-workout",
3914
3848
  feature: "workouts",
3915
3849
  operation: "get",
3916
- description: describeTool({
3917
- summary: "Read-only. Retrieves complete details for one workout by its ID.",
3918
- aliases: [
3919
- "show workout",
3920
- "fetch workout details",
3921
- "open workout log"
3922
- ],
3923
- useCase: "Use after get-workouts identifies the exact workout; do not use for browsing multiple workouts.",
3924
- importantNotes: "Requires a workoutId discovered from a workout list, event, or prior create response."
3925
- }),
3850
+ description: "Read-only. Gets one workout with exercises and sets by workout_id. Use get-workouts to discover IDs.",
3926
3851
  inputSchema: getWorkoutSchema,
3927
3852
  outputSchema: workoutResponse.outputSchema,
3928
3853
  annotations: readOnlyAnnotations("Get Workout"),
@@ -3931,13 +3856,13 @@ const hevyToolDefinitions = [
3931
3856
  execute: async (runtime, args) => {
3932
3857
  try {
3933
3858
  return {
3934
- workout: await runtime.getClient().getWorkout(args.workoutId),
3935
- workoutId: args.workoutId
3859
+ workout: await runtime.getClient().getWorkout(args.workout_id),
3860
+ workout_id: args.workout_id
3936
3861
  };
3937
3862
  } catch (error) {
3938
3863
  if (isExpectedReadNotFound(error)) return {
3939
3864
  workout: null,
3940
- workoutId: args.workoutId,
3865
+ workout_id: args.workout_id,
3941
3866
  expected404Outcome: "not_found"
3942
3867
  };
3943
3868
  throw error;
@@ -3948,16 +3873,7 @@ const hevyToolDefinitions = [
3948
3873
  name: "get-workout-count",
3949
3874
  feature: "workouts",
3950
3875
  operation: "count",
3951
- description: describeTool({
3952
- summary: "Read-only. Returns the total workout count for the account.",
3953
- aliases: [
3954
- "count workouts",
3955
- "how many workouts",
3956
- "workout total"
3957
- ],
3958
- useCase: "Use for totals, statistics, or estimating pages; use get-workouts for actual workout records.",
3959
- importantNotes: "Returns only a count and accepts no paging or date filters."
3960
- }),
3876
+ description: "Read-only. Returns the total workout count; it does not return records or accept date filters.",
3961
3877
  inputSchema: {},
3962
3878
  outputSchema: workoutCountResponse.outputSchema,
3963
3879
  annotations: readOnlyAnnotations("Get Workout Count"),
@@ -3971,16 +3887,7 @@ const hevyToolDefinitions = [
3971
3887
  name: "get-workout-events",
3972
3888
  feature: "workouts",
3973
3889
  operation: "sync",
3974
- description: describeTool({
3975
- summary: "Read-only. Lists workout update and delete events since a timestamp, newest first.",
3976
- aliases: [
3977
- "sync workout changes",
3978
- "workout change feed",
3979
- "deleted workouts"
3980
- ],
3981
- useCase: "Use to incrementally synchronize a local workout cache; use get-workouts for the current workout list.",
3982
- importantNotes: "since must be a timestamp string; events are paginated with pageSize at most 10, and the default since value reads from 1970."
3983
- }),
3890
+ description: "Read-only. Lists workout update and deletion events since a timestamp for incremental sync; results are paginated.",
3984
3891
  inputSchema: getWorkoutEventsSchema,
3985
3892
  outputSchema: workoutEventsResponse.outputSchema,
3986
3893
  annotations: readOnlyAnnotations("Get Workout Events"),
@@ -3990,7 +3897,7 @@ const hevyToolDefinitions = [
3990
3897
  try {
3991
3898
  const data = await runtime.getClient().getWorkoutEvents({
3992
3899
  page: args.page,
3993
- pageSize: args.pageSize,
3900
+ pageSize: args.page_size,
3994
3901
  since: args.since
3995
3902
  });
3996
3903
  return {
@@ -4014,46 +3921,48 @@ const hevyToolDefinitions = [
4014
3921
  name: "create-workout",
4015
3922
  feature: "workouts",
4016
3923
  operation: "create",
4017
- description: describeTool({
4018
- summary: "Writes to the Hevy account by creating a new workout.",
4019
- aliases: [
4020
- "log workout",
4021
- "add workout",
4022
- "record training session"
4023
- ],
4024
- useCase: "Use to add a completed workout; use update-workout only when modifying an existing workout ID.",
4025
- importantNotes: "Requires UTC startTime/endTime in YYYY-MM-DDTHH:mm:ssZ form and exercise template IDs. Retrying can create duplicates."
4026
- }),
3924
+ description: "Writes a completed workout. Requires exercise-template IDs and UTC times. Retries can create duplicates.",
4027
3925
  inputSchema: createWorkoutSchema,
4028
3926
  annotations: createAnnotations("Create Workout"),
4029
3927
  kind: "write",
4030
3928
  responseContract: createWorkoutResponse,
4031
3929
  execute: async (runtime, args) => {
4032
- return await runtime.getClient().createWorkout({ workout: buildWorkoutPayload(args) });
3930
+ return await runtime.getClient().createWorkout({ workout: args.workout });
4033
3931
  }
4034
3932
  },
4035
3933
  {
4036
3934
  name: "update-workout",
4037
3935
  feature: "workouts",
4038
3936
  operation: "update",
4039
- description: describeTool({
4040
- summary: "Mutates the Hevy account by replacing an existing workout.",
4041
- aliases: [
4042
- "edit workout",
4043
- "correct workout log",
4044
- "replace workout details"
4045
- ],
4046
- useCase: "Use to revise a known workout; use create-workout for a new training session.",
4047
- importantNotes: "Requires workoutId plus the complete title, times, privacy, exercises, and sets payload; omitted optional values may be cleared or defaulted."
4048
- }),
3937
+ description: "Mutates workout metadata by ID. Omitted fields and all exercises remain unchanged.",
4049
3938
  inputSchema: updateWorkoutSchema,
4050
3939
  annotations: updateAnnotations("Update Workout"),
4051
3940
  kind: "write",
4052
3941
  responseContract: updateWorkoutResponse,
4053
3942
  execute: async (runtime, args) => {
3943
+ const client = runtime.getClient();
3944
+ const payload = buildWorkoutUpdatePayload(await client.getWorkout(args.workout_id), args.workout);
4054
3945
  return {
4055
- workout: await runtime.getClient().updateWorkout(args.workoutId, { workout: buildWorkoutPayload(args) }),
4056
- workoutId: args.workoutId
3946
+ workout: await client.updateWorkout(args.workout_id, { workout: payload }),
3947
+ workout_id: args.workout_id
3948
+ };
3949
+ }
3950
+ },
3951
+ {
3952
+ name: "replace-workout-exercises",
3953
+ feature: "workouts",
3954
+ operation: "update",
3955
+ description: "Mutates a workout by replacing all exercises and sets. Workout metadata remains unchanged.",
3956
+ inputSchema: replaceWorkoutExercisesSchema,
3957
+ annotations: updateAnnotations("Replace Workout Exercises"),
3958
+ kind: "write",
3959
+ responseContract: updateWorkoutResponse,
3960
+ execute: async (runtime, args) => {
3961
+ const client = runtime.getClient();
3962
+ const payload = buildWorkoutUpdatePayload(await client.getWorkout(args.workout_id), {}, args.workout.exercises);
3963
+ return {
3964
+ workout: await client.updateWorkout(args.workout_id, { workout: payload }),
3965
+ workout_id: args.workout_id
4057
3966
  };
4058
3967
  }
4059
3968
  }
@@ -4132,55 +4041,59 @@ function withErrorHandling(fn, context, onError) {
4132
4041
  }
4133
4042
  //#endregion
4134
4043
  //#region ../core/src/tools/tool-runtime.ts
4135
- const STRUCTURAL_ARGUMENT_KEYS = [
4136
- "page",
4137
- "pageSize",
4138
- "since",
4139
- "workoutId",
4140
- "routineId",
4141
- "folderId",
4142
- "exerciseTemplateId",
4143
- "date",
4144
- "startDate",
4145
- "endDate",
4146
- "updatedSince",
4147
- "includeCustom",
4148
- "limit",
4149
- "offset",
4150
- "refresh",
4151
- "query",
4152
- "primaryMuscleGroup"
4153
- ];
4154
- const PRESENCE_ARGUMENT_KEYS = /* @__PURE__ */ new Set([
4155
- "since",
4156
- "workoutId",
4157
- "routineId",
4158
- "folderId",
4159
- "exerciseTemplateId",
4160
- "date",
4161
- "startDate",
4162
- "endDate",
4163
- "updatedSince",
4164
- "query",
4165
- "primaryMuscleGroup"
4166
- ]);
4167
- const NUMERIC_ARGUMENT_KEYS = /* @__PURE__ */ new Set([
4168
- "page",
4169
- "pageSize",
4170
- "limit",
4171
- "offset"
4172
- ]);
4173
- const BOOLEAN_ARGUMENT_KEYS = /* @__PURE__ */ new Set(["includeCustom", "refresh"]);
4044
+ const STRUCTURAL_ARGUMENT_KEYS = {
4045
+ page: true,
4046
+ page_size: true,
4047
+ since: true,
4048
+ workout_id: true,
4049
+ routine_id: true,
4050
+ folder_id: true,
4051
+ exercise_template_id: true,
4052
+ date: true,
4053
+ start_date: true,
4054
+ end_date: true,
4055
+ updated_since: true,
4056
+ include_custom: true,
4057
+ limit: true,
4058
+ offset: true,
4059
+ refresh: true,
4060
+ query: true,
4061
+ primary_muscle_group: true
4062
+ };
4063
+ const PRESENCE_ARGUMENT_KEYS = {
4064
+ since: true,
4065
+ workout_id: true,
4066
+ routine_id: true,
4067
+ folder_id: true,
4068
+ exercise_template_id: true,
4069
+ date: true,
4070
+ start_date: true,
4071
+ end_date: true,
4072
+ updated_since: true,
4073
+ query: true,
4074
+ primary_muscle_group: true
4075
+ };
4076
+ const NUMERIC_ARGUMENT_KEYS = {
4077
+ page: true,
4078
+ page_size: true,
4079
+ limit: true,
4080
+ offset: true
4081
+ };
4082
+ const BOOLEAN_ARGUMENT_KEYS = {
4083
+ include_custom: true,
4084
+ refresh: true
4085
+ };
4086
+ const structuralArgumentKeys = Object.keys(STRUCTURAL_ARGUMENT_KEYS);
4174
4087
  function createSafeInvocation(name, args, taxonomy) {
4175
- const argumentKeys = STRUCTURAL_ARGUMENT_KEYS.filter((key) => key in args);
4088
+ const argumentKeys = structuralArgumentKeys.filter((key) => key in args);
4176
4089
  const argumentPresence = {};
4177
4090
  const numericArgumentBuckets = {};
4178
4091
  const booleanArguments = {};
4179
4092
  for (const key of argumentKeys) {
4180
4093
  const value = args[key];
4181
- if (PRESENCE_ARGUMENT_KEYS.has(key) && value !== null && value !== void 0) argumentPresence[key] = true;
4182
- if (NUMERIC_ARGUMENT_KEYS.has(key) && typeof value === "number") numericArgumentBuckets[key] = bucketCount(value);
4183
- if (BOOLEAN_ARGUMENT_KEYS.has(key) && typeof value === "boolean") booleanArguments[key] = value;
4094
+ if (key in PRESENCE_ARGUMENT_KEYS && value !== null && value !== void 0) argumentPresence[key] = true;
4095
+ if (key in NUMERIC_ARGUMENT_KEYS && typeof value === "number") numericArgumentBuckets[key] = bucketCount(value);
4096
+ if (key in BOOLEAN_ARGUMENT_KEYS && typeof value === "boolean") booleanArguments[key] = value;
4184
4097
  }
4185
4098
  return {
4186
4099
  name,