gogcli-mcp-slides 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.
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "Extended Google Slides for Claude via gogcli — auth + full Slides support (create, edit, export, templates, markdown)",
10
- "version": "2.18.3"
10
+ "version": "2.18.4"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -15,7 +15,7 @@
15
15
  "displayName": "gogcli (Slides)",
16
16
  "source": "./",
17
17
  "description": "Extended Google Slides for Claude via gogcli — auth + full Slides support (create, edit, export, templates, markdown)",
18
- "version": "2.18.3",
18
+ "version": "2.18.4",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gogcli-mcp-slides",
3
3
  "displayName": "gogcli (Slides)",
4
- "version": "2.18.3",
4
+ "version": "2.18.4",
5
5
  "description": "Extended Google Slides for Claude via gogcli — auth + full Slides support (create, edit, export, templates, markdown)",
6
6
  "author": {
7
7
  "name": "Chris Hall",
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) {
@@ -31375,7 +31567,8 @@ ${accounts || "(none)"}${hint}`);
31375
31567
  }
31376
31568
  async function runOrDiagnose(args, options) {
31377
31569
  try {
31378
- return rawTextResult(await run(args, options));
31570
+ const raw = await run(args, options);
31571
+ return rawTextResult(options.lossless ? raw : normalizeTimestamps(raw));
31379
31572
  } catch (err) {
31380
31573
  return diagnose(err);
31381
31574
  }
@@ -31639,7 +31832,7 @@ function registerSlidesTools(server) {
31639
31832
  }
31640
31833
 
31641
31834
  // ../gogcli-mcp/src/server.ts
31642
- var VERSION = true ? "2.18.3" : "0.0.0";
31835
+ var VERSION = true ? "2.18.4" : "0.0.0";
31643
31836
 
31644
31837
  // src/tools/slides-extra.ts
31645
31838
  function registerExtraSlidesTools(server) {
@@ -31784,7 +31977,7 @@ function registerExtraSlidesTools(server) {
31784
31977
  }, async ({ presentationId, pretty, account }) => {
31785
31978
  const args = ["slides", "raw", presentationId];
31786
31979
  if (pretty) args.push("--pretty");
31787
- return runOrDiagnose(args, { account });
31980
+ return runOrDiagnose(args, { account, lossless: true });
31788
31981
  });
31789
31982
  server.registerTool("gog_slides_locate", {
31790
31983
  description: "Locate literal text in a presentation's shapes and table cells, returning the containing element object IDs and UTF-16 ranges needed for gog_slides_style_text / gog_slides_link / gog_slides_bullets / gog_slides_insert_text. Read-only.",
package/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.3",
4
4
  "name": "gogcli-mcp-slides",
5
5
  "display_name": "gogcli (Slides)",
6
- "version": "2.18.3",
6
+ "version": "2.18.4",
7
7
  "description": "Extended Google Slides for Claude via gogcli — auth + full Slides support (create, edit, export, templates, markdown)",
8
8
  "author": {
9
9
  "name": "Chris Hall",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-slides",
3
- "version": "2.18.3",
3
+ "version": "2.18.4",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp-slides",
5
5
  "description": "Extended Google Slides MCP server via gogcli — auth + full Slides 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-slides"
9
9
  },
10
- "version": "2.18.3",
10
+ "version": "2.18.4",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "gogcli-mcp-slides",
15
- "version": "2.18.3",
15
+ "version": "2.18.4",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },
@@ -176,7 +176,8 @@ export function registerExtraSlidesTools(server: McpServer): void {
176
176
  }, async ({ presentationId, pretty, account }) => {
177
177
  const args = ['slides', 'raw', presentationId];
178
178
  if (pretty) args.push('--pretty');
179
- return runOrDiagnose(args, { account });
179
+ // Verbatim by contract: see the `lossless` note on runOrDiagnose.
180
+ return runOrDiagnose(args, { account, lossless: true });
180
181
  });
181
182
 
182
183
  server.registerTool('gog_slides_locate', {
@@ -342,11 +342,11 @@ describe('gog_slides_replace_slide url mode', () => {
342
342
  describe('gog_slides_raw', () => {
343
343
  it('passes --pretty', async () => {
344
344
  await harness.callTool('gog_slides_raw', { presentationId: 'p1', pretty: true });
345
- expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'raw', 'p1', '--pretty'], { account: undefined });
345
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'raw', 'p1', '--pretty'], { account: undefined, lossless: true });
346
346
  });
347
347
  it('bare', async () => {
348
348
  await harness.callTool('gog_slides_raw', { presentationId: 'p1' });
349
- expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'raw', 'p1'], { account: undefined });
349
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'raw', 'p1'], { account: undefined, lossless: true });
350
350
  });
351
351
  });
352
352