gogcli-mcp-docs 2.18.3 → 2.18.4
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 +196 -3
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/tools/docs-extra.ts +2 -1
- package/tests/tools/docs-extra.test.ts +4 -4
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "Extended Google Docs for Claude via gogcli — auth + full Docs and comments support",
|
|
10
|
-
"version": "2.18.
|
|
10
|
+
"version": "2.18.4"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "gogcli (Docs)",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "Extended Google Docs for Claude via gogcli — auth + full Docs and comments support",
|
|
18
|
-
"version": "2.18.
|
|
18
|
+
"version": "2.18.4",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/dist/index.js
CHANGED
|
@@ -31275,6 +31275,198 @@ async function run(args, options = {}) {
|
|
|
31275
31275
|
}
|
|
31276
31276
|
}
|
|
31277
31277
|
|
|
31278
|
+
// ../gogcli-mcp/src/timestamps.ts
|
|
31279
|
+
var DEFAULT_DISPLAY_TZ = "America/New_York";
|
|
31280
|
+
function isValidTimeZone(tz) {
|
|
31281
|
+
try {
|
|
31282
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
31283
|
+
return true;
|
|
31284
|
+
} catch {
|
|
31285
|
+
return false;
|
|
31286
|
+
}
|
|
31287
|
+
}
|
|
31288
|
+
function displayTimeZone() {
|
|
31289
|
+
const configured = readEnvVar("DISPLAY_TZ");
|
|
31290
|
+
if (configured && isValidTimeZone(configured)) return configured;
|
|
31291
|
+
return DEFAULT_DISPLAY_TZ;
|
|
31292
|
+
}
|
|
31293
|
+
function naiveSourceTimeZone() {
|
|
31294
|
+
const configured = readEnvVar("GOG_TIMEZONE");
|
|
31295
|
+
if (configured && isValidTimeZone(configured)) return configured;
|
|
31296
|
+
return displayTimeZone();
|
|
31297
|
+
}
|
|
31298
|
+
function offsetAt(instant, tz) {
|
|
31299
|
+
const w = wallPartsIn(instant, tz);
|
|
31300
|
+
const asUTC = Date.UTC(w.year, w.month - 1, w.day, w.hour, w.minute, w.second, instant.getUTCMilliseconds());
|
|
31301
|
+
const minutes = Math.round((asUTC - instant.getTime()) / 6e4);
|
|
31302
|
+
const sign = minutes < 0 ? "-" : "+";
|
|
31303
|
+
const abs = Math.abs(minutes);
|
|
31304
|
+
return `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
|
|
31305
|
+
}
|
|
31306
|
+
function wallPartsIn(instant, tz) {
|
|
31307
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
31308
|
+
timeZone: tz,
|
|
31309
|
+
year: "numeric",
|
|
31310
|
+
month: "2-digit",
|
|
31311
|
+
day: "2-digit",
|
|
31312
|
+
hour: "2-digit",
|
|
31313
|
+
minute: "2-digit",
|
|
31314
|
+
second: "2-digit",
|
|
31315
|
+
// h23 pins midnight to hour 00; without it some ICU builds render hour 24.
|
|
31316
|
+
hourCycle: "h23"
|
|
31317
|
+
}).formatToParts(instant);
|
|
31318
|
+
const out = {};
|
|
31319
|
+
for (const p of parts) {
|
|
31320
|
+
if (p.type !== "literal") out[p.type] = Number(p.value);
|
|
31321
|
+
}
|
|
31322
|
+
return out;
|
|
31323
|
+
}
|
|
31324
|
+
function wallTimeToInstant(y, mo, d, h, mi, s, ms, tz) {
|
|
31325
|
+
let guess = Date.UTC(y, mo - 1, d, h, mi, s, ms);
|
|
31326
|
+
for (let i = 0; i < 2; i += 1) {
|
|
31327
|
+
const seen = wallPartsIn(new Date(guess), tz);
|
|
31328
|
+
const seenUTC = Date.UTC(seen.year, seen.month - 1, seen.day, seen.hour, seen.minute, seen.second, ms);
|
|
31329
|
+
const drift = Date.UTC(y, mo - 1, d, h, mi, s, ms) - seenUTC;
|
|
31330
|
+
if (drift === 0) break;
|
|
31331
|
+
guess += drift;
|
|
31332
|
+
}
|
|
31333
|
+
return new Date(guess);
|
|
31334
|
+
}
|
|
31335
|
+
function pad(n, width = 2) {
|
|
31336
|
+
return String(n).padStart(width, "0");
|
|
31337
|
+
}
|
|
31338
|
+
function isoWithOffset(instant, tz, offset) {
|
|
31339
|
+
const w = wallPartsIn(instant, tz);
|
|
31340
|
+
const msPart = instant.getUTCMilliseconds();
|
|
31341
|
+
const frac = msPart ? `.${pad(msPart, 3)}` : "";
|
|
31342
|
+
return `${pad(w.year, 4)}-${pad(w.month)}-${pad(w.day)}T${pad(w.hour)}:${pad(w.minute)}:${pad(w.second)}${frac}${offset}`;
|
|
31343
|
+
}
|
|
31344
|
+
function formatInstant(instant, tz = displayTimeZone()) {
|
|
31345
|
+
const offset = offsetAt(instant, tz);
|
|
31346
|
+
const display = new Intl.DateTimeFormat("en-US", {
|
|
31347
|
+
timeZone: tz,
|
|
31348
|
+
weekday: "short",
|
|
31349
|
+
month: "short",
|
|
31350
|
+
day: "numeric",
|
|
31351
|
+
year: "numeric",
|
|
31352
|
+
hour: "numeric",
|
|
31353
|
+
minute: "2-digit",
|
|
31354
|
+
timeZoneName: "short"
|
|
31355
|
+
}).format(instant);
|
|
31356
|
+
return { iso: isoWithOffset(instant, tz, offset), display };
|
|
31357
|
+
}
|
|
31358
|
+
var TIMESTAMP_KEYS = /* @__PURE__ */ new Set([
|
|
31359
|
+
"date",
|
|
31360
|
+
// gog gmail message/thread listings ("2026-07-28 03:36")
|
|
31361
|
+
"dateTime",
|
|
31362
|
+
// Calendar event start/end
|
|
31363
|
+
"internalDate",
|
|
31364
|
+
// Gmail, epoch milliseconds (authoritative)
|
|
31365
|
+
"modifiedTime",
|
|
31366
|
+
// Drive
|
|
31367
|
+
"createdTime",
|
|
31368
|
+
// Drive
|
|
31369
|
+
"createTime",
|
|
31370
|
+
"updateTime",
|
|
31371
|
+
"updated",
|
|
31372
|
+
"originalStartTime",
|
|
31373
|
+
"expirationTime",
|
|
31374
|
+
"lastModified",
|
|
31375
|
+
"sentAt",
|
|
31376
|
+
"viewedAt",
|
|
31377
|
+
"modifiedAt",
|
|
31378
|
+
"fetchedBodyAt",
|
|
31379
|
+
"asOf"
|
|
31380
|
+
]);
|
|
31381
|
+
var ZONE_NAME_KEYS = /* @__PURE__ */ new Set(["timeZone", "timezone"]);
|
|
31382
|
+
var RFC3339_WITH_OFFSET = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2}))?(?:\.(\d{1,9}))?(Z|[+-]\d{2}:?\d{2})$/;
|
|
31383
|
+
var NAIVE_DATE_TIME = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2}))?(?:\.(\d{1,9}))?$/;
|
|
31384
|
+
var EPOCH_MILLIS = /^\d{13}$/;
|
|
31385
|
+
var DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
31386
|
+
function isRealCalendarDate(p) {
|
|
31387
|
+
const utc = new Date(Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second));
|
|
31388
|
+
return utc.getUTCFullYear() === p.year && utc.getUTCMonth() === p.month - 1 && utc.getUTCDate() === p.day && utc.getUTCHours() === p.hour && utc.getUTCMinutes() === p.minute && utc.getUTCSeconds() === p.second;
|
|
31389
|
+
}
|
|
31390
|
+
function parseTimestampValue(key, value, assumeNaiveIn) {
|
|
31391
|
+
if (typeof value !== "string") return null;
|
|
31392
|
+
const raw = value.trim();
|
|
31393
|
+
if (raw === "" || DATE_ONLY.test(raw)) return null;
|
|
31394
|
+
if (key === "internalDate" && EPOCH_MILLIS.test(raw)) {
|
|
31395
|
+
return new Date(Number(raw));
|
|
31396
|
+
}
|
|
31397
|
+
if (RFC3339_WITH_OFFSET.test(raw)) {
|
|
31398
|
+
const parsed = new Date(raw.replace(" ", "T"));
|
|
31399
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
31400
|
+
}
|
|
31401
|
+
const naive = NAIVE_DATE_TIME.exec(raw);
|
|
31402
|
+
if (naive) {
|
|
31403
|
+
const [, y, mo, d, h, mi, s, frac] = naive;
|
|
31404
|
+
const ms = frac ? Number(frac.padEnd(3, "0").slice(0, 3)) : 0;
|
|
31405
|
+
const parts = {
|
|
31406
|
+
year: Number(y),
|
|
31407
|
+
month: Number(mo),
|
|
31408
|
+
day: Number(d),
|
|
31409
|
+
hour: Number(h),
|
|
31410
|
+
minute: Number(mi),
|
|
31411
|
+
second: Number(s ?? "0")
|
|
31412
|
+
};
|
|
31413
|
+
if (!isRealCalendarDate(parts)) return null;
|
|
31414
|
+
return wallTimeToInstant(
|
|
31415
|
+
parts.year,
|
|
31416
|
+
parts.month,
|
|
31417
|
+
parts.day,
|
|
31418
|
+
parts.hour,
|
|
31419
|
+
parts.minute,
|
|
31420
|
+
parts.second,
|
|
31421
|
+
ms,
|
|
31422
|
+
assumeNaiveIn
|
|
31423
|
+
);
|
|
31424
|
+
}
|
|
31425
|
+
return null;
|
|
31426
|
+
}
|
|
31427
|
+
function walk(node, tz, naiveTz) {
|
|
31428
|
+
let changed = false;
|
|
31429
|
+
if (Array.isArray(node)) {
|
|
31430
|
+
for (const item of node) {
|
|
31431
|
+
if (walk(item, tz, naiveTz)) changed = true;
|
|
31432
|
+
}
|
|
31433
|
+
return changed;
|
|
31434
|
+
}
|
|
31435
|
+
if (node === null || typeof node !== "object") return false;
|
|
31436
|
+
const obj = node;
|
|
31437
|
+
for (const key of Object.keys(obj)) {
|
|
31438
|
+
const value = obj[key];
|
|
31439
|
+
if (value !== null && typeof value === "object") {
|
|
31440
|
+
if (walk(value, tz, naiveTz)) changed = true;
|
|
31441
|
+
continue;
|
|
31442
|
+
}
|
|
31443
|
+
if (ZONE_NAME_KEYS.has(key) || !TIMESTAMP_KEYS.has(key)) continue;
|
|
31444
|
+
const instant = parseTimestampValue(key, value, naiveTz);
|
|
31445
|
+
if (!instant) continue;
|
|
31446
|
+
const { iso, display } = formatInstant(instant, tz);
|
|
31447
|
+
obj[key] = iso;
|
|
31448
|
+
obj[`${key}Display`] = display;
|
|
31449
|
+
changed = true;
|
|
31450
|
+
}
|
|
31451
|
+
return changed;
|
|
31452
|
+
}
|
|
31453
|
+
function detectIndent(text) {
|
|
31454
|
+
const match = /\n(\s+)\S/.exec(text);
|
|
31455
|
+
return match ? match[1].replace(/\t/g, " ").length : 0;
|
|
31456
|
+
}
|
|
31457
|
+
function normalizeTimestamps(text, tz = displayTimeZone(), naiveTz = naiveSourceTimeZone()) {
|
|
31458
|
+
const trimmed = text.trim();
|
|
31459
|
+
if (trimmed === "" || !/^[[{]/.test(trimmed)) return text;
|
|
31460
|
+
let parsed;
|
|
31461
|
+
try {
|
|
31462
|
+
parsed = JSON.parse(trimmed);
|
|
31463
|
+
} catch {
|
|
31464
|
+
return text;
|
|
31465
|
+
}
|
|
31466
|
+
if (!walk(parsed, tz, naiveTz)) return text;
|
|
31467
|
+
return JSON.stringify(parsed, null, detectIndent(text));
|
|
31468
|
+
}
|
|
31469
|
+
|
|
31278
31470
|
// ../gogcli-mcp/src/tools/utils.ts
|
|
31279
31471
|
var PAYLOAD_INLINE_MAX = 4096;
|
|
31280
31472
|
function payloadArg(inlineFlag, fileFlag, value, ext) {
|
|
@@ -31380,7 +31572,8 @@ ${accounts || "(none)"}${hint}`);
|
|
|
31380
31572
|
}
|
|
31381
31573
|
async function runOrDiagnose(args, options) {
|
|
31382
31574
|
try {
|
|
31383
|
-
|
|
31575
|
+
const raw = await run(args, options);
|
|
31576
|
+
return rawTextResult(options.lossless ? raw : normalizeTimestamps(raw));
|
|
31384
31577
|
} catch (err) {
|
|
31385
31578
|
return diagnose(err);
|
|
31386
31579
|
}
|
|
@@ -31660,7 +31853,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
|
|
|
31660
31853
|
);
|
|
31661
31854
|
|
|
31662
31855
|
// ../gogcli-mcp/src/server.ts
|
|
31663
|
-
var VERSION = true ? "2.18.
|
|
31856
|
+
var VERSION = true ? "2.18.4" : "0.0.0";
|
|
31664
31857
|
|
|
31665
31858
|
// src/tools/docs-extra.ts
|
|
31666
31859
|
function registerExtraDocsTools(server) {
|
|
@@ -31747,7 +31940,7 @@ function registerExtraDocsTools(server) {
|
|
|
31747
31940
|
const args2 = ["docs", "raw", docId, "--pretty"];
|
|
31748
31941
|
if (tab) args2.push(`--tab=${tab}`);
|
|
31749
31942
|
if (allTabs) args2.push("--all-tabs");
|
|
31750
|
-
return runOrDiagnose(args2, { account });
|
|
31943
|
+
return runOrDiagnose(args2, { account, lossless: true });
|
|
31751
31944
|
}
|
|
31752
31945
|
const args = ["docs", "cat", docId];
|
|
31753
31946
|
if (tab) args.push(`--tab=${tab}`);
|
package/manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"manifest_version": "0.3",
|
|
4
4
|
"name": "gogcli-mcp-docs",
|
|
5
5
|
"display_name": "gogcli (Docs)",
|
|
6
|
-
"version": "2.18.
|
|
6
|
+
"version": "2.18.4",
|
|
7
7
|
"description": "Extended Google Docs for Claude via gogcli — auth + full Docs and comments support",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Chris Hall",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp-docs",
|
|
3
|
-
"version": "2.18.
|
|
3
|
+
"version": "2.18.4",
|
|
4
4
|
"mcpName": "io.github.chrischall/gogcli-mcp-docs",
|
|
5
5
|
"description": "Extended Google Docs MCP server via gogcli — all base tools plus full Docs support",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
package/server.json
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"source": "github",
|
|
8
8
|
"subfolder": "packages/gogcli-mcp-docs"
|
|
9
9
|
},
|
|
10
|
-
"version": "2.18.
|
|
10
|
+
"version": "2.18.4",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "gogcli-mcp-docs",
|
|
15
|
-
"version": "2.18.
|
|
15
|
+
"version": "2.18.4",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|
package/src/tools/docs-extra.ts
CHANGED
|
@@ -91,7 +91,8 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
91
91
|
const args = ['docs', 'raw', docId, '--pretty'];
|
|
92
92
|
if (tab) args.push(`--tab=${tab}`);
|
|
93
93
|
if (allTabs) args.push('--all-tabs');
|
|
94
|
-
|
|
94
|
+
// Verbatim by contract: see the `lossless` note on runOrDiagnose.
|
|
95
|
+
return runOrDiagnose(args, { account, lossless: true });
|
|
95
96
|
}
|
|
96
97
|
const args = ['docs', 'cat', docId];
|
|
97
98
|
if (tab) args.push(`--tab=${tab}`);
|
|
@@ -748,7 +748,7 @@ describe('gog_docs_read', () => {
|
|
|
748
748
|
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
749
749
|
const harness = await setupHandlers();
|
|
750
750
|
await harness.callTool('gog_docs_read', { docId: 'd1', format: 'json' });
|
|
751
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'raw', 'd1', '--pretty'], { account: undefined });
|
|
751
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'raw', 'd1', '--pretty'], { account: undefined, lossless: true });
|
|
752
752
|
});
|
|
753
753
|
|
|
754
754
|
it('passes tab, allTabs, maxBytes in text mode', async () => {
|
|
@@ -772,7 +772,7 @@ describe('gog_docs_read', () => {
|
|
|
772
772
|
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
773
773
|
const harness = await setupHandlers();
|
|
774
774
|
await harness.callTool('gog_docs_read', { docId: 'd1', format: 'json', chips: true });
|
|
775
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'raw', 'd1', '--pretty'], { account: undefined });
|
|
775
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'raw', 'd1', '--pretty'], { account: undefined, lossless: true });
|
|
776
776
|
});
|
|
777
777
|
});
|
|
778
778
|
|
|
@@ -1576,14 +1576,14 @@ describe('gog_docs_read json tab targeting', () => {
|
|
|
1576
1576
|
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
1577
1577
|
const harness = await setupHandlers();
|
|
1578
1578
|
await harness.callTool('gog_docs_read', { docId: 'd1', format: 'json', tab: 'T' });
|
|
1579
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'raw', 'd1', '--pretty', '--tab=T'], { account: undefined });
|
|
1579
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'raw', 'd1', '--pretty', '--tab=T'], { account: undefined, lossless: true });
|
|
1580
1580
|
});
|
|
1581
1581
|
|
|
1582
1582
|
it('passes --all-tabs through to docs raw in json mode', async () => {
|
|
1583
1583
|
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
1584
1584
|
const harness = await setupHandlers();
|
|
1585
1585
|
await harness.callTool('gog_docs_read', { docId: 'd1', format: 'json', allTabs: true });
|
|
1586
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'raw', 'd1', '--pretty', '--all-tabs'], { account: undefined });
|
|
1586
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'raw', 'd1', '--pretty', '--all-tabs'], { account: undefined, lossless: true });
|
|
1587
1587
|
});
|
|
1588
1588
|
});
|
|
1589
1589
|
|