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
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23725
|
-
|
|
23726
|
-
|
|
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
|
-
|
|
23747
|
-
|
|
23748
|
-
|
|
23749
|
-
|
|
23750
|
-
|
|
23751
|
-
|
|
23752
|
-
|
|
23753
|
-
|
|
23754
|
-
|
|
23755
|
-
|
|
23756
|
-
|
|
23757
|
-
|
|
23758
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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
|
};
|