hevy-mcp 6.1.18 → 6.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # hevy-mcp
2
2
 
3
+ ## 6.1.19
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1223](https://github.com/chrisdoc/hevy-mcp/pull/1223) [`c8a1092`](https://github.com/chrisdoc/hevy-mcp/commit/c8a109237ee2df27dbedc19d02637ce253b4cf2d) Thanks [@chrisdoc](https://github.com/chrisdoc)! - Refresh the generated Hevy client and runtime dependency resolutions for the updated toolchain.
8
+
3
9
  ## 6.1.18
4
10
 
5
11
  ### Patch Changes
package/dist/cli.mjs CHANGED
@@ -8,7 +8,7 @@
8
8
  n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "d637b259-c547-4286-9669-7b858825b504", e._sentryDebugIdIdentifier = "sentry-dbid-d637b259-c547-4286-9669-7b858825b504");
9
9
  } catch (e) {}
10
10
  })();
11
- import { i as handleFatalStartupError, r as getSafeStartupMessage, t as runServer } from "./runtime-DjV2AF4_.mjs";
11
+ import { i as handleFatalStartupError, r as getSafeStartupMessage, t as runServer } from "./runtime-DiojMsCj.mjs";
12
12
  //#region src/cli.ts
13
13
  runServer().catch(async (error) => {
14
14
  await handleFatalStartupError(error);
@@ -751,7 +751,10 @@ var ResponseError = class extends Error {
751
751
  request;
752
752
  response;
753
753
  constructor(config) {
754
- super(`Request failed with status ${config.status}${config.statusText ? ` ${config.statusText}` : ""}`);
754
+ const cleanUrl = config.url?.split(/[?#]/)[0];
755
+ const target = [config.method, cleanUrl].filter(Boolean).join(" ");
756
+ const statusText = config.statusText?.trim();
757
+ super(`${target ? `${target} failed` : "Request failed"} with status ${config.status}${statusText ? ` ${statusText}` : ""}`);
755
758
  this.name = "ResponseError";
756
759
  this.data = config.data;
757
760
  this.status = config.status;
@@ -760,6 +763,12 @@ var ResponseError = class extends Error {
760
763
  this.request = config.request;
761
764
  this.response = config.response;
762
765
  }
766
+ /**
767
+ * Matches on `name`, not `instanceof`: every generated client bundles its own `ResponseError` class.
768
+ */
769
+ static is(error) {
770
+ return error instanceof Error && error.name === "ResponseError";
771
+ }
763
772
  };
764
773
  function serializeHeaders(headers) {
765
774
  if (!headers) return {};
@@ -813,9 +822,9 @@ function createInterceptorStack() {
813
822
  const entry = entries.find((item) => item.id === id);
814
823
  if (entry) entry.fn = fn;
815
824
  },
816
- async run(value) {
825
+ async run(value, context) {
817
826
  let result = value;
818
- for (const entry of entries) result = await entry.fn(result);
827
+ for (const entry of entries) result = await entry.fn(result, context);
819
828
  return result;
820
829
  }
821
830
  };
@@ -966,7 +975,7 @@ async function resolveRequest({ config, requestConfig }) {
966
975
  * validates it, and throws a `ResponseError` (after running the error interceptors) for a non-2xx
967
976
  * response under `throwOnError`.
968
977
  */
969
- async function settleResult({ result, request, codecs, throwOnError, validator, onValidationError, errorInterceptors }) {
978
+ async function settleResult({ result, request, codecs, throwOnError, validator, onValidationError, errorInterceptors, requestConfig }) {
970
979
  const isSuccess = result.status >= 200 && result.status < 300;
971
980
  const contentType = result.contentType ?? getResponseContentType(result.headers);
972
981
  let decoded = result.data;
@@ -1014,9 +1023,11 @@ async function settleResult({ result, request, codecs, throwOnError, validator,
1014
1023
  statusText: result.statusText,
1015
1024
  contentType,
1016
1025
  request: result.request,
1017
- response: result.response
1026
+ response: result.response,
1027
+ method: request.method,
1028
+ url: request.url
1018
1029
  });
1019
- await errorInterceptors.run(responseError);
1030
+ await errorInterceptors.run(responseError, requestConfig);
1020
1031
  throw responseError;
1021
1032
  }
1022
1033
  return {
@@ -1045,15 +1056,16 @@ function createClientCore(options) {
1045
1056
  config,
1046
1057
  requestConfig
1047
1058
  });
1048
- const resolvedRequest = await interceptors.request.run(request);
1059
+ const resolvedRequest = await interceptors.request.run(request, requestConfig);
1049
1060
  return settleResult({
1050
- result: await interceptors.response.run(await transport(resolvedRequest)),
1061
+ result: await interceptors.response.run(await transport(resolvedRequest), requestConfig),
1051
1062
  request: resolvedRequest,
1052
1063
  codecs,
1053
1064
  throwOnError: requestConfig.throwOnError ?? config.throwOnError ?? true,
1054
1065
  validator: requestConfig.validator,
1055
1066
  onValidationError: requestConfig.onValidationError ?? config.onValidationError,
1056
- errorInterceptors: interceptors.error
1067
+ errorInterceptors: interceptors.error,
1068
+ requestConfig
1057
1069
  });
1058
1070
  });
1059
1071
  client.getConfig = () => config;
@@ -1160,7 +1172,8 @@ function getV1BodyMeasurements(options) {
1160
1172
  return withUnwrap(request({
1161
1173
  method: "GET",
1162
1174
  url: "/v1/body_measurements",
1163
- ...config
1175
+ ...config,
1176
+ throwOnError: config.throwOnError ?? true
1164
1177
  }));
1165
1178
  }
1166
1179
  //#endregion
@@ -1174,7 +1187,8 @@ function getV1BodyMeasurementsDate(options) {
1174
1187
  return withUnwrap(request({
1175
1188
  method: "GET",
1176
1189
  url: "/v1/body_measurements/{date}",
1177
- ...config
1190
+ ...config,
1191
+ throwOnError: config.throwOnError ?? true
1178
1192
  }));
1179
1193
  }
1180
1194
  //#endregion
@@ -1188,7 +1202,8 @@ function getV1ExerciseHistoryExercisetemplateid(options) {
1188
1202
  return withUnwrap(request({
1189
1203
  method: "GET",
1190
1204
  url: "/v1/exercise_history/{exerciseTemplateId}",
1191
- ...config
1205
+ ...config,
1206
+ throwOnError: config.throwOnError ?? true
1192
1207
  }));
1193
1208
  }
1194
1209
  //#endregion
@@ -1202,7 +1217,8 @@ function getV1ExerciseTemplates(options) {
1202
1217
  return withUnwrap(request({
1203
1218
  method: "GET",
1204
1219
  url: "/v1/exercise_templates",
1205
- ...config
1220
+ ...config,
1221
+ throwOnError: config.throwOnError ?? true
1206
1222
  }));
1207
1223
  }
1208
1224
  //#endregion
@@ -1216,7 +1232,8 @@ function getV1ExerciseTemplatesExercisetemplateid(options) {
1216
1232
  return withUnwrap(request({
1217
1233
  method: "GET",
1218
1234
  url: "/v1/exercise_templates/{exerciseTemplateId}",
1219
- ...config
1235
+ ...config,
1236
+ throwOnError: config.throwOnError ?? true
1220
1237
  }));
1221
1238
  }
1222
1239
  //#endregion
@@ -1230,7 +1247,8 @@ function getV1RoutineFolders(options) {
1230
1247
  return withUnwrap(request({
1231
1248
  method: "GET",
1232
1249
  url: "/v1/routine_folders",
1233
- ...config
1250
+ ...config,
1251
+ throwOnError: config.throwOnError ?? true
1234
1252
  }));
1235
1253
  }
1236
1254
  //#endregion
@@ -1244,7 +1262,8 @@ function getV1RoutineFoldersFolderid(options) {
1244
1262
  return withUnwrap(request({
1245
1263
  method: "GET",
1246
1264
  url: "/v1/routine_folders/{folderId}",
1247
- ...config
1265
+ ...config,
1266
+ throwOnError: config.throwOnError ?? true
1248
1267
  }));
1249
1268
  }
1250
1269
  //#endregion
@@ -1258,7 +1277,8 @@ function getV1Routines(options) {
1258
1277
  return withUnwrap(request({
1259
1278
  method: "GET",
1260
1279
  url: "/v1/routines",
1261
- ...config
1280
+ ...config,
1281
+ throwOnError: config.throwOnError ?? true
1262
1282
  }));
1263
1283
  }
1264
1284
  //#endregion
@@ -1272,7 +1292,8 @@ function getV1RoutinesRoutineid(options) {
1272
1292
  return withUnwrap(request({
1273
1293
  method: "GET",
1274
1294
  url: "/v1/routines/{routineId}",
1275
- ...config
1295
+ ...config,
1296
+ throwOnError: config.throwOnError ?? true
1276
1297
  }));
1277
1298
  }
1278
1299
  //#endregion
@@ -1286,7 +1307,8 @@ function getV1UserInfo(options) {
1286
1307
  return withUnwrap(request({
1287
1308
  method: "GET",
1288
1309
  url: "/v1/user/info",
1289
- ...config
1310
+ ...config,
1311
+ throwOnError: config.throwOnError ?? true
1290
1312
  }));
1291
1313
  }
1292
1314
  //#endregion
@@ -1300,7 +1322,8 @@ function getV1Workouts(options) {
1300
1322
  return withUnwrap(request({
1301
1323
  method: "GET",
1302
1324
  url: "/v1/workouts",
1303
- ...config
1325
+ ...config,
1326
+ throwOnError: config.throwOnError ?? true
1304
1327
  }));
1305
1328
  }
1306
1329
  //#endregion
@@ -1314,7 +1337,8 @@ function getV1WorkoutsCount(options) {
1314
1337
  return withUnwrap(request({
1315
1338
  method: "GET",
1316
1339
  url: "/v1/workouts/count",
1317
- ...config
1340
+ ...config,
1341
+ throwOnError: config.throwOnError ?? true
1318
1342
  }));
1319
1343
  }
1320
1344
  //#endregion
@@ -1329,7 +1353,8 @@ function getV1WorkoutsEvents(options) {
1329
1353
  return withUnwrap(request({
1330
1354
  method: "GET",
1331
1355
  url: "/v1/workouts/events",
1332
- ...config
1356
+ ...config,
1357
+ throwOnError: config.throwOnError ?? true
1333
1358
  }));
1334
1359
  }
1335
1360
  //#endregion
@@ -1343,7 +1368,8 @@ function getV1WorkoutsWorkoutid(options) {
1343
1368
  return withUnwrap(request({
1344
1369
  method: "GET",
1345
1370
  url: "/v1/workouts/{workoutId}",
1346
- ...config
1371
+ ...config,
1372
+ throwOnError: config.throwOnError ?? true
1347
1373
  }));
1348
1374
  }
1349
1375
  //#endregion
@@ -1357,7 +1383,8 @@ function postV1BodyMeasurements(options) {
1357
1383
  return withUnwrap(request({
1358
1384
  method: "POST",
1359
1385
  url: "/v1/body_measurements",
1360
- ...config
1386
+ ...config,
1387
+ throwOnError: config.throwOnError ?? true
1361
1388
  }));
1362
1389
  }
1363
1390
  //#endregion
@@ -1371,7 +1398,8 @@ function postV1ExerciseTemplates(options) {
1371
1398
  return withUnwrap(request({
1372
1399
  method: "POST",
1373
1400
  url: "/v1/exercise_templates",
1374
- ...config
1401
+ ...config,
1402
+ throwOnError: config.throwOnError ?? true
1375
1403
  }));
1376
1404
  }
1377
1405
  //#endregion
@@ -1385,7 +1413,8 @@ function postV1RoutineFolders(options) {
1385
1413
  return withUnwrap(request({
1386
1414
  method: "POST",
1387
1415
  url: "/v1/routine_folders",
1388
- ...config
1416
+ ...config,
1417
+ throwOnError: config.throwOnError ?? true
1389
1418
  }));
1390
1419
  }
1391
1420
  //#endregion
@@ -1399,7 +1428,8 @@ function postV1Routines(options) {
1399
1428
  return withUnwrap(request({
1400
1429
  method: "POST",
1401
1430
  url: "/v1/routines",
1402
- ...config
1431
+ ...config,
1432
+ throwOnError: config.throwOnError ?? true
1403
1433
  }));
1404
1434
  }
1405
1435
  //#endregion
@@ -1413,7 +1443,8 @@ function postV1Workouts(options) {
1413
1443
  return withUnwrap(request({
1414
1444
  method: "POST",
1415
1445
  url: "/v1/workouts",
1416
- ...config
1446
+ ...config,
1447
+ throwOnError: config.throwOnError ?? true
1417
1448
  }));
1418
1449
  }
1419
1450
  //#endregion
@@ -1427,7 +1458,8 @@ function putV1BodyMeasurementsDate(options) {
1427
1458
  return withUnwrap(request({
1428
1459
  method: "PUT",
1429
1460
  url: "/v1/body_measurements/{date}",
1430
- ...config
1461
+ ...config,
1462
+ throwOnError: config.throwOnError ?? true
1431
1463
  }));
1432
1464
  }
1433
1465
  //#endregion
@@ -1441,7 +1473,8 @@ function putV1RoutinesRoutineid(options) {
1441
1473
  return withUnwrap(request({
1442
1474
  method: "PUT",
1443
1475
  url: "/v1/routines/{routineId}",
1444
- ...config
1476
+ ...config,
1477
+ throwOnError: config.throwOnError ?? true
1445
1478
  }));
1446
1479
  }
1447
1480
  //#endregion
@@ -1455,7 +1488,8 @@ function putV1WorkoutsWorkoutid(options) {
1455
1488
  return withUnwrap(request({
1456
1489
  method: "PUT",
1457
1490
  url: "/v1/workouts/{workoutId}",
1458
- ...config
1491
+ ...config,
1492
+ throwOnError: config.throwOnError ?? true
1459
1493
  }));
1460
1494
  }
1461
1495
  //#endregion
@@ -6921,7 +6955,7 @@ function readBuildGlobal(value) {
6921
6955
  }
6922
6956
  }
6923
6957
  const buildName = readBuildGlobal(() => "hevy-mcp");
6924
- const buildVersion = readBuildGlobal(() => "6.1.18");
6958
+ const buildVersion = readBuildGlobal(() => "6.1.19");
6925
6959
  const SERVER_NAME = readBuildString(buildName, "hevy-mcp");
6926
6960
  const SERVER_VERSION = readBuildString(buildVersion, "dev");
6927
6961
  const SERVER_INSTRUCTIONS = [
@@ -7504,4 +7538,4 @@ function assertApiKey(apiKey) {
7504
7538
  //#endregion
7505
7539
  export { isHevyHttpError as _, SAFE_USER_HASH_PATTERN as a, mergeAbortSignals as c, createSafeErrorDiagnostic as d, bucketCount as f, metricEndpointIdentity as g, diagnosticEndpointIdentity as h, createHevyMcpServer as i, sanitizeDiagnosticText as l, SAFE_OBSERVATION_CODES as m, assertApiKey as n, USER_HASH_CONTEXT as o, createHevyClient as p, parseConfig as r, createExecutionProjection as s, MissingHevyApiKeyError as t, FEEDBACK_MAX_MESSAGE_LENGTH as u };
7506
7540
 
7507
- //# sourceMappingURL=config-CuS0Wd_p.mjs.map
7541
+ //# sourceMappingURL=config-4VC4uWiL.mjs.map
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // Generated with tsdown
3
3
  // https://tsdown.dev
4
- import { i as createHevyMcpServer, n as assertApiKey, p as createHevyClient } from "./config-CuS0Wd_p.mjs";
4
+ import { i as createHevyMcpServer, n as assertApiKey, p as createHevyClient } from "./config-4VC4uWiL.mjs";
5
5
  //#region src/index.ts
6
6
  async function createNodeMcpServer({ apiKey, maxGetRetries, feedbackRecorder }, _transport = "stdio", lifecycleSignal) {
7
7
  assertApiKey(apiKey);
@@ -20,7 +20,7 @@ async function createNodeMcpServer({ apiKey, maxGetRetries, feedbackRecorder },
20
20
  * does not evaluate the runtime bootstrap; it is loaded only when invoked.
21
21
  */
22
22
  async function runStdioServer() {
23
- const { runStdioServer: run } = await import("./runtime-DjV2AF4_.mjs").then((n) => n.n);
23
+ const { runStdioServer: run } = await import("./runtime-DiojMsCj.mjs").then((n) => n.n);
24
24
  return run();
25
25
  }
26
26
  /**
@@ -28,7 +28,7 @@ async function runStdioServer() {
28
28
  * does not evaluate the runtime bootstrap; it is loaded only when invoked.
29
29
  */
30
30
  async function runServer() {
31
- const { runServer: run } = await import("./runtime-DjV2AF4_.mjs").then((n) => n.n);
31
+ const { runServer: run } = await import("./runtime-DiojMsCj.mjs").then((n) => n.n);
32
32
  return run();
33
33
  }
34
34
  //#endregion
@@ -5,10 +5,10 @@
5
5
  try {
6
6
  var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
7
7
  var n = new e.Error().stack;
8
- n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "ca7b9478-af87-4262-ac1c-5697975fffe6", e._sentryDebugIdIdentifier = "sentry-dbid-ca7b9478-af87-4262-ac1c-5697975fffe6");
8
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "25f8f909-61ef-4957-9aed-ba9784c08e60", e._sentryDebugIdIdentifier = "sentry-dbid-25f8f909-61ef-4957-9aed-ba9784c08e60");
9
9
  } catch (e) {}
10
10
  })();
11
- import { _ as isHevyHttpError, a as SAFE_USER_HASH_PATTERN, c as mergeAbortSignals, d as createSafeErrorDiagnostic, f as bucketCount, g as metricEndpointIdentity, h as diagnosticEndpointIdentity, i as createHevyMcpServer, l as sanitizeDiagnosticText$1, m as SAFE_OBSERVATION_CODES, n as assertApiKey, o as USER_HASH_CONTEXT, p as createHevyClient, r as parseConfig, s as createExecutionProjection, t as MissingHevyApiKeyError, u as FEEDBACK_MAX_MESSAGE_LENGTH } from "./config-CuS0Wd_p.mjs";
11
+ import { _ as isHevyHttpError, a as SAFE_USER_HASH_PATTERN, c as mergeAbortSignals, d as createSafeErrorDiagnostic, f as bucketCount, g as metricEndpointIdentity, h as diagnosticEndpointIdentity, i as createHevyMcpServer, l as sanitizeDiagnosticText$1, m as SAFE_OBSERVATION_CODES, n as assertApiKey, o as USER_HASH_CONTEXT, p as createHevyClient, r as parseConfig, s as createExecutionProjection, t as MissingHevyApiKeyError, u as FEEDBACK_MAX_MESSAGE_LENGTH } from "./config-4VC4uWiL.mjs";
12
12
  import { ZodError, z } from "zod";
13
13
  import { Data, Duration, Effect, Exit, Fiber, Layer, Scope, Semaphore } from "effect";
14
14
  import { deserializeMessage } from "@modelcontextprotocol/server";
@@ -385,7 +385,7 @@ function parseBuildString(value, fallback) {
385
385
  return z.string().parse(value ?? fallback);
386
386
  }
387
387
  const name$1 = parseBuildString(readBuildGlobal(() => "hevy-mcp"), "hevy-mcp");
388
- const version$1 = parseBuildString(readBuildGlobal(() => "6.1.18"), "dev");
388
+ const version$1 = parseBuildString(readBuildGlobal(() => "6.1.19"), "dev");
389
389
  const telemetryEnabled = process.env.HEVY_MCP_TELEMETRY !== "0";
390
390
  const collectorToken = z.string().safeParse(readBuildGlobal(() => "NH9vOela-HYreQxAbJa68cjEmORoEKM57EvneUDVcOo")).data ?? process.env.OTEL_COLLECTOR_TOKEN ?? "";
391
391
  const COLLECTOR_ENDPOINT = "https://otel.chrisdoc.dev/v1";
@@ -425,11 +425,10 @@ function initializeTelemetry() {
425
425
  release: sentryRelease,
426
426
  tracesSampleRate: 0,
427
427
  sendClientReports: false,
428
- sendDefaultPii: false,
428
+ dataCollection: { userInfo: false },
429
429
  beforeSend: sanitizeSentryEvent,
430
430
  integrations: (integrations) => integrations.filter((integration) => integration.name !== "OnUncaughtException" && integration.name !== "OnUnhandledRejection"),
431
- skipOpenTelemetrySetup: true,
432
- registerEsmLoaderHooks: false,
431
+ enableOpenTelemetrySetup: false,
433
432
  ignoreErrors: ["EPIPE", "broken pipe"]
434
433
  });
435
434
  const spanProcessors = [];
@@ -3009,4 +3008,4 @@ async function runServer() {
3009
3008
  //#endregion
3010
3009
  export { handleFatalStartupError as i, runtime_exports as n, getSafeStartupMessage as r, runServer as t };
3011
3010
 
3012
- //# sourceMappingURL=runtime-DjV2AF4_.mjs.map
3011
+ //# sourceMappingURL=runtime-DiojMsCj.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hevy-mcp",
3
- "version": "6.1.18",
3
+ "version": "6.1.19",
4
4
  "private": false,
5
5
  "description": "A Model Context Protocol (MCP) server implementation that interfaces with the Hevy fitness tracking app and its API.",
6
6
  "repository": {
@@ -28,9 +28,9 @@
28
28
  "access": "public"
29
29
  },
30
30
  "dependencies": {
31
- "@effect/opentelemetry": "4.0.0-rc.117",
32
- "@modelcontextprotocol/node": "^2.0.0",
33
- "@modelcontextprotocol/server": "^2.0.0",
31
+ "@effect/opentelemetry": "4.0.0",
32
+ "@modelcontextprotocol/node": "2.1.1",
33
+ "@modelcontextprotocol/server": "2.3.0",
34
34
  "@opentelemetry/api": "^1.9.1",
35
35
  "@opentelemetry/exporter-metrics-otlp-http": "^0.222.0",
36
36
  "@opentelemetry/exporter-trace-otlp-http": "^0.222.0",
@@ -38,16 +38,16 @@
38
38
  "@opentelemetry/sdk-metrics": "^2.11.0",
39
39
  "@opentelemetry/sdk-trace-base": "^2.11.0",
40
40
  "@opentelemetry/sdk-trace-node": "^2.11.0",
41
- "@sentry/node": "^10.75.0",
42
- "effect": "^4.0.0-rc.117",
41
+ "@sentry/node": "^11.4.0",
42
+ "effect": "^4.0.0",
43
43
  "semver": "^7.8.5",
44
44
  "zod": "^4.6.5"
45
45
  },
46
46
  "devDependencies": {
47
- "@hevy-mcp/core": "0.2.16",
48
- "@hevy-mcp/hevy-client": "0.2.10",
49
- "@sentry/core": "^10.75.0",
50
- "@types/node": "^26.6.2",
47
+ "@hevy-mcp/core": "0.2.17",
48
+ "@hevy-mcp/hevy-client": "0.2.11",
49
+ "@sentry/core": "^11.4.0",
50
+ "@types/node": "^26.6.4",
51
51
  "cross-env": "^10.1.0",
52
52
  "tsdown": "^0.23.0",
53
53
  "typescript": "^7.0.2"
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/chrisdoc/hevy-mcp",
8
8
  "source": "github"
9
9
  },
10
- "version": "6.1.18",
10
+ "version": "6.1.19",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "hevy-mcp",
15
- "version": "6.1.18",
15
+ "version": "6.1.19",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },