gogcli-mcp 4.5.0 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -956,14 +956,14 @@ function promiseAllObject(promisesObj) {
956
956
  }
957
957
  function randomString(length = 10) {
958
958
  const chars = "abcdefghijklmnopqrstuvwxyz";
959
- let str = "";
959
+ let str2 = "";
960
960
  for (let i = 0; i < length; i++) {
961
- str += chars[Math.floor(Math.random() * chars.length)];
961
+ str2 += chars[Math.floor(Math.random() * chars.length)];
962
962
  }
963
- return str;
963
+ return str2;
964
964
  }
965
- function esc(str) {
966
- return JSON.stringify(str);
965
+ function esc(str2) {
966
+ return JSON.stringify(str2);
967
967
  }
968
968
  function slugify(input2) {
969
969
  return input2.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
@@ -1077,8 +1077,8 @@ var primitiveTypes = /* @__PURE__ */ new Set([
1077
1077
  "symbol",
1078
1078
  "undefined"
1079
1079
  ]);
1080
- function escapeRegex(str) {
1081
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1080
+ function escapeRegex(str2) {
1081
+ return str2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1082
1082
  }
1083
1083
  function clone(inst, def, params) {
1084
1084
  const cl = new inst._zod.constr(def ?? inst._zod.def);
@@ -1332,13 +1332,13 @@ function getSizableOrigin(input2) {
1332
1332
  return "unknown";
1333
1333
  }
1334
1334
  var highSurrogate = /[\uD800-\uDBFF]/;
1335
- function codePointLength(str) {
1336
- const units = str.length;
1337
- if (!highSurrogate.test(str))
1335
+ function codePointLength(str2) {
1336
+ const units = str2.length;
1337
+ if (!highSurrogate.test(str2))
1338
1338
  return units;
1339
1339
  let count = units;
1340
1340
  for (let i = 0; i < units - 1; i++) {
1341
- if ((str.charCodeAt(i) & 64512) === 55296 && (str.charCodeAt(i + 1) & 64512) === 56320) {
1341
+ if ((str2.charCodeAt(i) & 64512) === 55296 && (str2.charCodeAt(i + 1) & 64512) === 56320) {
1342
1342
  count--;
1343
1343
  i++;
1344
1344
  }
@@ -25451,7 +25451,7 @@ var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
25451
25451
  }
25452
25452
  exports._ = _;
25453
25453
  const plus = new _Code("+");
25454
- function str(strs, ...args) {
25454
+ function str2(strs, ...args) {
25455
25455
  const expr = [safeStringify(strs[0])];
25456
25456
  let i = 0;
25457
25457
  while (i < args.length) {
@@ -25462,7 +25462,7 @@ var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
25462
25462
  optimize(expr);
25463
25463
  return new _Code(expr);
25464
25464
  }
25465
- exports.str = str;
25465
+ exports.str = str2;
25466
25466
  function addCodeArg(code, arg) {
25467
25467
  if (arg instanceof _Code) code.push(...arg._items);
25468
25468
  else if (arg instanceof Name) code.push(arg);
@@ -25495,7 +25495,7 @@ var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
25495
25495
  if (typeof b == "string" && b[0] === '"' && !(a instanceof Name)) return `"${a}${b.slice(1)}`;
25496
25496
  }
25497
25497
  function strConcat(c1, c2) {
25498
- return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str`${c1}${c2}`;
25498
+ return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str2`${c1}${c2}`;
25499
25499
  }
25500
25500
  exports.strConcat = strConcat;
25501
25501
  function interpolate(x) {
@@ -26374,21 +26374,21 @@ var require_util = /* @__PURE__ */ __commonJSMin(((exports) => {
26374
26374
  return (0, codegen_1._)`${topSchemaRef}${schemaPath}${(0, codegen_1.getProperty)(keyword)}`;
26375
26375
  }
26376
26376
  exports.schemaRefOrVal = schemaRefOrVal;
26377
- function unescapeFragment(str) {
26378
- return unescapeJsonPointer(decodeURIComponent(str));
26377
+ function unescapeFragment(str2) {
26378
+ return unescapeJsonPointer(decodeURIComponent(str2));
26379
26379
  }
26380
26380
  exports.unescapeFragment = unescapeFragment;
26381
- function escapeFragment(str) {
26382
- return encodeURIComponent(escapeJsonPointer(str));
26381
+ function escapeFragment(str2) {
26382
+ return encodeURIComponent(escapeJsonPointer(str2));
26383
26383
  }
26384
26384
  exports.escapeFragment = escapeFragment;
26385
- function escapeJsonPointer(str) {
26386
- if (typeof str == "number") return `${str}`;
26387
- return str.replace(/~/g, "~0").replace(/\//g, "~1");
26385
+ function escapeJsonPointer(str2) {
26386
+ if (typeof str2 == "number") return `${str2}`;
26387
+ return str2.replace(/~/g, "~0").replace(/\//g, "~1");
26388
26388
  }
26389
26389
  exports.escapeJsonPointer = escapeJsonPointer;
26390
- function unescapeJsonPointer(str) {
26391
- return str.replace(/~1/g, "/").replace(/~0/g, "~");
26390
+ function unescapeJsonPointer(str2) {
26391
+ return str2.replace(/~1/g, "/").replace(/~0/g, "~");
26392
26392
  }
26393
26393
  exports.unescapeJsonPointer = unescapeJsonPointer;
26394
26394
  function eachItem(xs, f) {
@@ -27274,8 +27274,8 @@ var require_json_schema_traverse = /* @__PURE__ */ __commonJSMin(((exports, modu
27274
27274
  post(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
27275
27275
  }
27276
27276
  }
27277
- function escapeJsonPtr(str) {
27278
- return str.replace(/~/g, "~0").replace(/\//g, "~1");
27277
+ function escapeJsonPtr(str2) {
27278
+ return str2.replace(/~/g, "~0").replace(/\//g, "~1");
27279
27279
  }
27280
27280
  }));
27281
27281
  var require_resolve = /* @__PURE__ */ __commonJSMin(((exports) => {
@@ -28167,9 +28167,9 @@ var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
28167
28167
  isIPV6: false
28168
28168
  };
28169
28169
  }
28170
- function findToken(str, token) {
28170
+ function findToken(str2, token) {
28171
28171
  let ind = 0;
28172
- for (let i = 0; i < str.length; i++) if (str[i] === token) ind++;
28172
+ for (let i = 0; i < str2.length; i++) if (str2[i] === token) ind++;
28173
28173
  return ind;
28174
28174
  }
28175
28175
  function removeDotSegments(path) {
@@ -28684,7 +28684,7 @@ var require_core$3 = /* @__PURE__ */ __commonJSMin(((exports) => {
28684
28684
  const util_1 = require_util();
28685
28685
  const $dataRefSchema = require_data();
28686
28686
  const uri_1 = require_uri();
28687
- const defaultRegExp = (str, flags) => new RegExp(str, flags);
28687
+ const defaultRegExp = (str2, flags) => new RegExp(str2, flags);
28688
28688
  defaultRegExp.code = "new RegExp";
28689
28689
  const META_IGNORE_OPTIONS = [
28690
28690
  "removeAdditional",
@@ -29390,16 +29390,16 @@ var require_multipleOf = /* @__PURE__ */ __commonJSMin(((exports) => {
29390
29390
  }));
29391
29391
  var require_ucs2length = /* @__PURE__ */ __commonJSMin(((exports) => {
29392
29392
  Object.defineProperty(exports, "__esModule", { value: true });
29393
- function ucs2length(str) {
29394
- const len = str.length;
29393
+ function ucs2length(str2) {
29394
+ const len = str2.length;
29395
29395
  let length = 0;
29396
29396
  let pos2 = 0;
29397
29397
  let value;
29398
29398
  while (pos2 < len) {
29399
29399
  length++;
29400
- value = str.charCodeAt(pos2++);
29400
+ value = str2.charCodeAt(pos2++);
29401
29401
  if (value >= 55296 && value <= 56319 && pos2 < len) {
29402
- value = str.charCodeAt(pos2);
29402
+ value = str2.charCodeAt(pos2);
29403
29403
  if ((value & 64512) === 56320) pos2++;
29404
29404
  }
29405
29405
  }
@@ -32110,8 +32110,8 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
32110
32110
  30,
32111
32111
  31
32112
32112
  ];
32113
- function date5(str) {
32114
- const matches = DATE.exec(str);
32113
+ function date5(str2) {
32114
+ const matches = DATE.exec(str2);
32115
32115
  if (!matches) return false;
32116
32116
  const year = +matches[1];
32117
32117
  const month = +matches[2];
@@ -32126,8 +32126,8 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
32126
32126
  }
32127
32127
  const TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
32128
32128
  function getTime(strictTimeZone) {
32129
- return function time3(str) {
32130
- const matches = TIME.exec(str);
32129
+ return function time3(str2) {
32130
+ const matches = TIME.exec(str2);
32131
32131
  if (!matches) return false;
32132
32132
  const hr = +matches[1];
32133
32133
  const min = +matches[2];
@@ -32164,8 +32164,8 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
32164
32164
  const DATE_TIME_SEPARATOR = /t|\s/i;
32165
32165
  function getDateTime(strictTimeZone) {
32166
32166
  const time3 = getTime(strictTimeZone);
32167
- return function date_time(str) {
32168
- const dateTime = str.split(DATE_TIME_SEPARATOR);
32167
+ return function date_time(str2) {
32168
+ const dateTime = str2.split(DATE_TIME_SEPARATOR);
32169
32169
  return dateTime.length === 2 && date5(dateTime[0]) && time3(dateTime[1]);
32170
32170
  };
32171
32171
  }
@@ -32186,13 +32186,13 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
32186
32186
  }
32187
32187
  const NOT_URI_FRAGMENT = /\/|:/;
32188
32188
  const URI = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
32189
- function uri(str) {
32190
- return NOT_URI_FRAGMENT.test(str) && URI.test(str);
32189
+ function uri(str2) {
32190
+ return NOT_URI_FRAGMENT.test(str2) && URI.test(str2);
32191
32191
  }
32192
32192
  const BYTE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gm;
32193
- function byte(str) {
32193
+ function byte(str2) {
32194
32194
  BYTE.lastIndex = 0;
32195
- return BYTE.test(str);
32195
+ return BYTE.test(str2);
32196
32196
  }
32197
32197
  const MIN_INT32 = -(2 ** 31);
32198
32198
  const MAX_INT32 = 2 ** 31 - 1;
@@ -32206,10 +32206,10 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
32206
32206
  return true;
32207
32207
  }
32208
32208
  const Z_ANCHOR = /[^\\]\\Z/;
32209
- function regex(str) {
32210
- if (Z_ANCHOR.test(str)) return false;
32209
+ function regex(str2) {
32210
+ if (Z_ANCHOR.test(str2)) return false;
32211
32211
  try {
32212
- new RegExp(str);
32212
+ new RegExp(str2);
32213
32213
  return true;
32214
32214
  } catch (e) {
32215
32215
  return false;
@@ -35971,6 +35971,7 @@ async function diagnose(err) {
35971
35971
  const isAuthError = !isTransientError && AUTH_ERROR_PATTERN.test(errText);
35972
35972
  const isGridLimitError = GRID_LIMIT_ERROR_PATTERN.test(errText);
35973
35973
  const hint = isInvalidGrant ? INVALID_GRANT_HINT : isAuthError ? AUTH_HINT : isTransientError ? TRANSIENT_HINT : isGridLimitError ? GRID_LIMIT_HINT : "";
35974
+ if (!isAuthError && !isInvalidGrant) return errorResult(`${errText}${hint}`);
35974
35975
  try {
35975
35976
  const accounts = formatAccountList(await run(["auth", "list"]));
35976
35977
  return errorResult(`${errText}
@@ -36115,12 +36116,53 @@ function attachmentPreview(details) {
36115
36116
  var CONFIRM_SEND_INSTRUCTION = "Show this preview to the user verbatim and send only after they explicitly approve in chat. Then call again with confirmToken.";
36116
36117
  var CONFIRM_ACTION_INSTRUCTION = CONFIRM_TOKEN_INSTRUCTION;
36117
36118
  var CONFIRM_FALLBACK_DESCRIPTION = ' If the client cannot show that prompt (no MCP elicitation, e.g. claude.ai), a two-step flow applies instead: call WITHOUT confirmToken and nothing is sent or changed \u2014 the result has status "confirmation-required", the full preview and a confirmToken. Follow its instruction (by default: show the preview to the user verbatim and only after they explicitly approve it in chat, call again with the SAME arguments plus confirmToken). The tool re-reads what it would act on and refuses (DRAFT_CHANGED, with a fresh preview and token) if it changed; TOKEN_EXPIRED / TOKEN_REUSED / TOKEN_INVALID also do nothing. A server set to MCP_CONFIRM_MODE=refuse refuses instead.';
36119
+ function refusedInRun(what, via, does, instead) {
36120
+ return `${what} ${does} and is not available through ${via}. ${instead}`;
36121
+ }
36118
36122
  function gatedElsewhere(what, via, does, tool) {
36119
- return `${what} ${does} and is not available through ${via}. Use ${tool}, which asks the user to confirm.`;
36123
+ return refusedInRun(what, via, does, `Use ${tool}, which asks the user to confirm.`);
36120
36124
  }
36121
36125
  function hasCommandWord(args, words) {
36122
36126
  return args.find((a) => words.has(a.toLowerCase()));
36123
36127
  }
36128
+ function flagValue(args, name) {
36129
+ const flag = `--${name}`;
36130
+ for (let i = 0; i < args.length; i++) {
36131
+ const a = args[i];
36132
+ if (a === flag) return args[i + 1] ?? "";
36133
+ if (a.startsWith(`${flag}=`)) return a.slice(flag.length + 1);
36134
+ }
36135
+ return void 0;
36136
+ }
36137
+ var COMMENT_ADD_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
36138
+ var COMMENT_REPLY_WORDS = /* @__PURE__ */ new Set(["reply", "respond"]);
36139
+ function hasTrueFlag(args, flag) {
36140
+ const name = `--${flag}`;
36141
+ return args.some((a) => {
36142
+ const lower = a.toLowerCase();
36143
+ if (lower === name) return true;
36144
+ return lower.startsWith(`${name}=`) && lower.slice(name.length + 1) !== "false";
36145
+ });
36146
+ }
36147
+ function vetCommentsRun(service, subcommand, args) {
36148
+ if (subcommand.toLowerCase() !== "comments") return void 0;
36149
+ const reply = hasCommandWord(args, COMMENT_REPLY_WORDS);
36150
+ if (reply) {
36151
+ return gatedElsewhere(
36152
+ `gog ${service} comments ${reply.toLowerCase()}`,
36153
+ `gog_${service}_run`,
36154
+ "notifies the comment thread",
36155
+ `gog_${service}_comments_reply`
36156
+ );
36157
+ }
36158
+ const add = hasCommandWord(args, COMMENT_ADD_WORDS);
36159
+ return add ? gatedElsewhere(
36160
+ `gog ${service} comments ${add.toLowerCase()}`,
36161
+ `gog_${service}_run`,
36162
+ "notifies the file's owner and anyone it mentions",
36163
+ `gog_${service}_comments_add`
36164
+ ) : void 0;
36165
+ }
36124
36166
  async function requireDispatchConfirmation(ctx, options) {
36125
36167
  const { action, fallback } = options;
36126
36168
  const confirmation = {
@@ -36149,16 +36191,33 @@ function resultText(result) {
36149
36191
  // src/tools/api.ts
36150
36192
  var GMAIL_API_BLOCKED = /(?:\.send$|forwarding|filters\.create|filters\.update|delegates\.create)/i;
36151
36193
  var DISPATCH_API_BLOCKED = {
36152
- chat: [{ method: /(?:^|\.)spaces\.messages\.create$/i, does: "posts a Chat message", tool: "gog_chat_messages_send / gog_chat_dm_send" }],
36153
- drive: [{ method: /(?:^|\.)permissions\.(?:create|update)$/i, does: "grants access to a file", tool: "gog_drive_share" }],
36194
+ chat: [
36195
+ { method: /(?:^|\.)spaces\.messages\.create$/i, does: "posts a Chat message", tool: "gog_chat_messages_send / gog_chat_dm_send" },
36196
+ { method: /(?:^|\.)spaces\.(?:setup|members\.create)$/i, does: "adds people to a space", tool: "gog_chat_spaces_create" }
36197
+ ],
36198
+ drive: [
36199
+ { method: /(?:^|\.)permissions\.(?:create|update)$/i, does: "grants access to a file", tool: "gog_drive_share" },
36200
+ { method: /(?:^|\.)comments\.create$/i, does: "notifies the file's owner and anyone it mentions", tool: "gog_drive_comments_add / gog_docs_comments_add" },
36201
+ { method: /(?:^|\.)replies\.create$/i, does: "notifies the comment thread", tool: "gog_drive_comments_reply / gog_docs_comments_reply" }
36202
+ ],
36154
36203
  classroom: [
36155
36204
  { method: /(?:^|\.)courses\.announcements\.create$/i, does: "posts to a class", tool: "gog_classroom_announcements_create" },
36156
- { method: /(?:^|\.)invitations\.create$/i, does: "invites someone to a class", tool: "gog_classroom_invitations_create" }
36205
+ { method: /(?:^|\.)invitations\.create$/i, does: "invites someone to a class", tool: "gog_classroom_invitations_create" },
36206
+ { method: /(?:^|\.)courses\.students\.create$/i, does: "adds someone to a class", tool: "gog_classroom_students_add" },
36207
+ { method: /(?:^|\.)courses\.teachers\.create$/i, does: "gives someone teacher access to a class", tool: "gog_classroom_teachers_add" },
36208
+ { method: /(?:^|\.)courses\.coursework\.create$/i, does: "posts coursework to a class", tool: "gog_classroom_coursework_create" },
36209
+ { method: /(?:^|\.)studentsubmissions\.return$/i, does: "returns work to a student", tool: "gog_classroom_submissions_return" }
36157
36210
  ],
36158
36211
  calendar: [
36159
36212
  { method: /(?:^|\.)events\.(?:insert|import|quickadd)$/i, does: "can put an event on guests' calendars", tool: "gog_calendar_create" },
36160
- { method: /(?:^|\.)events\.(?:update|patch)$/i, does: "can change what guests see", tool: "gog_calendar_update / gog_calendar_respond" }
36161
- ]
36213
+ { method: /(?:^|\.)events\.(?:update|patch)$/i, does: "can change what guests see", tool: "gog_calendar_update / gog_calendar_respond" },
36214
+ { method: /(?:^|\.)events\.move$/i, does: "can email every guest", tool: "gog_calendar_move" }
36215
+ ],
36216
+ gmail: [
36217
+ { method: /(?:^|\.)settings\.sendas\.create$/i, does: "makes Google email the address and adds a sending identity", tool: "gog_gmail_sendas_create" },
36218
+ { method: /(?:^|\.)settings\.updatevacation$/i, does: "can turn on an auto-reply to every sender", tool: "gog_gmail_vacation_update" }
36219
+ ],
36220
+ script: [{ method: /(?:^|\.)scripts\.run$/i, does: "executes code with this account's authority", tool: "gog_appscript_run_function" }]
36162
36221
  };
36163
36222
  function refusedApiCall(api, method) {
36164
36223
  const name = api.trim().toLowerCase();
@@ -36169,6 +36228,14 @@ function refusedApiCall(api, method) {
36169
36228
  const hit = (Object.hasOwn(DISPATCH_API_BLOCKED, name) ? DISPATCH_API_BLOCKED[name] : []).find((b) => b.method.test(m));
36170
36229
  return hit ? gatedElsewhere(`${name} ${m}`, "gog_api_call", hit.does, hit.tool) : void 0;
36171
36230
  }
36231
+ function previewJson(text) {
36232
+ if (text.length > BODY_PREVIEW_MAX) return bodyPreview(text);
36233
+ try {
36234
+ return JSON.parse(text);
36235
+ } catch {
36236
+ return text;
36237
+ }
36238
+ }
36172
36239
  function registerApiTools(server) {
36173
36240
  server.registerTool("gog_api_list", {
36174
36241
  description: "List the Google Discovery APIs available for gog_api_call / gog_api_describe (name + version + title).",
@@ -36197,7 +36264,7 @@ function registerApiTools(server) {
36197
36264
  return runOrDiagnose(args, { account });
36198
36265
  });
36199
36266
  server.registerTool("gog_api_call", {
36200
- description: "Call any Discovery-described Google API method directly \u2014 an escape hatch for endpoints gog has no dedicated tool for. Find the exact api/version/method/params with gog_api_describe first. Read methods (GET/LIST) run as-is. Mutating methods (POST/PUT/PATCH/DELETE) are refused unless you set allowWrite=true \u2014 keep it false to preview, or set dryRun=true to print the intended request without sending it. Gmail send and forwarding methods (users.messages.send, users.drafts.send, forwarding/auto-forwarding, filters, delegates) are refused \u2014 use the dedicated gog_gmail_* tools, which ask the user to confirm. So are the other methods a dedicated tool asks about: Chat spaces.messages.create, Drive permissions.create/update, Classroom courses.announcements.create and invitations.create, and Calendar events.insert/import/quickAdd/update/patch.",
36267
+ description: "Call any Discovery-described Google API method directly \u2014 an escape hatch for endpoints gog has no dedicated tool for. Find the exact api/version/method/params with gog_api_describe first. Read methods (GET/LIST) run as-is. Mutating methods (POST/PUT/PATCH/DELETE) are refused unless you set allowWrite=true, and every write then asks the MCP host to show the user a confirmation prompt with the exact api/version/method/params/body before anything is sent; set dryRun=true to print the intended request without sending it (no prompt, no changes). Gmail send and forwarding methods (users.messages.send, users.drafts.send, forwarding/auto-forwarding, filters, delegates) are refused outright \u2014 use the dedicated gog_gmail_* tools, which ask the user to confirm. So are the other methods a dedicated tool asks about with a better preview: Chat spaces.messages.create, Drive permissions.create/update, Classroom courses.announcements.create and invitations.create, and Calendar events.insert/import/quickAdd/update/patch." + CONFIRM_FALLBACK_DESCRIPTION,
36201
36268
  annotations: { destructiveHint: true },
36202
36269
  inputSchema: external_exports.object({
36203
36270
  api: external_exports.string().describe("Discovery API name (e.g. drive, gmail, calendar)"),
@@ -36206,11 +36273,12 @@ function registerApiTools(server) {
36206
36273
  params: external_exports.string().optional().describe('Query/path parameters as a JSON object string (e.g. {"fileId":"abc","fields":"name"})'),
36207
36274
  body: external_exports.string().optional().describe("Request body as a JSON string (for write methods)"),
36208
36275
  scope: external_exports.string().optional().describe("Override the OAuth scope used for the call"),
36209
- allowWrite: external_exports.boolean().optional().describe("Required to invoke a mutating method (POST/PUT/PATCH/DELETE). Without it, gog refuses write methods. Leave unset for read-only calls."),
36210
- dryRun: external_exports.boolean().optional().describe("Print the intended request and exit without sending it (no changes made)"),
36211
- account: accountParam
36276
+ allowWrite: external_exports.boolean().optional().describe("Required to invoke a mutating method (POST/PUT/PATCH/DELETE); the user is asked to confirm the exact request first. Without it, gog refuses write methods. Leave unset for read-only calls."),
36277
+ dryRun: external_exports.boolean().optional().describe("Print the intended request and exit without sending it (no changes made, no confirmation prompt)"),
36278
+ account: accountParam,
36279
+ confirmToken: confirmTokenParam
36212
36280
  })
36213
- }, async ({ api, version: version2, method, params, body, scope, allowWrite, dryRun, account }) => {
36281
+ }, async ({ api, version: version2, method, params, body, scope, allowWrite, dryRun, account, confirmToken }, ctx) => {
36214
36282
  try {
36215
36283
  assertSafeForwardedArgs([api, version2, method]);
36216
36284
  const refusal = refusedApiCall(api, method);
@@ -36220,6 +36288,27 @@ function registerApiTools(server) {
36220
36288
  } catch (err) {
36221
36289
  return errorResult(errorText(err));
36222
36290
  }
36291
+ if (allowWrite && !dryRun) {
36292
+ const request = { api, version: version2, method, params, body, scope };
36293
+ const view = { api, version: version2, method };
36294
+ if (params !== void 0) view.params = previewJson(params);
36295
+ if (body !== void 0) view.body = previewJson(body);
36296
+ if (scope !== void 0) view.scope = scope;
36297
+ const confirmation = await requireDispatchConfirmation(ctx, {
36298
+ action: "api.call",
36299
+ message: `Review and confirm this raw Google API write (${api} ${version2} ${method}):`,
36300
+ confirmationLabel: "Confirm that this API method should be invoked with exactly these parameters.",
36301
+ details: view,
36302
+ unsupportedNote: "Set dryRun=true to see the request gog would send without sending it, or use the dedicated gog_* tool for this operation.",
36303
+ fallback: {
36304
+ tool: "gog_api_call",
36305
+ account,
36306
+ confirmToken,
36307
+ subject: () => ({ target: `${api}/${version2}/${method}`, payload: request, preview: view })
36308
+ }
36309
+ });
36310
+ if (confirmation) return confirmation;
36311
+ }
36223
36312
  const args = ["api", "call", pos(api), pos(version2), pos(method)];
36224
36313
  if (params) args.push(`--params=${params}`);
36225
36314
  if (body) args.push(`--body=${body}`);
@@ -36232,6 +36321,22 @@ function registerApiTools(server) {
36232
36321
  }
36233
36322
 
36234
36323
  // src/tools/appscript.ts
36324
+ function vetAppScriptRun(subcommand, _args) {
36325
+ return subcommand.toLowerCase() === "run" ? gatedElsewhere("gog appscript run", "gog_appscript_run", "executes code with this account's authority", "gog_appscript_run_function") : void 0;
36326
+ }
36327
+ function projectSnapshot(raw, scriptId) {
36328
+ let project;
36329
+ try {
36330
+ project = JSON.parse(raw)?.project;
36331
+ } catch {
36332
+ project = void 0;
36333
+ }
36334
+ return {
36335
+ scriptId,
36336
+ ...typeof project?.title === "string" ? { title: project.title } : {},
36337
+ ...typeof project?.updateTime === "string" ? { updateTime: project.updateTime } : {}
36338
+ };
36339
+ }
36235
36340
  function registerAppScriptTools(server) {
36236
36341
  const scriptIdParam = external_exports.string().describe(
36237
36342
  "Apps Script project ID \u2014 the long ID in script.google.com/\u2026/projects/<scriptId>/\u2026, NOT the Drive file ID of a container document"
@@ -36312,16 +36417,18 @@ function registerAppScriptTools(server) {
36312
36417
  return runOrDiagnose(args, { account });
36313
36418
  });
36314
36419
  server.registerTool("gog_appscript_run_function", {
36315
- description: "Execute a function in a deployed Apps Script project. TREAT THIS AS ARBITRARY CODE EXECUTION: the script runs with this Google account's authority and can send mail, edit Drive files or call external services, and the wrapper cannot tell a read from a write \u2014 read the code with gog_appscript_content first if you did not write it. Requires the project to be deployed as an API executable and to share the OAuth client with the calling credentials, otherwise Google refuses regardless of scopes. devMode runs the latest saved code instead of the deployed version, and only works if the account owns the script. This is NOT the escape hatch \u2014 gog_appscript_run is that." + apiEnableNote,
36420
+ description: "Execute a function in a deployed Apps Script project. TREAT THIS AS ARBITRARY CODE EXECUTION: the script runs with this Google account's authority and can send mail, edit Drive files or call external services, and the wrapper cannot tell a read from a write \u2014 read the code with gog_appscript_content first if you did not write it. Requires the project to be deployed as an API executable and to share the OAuth client with the calling credentials, otherwise Google refuses regardless of scopes. devMode runs the latest saved code instead of the deployed version, and only works if the account owns the script. This is NOT the escape hatch \u2014 gog_appscript_run is that. Because the wrapper cannot tell what the code will do, it reads the project and asks the MCP host to show the user a confirmation prompt with the project, the function and its arguments first; nothing runs unless they accept." + CONFIRM_FALLBACK_DESCRIPTION + apiEnableNote,
36316
36421
  annotations: { destructiveHint: true },
36317
36422
  inputSchema: external_exports.object({
36318
36423
  scriptId: scriptIdParam,
36319
36424
  functionName: external_exports.string().describe('Name of the function to call, e.g. "doWork"'),
36320
36425
  params: external_exports.string().optional().describe(`Function parameters as a JSON ARRAY of positional arguments, e.g. '["a", 1]' \u2014 not an object`),
36321
36426
  devMode: external_exports.boolean().optional().describe("Run the latest saved code rather than the deployed version (owner only)"),
36322
- account: accountParam
36427
+ account: accountParam,
36428
+ confirmToken: confirmTokenParam
36323
36429
  })
36324
- }, async ({ scriptId, functionName, params, devMode, account }) => {
36430
+ }, async ({ scriptId, functionName, params, devMode, account, confirmToken }, ctx) => {
36431
+ let parsedParams;
36325
36432
  if (params !== void 0) {
36326
36433
  let parsed;
36327
36434
  try {
@@ -36332,7 +36439,26 @@ function registerAppScriptTools(server) {
36332
36439
  if (!Array.isArray(parsed)) {
36333
36440
  throw new Error(`params must be a JSON ARRAY of positional arguments, e.g. '["a", 1]' \u2014 Apps Script takes positional arguments, not named ones. Received: ${params}`);
36334
36441
  }
36442
+ parsedParams = parsed;
36335
36443
  }
36444
+ const got = await runOrDiagnose(["appscript", "get", pos(scriptId)], { account });
36445
+ if (got.isError) return got;
36446
+ const { updateTime, ...project } = projectSnapshot(resultText(got), scriptId);
36447
+ const execution = { project, functionName, params: parsedParams, devMode: Boolean(devMode), runsAs: senderPreview(account) };
36448
+ const confirmation = await requireDispatchConfirmation(ctx, {
36449
+ action: "appscript.run-function",
36450
+ message: "Review and confirm running this Apps Script function with the account's full authority:",
36451
+ confirmationLabel: "Confirm that this code should run now.",
36452
+ details: execution,
36453
+ unsupportedNote: "Ask the user to run it themselves from the Apps Script editor.",
36454
+ fallback: {
36455
+ tool: "gog_appscript_run_function",
36456
+ account,
36457
+ confirmToken,
36458
+ subject: () => ({ target: `${scriptId}/${functionName}`, revision: updateTime, payload: execution, preview: execution })
36459
+ }
36460
+ });
36461
+ if (confirmation) return confirmation;
36336
36462
  const args = ["appscript", "run", pos(scriptId), pos(functionName)];
36337
36463
  if (params !== void 0) args.push(`--params=${params}`);
36338
36464
  if (devMode) args.push("--dev-mode");
@@ -36341,6 +36467,7 @@ function registerAppScriptTools(server) {
36341
36467
  registerRunTool(server, {
36342
36468
  service: "appscript",
36343
36469
  examples: '"get", "content", "deployments"',
36470
+ vet: vetAppScriptRun,
36344
36471
  note: "To execute a function, use gog_appscript_run_function \u2014 this tool is the generic escape hatch."
36345
36472
  });
36346
36473
  }
@@ -36480,21 +36607,47 @@ function registerAuthTools(server) {
36480
36607
  }
36481
36608
 
36482
36609
  // src/tools/calendar.ts
36483
- var CALENDAR_GATED = {
36484
- create: "gog_calendar_create",
36485
- add: "gog_calendar_create",
36486
- new: "gog_calendar_create",
36487
- update: "gog_calendar_update",
36488
- edit: "gog_calendar_update",
36489
- set: "gog_calendar_update",
36490
- respond: "gog_calendar_respond",
36491
- rsvp: "gog_calendar_respond",
36492
- reply: "gog_calendar_respond"
36610
+ var ASKS = (tool) => `Use ${tool}, which asks the user to confirm.`;
36611
+ var CALENDAR_REFUSED = {
36612
+ create: { does: "can change what guests see", instead: ASKS("gog_calendar_create") },
36613
+ add: { does: "can change what guests see", instead: ASKS("gog_calendar_create") },
36614
+ new: { does: "can change what guests see", instead: ASKS("gog_calendar_create") },
36615
+ update: { does: "can change what guests see", instead: ASKS("gog_calendar_update") },
36616
+ edit: { does: "can change what guests see", instead: ASKS("gog_calendar_update") },
36617
+ set: { does: "can change what guests see", instead: ASKS("gog_calendar_update") },
36618
+ respond: { does: "can change what guests see", instead: ASKS("gog_calendar_respond") },
36619
+ rsvp: { does: "can change what guests see", instead: ASKS("gog_calendar_respond") },
36620
+ reply: { does: "can change what guests see", instead: ASKS("gog_calendar_respond") },
36621
+ move: { does: "moves an event off guests' calendars (and can email them)", instead: ASKS("gog_calendar_move") },
36622
+ transfer: { does: "moves an event off guests' calendars (and can email them)", instead: ASKS("gog_calendar_move") },
36623
+ delete: { does: "removes an event from every guest's calendar (the whole series by default)", instead: ASKS("gog_calendar_delete") },
36624
+ del: { does: "removes an event from every guest's calendar (the whole series by default)", instead: ASKS("gog_calendar_delete") },
36625
+ rm: { does: "removes an event from every guest's calendar (the whole series by default)", instead: ASKS("gog_calendar_delete") },
36626
+ remove: { does: "removes an event from every guest's calendar (the whole series by default)", instead: ASKS("gog_calendar_delete") },
36627
+ "delete-calendar": { does: "deletes a calendar and every event on it", instead: ASKS("gog_calendar_delete_calendar") },
36628
+ "out-of-office": { does: "auto-declines invitations and notifies their organizers", instead: ASKS("gog_calendar_out_of_office") },
36629
+ ooo: { does: "auto-declines invitations and notifies their organizers", instead: ASKS("gog_calendar_out_of_office") }
36493
36630
  };
36494
- function vetCalendarRun(subcommand, _args) {
36631
+ function vetCalendarRun(subcommand, args) {
36495
36632
  const sub = subcommand.toLowerCase();
36496
- const tool = Object.hasOwn(CALENDAR_GATED, sub) ? CALENDAR_GATED[sub] : void 0;
36497
- return tool ? gatedElsewhere(`gog calendar ${sub}`, "gog_calendar_run", "can change what guests see", tool) : void 0;
36633
+ const what = `gog calendar ${sub}`;
36634
+ const via = "gog_calendar_run";
36635
+ if (Object.hasOwn(CALENDAR_REFUSED, sub)) {
36636
+ const { does, instead } = CALENDAR_REFUSED[sub];
36637
+ return refusedInRun(what, via, does, instead);
36638
+ }
36639
+ if (sub === "propose-time" && (flagValue(args, "decline") !== void 0 || flagValue(args, "comment") !== void 0)) {
36640
+ return gatedElsewhere(`${what} --decline`, via, "declines the event and notifies the organizer", "gog_calendar_respond");
36641
+ }
36642
+ if ((sub === "focus-time" || sub === "focus") && flagValue(args, "auto-decline")?.toLowerCase() !== "none") {
36643
+ return refusedInRun(
36644
+ what,
36645
+ via,
36646
+ "auto-declines invitations and notifies their organizers (gog defaults --auto-decline to all)",
36647
+ "Pass --auto-decline=none, or ask the user to create it from Google Calendar."
36648
+ );
36649
+ }
36650
+ return void 0;
36498
36651
  }
36499
36652
  function parseAttendees(value) {
36500
36653
  if (!value) return [];
@@ -36511,15 +36664,17 @@ function eventSnapshot(raw) {
36511
36664
  } catch {
36512
36665
  event = void 0;
36513
36666
  }
36514
- const str = (v) => typeof v === "string" ? v : void 0;
36667
+ const str2 = (v) => typeof v === "string" ? v : void 0;
36515
36668
  return {
36516
- summary: str(event?.summary),
36669
+ summary: str2(event?.summary),
36517
36670
  start: timeOf(event?.start),
36518
36671
  end: timeOf(event?.end),
36519
- organizer: str(event?.organizer?.email),
36672
+ organizer: str2(event?.organizer?.email),
36520
36673
  // Guests are the OTHER people: not this account's own attendee row, not rooms.
36521
36674
  guests: (Array.isArray(event?.attendees) ? event.attendees : []).filter((a) => a.self !== true && a.resource !== true && typeof a.email === "string").map((a) => a.email),
36522
- etag: str(event?.etag)
36675
+ // A series, or one instance of one: gog's delete --scope defaults to all.
36676
+ ...Array.isArray(event?.recurrence) && event.recurrence.length > 0 || typeof event?.recurringEventId === "string" ? { recurring: true } : {},
36677
+ etag: str2(event?.etag)
36523
36678
  };
36524
36679
  }
36525
36680
  async function readEvent(calendarId, eventId, account) {
@@ -36740,14 +36895,35 @@ function registerCalendarTools(server) {
36740
36895
  return runOrDiagnose(args, { account });
36741
36896
  });
36742
36897
  server.registerTool("gog_calendar_delete", {
36743
- description: "Delete a calendar event.",
36898
+ description: "Delete a calendar event. It disappears from every guest's calendar too, and for a recurring event gog deletes the WHOLE series, so when the event has guests or recurs this reads it and asks the MCP host to show the user a confirmation prompt with the event as it stands; a guest-free, one-off event is deleted without asking." + CONFIRM_FALLBACK_DESCRIPTION,
36744
36899
  annotations: { destructiveHint: true },
36745
36900
  inputSchema: external_exports.object({
36746
36901
  calendarId: external_exports.string().describe("Calendar ID"),
36747
36902
  eventId: external_exports.string().describe("Event ID"),
36748
- account: accountParam
36903
+ account: accountParam,
36904
+ confirmToken: confirmTokenParam
36749
36905
  })
36750
- }, async ({ calendarId, eventId, account }) => {
36906
+ }, async ({ calendarId, eventId, account, confirmToken }, ctx) => {
36907
+ const read = await readEvent(calendarId, eventId, account);
36908
+ if (read.error) return read.error;
36909
+ const { etag, ...event } = read.event;
36910
+ if (event.guests.length > 0 || event.recurring) {
36911
+ const view = { calendarId, eventId, event, scope: event.recurring ? "the whole recurring series" : "this event" };
36912
+ const confirmation = await requireDispatchConfirmation(ctx, {
36913
+ action: "calendar.delete",
36914
+ message: event.recurring ? "Review and confirm deleting this ENTIRE recurring series:" : "Review and confirm deleting this event from every guest's calendar:",
36915
+ confirmationLabel: "Confirm that this event should be deleted now.",
36916
+ details: view,
36917
+ unsupportedNote: "Ask the user to delete it from Google Calendar.",
36918
+ fallback: {
36919
+ tool: "gog_calendar_delete",
36920
+ account,
36921
+ confirmToken,
36922
+ subject: () => ({ target: `${calendarId}/${eventId}`, revision: etag, payload: view, preview: view })
36923
+ }
36924
+ });
36925
+ if (confirmation) return confirmation;
36926
+ }
36751
36927
  return runOrDiagnose(["calendar", "delete", pos(calendarId), pos(eventId), "--force"], { account });
36752
36928
  });
36753
36929
  server.registerTool("gog_calendar_respond", {
@@ -36869,8 +37045,14 @@ function inlineAttachmentArgs(flag, attachments, siblingArgs = []) {
36869
37045
 
36870
37046
  // src/tools/chat.ts
36871
37047
  var CHAT_SEND_WORDS = /* @__PURE__ */ new Set(["send", "create", "post"]);
37048
+ var CHAT_SPACE_CREATE_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
36872
37049
  function vetChatRun(subcommand, args) {
36873
37050
  const sub = subcommand.toLowerCase();
37051
+ if (sub === "spaces") {
37052
+ const word2 = hasCommandWord(args, CHAT_SPACE_CREATE_WORDS);
37053
+ const withMembers = args.some((a) => /^--members?(?:=|$)/i.test(a));
37054
+ return word2 && withMembers ? gatedElsewhere(`gog chat spaces ${word2.toLowerCase()} --member`, "gog_chat_run", "adds people to a space", "gog_chat_spaces_create") : void 0;
37055
+ }
36874
37056
  if (sub !== "messages" && sub !== "dm") return void 0;
36875
37057
  const word = hasCommandWord(args, CHAT_SEND_WORDS);
36876
37058
  if (!word) return void 0;
@@ -36921,16 +37103,34 @@ function registerChatTools(server) {
36921
37103
  return runOrDiagnose(args, { account });
36922
37104
  });
36923
37105
  server.registerTool("gog_chat_spaces_create", {
36924
- description: "Create a named Chat space, optionally seeding its membership. Members are added immediately and are notified \u2014 this is visible to other people the moment it runs, so confirm the member list before calling it." + workspaceOnlyNote,
37106
+ description: "Create a named Chat space, optionally seeding its membership. Members are added immediately and are notified, so with members this asks the MCP host to show the user a confirmation prompt with the space name and every member first; nothing is created unless they accept. A space with no members reaches nobody and is created without asking." + CONFIRM_FALLBACK_DESCRIPTION + workspaceOnlyNote,
36925
37107
  annotations: { destructiveHint: true },
36926
37108
  inputSchema: external_exports.object({
36927
37109
  displayName: external_exports.string().describe("Display name for the new space"),
36928
37110
  members: external_exports.array(external_exports.string()).optional().describe('Initial members, as email addresses or "users/..." resource names'),
36929
- account: accountParam
37111
+ account: accountParam,
37112
+ confirmToken: confirmTokenParam
36930
37113
  })
36931
- }, async ({ displayName, members: members2, account }) => {
37114
+ }, async ({ displayName, members: members2, account, confirmToken }, ctx) => {
36932
37115
  const args = ["chat", "spaces", "create", pos(displayName)];
36933
37116
  if (members2) for (const member of members2) args.push(`--member=${member}`);
37117
+ if (members2?.length) {
37118
+ const space = { displayName, members: members2 };
37119
+ const confirmation = await requireDispatchConfirmation(ctx, {
37120
+ action: "chat.space-create",
37121
+ message: "Review and confirm this Chat space \u2014 every member is added and notified:",
37122
+ confirmationLabel: "Confirm that these people should be added to a new space now.",
37123
+ details: space,
37124
+ unsupportedNote: "Create the space without members instead; the user can add people from Google Chat.",
37125
+ fallback: {
37126
+ tool: "gog_chat_spaces_create",
37127
+ account,
37128
+ confirmToken,
37129
+ subject: () => ({ target: displayName, payload: space, preview: space })
37130
+ }
37131
+ });
37132
+ if (confirmation) return confirmation;
37133
+ }
36934
37134
  return runOrDiagnose(args, { account });
36935
37135
  });
36936
37136
  server.registerTool("gog_chat_threads_list", {
@@ -37124,20 +37324,85 @@ function registerChatTools(server) {
37124
37324
 
37125
37325
  // src/tools/classroom.ts
37126
37326
  var CLASSROOM_CREATE_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
37127
- var CLASSROOM_GATED = {
37128
- announcements: { does: "posts to a class", tool: "gog_classroom_announcements_create" },
37129
- announcement: { does: "posts to a class", tool: "gog_classroom_announcements_create" },
37130
- ann: { does: "posts to a class", tool: "gog_classroom_announcements_create" },
37131
- invitations: { does: "invites someone to a class", tool: "gog_classroom_invitations_create" },
37132
- invitation: { does: "invites someone to a class", tool: "gog_classroom_invitations_create" },
37133
- invites: { does: "invites someone to a class", tool: "gog_classroom_invitations_create" }
37327
+ var CLASSROOM_DELETE_WORDS = /* @__PURE__ */ new Set(["delete", "rm", "del", "remove"]);
37328
+ var CLASSROOM_UPDATE_WORDS = /* @__PURE__ */ new Set(["update", "edit", "set"]);
37329
+ var CLASSROOM_RETURN_WORDS = /* @__PURE__ */ new Set(["return", "send"]);
37330
+ var CLASSROOM_ASSIGNEE_WORDS = /* @__PURE__ */ new Set(["assignees", "assign"]);
37331
+ var CLASSROOM_GROUPS = {
37332
+ courses: "courses",
37333
+ course: "courses",
37334
+ announcements: "announcements",
37335
+ announcement: "announcements",
37336
+ ann: "announcements",
37337
+ invitations: "invitations",
37338
+ invitation: "invitations",
37339
+ invites: "invitations",
37340
+ coursework: "coursework",
37341
+ work: "coursework",
37342
+ materials: "materials",
37343
+ material: "materials",
37344
+ students: "students",
37345
+ student: "students",
37346
+ teachers: "teachers",
37347
+ teacher: "teachers",
37348
+ submissions: "submissions",
37349
+ submission: "submissions",
37350
+ "guardian-invitations": "guardian-invitations",
37351
+ "guardian-invites": "guardian-invitations"
37134
37352
  };
37353
+ function publishesToStudents(args) {
37354
+ return flagValue(args, "state")?.toUpperCase() === "PUBLISHED" || flagValue(args, "scheduled") !== void 0;
37355
+ }
37356
+ function createsVisibleWork(args) {
37357
+ return flagValue(args, "state")?.toUpperCase() !== "DRAFT" || flagValue(args, "scheduled") !== void 0;
37358
+ }
37359
+ function widensAssignees(args) {
37360
+ return flagValue(args, "mode")?.toUpperCase() === "ALL_STUDENTS" || flagValue(args, "add-student") !== void 0;
37361
+ }
37135
37362
  function vetClassroomRun(subcommand, args) {
37136
37363
  const sub = subcommand.toLowerCase();
37137
- const gated = Object.hasOwn(CLASSROOM_GATED, sub) ? CLASSROOM_GATED[sub] : void 0;
37138
- if (!gated) return void 0;
37139
- const word = hasCommandWord(args, CLASSROOM_CREATE_WORDS);
37140
- return word ? gatedElsewhere(`gog classroom ${sub} ${word.toLowerCase()}`, "gog_classroom_run", gated.does, gated.tool) : void 0;
37364
+ if (!Object.hasOwn(CLASSROOM_GROUPS, sub)) return void 0;
37365
+ const group = CLASSROOM_GROUPS[sub];
37366
+ const via = "gog_classroom_run";
37367
+ const what = (word) => `gog classroom ${sub} ${word.toLowerCase()}`;
37368
+ const create = hasCommandWord(args, CLASSROOM_CREATE_WORDS);
37369
+ const update = hasCommandWord(args, CLASSROOM_UPDATE_WORDS);
37370
+ const remove = hasCommandWord(args, CLASSROOM_DELETE_WORDS);
37371
+ const widenedAssignees = () => {
37372
+ const word = hasCommandWord(args, CLASSROOM_ASSIGNEE_WORDS);
37373
+ return word && widensAssignees(args) ? refusedInRun(what(word), via, "shows it to more students", "Ask the user to change who it is assigned to from Classroom.") : void 0;
37374
+ };
37375
+ switch (group) {
37376
+ case "courses":
37377
+ return remove ? gatedElsewhere(what(remove), via, "deletes a course", "gog_classroom_courses_delete") : void 0;
37378
+ case "announcements":
37379
+ if (create) return gatedElsewhere(what(create), via, "posts to a class", "gog_classroom_announcements_create");
37380
+ if (update && publishesToStudents(args)) return gatedElsewhere(what(update), via, "publishes an announcement to a class", "gog_classroom_announcements_update");
37381
+ return widenedAssignees();
37382
+ case "invitations":
37383
+ return create ? gatedElsewhere(what(create), via, "invites someone to a class", "gog_classroom_invitations_create") : void 0;
37384
+ case "coursework":
37385
+ if (create && createsVisibleWork(args)) return gatedElsewhere(what(create), via, "posts coursework to a class", "gog_classroom_coursework_create");
37386
+ if (remove) return gatedElsewhere(what(remove), via, "deletes coursework and every submission to it", "gog_classroom_coursework_delete");
37387
+ if (update && publishesToStudents(args)) return gatedElsewhere(what(update), via, "publishes work to students", "gog_classroom_coursework_update");
37388
+ return widenedAssignees();
37389
+ case "materials":
37390
+ if (create && createsVisibleWork(args)) {
37391
+ return refusedInRun(what(create), via, "publishes material to students", "Create it with --state=DRAFT and no --scheduled; ask the user to publish it from Classroom.");
37392
+ }
37393
+ if (update && publishesToStudents(args)) return refusedInRun(what(update), via, "publishes material to students", "Ask the user to publish it from Classroom.");
37394
+ return void 0;
37395
+ case "students":
37396
+ return create ? gatedElsewhere(what(create), via, "enrols someone in a class", "gog_classroom_students_add") : void 0;
37397
+ case "teachers":
37398
+ return create ? gatedElsewhere(what(create), via, "gives someone a teacher's access to a class and its roster", "gog_classroom_teachers_add") : void 0;
37399
+ case "submissions": {
37400
+ const word = hasCommandWord(args, CLASSROOM_RETURN_WORDS);
37401
+ return word ? gatedElsewhere(what(word), via, "returns work to a student, who is notified", "gog_classroom_submissions_return") : void 0;
37402
+ }
37403
+ case "guardian-invitations":
37404
+ return create ? refusedInRun(what(create), via, "emails a guardian invitation", "Ask the user to invite the guardian from Classroom.") : void 0;
37405
+ }
37141
37406
  }
37142
37407
  async function readCourse(courseId, account, runner = runOrDiagnose) {
37143
37408
  const got = await runner(["classroom", "courses", "get", pos(courseId)], { account });
@@ -37156,6 +37421,52 @@ async function readCourse(courseId, account, runner = runOrDiagnose) {
37156
37421
  }
37157
37422
  };
37158
37423
  }
37424
+ function nested(raw, key) {
37425
+ try {
37426
+ const parsed = JSON.parse(raw);
37427
+ const inner = parsed?.[key];
37428
+ return inner && typeof inner === "object" ? inner : void 0;
37429
+ } catch {
37430
+ return void 0;
37431
+ }
37432
+ }
37433
+ var str = (v) => typeof v === "string" ? v : void 0;
37434
+ var num = (v) => typeof v === "number" ? v : void 0;
37435
+ async function readCoursework(courseId, courseworkId, account, runner = runOrDiagnose) {
37436
+ const got = await runner(["classroom", "coursework", "get", pos(courseId), pos(courseworkId)], { account });
37437
+ if (got.isError) return { error: got };
37438
+ const work = nested(resultText(got), "coursework");
37439
+ const title = str(work?.title);
37440
+ return { coursework: { id: courseworkId, ...title !== void 0 ? { title } : {} } };
37441
+ }
37442
+ async function readSubmission(courseId, courseworkId, submissionId, account, runner = runOrDiagnose) {
37443
+ const got = await runner(["classroom", "submissions", "get", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
37444
+ if (got.isError) return { error: got };
37445
+ const sub = nested(resultText(got), "submission");
37446
+ const fields = {
37447
+ student: str(sub?.userId),
37448
+ state: str(sub?.state),
37449
+ draftGrade: num(sub?.draftGrade),
37450
+ assignedGrade: num(sub?.assignedGrade)
37451
+ };
37452
+ const updateTime = str(sub?.updateTime);
37453
+ return {
37454
+ submission: {
37455
+ id: submissionId,
37456
+ ...Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== void 0))
37457
+ },
37458
+ ...updateTime !== void 0 ? { updateTime } : {}
37459
+ };
37460
+ }
37461
+ async function studentLabel(courseId, userId, account, runner = runOrDiagnose) {
37462
+ const got = await runner(["classroom", "students", "get", pos(courseId), pos(userId)], { account });
37463
+ if (got.isError) return userId;
37464
+ const profile = nested(resultText(got), "student")?.profile;
37465
+ const name = str(profile?.name?.fullName);
37466
+ const email3 = str(profile?.emailAddress);
37467
+ if (name && email3) return `${name} <${email3}>`;
37468
+ return name ?? email3 ?? userId;
37469
+ }
37159
37470
  function registerClassroomTools(server) {
37160
37471
  server.registerTool("gog_classroom_courses_list", {
37161
37472
  description: "List Google Classroom courses.",
@@ -37368,15 +37679,38 @@ function registerClassroomTools(server) {
37368
37679
  return runOrDiagnose(args, { account });
37369
37680
  });
37370
37681
  server.registerTool("gog_classroom_submissions_return", {
37371
- description: "Return a graded submission to the student.",
37682
+ description: "Return a graded submission to the student, who is notified and sees the grade. Reads the course, the coursework and the submission and asks the MCP host to show the user a confirmation prompt with the class, the assignment, the student and the grade being returned; nothing is returned unless they accept." + CONFIRM_FALLBACK_DESCRIPTION,
37372
37683
  annotations: { destructiveHint: true },
37373
37684
  inputSchema: external_exports.object({
37374
37685
  courseId: external_exports.string().describe("Course ID"),
37375
37686
  courseworkId: external_exports.string().describe("Coursework ID"),
37376
37687
  submissionId: external_exports.string().describe("Submission ID"),
37377
- account: accountParam
37688
+ account: accountParam,
37689
+ confirmToken: confirmTokenParam
37378
37690
  })
37379
- }, async ({ courseId, courseworkId, submissionId, account }) => {
37691
+ }, async ({ courseId, courseworkId, submissionId, account, confirmToken }, ctx) => {
37692
+ const course = await readCourse(courseId, account);
37693
+ if (course.error) return course.error;
37694
+ const work = await readCoursework(courseId, courseworkId, account);
37695
+ if (work.error) return work.error;
37696
+ const sub = await readSubmission(courseId, courseworkId, submissionId, account);
37697
+ if (sub.error) return sub.error;
37698
+ const submission = sub.submission.student ? { ...sub.submission, student: await studentLabel(courseId, sub.submission.student, account) } : sub.submission;
37699
+ const view = { course: course.course, coursework: work.coursework, submission };
37700
+ const confirmation = await requireDispatchConfirmation(ctx, {
37701
+ action: "classroom.submission-return",
37702
+ message: "Review and confirm returning this work \u2014 the student is notified and sees the grade:",
37703
+ confirmationLabel: "Confirm that this submission should be returned to the student now.",
37704
+ details: view,
37705
+ unsupportedNote: "Ask the user to return it from Classroom.",
37706
+ fallback: {
37707
+ tool: "gog_classroom_submissions_return",
37708
+ account,
37709
+ confirmToken,
37710
+ subject: () => ({ target: `${courseId}/${courseworkId}/${submissionId}`, revision: sub.updateTime, payload: view, preview: view })
37711
+ }
37712
+ });
37713
+ if (confirmation) return confirmation;
37380
37714
  return runOrDiagnose(["classroom", "submissions", "return", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
37381
37715
  });
37382
37716
  server.registerTool("gog_classroom_submissions_turn_in", {
@@ -37438,13 +37772,13 @@ function registerClassroomTools(server) {
37438
37772
  return runOrDiagnose(["classroom", "announcements", "get", pos(courseId), pos(announcementId)], { account });
37439
37773
  });
37440
37774
  server.registerTool("gog_classroom_announcements_create", {
37441
- description: "Create an announcement in a Google Classroom course. Unless state is DRAFT (which students cannot see), this reads the course and asks the MCP host to show the user a confirmation prompt with the class, the full text and when it publishes; nothing is posted unless they accept. To stage one without asking, pass state DRAFT." + CONFIRM_FALLBACK_DESCRIPTION,
37775
+ description: "Create an announcement in a Google Classroom course. Unless state is DRAFT with no scheduled time (which students cannot see; a scheduled draft publishes itself), this reads the course and asks the MCP host to show the user a confirmation prompt with the class, the full text and when it publishes; nothing is posted unless they accept. To stage one without asking, pass state DRAFT and no scheduled time." + CONFIRM_FALLBACK_DESCRIPTION,
37442
37776
  annotations: { destructiveHint: true },
37443
37777
  inputSchema: external_exports.object({
37444
37778
  courseId: external_exports.string().describe("Course ID"),
37445
37779
  text: external_exports.string().describe("Announcement text"),
37446
- state: external_exports.enum(["PUBLISHED", "DRAFT"]).optional().describe("State (DRAFT is visible only to teachers and needs no confirmation)"),
37447
- scheduled: external_exports.string().optional().describe("Scheduled publish time"),
37780
+ state: external_exports.enum(["PUBLISHED", "DRAFT"]).optional().describe("State (an unscheduled DRAFT is visible only to teachers and needs no confirmation)"),
37781
+ scheduled: external_exports.string().optional().describe("Scheduled publish time (asks the user to confirm, even for a DRAFT)"),
37448
37782
  account: accountParam,
37449
37783
  confirmToken: confirmTokenParam
37450
37784
  })
@@ -37452,7 +37786,7 @@ function registerClassroomTools(server) {
37452
37786
  const args = ["classroom", "announcements", "create", pos(courseId), `--text=${text}`];
37453
37787
  if (state) args.push(`--state=${state}`);
37454
37788
  if (scheduled) args.push(`--scheduled=${scheduled}`);
37455
- if (state !== "DRAFT") {
37789
+ if (state !== "DRAFT" || scheduled) {
37456
37790
  const read = await readCourse(courseId, account);
37457
37791
  if (read.error) return read.error;
37458
37792
  const publishes = scheduled ? `at ${scheduled}` : "immediately";
@@ -37624,6 +37958,9 @@ function registerContactsTools(server) {
37624
37958
  }
37625
37959
 
37626
37960
  // src/tools/docs.ts
37961
+ function vetDocsRun(subcommand, args) {
37962
+ return vetCommentsRun("docs", subcommand, args);
37963
+ }
37627
37964
  function registerDocsTools(server) {
37628
37965
  server.registerTool("gog_docs_info", {
37629
37966
  description: "Get Google Doc metadata: title, ID, and other properties.",
@@ -37720,12 +38057,29 @@ function registerDocsTools(server) {
37720
38057
  }, async ({ docId, account }) => {
37721
38058
  return runOrDiagnose(["docs", "structure", pos(docId)], { account });
37722
38059
  });
37723
- registerRunTool(server, { service: "docs", examples: '"copy", "clear", "insert", "sed", "export"' });
38060
+ registerRunTool(server, { service: "docs", examples: '"copy", "clear", "insert", "sed", "export"', vet: vetDocsRun });
37724
38061
  }
37725
38062
 
37726
38063
  // src/tools/drive.ts
37727
- function vetDriveRun(subcommand, _args) {
37728
- return subcommand.toLowerCase() === "share" ? gatedElsewhere("gog drive share", "gog_drive_run", "grants access to a file", "gog_drive_share") : void 0;
38064
+ var DRIVE_DELETE_WORDS = /* @__PURE__ */ new Set(["delete", "rm", "del"]);
38065
+ function vetDriveRun(subcommand, args) {
38066
+ const sub = subcommand.toLowerCase();
38067
+ if (sub === "share") return gatedElsewhere("gog drive share", "gog_drive_run", "grants access to a file", "gog_drive_share");
38068
+ if (DRIVE_DELETE_WORDS.has(sub) && hasTrueFlag(args, "permanent")) {
38069
+ return gatedElsewhere(`gog drive ${sub} --permanent`, "gog_drive_run", "deletes a file for good, bypassing the trash", "gog_drive_delete");
38070
+ }
38071
+ if (sub === "bulk") {
38072
+ return gatedElsewhere(
38073
+ "gog drive bulk",
38074
+ "gog_drive_run",
38075
+ "rewrites sharing permissions across every matching file in a tree",
38076
+ "gog_drive_share (one file at a time)"
38077
+ );
38078
+ }
38079
+ if (sub === "unshare") {
38080
+ return refusedInRun("gog drive unshare", "gog_drive_run", "removes someone's access to a file", "Use gog_drive_unshare.");
38081
+ }
38082
+ return vetCommentsRun("drive", sub, args);
37729
38083
  }
37730
38084
  var GOOGLE_DOC_MIME = "application/vnd.google-apps.document";
37731
38085
  function shareTargetMeta(raw) {
@@ -37844,16 +38198,36 @@ function registerDriveTools(server) {
37844
38198
  return runOrDiagnose(["drive", "move", pos(fileId), `--parent=${parentId}`], { account });
37845
38199
  });
37846
38200
  server.registerTool("gog_drive_delete", {
37847
- description: "Move a Google Drive file to trash, or permanently delete it with permanent=true (irreversible).",
38201
+ description: "Move a Google Drive file to trash, or permanently delete it with permanent=true (irreversible). A permanent delete reads the file and asks the MCP host to show the user a confirmation prompt naming it first; nothing is deleted unless they accept. Moving to trash is recoverable and does not ask." + CONFIRM_FALLBACK_DESCRIPTION,
37848
38202
  annotations: { destructiveHint: true },
37849
38203
  inputSchema: external_exports.object({
37850
38204
  fileId: external_exports.string().describe("File ID to delete"),
37851
- permanent: external_exports.boolean().optional().describe("Permanently delete instead of moving to trash (irreversible)"),
37852
- account: accountParam
38205
+ permanent: external_exports.boolean().optional().describe("Permanently delete instead of moving to trash (irreversible; asks the user first)"),
38206
+ account: accountParam,
38207
+ confirmToken: confirmTokenParam
37853
38208
  })
37854
- }, async ({ fileId, permanent, account }) => {
38209
+ }, async ({ fileId, permanent, account, confirmToken }, ctx) => {
37855
38210
  const args = ["drive", "delete", pos(fileId)];
37856
- if (permanent) args.push("--permanent");
38211
+ if (permanent) {
38212
+ const got = await runOrDiagnose(["drive", "get", pos(fileId)], { account });
38213
+ if (got.isError) return got;
38214
+ const target = { file: { id: fileId, ...shareTargetMeta(resultText(got)) }, permanent: true };
38215
+ const confirmation = await requireDispatchConfirmation(ctx, {
38216
+ action: "drive.delete-permanent",
38217
+ message: "Review and confirm PERMANENTLY deleting this file \u2014 it skips the trash and cannot be recovered:",
38218
+ confirmationLabel: "Confirm that this file should be permanently deleted now.",
38219
+ details: target,
38220
+ unsupportedNote: "Move it to the trash instead (permanent=false); the user can empty the trash from Google Drive.",
38221
+ fallback: {
38222
+ tool: "gog_drive_delete",
38223
+ account,
38224
+ confirmToken,
38225
+ subject: () => ({ target: fileId, payload: target, preview: target })
38226
+ }
38227
+ });
38228
+ if (confirmation) return confirmation;
38229
+ args.push("--permanent");
38230
+ }
37857
38231
  args.push("--force");
37858
38232
  return runOrDiagnose(args, { account });
37859
38233
  });
@@ -38179,18 +38553,22 @@ function trustedDomains(account) {
38179
38553
  }
38180
38554
  function logGmailDispatch(tool, recipients, account) {
38181
38555
  const domains = trustedDomains(account);
38182
- const externalRecipients = recipients.filter((recipient) => {
38556
+ const externalDomains = /* @__PURE__ */ new Set();
38557
+ let externalRecipientCount = 0;
38558
+ for (const recipient of recipients) {
38183
38559
  const at = recipient.indexOf("@");
38184
- const domain2 = at > -1 ? recipient.slice(at + 1) : "";
38185
- return !domain2 || !domains.has(domain2);
38186
- });
38560
+ const domain2 = at > -1 ? recipient.slice(at + 1).toLowerCase() : "";
38561
+ if (domain2 && domains.has(domain2)) continue;
38562
+ externalRecipientCount += 1;
38563
+ externalDomains.add(domain2 || "(no domain)");
38564
+ }
38187
38565
  const event = {
38188
38566
  event: "gmail_dispatch",
38189
38567
  tool,
38190
38568
  recipientCount: recipients.length,
38191
- externalRecipientCount: externalRecipients.length,
38192
- hasExternalRecipients: externalRecipients.length > 0,
38193
- externalRecipients,
38569
+ externalRecipientCount,
38570
+ hasExternalRecipients: externalRecipientCount > 0,
38571
+ externalDomains: [...externalDomains].sort(),
38194
38572
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
38195
38573
  };
38196
38574
  process.stderr.write(`${JSON.stringify(event)}
@@ -38357,6 +38735,39 @@ async function sendReply(kind, toolName, messageId, account, flags, ctx, confirm
38357
38735
  return result;
38358
38736
  }
38359
38737
  var GMAIL_RUN_BLOCKED_SETTINGS = /* @__PURE__ */ new Set(["forwarding", "autoforward", "filters", "delegates"]);
38738
+ var GMAIL_DELETE_WORDS = /* @__PURE__ */ new Set(["delete", "rm", "del", "remove"]);
38739
+ var GMAIL_CREATE_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
38740
+ var GMAIL_UPDATE_WORDS = /* @__PURE__ */ new Set(["update", "edit", "set"]);
38741
+ var SENDAS = /* @__PURE__ */ new Set(["sendas"]);
38742
+ var VACATION = /* @__PURE__ */ new Set(["vacation"]);
38743
+ function vetGmailGatedRun(sub, args) {
38744
+ if (sub === "batch") {
38745
+ const word = hasCommandWord(args, GMAIL_DELETE_WORDS);
38746
+ return word ? gatedElsewhere(`gog gmail batch ${word.toLowerCase()}`, "gog_gmail_run", "deletes mail for good, bypassing the Trash", "gog_gmail_batch_delete") : void 0;
38747
+ }
38748
+ if (sub !== "settings" && sub !== "sendas" && sub !== "vacation") return void 0;
38749
+ const words = sub === "settings" ? args : [sub, ...args];
38750
+ if (hasCommandWord(words, SENDAS)) {
38751
+ const word = hasCommandWord(args, GMAIL_CREATE_WORDS);
38752
+ if (word) {
38753
+ return gatedElsewhere(
38754
+ `gog gmail sendas ${word.toLowerCase()}`,
38755
+ "gog_gmail_run",
38756
+ "makes Google email the address and adds a sending identity",
38757
+ "gog_gmail_sendas_create"
38758
+ );
38759
+ }
38760
+ }
38761
+ if (hasCommandWord(words, VACATION) && hasCommandWord(args, GMAIL_UPDATE_WORDS) && hasTrueFlag(args, "enable")) {
38762
+ return gatedElsewhere(
38763
+ "gog gmail vacation update --enable",
38764
+ "gog_gmail_run",
38765
+ "turns on an auto-reply to every sender",
38766
+ "gog_gmail_vacation_update"
38767
+ );
38768
+ }
38769
+ return void 0;
38770
+ }
38360
38771
  function vetGmailRun(subcommand, args) {
38361
38772
  if (subcommand === "autoreply") {
38362
38773
  return "gog gmail autoreply sends mail and is not available through gog_gmail_run. Use gog_gmail_autoreply, which asks the user to confirm.";
@@ -38364,6 +38775,8 @@ function vetGmailRun(subcommand, args) {
38364
38775
  if (GMAIL_RUN_BLOCKED_SETTINGS.has(subcommand)) {
38365
38776
  return `gog gmail ${subcommand} can forward or hand over mail and is not available through gog_gmail_run. Use the dedicated gog_gmail_* tool instead.`;
38366
38777
  }
38778
+ const gated = vetGmailGatedRun(subcommand.toLowerCase(), args);
38779
+ if (gated) return gated;
38367
38780
  if (subcommand === "settings") {
38368
38781
  const blocked = hasCommandWord(args, GMAIL_RUN_BLOCKED_SETTINGS);
38369
38782
  if (blocked) {
@@ -38575,6 +38988,13 @@ function countNonEmptyCells(getOutput) {
38575
38988
  }
38576
38989
 
38577
38990
  // src/tools/sheets.ts
38991
+ var DATASOURCE_ALIASES = /* @__PURE__ */ new Set(["datasource", "data-source", "data-sources", "connected-sheets"]);
38992
+ var DATASOURCE_BILLED = /* @__PURE__ */ new Set(["add", "update", "refresh"]);
38993
+ function vetSheetsRun(subcommand, args) {
38994
+ if (!DATASOURCE_ALIASES.has(subcommand.toLowerCase())) return void 0;
38995
+ const word = hasCommandWord(args, DATASOURCE_BILLED)?.toLowerCase();
38996
+ return word ? gatedElsewhere(`gog sheets datasource ${word}`, "gog_sheets_run", "runs a billed BigQuery job", `gog_sheets_datasource_${word}`) : void 0;
38997
+ }
38578
38998
  var cellValueParam = external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean(), external_exports.null()]);
38579
38999
  var dryRunParam = external_exports.boolean().optional().describe(
38580
39000
  "Preview the operation without modifying the sheet (gog --dry-run): reports the intended actions and exits without writing."
@@ -38690,7 +39110,7 @@ function registerSheetsTools(server) {
38690
39110
  }, async ({ spreadsheetId, find, replace, account }) => {
38691
39111
  return runOrDiagnose(["sheets", "find-replace", pos(spreadsheetId), pos(find), pos(replace)], { account });
38692
39112
  });
38693
- registerRunTool(server, { service: "sheets", examples: '"freeze", "add-tab", "rename-tab"' });
39113
+ registerRunTool(server, { service: "sheets", examples: '"freeze", "add-tab", "rename-tab"', vet: vetSheetsRun });
38694
39114
  }
38695
39115
 
38696
39116
  // src/tools/slides.ts
@@ -38854,7 +39274,7 @@ function registerTasksTools(server) {
38854
39274
  }
38855
39275
 
38856
39276
  // src/server.ts
38857
- var VERSION = true ? "4.5.0" : "0.0.0";
39277
+ var VERSION = true ? "4.5.1" : "0.0.0";
38858
39278
  var BASE_TOOL_REGISTRARS = [
38859
39279
  registerApiTools,
38860
39280
  registerAppScriptTools,