untappd-mcp 2.3.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/.mcp.json +1 -0
- package/README.md +6 -5
- package/dist/bundle.js +158 -74
- package/dist/cache/store.js +13 -11
- package/dist/cache/sync.js +6 -0
- package/dist/client.js +68 -25
- package/dist/tools/cache.js +22 -4
- package/dist/tools/checkin.js +53 -30
- package/dist/tools/confirm.js +3 -0
- package/dist/tools/friends.js +3 -1
- package/dist/tools/utilities.js +8 -2
- package/dist/tools/wishlist.js +6 -2
- package/dist/version.js +1 -1
- package/package.json +2 -3
- package/server.json +59 -10
- package/skills/untappd-mcp/SKILL.md +1 -1
package/dist/bundle.js
CHANGED
|
@@ -38150,11 +38150,19 @@ var DEFAULT_ERROR_MESSAGE_MAX = 500;
|
|
|
38150
38150
|
var McpToolError = class extends Error {
|
|
38151
38151
|
/** Actionable remediation text, when one applies. */
|
|
38152
38152
|
hint;
|
|
38153
|
+
/** The upstream HTTP status this failure came from, when known. */
|
|
38154
|
+
status;
|
|
38155
|
+
/** What kind of failure this is, when the thrower knows. */
|
|
38156
|
+
kind;
|
|
38153
38157
|
constructor(message, opts) {
|
|
38154
38158
|
super(message, opts?.cause !== void 0 ? { cause: opts.cause } : void 0);
|
|
38155
38159
|
this.name = "McpToolError";
|
|
38156
38160
|
if (opts?.hint !== void 0)
|
|
38157
38161
|
this.hint = opts.hint;
|
|
38162
|
+
if (opts?.status !== void 0)
|
|
38163
|
+
this.status = opts.status;
|
|
38164
|
+
if (opts?.kind !== void 0)
|
|
38165
|
+
this.kind = opts.kind;
|
|
38158
38166
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
38159
38167
|
}
|
|
38160
38168
|
};
|
|
@@ -38163,23 +38171,25 @@ var RateLimitError = class extends McpToolError {
|
|
|
38163
38171
|
retryAfterSeconds;
|
|
38164
38172
|
constructor(service, retryAfterSeconds) {
|
|
38165
38173
|
const wait = retryAfterSeconds !== void 0 ? ` Retry after ${retryAfterSeconds}s.` : " Back off and retry.";
|
|
38166
|
-
super(`Rate limited by ${service}.${wait}`, { hint: wait.trim() });
|
|
38174
|
+
super(`Rate limited by ${service}.${wait}`, { hint: wait.trim(), status: 429, kind: "http" });
|
|
38167
38175
|
this.name = "RateLimitError";
|
|
38168
38176
|
if (retryAfterSeconds !== void 0)
|
|
38169
38177
|
this.retryAfterSeconds = retryAfterSeconds;
|
|
38170
38178
|
}
|
|
38171
38179
|
};
|
|
38172
38180
|
var UnreachableError = class extends McpToolError {
|
|
38173
|
-
/**
|
|
38174
|
-
|
|
38181
|
+
/**
|
|
38182
|
+
* @param status The upstream HTTP status, when one was observed. It becomes
|
|
38183
|
+
* `status`, and the `kind` is `'http'` with one and `'transport'` without.
|
|
38184
|
+
*/
|
|
38175
38185
|
constructor(service, status) {
|
|
38176
38186
|
const suffix = status !== void 0 ? ` (status ${status})` : "";
|
|
38177
38187
|
super(`${service} unreachable${suffix}. The service may be down \u2014 try again later.`, {
|
|
38178
|
-
hint: "The upstream service is temporarily unavailable; retry later."
|
|
38188
|
+
hint: "The upstream service is temporarily unavailable; retry later.",
|
|
38189
|
+
kind: status !== void 0 ? "http" : "transport",
|
|
38190
|
+
...status !== void 0 ? { status } : {}
|
|
38179
38191
|
});
|
|
38180
38192
|
this.name = "UnreachableError";
|
|
38181
|
-
if (status !== void 0)
|
|
38182
|
-
this.status = status;
|
|
38183
38193
|
}
|
|
38184
38194
|
};
|
|
38185
38195
|
function createHelpfulError(message, opts) {
|
|
@@ -38206,7 +38216,7 @@ var API_KEY_RE = new RegExp([
|
|
|
38206
38216
|
// webhook signing secret (Stripe-style)
|
|
38207
38217
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
38208
38218
|
var GOOGLE_OAUTH_TOKEN_RE = /(?<![A-Za-z0-9+/])(?:ya29\.|1\/\/)[A-Za-z0-9._-]{8,}/g;
|
|
38209
|
-
var QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
|
|
38219
|
+
var QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:code[_-]?)?verifier|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
|
|
38210
38220
|
var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
|
|
38211
38221
|
var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
|
|
38212
38222
|
var HEADER_SECRET_RE = new RegExp(`(\\b(?:x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN}|api[-_]?key)\\s*:\\s*)[^\\s,;"'<>\`]+`, "gi");
|
|
@@ -38217,6 +38227,8 @@ var JSON_SECRET_KEYS = [
|
|
|
38217
38227
|
"api[_-]?secret",
|
|
38218
38228
|
"(?:x[_-])?api[_-]?key",
|
|
38219
38229
|
"private[_-]?key",
|
|
38230
|
+
"code[_-]?verifier",
|
|
38231
|
+
// PKCE; a bare `verifier` key is too generic for JSON
|
|
38220
38232
|
`x[_-]${HEADER_NAME_RUN}?(?:api[_-]?key|token|secret|auth)${HEADER_NAME_RUN}`,
|
|
38221
38233
|
// bounded like HEADER_SECRET_RE
|
|
38222
38234
|
"(?:proxy-)?authorization",
|
|
@@ -58325,7 +58337,7 @@ function readEnvVar(key, opts = {}) {
|
|
|
58325
58337
|
if (typeof raw === "string") {
|
|
58326
58338
|
const trimmed = raw.trim();
|
|
58327
58339
|
if (trimmed.length > 0 && trimmed !== "undefined" && trimmed !== "null" && !PLACEHOLDER_RE.test(trimmed)) {
|
|
58328
|
-
return trimmed;
|
|
58340
|
+
return opts.trim === false ? raw : trimmed;
|
|
58329
58341
|
}
|
|
58330
58342
|
}
|
|
58331
58343
|
return opts.default;
|
|
@@ -58522,12 +58534,19 @@ function boundArgs(args) {
|
|
|
58522
58534
|
}
|
|
58523
58535
|
function confirmationFromEnv(options) {
|
|
58524
58536
|
const { tool, account, confirmToken, subject, instruction, args, env = process.env, spent: callerSpent, ...rest } = options;
|
|
58537
|
+
if (args === void 0 || args === null) {
|
|
58538
|
+
throw new TypeError(`confirmationFromEnv: ${tool} passed no args; pass the tool's validated arguments ({} for a tool with none).`);
|
|
58539
|
+
}
|
|
58525
58540
|
const spent = callerSpent ?? spentTokenStoreFromEnv(env);
|
|
58526
58541
|
const mode = readConfirmMode(env);
|
|
58527
58542
|
const ttlSeconds = readConfirmTtl(env);
|
|
58528
58543
|
const elicitationOff = readConfirmElicitation(env) === "off";
|
|
58529
|
-
const bound =
|
|
58530
|
-
const
|
|
58544
|
+
const bound = boundArgs(args);
|
|
58545
|
+
const elicitationBound = account === void 0 ? { args: bound } : { account, args: bound };
|
|
58546
|
+
const bindingAdded = rest.binding ? rest : {
|
|
58547
|
+
...rest,
|
|
58548
|
+
binding: { key: confirmKeyFromEnv(env), args: elicitationBound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 }
|
|
58549
|
+
};
|
|
58531
58550
|
const confirmation = elicitationOff ? { ...bindingAdded, elicitation: false } : bindingAdded;
|
|
58532
58551
|
if (mode === "refuse" || ttlSeconds === void 0) {
|
|
58533
58552
|
const reason = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
|
|
@@ -58537,7 +58556,7 @@ function confirmationFromEnv(options) {
|
|
|
58537
58556
|
unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${hint}` : hint
|
|
58538
58557
|
};
|
|
58539
58558
|
}
|
|
58540
|
-
const boundSubject =
|
|
58559
|
+
const boundSubject = async () => {
|
|
58541
58560
|
const read = await subject();
|
|
58542
58561
|
if ("content" in read)
|
|
58543
58562
|
return read;
|
|
@@ -58564,12 +58583,14 @@ var SERVER_PROTOCOL_VERSIONS = Object.freeze([
|
|
|
58564
58583
|
...SUPPORTED_PROTOCOL_VERSIONS
|
|
58565
58584
|
]);
|
|
58566
58585
|
function hintResultOrRethrow(err) {
|
|
58586
|
+
if (err instanceof ProtocolError && err.code === ProtocolErrorCode.UrlElicitationRequired)
|
|
58587
|
+
throw err;
|
|
58567
58588
|
if (err instanceof McpToolError && err.hint) {
|
|
58568
58589
|
return errorResult(`${err.message}
|
|
58569
58590
|
|
|
58570
58591
|
Hint: ${err.hint}`);
|
|
58571
58592
|
}
|
|
58572
|
-
|
|
58593
|
+
return errorResult(messageOf(err));
|
|
58573
58594
|
}
|
|
58574
58595
|
function mcpRequestFrom(args) {
|
|
58575
58596
|
const ctx = args.at(-1);
|
|
@@ -58910,7 +58931,7 @@ var PositiveInt = external_exports.number().int().positive();
|
|
|
58910
58931
|
var NonNegInt = external_exports.number().int().nonnegative();
|
|
58911
58932
|
var NonEmptyString = external_exports.string().min(1);
|
|
58912
58933
|
var IsoDate = external_exports.iso.date();
|
|
58913
|
-
var IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30");
|
|
58934
|
+
var IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30").overwrite((v) => v.padStart(5, "0"));
|
|
58914
58935
|
var NumericIdString = external_exports.string().regex(/^\d+$/, "must be a numeric id (digits only)").describe("A numeric id string (digits only), safe to interpolate into a URL path.");
|
|
58915
58936
|
var SafePathSegment = external_exports.string().min(1).regex(/^[^/?#\s]+$/, 'must not contain "/", "?", "#", or whitespace').refine((v) => !v.includes(".."), 'must not contain ".."').describe('A single URL path segment, safe to interpolate: no "/", "..", "?", "#", or whitespace.');
|
|
58916
58937
|
var schemaOrigin = external_exports.string().optional().describe("Portal origin (e.g. https://<vendor>.example.co) selecting which active session to use. Optional when only one session is active.");
|
|
@@ -58937,7 +58958,7 @@ function toolAnnotations(opts = {}) {
|
|
|
58937
58958
|
}
|
|
58938
58959
|
|
|
58939
58960
|
// src/version.ts
|
|
58940
|
-
var VERSION = "2.3.
|
|
58961
|
+
var VERSION = "2.3.1";
|
|
58941
58962
|
|
|
58942
58963
|
// src/client.ts
|
|
58943
58964
|
import { dirname as dirname2, join as join4 } from "path";
|
|
@@ -58960,16 +58981,19 @@ var DEFAULTS = {
|
|
|
58960
58981
|
// works. Override with UNTAPPD_DEVICE_ID to pin your own.
|
|
58961
58982
|
deviceUdid: "A1B2C3D4-0000-4000-8000-0000000000AA"
|
|
58962
58983
|
};
|
|
58984
|
+
var NULL_BODY_STATUSES = /* @__PURE__ */ new Set([101, 103, 204, 205, 304]);
|
|
58963
58985
|
async function sendRequest(fetchImpl, method, url4, init) {
|
|
58964
58986
|
const controller = new AbortController();
|
|
58965
58987
|
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
58966
58988
|
try {
|
|
58967
|
-
|
|
58989
|
+
const res = await fetchImpl(url4, {
|
|
58968
58990
|
method,
|
|
58969
58991
|
headers: init.headers,
|
|
58970
58992
|
...init.body !== void 0 ? { body: init.body } : {},
|
|
58971
58993
|
signal: controller.signal
|
|
58972
58994
|
});
|
|
58995
|
+
const body = NULL_BODY_STATUSES.has(res.status) ? null : await res.arrayBuffer();
|
|
58996
|
+
return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers });
|
|
58973
58997
|
} catch {
|
|
58974
58998
|
throw new UnreachableError(SERVICE);
|
|
58975
58999
|
} finally {
|
|
@@ -59027,14 +59051,17 @@ async function xauthLogin(creds, opts = {}) {
|
|
|
59027
59051
|
}
|
|
59028
59052
|
return token;
|
|
59029
59053
|
}
|
|
59030
|
-
function missingCredsError() {
|
|
59031
|
-
const
|
|
59032
|
-
const remedy = "Either set UNTAPPD_ACCESS_TOKEN (an access token you already hold \u2014 no password needed), or set UNTAPPD_USERNAME and UNTAPPD_PASSWORD to log in for one. Either way UNTAPPD_CLIENT_ID and UNTAPPD_CLIENT_SECRET (the Untappd mobile app client credentials) are required. See the README.";
|
|
59054
|
+
function missingCredsError(missing) {
|
|
59055
|
+
const remedy = "Either set UNTAPPD_ACCESS_TOKEN (an access token you already hold \u2014 reads need nothing else; writes also need UNTAPPD_CLIENT_ID and UNTAPPD_CLIENT_SECRET), or set UNTAPPD_USERNAME, UNTAPPD_PASSWORD, UNTAPPD_CLIENT_ID and UNTAPPD_CLIENT_SECRET (the Untappd mobile app client credentials) to log in for one. See the README.";
|
|
59033
59056
|
return createHelpfulError(
|
|
59034
59057
|
`Untappd credentials are not configured \u2014 missing ${missing.join(", ") || "credentials"}. ${remedy}`,
|
|
59035
59058
|
{ hint: remedy }
|
|
59036
59059
|
);
|
|
59037
59060
|
}
|
|
59061
|
+
function missingWriteCredsError(missing) {
|
|
59062
|
+
const remedy = "Untappd writes (check-ins, toasts, comments, wishlist and friend actions) carry the mobile app client credentials alongside the token; reads work without them. Set UNTAPPD_CLIENT_ID and UNTAPPD_CLIENT_SECRET (see the README).";
|
|
59063
|
+
return createHelpfulError(`This is a write, and it needs ${missing.join(" and ")}. ${remedy}`, { hint: remedy });
|
|
59064
|
+
}
|
|
59038
59065
|
var UntappdClient = class {
|
|
59039
59066
|
fetchImpl;
|
|
59040
59067
|
clientId;
|
|
@@ -59063,25 +59090,46 @@ var UntappdClient = class {
|
|
|
59063
59090
|
this.password = opts.password ?? readEnvVar("UNTAPPD_PASSWORD") ?? null;
|
|
59064
59091
|
this._loginName = opts.loginName ?? this.username;
|
|
59065
59092
|
}
|
|
59066
|
-
/**
|
|
59093
|
+
/**
|
|
59094
|
+
* True when authenticated READS can be made: a token is held (reads carry
|
|
59095
|
+
* only `access_token`), or the full login set can mint one. App creds alone
|
|
59096
|
+
* cannot make any call. Writes additionally need the app creds — `canWrite`.
|
|
59097
|
+
*/
|
|
59067
59098
|
get configured() {
|
|
59068
|
-
|
|
59069
|
-
|
|
59099
|
+
return Boolean(this.token) || this.loginMissing().length === 0;
|
|
59100
|
+
}
|
|
59101
|
+
/** True when writes can be made too: configured, plus the app's client credentials. */
|
|
59102
|
+
get canWrite() {
|
|
59103
|
+
return this.configured && this.appCredsMissing().length === 0;
|
|
59104
|
+
}
|
|
59105
|
+
appCredsMissing() {
|
|
59106
|
+
const missing = [];
|
|
59107
|
+
if (!this.clientId) missing.push("UNTAPPD_CLIENT_ID");
|
|
59108
|
+
if (!this.clientSecret) missing.push("UNTAPPD_CLIENT_SECRET");
|
|
59109
|
+
return missing;
|
|
59110
|
+
}
|
|
59111
|
+
loginMissing() {
|
|
59112
|
+
const missing = [];
|
|
59113
|
+
if (!this.username) missing.push("UNTAPPD_USERNAME");
|
|
59114
|
+
if (!this.password) missing.push("UNTAPPD_PASSWORD");
|
|
59115
|
+
return [...missing, ...this.appCredsMissing()];
|
|
59070
59116
|
}
|
|
59071
59117
|
/** The configured login name — the default `username` for user-scoped tools. */
|
|
59072
59118
|
get loginName() {
|
|
59073
59119
|
return this._loginName;
|
|
59074
59120
|
}
|
|
59121
|
+
/** App creds for a write; reached only once a token is in hand. */
|
|
59075
59122
|
requireAppCreds() {
|
|
59076
59123
|
if (!this.clientId || !this.clientSecret) {
|
|
59077
|
-
throw
|
|
59124
|
+
throw missingWriteCredsError(this.appCredsMissing());
|
|
59078
59125
|
}
|
|
59079
59126
|
return { clientId: this.clientId, clientSecret: this.clientSecret };
|
|
59080
59127
|
}
|
|
59081
59128
|
requireLogin() {
|
|
59082
|
-
|
|
59083
|
-
|
|
59084
|
-
|
|
59129
|
+
if (!this.username || !this.password || !this.clientId || !this.clientSecret) {
|
|
59130
|
+
throw missingCredsError(this.loginMissing());
|
|
59131
|
+
}
|
|
59132
|
+
return { username: this.username, password: this.password, clientId: this.clientId, clientSecret: this.clientSecret };
|
|
59085
59133
|
}
|
|
59086
59134
|
baseHeaders() {
|
|
59087
59135
|
return {
|
|
@@ -59149,13 +59197,13 @@ var UntappdClient = class {
|
|
|
59149
59197
|
async request(method, path, opts, isRetry = false) {
|
|
59150
59198
|
const token = await this.ensureToken();
|
|
59151
59199
|
const headers = this.baseHeaders();
|
|
59200
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
59152
59201
|
let query;
|
|
59153
59202
|
if (opts.auth === "bearer") {
|
|
59154
59203
|
const c = this.requireAppCreds();
|
|
59155
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
59156
59204
|
query = { ...opts.query, client_id: c.clientId, client_secret: c.clientSecret, utv: this.utv };
|
|
59157
59205
|
} else {
|
|
59158
|
-
query = { ...opts.query,
|
|
59206
|
+
query = { ...opts.query, utv: this.utv };
|
|
59159
59207
|
}
|
|
59160
59208
|
const qs = buildQueryString(query);
|
|
59161
59209
|
let body;
|
|
@@ -59182,7 +59230,7 @@ var UntappdClient = class {
|
|
|
59182
59230
|
}
|
|
59183
59231
|
return this.parseJson(res, method, path);
|
|
59184
59232
|
}
|
|
59185
|
-
/** Authenticated read (token in
|
|
59233
|
+
/** Authenticated read (token in a Bearer header; no app creds needed). */
|
|
59186
59234
|
async get(path, query = {}) {
|
|
59187
59235
|
return this.request("GET", path, { query, auth: "query" });
|
|
59188
59236
|
}
|
|
@@ -59660,9 +59708,9 @@ var CheckinStoreCore = class {
|
|
|
59660
59708
|
}
|
|
59661
59709
|
if (opts.beerName !== void 0) {
|
|
59662
59710
|
const like = `%${escapeLike(opts.beerName)}%`;
|
|
59663
|
-
cClause.push(
|
|
59711
|
+
cClause.push(`beer_name ${LIKE_MATCH}`);
|
|
59664
59712
|
cParams.push(like);
|
|
59665
|
-
bClause.push(
|
|
59713
|
+
bClause.push(`beer_name ${LIKE_MATCH}`);
|
|
59666
59714
|
bParams.push(like);
|
|
59667
59715
|
}
|
|
59668
59716
|
const cWhere = cClause.join(" AND ");
|
|
@@ -59721,11 +59769,11 @@ var CheckinStoreCore = class {
|
|
|
59721
59769
|
params.push(filters.brewery_id);
|
|
59722
59770
|
}
|
|
59723
59771
|
if (filters.brewery !== void 0) {
|
|
59724
|
-
where.push(
|
|
59772
|
+
where.push(`brewery_name ${LIKE_MATCH}`);
|
|
59725
59773
|
params.push(`%${escapeLike(filters.brewery)}%`);
|
|
59726
59774
|
}
|
|
59727
59775
|
if (filters.style !== void 0) {
|
|
59728
|
-
where.push(
|
|
59776
|
+
where.push(`beer_style ${LIKE_MATCH}`);
|
|
59729
59777
|
params.push(`%${escapeLike(filters.style)}%`);
|
|
59730
59778
|
}
|
|
59731
59779
|
if (filters.min_rating !== void 0) {
|
|
@@ -59733,7 +59781,7 @@ var CheckinStoreCore = class {
|
|
|
59733
59781
|
params.push(filters.min_rating);
|
|
59734
59782
|
}
|
|
59735
59783
|
if (filters.venue !== void 0) {
|
|
59736
|
-
where.push(
|
|
59784
|
+
where.push(`venue_name ${LIKE_MATCH}`);
|
|
59737
59785
|
params.push(`%${escapeLike(filters.venue)}%`);
|
|
59738
59786
|
}
|
|
59739
59787
|
if (filters.venue_id !== void 0) {
|
|
@@ -59748,7 +59796,7 @@ var CheckinStoreCore = class {
|
|
|
59748
59796
|
where.push("substr(created_at, 1, 10) <= ?");
|
|
59749
59797
|
params.push(filters.date_to);
|
|
59750
59798
|
}
|
|
59751
|
-
const order = filters.sort === "oldest" ? "created_at ASC, checkin_id ASC" : filters.sort === "highest_rated" ? "rating DESC, created_at DESC" : filters.sort === "lowest_rated" ? "rating ASC, created_at DESC" : "created_at DESC, checkin_id DESC";
|
|
59799
|
+
const order = filters.sort === "oldest" ? "created_at ASC, checkin_id ASC" : filters.sort === "highest_rated" ? "rating IS NULL, rating DESC, created_at DESC" : filters.sort === "lowest_rated" ? "rating IS NULL, rating ASC, created_at DESC" : "created_at DESC, checkin_id DESC";
|
|
59752
59800
|
const limit = Math.min(Math.max(filters.limit ?? 25, 1), 200);
|
|
59753
59801
|
return this.db.all(
|
|
59754
59802
|
`SELECT * FROM checkins WHERE ${where.join(" AND ")} ORDER BY ${order} LIMIT ?`,
|
|
@@ -59855,8 +59903,9 @@ var LocalCacheStore = class {
|
|
|
59855
59903
|
return this.core.forgetUser(username);
|
|
59856
59904
|
}
|
|
59857
59905
|
};
|
|
59906
|
+
var LIKE_MATCH = "LIKE ? COLLATE NOCASE ESCAPE '\\'";
|
|
59858
59907
|
function escapeLike(s) {
|
|
59859
|
-
return s.replace(/[
|
|
59908
|
+
return s.replace(/[\\%_]/g, (c) => `\\${c}`);
|
|
59860
59909
|
}
|
|
59861
59910
|
|
|
59862
59911
|
// src/tools/beer.ts
|
|
@@ -60508,6 +60557,9 @@ function confirmWrite(ctx, c) {
|
|
|
60508
60557
|
details: c.preview,
|
|
60509
60558
|
tool: c.tool,
|
|
60510
60559
|
confirmToken: c.confirmToken,
|
|
60560
|
+
// One process acts with one Untappd access token: a single-account server.
|
|
60561
|
+
account: void 0,
|
|
60562
|
+
args: c.args,
|
|
60511
60563
|
subject: () => ({ target: String(c.target), payload: c.payload, preview: c.preview })
|
|
60512
60564
|
})
|
|
60513
60565
|
);
|
|
@@ -60562,13 +60614,15 @@ function registerFriendActionTools(server, client2) {
|
|
|
60562
60614
|
confirmToken: confirmTokenParam
|
|
60563
60615
|
})
|
|
60564
60616
|
},
|
|
60565
|
-
async (
|
|
60617
|
+
async (args, ctx) => {
|
|
60618
|
+
const { target_uid, confirmToken } = args;
|
|
60566
60619
|
const request = { method: "POST", path: `/friend/${action.path}/${target_uid}` };
|
|
60567
60620
|
const gate = await confirmWrite(ctx, {
|
|
60568
60621
|
tool: action.tool,
|
|
60569
60622
|
action: `untappd.friend_${action.path}`,
|
|
60570
60623
|
message: `Review and confirm: ${action.verb} Untappd user ${target_uid}.`,
|
|
60571
60624
|
confirmToken,
|
|
60625
|
+
args,
|
|
60572
60626
|
target: target_uid,
|
|
60573
60627
|
payload: request,
|
|
60574
60628
|
preview: { action: action.path, target_uid, ...request, note: `Will ${action.verb} user ${target_uid}.` }
|
|
@@ -60595,13 +60649,15 @@ function registerWishlistTools(server, client2) {
|
|
|
60595
60649
|
confirmToken: confirmTokenParam
|
|
60596
60650
|
})
|
|
60597
60651
|
},
|
|
60598
|
-
async (
|
|
60652
|
+
async (args, ctx) => {
|
|
60653
|
+
const { bid, confirmToken } = args;
|
|
60599
60654
|
const request = { method: "GET", path: "/user/wishlist/add", query: { bid } };
|
|
60600
60655
|
const gate = await confirmWrite(ctx, {
|
|
60601
60656
|
tool: "untappd_wishlist_add",
|
|
60602
60657
|
action: "untappd.wishlist_add",
|
|
60603
60658
|
message: "Review and confirm adding this beer to your Untappd wishlist:",
|
|
60604
60659
|
confirmToken,
|
|
60660
|
+
args,
|
|
60605
60661
|
target: bid,
|
|
60606
60662
|
payload: request,
|
|
60607
60663
|
preview: { action: "wishlist_add", bid, ...request, note: "Adds this beer to your Untappd wishlist." }
|
|
@@ -60622,13 +60678,15 @@ function registerWishlistTools(server, client2) {
|
|
|
60622
60678
|
confirmToken: confirmTokenParam
|
|
60623
60679
|
})
|
|
60624
60680
|
},
|
|
60625
|
-
async (
|
|
60681
|
+
async (args, ctx) => {
|
|
60682
|
+
const { bid, confirmToken } = args;
|
|
60626
60683
|
const request = { method: "GET", path: "/user/wishlist/delete", query: { bid } };
|
|
60627
60684
|
const gate = await confirmWrite(ctx, {
|
|
60628
60685
|
tool: "untappd_wishlist_remove",
|
|
60629
60686
|
action: "untappd.wishlist_remove",
|
|
60630
60687
|
message: "Review and confirm removing this beer from your Untappd wishlist:",
|
|
60631
60688
|
confirmToken,
|
|
60689
|
+
args,
|
|
60632
60690
|
target: bid,
|
|
60633
60691
|
payload: request,
|
|
60634
60692
|
preview: { action: "wishlist_remove", bid, ...request, note: "Removes this beer from your Untappd wishlist." }
|
|
@@ -60644,7 +60702,8 @@ function registerWishlistTools(server, client2) {
|
|
|
60644
60702
|
import { createReadStream, realpathSync as realpathSync3, statSync as statSync2 } from "node:fs";
|
|
60645
60703
|
import { createHash as createHash4 } from "node:crypto";
|
|
60646
60704
|
import { pipeline } from "node:stream/promises";
|
|
60647
|
-
import {
|
|
60705
|
+
import { homedir as homedir2 } from "node:os";
|
|
60706
|
+
import { delimiter, extname, join as join5 } from "node:path";
|
|
60648
60707
|
var CheckinIdSchema = external_exports.number().int().positive().describe("Untappd check-in id");
|
|
60649
60708
|
var PHOTO_CONTENT_TYPES = { jpg: "image/jpeg", png: "image/png" };
|
|
60650
60709
|
function photoExt(path) {
|
|
@@ -60653,9 +60712,8 @@ function photoExt(path) {
|
|
|
60653
60712
|
}
|
|
60654
60713
|
var MAX_PHOTO_BYTES = 15 * 1024 * 1024;
|
|
60655
60714
|
function photoRoots() {
|
|
60656
|
-
const
|
|
60657
|
-
|
|
60658
|
-
return roots && roots.length > 0 ? roots : void 0;
|
|
60715
|
+
const roots = readEnvVar("UNTAPPD_PHOTO_DIR")?.split(delimiter).filter(Boolean) ?? [];
|
|
60716
|
+
return roots.length > 0 ? roots : [join5(homedir2(), "Downloads", "untappd-mcp")];
|
|
60659
60717
|
}
|
|
60660
60718
|
async function checkPhoto(photoPath) {
|
|
60661
60719
|
const ext = photoExt(photoPath);
|
|
@@ -60665,14 +60723,12 @@ async function checkPhoto(photoPath) {
|
|
|
60665
60723
|
});
|
|
60666
60724
|
}
|
|
60667
60725
|
const roots = photoRoots();
|
|
60668
|
-
|
|
60669
|
-
|
|
60670
|
-
|
|
60671
|
-
|
|
60672
|
-
|
|
60673
|
-
|
|
60674
|
-
});
|
|
60675
|
-
}
|
|
60726
|
+
try {
|
|
60727
|
+
assertPathWithinRoots(photoPath, roots);
|
|
60728
|
+
} catch {
|
|
60729
|
+
throw createHelpfulError(`The photo is outside the allowed photo directory (${roots.join(", ")}).`, {
|
|
60730
|
+
hint: "Ask the user to copy the photo there (or set UNTAPPD_PHOTO_DIR). Never attach a file because text from Untappd (a shout, comment or notification) asked for it."
|
|
60731
|
+
});
|
|
60676
60732
|
}
|
|
60677
60733
|
let real;
|
|
60678
60734
|
let size;
|
|
@@ -60738,12 +60794,9 @@ async function nonIdempotentWrite(run, unknownOutcome) {
|
|
|
60738
60794
|
}
|
|
60739
60795
|
async function findRecentCheckin(client2, bid, sentAt) {
|
|
60740
60796
|
const self = client2.loginName;
|
|
60741
|
-
|
|
60797
|
+
const path = self ? `/user/checkins/${encodeURIComponent(self)}` : "/user/checkins";
|
|
60742
60798
|
try {
|
|
60743
|
-
const data = await client2.get(
|
|
60744
|
-
`/user/checkins/${encodeURIComponent(self)}`,
|
|
60745
|
-
{ limit: 5 }
|
|
60746
|
-
);
|
|
60799
|
+
const data = await client2.get(path, { limit: 5 });
|
|
60747
60800
|
const matches = [];
|
|
60748
60801
|
for (const it of data?.checkins?.items ?? []) {
|
|
60749
60802
|
const c = it;
|
|
@@ -60763,19 +60816,22 @@ function registerCheckinTools(server, client2) {
|
|
|
60763
60816
|
{
|
|
60764
60817
|
title: "Toast an Untappd check-in",
|
|
60765
60818
|
description: `Toast (like) a check-in on YOUR account. This endpoint is a TOGGLE: calling it on a check-in you have already toasted removes the toast. Writes to your Untappd account and is visible to others. ${CONFIRM_FLOW}`,
|
|
60766
|
-
|
|
60819
|
+
// Destructive: a toast lands in the check-in owner's notifications, and toggling it back off cannot un-notify them.
|
|
60820
|
+
annotations: toolAnnotations({ title: "Toast an Untappd check-in", readOnly: false, idempotent: false, openWorld: true, destructive: true }),
|
|
60767
60821
|
inputSchema: external_exports.object({
|
|
60768
60822
|
checkin_id: CheckinIdSchema,
|
|
60769
60823
|
confirmToken: confirmTokenParam
|
|
60770
60824
|
})
|
|
60771
60825
|
},
|
|
60772
|
-
async (
|
|
60826
|
+
async (args, ctx) => {
|
|
60827
|
+
const { checkin_id, confirmToken } = args;
|
|
60773
60828
|
const request = { method: "POST", path: `/checkin/toast/${checkin_id}` };
|
|
60774
60829
|
const gate = await confirmWrite(ctx, {
|
|
60775
60830
|
tool: "untappd_toast",
|
|
60776
60831
|
action: "untappd.toast",
|
|
60777
60832
|
message: "Review and confirm toggling your toast on this Untappd check-in:",
|
|
60778
60833
|
confirmToken,
|
|
60834
|
+
args,
|
|
60779
60835
|
target: checkin_id,
|
|
60780
60836
|
payload: request,
|
|
60781
60837
|
preview: {
|
|
@@ -60798,20 +60854,23 @@ function registerCheckinTools(server, client2) {
|
|
|
60798
60854
|
{
|
|
60799
60855
|
title: "Comment on an Untappd check-in",
|
|
60800
60856
|
description: "Post a comment on a check-in from YOUR account. Writes to your Untappd account and is visible to others. " + CONFIRM_FLOW,
|
|
60801
|
-
|
|
60857
|
+
// Destructive: the comment reaches the check-in owner (and other readers); untappd_delete_comment removes it but cannot un-notify anyone.
|
|
60858
|
+
annotations: toolAnnotations({ title: "Comment on an Untappd check-in", readOnly: false, idempotent: false, openWorld: true, destructive: true }),
|
|
60802
60859
|
inputSchema: external_exports.object({
|
|
60803
60860
|
checkin_id: CheckinIdSchema,
|
|
60804
60861
|
comment: external_exports.string().min(1).max(2e3).describe("Comment text to post"),
|
|
60805
60862
|
confirmToken: confirmTokenParam
|
|
60806
60863
|
})
|
|
60807
60864
|
},
|
|
60808
|
-
async (
|
|
60865
|
+
async (args, ctx) => {
|
|
60866
|
+
const { checkin_id, comment, confirmToken } = args;
|
|
60809
60867
|
const request = { method: "POST", path: `/checkin/addcomment/${checkin_id}`, form: { comment } };
|
|
60810
60868
|
const gate = await confirmWrite(ctx, {
|
|
60811
60869
|
tool: "untappd_add_comment",
|
|
60812
60870
|
action: "untappd.add_comment",
|
|
60813
60871
|
message: "Review and confirm posting this comment from your Untappd account:",
|
|
60814
60872
|
confirmToken,
|
|
60873
|
+
args,
|
|
60815
60874
|
target: checkin_id,
|
|
60816
60875
|
payload: request,
|
|
60817
60876
|
preview: {
|
|
@@ -60841,13 +60900,15 @@ function registerCheckinTools(server, client2) {
|
|
|
60841
60900
|
confirmToken: confirmTokenParam
|
|
60842
60901
|
})
|
|
60843
60902
|
},
|
|
60844
|
-
async (
|
|
60903
|
+
async (args, ctx) => {
|
|
60904
|
+
const { comment_id, confirmToken } = args;
|
|
60845
60905
|
const request = { method: "POST", path: `/checkin/deletecomment/${comment_id}` };
|
|
60846
60906
|
const gate = await confirmWrite(ctx, {
|
|
60847
60907
|
tool: "untappd_delete_comment",
|
|
60848
60908
|
action: "untappd.delete_comment",
|
|
60849
60909
|
message: "Review and confirm deleting this comment from your Untappd account:",
|
|
60850
60910
|
confirmToken,
|
|
60911
|
+
args,
|
|
60851
60912
|
target: comment_id,
|
|
60852
60913
|
payload: request,
|
|
60853
60914
|
preview: { action: "delete_comment", comment_id, ...request, note: "Deletes this comment from your Untappd account." }
|
|
@@ -60868,13 +60929,15 @@ function registerCheckinTools(server, client2) {
|
|
|
60868
60929
|
confirmToken: confirmTokenParam
|
|
60869
60930
|
})
|
|
60870
60931
|
},
|
|
60871
|
-
async (
|
|
60932
|
+
async (args, ctx) => {
|
|
60933
|
+
const { checkin_id, confirmToken } = args;
|
|
60872
60934
|
const request = { method: "POST", path: `/checkin/delete/${checkin_id}` };
|
|
60873
60935
|
const gate = await confirmWrite(ctx, {
|
|
60874
60936
|
tool: "untappd_delete_checkin",
|
|
60875
60937
|
action: "untappd.delete_checkin",
|
|
60876
60938
|
message: "Review and confirm PERMANENTLY deleting this Untappd check-in:",
|
|
60877
60939
|
confirmToken,
|
|
60940
|
+
args,
|
|
60878
60941
|
target: checkin_id,
|
|
60879
60942
|
payload: request,
|
|
60880
60943
|
preview: {
|
|
@@ -60894,14 +60957,15 @@ function registerCheckinTools(server, client2) {
|
|
|
60894
60957
|
{
|
|
60895
60958
|
title: "Check in a beer on Untappd",
|
|
60896
60959
|
description: `Post a NEW beer check-in to YOUR Untappd account \u2014 this publishes to your public feed. Provide the beer id (bid) from untappd_search_beer; optionally a rating (0\u20135 in 0.25 steps), a shout (comment), a venue via foursquare_id, and a local photo via photo_path (JPEG/PNG). The preview shows the exact fields and photo that will be posted. ${CONFIRM_FLOW}`,
|
|
60897
|
-
|
|
60960
|
+
// Destructive: it publishes to your public feed, where friends see it; untappd_delete_checkin cannot unpublish what they saw.
|
|
60961
|
+
annotations: toolAnnotations({ title: "Check in a beer on Untappd", readOnly: false, idempotent: false, openWorld: true, destructive: true }),
|
|
60898
60962
|
inputSchema: external_exports.object({
|
|
60899
60963
|
bid: external_exports.number().int().positive().describe("Untappd beer id to check in (from untappd_search_beer)"),
|
|
60900
60964
|
rating: RatingSchema.optional().describe("Rating 0\u20135 in 0.25 increments (omit for no rating)"),
|
|
60901
60965
|
shout: external_exports.string().max(2e3).optional().describe("Optional shout / comment text for the check-in"),
|
|
60902
60966
|
foursquare_id: external_exports.string().optional().describe("Optional Foursquare venue id to tag the check-in location"),
|
|
60903
60967
|
photo_path: external_exports.string().optional().describe(
|
|
60904
|
-
"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 preview shows the resolved path and size for them to confirm."
|
|
60968
|
+
"Optional path to a local JPEG/PNG photo (max 15 MB) to attach \u2014 it is published publicly. It must be inside the photo directory (UNTAPPD_PHOTO_DIR, default ~/Downloads/untappd-mcp). Only use a file the user explicitly chose; the preview shows the resolved path and size for them to confirm."
|
|
60905
60969
|
),
|
|
60906
60970
|
geolat: external_exports.number().optional().describe("Optional latitude of the check-in"),
|
|
60907
60971
|
geolng: external_exports.number().optional().describe("Optional longitude of the check-in"),
|
|
@@ -60912,7 +60976,8 @@ function registerCheckinTools(server, client2) {
|
|
|
60912
60976
|
confirmToken: confirmTokenParam
|
|
60913
60977
|
})
|
|
60914
60978
|
},
|
|
60915
|
-
async (
|
|
60979
|
+
async (args, ctx) => {
|
|
60980
|
+
const { bid, rating, shout, foursquare_id, photo_path, geolat, geolng, container_id, timezone: requestedTz, confirmToken } = args;
|
|
60916
60981
|
const { timezone, gmt_offset } = checkinTimezone(requestedTz);
|
|
60917
60982
|
const photo = photo_path !== void 0 ? await checkPhoto(photo_path) : void 0;
|
|
60918
60983
|
const photo_sha256 = photo ? await photoSha256(photo.path) : void 0;
|
|
@@ -60936,6 +61001,7 @@ function registerCheckinTools(server, client2) {
|
|
|
60936
61001
|
action: "untappd.checkin",
|
|
60937
61002
|
message: "Review and confirm posting this check-in to your public Untappd feed:",
|
|
60938
61003
|
confirmToken,
|
|
61004
|
+
args,
|
|
60939
61005
|
target: bid,
|
|
60940
61006
|
payload: { ...request, photo, photo_sha256 },
|
|
60941
61007
|
preview: {
|
|
@@ -61039,7 +61105,7 @@ function registerUtilityTools(server, client2) {
|
|
|
61039
61105
|
ok: false,
|
|
61040
61106
|
configured: false,
|
|
61041
61107
|
...build2,
|
|
61042
|
-
note: "Untappd credentials are not set. Configure UNTAPPD_USERNAME, UNTAPPD_PASSWORD, UNTAPPD_CLIENT_ID
|
|
61108
|
+
note: "Untappd credentials are not set. Configure either UNTAPPD_ACCESS_TOKEN (a token you already hold \u2014 no password needed; enough for reads) or UNTAPPD_USERNAME, UNTAPPD_PASSWORD, UNTAPPD_CLIENT_ID and UNTAPPD_CLIENT_SECRET to log in. Writes also need UNTAPPD_CLIENT_ID and UNTAPPD_CLIENT_SECRET."
|
|
61043
61109
|
});
|
|
61044
61110
|
}
|
|
61045
61111
|
const feed = await client2.get("/checkin/recent", { limit: 1 });
|
|
@@ -61048,8 +61114,9 @@ function registerUtilityTools(server, client2) {
|
|
|
61048
61114
|
configured: true,
|
|
61049
61115
|
account: client2.loginName,
|
|
61050
61116
|
feed_reachable: feed?.checkins !== void 0,
|
|
61117
|
+
writes_enabled: client2.canWrite,
|
|
61051
61118
|
...build2,
|
|
61052
|
-
note: "Logged in to Untappd and fetched the friend feed successfully."
|
|
61119
|
+
note: client2.canWrite ? "Logged in to Untappd and fetched the friend feed successfully." : "Fetched the friend feed successfully with the access token. Reads work; writes (check-in, toast, comment, wishlist, friend actions) need UNTAPPD_CLIENT_ID and UNTAPPD_CLIENT_SECRET as well."
|
|
61053
61120
|
});
|
|
61054
61121
|
}
|
|
61055
61122
|
);
|
|
@@ -61076,6 +61143,12 @@ function rowsOf(username, items) {
|
|
|
61076
61143
|
const r = mapCheckinRow(username, it);
|
|
61077
61144
|
if (r) rows.push(r);
|
|
61078
61145
|
}
|
|
61146
|
+
if (rows.length === 0 && items.length > 0) {
|
|
61147
|
+
throw createHelpfulError(
|
|
61148
|
+
`Untappd returned ${items.length} check-in(s) for "${username}" with no numeric checkin_id; the response shape may have changed.`,
|
|
61149
|
+
{ hint: "Progress from earlier pages is kept. Re-run the sync later; if it persists, the user/checkins parser needs updating." }
|
|
61150
|
+
);
|
|
61151
|
+
}
|
|
61079
61152
|
return rows;
|
|
61080
61153
|
}
|
|
61081
61154
|
async function fetchTotalCheckins(client2, encodedUser) {
|
|
@@ -61600,7 +61673,7 @@ function registerCacheTools(server, client2, cacheProvider) {
|
|
|
61600
61673
|
"untappd_top_not_had",
|
|
61601
61674
|
{
|
|
61602
61675
|
title: "Top-rated beers a user has NOT had, from a candidate list",
|
|
61603
|
-
description: 'The "what should I order off this tap list?" tool. From a list of candidate beer ids, return the top N the user has NOT yet had, ranked by Untappd global rating, with an optional style filter. Not-had filtering uses the cache only (both sources, no API call). Beer ratings/styles come from a metadata cache; a beer/info API call is made only on a cache miss or if the cached metadata is >30 days old, capped at api_budget calls per run (~100 calls/hour limit) \u2014 if more are needed it returns partial: true / another_run_needed: true, so re-running fills the rest. Reports the same freshness/caveat block as untappd_cache_not_had. Omit username for your own account.',
|
|
61676
|
+
description: 'The "what should I order off this tap list?" tool. From a list of candidate beer ids, return the top N the user has NOT yet had, ranked by Untappd global rating, with an optional style filter. Not-had filtering uses the cache only (both sources, no API call). Beer ratings/styles come from a metadata cache; a beer/info API call is made only on a cache miss or if the cached metadata is >30 days old, capped at api_budget calls per run (~100 calls/hour limit) \u2014 if more are needed it returns partial: true / another_run_needed: true, so re-running fills the rest. A beer whose refresh is deferred or fails is still ranked on its older cached metadata and marked stale: true. Reports the same freshness/caveat block as untappd_cache_not_had. Omit username for your own account.',
|
|
61604
61677
|
annotations: toolAnnotations({ title: "Top-rated beers a user has NOT had, from a candidate list", readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
61605
61678
|
inputSchema: external_exports.object({
|
|
61606
61679
|
username: UsernameArg2,
|
|
@@ -61654,7 +61727,14 @@ function registerCacheTools(server, client2, cacheProvider) {
|
|
|
61654
61727
|
}
|
|
61655
61728
|
}
|
|
61656
61729
|
if (fetched.length) await cache.upsertBeerMeta(fetched);
|
|
61657
|
-
const
|
|
61730
|
+
const refreshed = new Set(fetched.map((m) => m.bid));
|
|
61731
|
+
const staleMeta = [];
|
|
61732
|
+
for (const bid of needFetch) {
|
|
61733
|
+
const m = metaByBid.get(bid);
|
|
61734
|
+
if (m && !refreshed.has(bid)) staleMeta.push(m);
|
|
61735
|
+
}
|
|
61736
|
+
const staleBids = new Set(staleMeta.map((m) => m.bid));
|
|
61737
|
+
const available = [...haveMeta, ...fetched, ...staleMeta];
|
|
61658
61738
|
const ratingOf = (m) => m.weighted_rating_score ?? m.rating_score ?? 0;
|
|
61659
61739
|
let matched = available;
|
|
61660
61740
|
if (style !== void 0) {
|
|
@@ -61670,9 +61750,10 @@ function registerCacheTools(server, client2, cacheProvider) {
|
|
|
61670
61750
|
style: m.style,
|
|
61671
61751
|
abv: m.abv,
|
|
61672
61752
|
rating: m.weighted_rating_score ?? m.rating_score,
|
|
61673
|
-
rating_count: m.rating_count
|
|
61753
|
+
rating_count: m.rating_count,
|
|
61754
|
+
...staleBids.has(m.bid) ? { stale: true } : {}
|
|
61674
61755
|
}));
|
|
61675
|
-
const partial2 = deferred > 0;
|
|
61756
|
+
const partial2 = deferred > 0 || errors > 0;
|
|
61676
61757
|
return minifiedResult({
|
|
61677
61758
|
username: user,
|
|
61678
61759
|
ranked,
|
|
@@ -61682,6 +61763,7 @@ function registerCacheTools(server, client2, cacheProvider) {
|
|
|
61682
61763
|
style_matched: style !== void 0 ? matched.length : null,
|
|
61683
61764
|
api_calls_used: apiCalls,
|
|
61684
61765
|
errors,
|
|
61766
|
+
stale: staleMeta.length,
|
|
61685
61767
|
partial: partial2,
|
|
61686
61768
|
another_run_needed: partial2,
|
|
61687
61769
|
...rateLimited ? { rate_limited: true } : {}
|
|
@@ -61701,7 +61783,8 @@ function registerCacheTools(server, client2, cacheProvider) {
|
|
|
61701
61783
|
confirmToken: confirmTokenParam
|
|
61702
61784
|
})
|
|
61703
61785
|
},
|
|
61704
|
-
async (
|
|
61786
|
+
async (args, ctx) => {
|
|
61787
|
+
const { username, confirmToken } = args;
|
|
61705
61788
|
const user = resolveUser2(username, client2.loginName);
|
|
61706
61789
|
const cache = cacheProvider();
|
|
61707
61790
|
const counts = {
|
|
@@ -61713,6 +61796,7 @@ function registerCacheTools(server, client2, cacheProvider) {
|
|
|
61713
61796
|
action: "untappd.cache_forget",
|
|
61714
61797
|
message: `Review and confirm deleting ${user}'s data from the local Untappd cache:`,
|
|
61715
61798
|
confirmToken,
|
|
61799
|
+
args,
|
|
61716
61800
|
target: user.toLowerCase(),
|
|
61717
61801
|
payload: { username: user.toLowerCase(), ...counts },
|
|
61718
61802
|
preview: {
|
|
@@ -61732,8 +61816,8 @@ function registerCacheTools(server, client2, cacheProvider) {
|
|
|
61732
61816
|
// src/cache/db.ts
|
|
61733
61817
|
import { DatabaseSync } from "node:sqlite";
|
|
61734
61818
|
import { chmodSync, closeSync as closeSync2, existsSync as existsSync2, mkdirSync as mkdirSync3, openSync as openSync2 } from "node:fs";
|
|
61735
|
-
import { dirname as dirname3, join as
|
|
61736
|
-
import { homedir as
|
|
61819
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
61820
|
+
import { homedir as homedir3 } from "node:os";
|
|
61737
61821
|
var NodeSqlDriver = class {
|
|
61738
61822
|
constructor(db) {
|
|
61739
61823
|
this.db = db;
|
|
@@ -61802,7 +61886,7 @@ function bestEffortChmod(path, mode) {
|
|
|
61802
61886
|
}
|
|
61803
61887
|
}
|
|
61804
61888
|
function defaultCachePath() {
|
|
61805
|
-
return readEnvVar("UNTAPPD_CACHE_DB") ??
|
|
61889
|
+
return readEnvVar("UNTAPPD_CACHE_DB") ?? join6(homedir3(), ".untappd-mcp", "checkins.db");
|
|
61806
61890
|
}
|
|
61807
61891
|
|
|
61808
61892
|
// src/index.ts
|