gogcli-mcp 2.23.1 → 2.24.0

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
@@ -23042,7 +23042,7 @@ function activeExecutor() {
23042
23042
  return runExecutor.getStore() ?? defaultExecutor;
23043
23043
  }
23044
23044
  var TIMEOUT_MS = 3e4;
23045
- var MIN_GOG_VERSION = "0.35.0";
23045
+ var MIN_GOG_VERSION = "0.37.0";
23046
23046
  function readonlyEnvEnabled() {
23047
23047
  return readEnvVar("GOG_READONLY") !== void 0 && parseBoolEnv("GOG_READONLY", { default: true });
23048
23048
  }
@@ -23431,6 +23431,55 @@ function normalizeTimestamps(text, tz = displayTimeZone(), naiveTz = naiveSource
23431
23431
  return JSON.stringify(parsed, null, detectIndent(text));
23432
23432
  }
23433
23433
 
23434
+ // src/pagination.ts
23435
+ function detectIndent2(text) {
23436
+ const match = /\n(\s+)\S/.exec(text);
23437
+ return match ? match[1].replace(/\t/g, " ").length : 0;
23438
+ }
23439
+ function stripConsumedPageToken(text) {
23440
+ const trimmed = text.trim();
23441
+ if (trimmed === "" || !trimmed.startsWith("{")) return text;
23442
+ let parsed;
23443
+ try {
23444
+ parsed = JSON.parse(trimmed);
23445
+ } catch {
23446
+ return text;
23447
+ }
23448
+ const obj = parsed;
23449
+ if (obj.nextPageToken !== "") return text;
23450
+ delete obj.nextPageToken;
23451
+ return JSON.stringify(obj, null, detectIndent2(text));
23452
+ }
23453
+ function truncationWarning(returned, count) {
23454
+ const scope = count.total !== void 0 ? `returned ${returned} of ${count.total} matches` : count.atLeast !== void 0 ? `returned ${returned} of at least ${count.atLeast} matches` : `returned ${returned} matches and MORE EXIST beyond this page`;
23455
+ return `INCOMPLETE RESULT SET: ${scope}. Do not report an absence of results based on this response. Page with nextPageToken or narrow the query.`;
23456
+ }
23457
+ function annotateTruncation(out, returned, count) {
23458
+ out.truncated = true;
23459
+ out.returned = returned;
23460
+ if (count.total !== void 0) out.totalMatches = count.total;
23461
+ if (count.atLeast !== void 0) out.totalMatchesAtLeast = count.atLeast;
23462
+ out.warning = truncationWarning(returned, count);
23463
+ }
23464
+ function hasMorePages(parsed) {
23465
+ return typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "";
23466
+ }
23467
+ function annotateTruncatedList(result, itemsKey) {
23468
+ const first = result.content[0];
23469
+ if (result.isError || first?.type !== "text") return result;
23470
+ let parsed;
23471
+ try {
23472
+ parsed = JSON.parse(first.text);
23473
+ } catch {
23474
+ return result;
23475
+ }
23476
+ const items = parsed[itemsKey];
23477
+ if (!Array.isArray(items) || !hasMorePages(parsed)) return result;
23478
+ const out = { ...parsed };
23479
+ annotateTruncation(out, items.length, {});
23480
+ return rawTextResult(JSON.stringify(out));
23481
+ }
23482
+
23434
23483
  // src/tools/utils.ts
23435
23484
  var PAYLOAD_INLINE_MAX = 4096;
23436
23485
  function payloadArg(inlineFlag, fileFlag, value, ext) {
@@ -23466,14 +23515,25 @@ var ids = {
23466
23515
  // People API uses fully-qualified resource names ("people/c123") not bare IDs.
23467
23516
  person: external_exports.string().describe("Person resource name (people/...) or email")
23468
23517
  };
23518
+ var pageTokenParam = external_exports.string().optional().describe(
23519
+ "Cursor for the NEXT page. Pass back the nextPageToken from a previous response verbatim, keeping the query and max identical: call once, then call again with pageToken=<that value>. A response with NO nextPageToken is the last page."
23520
+ );
23521
+ var pageAliasParam = external_exports.string().optional().describe(
23522
+ "Deprecated alias for pageToken, accepted so existing callers keep working. Use pageToken \u2014 it matches the nextPageToken field in the response."
23523
+ );
23524
+ function resolvePageToken(p) {
23525
+ return p.pageToken ?? p.page;
23526
+ }
23469
23527
  var paginationParams = {
23470
23528
  max: external_exports.number().int().optional().describe("Max results"),
23471
- page: external_exports.string().optional().describe("Page token"),
23529
+ pageToken: pageTokenParam,
23530
+ page: pageAliasParam,
23472
23531
  all: external_exports.boolean().optional().describe("Fetch all pages")
23473
23532
  };
23474
23533
  function pushPaginationFlags(args, p) {
23475
23534
  if (p.max !== void 0) args.push(`--max=${p.max}`);
23476
- if (p.page) args.push(`--page=${p.page}`);
23535
+ const token = resolvePageToken(p);
23536
+ if (token) args.push(`--page=${token}`);
23477
23537
  if (p.all) args.push("--all");
23478
23538
  }
23479
23539
  function registerRunTool(server, options) {
@@ -23548,7 +23608,7 @@ ${accounts || "(none)"}${hint}`);
23548
23608
  async function runOrDiagnose(args, options) {
23549
23609
  try {
23550
23610
  const raw = await run(args, options);
23551
- return rawTextResult(options.lossless ? raw : normalizeTimestamps(raw));
23611
+ return rawTextResult(options.lossless ? raw : stripConsumedPageToken(normalizeTimestamps(raw)));
23552
23612
  } catch (err) {
23553
23613
  return diagnose(err);
23554
23614
  }
@@ -23652,6 +23712,7 @@ function registerApiTools(server) {
23652
23712
  // src/tools/auth.ts
23653
23713
  function registerAuthToolsWith(server, defaultServices) {
23654
23714
  const servicesDescribe = `Services to authorize: "all" or comma-separated list (e.g. "sheets,gmail,calendar"). Default: "${defaultServices}". Prefer the narrowest set you need \u2014 requesting a service whose Google API is not enabled on the OAuth client's project makes Google reject the WHOLE request with invalid_scope.`;
23715
+ const extraScopesDescribe = "Additional raw OAuth scope URIs to request, comma-separated, on top of the ones `services` implies. Use for scopes no service covers \u2014 e.g. https://www.googleapis.com/auth/bigquery.readonly, required before gog_sheets_datasource_* can read BigQuery-backed Connected Sheets. Leave unset otherwise: an extra scope whose API is not enabled on the OAuth client project makes Google reject the WHOLE authorization with invalid_scope.";
23655
23716
  server.registerTool("gog_auth_list", {
23656
23717
  description: "List the Google accounts stored in gogcli, with their scopes. This reads local configuration only \u2014 it does not contact Google and does NOT tell you whether an account still works: a signed-out account whose refresh token expired or was revoked is listed here exactly like a healthy one, scopes and all. Use gog_auth_health to check whether an account can actually authenticate.",
23657
23718
  annotations: { readOnlyHint: true },
@@ -23701,11 +23762,14 @@ function registerAuthToolsWith(server, defaultServices) {
23701
23762
  annotations: { destructiveHint: true },
23702
23763
  inputSchema: {
23703
23764
  email: external_exports.string().describe("Google account email to authorize"),
23704
- services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe)
23765
+ services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
23766
+ extraScopes: external_exports.string().optional().describe(extraScopesDescribe)
23705
23767
  }
23706
- }, async ({ email: email3, services = defaultServices }) => {
23768
+ }, async ({ email: email3, services = defaultServices, extraScopes }) => {
23707
23769
  try {
23708
- return rawTextResult(await run(["auth", "add", email3, "--services", services], {
23770
+ const args = ["auth", "add", email3, "--services", services];
23771
+ if (extraScopes) args.push(`--extra-scopes=${extraScopes}`, "--force-consent");
23772
+ return rawTextResult(await run(args, {
23709
23773
  interactive: true,
23710
23774
  timeout: 3e5
23711
23775
  }));
@@ -23717,14 +23781,14 @@ function registerAuthToolsWith(server, defaultServices) {
23717
23781
  description: "Begin REMOTE/headless Google authorization (step 1 of 2). Returns a sign-in URL to open in any browser \u2014 no local server or terminal on the gogcli host is needed, so this works over the hosted connector where the interactive gog_auth_add cannot. Hand the URL to the user; after they sign in, the browser is redirected to a localhost URL that fails to load \u2014 that is expected. They copy that full redirected URL (from the address bar) and you pass it to gog_auth_add_complete. The link is valid for 10 minutes. If you pass a custom `services` here, pass the SAME value to gog_auth_add_complete or the second step will not match this one.",
23718
23782
  inputSchema: {
23719
23783
  email: external_exports.string().describe("Google account email to authorize"),
23720
- services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe)
23784
+ services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
23785
+ extraScopes: external_exports.string().optional().describe(`${extraScopesDescribe} Pass the SAME value to gog_auth_add_complete.`)
23721
23786
  }
23722
- }, async ({ email: email3, services = defaultServices }) => {
23787
+ }, async ({ email: email3, services = defaultServices, extraScopes }) => {
23723
23788
  try {
23724
- return rawTextResult(await run(
23725
- ["auth", "add", email3, "--remote", "--step", "1", "--services", services, "--force-consent"],
23726
- { redactMode: "tokens" }
23727
- ));
23789
+ const args = ["auth", "add", email3, "--remote", "--step", "1", "--services", services, "--force-consent"];
23790
+ if (extraScopes) args.push(`--extra-scopes=${extraScopes}`);
23791
+ return rawTextResult(await run(args, { redactMode: "tokens" }));
23728
23792
  } catch (err) {
23729
23793
  return errorResult(errorText(err));
23730
23794
  }
@@ -23739,25 +23803,28 @@ function registerAuthToolsWith(server, defaultServices) {
23739
23803
  ),
23740
23804
  services: external_exports.string().optional().default(defaultServices).describe(
23741
23805
  `Services authorized \u2014 MUST match the value passed to gog_auth_add_url. Default: "${defaultServices}".`
23806
+ ),
23807
+ extraScopes: external_exports.string().optional().describe(
23808
+ "Extra OAuth scope URIs \u2014 MUST match the value passed to gog_auth_add_url, for the same reason `services` must: the two steps have to describe the same grant."
23742
23809
  )
23743
23810
  }
23744
- }, async ({ email: email3, redirectUrl, services = defaultServices }) => {
23811
+ }, async ({ email: email3, redirectUrl, services = defaultServices, extraScopes }) => {
23745
23812
  try {
23746
- return rawTextResult(await run(
23747
- [
23748
- "auth",
23749
- "add",
23750
- email3,
23751
- "--remote",
23752
- "--step",
23753
- "2",
23754
- "--auth-url",
23755
- redirectUrl,
23756
- "--services",
23757
- services,
23758
- "--force-consent"
23759
- ]
23760
- ));
23813
+ const args = [
23814
+ "auth",
23815
+ "add",
23816
+ email3,
23817
+ "--remote",
23818
+ "--step",
23819
+ "2",
23820
+ "--auth-url",
23821
+ redirectUrl,
23822
+ "--services",
23823
+ services,
23824
+ "--force-consent"
23825
+ ];
23826
+ if (extraScopes) args.push(`--extra-scopes=${extraScopes}`);
23827
+ return rawTextResult(await run(args));
23761
23828
  } catch (err) {
23762
23829
  return errorResult(errorText(err));
23763
23830
  }
@@ -23779,30 +23846,44 @@ function authToolsFor(defaultServices) {
23779
23846
  // src/tools/calendar.ts
23780
23847
  function registerCalendarTools(server) {
23781
23848
  server.registerTool("gog_calendar_events", {
23782
- description: "List calendar events. Filters can be combined (e.g. --from + --to for a range, or --today for just today).",
23849
+ description: 'List calendar events. Describe the window ONE way and one way only (gog >= 0.36.0 rejects the rest as ambiguous rather than silently discarding a flag): today on its own; or from + to; or from + days; or days on its own (a window of that many days starting today). today cannot be combined with from, to or days, and days cannot be combined with to. gog returns only 10 events by default, so a wide date range is USUALLY INCOMPLETE: raise max, or page with pageToken until the response carries no nextPageToken. A response carrying "truncated": true is an incomplete view \u2014 never conclude an event does not exist from one.',
23783
23850
  annotations: { readOnlyHint: true },
23784
23851
  inputSchema: {
23785
23852
  calendarId: external_exports.string().optional().describe("Calendar ID (default: primary calendar)"),
23786
23853
  from: external_exports.string().optional().describe("Start time filter (RFC3339, date, or natural language)"),
23787
- to: external_exports.string().optional().describe("End time filter (RFC3339, date, or natural language)"),
23788
- today: external_exports.boolean().optional().describe("Only show today's events"),
23854
+ to: external_exports.string().optional().describe("End time filter (RFC3339, date, or natural language). Mutually exclusive with today and with days."),
23855
+ // gog >= 0.36.0 (openclaw/gogcli#981). Before that release --days sat in
23856
+ // a switch arm evaluated ahead of --from, so `--from 2026-09-25 --days 5`
23857
+ // silently threw --from away and answered for today instead — at exit 0,
23858
+ // in a well-formed table. It is only exposed here now that it means what
23859
+ // it says.
23860
+ days: external_exports.number().int().positive().optional().describe('Window LENGTH in days (calendar days, DST-aware), measured from `from` when one is given and from today otherwise. Use from + days for "the week of the 25th"; days alone for "the next N days". Mutually exclusive with to and with today.'),
23861
+ today: external_exports.boolean().optional().describe("Only show today's events. A complete window on its own \u2014 mutually exclusive with from, to and days."),
23789
23862
  query: external_exports.string().optional().describe("Free text search within events"),
23790
- all: external_exports.boolean().optional().describe("Fetch events from all calendars"),
23863
+ max: external_exports.number().int().optional().describe("Max events to return. gog defaults to 10, which silently hides the rest \u2014 raise it, or page with pageToken."),
23864
+ pageToken: pageTokenParam,
23865
+ page: pageAliasParam,
23866
+ all: external_exports.boolean().optional().describe('Fetch events from ALL CALENDARS. NOTE: unlike the gmail search tools, this does NOT mean "all pages" \u2014 it widens the calendar set, not the page window. Use pageToken to reach later pages.'),
23791
23867
  eventTypes: external_exports.array(external_exports.enum(["default", "birthday", "focus-time", "from-gmail", "out-of-office", "working-location"])).optional().describe("Filter to specific event types (repeatable)"),
23792
23868
  timezone: external_exports.string().optional().describe(`Display timezone for event times (IANA name, e.g. America/New_York, or "local" for the system timezone). Default: each event's timezone, then its calendar's timezone.`),
23793
23869
  account: accountParam
23794
23870
  }
23795
- }, async ({ calendarId, from, to, today, query, all, eventTypes, timezone, account }) => {
23871
+ }, async ({ calendarId, from, to, days, today, query, max, pageToken, page, all, eventTypes, timezone, account }) => {
23796
23872
  const args = ["calendar", "events"];
23797
23873
  if (calendarId) args.push(calendarId);
23798
23874
  if (from) args.push(`--from=${from}`);
23799
23875
  if (to) args.push(`--to=${to}`);
23876
+ if (days !== void 0) args.push(`--days=${days}`);
23800
23877
  if (today) args.push("--today");
23801
23878
  if (query) args.push(`--query=${query}`);
23879
+ if (max !== void 0) args.push(`--max=${max}`);
23880
+ const token = resolvePageToken({ pageToken, page });
23881
+ if (token) args.push(`--page=${token}`);
23802
23882
  if (all) args.push("--all");
23803
23883
  if (eventTypes) for (const t of eventTypes) args.push(`--event-types=${t}`);
23804
23884
  if (timezone) args.push(`--timezone=${timezone}`);
23805
- return runOrDiagnose(args, { account });
23885
+ const result = await runOrDiagnose(args, { account });
23886
+ return annotateTruncatedList(result, "events");
23806
23887
  });
23807
23888
  server.registerTool("gog_calendar_get", {
23808
23889
  description: "Get a specific calendar event by ID.",
@@ -23919,17 +24000,19 @@ function registerClassroomTools(server) {
23919
24000
  teacher: external_exports.string().optional().describe("Filter by teacher user ID"),
23920
24001
  student: external_exports.string().optional().describe("Filter by student user ID"),
23921
24002
  max: external_exports.number().optional().describe("Max results per page"),
23922
- page: external_exports.string().optional().describe("Page token for pagination"),
24003
+ pageToken: pageTokenParam,
24004
+ page: pageAliasParam,
23923
24005
  all: external_exports.boolean().optional().describe("Fetch all pages"),
23924
24006
  account: accountParam
23925
24007
  }
23926
- }, async ({ state, teacher, student, max, page, all, account }) => {
24008
+ }, async ({ state, teacher, student, max, pageToken, page, all, account }) => {
23927
24009
  const args = ["classroom", "courses", "list"];
23928
24010
  if (state) args.push(`--state=${state}`);
23929
24011
  if (teacher) args.push(`--teacher=${teacher}`);
23930
24012
  if (student) args.push(`--student=${student}`);
23931
24013
  if (max !== void 0) args.push(`--max=${max}`);
23932
- if (page) args.push(`--page=${page}`);
24014
+ const token = resolvePageToken({ pageToken, page });
24015
+ if (token) args.push(`--page=${token}`);
23933
24016
  if (all) args.push("--all");
23934
24017
  return runOrDiagnose(args, { account });
23935
24018
  });
@@ -23949,14 +24032,16 @@ function registerClassroomTools(server) {
23949
24032
  inputSchema: {
23950
24033
  courseId: external_exports.string().describe("Course ID"),
23951
24034
  max: external_exports.number().optional().describe("Max results per page"),
23952
- page: external_exports.string().optional().describe("Page token"),
24035
+ pageToken: pageTokenParam,
24036
+ page: pageAliasParam,
23953
24037
  all: external_exports.boolean().optional().describe("Fetch all pages"),
23954
24038
  account: accountParam
23955
24039
  }
23956
- }, async ({ courseId, max, page, all, account }) => {
24040
+ }, async ({ courseId, max, pageToken, page, all, account }) => {
23957
24041
  const args = ["classroom", "students", "list", courseId];
23958
24042
  if (max !== void 0) args.push(`--max=${max}`);
23959
- if (page) args.push(`--page=${page}`);
24043
+ const token = resolvePageToken({ pageToken, page });
24044
+ if (token) args.push(`--page=${token}`);
23960
24045
  if (all) args.push("--all");
23961
24046
  return runOrDiagnose(args, { account });
23962
24047
  });
@@ -23977,14 +24062,16 @@ function registerClassroomTools(server) {
23977
24062
  inputSchema: {
23978
24063
  courseId: external_exports.string().describe("Course ID"),
23979
24064
  max: external_exports.number().optional().describe("Max results per page"),
23980
- page: external_exports.string().optional().describe("Page token"),
24065
+ pageToken: pageTokenParam,
24066
+ page: pageAliasParam,
23981
24067
  all: external_exports.boolean().optional().describe("Fetch all pages"),
23982
24068
  account: accountParam
23983
24069
  }
23984
- }, async ({ courseId, max, page, all, account }) => {
24070
+ }, async ({ courseId, max, pageToken, page, all, account }) => {
23985
24071
  const args = ["classroom", "teachers", "list", courseId];
23986
24072
  if (max !== void 0) args.push(`--max=${max}`);
23987
- if (page) args.push(`--page=${page}`);
24073
+ const token = resolvePageToken({ pageToken, page });
24074
+ if (token) args.push(`--page=${token}`);
23988
24075
  if (all) args.push("--all");
23989
24076
  return runOrDiagnose(args, { account });
23990
24077
  });
@@ -24007,16 +24094,18 @@ function registerClassroomTools(server) {
24007
24094
  students: external_exports.boolean().optional().describe("Include students only"),
24008
24095
  teachers: external_exports.boolean().optional().describe("Include teachers only"),
24009
24096
  max: external_exports.number().optional().describe("Max results per page"),
24010
- page: external_exports.string().optional().describe("Page token"),
24097
+ pageToken: pageTokenParam,
24098
+ page: pageAliasParam,
24011
24099
  all: external_exports.boolean().optional().describe("Fetch all pages"),
24012
24100
  account: accountParam
24013
24101
  }
24014
- }, async ({ courseId, students, teachers, max, page, all, account }) => {
24102
+ }, async ({ courseId, students, teachers, max, pageToken, page, all, account }) => {
24015
24103
  const args = ["classroom", "roster", courseId];
24016
24104
  if (students) args.push("--students");
24017
24105
  if (teachers) args.push("--teachers");
24018
24106
  if (max !== void 0) args.push(`--max=${max}`);
24019
- if (page) args.push(`--page=${page}`);
24107
+ const token = resolvePageToken({ pageToken, page });
24108
+ if (token) args.push(`--page=${token}`);
24020
24109
  if (all) args.push("--all");
24021
24110
  return runOrDiagnose(args, { account });
24022
24111
  });
@@ -24029,18 +24118,20 @@ function registerClassroomTools(server) {
24029
24118
  topic: external_exports.string().optional().describe("Filter by topic ID"),
24030
24119
  orderBy: external_exports.string().optional().describe('Sort order (e.g. "updateTime desc")'),
24031
24120
  max: external_exports.number().optional().describe("Max results per page"),
24032
- page: external_exports.string().optional().describe("Page token"),
24121
+ pageToken: pageTokenParam,
24122
+ page: pageAliasParam,
24033
24123
  all: external_exports.boolean().optional().describe("Fetch all pages"),
24034
24124
  scanPages: external_exports.number().optional().describe("Max pages to scan when filtering"),
24035
24125
  account: accountParam
24036
24126
  }
24037
- }, async ({ courseId, state, topic, orderBy, max, page, all, scanPages, account }) => {
24127
+ }, async ({ courseId, state, topic, orderBy, max, pageToken, page, all, scanPages, account }) => {
24038
24128
  const args = ["classroom", "coursework", "list", courseId];
24039
24129
  if (state) args.push(`--state=${state}`);
24040
24130
  if (topic) args.push(`--topic=${topic}`);
24041
24131
  if (orderBy) args.push(`--order-by=${orderBy}`);
24042
24132
  if (max !== void 0) args.push(`--max=${max}`);
24043
- if (page) args.push(`--page=${page}`);
24133
+ const token = resolvePageToken({ pageToken, page });
24134
+ if (token) args.push(`--page=${token}`);
24044
24135
  if (all) args.push("--all");
24045
24136
  if (scanPages !== void 0) args.push(`--scan-pages=${scanPages}`);
24046
24137
  return runOrDiagnose(args, { account });
@@ -24066,17 +24157,19 @@ function registerClassroomTools(server) {
24066
24157
  late: external_exports.enum(["late", "not-late"]).optional().describe("Filter by late status"),
24067
24158
  user: external_exports.string().optional().describe("Filter by student user ID"),
24068
24159
  max: external_exports.number().optional().describe("Max results per page"),
24069
- page: external_exports.string().optional().describe("Page token"),
24160
+ pageToken: pageTokenParam,
24161
+ page: pageAliasParam,
24070
24162
  all: external_exports.boolean().optional().describe("Fetch all pages"),
24071
24163
  account: accountParam
24072
24164
  }
24073
- }, async ({ courseId, courseworkId, state, late, user, max, page, all, account }) => {
24165
+ }, async ({ courseId, courseworkId, state, late, user, max, pageToken, page, all, account }) => {
24074
24166
  const args = ["classroom", "submissions", "list", courseId, courseworkId];
24075
24167
  if (state) args.push(`--state=${state}`);
24076
24168
  if (late) args.push(`--late=${late}`);
24077
24169
  if (user) args.push(`--user=${user}`);
24078
24170
  if (max !== void 0) args.push(`--max=${max}`);
24079
- if (page) args.push(`--page=${page}`);
24171
+ const token = resolvePageToken({ pageToken, page });
24172
+ if (token) args.push(`--page=${token}`);
24080
24173
  if (all) args.push("--all");
24081
24174
  return runOrDiagnose(args, { account });
24082
24175
  });
@@ -24153,16 +24246,18 @@ function registerClassroomTools(server) {
24153
24246
  state: external_exports.string().optional().describe("Filter by announcement state"),
24154
24247
  orderBy: external_exports.string().optional().describe("Sort order"),
24155
24248
  max: external_exports.number().optional().describe("Max results per page"),
24156
- page: external_exports.string().optional().describe("Page token"),
24249
+ pageToken: pageTokenParam,
24250
+ page: pageAliasParam,
24157
24251
  all: external_exports.boolean().optional().describe("Fetch all pages"),
24158
24252
  account: accountParam
24159
24253
  }
24160
- }, async ({ courseId, state, orderBy, max, page, all, account }) => {
24254
+ }, async ({ courseId, state, orderBy, max, pageToken, page, all, account }) => {
24161
24255
  const args = ["classroom", "announcements", "list", courseId];
24162
24256
  if (state) args.push(`--state=${state}`);
24163
24257
  if (orderBy) args.push(`--order-by=${orderBy}`);
24164
24258
  if (max !== void 0) args.push(`--max=${max}`);
24165
- if (page) args.push(`--page=${page}`);
24259
+ const token = resolvePageToken({ pageToken, page });
24260
+ if (token) args.push(`--page=${token}`);
24166
24261
  if (all) args.push("--all");
24167
24262
  return runOrDiagnose(args, { account });
24168
24263
  });
@@ -24198,14 +24293,16 @@ function registerClassroomTools(server) {
24198
24293
  inputSchema: {
24199
24294
  courseId: external_exports.string().describe("Course ID"),
24200
24295
  max: external_exports.number().optional().describe("Max results per page"),
24201
- page: external_exports.string().optional().describe("Page token"),
24296
+ pageToken: pageTokenParam,
24297
+ page: pageAliasParam,
24202
24298
  all: external_exports.boolean().optional().describe("Fetch all pages"),
24203
24299
  account: accountParam
24204
24300
  }
24205
- }, async ({ courseId, max, page, all, account }) => {
24301
+ }, async ({ courseId, max, pageToken, page, all, account }) => {
24206
24302
  const args = ["classroom", "topics", "list", courseId];
24207
24303
  if (max !== void 0) args.push(`--max=${max}`);
24208
- if (page) args.push(`--page=${page}`);
24304
+ const token = resolvePageToken({ pageToken, page });
24305
+ if (token) args.push(`--page=${token}`);
24209
24306
  if (all) args.push("--all");
24210
24307
  return runOrDiagnose(args, { account });
24211
24308
  });
