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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +283 -69
- package/dist/lib.js +293 -71
- package/manifest.json +3 -3
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/gmail-results.ts +239 -0
- package/src/lib.ts +9 -0
- package/src/pagination.ts +108 -0
- package/src/runner.ts +1 -1
- package/src/tools/auth.ts +39 -12
- package/src/tools/calendar.ts +28 -7
- package/src/tools/classroom.ts +46 -28
- package/src/tools/drive.ts +6 -4
- package/src/tools/gmail.ts +37 -6
- package/src/tools/utils.ts +36 -5
- package/src/worker.ts +1 -1
- package/tests/gmail-results.test.ts +285 -0
- package/tests/page-cursor-contract.test.ts +50 -0
- package/tests/pagination.test.ts +102 -0
- package/tests/tools/auth.test.ts +60 -0
- package/tests/tools/calendar.test.ts +81 -3
- package/tests/tools/gmail.test.ts +163 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
|
|
10
|
-
"version": "2.
|
|
10
|
+
"version": "2.24.0"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "gogcli",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
|
|
18
|
-
"version": "2.
|
|
18
|
+
"version": "2.24.0",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/dist/index.js
CHANGED
|
@@ -31552,6 +31552,55 @@ function normalizeTimestamps(text, tz = displayTimeZone(), naiveTz = naiveSource
|
|
|
31552
31552
|
return JSON.stringify(parsed, null, detectIndent(text));
|
|
31553
31553
|
}
|
|
31554
31554
|
|
|
31555
|
+
// src/pagination.ts
|
|
31556
|
+
function detectIndent2(text) {
|
|
31557
|
+
const match = /\n(\s+)\S/.exec(text);
|
|
31558
|
+
return match ? match[1].replace(/\t/g, " ").length : 0;
|
|
31559
|
+
}
|
|
31560
|
+
function stripConsumedPageToken(text) {
|
|
31561
|
+
const trimmed = text.trim();
|
|
31562
|
+
if (trimmed === "" || !trimmed.startsWith("{")) return text;
|
|
31563
|
+
let parsed;
|
|
31564
|
+
try {
|
|
31565
|
+
parsed = JSON.parse(trimmed);
|
|
31566
|
+
} catch {
|
|
31567
|
+
return text;
|
|
31568
|
+
}
|
|
31569
|
+
const obj = parsed;
|
|
31570
|
+
if (obj.nextPageToken !== "") return text;
|
|
31571
|
+
delete obj.nextPageToken;
|
|
31572
|
+
return JSON.stringify(obj, null, detectIndent2(text));
|
|
31573
|
+
}
|
|
31574
|
+
function truncationWarning(returned, count) {
|
|
31575
|
+
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`;
|
|
31576
|
+
return `INCOMPLETE RESULT SET: ${scope}. Do not report an absence of results based on this response. Page with nextPageToken or narrow the query.`;
|
|
31577
|
+
}
|
|
31578
|
+
function annotateTruncation(out, returned, count) {
|
|
31579
|
+
out.truncated = true;
|
|
31580
|
+
out.returned = returned;
|
|
31581
|
+
if (count.total !== void 0) out.totalMatches = count.total;
|
|
31582
|
+
if (count.atLeast !== void 0) out.totalMatchesAtLeast = count.atLeast;
|
|
31583
|
+
out.warning = truncationWarning(returned, count);
|
|
31584
|
+
}
|
|
31585
|
+
function hasMorePages(parsed) {
|
|
31586
|
+
return typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "";
|
|
31587
|
+
}
|
|
31588
|
+
function annotateTruncatedList(result, itemsKey) {
|
|
31589
|
+
const first = result.content[0];
|
|
31590
|
+
if (result.isError || first?.type !== "text") return result;
|
|
31591
|
+
let parsed;
|
|
31592
|
+
try {
|
|
31593
|
+
parsed = JSON.parse(first.text);
|
|
31594
|
+
} catch {
|
|
31595
|
+
return result;
|
|
31596
|
+
}
|
|
31597
|
+
const items = parsed[itemsKey];
|
|
31598
|
+
if (!Array.isArray(items) || !hasMorePages(parsed)) return result;
|
|
31599
|
+
const out = { ...parsed };
|
|
31600
|
+
annotateTruncation(out, items.length, {});
|
|
31601
|
+
return rawTextResult(JSON.stringify(out));
|
|
31602
|
+
}
|
|
31603
|
+
|
|
31555
31604
|
// src/tools/utils.ts
|
|
31556
31605
|
var PAYLOAD_INLINE_MAX = 4096;
|
|
31557
31606
|
function payloadArg(inlineFlag, fileFlag, value, ext) {
|
|
@@ -31587,9 +31636,19 @@ var ids = {
|
|
|
31587
31636
|
// People API uses fully-qualified resource names ("people/c123") not bare IDs.
|
|
31588
31637
|
person: external_exports.string().describe("Person resource name (people/...) or email")
|
|
31589
31638
|
};
|
|
31639
|
+
var pageTokenParam = external_exports.string().optional().describe(
|
|
31640
|
+
"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."
|
|
31641
|
+
);
|
|
31642
|
+
var pageAliasParam = external_exports.string().optional().describe(
|
|
31643
|
+
"Deprecated alias for pageToken, accepted so existing callers keep working. Use pageToken \u2014 it matches the nextPageToken field in the response."
|
|
31644
|
+
);
|
|
31645
|
+
function resolvePageToken(p) {
|
|
31646
|
+
return p.pageToken ?? p.page;
|
|
31647
|
+
}
|
|
31590
31648
|
var paginationParams = {
|
|
31591
31649
|
max: external_exports.number().int().optional().describe("Max results"),
|
|
31592
|
-
|
|
31650
|
+
pageToken: pageTokenParam,
|
|
31651
|
+
page: pageAliasParam,
|
|
31593
31652
|
all: external_exports.boolean().optional().describe("Fetch all pages")
|
|
31594
31653
|
};
|
|
31595
31654
|
function registerRunTool(server, options) {
|
|
@@ -31664,7 +31723,7 @@ ${accounts || "(none)"}${hint}`);
|
|
|
31664
31723
|
async function runOrDiagnose(args, options) {
|
|
31665
31724
|
try {
|
|
31666
31725
|
const raw = await run(args, options);
|
|
31667
|
-
return rawTextResult(options.lossless ? raw : normalizeTimestamps(raw));
|
|
31726
|
+
return rawTextResult(options.lossless ? raw : stripConsumedPageToken(normalizeTimestamps(raw)));
|
|
31668
31727
|
} catch (err) {
|
|
31669
31728
|
return diagnose(err);
|
|
31670
31729
|
}
|
|
@@ -31768,6 +31827,7 @@ function registerApiTools(server) {
|
|
|
31768
31827
|
// src/tools/auth.ts
|
|
31769
31828
|
function registerAuthToolsWith(server, defaultServices) {
|
|
31770
31829
|
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.`;
|
|
31830
|
+
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.";
|
|
31771
31831
|
server.registerTool("gog_auth_list", {
|
|
31772
31832
|
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.",
|
|
31773
31833
|
annotations: { readOnlyHint: true },
|
|
@@ -31817,11 +31877,14 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
31817
31877
|
annotations: { destructiveHint: true },
|
|
31818
31878
|
inputSchema: {
|
|
31819
31879
|
email: external_exports.string().describe("Google account email to authorize"),
|
|
31820
|
-
services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe)
|
|
31880
|
+
services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
|
|
31881
|
+
extraScopes: external_exports.string().optional().describe(extraScopesDescribe)
|
|
31821
31882
|
}
|
|
31822
|
-
}, async ({ email: email3, services = defaultServices }) => {
|
|
31883
|
+
}, async ({ email: email3, services = defaultServices, extraScopes }) => {
|
|
31823
31884
|
try {
|
|
31824
|
-
|
|
31885
|
+
const args = ["auth", "add", email3, "--services", services];
|
|
31886
|
+
if (extraScopes) args.push(`--extra-scopes=${extraScopes}`, "--force-consent");
|
|
31887
|
+
return rawTextResult(await run(args, {
|
|
31825
31888
|
interactive: true,
|
|
31826
31889
|
timeout: 3e5
|
|
31827
31890
|
}));
|
|
@@ -31833,14 +31896,14 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
31833
31896
|
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.",
|
|
31834
31897
|
inputSchema: {
|
|
31835
31898
|
email: external_exports.string().describe("Google account email to authorize"),
|
|
31836
|
-
services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe)
|
|
31899
|
+
services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
|
|
31900
|
+
extraScopes: external_exports.string().optional().describe(`${extraScopesDescribe} Pass the SAME value to gog_auth_add_complete.`)
|
|
31837
31901
|
}
|
|
31838
|
-
}, async ({ email: email3, services = defaultServices }) => {
|
|
31902
|
+
}, async ({ email: email3, services = defaultServices, extraScopes }) => {
|
|
31839
31903
|
try {
|
|
31840
|
-
|
|
31841
|
-
|
|
31842
|
-
|
|
31843
|
-
));
|
|
31904
|
+
const args = ["auth", "add", email3, "--remote", "--step", "1", "--services", services, "--force-consent"];
|
|
31905
|
+
if (extraScopes) args.push(`--extra-scopes=${extraScopes}`);
|
|
31906
|
+
return rawTextResult(await run(args, { redactMode: "tokens" }));
|
|
31844
31907
|
} catch (err) {
|
|
31845
31908
|
return errorResult(errorText(err));
|
|
31846
31909
|
}
|
|
@@ -31855,25 +31918,28 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
31855
31918
|
),
|
|
31856
31919
|
services: external_exports.string().optional().default(defaultServices).describe(
|
|
31857
31920
|
`Services authorized \u2014 MUST match the value passed to gog_auth_add_url. Default: "${defaultServices}".`
|
|
31921
|
+
),
|
|
31922
|
+
extraScopes: external_exports.string().optional().describe(
|
|
31923
|
+
"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."
|
|
31858
31924
|
)
|
|
31859
31925
|
}
|
|
31860
|
-
}, async ({ email: email3, redirectUrl, services = defaultServices }) => {
|
|
31926
|
+
}, async ({ email: email3, redirectUrl, services = defaultServices, extraScopes }) => {
|
|
31861
31927
|
try {
|
|
31862
|
-
|
|
31863
|
-
|
|
31864
|
-
|
|
31865
|
-
|
|
31866
|
-
|
|
31867
|
-
|
|
31868
|
-
|
|
31869
|
-
|
|
31870
|
-
|
|
31871
|
-
|
|
31872
|
-
|
|
31873
|
-
|
|
31874
|
-
|
|
31875
|
-
|
|
31876
|
-
));
|
|
31928
|
+
const args = [
|
|
31929
|
+
"auth",
|
|
31930
|
+
"add",
|
|
31931
|
+
email3,
|
|
31932
|
+
"--remote",
|
|
31933
|
+
"--step",
|
|
31934
|
+
"2",
|
|
31935
|
+
"--auth-url",
|
|
31936
|
+
redirectUrl,
|
|
31937
|
+
"--services",
|
|
31938
|
+
services,
|
|
31939
|
+
"--force-consent"
|
|
31940
|
+
];
|
|
31941
|
+
if (extraScopes) args.push(`--extra-scopes=${extraScopes}`);
|
|
31942
|
+
return rawTextResult(await run(args));
|
|
31877
31943
|
} catch (err) {
|
|
31878
31944
|
return errorResult(errorText(err));
|
|
31879
31945
|
}
|
|
@@ -31892,30 +31958,44 @@ function registerAuthTools(server) {
|
|
|
31892
31958
|
// src/tools/calendar.ts
|
|
31893
31959
|
function registerCalendarTools(server) {
|
|
31894
31960
|
server.registerTool("gog_calendar_events", {
|
|
31895
|
-
description:
|
|
31961
|
+
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.',
|
|
31896
31962
|
annotations: { readOnlyHint: true },
|
|
31897
31963
|
inputSchema: {
|
|
31898
31964
|
calendarId: external_exports.string().optional().describe("Calendar ID (default: primary calendar)"),
|
|
31899
31965
|
from: external_exports.string().optional().describe("Start time filter (RFC3339, date, or natural language)"),
|
|
31900
|
-
to: external_exports.string().optional().describe("End time filter (RFC3339, date, or natural language)"),
|
|
31901
|
-
|
|
31966
|
+
to: external_exports.string().optional().describe("End time filter (RFC3339, date, or natural language). Mutually exclusive with today and with days."),
|
|
31967
|
+
// gog >= 0.36.0 (openclaw/gogcli#981). Before that release --days sat in
|
|
31968
|
+
// a switch arm evaluated ahead of --from, so `--from 2026-09-25 --days 5`
|
|
31969
|
+
// silently threw --from away and answered for today instead — at exit 0,
|
|
31970
|
+
// in a well-formed table. It is only exposed here now that it means what
|
|
31971
|
+
// it says.
|
|
31972
|
+
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.'),
|
|
31973
|
+
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."),
|
|
31902
31974
|
query: external_exports.string().optional().describe("Free text search within events"),
|
|
31903
|
-
|
|
31975
|
+
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."),
|
|
31976
|
+
pageToken: pageTokenParam,
|
|
31977
|
+
page: pageAliasParam,
|
|
31978
|
+
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.'),
|
|
31904
31979
|
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)"),
|
|
31905
31980
|
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.`),
|
|
31906
31981
|
account: accountParam
|
|
31907
31982
|
}
|
|
31908
|
-
}, async ({ calendarId, from, to, today, query, all, eventTypes, timezone, account }) => {
|
|
31983
|
+
}, async ({ calendarId, from, to, days, today, query, max, pageToken, page, all, eventTypes, timezone, account }) => {
|
|
31909
31984
|
const args = ["calendar", "events"];
|
|
31910
31985
|
if (calendarId) args.push(calendarId);
|
|
31911
31986
|
if (from) args.push(`--from=${from}`);
|
|
31912
31987
|
if (to) args.push(`--to=${to}`);
|
|
31988
|
+
if (days !== void 0) args.push(`--days=${days}`);
|
|
31913
31989
|
if (today) args.push("--today");
|
|
31914
31990
|
if (query) args.push(`--query=${query}`);
|
|
31991
|
+
if (max !== void 0) args.push(`--max=${max}`);
|
|
31992
|
+
const token = resolvePageToken({ pageToken, page });
|
|
31993
|
+
if (token) args.push(`--page=${token}`);
|
|
31915
31994
|
if (all) args.push("--all");
|
|
31916
31995
|
if (eventTypes) for (const t of eventTypes) args.push(`--event-types=${t}`);
|
|
31917
31996
|
if (timezone) args.push(`--timezone=${timezone}`);
|
|
31918
|
-
|
|
31997
|
+
const result = await runOrDiagnose(args, { account });
|
|
31998
|
+
return annotateTruncatedList(result, "events");
|
|
31919
31999
|
});
|
|
31920
32000
|
server.registerTool("gog_calendar_get", {
|
|
31921
32001
|
description: "Get a specific calendar event by ID.",
|
|
@@ -32032,17 +32112,19 @@ function registerClassroomTools(server) {
|
|
|
32032
32112
|
teacher: external_exports.string().optional().describe("Filter by teacher user ID"),
|
|
32033
32113
|
student: external_exports.string().optional().describe("Filter by student user ID"),
|
|
32034
32114
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32035
|
-
|
|
32115
|
+
pageToken: pageTokenParam,
|
|
32116
|
+
page: pageAliasParam,
|
|
32036
32117
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32037
32118
|
account: accountParam
|
|
32038
32119
|
}
|
|
32039
|
-
}, async ({ state, teacher, student, max, page, all, account }) => {
|
|
32120
|
+
}, async ({ state, teacher, student, max, pageToken, page, all, account }) => {
|
|
32040
32121
|
const args = ["classroom", "courses", "list"];
|
|
32041
32122
|
if (state) args.push(`--state=${state}`);
|
|
32042
32123
|
if (teacher) args.push(`--teacher=${teacher}`);
|
|
32043
32124
|
if (student) args.push(`--student=${student}`);
|
|
32044
32125
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32045
|
-
|
|
32126
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32127
|
+
if (token) args.push(`--page=${token}`);
|
|
32046
32128
|
if (all) args.push("--all");
|
|
32047
32129
|
return runOrDiagnose(args, { account });
|
|
32048
32130
|
});
|
|
@@ -32062,14 +32144,16 @@ function registerClassroomTools(server) {
|
|
|
32062
32144
|
inputSchema: {
|
|
32063
32145
|
courseId: external_exports.string().describe("Course ID"),
|
|
32064
32146
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32065
|
-
|
|
32147
|
+
pageToken: pageTokenParam,
|
|
32148
|
+
page: pageAliasParam,
|
|
32066
32149
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32067
32150
|
account: accountParam
|
|
32068
32151
|
}
|
|
32069
|
-
}, async ({ courseId, max, page, all, account }) => {
|
|
32152
|
+
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
32070
32153
|
const args = ["classroom", "students", "list", courseId];
|
|
32071
32154
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32072
|
-
|
|
32155
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32156
|
+
if (token) args.push(`--page=${token}`);
|
|
32073
32157
|
if (all) args.push("--all");
|
|
32074
32158
|
return runOrDiagnose(args, { account });
|
|
32075
32159
|
});
|
|
@@ -32090,14 +32174,16 @@ function registerClassroomTools(server) {
|
|
|
32090
32174
|
inputSchema: {
|
|
32091
32175
|
courseId: external_exports.string().describe("Course ID"),
|
|
32092
32176
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32093
|
-
|
|
32177
|
+
pageToken: pageTokenParam,
|
|
32178
|
+
page: pageAliasParam,
|
|
32094
32179
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32095
32180
|
account: accountParam
|
|
32096
32181
|
}
|
|
32097
|
-
}, async ({ courseId, max, page, all, account }) => {
|
|
32182
|
+
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
32098
32183
|
const args = ["classroom", "teachers", "list", courseId];
|
|
32099
32184
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32100
|
-
|
|
32185
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32186
|
+
if (token) args.push(`--page=${token}`);
|
|
32101
32187
|
if (all) args.push("--all");
|
|
32102
32188
|
return runOrDiagnose(args, { account });
|
|
32103
32189
|
});
|
|
@@ -32120,16 +32206,18 @@ function registerClassroomTools(server) {
|
|
|
32120
32206
|
students: external_exports.boolean().optional().describe("Include students only"),
|
|
32121
32207
|
teachers: external_exports.boolean().optional().describe("Include teachers only"),
|
|
32122
32208
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32123
|
-
|
|
32209
|
+
pageToken: pageTokenParam,
|
|
32210
|
+
page: pageAliasParam,
|
|
32124
32211
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32125
32212
|
account: accountParam
|
|
32126
32213
|
}
|
|
32127
|
-
}, async ({ courseId, students, teachers, max, page, all, account }) => {
|
|
32214
|
+
}, async ({ courseId, students, teachers, max, pageToken, page, all, account }) => {
|
|
32128
32215
|
const args = ["classroom", "roster", courseId];
|
|
32129
32216
|
if (students) args.push("--students");
|
|
32130
32217
|
if (teachers) args.push("--teachers");
|
|
32131
32218
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32132
|
-
|
|
32219
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32220
|
+
if (token) args.push(`--page=${token}`);
|
|
32133
32221
|
if (all) args.push("--all");
|
|
32134
32222
|
return runOrDiagnose(args, { account });
|
|
32135
32223
|
});
|
|
@@ -32142,18 +32230,20 @@ function registerClassroomTools(server) {
|
|
|
32142
32230
|
topic: external_exports.string().optional().describe("Filter by topic ID"),
|
|
32143
32231
|
orderBy: external_exports.string().optional().describe('Sort order (e.g. "updateTime desc")'),
|
|
32144
32232
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32145
|
-
|
|
32233
|
+
pageToken: pageTokenParam,
|
|
32234
|
+
page: pageAliasParam,
|
|
32146
32235
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32147
32236
|
scanPages: external_exports.number().optional().describe("Max pages to scan when filtering"),
|
|
32148
32237
|
account: accountParam
|
|
32149
32238
|
}
|
|
32150
|
-
}, async ({ courseId, state, topic, orderBy, max, page, all, scanPages, account }) => {
|
|
32239
|
+
}, async ({ courseId, state, topic, orderBy, max, pageToken, page, all, scanPages, account }) => {
|
|
32151
32240
|
const args = ["classroom", "coursework", "list", courseId];
|
|
32152
32241
|
if (state) args.push(`--state=${state}`);
|
|
32153
32242
|
if (topic) args.push(`--topic=${topic}`);
|
|
32154
32243
|
if (orderBy) args.push(`--order-by=${orderBy}`);
|
|
32155
32244
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32156
|
-
|
|
32245
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32246
|
+
if (token) args.push(`--page=${token}`);
|
|
32157
32247
|
if (all) args.push("--all");
|
|
32158
32248
|
if (scanPages !== void 0) args.push(`--scan-pages=${scanPages}`);
|
|
32159
32249
|
return runOrDiagnose(args, { account });
|
|
@@ -32179,17 +32269,19 @@ function registerClassroomTools(server) {
|
|
|
32179
32269
|
late: external_exports.enum(["late", "not-late"]).optional().describe("Filter by late status"),
|
|
32180
32270
|
user: external_exports.string().optional().describe("Filter by student user ID"),
|
|
32181
32271
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32182
|
-
|
|
32272
|
+
pageToken: pageTokenParam,
|
|
32273
|
+
page: pageAliasParam,
|
|
32183
32274
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32184
32275
|
account: accountParam
|
|
32185
32276
|
}
|
|
32186
|
-
}, async ({ courseId, courseworkId, state, late: late2, user, max, page, all, account }) => {
|
|
32277
|
+
}, async ({ courseId, courseworkId, state, late: late2, user, max, pageToken, page, all, account }) => {
|
|
32187
32278
|
const args = ["classroom", "submissions", "list", courseId, courseworkId];
|
|
32188
32279
|
if (state) args.push(`--state=${state}`);
|
|
32189
32280
|
if (late2) args.push(`--late=${late2}`);
|
|
32190
32281
|
if (user) args.push(`--user=${user}`);
|
|
32191
32282
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32192
|
-
|
|
32283
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32284
|
+
if (token) args.push(`--page=${token}`);
|
|
32193
32285
|
if (all) args.push("--all");
|
|
32194
32286
|
return runOrDiagnose(args, { account });
|
|
32195
32287
|
});
|
|
@@ -32266,16 +32358,18 @@ function registerClassroomTools(server) {
|
|
|
32266
32358
|
state: external_exports.string().optional().describe("Filter by announcement state"),
|
|
32267
32359
|
orderBy: external_exports.string().optional().describe("Sort order"),
|
|
32268
32360
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32269
|
-
|
|
32361
|
+
pageToken: pageTokenParam,
|
|
32362
|
+
page: pageAliasParam,
|
|
32270
32363
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32271
32364
|
account: accountParam
|
|
32272
32365
|
}
|
|
32273
|
-
}, async ({ courseId, state, orderBy, max, page, all, account }) => {
|
|
32366
|
+
}, async ({ courseId, state, orderBy, max, pageToken, page, all, account }) => {
|
|
32274
32367
|
const args = ["classroom", "announcements", "list", courseId];
|
|
32275
32368
|
if (state) args.push(`--state=${state}`);
|
|
32276
32369
|
if (orderBy) args.push(`--order-by=${orderBy}`);
|
|
32277
32370
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32278
|
-
|
|
32371
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32372
|
+
if (token) args.push(`--page=${token}`);
|
|
32279
32373
|
if (all) args.push("--all");
|
|
32280
32374
|
return runOrDiagnose(args, { account });
|
|
32281
32375
|
});
|
|
@@ -32311,14 +32405,16 @@ function registerClassroomTools(server) {
|
|
|
32311
32405
|
inputSchema: {
|
|
32312
32406
|
courseId: external_exports.string().describe("Course ID"),
|
|
32313
32407
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32314
|
-
|
|
32408
|
+
pageToken: pageTokenParam,
|
|
32409
|
+
page: pageAliasParam,
|
|
32315
32410
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32316
32411
|
account: accountParam
|
|
32317
32412
|
}
|
|
32318
|
-
}, async ({ courseId, max, page, all, account }) => {
|
|
32413
|
+
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
32319
32414
|
const args = ["classroom", "topics", "list", courseId];
|
|
32320
32415
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32321
|
-
|
|
32416
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32417
|
+
if (token) args.push(`--page=${token}`);
|
|
32322
32418
|
if (all) args.push("--all");
|
|
32323
32419
|
return runOrDiagnose(args, { account });
|
|
32324
32420
|
});
|
|
@@ -32340,16 +32436,18 @@ function registerClassroomTools(server) {
|
|
|
32340
32436
|
course: external_exports.string().optional().describe("Filter by course ID"),
|
|
32341
32437
|
user: external_exports.string().optional().describe("Filter by user ID"),
|
|
32342
32438
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32343
|
-
|
|
32439
|
+
pageToken: pageTokenParam,
|
|
32440
|
+
page: pageAliasParam,
|
|
32344
32441
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32345
32442
|
account: accountParam
|
|
32346
32443
|
}
|
|
32347
|
-
}, async ({ course, user, max, page, all, account }) => {
|
|
32444
|
+
}, async ({ course, user, max, pageToken, page, all, account }) => {
|
|
32348
32445
|
const args = ["classroom", "invitations", "list"];
|
|
32349
32446
|
if (course) args.push(`--course=${course}`);
|
|
32350
32447
|
if (user) args.push(`--user=${user}`);
|
|
32351
32448
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32352
|
-
|
|
32449
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32450
|
+
if (token) args.push(`--page=${token}`);
|
|
32353
32451
|
if (all) args.push("--all");
|
|
32354
32452
|
return runOrDiagnose(args, { account });
|
|
32355
32453
|
});
|
|
@@ -32559,16 +32657,18 @@ function registerDriveTools(server) {
|
|
|
32559
32657
|
inputSchema: {
|
|
32560
32658
|
folderId: external_exports.string().optional().describe("Folder ID to list (default: root)"),
|
|
32561
32659
|
max: external_exports.number().optional().describe("Max results (default: 20)"),
|
|
32562
|
-
|
|
32660
|
+
pageToken: pageTokenParam,
|
|
32661
|
+
page: pageAliasParam,
|
|
32563
32662
|
query: external_exports.string().optional().describe(`Drive query filter (e.g. "name contains 'budget'")`),
|
|
32564
32663
|
allDrives: external_exports.boolean().optional().describe("Include shared drives (default: true). Set false for My Drive only."),
|
|
32565
32664
|
account: accountParam
|
|
32566
32665
|
}
|
|
32567
|
-
}, async ({ folderId, max, page, query, allDrives, account }) => {
|
|
32666
|
+
}, async ({ folderId, max, pageToken, page, query, allDrives, account }) => {
|
|
32568
32667
|
const args = ["drive", "ls"];
|
|
32569
32668
|
if (folderId) args.push(`--parent=${folderId}`);
|
|
32570
32669
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32571
|
-
|
|
32670
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32671
|
+
if (token) args.push(`--page=${token}`);
|
|
32572
32672
|
if (query) args.push(`--query=${query}`);
|
|
32573
32673
|
if (allDrives === false) args.push("--no-all-drives");
|
|
32574
32674
|
return runOrDiagnose(args, { account });
|
|
@@ -32758,34 +32858,148 @@ function registerDriveTools(server) {
|
|
|
32758
32858
|
registerRunTool(server, { service: "drive", examples: '"copy", "upload", "download", "permissions"' });
|
|
32759
32859
|
}
|
|
32760
32860
|
|
|
32861
|
+
// src/gmail-results.ts
|
|
32862
|
+
function sortKey(item) {
|
|
32863
|
+
for (const raw of [item.internalDateIso, item.date]) {
|
|
32864
|
+
if (typeof raw !== "string" || !raw) continue;
|
|
32865
|
+
const t = Date.parse(raw);
|
|
32866
|
+
if (!Number.isNaN(t)) return t;
|
|
32867
|
+
}
|
|
32868
|
+
return Number.NEGATIVE_INFINITY;
|
|
32869
|
+
}
|
|
32870
|
+
function sortNewestFirst(items) {
|
|
32871
|
+
return [...items].sort((a, b) => {
|
|
32872
|
+
const ka = sortKey(a);
|
|
32873
|
+
const kb = sortKey(b);
|
|
32874
|
+
return ka === kb ? 0 : kb - ka;
|
|
32875
|
+
});
|
|
32876
|
+
}
|
|
32877
|
+
var COUNT_PROBE_PAGE_SIZE = 500;
|
|
32878
|
+
async function countMatches(method, itemsKey, query, account) {
|
|
32879
|
+
try {
|
|
32880
|
+
const params = JSON.stringify({
|
|
32881
|
+
userId: "me",
|
|
32882
|
+
q: query,
|
|
32883
|
+
maxResults: COUNT_PROBE_PAGE_SIZE,
|
|
32884
|
+
fields: `${itemsKey}/id,nextPageToken`
|
|
32885
|
+
});
|
|
32886
|
+
const raw = await run(["api", "call", "gmail", "v1", method, `--params=${params}`], { account });
|
|
32887
|
+
const parsed = JSON.parse(raw);
|
|
32888
|
+
const items = parsed[itemsKey];
|
|
32889
|
+
if (!Array.isArray(items)) return {};
|
|
32890
|
+
const more = typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "";
|
|
32891
|
+
return more ? { atLeast: items.length } : { total: items.length };
|
|
32892
|
+
} catch {
|
|
32893
|
+
return {};
|
|
32894
|
+
}
|
|
32895
|
+
}
|
|
32896
|
+
async function finalizeGmailSearch(result, options) {
|
|
32897
|
+
const { itemsKey, method, query, account, queryIsExact = true } = options;
|
|
32898
|
+
const first = result.content[0];
|
|
32899
|
+
if (result.isError || first?.type !== "text") return result;
|
|
32900
|
+
let parsed;
|
|
32901
|
+
try {
|
|
32902
|
+
parsed = JSON.parse(first.text);
|
|
32903
|
+
} catch {
|
|
32904
|
+
return result;
|
|
32905
|
+
}
|
|
32906
|
+
const items = parsed[itemsKey];
|
|
32907
|
+
if (!Array.isArray(items)) return result;
|
|
32908
|
+
const sorted = sortNewestFirst(items);
|
|
32909
|
+
const out = { ...parsed, [itemsKey]: sorted };
|
|
32910
|
+
if (hasMorePages(parsed)) {
|
|
32911
|
+
const count = queryIsExact ? await countMatches(method, itemsKey, query, account) : {};
|
|
32912
|
+
annotateTruncation(out, sorted.length, count);
|
|
32913
|
+
}
|
|
32914
|
+
return rawTextResult(JSON.stringify(out));
|
|
32915
|
+
}
|
|
32916
|
+
async function fetchGmailPages(runPage, itemsKey, maxPages, startToken) {
|
|
32917
|
+
const merged = [];
|
|
32918
|
+
let base;
|
|
32919
|
+
let token = startToken;
|
|
32920
|
+
for (let pages = 0; pages < maxPages; pages++) {
|
|
32921
|
+
const result = await runPage(token);
|
|
32922
|
+
const parsed = parsePage(result, itemsKey);
|
|
32923
|
+
if (parsed === void 0) {
|
|
32924
|
+
return base === void 0 ? result : finish(base, itemsKey, merged, token);
|
|
32925
|
+
}
|
|
32926
|
+
base = parsed;
|
|
32927
|
+
merged.push(...parsed[itemsKey]);
|
|
32928
|
+
token = typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "" ? parsed.nextPageToken : void 0;
|
|
32929
|
+
if (token === void 0) break;
|
|
32930
|
+
}
|
|
32931
|
+
return finish(base, itemsKey, merged, token);
|
|
32932
|
+
}
|
|
32933
|
+
function parsePage(result, itemsKey) {
|
|
32934
|
+
const first = result.content[0];
|
|
32935
|
+
if (result.isError || first?.type !== "text") return void 0;
|
|
32936
|
+
let parsed;
|
|
32937
|
+
try {
|
|
32938
|
+
parsed = JSON.parse(first.text);
|
|
32939
|
+
} catch {
|
|
32940
|
+
return void 0;
|
|
32941
|
+
}
|
|
32942
|
+
if (parsed === null || typeof parsed !== "object") return void 0;
|
|
32943
|
+
const obj = parsed;
|
|
32944
|
+
return Array.isArray(obj[itemsKey]) ? obj : void 0;
|
|
32945
|
+
}
|
|
32946
|
+
function finish(base, itemsKey, merged, token) {
|
|
32947
|
+
const out = { ...base, [itemsKey]: merged };
|
|
32948
|
+
if (token === void 0) delete out.nextPageToken;
|
|
32949
|
+
else out.nextPageToken = token;
|
|
32950
|
+
return rawTextResult(JSON.stringify(out));
|
|
32951
|
+
}
|
|
32952
|
+
|
|
32761
32953
|
// src/tools/gmail.ts
|
|
32762
32954
|
function registerGmailTools(server) {
|
|
32763
32955
|
server.registerTool("gog_gmail_search", {
|
|
32764
|
-
description:
|
|
32956
|
+
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.',
|
|
32765
32957
|
annotations: { readOnlyHint: true },
|
|
32766
32958
|
inputSchema: {
|
|
32767
32959
|
query: external_exports.string().describe("Gmail search query"),
|
|
32768
32960
|
max: external_exports.number().int().optional().describe("Max results to return (default: 10)"),
|
|
32961
|
+
pageToken: pageTokenParam,
|
|
32962
|
+
page: pageAliasParam,
|
|
32963
|
+
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.'),
|
|
32964
|
+
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.'),
|
|
32769
32965
|
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."),
|
|
32770
32966
|
account: accountParam
|
|
32771
32967
|
}
|
|
32772
|
-
}, async ({ query, max, fromContact, account }) => {
|
|
32968
|
+
}, async ({ query, max, pageToken, page, maxPages, all, fromContact, account }) => {
|
|
32773
32969
|
const args = ["gmail", "search", query];
|
|
32774
32970
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32971
|
+
if (all) args.push("--all");
|
|
32775
32972
|
if (fromContact) args.push(`--from-contact=${fromContact}`);
|
|
32776
|
-
|
|
32973
|
+
const runPage = (tok) => runOrDiagnose(tok ? [...args, `--page=${tok}`] : args, { account });
|
|
32974
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32975
|
+
const result = maxPages !== void 0 ? await fetchGmailPages(runPage, "threads", maxPages, token) : await runPage(token);
|
|
32976
|
+
return finalizeGmailSearch(result, {
|
|
32977
|
+
itemsKey: "threads",
|
|
32978
|
+
method: "users.threads.list",
|
|
32979
|
+
query,
|
|
32980
|
+
account,
|
|
32981
|
+
// --from-contact is expanded INSIDE gog, against the People API, so the
|
|
32982
|
+
// query Gmail actually saw is not the one we hold here.
|
|
32983
|
+
queryIsExact: !fromContact
|
|
32984
|
+
});
|
|
32777
32985
|
});
|
|
32778
32986
|
server.registerTool("gog_gmail_get", {
|
|
32779
|
-
description: "Get a Gmail message by ID.",
|
|
32987
|
+
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.",
|
|
32780
32988
|
annotations: { readOnlyHint: true },
|
|
32781
32989
|
inputSchema: {
|
|
32782
32990
|
messageId: external_exports.string().describe("Message ID"),
|
|
32783
32991
|
format: external_exports.enum(["full", "metadata", "raw"]).optional().describe("Message format (default: full)"),
|
|
32992
|
+
// Requires gog >= 0.37.0. Before that (openclaw/gogcli#992) the JSON
|
|
32993
|
+
// carried the headers and body TWICE — once inside `message`, once
|
|
32994
|
+
// copied to the top level — so the flag meant to shrink the payload
|
|
32995
|
+
// enlarged it. MIN_GOG_VERSION is the guard; there is no runtime check.
|
|
32996
|
+
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."),
|
|
32784
32997
|
account: accountParam
|
|
32785
32998
|
}
|
|
32786
|
-
}, async ({ messageId, format, account }) => {
|
|
32999
|
+
}, async ({ messageId, format, sanitizeContent, account }) => {
|
|
32787
33000
|
const args = ["gmail", "get", messageId];
|
|
32788
33001
|
if (format) args.push(`--format=${format}`);
|
|
33002
|
+
if (sanitizeContent) args.push("--sanitize-content");
|
|
32789
33003
|
return runOrDiagnose(args, { account });
|
|
32790
33004
|
});
|
|
32791
33005
|
server.registerTool("gog_gmail_send", {
|
|
@@ -33138,7 +33352,7 @@ function registerTasksTools(server) {
|
|
|
33138
33352
|
}
|
|
33139
33353
|
|
|
33140
33354
|
// src/server.ts
|
|
33141
|
-
var VERSION = true ? "2.
|
|
33355
|
+
var VERSION = true ? "2.24.0" : "0.0.0";
|
|
33142
33356
|
var BASE_TOOL_REGISTRARS = [
|
|
33143
33357
|
registerApiTools,
|
|
33144
33358
|
registerAuthTools,
|