gogcli-mcp 2.23.1 → 2.23.2
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 +234 -40
- package/dist/lib.js +243 -41
- package/manifest.json +3 -3
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/gmail-results.ts +228 -0
- package/src/lib.ts +9 -0
- package/src/pagination.ts +108 -0
- package/src/tools/calendar.ts +18 -5
- package/src/tools/classroom.ts +46 -28
- package/src/tools/drive.ts +6 -4
- package/src/tools/gmail.ts +29 -4
- 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/calendar.test.ts +51 -0
- package/tests/tools/gmail.test.ts +146 -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.23.
|
|
10
|
+
"version": "2.23.2"
|
|
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.23.
|
|
18
|
+
"version": "2.23.2",
|
|
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
|
}
|
|
@@ -31892,7 +31951,7 @@ function registerAuthTools(server) {
|
|
|
31892
31951
|
// src/tools/calendar.ts
|
|
31893
31952
|
function registerCalendarTools(server) {
|
|
31894
31953
|
server.registerTool("gog_calendar_events", {
|
|
31895
|
-
description:
|
|
31954
|
+
description: 'List calendar events. Filters can be combined (e.g. --from + --to for a range, or --today for just today). 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
31955
|
annotations: { readOnlyHint: true },
|
|
31897
31956
|
inputSchema: {
|
|
31898
31957
|
calendarId: external_exports.string().optional().describe("Calendar ID (default: primary calendar)"),
|
|
@@ -31900,22 +31959,29 @@ function registerCalendarTools(server) {
|
|
|
31900
31959
|
to: external_exports.string().optional().describe("End time filter (RFC3339, date, or natural language)"),
|
|
31901
31960
|
today: external_exports.boolean().optional().describe("Only show today's events"),
|
|
31902
31961
|
query: external_exports.string().optional().describe("Free text search within events"),
|
|
31903
|
-
|
|
31962
|
+
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."),
|
|
31963
|
+
pageToken: pageTokenParam,
|
|
31964
|
+
page: pageAliasParam,
|
|
31965
|
+
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
31966
|
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
31967
|
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
31968
|
account: accountParam
|
|
31907
31969
|
}
|
|
31908
|
-
}, async ({ calendarId, from, to, today, query, all, eventTypes, timezone, account }) => {
|
|
31970
|
+
}, async ({ calendarId, from, to, today, query, max, pageToken, page, all, eventTypes, timezone, account }) => {
|
|
31909
31971
|
const args = ["calendar", "events"];
|
|
31910
31972
|
if (calendarId) args.push(calendarId);
|
|
31911
31973
|
if (from) args.push(`--from=${from}`);
|
|
31912
31974
|
if (to) args.push(`--to=${to}`);
|
|
31913
31975
|
if (today) args.push("--today");
|
|
31914
31976
|
if (query) args.push(`--query=${query}`);
|
|
31977
|
+
if (max !== void 0) args.push(`--max=${max}`);
|
|
31978
|
+
const token = resolvePageToken({ pageToken, page });
|
|
31979
|
+
if (token) args.push(`--page=${token}`);
|
|
31915
31980
|
if (all) args.push("--all");
|
|
31916
31981
|
if (eventTypes) for (const t of eventTypes) args.push(`--event-types=${t}`);
|
|
31917
31982
|
if (timezone) args.push(`--timezone=${timezone}`);
|
|
31918
|
-
|
|
31983
|
+
const result = await runOrDiagnose(args, { account });
|
|
31984
|
+
return annotateTruncatedList(result, "events");
|
|
31919
31985
|
});
|
|
31920
31986
|
server.registerTool("gog_calendar_get", {
|
|
31921
31987
|
description: "Get a specific calendar event by ID.",
|
|
@@ -32032,17 +32098,19 @@ function registerClassroomTools(server) {
|
|
|
32032
32098
|
teacher: external_exports.string().optional().describe("Filter by teacher user ID"),
|
|
32033
32099
|
student: external_exports.string().optional().describe("Filter by student user ID"),
|
|
32034
32100
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32035
|
-
|
|
32101
|
+
pageToken: pageTokenParam,
|
|
32102
|
+
page: pageAliasParam,
|
|
32036
32103
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32037
32104
|
account: accountParam
|
|
32038
32105
|
}
|
|
32039
|
-
}, async ({ state, teacher, student, max, page, all, account }) => {
|
|
32106
|
+
}, async ({ state, teacher, student, max, pageToken, page, all, account }) => {
|
|
32040
32107
|
const args = ["classroom", "courses", "list"];
|
|
32041
32108
|
if (state) args.push(`--state=${state}`);
|
|
32042
32109
|
if (teacher) args.push(`--teacher=${teacher}`);
|
|
32043
32110
|
if (student) args.push(`--student=${student}`);
|
|
32044
32111
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32045
|
-
|
|
32112
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32113
|
+
if (token) args.push(`--page=${token}`);
|
|
32046
32114
|
if (all) args.push("--all");
|
|
32047
32115
|
return runOrDiagnose(args, { account });
|
|
32048
32116
|
});
|
|
@@ -32062,14 +32130,16 @@ function registerClassroomTools(server) {
|
|
|
32062
32130
|
inputSchema: {
|
|
32063
32131
|
courseId: external_exports.string().describe("Course ID"),
|
|
32064
32132
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32065
|
-
|
|
32133
|
+
pageToken: pageTokenParam,
|
|
32134
|
+
page: pageAliasParam,
|
|
32066
32135
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32067
32136
|
account: accountParam
|
|
32068
32137
|
}
|
|
32069
|
-
}, async ({ courseId, max, page, all, account }) => {
|
|
32138
|
+
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
32070
32139
|
const args = ["classroom", "students", "list", courseId];
|
|
32071
32140
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32072
|
-
|
|
32141
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32142
|
+
if (token) args.push(`--page=${token}`);
|
|
32073
32143
|
if (all) args.push("--all");
|
|
32074
32144
|
return runOrDiagnose(args, { account });
|
|
32075
32145
|
});
|
|
@@ -32090,14 +32160,16 @@ function registerClassroomTools(server) {
|
|
|
32090
32160
|
inputSchema: {
|
|
32091
32161
|
courseId: external_exports.string().describe("Course ID"),
|
|
32092
32162
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32093
|
-
|
|
32163
|
+
pageToken: pageTokenParam,
|
|
32164
|
+
page: pageAliasParam,
|
|
32094
32165
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32095
32166
|
account: accountParam
|
|
32096
32167
|
}
|
|
32097
|
-
}, async ({ courseId, max, page, all, account }) => {
|
|
32168
|
+
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
32098
32169
|
const args = ["classroom", "teachers", "list", courseId];
|
|
32099
32170
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32100
|
-
|
|
32171
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32172
|
+
if (token) args.push(`--page=${token}`);
|
|
32101
32173
|
if (all) args.push("--all");
|
|
32102
32174
|
return runOrDiagnose(args, { account });
|
|
32103
32175
|
});
|
|
@@ -32120,16 +32192,18 @@ function registerClassroomTools(server) {
|
|
|
32120
32192
|
students: external_exports.boolean().optional().describe("Include students only"),
|
|
32121
32193
|
teachers: external_exports.boolean().optional().describe("Include teachers only"),
|
|
32122
32194
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32123
|
-
|
|
32195
|
+
pageToken: pageTokenParam,
|
|
32196
|
+
page: pageAliasParam,
|
|
32124
32197
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32125
32198
|
account: accountParam
|
|
32126
32199
|
}
|
|
32127
|
-
}, async ({ courseId, students, teachers, max, page, all, account }) => {
|
|
32200
|
+
}, async ({ courseId, students, teachers, max, pageToken, page, all, account }) => {
|
|
32128
32201
|
const args = ["classroom", "roster", courseId];
|
|
32129
32202
|
if (students) args.push("--students");
|
|
32130
32203
|
if (teachers) args.push("--teachers");
|
|
32131
32204
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32132
|
-
|
|
32205
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32206
|
+
if (token) args.push(`--page=${token}`);
|
|
32133
32207
|
if (all) args.push("--all");
|
|
32134
32208
|
return runOrDiagnose(args, { account });
|
|
32135
32209
|
});
|
|
@@ -32142,18 +32216,20 @@ function registerClassroomTools(server) {
|
|
|
32142
32216
|
topic: external_exports.string().optional().describe("Filter by topic ID"),
|
|
32143
32217
|
orderBy: external_exports.string().optional().describe('Sort order (e.g. "updateTime desc")'),
|
|
32144
32218
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32145
|
-
|
|
32219
|
+
pageToken: pageTokenParam,
|
|
32220
|
+
page: pageAliasParam,
|
|
32146
32221
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32147
32222
|
scanPages: external_exports.number().optional().describe("Max pages to scan when filtering"),
|
|
32148
32223
|
account: accountParam
|
|
32149
32224
|
}
|
|
32150
|
-
}, async ({ courseId, state, topic, orderBy, max, page, all, scanPages, account }) => {
|
|
32225
|
+
}, async ({ courseId, state, topic, orderBy, max, pageToken, page, all, scanPages, account }) => {
|
|
32151
32226
|
const args = ["classroom", "coursework", "list", courseId];
|
|
32152
32227
|
if (state) args.push(`--state=${state}`);
|
|
32153
32228
|
if (topic) args.push(`--topic=${topic}`);
|
|
32154
32229
|
if (orderBy) args.push(`--order-by=${orderBy}`);
|
|
32155
32230
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32156
|
-
|
|
32231
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32232
|
+
if (token) args.push(`--page=${token}`);
|
|
32157
32233
|
if (all) args.push("--all");
|
|
32158
32234
|
if (scanPages !== void 0) args.push(`--scan-pages=${scanPages}`);
|
|
32159
32235
|
return runOrDiagnose(args, { account });
|
|
@@ -32179,17 +32255,19 @@ function registerClassroomTools(server) {
|
|
|
32179
32255
|
late: external_exports.enum(["late", "not-late"]).optional().describe("Filter by late status"),
|
|
32180
32256
|
user: external_exports.string().optional().describe("Filter by student user ID"),
|
|
32181
32257
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32182
|
-
|
|
32258
|
+
pageToken: pageTokenParam,
|
|
32259
|
+
page: pageAliasParam,
|
|
32183
32260
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32184
32261
|
account: accountParam
|
|
32185
32262
|
}
|
|
32186
|
-
}, async ({ courseId, courseworkId, state, late: late2, user, max, page, all, account }) => {
|
|
32263
|
+
}, async ({ courseId, courseworkId, state, late: late2, user, max, pageToken, page, all, account }) => {
|
|
32187
32264
|
const args = ["classroom", "submissions", "list", courseId, courseworkId];
|
|
32188
32265
|
if (state) args.push(`--state=${state}`);
|
|
32189
32266
|
if (late2) args.push(`--late=${late2}`);
|
|
32190
32267
|
if (user) args.push(`--user=${user}`);
|
|
32191
32268
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32192
|
-
|
|
32269
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32270
|
+
if (token) args.push(`--page=${token}`);
|
|
32193
32271
|
if (all) args.push("--all");
|
|
32194
32272
|
return runOrDiagnose(args, { account });
|
|
32195
32273
|
});
|
|
@@ -32266,16 +32344,18 @@ function registerClassroomTools(server) {
|
|
|
32266
32344
|
state: external_exports.string().optional().describe("Filter by announcement state"),
|
|
32267
32345
|
orderBy: external_exports.string().optional().describe("Sort order"),
|
|
32268
32346
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32269
|
-
|
|
32347
|
+
pageToken: pageTokenParam,
|
|
32348
|
+
page: pageAliasParam,
|
|
32270
32349
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32271
32350
|
account: accountParam
|
|
32272
32351
|
}
|
|
32273
|
-
}, async ({ courseId, state, orderBy, max, page, all, account }) => {
|
|
32352
|
+
}, async ({ courseId, state, orderBy, max, pageToken, page, all, account }) => {
|
|
32274
32353
|
const args = ["classroom", "announcements", "list", courseId];
|
|
32275
32354
|
if (state) args.push(`--state=${state}`);
|
|
32276
32355
|
if (orderBy) args.push(`--order-by=${orderBy}`);
|
|
32277
32356
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32278
|
-
|
|
32357
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32358
|
+
if (token) args.push(`--page=${token}`);
|
|
32279
32359
|
if (all) args.push("--all");
|
|
32280
32360
|
return runOrDiagnose(args, { account });
|
|
32281
32361
|
});
|
|
@@ -32311,14 +32391,16 @@ function registerClassroomTools(server) {
|
|
|
32311
32391
|
inputSchema: {
|
|
32312
32392
|
courseId: external_exports.string().describe("Course ID"),
|
|
32313
32393
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32314
|
-
|
|
32394
|
+
pageToken: pageTokenParam,
|
|
32395
|
+
page: pageAliasParam,
|
|
32315
32396
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32316
32397
|
account: accountParam
|
|
32317
32398
|
}
|
|
32318
|
-
}, async ({ courseId, max, page, all, account }) => {
|
|
32399
|
+
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
32319
32400
|
const args = ["classroom", "topics", "list", courseId];
|
|
32320
32401
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32321
|
-
|
|
32402
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32403
|
+
if (token) args.push(`--page=${token}`);
|
|
32322
32404
|
if (all) args.push("--all");
|
|
32323
32405
|
return runOrDiagnose(args, { account });
|
|
32324
32406
|
});
|
|
@@ -32340,16 +32422,18 @@ function registerClassroomTools(server) {
|
|
|
32340
32422
|
course: external_exports.string().optional().describe("Filter by course ID"),
|
|
32341
32423
|
user: external_exports.string().optional().describe("Filter by user ID"),
|
|
32342
32424
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
32343
|
-
|
|
32425
|
+
pageToken: pageTokenParam,
|
|
32426
|
+
page: pageAliasParam,
|
|
32344
32427
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
32345
32428
|
account: accountParam
|
|
32346
32429
|
}
|
|
32347
|
-
}, async ({ course, user, max, page, all, account }) => {
|
|
32430
|
+
}, async ({ course, user, max, pageToken, page, all, account }) => {
|
|
32348
32431
|
const args = ["classroom", "invitations", "list"];
|
|
32349
32432
|
if (course) args.push(`--course=${course}`);
|
|
32350
32433
|
if (user) args.push(`--user=${user}`);
|
|
32351
32434
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32352
|
-
|
|
32435
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32436
|
+
if (token) args.push(`--page=${token}`);
|
|
32353
32437
|
if (all) args.push("--all");
|
|
32354
32438
|
return runOrDiagnose(args, { account });
|
|
32355
32439
|
});
|
|
@@ -32559,16 +32643,18 @@ function registerDriveTools(server) {
|
|
|
32559
32643
|
inputSchema: {
|
|
32560
32644
|
folderId: external_exports.string().optional().describe("Folder ID to list (default: root)"),
|
|
32561
32645
|
max: external_exports.number().optional().describe("Max results (default: 20)"),
|
|
32562
|
-
|
|
32646
|
+
pageToken: pageTokenParam,
|
|
32647
|
+
page: pageAliasParam,
|
|
32563
32648
|
query: external_exports.string().optional().describe(`Drive query filter (e.g. "name contains 'budget'")`),
|
|
32564
32649
|
allDrives: external_exports.boolean().optional().describe("Include shared drives (default: true). Set false for My Drive only."),
|
|
32565
32650
|
account: accountParam
|
|
32566
32651
|
}
|
|
32567
|
-
}, async ({ folderId, max, page, query, allDrives, account }) => {
|
|
32652
|
+
}, async ({ folderId, max, pageToken, page, query, allDrives, account }) => {
|
|
32568
32653
|
const args = ["drive", "ls"];
|
|
32569
32654
|
if (folderId) args.push(`--parent=${folderId}`);
|
|
32570
32655
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32571
|
-
|
|
32656
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32657
|
+
if (token) args.push(`--page=${token}`);
|
|
32572
32658
|
if (query) args.push(`--query=${query}`);
|
|
32573
32659
|
if (allDrives === false) args.push("--no-all-drives");
|
|
32574
32660
|
return runOrDiagnose(args, { account });
|
|
@@ -32758,22 +32844,130 @@ function registerDriveTools(server) {
|
|
|
32758
32844
|
registerRunTool(server, { service: "drive", examples: '"copy", "upload", "download", "permissions"' });
|
|
32759
32845
|
}
|
|
32760
32846
|
|
|
32847
|
+
// src/gmail-results.ts
|
|
32848
|
+
function sortKey(item) {
|
|
32849
|
+
for (const raw of [item.internalDateIso, item.date]) {
|
|
32850
|
+
if (typeof raw !== "string" || !raw) continue;
|
|
32851
|
+
const t = Date.parse(raw);
|
|
32852
|
+
if (!Number.isNaN(t)) return t;
|
|
32853
|
+
}
|
|
32854
|
+
return Number.NEGATIVE_INFINITY;
|
|
32855
|
+
}
|
|
32856
|
+
function sortNewestFirst(items) {
|
|
32857
|
+
return [...items].sort((a, b) => {
|
|
32858
|
+
const ka = sortKey(a);
|
|
32859
|
+
const kb = sortKey(b);
|
|
32860
|
+
return ka === kb ? 0 : kb - ka;
|
|
32861
|
+
});
|
|
32862
|
+
}
|
|
32863
|
+
var COUNT_PROBE_PAGE_SIZE = 500;
|
|
32864
|
+
async function countMatches(method, itemsKey, query, account) {
|
|
32865
|
+
try {
|
|
32866
|
+
const params = JSON.stringify({
|
|
32867
|
+
userId: "me",
|
|
32868
|
+
q: query,
|
|
32869
|
+
maxResults: COUNT_PROBE_PAGE_SIZE,
|
|
32870
|
+
fields: `${itemsKey}/id,nextPageToken`
|
|
32871
|
+
});
|
|
32872
|
+
const raw = await run(["api", "call", "gmail", "v1", method, `--params=${params}`], { account });
|
|
32873
|
+
const parsed = JSON.parse(raw);
|
|
32874
|
+
const items = parsed[itemsKey];
|
|
32875
|
+
if (!Array.isArray(items)) return {};
|
|
32876
|
+
const more = typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "";
|
|
32877
|
+
return more ? { atLeast: items.length } : { total: items.length };
|
|
32878
|
+
} catch {
|
|
32879
|
+
return {};
|
|
32880
|
+
}
|
|
32881
|
+
}
|
|
32882
|
+
async function finalizeGmailSearch(result, options) {
|
|
32883
|
+
const { itemsKey, method, query, account, queryIsExact = true } = options;
|
|
32884
|
+
const first = result.content[0];
|
|
32885
|
+
if (result.isError || first?.type !== "text") return result;
|
|
32886
|
+
let parsed;
|
|
32887
|
+
try {
|
|
32888
|
+
parsed = JSON.parse(first.text);
|
|
32889
|
+
} catch {
|
|
32890
|
+
return result;
|
|
32891
|
+
}
|
|
32892
|
+
const items = parsed[itemsKey];
|
|
32893
|
+
if (!Array.isArray(items)) return result;
|
|
32894
|
+
const sorted = sortNewestFirst(items);
|
|
32895
|
+
const out = { ...parsed, [itemsKey]: sorted };
|
|
32896
|
+
if (hasMorePages(parsed)) {
|
|
32897
|
+
const count = queryIsExact ? await countMatches(method, itemsKey, query, account) : {};
|
|
32898
|
+
annotateTruncation(out, sorted.length, count);
|
|
32899
|
+
}
|
|
32900
|
+
return rawTextResult(JSON.stringify(out));
|
|
32901
|
+
}
|
|
32902
|
+
async function fetchGmailPages(runPage, itemsKey, maxPages, startToken) {
|
|
32903
|
+
const merged = [];
|
|
32904
|
+
let base;
|
|
32905
|
+
let token = startToken;
|
|
32906
|
+
for (let pages = 0; pages < maxPages; pages++) {
|
|
32907
|
+
const result = await runPage(token);
|
|
32908
|
+
const parsed = parsePage(result, itemsKey);
|
|
32909
|
+
if (parsed === void 0) {
|
|
32910
|
+
return base === void 0 ? result : finish(base, itemsKey, merged, token);
|
|
32911
|
+
}
|
|
32912
|
+
base = parsed;
|
|
32913
|
+
merged.push(...parsed[itemsKey]);
|
|
32914
|
+
token = typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "" ? parsed.nextPageToken : void 0;
|
|
32915
|
+
if (token === void 0) break;
|
|
32916
|
+
}
|
|
32917
|
+
return finish(base, itemsKey, merged, token);
|
|
32918
|
+
}
|
|
32919
|
+
function parsePage(result, itemsKey) {
|
|
32920
|
+
const first = result.content[0];
|
|
32921
|
+
if (result.isError || first?.type !== "text") return void 0;
|
|
32922
|
+
let parsed;
|
|
32923
|
+
try {
|
|
32924
|
+
parsed = JSON.parse(first.text);
|
|
32925
|
+
} catch {
|
|
32926
|
+
return void 0;
|
|
32927
|
+
}
|
|
32928
|
+
if (parsed === null || typeof parsed !== "object") return void 0;
|
|
32929
|
+
const obj = parsed;
|
|
32930
|
+
return Array.isArray(obj[itemsKey]) ? obj : void 0;
|
|
32931
|
+
}
|
|
32932
|
+
function finish(base, itemsKey, merged, token) {
|
|
32933
|
+
const out = { ...base, [itemsKey]: merged };
|
|
32934
|
+
if (token === void 0) delete out.nextPageToken;
|
|
32935
|
+
else out.nextPageToken = token;
|
|
32936
|
+
return rawTextResult(JSON.stringify(out));
|
|
32937
|
+
}
|
|
32938
|
+
|
|
32761
32939
|
// src/tools/gmail.ts
|
|
32762
32940
|
function registerGmailTools(server) {
|
|
32763
32941
|
server.registerTool("gog_gmail_search", {
|
|
32764
|
-
description:
|
|
32942
|
+
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
32943
|
annotations: { readOnlyHint: true },
|
|
32766
32944
|
inputSchema: {
|
|
32767
32945
|
query: external_exports.string().describe("Gmail search query"),
|
|
32768
32946
|
max: external_exports.number().int().optional().describe("Max results to return (default: 10)"),
|
|
32947
|
+
pageToken: pageTokenParam,
|
|
32948
|
+
page: pageAliasParam,
|
|
32949
|
+
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.'),
|
|
32950
|
+
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
32951
|
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
32952
|
account: accountParam
|
|
32771
32953
|
}
|
|
32772
|
-
}, async ({ query, max, fromContact, account }) => {
|
|
32954
|
+
}, async ({ query, max, pageToken, page, maxPages, all, fromContact, account }) => {
|
|
32773
32955
|
const args = ["gmail", "search", query];
|
|
32774
32956
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
32957
|
+
if (all) args.push("--all");
|
|
32775
32958
|
if (fromContact) args.push(`--from-contact=${fromContact}`);
|
|
32776
|
-
|
|
32959
|
+
const runPage = (tok) => runOrDiagnose(tok ? [...args, `--page=${tok}`] : args, { account });
|
|
32960
|
+
const token = resolvePageToken({ pageToken, page });
|
|
32961
|
+
const result = maxPages !== void 0 ? await fetchGmailPages(runPage, "threads", maxPages, token) : await runPage(token);
|
|
32962
|
+
return finalizeGmailSearch(result, {
|
|
32963
|
+
itemsKey: "threads",
|
|
32964
|
+
method: "users.threads.list",
|
|
32965
|
+
query,
|
|
32966
|
+
account,
|
|
32967
|
+
// --from-contact is expanded INSIDE gog, against the People API, so the
|
|
32968
|
+
// query Gmail actually saw is not the one we hold here.
|
|
32969
|
+
queryIsExact: !fromContact
|
|
32970
|
+
});
|
|
32777
32971
|
});
|
|
32778
32972
|
server.registerTool("gog_gmail_get", {
|
|
32779
32973
|
description: "Get a Gmail message by ID.",
|
|
@@ -33138,7 +33332,7 @@ function registerTasksTools(server) {
|
|
|
33138
33332
|
}
|
|
33139
33333
|
|
|
33140
33334
|
// src/server.ts
|
|
33141
|
-
var VERSION = true ? "2.23.
|
|
33335
|
+
var VERSION = true ? "2.23.2" : "0.0.0";
|
|
33142
33336
|
var BASE_TOOL_REGISTRARS = [
|
|
33143
33337
|
registerApiTools,
|
|
33144
33338
|
registerAuthTools,
|