@@ -24227,16 +24324,18 @@ function registerClassroomTools(server) {
24227
24324
  course: external_exports.string().optional().describe("Filter by course ID"),
24228
24325
  user: external_exports.string().optional().describe("Filter by user ID"),
24229
24326
  max: external_exports.number().optional().describe("Max results per page"),
24230
- page: external_exports.string().optional().describe("Page token"),
24327
+ pageToken: pageTokenParam,
24328
+ page: pageAliasParam,
24231
24329
  all: external_exports.boolean().optional().describe("Fetch all pages"),
24232
24330
  account: accountParam
24233
24331
  }
24234
- }, async ({ course, user, max, page, all, account }) => {
24332
+ }, async ({ course, user, max, pageToken, page, all, account }) => {
24235
24333
  const args = ["classroom", "invitations", "list"];
24236
24334
  if (course) args.push(`--course=${course}`);
24237
24335
  if (user) args.push(`--user=${user}`);
24238
24336
  if (max !== void 0) args.push(`--max=${max}`);
24239
- if (page) args.push(`--page=${page}`);
24337
+ const token = resolvePageToken({ pageToken, page });
24338
+ if (token) args.push(`--page=${token}`);
24240
24339
  if (all) args.push("--all");
24241
24340
  return runOrDiagnose(args, { account });
24242
24341
  });
@@ -24446,16 +24545,18 @@ function registerDriveTools(server) {
24446
24545
  inputSchema: {
24447
24546
  folderId: external_exports.string().optional().describe("Folder ID to list (default: root)"),
24448
24547
  max: external_exports.number().optional().describe("Max results (default: 20)"),
24449
- page: external_exports.string().optional().describe("Page token for pagination"),
24548
+ pageToken: pageTokenParam,
24549
+ page: pageAliasParam,
24450
24550
  query: external_exports.string().optional().describe(`Drive query filter (e.g. "name contains 'budget'")`),
24451
24551
  allDrives: external_exports.boolean().optional().describe("Include shared drives (default: true). Set false for My Drive only."),
24452
24552
  account: accountParam
24453
24553
  }
24454
- }, async ({ folderId, max, page, query, allDrives, account }) => {
24554
+ }, async ({ folderId, max, pageToken, page, query, allDrives, account }) => {
24455
24555
  const args = ["drive", "ls"];
24456
24556
  if (folderId) args.push(`--parent=${folderId}`);
24457
24557
  if (max !== void 0) args.push(`--max=${max}`);
24458
- if (page) args.push(`--page=${page}`);
24558
+ const token = resolvePageToken({ pageToken, page });
24559
+ if (token) args.push(`--page=${token}`);
24459
24560
  if (query) args.push(`--query=${query}`);
24460
24561
  if (allDrives === false) args.push("--no-all-drives");
24461
24562
  return runOrDiagnose(args, { account });
@@ -24645,34 +24746,148 @@ function registerDriveTools(server) {
24645
24746
  registerRunTool(server, { service: "drive", examples: '"copy", "upload", "download", "permissions"' });
24646
24747
  }
24647
24748
 
24749
+ // src/gmail-results.ts
24750
+ function sortKey(item) {
24751
+ for (const raw of [item.internalDateIso, item.date]) {
24752
+ if (typeof raw !== "string" || !raw) continue;
24753
+ const t = Date.parse(raw);
24754
+ if (!Number.isNaN(t)) return t;
24755
+ }
24756
+ return Number.NEGATIVE_INFINITY;
24757
+ }
24758
+ function sortNewestFirst(items) {
24759
+ return [...items].sort((a, b) => {
24760
+ const ka = sortKey(a);
24761
+ const kb = sortKey(b);
24762
+ return ka === kb ? 0 : kb - ka;
24763
+ });
24764
+ }
24765
+ var COUNT_PROBE_PAGE_SIZE = 500;
24766
+ async function countMatches(method, itemsKey, query, account) {
24767
+ try {
24768
+ const params = JSON.stringify({
24769
+ userId: "me",
24770
+ q: query,
24771
+ maxResults: COUNT_PROBE_PAGE_SIZE,
24772
+ fields: `${itemsKey}/id,nextPageToken`
24773
+ });
24774
+ const raw = await run(["api", "call", "gmail", "v1", method, `--params=${params}`], { account });
24775
+ const parsed = JSON.parse(raw);
24776
+ const items = parsed[itemsKey];
24777
+ if (!Array.isArray(items)) return {};
24778
+ const more = typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "";
24779
+ return more ? { atLeast: items.length } : { total: items.length };
24780
+ } catch {
24781
+ return {};
24782
+ }
24783
+ }
24784
+ async function finalizeGmailSearch(result, options) {
24785
+ const { itemsKey, method, query, account, queryIsExact = true } = options;
24786
+ const first = result.content[0];
24787
+ if (result.isError || first?.type !== "text") return result;
24788
+ let parsed;
24789
+ try {
24790
+ parsed = JSON.parse(first.text);
24791
+ } catch {
24792
+ return result;
24793
+ }
24794
+ const items = parsed[itemsKey];
24795
+ if (!Array.isArray(items)) return result;
24796
+ const sorted = sortNewestFirst(items);
24797
+ const out = { ...parsed, [itemsKey]: sorted };
24798
+ if (hasMorePages(parsed)) {
24799
+ const count = queryIsExact ? await countMatches(method, itemsKey, query, account) : {};
24800
+ annotateTruncation(out, sorted.length, count);
24801
+ }
24802
+ return rawTextResult(JSON.stringify(out));
24803
+ }
24804
+ async function fetchGmailPages(runPage, itemsKey, maxPages, startToken) {
24805
+ const merged = [];
24806
+ let base;
24807
+ let token = startToken;
24808
+ for (let pages = 0; pages < maxPages; pages++) {
24809
+ const result = await runPage(token);
24810
+ const parsed = parsePage(result, itemsKey);
24811
+ if (parsed === void 0) {
24812
+ return base === void 0 ? result : finish(base, itemsKey, merged, token);
24813
+ }
24814
+ base = parsed;
24815
+ merged.push(...parsed[itemsKey]);
24816
+ token = typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "" ? parsed.nextPageToken : void 0;
24817
+ if (token === void 0) break;
24818
+ }
24819
+ return finish(base, itemsKey, merged, token);
24820
+ }
24821
+ function parsePage(result, itemsKey) {
24822
+ const first = result.content[0];
24823
+ if (result.isError || first?.type !== "text") return void 0;
24824
+ let parsed;
24825
+ try {
24826
+ parsed = JSON.parse(first.text);
24827
+ } catch {
24828
+ return void 0;
24829
+ }
24830
+ if (parsed === null || typeof parsed !== "object") return void 0;
24831
+ const obj = parsed;
24832
+ return Array.isArray(obj[itemsKey]) ? obj : void 0;
24833
+ }
24834
+ function finish(base, itemsKey, merged, token) {
24835
+ const out = { ...base, [itemsKey]: merged };
24836
+ if (token === void 0) delete out.nextPageToken;
24837
+ else out.nextPageToken = token;
24838
+ return rawTextResult(JSON.stringify(out));
24839
+ }
24840
+
24648
24841
  // src/tools/gmail.ts
24649
24842
  function registerGmailTools(server) {
24650
24843
  server.registerTool("gog_gmail_search", {
24651
- description: `Search Gmail threads using Gmail query syntax (e.g. "from:alice subject:invoice is:unread"). The query is passed verbatim to Gmail; a bare name token (from:alison) matches per Gmail's own heuristics, a full address (from:alison@example.com) is exact. To match a contact across several addresses, OR them: from:(a@x.com OR b@y.com).`,
24844
+ description: 'Search Gmail threads using Gmail query syntax (e.g. "from:alice subject:invoice is:unread"). The query is passed verbatim to Gmail; a bare name token (from:alison) matches per Gmail\'s own heuristics, a full address (from:alison@example.com) is exact. To match a contact across several addresses, OR them: from:(a@x.com OR b@y.com). Results are ALWAYS newest-first by Gmail\'s internalDate \u2014 the wrapper sorts them, so the first result is the most recent match and a recent message can never be buried below older ones. IMPORTANT \u2014 a response carrying "truncated": true is an INCOMPLETE view of the matches: NEVER report that a message does not exist, or that there is no such mail, on the strength of one. Page through it (pass nextPageToken back as `pageToken`), set maxPages to walk several pages in one call, or narrow the query, and only then draw a conclusion. If you already know the thread, do not search for it at all \u2014 read it directly with gog_gmail_thread_get, which returns the whole thread and cannot be truncated or mis-ranked.',
24652
24845
  annotations: { readOnlyHint: true },
24653
24846
  inputSchema: {
24654
24847
  query: external_exports.string().describe("Gmail search query"),
24655
24848
  max: external_exports.number().int().optional().describe("Max results to return (default: 10)"),
24849
+ pageToken: pageTokenParam,
24850
+ page: pageAliasParam,
24851
+ maxPages: external_exports.number().int().positive().max(20).optional().describe('Walk up to this many pages in ONE call and merge the results, instead of returning a single page. Use it for existence questions ("is there any mail matching X?"), which a single page cannot answer. Stops early at the last page; if pages remain when the cap is hit the response is still marked truncated. Prefer this over all=true, which is unbounded.'),
24852
+ all: external_exports.boolean().optional().describe('Fetch every page instead of one. Removes truncation entirely, at the cost of one API round-trip per page \u2014 the reliable way to answer "does any message match?" for a query with few expected hits.'),
24656
24853
  fromContact: external_exports.string().optional().describe("Resolve a Google Contact (name or email) to its addresses and AND a from:(addr OR addr) clause onto the query \u2014 saves looking the contact up first when you only know who, not which address."),
24657
24854
  account: accountParam
24658
24855
  }
24659
- }, async ({ query, max, fromContact, account }) => {
24856
+ }, async ({ query, max, pageToken, page, maxPages, all, fromContact, account }) => {
24660
24857
  const args = ["gmail", "search", query];
24661
24858
  if (max !== void 0) args.push(`--max=${max}`);
24859
+ if (all) args.push("--all");
24662
24860
  if (fromContact) args.push(`--from-contact=${fromContact}`);
24663
- return runOrDiagnose(args, { account });
24861
+ const runPage = (tok) => runOrDiagnose(tok ? [...args, `--page=${tok}`] : args, { account });
24862
+ const token = resolvePageToken({ pageToken, page });
24863
+ const result = maxPages !== void 0 ? await fetchGmailPages(runPage, "threads", maxPages, token) : await runPage(token);
24864
+ return finalizeGmailSearch(result, {
24865
+ itemsKey: "threads",
24866
+ method: "users.threads.list",
24867
+ query,
24868
+ account,
24869
+ // --from-contact is expanded INSIDE gog, against the People API, so the
24870
+ // query Gmail actually saw is not the one we hold here.
24871
+ queryIsExact: !fromContact
24872
+ });
24664
24873
  });
24665
24874
  server.registerTool("gog_gmail_get", {
24666
- description: "Get a Gmail message by ID.",
24875
+ description: "Get a Gmail message by ID. For a long message, sanitizeContent is the cheapest way to keep it in context: it drops the raw MIME payload and the HTML part, which are usually the bulk of the response.",
24667
24876
  annotations: { readOnlyHint: true },
24668
24877
  inputSchema: {
24669
24878
  messageId: external_exports.string().describe("Message ID"),
24670
24879
  format: external_exports.enum(["full", "metadata", "raw"]).optional().describe("Message format (default: full)"),
24880
+ // Requires gog >= 0.37.0. Before that (openclaw/gogcli#992) the JSON
24881
+ // carried the headers and body TWICE — once inside `message`, once
24882
+ // copied to the top level — so the flag meant to shrink the payload
24883
+ // enlarged it. MIN_GOG_VERSION is the guard; there is no runtime check.
24884
+ sanitizeContent: external_exports.boolean().optional().describe("Return agent-oriented sanitized content: HTML stripped, HTTP(S) URLs removed, raw Gmail payloads omitted from the JSON. The largest payload-size reduction available here. Note the URL removal is lossy \u2014 omit this when you need to follow a link out of the message."),
24671
24885
  account: accountParam
24672
24886
  }
24673
- }, async ({ messageId, format, account }) => {
24887
+ }, async ({ messageId, format, sanitizeContent, account }) => {
24674
24888
  const args = ["gmail", "get", messageId];
24675
24889
  if (format) args.push(`--format=${format}`);
24890
+ if (sanitizeContent) args.push("--sanitize-content");
24676
24891
  return runOrDiagnose(args, { account });
24677
24892
  });
24678
24893
  server.registerTool("gog_gmail_send", {
@@ -25025,7 +25240,7 @@ function registerTasksTools(server) {
25025
25240
  }
25026
25241
 
25027
25242
  // src/server.ts
25028
- var VERSION = true ? "2.23.1" : "0.0.0";
25243
+ var VERSION = true ? "2.24.0" : "0.0.0";
25029
25244
  var BASE_TOOL_REGISTRARS = [
25030
25245
  registerApiTools,
25031
25246
  registerAuthTools,
@@ -25529,12 +25744,17 @@ export {
25529
25744
  PAYLOAD_INLINE_MAX,
25530
25745
  VERSION,
25531
25746
  accountParam,
25747
+ annotateTruncatedList,
25532
25748
  authToolsFor,
25533
25749
  diagnose,
25534
25750
  errorText,
25751
+ fetchGmailPages,
25752
+ finalizeGmailSearch,
25535
25753
  ids,
25536
25754
  isGogFileArg,
25537
25755
  normalizeTimestamps,
25756
+ pageAliasParam,
25757
+ pageTokenParam,
25538
25758
  paginationParams,
25539
25759
  payloadArg,
25540
25760
  pushPaginationFlags,
@@ -25550,9 +25770,11 @@ export {
25550
25770
  registerSheetsTools,
25551
25771
  registerSlidesTools,
25552
25772
  registerTasksTools,
25773
+ resolvePageToken,
25553
25774
  run,
25554
25775
  runBinary,
25555
25776
  runExecutor,
25556
25777
  runOrDiagnose,
25778
+ stripConsumedPageToken,
25557
25779
  useRemoteGogRunner
25558
25780
  };