@mulmoclaude/core 0.22.1 → 0.23.1

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.
Files changed (59) hide show
  1. package/assets/helps/collection-skills.md +110 -1
  2. package/assets/helps/custom-view.md +38 -0
  3. package/assets/helps/error-recovery.md +36 -0
  4. package/assets/skills-preset/mc-zenn/SKILL.md +31 -0
  5. package/dist/collection/core/queryZ.d.ts +90 -0
  6. package/dist/collection/core/schema.d.ts +14 -1
  7. package/dist/collection/core/schemaZ.d.ts +18 -1
  8. package/dist/collection/index.cjs +3 -2
  9. package/dist/collection/index.cjs.map +1 -1
  10. package/dist/collection/index.d.ts +1 -0
  11. package/dist/collection/index.js +3 -3
  12. package/dist/collection/index.js.map +1 -1
  13. package/dist/collection/registry/server/index.cjs +12 -4
  14. package/dist/collection/registry/server/index.cjs.map +1 -1
  15. package/dist/collection/registry/server/index.js +12 -4
  16. package/dist/collection/registry/server/index.js.map +1 -1
  17. package/dist/collection/server/csvQuery.d.ts +27 -0
  18. package/dist/collection/server/csvStore.d.ts +51 -0
  19. package/dist/collection/server/discoveredCollection.d.ts +9 -1
  20. package/dist/collection/server/discovery.d.ts +7 -3
  21. package/dist/collection/server/index.cjs +17 -2
  22. package/dist/collection/server/index.d.ts +6 -0
  23. package/dist/collection/server/index.js +3 -3
  24. package/dist/collection/server/jsonlQuery.d.ts +4 -0
  25. package/dist/collection/server/manageTool.d.ts +4 -0
  26. package/dist/collection/server/store.d.ts +31 -0
  27. package/dist/collection-watchers/index.cjs +104 -3
  28. package/dist/collection-watchers/index.cjs.map +1 -1
  29. package/dist/collection-watchers/index.js +102 -3
  30. package/dist/collection-watchers/index.js.map +1 -1
  31. package/dist/feeds/index.cjs +2 -2
  32. package/dist/feeds/index.js +2 -2
  33. package/dist/feeds/server/index.cjs +3 -3
  34. package/dist/feeds/server/index.js +3 -3
  35. package/dist/google/auth.d.ts +18 -3
  36. package/dist/google/authFlow.d.ts +1 -0
  37. package/dist/google/calendar.d.ts +44 -0
  38. package/dist/google/index.cjs +165 -47
  39. package/dist/google/index.cjs.map +1 -1
  40. package/dist/google/index.d.ts +2 -2
  41. package/dist/google/index.js +159 -48
  42. package/dist/google/index.js.map +1 -1
  43. package/dist/{ingestTypes-DEjpiZGM.js → ingestTypes-B-dXxUF9.js} +2 -2
  44. package/dist/{ingestTypes-DEjpiZGM.js.map → ingestTypes-B-dXxUF9.js.map} +1 -1
  45. package/dist/{ingestTypes-Dh5lniBL.cjs → ingestTypes-Cev9q77C.cjs} +2 -2
  46. package/dist/{ingestTypes-Dh5lniBL.cjs.map → ingestTypes-Cev9q77C.cjs.map} +1 -1
  47. package/dist/{promptSafety-DN5V__Ku.cjs → promptSafety-DRd15gQ6.cjs} +15 -1
  48. package/dist/promptSafety-DRd15gQ6.cjs.map +1 -0
  49. package/dist/{promptSafety-DyG3EuC7.js → promptSafety-rDWA9_JS.js} +10 -2
  50. package/dist/promptSafety-rDWA9_JS.js.map +1 -0
  51. package/dist/{server-Bd8l1bhv.js → server-D2ibbixF.js} +772 -79
  52. package/dist/server-D2ibbixF.js.map +1 -0
  53. package/dist/{server-Bh-DPK-H.cjs → server-Jaogu3Ky.cjs} +860 -76
  54. package/dist/server-Jaogu3Ky.cjs.map +1 -0
  55. package/package.json +3 -1
  56. package/dist/promptSafety-DN5V__Ku.cjs.map +0 -1
  57. package/dist/promptSafety-DyG3EuC7.js.map +0 -1
  58. package/dist/server-Bd8l1bhv.js.map +0 -1
  59. package/dist/server-Bh-DPK-H.cjs.map +0 -1
@@ -347,13 +347,20 @@ async function brokerRefresh(refreshToken, baseUrl = brokerBaseUrl()) {
347
347
  //#endregion
348
348
  //#region src/google/auth.ts
349
349
  var GOOGLE_CALENDAR_SCOPE = "https://www.googleapis.com/auth/calendar.events";
350
+ /** Minimal read scope for the calendar list + per-calendar colours.
351
+ * `calendar.events` already covers reading/writing events on any calendar, but
352
+ * discovering WHICH calendars the user has (CalendarList.list) needs a
353
+ * calendar-list read scope — this is the narrowest one that grants it. Kept in
354
+ * step with the broker's consent scopes so local- and broker-linked accounts
355
+ * grant the same set. */
356
+ var GOOGLE_CALENDARLIST_SCOPE = "https://www.googleapis.com/auth/calendar.calendarlist.readonly";
350
357
  var GOOGLE_TASKS_SCOPE = "https://www.googleapis.com/auth/tasks";
351
358
  var GOOGLE_DRIVE_FILE_SCOPE = "https://www.googleapis.com/auth/drive.file";
352
359
  /** Requested at consent as one set — matches the scopes registered on the
353
- * OAuth consent screen, so a single re-link covers every supported API
354
- * (Calendar now; Tasks / Drive tools ride the same grant later). */
360
+ * OAuth consent screen, so a single re-link covers every supported API. */
355
361
  var GOOGLE_SCOPES = [
356
362
  GOOGLE_CALENDAR_SCOPE,
363
+ GOOGLE_CALENDARLIST_SCOPE,
357
364
  GOOGLE_TASKS_SCOPE,
358
365
  GOOGLE_DRIVE_FILE_SCOPE
359
366
  ];
@@ -446,29 +453,50 @@ var respondHtml = (res, status, message) => {
446
453
  res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
447
454
  res.end(`<html><body><h3>${message}</h3></body></html>`);
448
455
  };
449
- var waitForAuthCode = (server, expectedState, timeoutMs) => new Promise((resolve, reject) => {
456
+ /** Reject reason when a pending flow is torn down (a settings-UI restart or an
457
+ * explicit cancel), kept distinct from a real authorization error so callers
458
+ * can treat it as "user changed their mind", not "linking failed". */
459
+ var GOOGLE_AUTH_CANCELLED = "authorization cancelled";
460
+ var handleCallbackRequest = (req, res, expectedState, settle) => {
461
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
462
+ if (url.pathname !== CALLBACK_PATH) {
463
+ res.writeHead(404);
464
+ res.end();
465
+ return;
466
+ }
467
+ if (url.searchParams.get("state") !== expectedState) {
468
+ respondHtml(res, 400, "Invalid authorization callback. You can close this tab.");
469
+ return;
470
+ }
471
+ try {
472
+ const code = authCodeFromCallback(url, expectedState);
473
+ respondHtml(res, 200, "Authorization complete — you can close this tab.");
474
+ settle.resolve(code);
475
+ } catch (err) {
476
+ respondHtml(res, 400, "Authorization failed — see the terminal for details. You can close this tab.");
477
+ settle.reject(err);
478
+ }
479
+ };
480
+ var waitForAuthCode = (server, expectedState, timeoutMs, signal) => new Promise((resolve, reject) => {
450
481
  const timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`authorization timed out after ${timeoutMs / ONE_SECOND_MS}s`)), timeoutMs);
451
- server.on("request", (req, res) => {
452
- const url = new URL(req.url ?? "/", "http://127.0.0.1");
453
- if (url.pathname !== CALLBACK_PATH) {
454
- res.writeHead(404);
455
- res.end();
456
- return;
482
+ const settle = {
483
+ resolve: (code) => {
484
+ clearTimeout(timer);
485
+ resolve(code);
486
+ },
487
+ reject: (err) => {
488
+ clearTimeout(timer);
489
+ reject(err instanceof Error ? err : new Error(String(err)));
457
490
  }
458
- if (url.searchParams.get("state") !== expectedState) {
459
- respondHtml(res, 400, "Invalid authorization callback. You can close this tab.");
491
+ };
492
+ if (signal) {
493
+ if (signal.aborted) {
494
+ settle.reject(/* @__PURE__ */ new Error(GOOGLE_AUTH_CANCELLED));
460
495
  return;
461
496
  }
462
- clearTimeout(timer);
463
- try {
464
- const code = authCodeFromCallback(url, expectedState);
465
- respondHtml(res, 200, "Authorization complete — you can close this tab.");
466
- resolve(code);
467
- } catch (err) {
468
- respondHtml(res, 400, "Authorization failed — see the terminal for details. You can close this tab.");
469
- reject(err);
470
- }
471
- });
497
+ signal.addEventListener("abort", () => settle.reject(/* @__PURE__ */ new Error(GOOGLE_AUTH_CANCELLED)), { once: true });
498
+ }
499
+ server.on("request", (req, res) => handleCallbackRequest(req, res, expectedState, settle));
472
500
  });
473
501
  var buildConsentUrl = (client, codeChallenge, state) => client.generateAuthUrl({
474
502
  access_type: "offline",
@@ -492,7 +520,7 @@ var authorizeWithLocalClient = async (secret, server, port, opts) => {
492
520
  if (!codeChallenge) throw new Error("failed to derive a PKCE code challenge");
493
521
  const state = (0, node_crypto.randomBytes)(STATE_BYTES).toString("hex");
494
522
  opts.onAuthUrl?.(buildConsentUrl(client, codeChallenge, state));
495
- const code = await waitForAuthCode(server, state, opts.timeoutMs ?? AUTH_TIMEOUT_MS);
523
+ const code = await waitForAuthCode(server, state, opts.timeoutMs ?? AUTH_TIMEOUT_MS, opts.signal);
496
524
  const { tokens } = await client.getToken({
497
525
  code,
498
526
  codeVerifier
@@ -505,11 +533,18 @@ var authorizeWithBroker = async (server, port, opts) => {
505
533
  const { authUrl, state } = await brokerStart(port, codeChallenge);
506
534
  opts.onAuthUrl?.(authUrl);
507
535
  return await brokerExchange({
508
- code: await waitForAuthCode(server, state, opts.timeoutMs ?? AUTH_TIMEOUT_MS),
536
+ code: await waitForAuthCode(server, state, opts.timeoutMs ?? AUTH_TIMEOUT_MS, opts.signal),
509
537
  state,
510
538
  codeVerifier
511
539
  });
512
540
  };
541
+ var commitLinkedTokens = async (tokens, issuedVia, opts) => {
542
+ if (opts.signal?.aborted) throw new Error(GOOGLE_AUTH_CANCELLED);
543
+ return await saveGoogleTokens({
544
+ ...tokens,
545
+ issuedVia
546
+ }, opts.home);
547
+ };
513
548
  async function authorizeGoogle(opts = {}) {
514
549
  const presence = await clientSecretPresence(opts.home);
515
550
  if (presence === "ambiguous") await loadClientSecret(opts.home);
@@ -517,10 +552,7 @@ async function authorizeGoogle(opts = {}) {
517
552
  const { server, port } = await startLoopbackServer();
518
553
  try {
519
554
  const issuedVia = useLocalClient ? "local" : "broker";
520
- return await saveGoogleTokens({
521
- ...useLocalClient ? await authorizeWithLocalClient(await loadClientSecret(opts.home), server, port, opts) : await authorizeWithBroker(server, port, opts),
522
- issuedVia
523
- }, opts.home);
555
+ return await commitLinkedTokens(useLocalClient ? await authorizeWithLocalClient(await loadClientSecret(opts.home), server, port, opts) : await authorizeWithBroker(server, port, opts), issuedVia, opts);
524
556
  } finally {
525
557
  server.close();
526
558
  }
@@ -528,25 +560,38 @@ async function authorizeGoogle(opts = {}) {
528
560
  //#endregion
529
561
  //#region src/google/authFlow.ts
530
562
  var createGoogleAuthFlow = (authorize) => {
531
- let inFlightStart = null;
532
563
  let flowRunning = false;
533
564
  let lastError = null;
534
- const launchFlow = () => new Promise((resolve, reject) => {
565
+ let active = null;
566
+ const launch = () => {
567
+ const controller = new AbortController();
568
+ active = controller;
535
569
  flowRunning = true;
536
- authorize({ onAuthUrl: (url) => resolve({ authUrl: url }) }).then(() => log.info("google", "authorize flow completed")).catch((err) => {
537
- lastError = errorMessage(err);
538
- log.warn("google", "authorize flow failed", { error: lastError });
539
- reject(err instanceof Error ? err : new Error(String(err)));
540
- }).finally(() => {
541
- flowRunning = false;
542
- inFlightStart = null;
570
+ return new Promise((resolve, reject) => {
571
+ authorize({
572
+ onAuthUrl: (url) => resolve({ authUrl: url }),
573
+ signal: controller.signal
574
+ }).then(() => log.info("google", "authorize flow completed")).catch((err) => {
575
+ if (!controller.signal.aborted) {
576
+ lastError = errorMessage(err);
577
+ log.warn("google", "authorize flow failed", { error: lastError });
578
+ }
579
+ reject(err instanceof Error ? err : new Error(String(err)));
580
+ }).finally(() => {
581
+ if (active === controller) {
582
+ flowRunning = false;
583
+ active = null;
584
+ }
585
+ });
543
586
  });
544
- });
587
+ };
588
+ const cancel = () => {
589
+ active?.abort();
590
+ };
545
591
  const start = () => {
546
- if (inFlightStart) return inFlightStart;
592
+ cancel();
547
593
  lastError = null;
548
- inFlightStart = launchFlow();
549
- return inFlightStart;
594
+ return launch();
550
595
  };
551
596
  const status = () => ({
552
597
  pending: flowRunning,
@@ -554,6 +599,7 @@ var createGoogleAuthFlow = (authorize) => {
554
599
  });
555
600
  return {
556
601
  start,
602
+ cancel,
557
603
  status
558
604
  };
559
605
  };
@@ -598,8 +644,12 @@ var itemsOf = (value) => {
598
644
  };
599
645
  //#endregion
600
646
  //#region src/google/calendar.ts
601
- var CALENDAR_EVENTS_URL = "https://www.googleapis.com/calendar/v3/calendars/primary/events";
647
+ var CALENDAR_BASE_URL = "https://www.googleapis.com/calendar/v3";
602
648
  var CALENDAR_API_LABEL = "Google Calendar API";
649
+ var DEFAULT_CALENDAR_ID = "primary";
650
+ var CALENDAR_LIST_PAGE_SIZE = 250;
651
+ var MAX_CALENDAR_LIST_PAGES = 40;
652
+ var eventsUrl = (calendarId) => `${CALENDAR_BASE_URL}/calendars/${encodeURIComponent(calendarId || DEFAULT_CALENDAR_ID)}/events`;
603
653
  var eventTime = (value) => {
604
654
  if (!isRecord(value)) return "";
605
655
  if (typeof value.dateTime === "string") return value.dateTime;
@@ -614,9 +664,33 @@ var toEventSummary = (value) => {
614
664
  start: eventTime(record.start),
615
665
  end: eventTime(record.end),
616
666
  htmlLink: stringField(record, "htmlLink"),
617
- status: stringField(record, "status")
667
+ status: stringField(record, "status"),
668
+ colorId: stringField(record, "colorId")
669
+ };
670
+ };
671
+ var toCalendarSummary = (value) => {
672
+ const record = asRecord(value);
673
+ return {
674
+ id: stringField(record, "id"),
675
+ summary: stringField(record, "summary"),
676
+ description: stringField(record, "description"),
677
+ primary: record.primary === true,
678
+ accessRole: stringField(record, "accessRole"),
679
+ backgroundColor: stringField(record, "backgroundColor"),
680
+ foregroundColor: stringField(record, "foregroundColor"),
681
+ colorId: stringField(record, "colorId")
618
682
  };
619
683
  };
684
+ var toColorMap = (value) => {
685
+ const entries = Object.entries(asRecord(value)).map(([colorId, entry]) => {
686
+ const record = asRecord(entry);
687
+ return [colorId, {
688
+ background: stringField(record, "background"),
689
+ foreground: stringField(record, "foreground")
690
+ }];
691
+ });
692
+ return Object.fromEntries(entries);
693
+ };
620
694
  /** Kept as a named export for the existing unit tests / callers; the shared
621
695
  * helper now carries the wording. */
622
696
  var calendarApiError = (status, body) => googleApiError(CALENDAR_API_LABEL, status, body);
@@ -625,21 +699,58 @@ async function createCalendarEvent(accessToken, input) {
625
699
  summary: input.summary,
626
700
  description: input.description,
627
701
  start: { dateTime: input.startDateTime },
628
- end: { dateTime: input.endDateTime }
702
+ end: { dateTime: input.endDateTime },
703
+ ...input.colorId ? { colorId: input.colorId } : {}
629
704
  };
630
- return toEventSummary(await googleRequest(CALENDAR_API_LABEL, accessToken, CALENDAR_EVENTS_URL, {
705
+ return toEventSummary(await googleRequest(CALENDAR_API_LABEL, accessToken, eventsUrl(input.calendarId), {
631
706
  method: "POST",
632
707
  body: JSON.stringify(body)
633
708
  }));
634
709
  }
635
710
  async function listCalendarEvents(accessToken, input = {}) {
636
- const record = asRecord(await googleRequest(CALENDAR_API_LABEL, accessToken, `${CALENDAR_EVENTS_URL}?${new URLSearchParams({
711
+ const params = new URLSearchParams({
637
712
  timeMin: input.timeMin ?? (/* @__PURE__ */ new Date()).toISOString(),
638
713
  maxResults: String(input.maxResults ?? 10),
639
714
  singleEvents: "true",
640
715
  orderBy: "startTime"
641
- }).toString()}`));
642
- return (Array.isArray(record.items) ? record.items : []).map(toEventSummary);
716
+ });
717
+ return itemsOf(await googleRequest(CALENDAR_API_LABEL, accessToken, `${eventsUrl(input.calendarId)}?${params.toString()}`)).map(toEventSummary);
718
+ }
719
+ /** Pagination loop for CalendarList.list, extracted so it can be tested without
720
+ * network. Stops at the last page (no token) or the runaway page cap. */
721
+ async function collectCalendarPages(fetchPage, maxPages = MAX_CALENDAR_LIST_PAGES) {
722
+ const calendars = [];
723
+ let pageToken;
724
+ for (let page = 0; page < maxPages; page += 1) {
725
+ const { items, nextPageToken } = await fetchPage(pageToken);
726
+ calendars.push(...items.map(toCalendarSummary));
727
+ if (!nextPageToken) break;
728
+ pageToken = nextPageToken;
729
+ }
730
+ return calendars;
731
+ }
732
+ /** The calendars the user has added/subscribed to (primary + secondary +
733
+ * shared), each with its id, name and colour, following pagination. Needs the
734
+ * calendar-list read scope (GOOGLE_CALENDARLIST_SCOPE). */
735
+ async function listCalendars(accessToken) {
736
+ return collectCalendarPages(async (pageToken) => {
737
+ const params = new URLSearchParams({ maxResults: String(CALENDAR_LIST_PAGE_SIZE) });
738
+ if (pageToken) params.set("pageToken", pageToken);
739
+ const payload = await googleRequest(CALENDAR_API_LABEL, accessToken, `${CALENDAR_BASE_URL}/users/me/calendarList?${params.toString()}`);
740
+ const record = asRecord(payload);
741
+ return {
742
+ items: itemsOf(payload),
743
+ nextPageToken: typeof record.nextPageToken === "string" ? record.nextPageToken : void 0
744
+ };
745
+ });
746
+ }
747
+ /** Resolve a `colorId` (on an event or calendar) to its hex background/foreground. */
748
+ async function getCalendarColors(accessToken) {
749
+ const record = asRecord(await googleRequest(CALENDAR_API_LABEL, accessToken, `${CALENDAR_BASE_URL}/colors`));
750
+ return {
751
+ event: toColorMap(record.event),
752
+ calendar: toColorMap(record.calendar)
753
+ };
643
754
  }
644
755
  //#endregion
645
756
  //#region src/google/tasks.ts
@@ -791,6 +902,8 @@ async function deleteDriveFile(accessToken, input) {
791
902
  }
792
903
  //#endregion
793
904
  exports.DEFAULT_LIST_MAX_RESULTS = DEFAULT_LIST_MAX_RESULTS;
905
+ exports.GOOGLE_AUTH_CANCELLED = GOOGLE_AUTH_CANCELLED;
906
+ exports.GOOGLE_CALENDARLIST_SCOPE = GOOGLE_CALENDARLIST_SCOPE;
794
907
  exports.GOOGLE_CALENDAR_SCOPE = GOOGLE_CALENDAR_SCOPE;
795
908
  exports.GOOGLE_DRIVE_FILE_SCOPE = GOOGLE_DRIVE_FILE_SCOPE;
796
909
  exports.GOOGLE_SCOPES = GOOGLE_SCOPES;
@@ -805,6 +918,8 @@ exports.brokerStart = brokerStart;
805
918
  exports.buildMultipartBody = buildMultipartBody;
806
919
  exports.calendarApiError = calendarApiError;
807
920
  exports.clientSecretPresence = clientSecretPresence;
921
+ exports.collectCalendarPages = collectCalendarPages;
922
+ exports.commitLinkedTokens = commitLinkedTokens;
808
923
  exports.completeTask = completeTask;
809
924
  exports.configureGoogleHost = configureGoogleHost;
810
925
  exports.createCalendarEvent = createCalendarEvent;
@@ -815,6 +930,7 @@ exports.deleteDriveFile = deleteDriveFile;
815
930
  exports.deleteGoogleTokens = deleteGoogleTokens;
816
931
  exports.deleteTask = deleteTask;
817
932
  exports.findClientSecretPath = findClientSecretPath;
933
+ exports.getCalendarColors = getCalendarColors;
818
934
  exports.getGoogleAccessToken = getGoogleAccessToken;
819
935
  exports.googleApiError = googleApiError;
820
936
  exports.googleAuthFlow = googleAuthFlow;
@@ -825,6 +941,7 @@ exports.isIsoDateTimeWithOffset = isIsoDateTimeWithOffset;
825
941
  exports.isTextMimeType = isTextMimeType;
826
942
  exports.legacyGoogleTokenPath = legacyGoogleTokenPath;
827
943
  exports.listCalendarEvents = listCalendarEvents;
944
+ exports.listCalendars = listCalendars;
828
945
  exports.listDriveFiles = listDriveFiles;
829
946
  exports.listTaskLists = listTaskLists;
830
947
  exports.listTasks = listTasks;
@@ -834,6 +951,7 @@ exports.mergeGoogleTokens = mergeGoogleTokens;
834
951
  exports.pickBoundary = pickBoundary;
835
952
  exports.readDriveFile = readDriveFile;
836
953
  exports.saveGoogleTokens = saveGoogleTokens;
954
+ exports.toCalendarSummary = toCalendarSummary;
837
955
  exports.toDriveFileSummary = toDriveFileSummary;
838
956
  exports.toEventSummary = toEventSummary;
839
957
  exports.toTaskListSummary = toTaskListSummary;