@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
@@ -343,13 +343,20 @@ async function brokerRefresh(refreshToken, baseUrl = brokerBaseUrl()) {
343
343
  //#endregion
344
344
  //#region src/google/auth.ts
345
345
  var GOOGLE_CALENDAR_SCOPE = "https://www.googleapis.com/auth/calendar.events";
346
+ /** Minimal read scope for the calendar list + per-calendar colours.
347
+ * `calendar.events` already covers reading/writing events on any calendar, but
348
+ * discovering WHICH calendars the user has (CalendarList.list) needs a
349
+ * calendar-list read scope — this is the narrowest one that grants it. Kept in
350
+ * step with the broker's consent scopes so local- and broker-linked accounts
351
+ * grant the same set. */
352
+ var GOOGLE_CALENDARLIST_SCOPE = "https://www.googleapis.com/auth/calendar.calendarlist.readonly";
346
353
  var GOOGLE_TASKS_SCOPE = "https://www.googleapis.com/auth/tasks";
347
354
  var GOOGLE_DRIVE_FILE_SCOPE = "https://www.googleapis.com/auth/drive.file";
348
355
  /** Requested at consent as one set — matches the scopes registered on the
349
- * OAuth consent screen, so a single re-link covers every supported API
350
- * (Calendar now; Tasks / Drive tools ride the same grant later). */
356
+ * OAuth consent screen, so a single re-link covers every supported API. */
351
357
  var GOOGLE_SCOPES = [
352
358
  GOOGLE_CALENDAR_SCOPE,
359
+ GOOGLE_CALENDARLIST_SCOPE,
353
360
  GOOGLE_TASKS_SCOPE,
354
361
  GOOGLE_DRIVE_FILE_SCOPE
355
362
  ];
@@ -442,29 +449,50 @@ var respondHtml = (res, status, message) => {
442
449
  res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
443
450
  res.end(`<html><body><h3>${message}</h3></body></html>`);
444
451
  };
445
- var waitForAuthCode = (server, expectedState, timeoutMs) => new Promise((resolve, reject) => {
452
+ /** Reject reason when a pending flow is torn down (a settings-UI restart or an
453
+ * explicit cancel), kept distinct from a real authorization error so callers
454
+ * can treat it as "user changed their mind", not "linking failed". */
455
+ var GOOGLE_AUTH_CANCELLED = "authorization cancelled";
456
+ var handleCallbackRequest = (req, res, expectedState, settle) => {
457
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
458
+ if (url.pathname !== CALLBACK_PATH) {
459
+ res.writeHead(404);
460
+ res.end();
461
+ return;
462
+ }
463
+ if (url.searchParams.get("state") !== expectedState) {
464
+ respondHtml(res, 400, "Invalid authorization callback. You can close this tab.");
465
+ return;
466
+ }
467
+ try {
468
+ const code = authCodeFromCallback(url, expectedState);
469
+ respondHtml(res, 200, "Authorization complete — you can close this tab.");
470
+ settle.resolve(code);
471
+ } catch (err) {
472
+ respondHtml(res, 400, "Authorization failed — see the terminal for details. You can close this tab.");
473
+ settle.reject(err);
474
+ }
475
+ };
476
+ var waitForAuthCode = (server, expectedState, timeoutMs, signal) => new Promise((resolve, reject) => {
446
477
  const timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`authorization timed out after ${timeoutMs / ONE_SECOND_MS}s`)), timeoutMs);
447
- server.on("request", (req, res) => {
448
- const url = new URL(req.url ?? "/", "http://127.0.0.1");
449
- if (url.pathname !== CALLBACK_PATH) {
450
- res.writeHead(404);
451
- res.end();
452
- return;
478
+ const settle = {
479
+ resolve: (code) => {
480
+ clearTimeout(timer);
481
+ resolve(code);
482
+ },
483
+ reject: (err) => {
484
+ clearTimeout(timer);
485
+ reject(err instanceof Error ? err : new Error(String(err)));
453
486
  }
454
- if (url.searchParams.get("state") !== expectedState) {
455
- respondHtml(res, 400, "Invalid authorization callback. You can close this tab.");
487
+ };
488
+ if (signal) {
489
+ if (signal.aborted) {
490
+ settle.reject(/* @__PURE__ */ new Error(GOOGLE_AUTH_CANCELLED));
456
491
  return;
457
492
  }
458
- clearTimeout(timer);
459
- try {
460
- const code = authCodeFromCallback(url, expectedState);
461
- respondHtml(res, 200, "Authorization complete — you can close this tab.");
462
- resolve(code);
463
- } catch (err) {
464
- respondHtml(res, 400, "Authorization failed — see the terminal for details. You can close this tab.");
465
- reject(err);
466
- }
467
- });
493
+ signal.addEventListener("abort", () => settle.reject(/* @__PURE__ */ new Error(GOOGLE_AUTH_CANCELLED)), { once: true });
494
+ }
495
+ server.on("request", (req, res) => handleCallbackRequest(req, res, expectedState, settle));
468
496
  });
469
497
  var buildConsentUrl = (client, codeChallenge, state) => client.generateAuthUrl({
470
498
  access_type: "offline",
@@ -488,7 +516,7 @@ var authorizeWithLocalClient = async (secret, server, port, opts) => {
488
516
  if (!codeChallenge) throw new Error("failed to derive a PKCE code challenge");
489
517
  const state = randomBytes(STATE_BYTES).toString("hex");
490
518
  opts.onAuthUrl?.(buildConsentUrl(client, codeChallenge, state));
491
- const code = await waitForAuthCode(server, state, opts.timeoutMs ?? AUTH_TIMEOUT_MS);
519
+ const code = await waitForAuthCode(server, state, opts.timeoutMs ?? AUTH_TIMEOUT_MS, opts.signal);
492
520
  const { tokens } = await client.getToken({
493
521
  code,
494
522
  codeVerifier
@@ -501,11 +529,18 @@ var authorizeWithBroker = async (server, port, opts) => {
501
529
  const { authUrl, state } = await brokerStart(port, codeChallenge);
502
530
  opts.onAuthUrl?.(authUrl);
503
531
  return await brokerExchange({
504
- code: await waitForAuthCode(server, state, opts.timeoutMs ?? AUTH_TIMEOUT_MS),
532
+ code: await waitForAuthCode(server, state, opts.timeoutMs ?? AUTH_TIMEOUT_MS, opts.signal),
505
533
  state,
506
534
  codeVerifier
507
535
  });
508
536
  };
537
+ var commitLinkedTokens = async (tokens, issuedVia, opts) => {
538
+ if (opts.signal?.aborted) throw new Error(GOOGLE_AUTH_CANCELLED);
539
+ return await saveGoogleTokens({
540
+ ...tokens,
541
+ issuedVia
542
+ }, opts.home);
543
+ };
509
544
  async function authorizeGoogle(opts = {}) {
510
545
  const presence = await clientSecretPresence(opts.home);
511
546
  if (presence === "ambiguous") await loadClientSecret(opts.home);
@@ -513,10 +548,7 @@ async function authorizeGoogle(opts = {}) {
513
548
  const { server, port } = await startLoopbackServer();
514
549
  try {
515
550
  const issuedVia = useLocalClient ? "local" : "broker";
516
- return await saveGoogleTokens({
517
- ...useLocalClient ? await authorizeWithLocalClient(await loadClientSecret(opts.home), server, port, opts) : await authorizeWithBroker(server, port, opts),
518
- issuedVia
519
- }, opts.home);
551
+ return await commitLinkedTokens(useLocalClient ? await authorizeWithLocalClient(await loadClientSecret(opts.home), server, port, opts) : await authorizeWithBroker(server, port, opts), issuedVia, opts);
520
552
  } finally {
521
553
  server.close();
522
554
  }
@@ -524,25 +556,38 @@ async function authorizeGoogle(opts = {}) {
524
556
  //#endregion
525
557
  //#region src/google/authFlow.ts
526
558
  var createGoogleAuthFlow = (authorize) => {
527
- let inFlightStart = null;
528
559
  let flowRunning = false;
529
560
  let lastError = null;
530
- const launchFlow = () => new Promise((resolve, reject) => {
561
+ let active = null;
562
+ const launch = () => {
563
+ const controller = new AbortController();
564
+ active = controller;
531
565
  flowRunning = true;
532
- authorize({ onAuthUrl: (url) => resolve({ authUrl: url }) }).then(() => log.info("google", "authorize flow completed")).catch((err) => {
533
- lastError = errorMessage(err);
534
- log.warn("google", "authorize flow failed", { error: lastError });
535
- reject(err instanceof Error ? err : new Error(String(err)));
536
- }).finally(() => {
537
- flowRunning = false;
538
- inFlightStart = null;
566
+ return new Promise((resolve, reject) => {
567
+ authorize({
568
+ onAuthUrl: (url) => resolve({ authUrl: url }),
569
+ signal: controller.signal
570
+ }).then(() => log.info("google", "authorize flow completed")).catch((err) => {
571
+ if (!controller.signal.aborted) {
572
+ lastError = errorMessage(err);
573
+ log.warn("google", "authorize flow failed", { error: lastError });
574
+ }
575
+ reject(err instanceof Error ? err : new Error(String(err)));
576
+ }).finally(() => {
577
+ if (active === controller) {
578
+ flowRunning = false;
579
+ active = null;
580
+ }
581
+ });
539
582
  });
540
- });
583
+ };
584
+ const cancel = () => {
585
+ active?.abort();
586
+ };
541
587
  const start = () => {
542
- if (inFlightStart) return inFlightStart;
588
+ cancel();
543
589
  lastError = null;
544
- inFlightStart = launchFlow();
545
- return inFlightStart;
590
+ return launch();
546
591
  };
547
592
  const status = () => ({
548
593
  pending: flowRunning,
@@ -550,6 +595,7 @@ var createGoogleAuthFlow = (authorize) => {
550
595
  });
551
596
  return {
552
597
  start,
598
+ cancel,
553
599
  status
554
600
  };
555
601
  };
@@ -594,8 +640,12 @@ var itemsOf = (value) => {
594
640
  };
595
641
  //#endregion
596
642
  //#region src/google/calendar.ts
597
- var CALENDAR_EVENTS_URL = "https://www.googleapis.com/calendar/v3/calendars/primary/events";
643
+ var CALENDAR_BASE_URL = "https://www.googleapis.com/calendar/v3";
598
644
  var CALENDAR_API_LABEL = "Google Calendar API";
645
+ var DEFAULT_CALENDAR_ID = "primary";
646
+ var CALENDAR_LIST_PAGE_SIZE = 250;
647
+ var MAX_CALENDAR_LIST_PAGES = 40;
648
+ var eventsUrl = (calendarId) => `${CALENDAR_BASE_URL}/calendars/${encodeURIComponent(calendarId || DEFAULT_CALENDAR_ID)}/events`;
599
649
  var eventTime = (value) => {
600
650
  if (!isRecord(value)) return "";
601
651
  if (typeof value.dateTime === "string") return value.dateTime;
@@ -610,9 +660,33 @@ var toEventSummary = (value) => {
610
660
  start: eventTime(record.start),
611
661
  end: eventTime(record.end),
612
662
  htmlLink: stringField(record, "htmlLink"),
613
- status: stringField(record, "status")
663
+ status: stringField(record, "status"),
664
+ colorId: stringField(record, "colorId")
665
+ };
666
+ };
667
+ var toCalendarSummary = (value) => {
668
+ const record = asRecord(value);
669
+ return {
670
+ id: stringField(record, "id"),
671
+ summary: stringField(record, "summary"),
672
+ description: stringField(record, "description"),
673
+ primary: record.primary === true,
674
+ accessRole: stringField(record, "accessRole"),
675
+ backgroundColor: stringField(record, "backgroundColor"),
676
+ foregroundColor: stringField(record, "foregroundColor"),
677
+ colorId: stringField(record, "colorId")
614
678
  };
615
679
  };
680
+ var toColorMap = (value) => {
681
+ const entries = Object.entries(asRecord(value)).map(([colorId, entry]) => {
682
+ const record = asRecord(entry);
683
+ return [colorId, {
684
+ background: stringField(record, "background"),
685
+ foreground: stringField(record, "foreground")
686
+ }];
687
+ });
688
+ return Object.fromEntries(entries);
689
+ };
616
690
  /** Kept as a named export for the existing unit tests / callers; the shared
617
691
  * helper now carries the wording. */
618
692
  var calendarApiError = (status, body) => googleApiError(CALENDAR_API_LABEL, status, body);
@@ -621,21 +695,58 @@ async function createCalendarEvent(accessToken, input) {
621
695
  summary: input.summary,
622
696
  description: input.description,
623
697
  start: { dateTime: input.startDateTime },
624
- end: { dateTime: input.endDateTime }
698
+ end: { dateTime: input.endDateTime },
699
+ ...input.colorId ? { colorId: input.colorId } : {}
625
700
  };
626
- return toEventSummary(await googleRequest(CALENDAR_API_LABEL, accessToken, CALENDAR_EVENTS_URL, {
701
+ return toEventSummary(await googleRequest(CALENDAR_API_LABEL, accessToken, eventsUrl(input.calendarId), {
627
702
  method: "POST",
628
703
  body: JSON.stringify(body)
629
704
  }));
630
705
  }
631
706
  async function listCalendarEvents(accessToken, input = {}) {
632
- const record = asRecord(await googleRequest(CALENDAR_API_LABEL, accessToken, `${CALENDAR_EVENTS_URL}?${new URLSearchParams({
707
+ const params = new URLSearchParams({
633
708
  timeMin: input.timeMin ?? (/* @__PURE__ */ new Date()).toISOString(),
634
709
  maxResults: String(input.maxResults ?? 10),
635
710
  singleEvents: "true",
636
711
  orderBy: "startTime"
637
- }).toString()}`));
638
- return (Array.isArray(record.items) ? record.items : []).map(toEventSummary);
712
+ });
713
+ return itemsOf(await googleRequest(CALENDAR_API_LABEL, accessToken, `${eventsUrl(input.calendarId)}?${params.toString()}`)).map(toEventSummary);
714
+ }
715
+ /** Pagination loop for CalendarList.list, extracted so it can be tested without
716
+ * network. Stops at the last page (no token) or the runaway page cap. */
717
+ async function collectCalendarPages(fetchPage, maxPages = MAX_CALENDAR_LIST_PAGES) {
718
+ const calendars = [];
719
+ let pageToken;
720
+ for (let page = 0; page < maxPages; page += 1) {
721
+ const { items, nextPageToken } = await fetchPage(pageToken);
722
+ calendars.push(...items.map(toCalendarSummary));
723
+ if (!nextPageToken) break;
724
+ pageToken = nextPageToken;
725
+ }
726
+ return calendars;
727
+ }
728
+ /** The calendars the user has added/subscribed to (primary + secondary +
729
+ * shared), each with its id, name and colour, following pagination. Needs the
730
+ * calendar-list read scope (GOOGLE_CALENDARLIST_SCOPE). */
731
+ async function listCalendars(accessToken) {
732
+ return collectCalendarPages(async (pageToken) => {
733
+ const params = new URLSearchParams({ maxResults: String(CALENDAR_LIST_PAGE_SIZE) });
734
+ if (pageToken) params.set("pageToken", pageToken);
735
+ const payload = await googleRequest(CALENDAR_API_LABEL, accessToken, `${CALENDAR_BASE_URL}/users/me/calendarList?${params.toString()}`);
736
+ const record = asRecord(payload);
737
+ return {
738
+ items: itemsOf(payload),
739
+ nextPageToken: typeof record.nextPageToken === "string" ? record.nextPageToken : void 0
740
+ };
741
+ });
742
+ }
743
+ /** Resolve a `colorId` (on an event or calendar) to its hex background/foreground. */
744
+ async function getCalendarColors(accessToken) {
745
+ const record = asRecord(await googleRequest(CALENDAR_API_LABEL, accessToken, `${CALENDAR_BASE_URL}/colors`));
746
+ return {
747
+ event: toColorMap(record.event),
748
+ calendar: toColorMap(record.calendar)
749
+ };
639
750
  }
640
751
  //#endregion
641
752
  //#region src/google/tasks.ts
@@ -786,6 +897,6 @@ async function deleteDriveFile(accessToken, input) {
786
897
  await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}/${encodeURIComponent(input.fileId)}`, { method: "DELETE" });
787
898
  }
788
899
  //#endregion
789
- export { DEFAULT_LIST_MAX_RESULTS, GOOGLE_CALENDAR_SCOPE, GOOGLE_DRIVE_FILE_SCOPE, GOOGLE_SCOPES, GOOGLE_TASKS_SCOPE, MAX_LIST_RESULTS, assertSafeMimeType, authorizeGoogle, brokerBaseUrl, brokerExchange, brokerRefresh, brokerStart, buildMultipartBody, calendarApiError, clientSecretPresence, completeTask, configureGoogleHost, createCalendarEvent, createDriveFile, createGoogleAuthFlow, createTask, deleteDriveFile, deleteGoogleTokens, deleteTask, findClientSecretPath, getGoogleAccessToken, googleApiError, googleAuthFlow, googleConfigDir, googleSecretsDir, googleTokenPath, isIsoDateTimeWithOffset, isTextMimeType, legacyGoogleTokenPath, listCalendarEvents, listDriveFiles, listTaskLists, listTasks, loadClientSecret, loadGoogleTokens, mergeGoogleTokens, pickBoundary, readDriveFile, saveGoogleTokens, toDriveFileSummary, toEventSummary, toTaskListSummary, toTaskSummary, unlinkGoogle, waitForAuthCode };
900
+ export { DEFAULT_LIST_MAX_RESULTS, GOOGLE_AUTH_CANCELLED, GOOGLE_CALENDARLIST_SCOPE, GOOGLE_CALENDAR_SCOPE, GOOGLE_DRIVE_FILE_SCOPE, GOOGLE_SCOPES, GOOGLE_TASKS_SCOPE, MAX_LIST_RESULTS, assertSafeMimeType, authorizeGoogle, brokerBaseUrl, brokerExchange, brokerRefresh, brokerStart, buildMultipartBody, calendarApiError, clientSecretPresence, collectCalendarPages, commitLinkedTokens, completeTask, configureGoogleHost, createCalendarEvent, createDriveFile, createGoogleAuthFlow, createTask, deleteDriveFile, deleteGoogleTokens, deleteTask, findClientSecretPath, getCalendarColors, getGoogleAccessToken, googleApiError, googleAuthFlow, googleConfigDir, googleSecretsDir, googleTokenPath, isIsoDateTimeWithOffset, isTextMimeType, legacyGoogleTokenPath, listCalendarEvents, listCalendars, listDriveFiles, listTaskLists, listTasks, loadClientSecret, loadGoogleTokens, mergeGoogleTokens, pickBoundary, readDriveFile, saveGoogleTokens, toCalendarSummary, toDriveFileSummary, toEventSummary, toTaskListSummary, toTaskSummary, unlinkGoogle, waitForAuthCode };
790
901
 
791
902
  //# sourceMappingURL=index.js.map