untappd-mcp 2.1.1 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bundle.js CHANGED
@@ -1585,8 +1585,8 @@ var require_regexes = __commonJS({
1585
1585
  }
1586
1586
  exports.ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
1587
1587
  exports.ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
1588
- var mac3 = (delimiter) => {
1589
- const escapedDelim = util.escapeRegex(delimiter ?? ":");
1588
+ var mac3 = (delimiter2) => {
1589
+ const escapedDelim = util.escapeRegex(delimiter2 ?? ":");
1590
1590
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
1591
1591
  };
1592
1592
  exports.mac = mac3;
@@ -2282,7 +2282,7 @@ var require_versions = __commonJS({
2282
2282
  exports.version = {
2283
2283
  major: 4,
2284
2284
  minor: 6,
2285
- patch: 4
2285
+ patch: 5
2286
2286
  };
2287
2287
  (function() {
2288
2288
  var keys = Object.getOwnPropertyNames(exports);
@@ -4851,7 +4851,7 @@ var require_memoizer = __commonJS({
4851
4851
  var NONE2 = 0;
4852
4852
  var ASSUMED2 = 1;
4853
4853
  var PROVEN2 = 2;
4854
- function isRecursive2(inst, stack, resolve) {
4854
+ function isRecursive2(inst, stack, resolve3) {
4855
4855
  const cached2 = recursive2.get(inst);
4856
4856
  if (cached2 !== void 0)
4857
4857
  return cached2 ? PROVEN2 : NONE2;
@@ -4861,7 +4861,7 @@ var require_memoizer = __commonJS({
4861
4861
  let result = NONE2;
4862
4862
  const check2 = (child) => {
4863
4863
  if (result !== PROVEN2 && child?._zod) {
4864
- const answer = isRecursive2(child, stack, resolve);
4864
+ const answer = isRecursive2(child, stack, resolve3);
4865
4865
  if (answer > result)
4866
4866
  result = answer;
4867
4867
  }
@@ -4872,7 +4872,7 @@ var require_memoizer = __commonJS({
4872
4872
  const desc = Object.getOwnPropertyDescriptor(sh, key);
4873
4873
  if (spread && !desc.enumerable)
4874
4874
  continue;
4875
- const child = desc.get ? ASSUMED2 : desc.value?._zod ? isRecursive2(desc.value, stack, resolve) : NONE2;
4875
+ const child = desc.get ? ASSUMED2 : desc.value?._zod ? isRecursive2(desc.value, stack, resolve3) : NONE2;
4876
4876
  if (child > answer)
4877
4877
  answer = child;
4878
4878
  }
@@ -4936,7 +4936,7 @@ var require_memoizer = __commonJS({
4936
4936
  break;
4937
4937
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
4938
4938
  case "lazy": {
4939
- const inner = def._cachedInner ?? (resolve ? inst._zod.innerType : void 0);
4939
+ const inner = def._cachedInner ?? (resolve3 ? inst._zod.innerType : void 0);
4940
4940
  merge2(inner ? isRecursive2(inner, stack, false) : ASSUMED2);
4941
4941
  break;
4942
4942
  }
@@ -5000,7 +5000,7 @@ var require_memoizer = __commonJS({
5000
5000
  return bucket;
5001
5001
  }
5002
5002
  var handoff2;
5003
- var open2 = [];
5003
+ var open3 = [];
5004
5004
  var memo3 = {
5005
5005
  alloc(_inst, payload, empty) {
5006
5006
  const bucket = handoff2;
@@ -5009,7 +5009,7 @@ var require_memoizer = __commonJS({
5009
5009
  handoff2 = void 0;
5010
5010
  const entry = { value: empty, issues: null };
5011
5011
  bucket.set(payload.value, entry);
5012
- open2.push(entry);
5012
+ open3.push(entry);
5013
5013
  return empty;
5014
5014
  },
5015
5015
  guard(inst) {
@@ -5080,10 +5080,10 @@ var require_memoizer = __commonJS({
5080
5080
  return payload;
5081
5081
  }
5082
5082
  handoff2 = bucket;
5083
- const depth = open2.length;
5083
+ const depth = open3.length;
5084
5084
  const result = base(payload, ctx);
5085
5085
  handoff2 = void 0;
5086
- const entry = open2.length > depth ? open2.pop() : void 0;
5086
+ const entry = open3.length > depth ? open3.pop() : void 0;
5087
5087
  if (result instanceof Promise) {
5088
5088
  return result.then((r) => {
5089
5089
  if (entry)
@@ -27718,14 +27718,14 @@ function inputRequiredRoundsExceededMessage(method, maxRounds) {
27718
27718
  return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
27719
27719
  }
27720
27720
  function sleep(ms, signal) {
27721
- return new Promise((resolve, reject) => {
27721
+ return new Promise((resolve3, reject) => {
27722
27722
  if (signal?.aborted) {
27723
27723
  reject(signal.reason instanceof SdkError ? signal.reason : new SdkError(SdkErrorCode.RequestTimeout, String(signal.reason)));
27724
27724
  return;
27725
27725
  }
27726
27726
  const timer = setTimeout(() => {
27727
27727
  signal?.removeEventListener("abort", onAbort);
27728
- resolve();
27728
+ resolve3();
27729
27729
  }, ms);
27730
27730
  const onAbort = () => {
27731
27731
  clearTimeout(timer);
@@ -28519,7 +28519,7 @@ var Protocol = class {
28519
28519
  const flowStartedAt = Date.now();
28520
28520
  let onAbort;
28521
28521
  let cleanupMessageId;
28522
- return new Promise((resolve, reject) => {
28522
+ return new Promise((resolve3, reject) => {
28523
28523
  const earlyReject = (error62) => {
28524
28524
  reject(error62);
28525
28525
  };
@@ -28587,7 +28587,7 @@ var Protocol = class {
28587
28587
  }
28588
28588
  if (decoded.kind === "invalid") return reject(decoded.error);
28589
28589
  if (decoded.kind === "input_required") {
28590
- if (options?.allowInputRequired === true) return resolve(manualInputRequiredValue(decoded));
28590
+ if (options?.allowInputRequired === true) return resolve3(manualInputRequiredValue(decoded));
28591
28591
  const flow = {
28592
28592
  codec: codec2,
28593
28593
  request,
@@ -28599,11 +28599,11 @@ var Protocol = class {
28599
28599
  params
28600
28600
  }, resultSchema, legOptions)
28601
28601
  };
28602
- return resolve(this._resolveNonCompleteResult(decoded, flow));
28602
+ return resolve3(this._resolveNonCompleteResult(decoded, flow));
28603
28603
  }
28604
28604
  const result = decoded.result;
28605
28605
  validateStandardSchema(resultSchema, result).then((parseResult) => {
28606
- if (parseResult.success) resolve(parseResult.data);
28606
+ if (parseResult.success) resolve3(parseResult.data);
28607
28607
  else reject(new SdkError(SdkErrorCode.InvalidResult, `Invalid result for ${request.method}: ${parseResult.error}`));
28608
28608
  }, reject);
28609
28609
  });
@@ -31515,7 +31515,7 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin(((exports) => {
31515
31515
  ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
31516
31516
  const schOrFunc = root.refs[ref];
31517
31517
  if (schOrFunc) return schOrFunc;
31518
- let _sch = resolve.call(this, root, ref);
31518
+ let _sch = resolve3.call(this, root, ref);
31519
31519
  if (_sch === void 0) {
31520
31520
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
31521
31521
  const { schemaId } = this.opts;
@@ -31541,7 +31541,7 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin(((exports) => {
31541
31541
  function sameSchemaEnv(s1, s2) {
31542
31542
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
31543
31543
  }
31544
- function resolve(root, ref) {
31544
+ function resolve3(root, ref) {
31545
31545
  let sch;
31546
31546
  while (typeof (sch = this.refs[ref]) == "string") ref = sch;
31547
31547
  return sch || this.schemas[ref] || resolveSchema.call(this, root, ref);
@@ -31991,47 +31991,47 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
31991
31991
  else if (typeof uri === "object") uri = parse3(serialize(uri, options), options);
31992
31992
  return uri;
31993
31993
  }
31994
- function resolve(baseURI, relativeURI, options) {
31994
+ function resolve3(baseURI, relativeURI, options) {
31995
31995
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
31996
31996
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
31997
31997
  schemelessOptions.skipEscape = true;
31998
31998
  return serialize(resolved, schemelessOptions);
31999
31999
  }
32000
- function resolveComponent(base, relative, options, skipNormalization) {
32000
+ function resolveComponent(base, relative2, options, skipNormalization) {
32001
32001
  const target = {};
32002
32002
  if (!skipNormalization) {
32003
32003
  base = parse3(serialize(base, options), options);
32004
- relative = parse3(serialize(relative, options), options);
32004
+ relative2 = parse3(serialize(relative2, options), options);
32005
32005
  }
32006
32006
  options = options || {};
32007
- if (!options.tolerant && relative.scheme) {
32008
- target.scheme = relative.scheme;
32009
- target.userinfo = relative.userinfo;
32010
- target.host = relative.host;
32011
- target.port = relative.port;
32012
- target.path = removeDotSegments(relative.path || "");
32013
- target.query = relative.query;
32007
+ if (!options.tolerant && relative2.scheme) {
32008
+ target.scheme = relative2.scheme;
32009
+ target.userinfo = relative2.userinfo;
32010
+ target.host = relative2.host;
32011
+ target.port = relative2.port;
32012
+ target.path = removeDotSegments(relative2.path || "");
32013
+ target.query = relative2.query;
32014
32014
  } else {
32015
- if (relative.userinfo !== void 0 || relative.host !== void 0 || relative.port !== void 0) {
32016
- target.userinfo = relative.userinfo;
32017
- target.host = relative.host;
32018
- target.port = relative.port;
32019
- target.path = removeDotSegments(relative.path || "");
32020
- target.query = relative.query;
32015
+ if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
32016
+ target.userinfo = relative2.userinfo;
32017
+ target.host = relative2.host;
32018
+ target.port = relative2.port;
32019
+ target.path = removeDotSegments(relative2.path || "");
32020
+ target.query = relative2.query;
32021
32021
  } else {
32022
- if (!relative.path) {
32022
+ if (!relative2.path) {
32023
32023
  target.path = base.path;
32024
- if (relative.query !== void 0) target.query = relative.query;
32024
+ if (relative2.query !== void 0) target.query = relative2.query;
32025
32025
  else target.query = base.query;
32026
32026
  } else {
32027
- if (relative.path[0] === "/") target.path = removeDotSegments(relative.path);
32027
+ if (relative2.path[0] === "/") target.path = removeDotSegments(relative2.path);
32028
32028
  else {
32029
- if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) target.path = "/" + relative.path;
32030
- else if (!base.path) target.path = relative.path;
32031
- else target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative.path;
32029
+ if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) target.path = "/" + relative2.path;
32030
+ else if (!base.path) target.path = relative2.path;
32031
+ else target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative2.path;
32032
32032
  target.path = removeDotSegments(target.path);
32033
32033
  }
32034
- target.query = relative.query;
32034
+ target.query = relative2.query;
32035
32035
  }
32036
32036
  target.userinfo = base.userinfo;
32037
32037
  target.host = base.host;
@@ -32039,7 +32039,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
32039
32039
  }
32040
32040
  target.scheme = base.scheme;
32041
32041
  }
32042
- target.fragment = relative.fragment;
32042
+ target.fragment = relative2.fragment;
32043
32043
  return target;
32044
32044
  }
32045
32045
  function equal(uriA, uriB, options) {
@@ -32165,7 +32165,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
32165
32165
  const fastUri = {
32166
32166
  SCHEMES,
32167
32167
  normalize,
32168
- resolve,
32168
+ resolve: resolve3,
32169
32169
  resolveComponent,
32170
32170
  equal,
32171
32171
  serialize,
@@ -37538,7 +37538,7 @@ var StdioServerTransport = class {
37538
37538
  }
37539
37539
  send(message) {
37540
37540
  if (this._closed) return Promise.reject(/* @__PURE__ */ new Error("StdioServerTransport is closed"));
37541
- return new Promise((resolve, reject) => {
37541
+ return new Promise((resolve3, reject) => {
37542
37542
  const json2 = serializeMessage(message);
37543
37543
  let settled = false;
37544
37544
  const onError = (error62) => {
@@ -37553,14 +37553,14 @@ var StdioServerTransport = class {
37553
37553
  settled = true;
37554
37554
  this._stdout.off("error", onError);
37555
37555
  this._stdout.off("drain", onDrain);
37556
- resolve();
37556
+ resolve3();
37557
37557
  };
37558
37558
  this._stdout.once("error", onError);
37559
37559
  if (this._stdout.write(json2)) {
37560
37560
  if (settled) return;
37561
37561
  settled = true;
37562
37562
  this._stdout.off("error", onError);
37563
- resolve();
37563
+ resolve3();
37564
37564
  } else if (!settled) this._stdout.once("drain", onDrain);
37565
37565
  });
37566
37566
  }
@@ -37614,14 +37614,14 @@ var StdioConnectionChannel = class {
37614
37614
  */
37615
37615
  async whenRequestsAnswered(timeoutMs) {
37616
37616
  if (this._closed || this._pendingRequests.size === 0) return true;
37617
- return await new Promise((resolve) => {
37617
+ return await new Promise((resolve3) => {
37618
37618
  const waiter = () => {
37619
37619
  clearTimeout(timer);
37620
- resolve(true);
37620
+ resolve3(true);
37621
37621
  };
37622
37622
  const timer = setTimeout(() => {
37623
37623
  this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
37624
- resolve(false);
37624
+ resolve3(false);
37625
37625
  }, timeoutMs);
37626
37626
  this._drainWaiters.push(waiter);
37627
37627
  });
@@ -38019,13 +38019,28 @@ var API_KEY_RE = new RegExp([
38019
38019
  "whsec_[A-Za-z0-9]{16,}\\b"
38020
38020
  // webhook signing secret (Stripe-style)
38021
38021
  ].map((p) => `\\b${p}`).join("|"), "g");
38022
- var QUERY_SECRET_RE = /([?&](?:access_token|refresh_token|client_secret|api_?key|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
38022
+ var QUERY_SECRET_RE = /([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
38023
+ var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
38024
+ var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
38025
+ var HEADER_SECRET_RE = /(\b(?:x[-_][a-z0-9_-]*?(?:api[-_]?key|token|secret|auth)[a-z0-9_-]*|api[-_]?key)\s*:\s*)[^\s,;"'<>`]+/gi;
38023
38026
  var AWS_SIGV4_RE = /([?&]X-Amz-(?:Signature|Security-Token|Credential)=)[^&#\s"'<>`]+/gi;
38024
- var JSON_SECRET_KEYS = "access_token|refresh_token|client_secret|api_?key|password|passwd|secret|token";
38025
- var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")[^"]*(")`, "gi");
38026
- var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')[^']*(')`, "gi");
38027
+ var JSON_SECRET_KEYS = [
38028
+ "(?:access|refresh|id|auth|session|bearer|csrf|xsrf)[_-]?token",
38029
+ "client[_-]?secret",
38030
+ "api[_-]?secret",
38031
+ "(?:x[_-])?api[_-]?key",
38032
+ "private[_-]?key",
38033
+ "x[_-][a-z0-9_-]*?(?:api[_-]?key|token|secret|auth)[a-z0-9_-]*",
38034
+ "(?:proxy-)?authorization",
38035
+ "password",
38036
+ "passwd",
38037
+ "secret",
38038
+ "token"
38039
+ ].join("|");
38040
+ var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")(?:[^"\\\\]|\\\\.)*(")`, "gi");
38041
+ var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')(?:[^'\\\\]|\\\\.)*(')`, "gi");
38027
38042
  function redactSecrets(text) {
38028
- return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
38043
+ return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(HEADER_SECRET_RE, "$1[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(OAUTH_CODE_RE, "$1[REDACTED]").replace(FORM_PASSWORD_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
38029
38044
  }
38030
38045
  function truncateErrorMessage(text, max = DEFAULT_ERROR_MESSAGE_MAX) {
38031
38046
  const str2 = text === null || text === void 0 ? "" : String(text);
@@ -40037,8 +40052,8 @@ function emoji() {
40037
40052
  }
40038
40053
  var ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
40039
40054
  var ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
40040
- var mac = (delimiter) => {
40041
- const escapedDelim = escapeRegex(delimiter ?? ":");
40055
+ var mac = (delimiter2) => {
40056
+ const escapedDelim = escapeRegex(delimiter2 ?? ":");
40042
40057
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
40043
40058
  };
40044
40059
  var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
@@ -40630,7 +40645,7 @@ ${content.join("\n")}
40630
40645
  var version = {
40631
40646
  major: 4,
40632
40647
  minor: 6,
40633
- patch: 4
40648
+ patch: 5
40634
40649
  };
40635
40650
 
40636
40651
  // node_modules/zod/v4/core/schemas.js
@@ -43066,7 +43081,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
43066
43081
  var NONE = 0;
43067
43082
  var ASSUMED = 1;
43068
43083
  var PROVEN = 2;
43069
- function isRecursive(inst, stack, resolve) {
43084
+ function isRecursive(inst, stack, resolve3) {
43070
43085
  const cached2 = recursive.get(inst);
43071
43086
  if (cached2 !== void 0)
43072
43087
  return cached2 ? PROVEN : NONE;
@@ -43076,7 +43091,7 @@ function isRecursive(inst, stack, resolve) {
43076
43091
  let result = NONE;
43077
43092
  const check2 = (child) => {
43078
43093
  if (result !== PROVEN && child?._zod) {
43079
- const answer = isRecursive(child, stack, resolve);
43094
+ const answer = isRecursive(child, stack, resolve3);
43080
43095
  if (answer > result)
43081
43096
  result = answer;
43082
43097
  }
@@ -43087,7 +43102,7 @@ function isRecursive(inst, stack, resolve) {
43087
43102
  const desc = Object.getOwnPropertyDescriptor(sh, key);
43088
43103
  if (spread && !desc.enumerable)
43089
43104
  continue;
43090
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve) : NONE;
43105
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve3) : NONE;
43091
43106
  if (child > answer)
43092
43107
  answer = child;
43093
43108
  }
@@ -43151,7 +43166,7 @@ function isRecursive(inst, stack, resolve) {
43151
43166
  break;
43152
43167
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
43153
43168
  case "lazy": {
43154
- const inner = def._cachedInner ?? (resolve ? inst._zod.innerType : void 0);
43169
+ const inner = def._cachedInner ?? (resolve3 ? inst._zod.innerType : void 0);
43155
43170
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
43156
43171
  break;
43157
43172
  }
@@ -57844,13 +57859,47 @@ async function createMcpServer(opts) {
57844
57859
  }
57845
57860
  return server;
57846
57861
  }
57862
+ var DEFAULT_SHUTDOWN_TIMEOUT_MS = 5e3;
57863
+ var DEFAULT_REPEAT_SIGNAL_GRACE_MS = 500;
57847
57864
  function withGracefulShutdown(target, opts = {}) {
57848
57865
  const shouldExit = opts.exit ?? true;
57866
+ if (opts.timeoutMs !== void 0 && !(Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0)) {
57867
+ throw new RangeError("withGracefulShutdown: timeoutMs must be a finite number greater than 0.");
57868
+ }
57869
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
57870
+ if (opts.repeatSignalGraceMs !== void 0 && !(Number.isFinite(opts.repeatSignalGraceMs) && opts.repeatSignalGraceMs >= 0)) {
57871
+ throw new RangeError("withGracefulShutdown: repeatSignalGraceMs must be a finite number >= 0.");
57872
+ }
57873
+ const repeatGraceMs = opts.repeatSignalGraceMs ?? DEFAULT_REPEAT_SIGNAL_GRACE_MS;
57849
57874
  let shuttingDown = false;
57875
+ let firstSignalAt = 0;
57876
+ let exited = false;
57877
+ const exitOnce = () => {
57878
+ if (exited)
57879
+ return;
57880
+ exited = true;
57881
+ process.exit(0);
57882
+ };
57850
57883
  const handler = (signal) => {
57851
- if (shuttingDown)
57884
+ if (shuttingDown) {
57885
+ if (Date.now() - firstSignalAt < repeatGraceMs)
57886
+ return;
57887
+ if (shouldExit) {
57888
+ console.error(`[mcp-utils] second ${signal} during shutdown \u2014 exiting now`);
57889
+ exitOnce();
57890
+ }
57852
57891
  return;
57892
+ }
57853
57893
  shuttingDown = true;
57894
+ firstSignalAt = Date.now();
57895
+ let timer;
57896
+ if (shouldExit) {
57897
+ timer = setTimeout(() => {
57898
+ console.error(`[mcp-utils] graceful shutdown on ${signal} did not finish in ${timeoutMs}ms \u2014 exiting`);
57899
+ exitOnce();
57900
+ }, timeoutMs);
57901
+ timer.unref?.();
57902
+ }
57854
57903
  void (async () => {
57855
57904
  try {
57856
57905
  if (opts.onSignal)
@@ -57859,8 +57908,10 @@ function withGracefulShutdown(target, opts = {}) {
57859
57908
  } catch (err) {
57860
57909
  console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
57861
57910
  } finally {
57911
+ if (timer !== void 0)
57912
+ clearTimeout(timer);
57862
57913
  if (shouldExit)
57863
- process.exit(0);
57914
+ exitOnce();
57864
57915
  }
57865
57916
  })();
57866
57917
  };
@@ -57890,6 +57941,8 @@ function runMcp(opts) {
57890
57941
  }
57891
57942
 
57892
57943
  // node_modules/@chrischall/mcp-utils/dist/config/index.js
57944
+ import { homedir } from "node:os";
57945
+ import { isAbsolute, join, resolve } from "node:path";
57893
57946
  var PLACEHOLDER_RE = /^\$\{[^}]*\}$/;
57894
57947
  function readEnvVar(key, opts = {}) {
57895
57948
  const env = opts.env ?? process.env;
@@ -57902,6 +57955,15 @@ function readEnvVar(key, opts = {}) {
57902
57955
  }
57903
57956
  return opts.default;
57904
57957
  }
57958
+ function expandPath(p) {
57959
+ let expanded = p;
57960
+ if (p === "~") {
57961
+ expanded = homedir();
57962
+ } else if (p.startsWith("~/")) {
57963
+ expanded = join(homedir(), p.slice(2));
57964
+ }
57965
+ return isAbsolute(expanded) ? expanded : resolve(expanded);
57966
+ }
57905
57967
  async function loadDotenvSafely(opts = {}) {
57906
57968
  try {
57907
57969
  const mod = await import(
@@ -57921,10 +57983,67 @@ async function loadDotenvSafely(opts = {}) {
57921
57983
 
57922
57984
  // node_modules/@chrischall/mcp-utils/dist/fs/index.js
57923
57985
  import { openAsBlob } from "node:fs";
57986
+ import { open as open2 } from "node:fs/promises";
57987
+
57988
+ // node_modules/@chrischall/mcp-utils/dist/fs/confine.js
57989
+ import { existsSync, realpathSync } from "node:fs";
57990
+ import { dirname, isAbsolute as isAbsolute2, relative, resolve as resolve2, sep } from "node:path";
57991
+ function isInside(root, target) {
57992
+ const rel = relative(root, target);
57993
+ return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute2(rel);
57994
+ }
57995
+ function realPathAllowingMissing(p) {
57996
+ const abs = resolve2(p);
57997
+ let existing = abs;
57998
+ const tail = [];
57999
+ while (!existsSync(existing)) {
58000
+ const parent = dirname(existing);
58001
+ if (parent === existing)
58002
+ break;
58003
+ tail.unshift(existing.slice(parent.length).replace(/^[/\\]+/, ""));
58004
+ existing = parent;
58005
+ }
58006
+ let real;
58007
+ try {
58008
+ real = realpathSync(existing);
58009
+ } catch {
58010
+ real = existing;
58011
+ }
58012
+ return tail.length > 0 ? resolve2(real, ...tail) : real;
58013
+ }
58014
+ function assertPathWithinRoots(path, roots) {
58015
+ const target = realPathAllowingMissing(expandPath(path));
58016
+ for (const root of roots) {
58017
+ if (isInside(realPathAllowingMissing(expandPath(root)), target))
58018
+ return target;
58019
+ }
58020
+ throw new Error(`Path is outside the allowed directories: ${path}`);
58021
+ }
58022
+
58023
+ // node_modules/@chrischall/mcp-utils/dist/fs/output.js
58024
+ function sniffMimeBytes(bytes) {
58025
+ const buf = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
58026
+ if (buf.length >= 8 && buf.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
58027
+ return "image/png";
58028
+ }
58029
+ if (buf.length >= 3 && buf[0] === 255 && buf[1] === 216 && buf[2] === 255) {
58030
+ return "image/jpeg";
58031
+ }
58032
+ if (buf.length >= 12 && buf.subarray(0, 4).toString("latin1") === "RIFF" && buf.subarray(8, 12).toString("latin1") === "WEBP") {
58033
+ return "image/webp";
58034
+ }
58035
+ if (buf.length >= 4 && buf.subarray(0, 4).toString("latin1") === "GIF8") {
58036
+ return "image/gif";
58037
+ }
58038
+ return void 0;
58039
+ }
58040
+
58041
+ // node_modules/@chrischall/mcp-utils/dist/fs/index.js
57924
58042
  async function fileBlob(path, opts = {}) {
58043
+ const target = opts.allowedRoots ? assertPathWithinRoots(path, opts.allowedRoots) : path;
57925
58044
  let blob;
57926
58045
  try {
57927
- blob = await openAsBlob(path, opts.type !== void 0 ? { type: opts.type } : void 0);
58046
+ blob = await openAsBlob(target, opts.type !== void 0 ? { type: opts.type } : void 0);
57928
58047
  } catch {
57929
58048
  throw new Error(`Cannot read file for upload: ${path}`);
57930
58049
  }
@@ -57933,6 +58052,22 @@ async function fileBlob(path, opts = {}) {
57933
58052
  }
57934
58053
  return blob;
57935
58054
  }
58055
+ async function readFileHead(path, bytes, opts = {}) {
58056
+ const target = opts.allowedRoots ? assertPathWithinRoots(path, opts.allowedRoots) : path;
58057
+ let fh;
58058
+ try {
58059
+ fh = await open2(target, "r");
58060
+ } catch {
58061
+ throw new Error(`Cannot read file: ${path}`);
58062
+ }
58063
+ try {
58064
+ const buf = Buffer.alloc(bytes);
58065
+ const { bytesRead } = await fh.read(buf, 0, bytes, 0);
58066
+ return buf.subarray(0, bytesRead);
58067
+ } finally {
58068
+ await fh.close();
58069
+ }
58070
+ }
57936
58071
 
57937
58072
  // node_modules/@chrischall/mcp-utils/dist/http/index.js
57938
58073
  function buildQueryString(params) {
@@ -57978,9 +58113,12 @@ var pageSchema = {
57978
58113
  page_size: external_exports.number().int().min(1).max(200).default(50).describe("Number of items per page (1-200).")
57979
58114
  };
57980
58115
  function toolAnnotations(opts = {}) {
58116
+ if (opts.readOnly === true && opts.destructive === true) {
58117
+ throw new Error("toolAnnotations: readOnly: true contradicts destructive: true \u2014 a destructive tool is not read-only.");
58118
+ }
57981
58119
  return {
57982
58120
  ...opts.title !== void 0 ? { title: opts.title } : {},
57983
- readOnlyHint: opts.readOnly ?? true,
58121
+ readOnlyHint: opts.readOnly ?? opts.destructive === void 0,
57984
58122
  ...opts.idempotent !== void 0 ? { idempotentHint: opts.idempotent } : {},
57985
58123
  ...opts.destructive !== void 0 ? { destructiveHint: opts.destructive } : {},
57986
58124
  ...opts.openWorld !== void 0 ? { openWorldHint: opts.openWorld } : {}
@@ -57988,14 +58126,14 @@ function toolAnnotations(opts = {}) {
57988
58126
  }
57989
58127
 
57990
58128
  // src/version.ts
57991
- var VERSION = "2.1.1";
58129
+ var VERSION = "2.1.3";
57992
58130
 
57993
58131
  // src/client.ts
57994
- import { dirname, join } from "path";
58132
+ import { dirname as dirname2, join as join2 } from "path";
57995
58133
  import { fileURLToPath } from "url";
57996
58134
  if (import.meta.url) {
57997
- const __dirname = dirname(fileURLToPath(import.meta.url));
57998
- await loadDotenvSafely({ path: join(__dirname, "..", ".env"), override: false });
58135
+ const __dirname = dirname2(fileURLToPath(import.meta.url));
58136
+ await loadDotenvSafely({ path: join2(__dirname, "..", ".env"), override: false });
57999
58137
  }
58000
58138
  var BASE_URL = "https://api.untappd.com/v4";
58001
58139
  var SERVICE = "Untappd";
@@ -58495,7 +58633,9 @@ var SCHEMA_STATEMENTS = [
58495
58633
  beers_offset INTEGER,
58496
58634
  beers_total INTEGER,
58497
58635
  beers_complete INTEGER NOT NULL DEFAULT 0,
58498
- served_backfill_last INTEGER NOT NULL DEFAULT 0
58636
+ served_backfill_last INTEGER NOT NULL DEFAULT 0,
58637
+ beers_accepted_gap INTEGER NOT NULL DEFAULT 0,
58638
+ beers_rescan_target INTEGER
58499
58639
  )`
58500
58640
  ];
58501
58641
  var MIGRATIONS = [
@@ -58504,7 +58644,9 @@ var MIGRATIONS = [
58504
58644
  "ALTER TABLE sync_state ADD COLUMN beers_offset INTEGER",
58505
58645
  "ALTER TABLE sync_state ADD COLUMN beers_total INTEGER",
58506
58646
  "ALTER TABLE sync_state ADD COLUMN beers_complete INTEGER NOT NULL DEFAULT 0",
58507
- "ALTER TABLE sync_state ADD COLUMN served_backfill_last INTEGER NOT NULL DEFAULT 0"
58647
+ "ALTER TABLE sync_state ADD COLUMN served_backfill_last INTEGER NOT NULL DEFAULT 0",
58648
+ "ALTER TABLE sync_state ADD COLUMN beers_accepted_gap INTEGER NOT NULL DEFAULT 0",
58649
+ "ALTER TABLE sync_state ADD COLUMN beers_rescan_target INTEGER"
58508
58650
  ];
58509
58651
  var CheckinStoreCore = class {
58510
58652
  constructor(db) {
@@ -58626,7 +58768,9 @@ var CheckinStoreCore = class {
58626
58768
  beers_offset: num(row.beers_offset),
58627
58769
  beers_total: num(row.beers_total),
58628
58770
  beers_complete: Number(row.beers_complete) === 1,
58629
- served_backfill_last: Number(row.served_backfill_last) === 1
58771
+ served_backfill_last: Number(row.served_backfill_last) === 1,
58772
+ beers_accepted_gap: num(row.beers_accepted_gap) ?? 0,
58773
+ beers_rescan_target: num(row.beers_rescan_target)
58630
58774
  };
58631
58775
  }
58632
58776
  /**
@@ -58650,19 +58794,22 @@ var CheckinStoreCore = class {
58650
58794
  beers_offset: pick2("beers_offset", null),
58651
58795
  beers_total: pick2("beers_total", null),
58652
58796
  beers_complete: pick2("beers_complete", false),
58653
- served_backfill_last: pick2("served_backfill_last", false)
58797
+ served_backfill_last: pick2("served_backfill_last", false),
58798
+ beers_accepted_gap: pick2("beers_accepted_gap", 0),
58799
+ beers_rescan_target: pick2("beers_rescan_target", null)
58654
58800
  };
58655
58801
  this.db.run(
58656
58802
  `INSERT INTO sync_state
58657
- (username, oldest_max_id, newest_checkin_id, catchup_max_id, last_synced_at, backfill_complete, total_checkins, checkins_truncated, beers_offset, beers_total, beers_complete, served_backfill_last)
58658
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
58803
+ (username, oldest_max_id, newest_checkin_id, catchup_max_id, last_synced_at, backfill_complete, total_checkins, checkins_truncated, beers_offset, beers_total, beers_complete, served_backfill_last, beers_accepted_gap, beers_rescan_target)
58804
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
58659
58805
  ON CONFLICT(username) DO UPDATE SET
58660
58806
  oldest_max_id=excluded.oldest_max_id, newest_checkin_id=excluded.newest_checkin_id,
58661
58807
  catchup_max_id=excluded.catchup_max_id, last_synced_at=excluded.last_synced_at,
58662
58808
  backfill_complete=excluded.backfill_complete, total_checkins=excluded.total_checkins,
58663
58809
  checkins_truncated=excluded.checkins_truncated, beers_offset=excluded.beers_offset,
58664
58810
  beers_total=excluded.beers_total, beers_complete=excluded.beers_complete,
58665
- served_backfill_last=excluded.served_backfill_last`,
58811
+ served_backfill_last=excluded.served_backfill_last, beers_accepted_gap=excluded.beers_accepted_gap,
58812
+ beers_rescan_target=excluded.beers_rescan_target`,
58666
58813
  [
58667
58814
  key,
58668
58815
  next.oldest_max_id,
@@ -58675,7 +58822,9 @@ var CheckinStoreCore = class {
58675
58822
  next.beers_offset,
58676
58823
  next.beers_total,
58677
58824
  next.beers_complete ? 1 : 0,
58678
- next.served_backfill_last ? 1 : 0
58825
+ next.served_backfill_last ? 1 : 0,
58826
+ next.beers_accepted_gap,
58827
+ next.beers_rescan_target
58679
58828
  ]
58680
58829
  );
58681
58830
  }
@@ -59637,19 +59786,108 @@ function registerWishlistTools(server, client2) {
59637
59786
  }
59638
59787
 
59639
59788
  // src/tools/checkin.ts
59640
- import { existsSync } from "node:fs";
59641
- import { extname } from "node:path";
59789
+ import { realpathSync as realpathSync2, statSync } from "node:fs";
59790
+ import { delimiter, extname } from "node:path";
59642
59791
  var CheckinIdSchema = external_exports.number().int().positive().describe("Untappd check-in id");
59643
59792
  var PHOTO_CONTENT_TYPES = { jpg: "image/jpeg", png: "image/png" };
59644
59793
  function photoExt(path) {
59645
59794
  const ext = extname(path).slice(1).toLowerCase();
59646
59795
  return ext === "jpeg" ? "jpg" : ext;
59647
59796
  }
59797
+ var MAX_PHOTO_BYTES = 15 * 1024 * 1024;
59798
+ function photoRoots() {
59799
+ const raw = readEnvVar("UNTAPPD_PHOTO_DIR");
59800
+ const roots = raw?.split(delimiter).filter(Boolean);
59801
+ return roots && roots.length > 0 ? roots : void 0;
59802
+ }
59803
+ async function checkPhoto(photoPath) {
59804
+ const ext = photoExt(photoPath);
59805
+ if (!(ext in PHOTO_CONTENT_TYPES)) {
59806
+ throw createHelpfulError(`Unsupported photo type "${ext || "(none)"}".`, {
59807
+ hint: "Attach a .jpg, .jpeg, or .png file."
59808
+ });
59809
+ }
59810
+ const roots = photoRoots();
59811
+ if (roots) {
59812
+ try {
59813
+ assertPathWithinRoots(photoPath, roots);
59814
+ } catch {
59815
+ throw createHelpfulError("The photo is outside the allowed photo directory.", {
59816
+ hint: "UNTAPPD_PHOTO_DIR restricts which files can be attached to a check-in."
59817
+ });
59818
+ }
59819
+ }
59820
+ let real;
59821
+ let size;
59822
+ try {
59823
+ real = realpathSync2(photoPath);
59824
+ const st = statSync(real);
59825
+ if (!st.isFile()) throw new Error("not a file");
59826
+ size = st.size;
59827
+ } catch {
59828
+ throw new McpToolError("Photo file not found or not readable.");
59829
+ }
59830
+ if (size > MAX_PHOTO_BYTES) {
59831
+ throw createHelpfulError(`Photo is too large (${size} bytes; the limit is ${MAX_PHOTO_BYTES}).`, {
59832
+ hint: "Attach a normal-sized JPEG or PNG photo."
59833
+ });
59834
+ }
59835
+ const sniffed = sniffMimeBytes(await readFileHead(real, 16));
59836
+ if (sniffed !== PHOTO_CONTENT_TYPES[ext]) {
59837
+ throw createHelpfulError(
59838
+ sniffed === "image/jpeg" || sniffed === "image/png" ? `Photo content (${sniffed}) does not match its .${ext} extension.` : "The file is not a JPEG or PNG image.",
59839
+ { hint: "Attach a real .jpg/.jpeg or .png photo." }
59840
+ );
59841
+ }
59842
+ return { path: real, size_bytes: size, ext, content_type: sniffed };
59843
+ }
59648
59844
  var RatingSchema = external_exports.number().min(0).max(5).refine((r) => Math.round(r * 4) === r * 4, { message: "rating must be a multiple of 0.25" });
59649
- function localTimezone() {
59650
- const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
59651
- const gmt_offset = -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60;
59652
- return { timezone, gmt_offset };
59845
+ function gmtOffsetHours(timeZone, at) {
59846
+ const name = new Intl.DateTimeFormat("en-US", { timeZone, timeZoneName: "longOffset" }).formatToParts(at).find((p) => p.type === "timeZoneName")?.value;
59847
+ const m = /GMT([+-])(\d{1,2})(?::(\d{2}))?/.exec(name ?? "");
59848
+ if (!m) return 0;
59849
+ const hours = Number(m[2]) + Number(m[3] ?? 0) / 60;
59850
+ return m[1] === "-" ? -hours : hours;
59851
+ }
59852
+ function checkinTimezone(requested) {
59853
+ const timezone = requested ?? readEnvVar("UNTAPPD_TIMEZONE") ?? (Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC");
59854
+ try {
59855
+ return { timezone, gmt_offset: gmtOffsetHours(timezone, /* @__PURE__ */ new Date()) };
59856
+ } catch {
59857
+ throw createHelpfulError(`Unknown timezone "${timezone}".`, {
59858
+ hint: 'Pass an IANA timezone name such as "America/New_York" or "Europe/London".'
59859
+ });
59860
+ }
59861
+ }
59862
+ var RECOVERY_WINDOW_MS = 3e4;
59863
+ async function nonIdempotentWrite(run, unknownOutcome) {
59864
+ try {
59865
+ return await run();
59866
+ } catch (e) {
59867
+ if (e instanceof UnreachableError) throw new McpToolError(unknownOutcome);
59868
+ throw e;
59869
+ }
59870
+ }
59871
+ async function findRecentCheckin(client2, bid, sentAt) {
59872
+ const self = client2.loginName;
59873
+ if (!self) return null;
59874
+ try {
59875
+ const data = await client2.get(
59876
+ `/user/checkins/${encodeURIComponent(self)}`,
59877
+ { limit: 5 }
59878
+ );
59879
+ const matches = [];
59880
+ for (const it of data?.checkins?.items ?? []) {
59881
+ const c = it;
59882
+ const at = Date.parse(c.created_at ?? "");
59883
+ if (c.beer?.bid === bid && typeof c.checkin_id === "number" && at >= sentAt - RECOVERY_WINDOW_MS) {
59884
+ matches.push(c.checkin_id);
59885
+ }
59886
+ }
59887
+ if (matches.length === 1) return matches[0];
59888
+ } catch {
59889
+ }
59890
+ return null;
59653
59891
  }
59654
59892
  function registerCheckinTools(server, client2) {
59655
59893
  server.registerTool(
@@ -59672,7 +59910,10 @@ function registerCheckinTools(server, client2) {
59672
59910
  note: "Dry run \u2014 re-run with confirm: true to toggle your toast on this check-in."
59673
59911
  });
59674
59912
  }
59675
- const data = await client2.write("POST", `/checkin/toast/${checkin_id}`);
59913
+ const data = await nonIdempotentWrite(
59914
+ () => client2.write("POST", `/checkin/toast/${checkin_id}`),
59915
+ `Untappd did not answer the toast request for check-in ${checkin_id} in time, so it may or may not have been applied. Toast is a TOGGLE: retrying could remove a toast that did land. Check with untappd_checkin_info (checkin_id ${checkin_id}) before retrying.`
59916
+ );
59676
59917
  return minifiedResult({ toggled: true, checkin_id, result: data?.result, like_type: data?.like_type });
59677
59918
  }
59678
59919
  );
@@ -59698,7 +59939,10 @@ function registerCheckinTools(server, client2) {
59698
59939
  note: "Dry run \u2014 re-run with confirm: true to post this comment to your Untappd account."
59699
59940
  });
59700
59941
  }
59701
- const data = await client2.write("POST", `/checkin/addcomment/${checkin_id}`, { form: { comment } });
59942
+ const data = await nonIdempotentWrite(
59943
+ () => client2.write("POST", `/checkin/addcomment/${checkin_id}`, { form: { comment } }),
59944
+ `Untappd did not answer in time, so the comment may have been posted already. Check the comments with untappd_checkin_info (checkin_id ${checkin_id}) before retrying, or it may be posted twice.`
59945
+ );
59702
59946
  return minifiedResult({ posted: true, checkin_id, response: data });
59703
59947
  }
59704
59948
  );
@@ -59761,24 +60005,21 @@ function registerCheckinTools(server, client2) {
59761
60005
  rating: RatingSchema.optional().describe("Rating 0\u20135 in 0.25 increments (omit for no rating)"),
59762
60006
  shout: external_exports.string().max(2e3).optional().describe("Optional shout / comment text for the check-in"),
59763
60007
  foursquare_id: external_exports.string().optional().describe("Optional Foursquare venue id to tag the check-in location"),
59764
- photo_path: external_exports.string().optional().describe("Optional path to a local JPEG/PNG photo to attach to the check-in"),
60008
+ photo_path: external_exports.string().optional().describe(
60009
+ "Optional path to a local JPEG/PNG photo (max 15 MB) to attach \u2014 it is published publicly. Only use a file the user explicitly chose; the dry run shows the resolved path and size for them to confirm."
60010
+ ),
59765
60011
  geolat: external_exports.number().optional().describe("Optional latitude of the check-in"),
59766
60012
  geolng: external_exports.number().optional().describe("Optional longitude of the check-in"),
59767
60013
  container_id: external_exports.number().int().optional().describe("Optional serving container id (e.g. 1 = draft, 2 = bottle, 3 = can)"),
60014
+ timezone: external_exports.string().min(1).optional().describe(
60015
+ "The drinker's IANA timezone (e.g. America/New_York), which sets the check-in's local time. Defaults to UNTAPPD_TIMEZONE, else the server's own zone \u2014 which on a hosted connector is usually UTC, so pass it when you know where the user is."
60016
+ ),
59768
60017
  confirm: schemaConfirm
59769
60018
  })
59770
60019
  },
59771
- async ({ bid, rating, shout, foursquare_id, photo_path, geolat, geolng, container_id, confirm }) => {
59772
- const { timezone, gmt_offset } = localTimezone();
59773
- let ext;
59774
- if (photo_path !== void 0) {
59775
- ext = photoExt(photo_path);
59776
- if (!(ext in PHOTO_CONTENT_TYPES)) {
59777
- throw createHelpfulError(`Unsupported photo type "${ext || "(none)"}".`, {
59778
- hint: "Attach a .jpg, .jpeg, or .png file."
59779
- });
59780
- }
59781
- }
60020
+ async ({ bid, rating, shout, foursquare_id, photo_path, geolat, geolng, container_id, timezone: requestedTz, confirm }) => {
60021
+ const { timezone, gmt_offset } = checkinTimezone(requestedTz);
60022
+ const photo = photo_path !== void 0 ? await checkPhoto(photo_path) : void 0;
59782
60023
  const form = {
59783
60024
  bid,
59784
60025
  rating: rating !== void 0 ? rating.toFixed(2) : void 0,
@@ -59789,8 +60030,8 @@ function registerCheckinTools(server, client2) {
59789
60030
  container_id,
59790
60031
  timezone,
59791
60032
  gmt_offset,
59792
- is_photo: photo_path !== void 0 ? "true" : "false",
59793
- photo_file_ext: photo_path !== void 0 ? ext : void 0,
60033
+ is_photo: photo ? "true" : "false",
60034
+ photo_file_ext: photo?.ext,
59794
60035
  platform: "ios"
59795
60036
  };
59796
60037
  if (confirm !== true) {
@@ -59798,22 +60039,41 @@ function registerCheckinTools(server, client2) {
59798
60039
  dryRun: true,
59799
60040
  action: "checkin",
59800
60041
  form,
59801
- photo: photo_path !== void 0 ? { path: photo_path, note: "will be uploaded after the check-in is created" } : void 0,
60042
+ photo: photo ? { ...photo, note: "this exact file will be uploaded PUBLICLY after the check-in is created" } : void 0,
59802
60043
  note: "Dry run \u2014 re-run with confirm: true to POST this check-in to your public Untappd feed."
59803
60044
  });
59804
60045
  }
59805
60046
  let blob;
59806
- if (photo_path !== void 0) {
59807
- if (!existsSync(photo_path)) throw new McpToolError(`Photo file not found: ${photo_path}`);
59808
- blob = await fileBlob(photo_path);
60047
+ if (photo) {
60048
+ blob = await fileBlob(photo.path, { maxBytes: MAX_PHOTO_BYTES, label: "Photo", allowedRoots: photoRoots() });
60049
+ }
60050
+ const sentAt = Date.now();
60051
+ let data;
60052
+ try {
60053
+ data = await client2.write("POST", "/checkin/add", { form });
60054
+ } catch (e) {
60055
+ if (!(e instanceof UnreachableError)) throw e;
60056
+ const found = await findRecentCheckin(client2, bid, sentAt);
60057
+ if (found === null) {
60058
+ throw new McpToolError(
60059
+ "Untappd did not answer the check-in request in time, so the check-in may have been created anyway (it could not be confirmed either way). Look at your latest check-ins with untappd_user_checkins before retrying \u2014 retrying a check-in that did land posts it twice."
60060
+ );
60061
+ }
60062
+ return minifiedResult({
60063
+ checked_in: true,
60064
+ checkin_id: found,
60065
+ recovered: true,
60066
+ photo_attached: false,
60067
+ ...photo ? { photo_error: `Check-in ${found} was created, but its photo upload URL was lost with the timed-out response, so no photo was attached.` } : {},
60068
+ note: "Untappd did not answer in time, but the check-in was found on your account \u2014 it was created. Do not retry."
60069
+ });
59809
60070
  }
59810
- const data = await client2.write("POST", "/checkin/add", { form });
59811
60071
  let photo_attached = false;
59812
60072
  let photo_error;
59813
- if (photo_path !== void 0) {
60073
+ if (photo) {
59814
60074
  if (data?.photo_upload?.url && data.checkin_id) {
59815
60075
  try {
59816
- await client2.putBinary(data.photo_upload.url, blob, PHOTO_CONTENT_TYPES[ext]);
60076
+ await client2.putBinary(data.photo_upload.url, blob, photo.content_type);
59817
60077
  await client2.write("POST", "/photo/uploadComplete", {
59818
60078
  form: { checkin_id: data.checkin_id, destination_url: data.photo_upload.destination_url }
59819
60079
  });
@@ -60123,6 +60383,7 @@ async function syncCheckins(client2, cache, rawUsername, options = {}) {
60123
60383
 
60124
60384
  // src/cache/sync-beers.ts
60125
60385
  var PAGE_LIMIT2 = 50;
60386
+ var RESUME_OVERLAP = 10;
60126
60387
  async function fetchBeersPage(client2, encodedUser, offset) {
60127
60388
  const data = await client2.get(
60128
60389
  `/user/beers/${encodedUser}`,
@@ -60145,8 +60406,11 @@ async function syncUserBeers(client2, cache, rawUsername, maxPages = 10) {
60145
60406
  const now = () => (/* @__PURE__ */ new Date()).toISOString();
60146
60407
  const prior = await cache.getState(rawUsername);
60147
60408
  const incremental = (prior?.beers_complete ?? false) && !!prior?.last_synced_at;
60148
- let offset = incremental ? 0 : prior?.beers_offset ?? 0;
60409
+ let refreshing = incremental;
60410
+ let offset = incremental ? 0 : Math.max(0, (prior?.beers_offset ?? 0) - RESUME_OVERLAP);
60149
60411
  let total = prior?.beers_total ?? null;
60412
+ let acceptedGap = prior?.beers_accepted_gap ?? 0;
60413
+ let rescanTarget = incremental ? null : prior?.beers_rescan_target ?? null;
60150
60414
  let complete = false;
60151
60415
  let pages = 0;
60152
60416
  let added = 0;
@@ -60160,31 +60424,41 @@ async function syncUserBeers(client2, cache, rawUsername, maxPages = 10) {
60160
60424
  });
60161
60425
  }
60162
60426
  };
60427
+ const gapNow = async () => total === null ? 0 : total - await cache.distinctBeersCount(rawUsername);
60163
60428
  for (; pages < maxPages && !complete; ) {
60164
60429
  const page = pages === 0 ? await firstFetch(offset) : await fetchBeersPage(client2, encodedUser, offset);
60165
60430
  pages++;
60166
60431
  if (page.total !== null) total = page.total;
60167
- if (page.items.length === 0) {
60168
- complete = true;
60169
- await cache.setState(rawUsername, {
60170
- beers_offset: incremental ? total ?? offset : offset,
60171
- beers_total: total,
60172
- beers_complete: true,
60173
- last_synced_at: now()
60174
- });
60175
- break;
60432
+ let atEnd = page.items.length === 0;
60433
+ if (!atEnd) {
60434
+ const rows = rowsOf2(rawUsername, page.items);
60435
+ const netNew = await cache.upsertDistinctBeers(rawUsername, rows);
60436
+ added += netNew;
60437
+ offset += page.items.length;
60438
+ if (total !== null && offset >= total) atEnd = true;
60439
+ else if (page.items.length < PAGE_LIMIT2) atEnd = true;
60440
+ else if (refreshing && netNew === 0) atEnd = true;
60441
+ else if (rescanTarget !== null && await gapNow() <= rescanTarget) atEnd = true;
60442
+ }
60443
+ if (atEnd) {
60444
+ const gap = await gapNow();
60445
+ if (gap > acceptedGap && rescanTarget === null) {
60446
+ rescanTarget = acceptedGap;
60447
+ acceptedGap = gap;
60448
+ refreshing = false;
60449
+ offset = 0;
60450
+ } else {
60451
+ complete = true;
60452
+ rescanTarget = null;
60453
+ acceptedGap = Math.max(0, gap);
60454
+ }
60176
60455
  }
60177
- const rows = rowsOf2(rawUsername, page.items);
60178
- const netNew = await cache.upsertDistinctBeers(rawUsername, rows);
60179
- added += netNew;
60180
- offset += page.items.length;
60181
- if (total !== null && offset >= total) complete = true;
60182
- else if (page.items.length < PAGE_LIMIT2) complete = true;
60183
- else if (incremental && netNew === 0) complete = true;
60184
60456
  await cache.setState(rawUsername, {
60185
- beers_offset: incremental ? total ?? offset : offset,
60457
+ beers_offset: refreshing ? total ?? offset : offset,
60186
60458
  beers_total: total,
60187
60459
  beers_complete: complete,
60460
+ beers_accepted_gap: acceptedGap,
60461
+ beers_rescan_target: rescanTarget,
60188
60462
  last_synced_at: now()
60189
60463
  });
60190
60464
  }
@@ -60518,8 +60792,8 @@ function registerCacheTools(server, client2, cacheProvider) {
60518
60792
  // src/cache/db.ts
60519
60793
  import { DatabaseSync } from "node:sqlite";
60520
60794
  import { mkdirSync } from "node:fs";
60521
- import { dirname as dirname2, join as join2 } from "node:path";
60522
- import { homedir } from "node:os";
60795
+ import { dirname as dirname3, join as join3 } from "node:path";
60796
+ import { homedir as homedir2 } from "node:os";
60523
60797
  var NodeSqlDriver = class {
60524
60798
  constructor(db) {
60525
60799
  this.db = db;
@@ -60556,7 +60830,7 @@ var CheckinCache = class _CheckinCache extends LocalCacheStore {
60556
60830
  db;
60557
60831
  /** Open (creating parent dirs) a file-backed cache, or `:memory:` for tests. */
60558
60832
  static open(path) {
60559
- if (path !== ":memory:") mkdirSync(dirname2(path), { recursive: true });
60833
+ if (path !== ":memory:") mkdirSync(dirname3(path), { recursive: true });
60560
60834
  return new _CheckinCache(new DatabaseSync(path));
60561
60835
  }
60562
60836
  close() {
@@ -60564,7 +60838,7 @@ var CheckinCache = class _CheckinCache extends LocalCacheStore {
60564
60838
  }
60565
60839
  };
60566
60840
  function defaultCachePath() {
60567
- return readEnvVar("UNTAPPD_CACHE_DB") ?? join2(homedir(), ".untappd-mcp", "checkins.db");
60841
+ return readEnvVar("UNTAPPD_CACHE_DB") ?? join3(homedir2(), ".untappd-mcp", "checkins.db");
60568
60842
  }
60569
60843
 
60570
60844
  // src/index.ts