gogcli-mcp-classroom 2.18.3 → 2.19.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.
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "Extended Google Classroom for Claude via gogcli — auth + full Classroom support (courses, rosters, coursework, submissions, announcements, topics, invitations)",
10
- "version": "2.18.3"
10
+ "version": "2.19.0"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -15,7 +15,7 @@
15
15
  "displayName": "gogcli (Classroom)",
16
16
  "source": "./",
17
17
  "description": "Extended Google Classroom for Claude via gogcli — auth + full Classroom support (courses, rosters, coursework, submissions, announcements, topics, invitations)",
18
- "version": "2.18.3",
18
+ "version": "2.19.0",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gogcli-mcp-classroom",
3
3
  "displayName": "gogcli (Classroom)",
4
- "version": "2.18.3",
4
+ "version": "2.19.0",
5
5
  "description": "Extended Google Classroom for Claude via gogcli — auth + full Classroom support (courses, rosters, coursework, submissions, announcements, topics, invitations)",
6
6
  "author": {
7
7
  "name": "Chris Hall",
package/dist/index.js CHANGED
@@ -3644,7 +3644,12 @@ var require_fast_uri = __commonJS({
3644
3644
  }
3645
3645
  function resolve(baseURI, relativeURI, options) {
3646
3646
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3647
- const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
3647
+ const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
3648
+ const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
3649
+ if (baseMalformed || relativeMalformed) {
3650
+ throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
3651
+ }
3652
+ const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
3648
3653
  schemelessOptions.skipEscape = true;
3649
3654
  return serialize(resolved, schemelessOptions);
3650
3655
  }
@@ -3770,6 +3775,7 @@ var require_fast_uri = __commonJS({
3770
3775
  }
3771
3776
  var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
3772
3777
  var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
3778
+ var AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
3773
3779
  function getParseError(parsed, matches) {
3774
3780
  if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
3775
3781
  return 'URI path must start with "/" when authority is present.';
@@ -3804,6 +3810,20 @@ var require_fast_uri = __commonJS({
3804
3810
  parsed.error = "URI authority must not contain a literal backslash.";
3805
3811
  malformedAuthorityOrPort = true;
3806
3812
  }
3813
+ const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION);
3814
+ if (introducerMatch !== null) {
3815
+ const region = introducerMatch[1];
3816
+ const normalizedRegion = region.replace(/[\t\n\r]/g, "");
3817
+ if (normalizedRegion.length >= 2) {
3818
+ if (normalizedRegion.slice(0, 2) !== "//") {
3819
+ parsed.error = parsed.error || "URI authority must not contain a literal backslash.";
3820
+ malformedAuthorityOrPort = true;
3821
+ } else if (region.length !== normalizedRegion.length) {
3822
+ parsed.error = parsed.error || "URI authority introducer must not contain whitespace.";
3823
+ malformedAuthorityOrPort = true;
3824
+ }
3825
+ }
3826
+ }
3807
3827
  const matches = uri.match(URI_PARSE);
3808
3828
  if (matches) {
3809
3829
  parsed.scheme = matches[1];
@@ -31275,6 +31295,198 @@ async function run(args, options = {}) {
31275
31295
  }
31276
31296
  }
31277
31297
 
31298
+ // ../gogcli-mcp/src/timestamps.ts
31299
+ var DEFAULT_DISPLAY_TZ = "America/New_York";
31300
+ function isValidTimeZone(tz) {
31301
+ try {
31302
+ new Intl.DateTimeFormat("en-US", { timeZone: tz });
31303
+ return true;
31304
+ } catch {
31305
+ return false;
31306
+ }
31307
+ }
31308
+ function displayTimeZone() {
31309
+ const configured = readEnvVar("DISPLAY_TZ");
31310
+ if (configured && isValidTimeZone(configured)) return configured;
31311
+ return DEFAULT_DISPLAY_TZ;
31312
+ }
31313
+ function naiveSourceTimeZone() {
31314
+ const configured = readEnvVar("GOG_TIMEZONE");
31315
+ if (configured && isValidTimeZone(configured)) return configured;
31316
+ return displayTimeZone();
31317
+ }
31318
+ function offsetAt(instant, tz) {
31319
+ const w = wallPartsIn(instant, tz);
31320
+ const asUTC = Date.UTC(w.year, w.month - 1, w.day, w.hour, w.minute, w.second, instant.getUTCMilliseconds());
31321
+ const minutes = Math.round((asUTC - instant.getTime()) / 6e4);
31322
+ const sign = minutes < 0 ? "-" : "+";
31323
+ const abs = Math.abs(minutes);
31324
+ return `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
31325
+ }
31326
+ function wallPartsIn(instant, tz) {
31327
+ const parts = new Intl.DateTimeFormat("en-US", {
31328
+ timeZone: tz,
31329
+ year: "numeric",
31330
+ month: "2-digit",
31331
+ day: "2-digit",
31332
+ hour: "2-digit",
31333
+ minute: "2-digit",
31334
+ second: "2-digit",
31335
+ // h23 pins midnight to hour 00; without it some ICU builds render hour 24.
31336
+ hourCycle: "h23"
31337
+ }).formatToParts(instant);
31338
+ const out = {};
31339
+ for (const p of parts) {
31340
+ if (p.type !== "literal") out[p.type] = Number(p.value);
31341
+ }
31342
+ return out;
31343
+ }
31344
+ function wallTimeToInstant(y, mo, d, h, mi, s, ms, tz) {
31345
+ let guess = Date.UTC(y, mo - 1, d, h, mi, s, ms);
31346
+ for (let i = 0; i < 2; i += 1) {
31347
+ const seen = wallPartsIn(new Date(guess), tz);
31348
+ const seenUTC = Date.UTC(seen.year, seen.month - 1, seen.day, seen.hour, seen.minute, seen.second, ms);
31349
+ const drift = Date.UTC(y, mo - 1, d, h, mi, s, ms) - seenUTC;
31350
+ if (drift === 0) break;
31351
+ guess += drift;
31352
+ }
31353
+ return new Date(guess);
31354
+ }
31355
+ function pad(n, width = 2) {
31356
+ return String(n).padStart(width, "0");
31357
+ }
31358
+ function isoWithOffset(instant, tz, offset) {
31359
+ const w = wallPartsIn(instant, tz);
31360
+ const msPart = instant.getUTCMilliseconds();
31361
+ const frac = msPart ? `.${pad(msPart, 3)}` : "";
31362
+ return `${pad(w.year, 4)}-${pad(w.month)}-${pad(w.day)}T${pad(w.hour)}:${pad(w.minute)}:${pad(w.second)}${frac}${offset}`;
31363
+ }
31364
+ function formatInstant(instant, tz = displayTimeZone()) {
31365
+ const offset = offsetAt(instant, tz);
31366
+ const display = new Intl.DateTimeFormat("en-US", {
31367
+ timeZone: tz,
31368
+ weekday: "short",
31369
+ month: "short",
31370
+ day: "numeric",
31371
+ year: "numeric",
31372
+ hour: "numeric",
31373
+ minute: "2-digit",
31374
+ timeZoneName: "short"
31375
+ }).format(instant);
31376
+ return { iso: isoWithOffset(instant, tz, offset), display };
31377
+ }
31378
+ var TIMESTAMP_KEYS = /* @__PURE__ */ new Set([
31379
+ "date",
31380
+ // gog gmail message/thread listings ("2026-07-28 03:36")
31381
+ "dateTime",
31382
+ // Calendar event start/end
31383
+ "internalDate",
31384
+ // Gmail, epoch milliseconds (authoritative)
31385
+ "modifiedTime",
31386
+ // Drive
31387
+ "createdTime",
31388
+ // Drive
31389
+ "createTime",
31390
+ "updateTime",
31391
+ "updated",
31392
+ "originalStartTime",
31393
+ "expirationTime",
31394
+ "lastModified",
31395
+ "sentAt",
31396
+ "viewedAt",
31397
+ "modifiedAt",
31398
+ "fetchedBodyAt",
31399
+ "asOf"
31400
+ ]);
31401
+ var ZONE_NAME_KEYS = /* @__PURE__ */ new Set(["timeZone", "timezone"]);
31402
+ 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})$/;
31403
+ var NAIVE_DATE_TIME = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2}))?(?:\.(\d{1,9}))?$/;
31404
+ var EPOCH_MILLIS = /^\d{13}$/;
31405
+ var DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
31406
+ function isRealCalendarDate(p) {
31407
+ const utc = new Date(Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second));
31408
+ 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;
31409
+ }
31410
+ function parseTimestampValue(key, value, assumeNaiveIn) {
31411
+ if (typeof value !== "string") return null;
31412
+ const raw = value.trim();
31413
+ if (raw === "" || DATE_ONLY.test(raw)) return null;
31414
+ if (key === "internalDate" && EPOCH_MILLIS.test(raw)) {
31415
+ return new Date(Number(raw));
31416
+ }
31417
+ if (RFC3339_WITH_OFFSET.test(raw)) {
31418
+ const parsed = new Date(raw.replace(" ", "T"));
31419
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
31420
+ }
31421
+ const naive = NAIVE_DATE_TIME.exec(raw);
31422
+ if (naive) {
31423
+ const [, y, mo, d, h, mi, s, frac] = naive;
31424
+ const ms = frac ? Number(frac.padEnd(3, "0").slice(0, 3)) : 0;
31425
+ const parts = {
31426
+ year: Number(y),
31427
+ month: Number(mo),
31428
+ day: Number(d),
31429
+ hour: Number(h),
31430
+ minute: Number(mi),
31431
+ second: Number(s ?? "0")
31432
+ };
31433
+ if (!isRealCalendarDate(parts)) return null;
31434
+ return wallTimeToInstant(
31435
+ parts.year,
31436
+ parts.month,
31437
+ parts.day,
31438
+ parts.hour,
31439
+ parts.minute,
31440
+ parts.second,
31441
+ ms,
31442
+ assumeNaiveIn
31443
+ );
31444
+ }
31445
+ return null;
31446
+ }
31447
+ function walk(node, tz, naiveTz) {
31448
+ let changed = false;
31449
+ if (Array.isArray(node)) {
31450
+ for (const item of node) {
31451
+ if (walk(item, tz, naiveTz)) changed = true;
31452
+ }
31453
+ return changed;
31454
+ }
31455
+ if (node === null || typeof node !== "object") return false;
31456
+ const obj = node;
31457
+ for (const key of Object.keys(obj)) {
31458
+ const value = obj[key];
31459
+ if (value !== null && typeof value === "object") {
31460
+ if (walk(value, tz, naiveTz)) changed = true;
31461
+ continue;
31462
+ }
31463
+ if (ZONE_NAME_KEYS.has(key) || !TIMESTAMP_KEYS.has(key)) continue;
31464
+ const instant = parseTimestampValue(key, value, naiveTz);
31465
+ if (!instant) continue;
31466
+ const { iso, display } = formatInstant(instant, tz);
31467
+ obj[key] = iso;
31468
+ obj[`${key}Display`] = display;
31469
+ changed = true;
31470
+ }
31471
+ return changed;
31472
+ }
31473
+ function detectIndent(text) {
31474
+ const match = /\n(\s+)\S/.exec(text);
31475
+ return match ? match[1].replace(/\t/g, " ").length : 0;
31476
+ }
31477
+ function normalizeTimestamps(text, tz = displayTimeZone(), naiveTz = naiveSourceTimeZone()) {
31478
+ const trimmed = text.trim();
31479
+ if (trimmed === "" || !/^[[{]/.test(trimmed)) return text;
31480
+ let parsed;
31481
+ try {
31482
+ parsed = JSON.parse(trimmed);
31483
+ } catch {
31484
+ return text;
31485
+ }
31486
+ if (!walk(parsed, tz, naiveTz)) return text;
31487
+ return JSON.stringify(parsed, null, detectIndent(text));
31488
+ }
31489
+
31278
31490
  // ../gogcli-mcp/src/tools/utils.ts
31279
31491
  var accountParam = external_exports.string().optional().describe(
31280
31492
  "Google account email to use, e.g. you@gmail.com \u2014 must be the full address, not a bare username. Overrides the GOG_ACCOUNT env var. Omit to use the single configured account."
@@ -31368,7 +31580,8 @@ ${accounts || "(none)"}${hint}`);
31368
31580
  }
31369
31581
  async function runOrDiagnose(args, options) {
31370
31582
  try {
31371
- return rawTextResult(await run(args, options));
31583
+ const raw = await run(args, options);
31584
+ return rawTextResult(options.lossless ? raw : normalizeTimestamps(raw));
31372
31585
  } catch (err) {
31373
31586
  return diagnose(err);
31374
31587
  }
@@ -31918,7 +32131,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
31918
32131
  );
31919
32132
 
31920
32133
  // ../gogcli-mcp/src/server.ts
31921
- var VERSION = true ? "2.18.3" : "0.0.0";
32134
+ var VERSION = true ? "2.19.0" : "0.0.0";
31922
32135
 
31923
32136
  // src/tools/classroom-extra.ts
31924
32137
  var courseState = external_exports.enum(["ACTIVE", "ARCHIVED", "PROVISIONED", "DECLINED", "SUSPENDED"]);
package/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.3",
4
4
  "name": "gogcli-mcp-classroom",
5
5
  "display_name": "gogcli (Classroom)",
6
- "version": "2.18.3",
6
+ "version": "2.19.0",
7
7
  "description": "Extended Google Classroom for Claude via gogcli — auth + full Classroom support (courses, rosters, coursework, submissions, announcements, topics, invitations)",
8
8
  "author": {
9
9
  "name": "Chris Hall",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-classroom",
3
- "version": "2.18.3",
3
+ "version": "2.19.0",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp-classroom",
5
5
  "description": "Extended Google Classroom MCP server via gogcli — auth + full Classroom 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-classroom"
9
9
  },
10
- "version": "2.18.3",
10
+ "version": "2.19.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "gogcli-mcp-classroom",
15
- "version": "2.18.3",
15
+ "version": "2.19.0",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },