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/lib.js CHANGED
@@ -838,14 +838,14 @@ function promiseAllObject(promisesObj) {
838
838
  }
839
839
  function randomString(length = 10) {
840
840
  const chars = "abcdefghijklmnopqrstuvwxyz";
841
- let str = "";
841
+ let str2 = "";
842
842
  for (let i = 0; i < length; i++) {
843
- str += chars[Math.floor(Math.random() * chars.length)];
843
+ str2 += chars[Math.floor(Math.random() * chars.length)];
844
844
  }
845
- return str;
845
+ return str2;
846
846
  }
847
- function esc(str) {
848
- return JSON.stringify(str);
847
+ function esc(str2) {
848
+ return JSON.stringify(str2);
849
849
  }
850
850
  function slugify(input2) {
851
851
  return input2.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
@@ -959,8 +959,8 @@ var primitiveTypes = /* @__PURE__ */ new Set([
959
959
  "symbol",
960
960
  "undefined"
961
961
  ]);
962
- function escapeRegex(str) {
963
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
962
+ function escapeRegex(str2) {
963
+ return str2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
964
964
  }
965
965
  function clone(inst, def, params) {
966
966
  const cl = new inst._zod.constr(def ?? inst._zod.def);
@@ -1214,13 +1214,13 @@ function getSizableOrigin(input2) {
1214
1214
  return "unknown";
1215
1215
  }
1216
1216
  var highSurrogate = /[\uD800-\uDBFF]/;
1217
- function codePointLength(str) {
1218
- const units = str.length;
1219
- if (!highSurrogate.test(str))
1217
+ function codePointLength(str2) {
1218
+ const units = str2.length;
1219
+ if (!highSurrogate.test(str2))
1220
1220
  return units;
1221
1221
  let count = units;
1222
1222
  for (let i = 0; i < units - 1; i++) {
1223
- if ((str.charCodeAt(i) & 64512) === 55296 && (str.charCodeAt(i + 1) & 64512) === 56320) {
1223
+ if ((str2.charCodeAt(i) & 64512) === 55296 && (str2.charCodeAt(i + 1) & 64512) === 56320) {
1224
1224
  count--;
1225
1225
  i++;
1226
1226
  }
@@ -25182,7 +25182,7 @@ var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
25182
25182
  }
25183
25183
  exports._ = _;
25184
25184
  const plus = new _Code("+");
25185
- function str(strs, ...args) {
25185
+ function str2(strs, ...args) {
25186
25186
  const expr = [safeStringify(strs[0])];
25187
25187
  let i = 0;
25188
25188
  while (i < args.length) {
@@ -25193,7 +25193,7 @@ var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
25193
25193
  optimize(expr);
25194
25194
  return new _Code(expr);
25195
25195
  }
25196
- exports.str = str;
25196
+ exports.str = str2;
25197
25197
  function addCodeArg(code, arg) {
25198
25198
  if (arg instanceof _Code) code.push(...arg._items);
25199
25199
  else if (arg instanceof Name) code.push(arg);
@@ -25226,7 +25226,7 @@ var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
25226
25226
  if (typeof b == "string" && b[0] === '"' && !(a instanceof Name)) return `"${a}${b.slice(1)}`;
25227
25227
  }
25228
25228
  function strConcat(c1, c2) {
25229
- return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str`${c1}${c2}`;
25229
+ return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str2`${c1}${c2}`;
25230
25230
  }
25231
25231
  exports.strConcat = strConcat;
25232
25232
  function interpolate(x) {
@@ -26105,21 +26105,21 @@ var require_util = /* @__PURE__ */ __commonJSMin(((exports) => {
26105
26105
  return (0, codegen_1._)`${topSchemaRef}${schemaPath}${(0, codegen_1.getProperty)(keyword)}`;
26106
26106
  }
26107
26107
  exports.schemaRefOrVal = schemaRefOrVal;
26108
- function unescapeFragment(str) {
26109
- return unescapeJsonPointer(decodeURIComponent(str));
26108
+ function unescapeFragment(str2) {
26109
+ return unescapeJsonPointer(decodeURIComponent(str2));
26110
26110
  }
26111
26111
  exports.unescapeFragment = unescapeFragment;
26112
- function escapeFragment(str) {
26113
- return encodeURIComponent(escapeJsonPointer(str));
26112
+ function escapeFragment(str2) {
26113
+ return encodeURIComponent(escapeJsonPointer(str2));
26114
26114
  }
26115
26115
  exports.escapeFragment = escapeFragment;
26116
- function escapeJsonPointer(str) {
26117
- if (typeof str == "number") return `${str}`;
26118
- return str.replace(/~/g, "~0").replace(/\//g, "~1");
26116
+ function escapeJsonPointer(str2) {
26117
+ if (typeof str2 == "number") return `${str2}`;
26118
+ return str2.replace(/~/g, "~0").replace(/\//g, "~1");
26119
26119
  }
26120
26120
  exports.escapeJsonPointer = escapeJsonPointer;
26121
- function unescapeJsonPointer(str) {
26122
- return str.replace(/~1/g, "/").replace(/~0/g, "~");
26121
+ function unescapeJsonPointer(str2) {
26122
+ return str2.replace(/~1/g, "/").replace(/~0/g, "~");
26123
26123
  }
26124
26124
  exports.unescapeJsonPointer = unescapeJsonPointer;
26125
26125
  function eachItem(xs, f) {
@@ -27005,8 +27005,8 @@ var require_json_schema_traverse = /* @__PURE__ */ __commonJSMin(((exports, modu
27005
27005
  post(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
27006
27006
  }
27007
27007
  }
27008
- function escapeJsonPtr(str) {
27009
- return str.replace(/~/g, "~0").replace(/\//g, "~1");
27008
+ function escapeJsonPtr(str2) {
27009
+ return str2.replace(/~/g, "~0").replace(/\//g, "~1");
27010
27010
  }
27011
27011
  }));
27012
27012
  var require_resolve = /* @__PURE__ */ __commonJSMin(((exports) => {
@@ -27898,9 +27898,9 @@ var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
27898
27898
  isIPV6: false
27899
27899
  };
27900
27900
  }
27901
- function findToken(str, token) {
27901
+ function findToken(str2, token) {
27902
27902
  let ind = 0;
27903
- for (let i = 0; i < str.length; i++) if (str[i] === token) ind++;
27903
+ for (let i = 0; i < str2.length; i++) if (str2[i] === token) ind++;
27904
27904
  return ind;
27905
27905
  }
27906
27906
  function removeDotSegments(path) {
@@ -28415,7 +28415,7 @@ var require_core$3 = /* @__PURE__ */ __commonJSMin(((exports) => {
28415
28415
  const util_1 = require_util();
28416
28416
  const $dataRefSchema = require_data();
28417
28417
  const uri_1 = require_uri();
28418
- const defaultRegExp = (str, flags) => new RegExp(str, flags);
28418
+ const defaultRegExp = (str2, flags) => new RegExp(str2, flags);
28419
28419
  defaultRegExp.code = "new RegExp";
28420
28420
  const META_IGNORE_OPTIONS = [
28421
28421
  "removeAdditional",
@@ -29121,16 +29121,16 @@ var require_multipleOf = /* @__PURE__ */ __commonJSMin(((exports) => {
29121
29121
  }));
29122
29122
  var require_ucs2length = /* @__PURE__ */ __commonJSMin(((exports) => {
29123
29123
  Object.defineProperty(exports, "__esModule", { value: true });
29124
- function ucs2length(str) {
29125
- const len = str.length;
29124
+ function ucs2length(str2) {
29125
+ const len = str2.length;
29126
29126
  let length = 0;
29127
29127
  let pos2 = 0;
29128
29128
  let value;
29129
29129
  while (pos2 < len) {
29130
29130
  length++;
29131
- value = str.charCodeAt(pos2++);
29131
+ value = str2.charCodeAt(pos2++);
29132
29132
  if (value >= 55296 && value <= 56319 && pos2 < len) {
29133
- value = str.charCodeAt(pos2);
29133
+ value = str2.charCodeAt(pos2);
29134
29134
  if ((value & 64512) === 56320) pos2++;
29135
29135
  }
29136
29136
  }
@@ -31841,8 +31841,8 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
31841
31841
  30,
31842
31842
  31
31843
31843
  ];
31844
- function date5(str) {
31845
- const matches = DATE.exec(str);
31844
+ function date5(str2) {
31845
+ const matches = DATE.exec(str2);
31846
31846
  if (!matches) return false;
31847
31847
  const year = +matches[1];
31848
31848
  const month = +matches[2];
@@ -31857,8 +31857,8 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
31857
31857
  }
31858
31858
  const TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
31859
31859
  function getTime(strictTimeZone) {
31860
- return function time3(str) {
31861
- const matches = TIME.exec(str);
31860
+ return function time3(str2) {
31861
+ const matches = TIME.exec(str2);
31862
31862
  if (!matches) return false;
31863
31863
  const hr = +matches[1];
31864
31864
  const min = +matches[2];
@@ -31895,8 +31895,8 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
31895
31895
  const DATE_TIME_SEPARATOR = /t|\s/i;
31896
31896
  function getDateTime(strictTimeZone) {
31897
31897
  const time3 = getTime(strictTimeZone);
31898
- return function date_time(str) {
31899
- const dateTime = str.split(DATE_TIME_SEPARATOR);
31898
+ return function date_time(str2) {
31899
+ const dateTime = str2.split(DATE_TIME_SEPARATOR);
31900
31900
  return dateTime.length === 2 && date5(dateTime[0]) && time3(dateTime[1]);
31901
31901
  };
31902
31902
  }
@@ -31917,13 +31917,13 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
31917
31917
  }
31918
31918
  const NOT_URI_FRAGMENT = /\/|:/;
31919
31919
  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;
31920
- function uri(str) {
31921
- return NOT_URI_FRAGMENT.test(str) && URI.test(str);
31920
+ function uri(str2) {
31921
+ return NOT_URI_FRAGMENT.test(str2) && URI.test(str2);
31922
31922
  }
31923
31923
  const BYTE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gm;
31924
- function byte(str) {
31924
+ function byte(str2) {
31925
31925
  BYTE.lastIndex = 0;
31926
- return BYTE.test(str);
31926
+ return BYTE.test(str2);
31927
31927
  }
31928
31928
  const MIN_INT32 = -(2 ** 31);
31929
31929
  const MAX_INT32 = 2 ** 31 - 1;
@@ -31937,10 +31937,10 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
31937
31937
  return true;
31938
31938
  }
31939
31939
  const Z_ANCHOR = /[^\\]\\Z/;
31940
- function regex(str) {
31941
- if (Z_ANCHOR.test(str)) return false;
31940
+ function regex(str2) {
31941
+ if (Z_ANCHOR.test(str2)) return false;
31942
31942
  try {
31943
- new RegExp(str);
31943
+ new RegExp(str2);
31944
31944
  return true;
31945
31945
  } catch (e) {
31946
31946
  return false;
@@ -34327,6 +34327,7 @@ async function diagnose(err) {
34327
34327
  const isAuthError = !isTransientError && AUTH_ERROR_PATTERN.test(errText);
34328
34328
  const isGridLimitError = GRID_LIMIT_ERROR_PATTERN.test(errText);
34329
34329
  const hint = isInvalidGrant ? INVALID_GRANT_HINT : isAuthError ? AUTH_HINT : isTransientError ? TRANSIENT_HINT : isGridLimitError ? GRID_LIMIT_HINT : "";
34330
+ if (!isAuthError && !isInvalidGrant) return errorResult(`${errText}${hint}`);
34330
34331
  try {
34331
34332
  const accounts = formatAccountList(await run(["auth", "list"]));
34332
34333
  return errorResult(`${errText}
@@ -34471,12 +34472,53 @@ function attachmentPreview(details) {
34471
34472
  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.";
34472
34473
  var CONFIRM_ACTION_INSTRUCTION = CONFIRM_TOKEN_INSTRUCTION;
34473
34474
  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.';
34475
+ function refusedInRun(what, via, does, instead) {
34476
+ return `${what} ${does} and is not available through ${via}. ${instead}`;
34477
+ }
34474
34478
  function gatedElsewhere(what, via, does, tool) {
34475
- return `${what} ${does} and is not available through ${via}. Use ${tool}, which asks the user to confirm.`;
34479
+ return refusedInRun(what, via, does, `Use ${tool}, which asks the user to confirm.`);
34476
34480
  }
34477
34481
  function hasCommandWord(args, words) {
34478
34482
  return args.find((a) => words.has(a.toLowerCase()));
34479
34483
  }
34484
+ function flagValue(args, name) {
34485
+ const flag = `--${name}`;
34486
+ for (let i = 0; i < args.length; i++) {
34487
+ const a = args[i];
34488
+ if (a === flag) return args[i + 1] ?? "";
34489
+ if (a.startsWith(`${flag}=`)) return a.slice(flag.length + 1);
34490
+ }
34491
+ return void 0;
34492
+ }
34493
+ var COMMENT_ADD_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
34494
+ var COMMENT_REPLY_WORDS = /* @__PURE__ */ new Set(["reply", "respond"]);
34495
+ function hasTrueFlag(args, flag) {
34496
+ const name = `--${flag}`;
34497
+ return args.some((a) => {
34498
+ const lower = a.toLowerCase();
34499
+ if (lower === name) return true;
34500
+ return lower.startsWith(`${name}=`) && lower.slice(name.length + 1) !== "false";
34501
+ });
34502
+ }
34503
+ function vetCommentsRun(service, subcommand, args) {
34504
+ if (subcommand.toLowerCase() !== "comments") return void 0;
34505
+ const reply = hasCommandWord(args, COMMENT_REPLY_WORDS);
34506
+ if (reply) {
34507
+ return gatedElsewhere(
34508
+ `gog ${service} comments ${reply.toLowerCase()}`,
34509
+ `gog_${service}_run`,
34510
+ "notifies the comment thread",
34511
+ `gog_${service}_comments_reply`
34512
+ );
34513
+ }
34514
+ const add = hasCommandWord(args, COMMENT_ADD_WORDS);
34515
+ return add ? gatedElsewhere(
34516
+ `gog ${service} comments ${add.toLowerCase()}`,
34517
+ `gog_${service}_run`,
34518
+ "notifies the file's owner and anyone it mentions",
34519
+ `gog_${service}_comments_add`
34520
+ ) : void 0;
34521
+ }
34480
34522
  async function requireDispatchConfirmation(ctx, options) {
34481
34523
  const { action, fallback } = options;
34482
34524
  const confirmation = {
@@ -34505,16 +34547,33 @@ function resultText(result) {
34505
34547
  // src/tools/api.ts
34506
34548
  var GMAIL_API_BLOCKED = /(?:\.send$|forwarding|filters\.create|filters\.update|delegates\.create)/i;
34507
34549
  var DISPATCH_API_BLOCKED = {
34508
- chat: [{ method: /(?:^|\.)spaces\.messages\.create$/i, does: "posts a Chat message", tool: "gog_chat_messages_send / gog_chat_dm_send" }],
34509
- drive: [{ method: /(?:^|\.)permissions\.(?:create|update)$/i, does: "grants access to a file", tool: "gog_drive_share" }],
34550
+ chat: [
34551
+ { method: /(?:^|\.)spaces\.messages\.create$/i, does: "posts a Chat message", tool: "gog_chat_messages_send / gog_chat_dm_send" },
34552
+ { method: /(?:^|\.)spaces\.(?:setup|members\.create)$/i, does: "adds people to a space", tool: "gog_chat_spaces_create" }
34553
+ ],
34554
+ drive: [
34555
+ { method: /(?:^|\.)permissions\.(?:create|update)$/i, does: "grants access to a file", tool: "gog_drive_share" },
34556
+ { method: /(?:^|\.)comments\.create$/i, does: "notifies the file's owner and anyone it mentions", tool: "gog_drive_comments_add / gog_docs_comments_add" },
34557
+ { method: /(?:^|\.)replies\.create$/i, does: "notifies the comment thread", tool: "gog_drive_comments_reply / gog_docs_comments_reply" }
34558
+ ],
34510
34559
  classroom: [
34511
34560
  { method: /(?:^|\.)courses\.announcements\.create$/i, does: "posts to a class", tool: "gog_classroom_announcements_create" },
34512
- { method: /(?:^|\.)invitations\.create$/i, does: "invites someone to a class", tool: "gog_classroom_invitations_create" }
34561
+ { method: /(?:^|\.)invitations\.create$/i, does: "invites someone to a class", tool: "gog_classroom_invitations_create" },
34562
+ { method: /(?:^|\.)courses\.students\.create$/i, does: "adds someone to a class", tool: "gog_classroom_students_add" },
34563
+ { method: /(?:^|\.)courses\.teachers\.create$/i, does: "gives someone teacher access to a class", tool: "gog_classroom_teachers_add" },
34564
+ { method: /(?:^|\.)courses\.coursework\.create$/i, does: "posts coursework to a class", tool: "gog_classroom_coursework_create" },
34565
+ { method: /(?:^|\.)studentsubmissions\.return$/i, does: "returns work to a student", tool: "gog_classroom_submissions_return" }
34513
34566
  ],
34514
34567
  calendar: [
34515
34568
  { method: /(?:^|\.)events\.(?:insert|import|quickadd)$/i, does: "can put an event on guests' calendars", tool: "gog_calendar_create" },
34516
- { method: /(?:^|\.)events\.(?:update|patch)$/i, does: "can change what guests see", tool: "gog_calendar_update / gog_calendar_respond" }
34517
- ]
34569
+ { method: /(?:^|\.)events\.(?:update|patch)$/i, does: "can change what guests see", tool: "gog_calendar_update / gog_calendar_respond" },
34570
+ { method: /(?:^|\.)events\.move$/i, does: "can email every guest", tool: "gog_calendar_move" }
34571
+ ],
34572
+ gmail: [
34573
+ { method: /(?:^|\.)settings\.sendas\.create$/i, does: "makes Google email the address and adds a sending identity", tool: "gog_gmail_sendas_create" },
34574
+ { method: /(?:^|\.)settings\.updatevacation$/i, does: "can turn on an auto-reply to every sender", tool: "gog_gmail_vacation_update" }
34575
+ ],
34576
+ script: [{ method: /(?:^|\.)scripts\.run$/i, does: "executes code with this account's authority", tool: "gog_appscript_run_function" }]
34518
34577
  };
34519
34578
  function refusedApiCall(api, method) {
34520
34579
  const name = api.trim().toLowerCase();
@@ -34525,6 +34584,14 @@ function refusedApiCall(api, method) {
34525
34584
  const hit = (Object.hasOwn(DISPATCH_API_BLOCKED, name) ? DISPATCH_API_BLOCKED[name] : []).find((b) => b.method.test(m));
34526
34585
  return hit ? gatedElsewhere(`${name} ${m}`, "gog_api_call", hit.does, hit.tool) : void 0;
34527
34586
  }
34587
+ function previewJson(text) {
34588
+ if (text.length > BODY_PREVIEW_MAX) return bodyPreview(text);
34589
+ try {
34590
+ return JSON.parse(text);
34591
+ } catch {
34592
+ return text;
34593
+ }
34594
+ }
34528
34595
  function registerApiTools(server) {
34529
34596
  server.registerTool("gog_api_list", {
34530
34597
  description: "List the Google Discovery APIs available for gog_api_call / gog_api_describe (name + version + title).",
@@ -34553,7 +34620,7 @@ function registerApiTools(server) {
34553
34620
  return runOrDiagnose(args, { account });
34554
34621
  });
34555
34622
  server.registerTool("gog_api_call", {
34556
- 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.",
34623
+ 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,
34557
34624
  annotations: { destructiveHint: true },
34558
34625
  inputSchema: external_exports.object({
34559
34626
  api: external_exports.string().describe("Discovery API name (e.g. drive, gmail, calendar)"),
@@ -34562,11 +34629,12 @@ function registerApiTools(server) {
34562
34629
  params: external_exports.string().optional().describe('Query/path parameters as a JSON object string (e.g. {"fileId":"abc","fields":"name"})'),
34563
34630
  body: external_exports.string().optional().describe("Request body as a JSON string (for write methods)"),
34564
34631
  scope: external_exports.string().optional().describe("Override the OAuth scope used for the call"),
34565
- 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."),
34566
- dryRun: external_exports.boolean().optional().describe("Print the intended request and exit without sending it (no changes made)"),
34567
- account: accountParam
34632
+ 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."),
34633
+ dryRun: external_exports.boolean().optional().describe("Print the intended request and exit without sending it (no changes made, no confirmation prompt)"),
34634
+ account: accountParam,
34635
+ confirmToken: confirmTokenParam
34568
34636
  })
34569
- }, async ({ api, version: version2, method, params, body, scope, allowWrite, dryRun, account }) => {
34637
+ }, async ({ api, version: version2, method, params, body, scope, allowWrite, dryRun, account, confirmToken }, ctx) => {
34570
34638
  try {
34571
34639
  assertSafeForwardedArgs([api, version2, method]);
34572
34640
  const refusal = refusedApiCall(api, method);
@@ -34576,6 +34644,27 @@ function registerApiTools(server) {
34576
34644
  } catch (err) {
34577
34645
  return errorResult(errorText(err));
34578
34646
  }
34647
+ if (allowWrite && !dryRun) {
34648
+ const request = { api, version: version2, method, params, body, scope };
34649
+ const view = { api, version: version2, method };
34650
+ if (params !== void 0) view.params = previewJson(params);
34651
+ if (body !== void 0) view.body = previewJson(body);
34652
+ if (scope !== void 0) view.scope = scope;
34653
+ const confirmation = await requireDispatchConfirmation(ctx, {
34654
+ action: "api.call",
34655
+ message: `Review and confirm this raw Google API write (${api} ${version2} ${method}):`,
34656
+ confirmationLabel: "Confirm that this API method should be invoked with exactly these parameters.",
34657
+ details: view,
34658
+ unsupportedNote: "Set dryRun=true to see the request gog would send without sending it, or use the dedicated gog_* tool for this operation.",
34659
+ fallback: {
34660
+ tool: "gog_api_call",
34661
+ account,
34662
+ confirmToken,
34663
+ subject: () => ({ target: `${api}/${version2}/${method}`, payload: request, preview: view })
34664
+ }
34665
+ });
34666
+ if (confirmation) return confirmation;
34667
+ }
34579
34668
  const args = ["api", "call", pos(api), pos(version2), pos(method)];
34580
34669
  if (params) args.push(`--params=${params}`);
34581
34670
  if (body) args.push(`--body=${body}`);
@@ -34588,6 +34677,22 @@ function registerApiTools(server) {
34588
34677
  }
34589
34678
 
34590
34679
  // src/tools/appscript.ts
34680
+ function vetAppScriptRun(subcommand, _args) {
34681
+ return subcommand.toLowerCase() === "run" ? gatedElsewhere("gog appscript run", "gog_appscript_run", "executes code with this account's authority", "gog_appscript_run_function") : void 0;
34682
+ }
34683
+ function projectSnapshot(raw, scriptId) {
34684
+ let project;
34685
+ try {
34686
+ project = JSON.parse(raw)?.project;
34687
+ } catch {
34688
+ project = void 0;
34689
+ }
34690
+ return {
34691
+ scriptId,
34692
+ ...typeof project?.title === "string" ? { title: project.title } : {},
34693
+ ...typeof project?.updateTime === "string" ? { updateTime: project.updateTime } : {}
34694
+ };
34695
+ }
34591
34696
  function registerAppScriptTools(server) {
34592
34697
  const scriptIdParam = external_exports.string().describe(
34593
34698
  "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"
@@ -34668,16 +34773,18 @@ function registerAppScriptTools(server) {
34668
34773
  return runOrDiagnose(args, { account });
34669
34774
  });
34670
34775
  server.registerTool("gog_appscript_run_function", {
34671
- 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,
34776
+ 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,
34672
34777
  annotations: { destructiveHint: true },
34673
34778
  inputSchema: external_exports.object({
34674
34779
  scriptId: scriptIdParam,
34675
34780
  functionName: external_exports.string().describe('Name of the function to call, e.g. "doWork"'),
34676
34781
  params: external_exports.string().optional().describe(`Function parameters as a JSON ARRAY of positional arguments, e.g. '["a", 1]' \u2014 not an object`),
34677
34782
  devMode: external_exports.boolean().optional().describe("Run the latest saved code rather than the deployed version (owner only)"),
34678
- account: accountParam
34783
+ account: accountParam,
34784
+ confirmToken: confirmTokenParam
34679
34785
  })
34680
- }, async ({ scriptId, functionName, params, devMode, account }) => {
34786
+ }, async ({ scriptId, functionName, params, devMode, account, confirmToken }, ctx) => {
34787
+ let parsedParams;
34681
34788
  if (params !== void 0) {
34682
34789
  let parsed;
34683
34790
  try {
@@ -34688,7 +34795,26 @@ function registerAppScriptTools(server) {
34688
34795
  if (!Array.isArray(parsed)) {
34689
34796
  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}`);
34690
34797
  }
34798
+ parsedParams = parsed;
34691
34799
  }
34800
+ const got = await runOrDiagnose(["appscript", "get", pos(scriptId)], { account });
34801
+ if (got.isError) return got;
34802
+ const { updateTime, ...project } = projectSnapshot(resultText(got), scriptId);
34803
+ const execution = { project, functionName, params: parsedParams, devMode: Boolean(devMode), runsAs: senderPreview(account) };
34804
+ const confirmation = await requireDispatchConfirmation(ctx, {
34805
+ action: "appscript.run-function",
34806
+ message: "Review and confirm running this Apps Script function with the account's full authority:",
34807
+ confirmationLabel: "Confirm that this code should run now.",
34808
+ details: execution,
34809
+ unsupportedNote: "Ask the user to run it themselves from the Apps Script editor.",
34810
+ fallback: {
34811
+ tool: "gog_appscript_run_function",
34812
+ account,
34813
+ confirmToken,
34814
+ subject: () => ({ target: `${scriptId}/${functionName}`, revision: updateTime, payload: execution, preview: execution })
34815
+ }
34816
+ });
34817
+ if (confirmation) return confirmation;
34692
34818
  const args = ["appscript", "run", pos(scriptId), pos(functionName)];
34693
34819
  if (params !== void 0) args.push(`--params=${params}`);
34694
34820
  if (devMode) args.push("--dev-mode");
@@ -34697,6 +34823,7 @@ function registerAppScriptTools(server) {
34697
34823
  registerRunTool(server, {
34698
34824
  service: "appscript",
34699
34825
  examples: '"get", "content", "deployments"',
34826
+ vet: vetAppScriptRun,
34700
34827
  note: "To execute a function, use gog_appscript_run_function \u2014 this tool is the generic escape hatch."
34701
34828
  });
34702
34829
  }
@@ -34839,21 +34966,47 @@ function authToolsFor(defaultServices) {
34839
34966
  }
34840
34967
 
34841
34968
  // src/tools/calendar.ts
34842
- var CALENDAR_GATED = {
34843
- create: "gog_calendar_create",
34844
- add: "gog_calendar_create",
34845
- new: "gog_calendar_create",
34846
- update: "gog_calendar_update",
34847
- edit: "gog_calendar_update",
34848
- set: "gog_calendar_update",
34849
- respond: "gog_calendar_respond",
34850
- rsvp: "gog_calendar_respond",
34851
- reply: "gog_calendar_respond"
34969
+ var ASKS = (tool) => `Use ${tool}, which asks the user to confirm.`;
34970
+ var CALENDAR_REFUSED = {
34971
+ create: { does: "can change what guests see", instead: ASKS("gog_calendar_create") },
34972
+ add: { does: "can change what guests see", instead: ASKS("gog_calendar_create") },
34973
+ new: { does: "can change what guests see", instead: ASKS("gog_calendar_create") },
34974
+ update: { does: "can change what guests see", instead: ASKS("gog_calendar_update") },
34975
+ edit: { does: "can change what guests see", instead: ASKS("gog_calendar_update") },
34976
+ set: { does: "can change what guests see", instead: ASKS("gog_calendar_update") },
34977
+ respond: { does: "can change what guests see", instead: ASKS("gog_calendar_respond") },
34978
+ rsvp: { does: "can change what guests see", instead: ASKS("gog_calendar_respond") },
34979
+ reply: { does: "can change what guests see", instead: ASKS("gog_calendar_respond") },
34980
+ move: { does: "moves an event off guests' calendars (and can email them)", instead: ASKS("gog_calendar_move") },
34981
+ transfer: { does: "moves an event off guests' calendars (and can email them)", instead: ASKS("gog_calendar_move") },
34982
+ delete: { does: "removes an event from every guest's calendar (the whole series by default)", instead: ASKS("gog_calendar_delete") },
34983
+ del: { does: "removes an event from every guest's calendar (the whole series by default)", instead: ASKS("gog_calendar_delete") },
34984
+ rm: { does: "removes an event from every guest's calendar (the whole series by default)", instead: ASKS("gog_calendar_delete") },
34985
+ remove: { does: "removes an event from every guest's calendar (the whole series by default)", instead: ASKS("gog_calendar_delete") },
34986
+ "delete-calendar": { does: "deletes a calendar and every event on it", instead: ASKS("gog_calendar_delete_calendar") },
34987
+ "out-of-office": { does: "auto-declines invitations and notifies their organizers", instead: ASKS("gog_calendar_out_of_office") },
34988
+ ooo: { does: "auto-declines invitations and notifies their organizers", instead: ASKS("gog_calendar_out_of_office") }
34852
34989
  };
34853
- function vetCalendarRun(subcommand, _args) {
34990
+ function vetCalendarRun(subcommand, args) {
34854
34991
  const sub = subcommand.toLowerCase();
34855
- const tool = Object.hasOwn(CALENDAR_GATED, sub) ? CALENDAR_GATED[sub] : void 0;
34856
- return tool ? gatedElsewhere(`gog calendar ${sub}`, "gog_calendar_run", "can change what guests see", tool) : void 0;
34992
+ const what = `gog calendar ${sub}`;
34993
+ const via = "gog_calendar_run";
34994
+ if (Object.hasOwn(CALENDAR_REFUSED, sub)) {
34995
+ const { does, instead } = CALENDAR_REFUSED[sub];
34996
+ return refusedInRun(what, via, does, instead);
34997
+ }
34998
+ if (sub === "propose-time" && (flagValue(args, "decline") !== void 0 || flagValue(args, "comment") !== void 0)) {
34999
+ return gatedElsewhere(`${what} --decline`, via, "declines the event and notifies the organizer", "gog_calendar_respond");
35000
+ }
35001
+ if ((sub === "focus-time" || sub === "focus") && flagValue(args, "auto-decline")?.toLowerCase() !== "none") {
35002
+ return refusedInRun(
35003
+ what,
35004
+ via,
35005
+ "auto-declines invitations and notifies their organizers (gog defaults --auto-decline to all)",
35006
+ "Pass --auto-decline=none, or ask the user to create it from Google Calendar."
35007
+ );
35008
+ }
35009
+ return void 0;
34857
35010
  }
34858
35011
  function parseAttendees(value) {
34859
35012
  if (!value) return [];
@@ -34870,15 +35023,17 @@ function eventSnapshot(raw) {
34870
35023
  } catch {
34871
35024
  event = void 0;
34872
35025
  }
34873
- const str = (v) => typeof v === "string" ? v : void 0;
35026
+ const str2 = (v) => typeof v === "string" ? v : void 0;
34874
35027
  return {
34875
- summary: str(event?.summary),
35028
+ summary: str2(event?.summary),
34876
35029
  start: timeOf(event?.start),
34877
35030
  end: timeOf(event?.end),
34878
- organizer: str(event?.organizer?.email),
35031
+ organizer: str2(event?.organizer?.email),
34879
35032
  // Guests are the OTHER people: not this account's own attendee row, not rooms.
34880
35033
  guests: (Array.isArray(event?.attendees) ? event.attendees : []).filter((a) => a.self !== true && a.resource !== true && typeof a.email === "string").map((a) => a.email),
34881
- etag: str(event?.etag)
35034
+ // A series, or one instance of one: gog's delete --scope defaults to all.
35035
+ ...Array.isArray(event?.recurrence) && event.recurrence.length > 0 || typeof event?.recurringEventId === "string" ? { recurring: true } : {},
35036
+ etag: str2(event?.etag)
34882
35037
  };
34883
35038
  }
34884
35039
  async function readEvent(calendarId, eventId, account) {
@@ -35099,14 +35254,35 @@ function registerCalendarTools(server) {
35099
35254
  return runOrDiagnose(args, { account });
35100
35255
  });
35101
35256
  server.registerTool("gog_calendar_delete", {
35102
- description: "Delete a calendar event.",
35257
+ 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,
35103
35258
  annotations: { destructiveHint: true },
35104
35259
  inputSchema: external_exports.object({
35105
35260
  calendarId: external_exports.string().describe("Calendar ID"),
35106
35261
  eventId: external_exports.string().describe("Event ID"),
35107
- account: accountParam
35262
+ account: accountParam,
35263
+ confirmToken: confirmTokenParam
35108
35264
  })
35109
- }, async ({ calendarId, eventId, account }) => {
35265
+ }, async ({ calendarId, eventId, account, confirmToken }, ctx) => {
35266
+ const read = await readEvent(calendarId, eventId, account);
35267
+ if (read.error) return read.error;
35268
+ const { etag, ...event } = read.event;
35269
+ if (event.guests.length > 0 || event.recurring) {
35270
+ const view = { calendarId, eventId, event, scope: event.recurring ? "the whole recurring series" : "this event" };
35271
+ const confirmation = await requireDispatchConfirmation(ctx, {
35272
+ action: "calendar.delete",
35273
+ message: event.recurring ? "Review and confirm deleting this ENTIRE recurring series:" : "Review and confirm deleting this event from every guest's calendar:",
35274
+ confirmationLabel: "Confirm that this event should be deleted now.",
35275
+ details: view,
35276
+ unsupportedNote: "Ask the user to delete it from Google Calendar.",
35277
+ fallback: {
35278
+ tool: "gog_calendar_delete",
35279
+ account,
35280
+ confirmToken,
35281
+ subject: () => ({ target: `${calendarId}/${eventId}`, revision: etag, payload: view, preview: view })
35282
+ }
35283
+ });
35284
+ if (confirmation) return confirmation;
35285
+ }
35110
35286
  return runOrDiagnose(["calendar", "delete", pos(calendarId), pos(eventId), "--force"], { account });
35111
35287
  });
35112
35288
  server.registerTool("gog_calendar_respond", {
@@ -35228,8 +35404,14 @@ function inlineAttachmentArgs(flag, attachments, siblingArgs = []) {
35228
35404
 
35229
35405
  // src/tools/chat.ts
35230
35406
  var CHAT_SEND_WORDS = /* @__PURE__ */ new Set(["send", "create", "post"]);
35407
+ var CHAT_SPACE_CREATE_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
35231
35408
  function vetChatRun(subcommand, args) {
35232
35409
  const sub = subcommand.toLowerCase();
35410
+ if (sub === "spaces") {
35411
+ const word2 = hasCommandWord(args, CHAT_SPACE_CREATE_WORDS);
35412
+ const withMembers = args.some((a) => /^--members?(?:=|$)/i.test(a));
35413
+ return word2 && withMembers ? gatedElsewhere(`gog chat spaces ${word2.toLowerCase()} --member`, "gog_chat_run", "adds people to a space", "gog_chat_spaces_create") : void 0;
35414
+ }
35233
35415
  if (sub !== "messages" && sub !== "dm") return void 0;
35234
35416
  const word = hasCommandWord(args, CHAT_SEND_WORDS);
35235
35417
  if (!word) return void 0;
@@ -35280,16 +35462,34 @@ function registerChatTools(server) {
35280
35462
  return runOrDiagnose(args, { account });
35281
35463
  });
35282
35464
  server.registerTool("gog_chat_spaces_create", {
35283
- 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,
35465
+ 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,
35284
35466
  annotations: { destructiveHint: true },
35285
35467
  inputSchema: external_exports.object({
35286
35468
  displayName: external_exports.string().describe("Display name for the new space"),
35287
35469
  members: external_exports.array(external_exports.string()).optional().describe('Initial members, as email addresses or "users/..." resource names'),
35288
- account: accountParam
35470
+ account: accountParam,
35471
+ confirmToken: confirmTokenParam
35289
35472
  })
35290
- }, async ({ displayName, members: members2, account }) => {
35473
+ }, async ({ displayName, members: members2, account, confirmToken }, ctx) => {
35291
35474
  const args = ["chat", "spaces", "create", pos(displayName)];
35292
35475
  if (members2) for (const member of members2) args.push(`--member=${member}`);
35476
+ if (members2?.length) {
35477
+ const space = { displayName, members: members2 };
35478
+ const confirmation = await requireDispatchConfirmation(ctx, {
35479
+ action: "chat.space-create",
35480
+ message: "Review and confirm this Chat space \u2014 every member is added and notified:",
35481
+ confirmationLabel: "Confirm that these people should be added to a new space now.",
35482
+ details: space,
35483
+ unsupportedNote: "Create the space without members instead; the user can add people from Google Chat.",
35484
+ fallback: {
35485
+ tool: "gog_chat_spaces_create",
35486
+ account,
35487
+ confirmToken,
35488
+ subject: () => ({ target: displayName, payload: space, preview: space })
35489
+ }
35490
+ });
35491
+ if (confirmation) return confirmation;
35492
+ }
35293
35493
  return runOrDiagnose(args, { account });
35294
35494
  });
35295
35495
  server.registerTool("gog_chat_threads_list", {
@@ -35483,20 +35683,85 @@ function registerChatTools(server) {
35483
35683
 
35484
35684
  // src/tools/classroom.ts
35485
35685
  var CLASSROOM_CREATE_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
35486
- var CLASSROOM_GATED = {
35487
- announcements: { does: "posts to a class", tool: "gog_classroom_announcements_create" },
35488
- announcement: { does: "posts to a class", tool: "gog_classroom_announcements_create" },
35489
- ann: { does: "posts to a class", tool: "gog_classroom_announcements_create" },
35490
- invitations: { does: "invites someone to a class", tool: "gog_classroom_invitations_create" },
35491
- invitation: { does: "invites someone to a class", tool: "gog_classroom_invitations_create" },
35492
- invites: { does: "invites someone to a class", tool: "gog_classroom_invitations_create" }
35686
+ var CLASSROOM_DELETE_WORDS = /* @__PURE__ */ new Set(["delete", "rm", "del", "remove"]);
35687
+ var CLASSROOM_UPDATE_WORDS = /* @__PURE__ */ new Set(["update", "edit", "set"]);
35688
+ var CLASSROOM_RETURN_WORDS = /* @__PURE__ */ new Set(["return", "send"]);
35689
+ var CLASSROOM_ASSIGNEE_WORDS = /* @__PURE__ */ new Set(["assignees", "assign"]);
35690
+ var CLASSROOM_GROUPS = {
35691
+ courses: "courses",
35692
+ course: "courses",
35693
+ announcements: "announcements",
35694
+ announcement: "announcements",
35695
+ ann: "announcements",
35696
+ invitations: "invitations",
35697
+ invitation: "invitations",
35698
+ invites: "invitations",
35699
+ coursework: "coursework",
35700
+ work: "coursework",
35701
+ materials: "materials",
35702
+ material: "materials",
35703
+ students: "students",
35704
+ student: "students",
35705
+ teachers: "teachers",
35706
+ teacher: "teachers",
35707
+ submissions: "submissions",
35708
+ submission: "submissions",
35709
+ "guardian-invitations": "guardian-invitations",
35710
+ "guardian-invites": "guardian-invitations"
35493
35711
  };
35712
+ function publishesToStudents(args) {
35713
+ return flagValue(args, "state")?.toUpperCase() === "PUBLISHED" || flagValue(args, "scheduled") !== void 0;
35714
+ }
35715
+ function createsVisibleWork(args) {
35716
+ return flagValue(args, "state")?.toUpperCase() !== "DRAFT" || flagValue(args, "scheduled") !== void 0;
35717
+ }
35718
+ function widensAssignees(args) {
35719
+ return flagValue(args, "mode")?.toUpperCase() === "ALL_STUDENTS" || flagValue(args, "add-student") !== void 0;
35720
+ }
35494
35721
  function vetClassroomRun(subcommand, args) {
35495
35722
  const sub = subcommand.toLowerCase();
35496
- const gated = Object.hasOwn(CLASSROOM_GATED, sub) ? CLASSROOM_GATED[sub] : void 0;
35497
- if (!gated) return void 0;
35498
- const word = hasCommandWord(args, CLASSROOM_CREATE_WORDS);
35499
- return word ? gatedElsewhere(`gog classroom ${sub} ${word.toLowerCase()}`, "gog_classroom_run", gated.does, gated.tool) : void 0;
35723
+ if (!Object.hasOwn(CLASSROOM_GROUPS, sub)) return void 0;
35724
+ const group = CLASSROOM_GROUPS[sub];
35725
+ const via = "gog_classroom_run";
35726
+ const what = (word) => `gog classroom ${sub} ${word.toLowerCase()}`;
35727
+ const create = hasCommandWord(args, CLASSROOM_CREATE_WORDS);
35728
+ const update = hasCommandWord(args, CLASSROOM_UPDATE_WORDS);
35729
+ const remove = hasCommandWord(args, CLASSROOM_DELETE_WORDS);
35730
+ const widenedAssignees = () => {
35731
+ const word = hasCommandWord(args, CLASSROOM_ASSIGNEE_WORDS);
35732
+ 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;
35733
+ };
35734
+ switch (group) {
35735
+ case "courses":
35736
+ return remove ? gatedElsewhere(what(remove), via, "deletes a course", "gog_classroom_courses_delete") : void 0;
35737
+ case "announcements":
35738
+ if (create) return gatedElsewhere(what(create), via, "posts to a class", "gog_classroom_announcements_create");
35739
+ if (update && publishesToStudents(args)) return gatedElsewhere(what(update), via, "publishes an announcement to a class", "gog_classroom_announcements_update");
35740
+ return widenedAssignees();
35741
+ case "invitations":
35742
+ return create ? gatedElsewhere(what(create), via, "invites someone to a class", "gog_classroom_invitations_create") : void 0;
35743
+ case "coursework":
35744
+ if (create && createsVisibleWork(args)) return gatedElsewhere(what(create), via, "posts coursework to a class", "gog_classroom_coursework_create");
35745
+ if (remove) return gatedElsewhere(what(remove), via, "deletes coursework and every submission to it", "gog_classroom_coursework_delete");
35746
+ if (update && publishesToStudents(args)) return gatedElsewhere(what(update), via, "publishes work to students", "gog_classroom_coursework_update");
35747
+ return widenedAssignees();
35748
+ case "materials":
35749
+ if (create && createsVisibleWork(args)) {
35750
+ 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.");
35751
+ }
35752
+ if (update && publishesToStudents(args)) return refusedInRun(what(update), via, "publishes material to students", "Ask the user to publish it from Classroom.");
35753
+ return void 0;
35754
+ case "students":
35755
+ return create ? gatedElsewhere(what(create), via, "enrols someone in a class", "gog_classroom_students_add") : void 0;
35756
+ case "teachers":
35757
+ return create ? gatedElsewhere(what(create), via, "gives someone a teacher's access to a class and its roster", "gog_classroom_teachers_add") : void 0;
35758
+ case "submissions": {
35759
+ const word = hasCommandWord(args, CLASSROOM_RETURN_WORDS);
35760
+ return word ? gatedElsewhere(what(word), via, "returns work to a student, who is notified", "gog_classroom_submissions_return") : void 0;
35761
+ }
35762
+ case "guardian-invitations":
35763
+ return create ? refusedInRun(what(create), via, "emails a guardian invitation", "Ask the user to invite the guardian from Classroom.") : void 0;
35764
+ }
35500
35765
  }
35501
35766
  async function readCourse(courseId, account, runner = runOrDiagnose) {
35502
35767
  const got = await runner(["classroom", "courses", "get", pos(courseId)], { account });
@@ -35515,6 +35780,63 @@ async function readCourse(courseId, account, runner = runOrDiagnose) {
35515
35780
  }
35516
35781
  };
35517
35782
  }
35783
+ function nested(raw, key) {
35784
+ try {
35785
+ const parsed = JSON.parse(raw);
35786
+ const inner = parsed?.[key];
35787
+ return inner && typeof inner === "object" ? inner : void 0;
35788
+ } catch {
35789
+ return void 0;
35790
+ }
35791
+ }
35792
+ var str = (v) => typeof v === "string" ? v : void 0;
35793
+ var num = (v) => typeof v === "number" ? v : void 0;
35794
+ async function readCoursework(courseId, courseworkId, account, runner = runOrDiagnose) {
35795
+ const got = await runner(["classroom", "coursework", "get", pos(courseId), pos(courseworkId)], { account });
35796
+ if (got.isError) return { error: got };
35797
+ const work = nested(resultText(got), "coursework");
35798
+ const title = str(work?.title);
35799
+ return { coursework: { id: courseworkId, ...title !== void 0 ? { title } : {} } };
35800
+ }
35801
+ async function readClassroomWork(kind, courseId, itemId, account, runner = runOrDiagnose) {
35802
+ const got = await runner(["classroom", kind, "get", pos(courseId), pos(itemId)], { account });
35803
+ if (got.isError) return { error: got };
35804
+ const item = nested(resultText(got), kind === "announcements" ? "announcement" : "coursework");
35805
+ const work = { id: itemId };
35806
+ for (const key of ["text", "title", "description", "state", "updateTime"]) {
35807
+ const value = str(item?.[key]);
35808
+ if (value !== void 0) work[key] = value;
35809
+ }
35810
+ return { work };
35811
+ }
35812
+ async function readSubmission(courseId, courseworkId, submissionId, account, runner = runOrDiagnose) {
35813
+ const got = await runner(["classroom", "submissions", "get", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
35814
+ if (got.isError) return { error: got };
35815
+ const sub = nested(resultText(got), "submission");
35816
+ const fields = {
35817
+ student: str(sub?.userId),
35818
+ state: str(sub?.state),
35819
+ draftGrade: num(sub?.draftGrade),
35820
+ assignedGrade: num(sub?.assignedGrade)
35821
+ };
35822
+ const updateTime = str(sub?.updateTime);
35823
+ return {
35824
+ submission: {
35825
+ id: submissionId,
35826
+ ...Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== void 0))
35827
+ },
35828
+ ...updateTime !== void 0 ? { updateTime } : {}
35829
+ };
35830
+ }
35831
+ async function studentLabel(courseId, userId, account, runner = runOrDiagnose) {
35832
+ const got = await runner(["classroom", "students", "get", pos(courseId), pos(userId)], { account });
35833
+ if (got.isError) return userId;
35834
+ const profile = nested(resultText(got), "student")?.profile;
35835
+ const name = str(profile?.name?.fullName);
35836
+ const email3 = str(profile?.emailAddress);
35837
+ if (name && email3) return `${name} <${email3}>`;
35838
+ return name ?? email3 ?? userId;
35839
+ }
35518
35840
  function registerClassroomTools(server) {
35519
35841
  server.registerTool("gog_classroom_courses_list", {
35520
35842
  description: "List Google Classroom courses.",
@@ -35727,15 +36049,38 @@ function registerClassroomTools(server) {
35727
36049
  return runOrDiagnose(args, { account });
35728
36050
  });
35729
36051
  server.registerTool("gog_classroom_submissions_return", {
35730
- description: "Return a graded submission to the student.",
36052
+ 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,
35731
36053
  annotations: { destructiveHint: true },
35732
36054
  inputSchema: external_exports.object({
35733
36055
  courseId: external_exports.string().describe("Course ID"),
35734
36056
  courseworkId: external_exports.string().describe("Coursework ID"),
35735
36057
  submissionId: external_exports.string().describe("Submission ID"),
35736
- account: accountParam
36058
+ account: accountParam,
36059
+ confirmToken: confirmTokenParam
35737
36060
  })
35738
- }, async ({ courseId, courseworkId, submissionId, account }) => {
36061
+ }, async ({ courseId, courseworkId, submissionId, account, confirmToken }, ctx) => {
36062
+ const course = await readCourse(courseId, account);
36063
+ if (course.error) return course.error;
36064
+ const work = await readCoursework(courseId, courseworkId, account);
36065
+ if (work.error) return work.error;
36066
+ const sub = await readSubmission(courseId, courseworkId, submissionId, account);
36067
+ if (sub.error) return sub.error;
36068
+ const submission = sub.submission.student ? { ...sub.submission, student: await studentLabel(courseId, sub.submission.student, account) } : sub.submission;
36069
+ const view = { course: course.course, coursework: work.coursework, submission };
36070
+ const confirmation = await requireDispatchConfirmation(ctx, {
36071
+ action: "classroom.submission-return",
36072
+ message: "Review and confirm returning this work \u2014 the student is notified and sees the grade:",
36073
+ confirmationLabel: "Confirm that this submission should be returned to the student now.",
36074
+ details: view,
36075
+ unsupportedNote: "Ask the user to return it from Classroom.",
36076
+ fallback: {
36077
+ tool: "gog_classroom_submissions_return",
36078
+ account,
36079
+ confirmToken,
36080
+ subject: () => ({ target: `${courseId}/${courseworkId}/${submissionId}`, revision: sub.updateTime, payload: view, preview: view })
36081
+ }
36082
+ });
36083
+ if (confirmation) return confirmation;
35739
36084
  return runOrDiagnose(["classroom", "submissions", "return", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
35740
36085
  });
35741
36086
  server.registerTool("gog_classroom_submissions_turn_in", {
@@ -35797,13 +36142,13 @@ function registerClassroomTools(server) {
35797
36142
  return runOrDiagnose(["classroom", "announcements", "get", pos(courseId), pos(announcementId)], { account });
35798
36143
  });
35799
36144
  server.registerTool("gog_classroom_announcements_create", {
35800
- 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,
36145
+ 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,
35801
36146
  annotations: { destructiveHint: true },
35802
36147
  inputSchema: external_exports.object({
35803
36148
  courseId: external_exports.string().describe("Course ID"),
35804
36149
  text: external_exports.string().describe("Announcement text"),
35805
- state: external_exports.enum(["PUBLISHED", "DRAFT"]).optional().describe("State (DRAFT is visible only to teachers and needs no confirmation)"),
35806
- scheduled: external_exports.string().optional().describe("Scheduled publish time"),
36150
+ state: external_exports.enum(["PUBLISHED", "DRAFT"]).optional().describe("State (an unscheduled DRAFT is visible only to teachers and needs no confirmation)"),
36151
+ scheduled: external_exports.string().optional().describe("Scheduled publish time (asks the user to confirm, even for a DRAFT)"),
35807
36152
  account: accountParam,
35808
36153
  confirmToken: confirmTokenParam
35809
36154
  })
@@ -35811,7 +36156,7 @@ function registerClassroomTools(server) {
35811
36156
  const args = ["classroom", "announcements", "create", pos(courseId), `--text=${text}`];
35812
36157
  if (state) args.push(`--state=${state}`);
35813
36158
  if (scheduled) args.push(`--scheduled=${scheduled}`);
35814
- if (state !== "DRAFT") {
36159
+ if (state !== "DRAFT" || scheduled) {
35815
36160
  const read = await readCourse(courseId, account);
35816
36161
  if (read.error) return read.error;
35817
36162
  const publishes = scheduled ? `at ${scheduled}` : "immediately";
@@ -35983,6 +36328,9 @@ function registerContactsTools(server) {
35983
36328
  }
35984
36329
 
35985
36330
  // src/tools/docs.ts
36331
+ function vetDocsRun(subcommand, args) {
36332
+ return vetCommentsRun("docs", subcommand, args);
36333
+ }
35986
36334
  function registerDocsTools(server) {
35987
36335
  server.registerTool("gog_docs_info", {
35988
36336
  description: "Get Google Doc metadata: title, ID, and other properties.",
@@ -36079,12 +36427,29 @@ function registerDocsTools(server) {
36079
36427
  }, async ({ docId, account }) => {
36080
36428
  return runOrDiagnose(["docs", "structure", pos(docId)], { account });
36081
36429
  });
36082
- registerRunTool(server, { service: "docs", examples: '"copy", "clear", "insert", "sed", "export"' });
36430
+ registerRunTool(server, { service: "docs", examples: '"copy", "clear", "insert", "sed", "export"', vet: vetDocsRun });
36083
36431
  }
36084
36432
 
36085
36433
  // src/tools/drive.ts
36086
- function vetDriveRun(subcommand, _args) {
36087
- return subcommand.toLowerCase() === "share" ? gatedElsewhere("gog drive share", "gog_drive_run", "grants access to a file", "gog_drive_share") : void 0;
36434
+ var DRIVE_DELETE_WORDS = /* @__PURE__ */ new Set(["delete", "rm", "del"]);
36435
+ function vetDriveRun(subcommand, args) {
36436
+ const sub = subcommand.toLowerCase();
36437
+ if (sub === "share") return gatedElsewhere("gog drive share", "gog_drive_run", "grants access to a file", "gog_drive_share");
36438
+ if (DRIVE_DELETE_WORDS.has(sub) && hasTrueFlag(args, "permanent")) {
36439
+ return gatedElsewhere(`gog drive ${sub} --permanent`, "gog_drive_run", "deletes a file for good, bypassing the trash", "gog_drive_delete");
36440
+ }
36441
+ if (sub === "bulk") {
36442
+ return gatedElsewhere(
36443
+ "gog drive bulk",
36444
+ "gog_drive_run",
36445
+ "rewrites sharing permissions across every matching file in a tree",
36446
+ "gog_drive_share (one file at a time)"
36447
+ );
36448
+ }
36449
+ if (sub === "unshare") {
36450
+ return refusedInRun("gog drive unshare", "gog_drive_run", "removes someone's access to a file", "Use gog_drive_unshare.");
36451
+ }
36452
+ return vetCommentsRun("drive", sub, args);
36088
36453
  }
36089
36454
  var GOOGLE_DOC_MIME = "application/vnd.google-apps.document";
36090
36455
  function shareTargetMeta(raw) {
@@ -36095,6 +36460,26 @@ function shareTargetMeta(raw) {
36095
36460
  return {};
36096
36461
  }
36097
36462
  }
36463
+ function commentSnapshot(raw) {
36464
+ let comment;
36465
+ try {
36466
+ const parsed = JSON.parse(raw);
36467
+ comment = parsed?.comment ?? parsed ?? void 0;
36468
+ } catch {
36469
+ comment = void 0;
36470
+ }
36471
+ const name = typeof comment?.author?.displayName === "string" ? comment.author.displayName : void 0;
36472
+ const email3 = typeof comment?.author?.emailAddress === "string" ? comment.author.emailAddress : void 0;
36473
+ const author = name && email3 ? `${name} <${email3}>` : name ?? email3;
36474
+ return {
36475
+ ...author ? { author } : {},
36476
+ ...typeof comment?.content === "string" ? { content: comment.content } : {},
36477
+ ...typeof comment?.resolved === "boolean" ? { resolved: comment.resolved } : {}
36478
+ };
36479
+ }
36480
+ function commentMentions(text) {
36481
+ return [...new Set(text.match(/(?<=[+@])[\w.+-]+@[\w-]+(?:\.[\w-]+)+/g) ?? [])];
36482
+ }
36098
36483
  function fileMeta(raw) {
36099
36484
  const parsed = JSON.parse(raw);
36100
36485
  const f = parsed.file ?? parsed;
@@ -36203,16 +36588,36 @@ function registerDriveTools(server) {
36203
36588
  return runOrDiagnose(["drive", "move", pos(fileId), `--parent=${parentId}`], { account });
36204
36589
  });
36205
36590
  server.registerTool("gog_drive_delete", {
36206
- description: "Move a Google Drive file to trash, or permanently delete it with permanent=true (irreversible).",
36591
+ 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,
36207
36592
  annotations: { destructiveHint: true },
36208
36593
  inputSchema: external_exports.object({
36209
36594
  fileId: external_exports.string().describe("File ID to delete"),
36210
- permanent: external_exports.boolean().optional().describe("Permanently delete instead of moving to trash (irreversible)"),
36211
- account: accountParam
36595
+ permanent: external_exports.boolean().optional().describe("Permanently delete instead of moving to trash (irreversible; asks the user first)"),
36596
+ account: accountParam,
36597
+ confirmToken: confirmTokenParam
36212
36598
  })
36213
- }, async ({ fileId, permanent, account }) => {
36599
+ }, async ({ fileId, permanent, account, confirmToken }, ctx) => {
36214
36600
  const args = ["drive", "delete", pos(fileId)];
36215
- if (permanent) args.push("--permanent");
36601
+ if (permanent) {
36602
+ const got = await runOrDiagnose(["drive", "get", pos(fileId)], { account });
36603
+ if (got.isError) return got;
36604
+ const target = { file: { id: fileId, ...shareTargetMeta(resultText(got)) }, permanent: true };
36605
+ const confirmation = await requireDispatchConfirmation(ctx, {
36606
+ action: "drive.delete-permanent",
36607
+ message: "Review and confirm PERMANENTLY deleting this file \u2014 it skips the trash and cannot be recovered:",
36608
+ confirmationLabel: "Confirm that this file should be permanently deleted now.",
36609
+ details: target,
36610
+ unsupportedNote: "Move it to the trash instead (permanent=false); the user can empty the trash from Google Drive.",
36611
+ fallback: {
36612
+ tool: "gog_drive_delete",
36613
+ account,
36614
+ confirmToken,
36615
+ subject: () => ({ target: fileId, payload: target, preview: target })
36616
+ }
36617
+ });
36618
+ if (confirmation) return confirmation;
36619
+ args.push("--permanent");
36620
+ }
36216
36621
  args.push("--force");
36217
36622
  return runOrDiagnose(args, { account });
36218
36623
  });
@@ -36538,18 +36943,22 @@ function trustedDomains(account) {
36538
36943
  }
36539
36944
  function logGmailDispatch(tool, recipients, account) {
36540
36945
  const domains = trustedDomains(account);
36541
- const externalRecipients = recipients.filter((recipient) => {
36946
+ const externalDomains = /* @__PURE__ */ new Set();
36947
+ let externalRecipientCount = 0;
36948
+ for (const recipient of recipients) {
36542
36949
  const at = recipient.indexOf("@");
36543
- const domain2 = at > -1 ? recipient.slice(at + 1) : "";
36544
- return !domain2 || !domains.has(domain2);
36545
- });
36950
+ const domain2 = at > -1 ? recipient.slice(at + 1).toLowerCase() : "";
36951
+ if (domain2 && domains.has(domain2)) continue;
36952
+ externalRecipientCount += 1;
36953
+ externalDomains.add(domain2 || "(no domain)");
36954
+ }
36546
36955
  const event = {
36547
36956
  event: "gmail_dispatch",
36548
36957
  tool,
36549
36958
  recipientCount: recipients.length,
36550
- externalRecipientCount: externalRecipients.length,
36551
- hasExternalRecipients: externalRecipients.length > 0,
36552
- externalRecipients,
36959
+ externalRecipientCount,
36960
+ hasExternalRecipients: externalRecipientCount > 0,
36961
+ externalDomains: [...externalDomains].sort(),
36553
36962
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
36554
36963
  };
36555
36964
  process.stderr.write(`${JSON.stringify(event)}
@@ -36716,6 +37125,39 @@ async function sendReply(kind, toolName, messageId, account, flags, ctx, confirm
36716
37125
  return result;
36717
37126
  }
36718
37127
  var GMAIL_RUN_BLOCKED_SETTINGS = /* @__PURE__ */ new Set(["forwarding", "autoforward", "filters", "delegates"]);
37128
+ var GMAIL_DELETE_WORDS = /* @__PURE__ */ new Set(["delete", "rm", "del", "remove"]);
37129
+ var GMAIL_CREATE_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
37130
+ var GMAIL_UPDATE_WORDS = /* @__PURE__ */ new Set(["update", "edit", "set"]);
37131
+ var SENDAS = /* @__PURE__ */ new Set(["sendas"]);
37132
+ var VACATION = /* @__PURE__ */ new Set(["vacation"]);
37133
+ function vetGmailGatedRun(sub, args) {
37134
+ if (sub === "batch") {
37135
+ const word = hasCommandWord(args, GMAIL_DELETE_WORDS);
37136
+ return word ? gatedElsewhere(`gog gmail batch ${word.toLowerCase()}`, "gog_gmail_run", "deletes mail for good, bypassing the Trash", "gog_gmail_batch_delete") : void 0;
37137
+ }
37138
+ if (sub !== "settings" && sub !== "sendas" && sub !== "vacation") return void 0;
37139
+ const words = sub === "settings" ? args : [sub, ...args];
37140
+ if (hasCommandWord(words, SENDAS)) {
37141
+ const word = hasCommandWord(args, GMAIL_CREATE_WORDS);
37142
+ if (word) {
37143
+ return gatedElsewhere(
37144
+ `gog gmail sendas ${word.toLowerCase()}`,
37145
+ "gog_gmail_run",
37146
+ "makes Google email the address and adds a sending identity",
37147
+ "gog_gmail_sendas_create"
37148
+ );
37149
+ }
37150
+ }
37151
+ if (hasCommandWord(words, VACATION) && hasCommandWord(args, GMAIL_UPDATE_WORDS) && hasTrueFlag(args, "enable")) {
37152
+ return gatedElsewhere(
37153
+ "gog gmail vacation update --enable",
37154
+ "gog_gmail_run",
37155
+ "turns on an auto-reply to every sender",
37156
+ "gog_gmail_vacation_update"
37157
+ );
37158
+ }
37159
+ return void 0;
37160
+ }
36719
37161
  function vetGmailRun(subcommand, args) {
36720
37162
  if (subcommand === "autoreply") {
36721
37163
  return "gog gmail autoreply sends mail and is not available through gog_gmail_run. Use gog_gmail_autoreply, which asks the user to confirm.";
@@ -36723,6 +37165,8 @@ function vetGmailRun(subcommand, args) {
36723
37165
  if (GMAIL_RUN_BLOCKED_SETTINGS.has(subcommand)) {
36724
37166
  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.`;
36725
37167
  }
37168
+ const gated = vetGmailGatedRun(subcommand.toLowerCase(), args);
37169
+ if (gated) return gated;
36726
37170
  if (subcommand === "settings") {
36727
37171
  const blocked = hasCommandWord(args, GMAIL_RUN_BLOCKED_SETTINGS);
36728
37172
  if (blocked) {
@@ -36934,6 +37378,13 @@ function countNonEmptyCells(getOutput) {
36934
37378
  }
36935
37379
 
36936
37380
  // src/tools/sheets.ts
37381
+ var DATASOURCE_ALIASES = /* @__PURE__ */ new Set(["datasource", "data-source", "data-sources", "connected-sheets"]);
37382
+ var DATASOURCE_BILLED = /* @__PURE__ */ new Set(["add", "update", "refresh"]);
37383
+ function vetSheetsRun(subcommand, args) {
37384
+ if (!DATASOURCE_ALIASES.has(subcommand.toLowerCase())) return void 0;
37385
+ const word = hasCommandWord(args, DATASOURCE_BILLED)?.toLowerCase();
37386
+ return word ? gatedElsewhere(`gog sheets datasource ${word}`, "gog_sheets_run", "runs a billed BigQuery job", `gog_sheets_datasource_${word}`) : void 0;
37387
+ }
36937
37388
  var cellValueParam = external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean(), external_exports.null()]);
36938
37389
  var dryRunParam = external_exports.boolean().optional().describe(
36939
37390
  "Preview the operation without modifying the sheet (gog --dry-run): reports the intended actions and exits without writing."
@@ -37049,7 +37500,7 @@ function registerSheetsTools(server) {
37049
37500
  }, async ({ spreadsheetId, find, replace, account }) => {
37050
37501
  return runOrDiagnose(["sheets", "find-replace", pos(spreadsheetId), pos(find), pos(replace)], { account });
37051
37502
  });
37052
- registerRunTool(server, { service: "sheets", examples: '"freeze", "add-tab", "rename-tab"' });
37503
+ registerRunTool(server, { service: "sheets", examples: '"freeze", "add-tab", "rename-tab"', vet: vetSheetsRun });
37053
37504
  }
37054
37505
 
37055
37506
  // src/tools/slides.ts
@@ -37213,7 +37664,7 @@ function registerTasksTools(server) {
37213
37664
  }
37214
37665
 
37215
37666
  // src/server.ts
37216
- var VERSION = true ? "4.5.0" : "0.0.0";
37667
+ var VERSION = true ? "4.5.1" : "0.0.0";
37217
37668
  var BASE_TOOL_REGISTRARS = [
37218
37669
  registerApiTools,
37219
37670
  registerAppScriptTools,
@@ -37515,6 +37966,8 @@ export {
37515
37966
  blobStoreFromEnv,
37516
37967
  bodyPreview,
37517
37968
  bootstrapGogAuth,
37969
+ commentMentions,
37970
+ commentSnapshot,
37518
37971
  confineAtFile,
37519
37972
  confinePath,
37520
37973
  confinePaths,
@@ -37523,6 +37976,7 @@ export {
37523
37976
  defaultFileRoot,
37524
37977
  diagnose,
37525
37978
  errorText,
37979
+ eventSnapshot,
37526
37980
  extractEmails,
37527
37981
  fetchGmailPages,
37528
37982
  fileRoots,
@@ -37537,11 +37991,14 @@ export {
37537
37991
  pageAliasParam,
37538
37992
  pageTokenParam,
37539
37993
  paginationParams,
37994
+ parseMetadataHeaders,
37540
37995
  payloadArg,
37541
37996
  pos,
37542
37997
  prepareDownloadRoot,
37543
37998
  pushPaginationFlags,
37999
+ readClassroomWork,
37544
38000
  readCourse,
38001
+ readCoursework,
37545
38002
  registerApiTools,
37546
38003
  registerAppScriptTools,
37547
38004
  registerAuthTools,
@@ -37566,7 +38023,9 @@ export {
37566
38023
  runBinary,
37567
38024
  runOrDiagnose,
37568
38025
  senderPreview,
38026
+ shareTargetMeta,
37569
38027
  stripConsumedPageToken,
38028
+ studentLabel,
37570
38029
  uploadToBlobStore
37571
38030
  };
37572
38031
  /*! Bundled license information: