@laylzj777/my-apple-apple-eventkit 1.1.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.
Files changed (123) hide show
  1. package/CHANGELOG.md +575 -0
  2. package/LICENSE +21 -0
  3. package/README.md +304 -0
  4. package/README.zh-CN.md +270 -0
  5. package/bin/event +0 -0
  6. package/bin/event-disclaim +0 -0
  7. package/dist/bin/apple-calendar.d.ts +6 -0
  8. package/dist/bin/apple-calendar.js +8 -0
  9. package/dist/bin/apple-calendar.js.map +1 -0
  10. package/dist/bin/apple-reminders.d.ts +7 -0
  11. package/dist/bin/apple-reminders.js +9 -0
  12. package/dist/bin/apple-reminders.js.map +1 -0
  13. package/dist/bootstrap.d.ts +11 -0
  14. package/dist/bootstrap.js +44 -0
  15. package/dist/bootstrap.js.map +1 -0
  16. package/dist/index.d.ts +10 -0
  17. package/dist/index.js +12 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/server/handlers.d.ts +12 -0
  20. package/dist/server/handlers.js +58 -0
  21. package/dist/server/handlers.js.map +1 -0
  22. package/dist/server/mode.d.ts +19 -0
  23. package/dist/server/mode.js +47 -0
  24. package/dist/server/mode.js.map +1 -0
  25. package/dist/server/promptAbstractions.d.ts +61 -0
  26. package/dist/server/promptAbstractions.js +140 -0
  27. package/dist/server/promptAbstractions.js.map +1 -0
  28. package/dist/server/prompts.d.ts +13 -0
  29. package/dist/server/prompts.js +461 -0
  30. package/dist/server/prompts.js.map +1 -0
  31. package/dist/server/server.d.ts +61 -0
  32. package/dist/server/server.js +112 -0
  33. package/dist/server/server.js.map +1 -0
  34. package/dist/tools/definitions.d.ts +9 -0
  35. package/dist/tools/definitions.js +272 -0
  36. package/dist/tools/definitions.js.map +1 -0
  37. package/dist/tools/handlers/calendarHandlers.d.ts +11 -0
  38. package/dist/tools/handlers/calendarHandlers.js +151 -0
  39. package/dist/tools/handlers/calendarHandlers.js.map +1 -0
  40. package/dist/tools/handlers/formatters.d.ts +24 -0
  41. package/dist/tools/handlers/formatters.js +105 -0
  42. package/dist/tools/handlers/formatters.js.map +1 -0
  43. package/dist/tools/handlers/index.d.ts +8 -0
  44. package/dist/tools/handlers/index.js +9 -0
  45. package/dist/tools/handlers/index.js.map +1 -0
  46. package/dist/tools/handlers/listHandlers.d.ts +10 -0
  47. package/dist/tools/handlers/listHandlers.js +47 -0
  48. package/dist/tools/handlers/listHandlers.js.map +1 -0
  49. package/dist/tools/handlers/reminderHandlers.d.ts +10 -0
  50. package/dist/tools/handlers/reminderHandlers.js +219 -0
  51. package/dist/tools/handlers/reminderHandlers.js.map +1 -0
  52. package/dist/tools/handlers/shared.d.ts +28 -0
  53. package/dist/tools/handlers/shared.js +57 -0
  54. package/dist/tools/handlers/shared.js.map +1 -0
  55. package/dist/tools/handlers/subtaskHandlers.d.ts +12 -0
  56. package/dist/tools/handlers/subtaskHandlers.js +113 -0
  57. package/dist/tools/handlers/subtaskHandlers.js.map +1 -0
  58. package/dist/tools/index.d.ts +10 -0
  59. package/dist/tools/index.js +74 -0
  60. package/dist/tools/index.js.map +1 -0
  61. package/dist/types/index.d.ts +263 -0
  62. package/dist/types/index.js +42 -0
  63. package/dist/types/index.js.map +1 -0
  64. package/dist/types/prompts.d.ts +88 -0
  65. package/dist/types/prompts.js +6 -0
  66. package/dist/types/prompts.js.map +1 -0
  67. package/dist/types/repository.d.ts +197 -0
  68. package/dist/types/repository.js +14 -0
  69. package/dist/types/repository.js.map +1 -0
  70. package/dist/utils/binaryValidator.d.ts +52 -0
  71. package/dist/utils/binaryValidator.js +185 -0
  72. package/dist/utils/binaryValidator.js.map +1 -0
  73. package/dist/utils/calendarRepository.d.ts +49 -0
  74. package/dist/utils/calendarRepository.js +226 -0
  75. package/dist/utils/calendarRepository.js.map +1 -0
  76. package/dist/utils/constants.d.ts +79 -0
  77. package/dist/utils/constants.js +80 -0
  78. package/dist/utils/constants.js.map +1 -0
  79. package/dist/utils/dateFiltering.d.ts +41 -0
  80. package/dist/utils/dateFiltering.js +129 -0
  81. package/dist/utils/dateFiltering.js.map +1 -0
  82. package/dist/utils/dateUtils.d.ts +27 -0
  83. package/dist/utils/dateUtils.js +71 -0
  84. package/dist/utils/dateUtils.js.map +1 -0
  85. package/dist/utils/errorHandling.d.ts +27 -0
  86. package/dist/utils/errorHandling.js +88 -0
  87. package/dist/utils/errorHandling.js.map +1 -0
  88. package/dist/utils/eventCli.d.ts +40 -0
  89. package/dist/utils/eventCli.js +294 -0
  90. package/dist/utils/eventCli.js.map +1 -0
  91. package/dist/utils/helpers.d.ts +35 -0
  92. package/dist/utils/helpers.js +61 -0
  93. package/dist/utils/helpers.js.map +1 -0
  94. package/dist/utils/looseDateParser.d.ts +19 -0
  95. package/dist/utils/looseDateParser.js +149 -0
  96. package/dist/utils/looseDateParser.js.map +1 -0
  97. package/dist/utils/projectUtils.d.ts +13 -0
  98. package/dist/utils/projectUtils.js +92 -0
  99. package/dist/utils/projectUtils.js.map +1 -0
  100. package/dist/utils/reminderDateParser.d.ts +8 -0
  101. package/dist/utils/reminderDateParser.js +89 -0
  102. package/dist/utils/reminderDateParser.js.map +1 -0
  103. package/dist/utils/reminderRepository.d.ts +33 -0
  104. package/dist/utils/reminderRepository.js +262 -0
  105. package/dist/utils/reminderRepository.js.map +1 -0
  106. package/dist/utils/subtaskUtils.d.ts +103 -0
  107. package/dist/utils/subtaskUtils.js +253 -0
  108. package/dist/utils/subtaskUtils.js.map +1 -0
  109. package/dist/utils/tagUtils.d.ts +58 -0
  110. package/dist/utils/tagUtils.js +177 -0
  111. package/dist/utils/tagUtils.js.map +1 -0
  112. package/dist/utils/timeHelpers.d.ts +40 -0
  113. package/dist/utils/timeHelpers.js +136 -0
  114. package/dist/utils/timeHelpers.js.map +1 -0
  115. package/dist/validation/schemas.d.ts +385 -0
  116. package/dist/validation/schemas.js +579 -0
  117. package/dist/validation/schemas.js.map +1 -0
  118. package/package.json +96 -0
  119. package/scripts/build-event.mjs +513 -0
  120. package/scripts/disclaim.c +69 -0
  121. package/scripts/event-Info.plist +29 -0
  122. package/scripts/event.entitlements +10 -0
  123. package/scripts/postinstall.mjs +91 -0
@@ -0,0 +1,92 @@
1
+ /**
2
+ * projectUtils.ts
3
+ * Shared utilities for project-related operations
4
+ */
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { FILE_SYSTEM } from './constants.js';
9
+ // Project root is immutable for the lifetime of the process. Cache the first
10
+ // resolved path so subsequent MCP tool calls skip the up-to-10-level walk +
11
+ // package.json reads. Cleared by tests via `__clearProjectRootCache`.
12
+ let cachedProjectRoot = null;
13
+ /**
14
+ * Finds the project root directory by looking for package.json
15
+ * @param maxDepth - Maximum directory levels to traverse upward
16
+ * @returns Project root directory path
17
+ * @throws Error if project root cannot be found
18
+ */
19
+ export function findProjectRoot(maxDepth = FILE_SYSTEM.MAX_DIRECTORY_SEARCH_DEPTH) {
20
+ if (cachedProjectRoot) {
21
+ return cachedProjectRoot;
22
+ }
23
+ const currentDir = getCurrentModuleDir();
24
+ const root = locateProjectRoot(currentDir, maxDepth);
25
+ if (root) {
26
+ cachedProjectRoot = root;
27
+ return root;
28
+ }
29
+ throw new Error(`Project root not found within ${maxDepth} directory levels`);
30
+ }
31
+ /** Test helper — drops the cached project root so a fresh resolve happens. */
32
+ export function __clearProjectRootCache() {
33
+ cachedProjectRoot = null;
34
+ }
35
+ /**
36
+ * Attempts to find the project root starting from the provided directory.
37
+ * @param startDir - Directory to begin the search from
38
+ * @param maxDepth - Maximum directory levels to traverse upward
39
+ * @returns The project root when found, otherwise `undefined`
40
+ */
41
+ function locateProjectRoot(startDir, maxDepth = FILE_SYSTEM.MAX_DIRECTORY_SEARCH_DEPTH) {
42
+ let currentDir = startDir;
43
+ let depth = 0;
44
+ while (depth < maxDepth) {
45
+ if (isCorrectProjectRoot(currentDir)) {
46
+ return currentDir;
47
+ }
48
+ const parentDir = path.dirname(currentDir);
49
+ if (parentDir === currentDir) {
50
+ break; // Reached filesystem root
51
+ }
52
+ currentDir = parentDir;
53
+ depth++;
54
+ }
55
+ return undefined;
56
+ }
57
+ /**
58
+ * Checks if a directory contains the correct package.json for this project
59
+ */
60
+ function isCorrectProjectRoot(dir) {
61
+ const packageJsonPath = path.join(dir, FILE_SYSTEM.PACKAGE_JSON_FILENAME);
62
+ if (!fs.existsSync(packageJsonPath)) {
63
+ return false;
64
+ }
65
+ try {
66
+ const packageContent = fs.readFileSync(packageJsonPath, 'utf8');
67
+ const packageData = JSON.parse(packageContent);
68
+ // Accept both this package name and the upstream name: test fixtures and
69
+ // future upstream merges share the same detection logic.
70
+ return (packageData.name === '@laylzj777/my-apple-apple-eventkit' ||
71
+ packageData.name === 'mcp-server-apple-events');
72
+ }
73
+ catch {
74
+ // Silently return false for any read/parse errors during project root discovery
75
+ // This allows the search to continue up the directory tree
76
+ return false;
77
+ }
78
+ }
79
+ /**
80
+ * Get the current module's directory
81
+ * Handles both production and test environments
82
+ */
83
+ function getCurrentModuleDir() {
84
+ if (process.env.NODE_ENV === 'test') {
85
+ return path.join(process.cwd(), 'src', 'utils');
86
+ }
87
+ // In production, use import.meta.url
88
+ // This line is excluded from coverage due to Jest ESM limitations
89
+ /* istanbul ignore next */
90
+ return path.dirname(fileURLToPath(import.meta.url));
91
+ }
92
+ //# sourceMappingURL=projectUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projectUtils.js","sourceRoot":"","sources":["../../src/utils/projectUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,6EAA6E;AAC7E,4EAA4E;AAC5E,sEAAsE;AACtE,IAAI,iBAAiB,GAAkB,IAAI,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAQ,GAAG,WAAW,CAAC,0BAA0B;IAEjD,IAAI,iBAAiB,EAAE,CAAC;QACtB,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IACD,MAAM,UAAU,GAAG,mBAAmB,EAAE,CAAC;IACzC,MAAM,IAAI,GAAG,iBAAiB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAErD,IAAI,IAAI,EAAE,CAAC;QACT,iBAAiB,GAAG,IAAI,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,iCAAiC,QAAQ,mBAAmB,CAAC,CAAC;AAChF,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,uBAAuB;IACrC,iBAAiB,GAAG,IAAI,CAAC;AAC3B,CAAC;AAED;;;;;GAKG;AACH,SAAS,iBAAiB,CACxB,QAAgB,EAChB,QAAQ,GAAG,WAAW,CAAC,0BAA0B;IAEjD,IAAI,UAAU,GAAG,QAAQ,CAAC;IAC1B,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,OAAO,KAAK,GAAG,QAAQ,EAAE,CAAC;QACxB,IAAI,oBAAoB,CAAC,UAAU,CAAC,EAAE,CAAC;YACrC,OAAO,UAAU,CAAC;QACpB,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC3C,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;YAC7B,MAAM,CAAC,0BAA0B;QACnC,CAAC;QAED,UAAU,GAAG,SAAS,CAAC;QACvB,KAAK,EAAE,CAAC;IACV,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,SAAS,oBAAoB,CAAC,GAAW;IACvC,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,qBAAqB,CAAC,CAAC;IAC1E,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QACpC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC;QACH,MAAM,cAAc,GAAG,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;QAChE,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QAC/C,yEAAyE;QACzE,yDAAyD;QACzD,OAAO,CACL,WAAW,CAAC,IAAI,KAAK,oCAAoC;YACzD,WAAW,CAAC,IAAI,KAAK,yBAAyB,CAC/C,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,gFAAgF;QAChF,2DAA2D;QAC3D,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,mBAAmB;IAC1B,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAED,qCAAqC;IACrC,kEAAkE;IAClE,0BAA0B;IAC1B,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACtD,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * reminderDateParser.ts
3
+ * Helper utilities for parsing reminder dueDate strings safely across timezones.
4
+ */
5
+ /**
6
+ * Parses reminder dueDate strings into Date objects without losing local timezone semantics.
7
+ */
8
+ export declare const parseReminderDueDate: (dueDate?: string | null) => Date | undefined;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * reminderDateParser.ts
3
+ * Helper utilities for parsing reminder dueDate strings safely across timezones.
4
+ */
5
+ const DATE_ONLY_REGEX = /^(\d{4})-(\d{2})-(\d{2})$/;
6
+ const DATE_ONLY_WITH_TZ_REGEX = /^(\d{4}-\d{2}-\d{2})(Z|[+-]\d{2}:?\d{2})$/i;
7
+ const DATE_TIME_NO_TZ_REGEX = /^(\d{4})-(\d{2})-(\d{2})[ T]+(\d{2}):(\d{2})(?::(\d{2}))?(?:\.\d+)?$/;
8
+ const TIMEZONE_SUFFIX_REGEX = /(Z|[+-]\d{2}(?::?\d{2})?)$/i;
9
+ const toNumber = (value) => Number.parseInt(value, 10);
10
+ const createLocalDate = (year, month, day, hour = 0, minute = 0, second = 0) => {
11
+ if ([year, month, day, hour, minute, second].some(Number.isNaN)) {
12
+ return undefined;
13
+ }
14
+ const date = new Date(year, month - 1, day, hour, minute, second, 0);
15
+ // Validate that the date wasn't auto-corrected by JavaScript
16
+ if (date.getFullYear() !== year ||
17
+ date.getMonth() !== month - 1 ||
18
+ date.getDate() !== day ||
19
+ date.getHours() !== hour ||
20
+ date.getMinutes() !== minute ||
21
+ date.getSeconds() !== second) {
22
+ return undefined;
23
+ }
24
+ return date;
25
+ };
26
+ const normalizeTimezoneSegment = (segment) => {
27
+ if (!segment)
28
+ return segment;
29
+ if (segment === 'Z' || segment === 'z')
30
+ return 'Z';
31
+ const clean = segment.replace(':', '').replace(' ', '');
32
+ if (clean.length === 3) {
33
+ return `${clean}:00`;
34
+ }
35
+ if (clean.length === 5) {
36
+ return `${clean.slice(0, 3)}:${clean.slice(3)}`;
37
+ }
38
+ return segment.includes(':')
39
+ ? segment
40
+ : `${segment.slice(0, 3)}:${segment.slice(3)}`;
41
+ };
42
+ const normalizeIsoString = (value) => {
43
+ let normalized = value.trim();
44
+ if (normalized.includes(' ') && normalized.indexOf(' ') === 10) {
45
+ normalized = `${normalized.slice(0, 10)}T${normalized.slice(11)}`;
46
+ }
47
+ normalized = normalized.replace(TIMEZONE_SUFFIX_REGEX, (match) => normalizeTimezoneSegment(match));
48
+ return normalized;
49
+ };
50
+ const parseWithNative = (value) => {
51
+ const normalized = normalizeIsoString(value);
52
+ const parsed = new Date(normalized);
53
+ return Number.isNaN(parsed.getTime()) ? undefined : parsed;
54
+ };
55
+ /**
56
+ * Parses reminder dueDate strings into Date objects without losing local timezone semantics.
57
+ */
58
+ export const parseReminderDueDate = (dueDate) => {
59
+ if (!dueDate)
60
+ return undefined;
61
+ const trimmed = dueDate.trim();
62
+ if (!trimmed)
63
+ return undefined;
64
+ const dateOnlyMatch = trimmed.match(DATE_ONLY_REGEX);
65
+ if (dateOnlyMatch) {
66
+ // Groups 1-3 are mandatory in DATE_ONLY_REGEX, so they are always defined.
67
+ return createLocalDate(toNumber(dateOnlyMatch[1]), toNumber(dateOnlyMatch[2]), toNumber(dateOnlyMatch[3]));
68
+ }
69
+ const dateWithTzMatch = trimmed.match(DATE_ONLY_WITH_TZ_REGEX);
70
+ if (dateWithTzMatch) {
71
+ return parseWithNative(`${dateWithTzMatch[1]}T00:00:00${normalizeTimezoneSegment(dateWithTzMatch[2])}`);
72
+ }
73
+ const localDateTimeMatch = trimmed.match(DATE_TIME_NO_TZ_REGEX);
74
+ if (localDateTimeMatch) {
75
+ // Groups 1-5 are mandatory; group 6 (seconds) is optional.
76
+ const secondStr = localDateTimeMatch[6];
77
+ return createLocalDate(toNumber(localDateTimeMatch[1]), toNumber(localDateTimeMatch[2]), toNumber(localDateTimeMatch[3]), toNumber(localDateTimeMatch[4]), toNumber(localDateTimeMatch[5]), secondStr ? toNumber(secondStr) : 0);
78
+ }
79
+ if (TIMEZONE_SUFFIX_REGEX.test(trimmed)) {
80
+ return parseWithNative(trimmed);
81
+ }
82
+ // Reject strings that don't match expected date format patterns
83
+ // Valid formats should only contain: digits, hyphens, colons, spaces, T, Z, +, -, and dots
84
+ if (!/^[\d\-:T\s.Z+]+$/.test(trimmed)) {
85
+ return undefined;
86
+ }
87
+ return parseWithNative(trimmed);
88
+ };
89
+ //# sourceMappingURL=reminderDateParser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reminderDateParser.js","sourceRoot":"","sources":["../../src/utils/reminderDateParser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * reminderRepository.ts
3
+ * Repository for reminder data access via the vendored `event` CLI.
4
+ *
5
+ * Tags and subtasks are TS-managed in the notes field (`[#tag]` and
6
+ * `---SUBTASKS---` blocks); the `event --tags` / `--parentTitle` flags are
7
+ * not used so the AdvancedReminderEdit Shortcut is not required.
8
+ */
9
+ import type { Reminder, ReminderList } from '../types/index.js';
10
+ import type { CreateReminderData, IReminderRepository, ReminderJSON, UpdateReminderData } from '../types/repository.js';
11
+ import type { ReminderFilters } from './dateFiltering.js';
12
+ declare class ReminderRepository implements IReminderRepository {
13
+ private mapReminder;
14
+ private mapReminders;
15
+ private readAllRemindersIncludingCompleted;
16
+ findReminderById(id: string): Promise<Reminder>;
17
+ findReminders(filters?: ReminderFilters): Promise<Reminder[]>;
18
+ findAllLists(): Promise<ReminderList[]>;
19
+ createReminder(data: CreateReminderData): Promise<ReminderJSON>;
20
+ updateReminder(data: UpdateReminderData): Promise<ReminderJSON>;
21
+ deleteReminder(id: string): Promise<void>;
22
+ createReminderList(name: string): Promise<ReminderList>;
23
+ /**
24
+ * Resolves a list name to its EventKit identifier. `event reminders lists
25
+ * update|delete` are keyed by id, not name, so we have to round-trip
26
+ * through `lists list` whenever a caller targets a list by name.
27
+ */
28
+ private resolveListIdByName;
29
+ updateReminderList(currentName: string, newName: string): Promise<ReminderList>;
30
+ deleteReminderList(name: string): Promise<void>;
31
+ }
32
+ export declare const reminderRepository: ReminderRepository;
33
+ export { ReminderRepository };
@@ -0,0 +1,262 @@
1
+ /**
2
+ * reminderRepository.ts
3
+ * Repository for reminder data access via the vendored `event` CLI.
4
+ *
5
+ * Tags and subtasks are TS-managed in the notes field (`[#tag]` and
6
+ * `---SUBTASKS---` blocks); the `event --tags` / `--parentTitle` flags are
7
+ * not used so the AdvancedReminderEdit Shortcut is not required.
8
+ */
9
+ import { applyReminderFilters, prefilterReminderJsons, } from './dateFiltering.js';
10
+ import { CliUserError } from './errorHandling.js';
11
+ import { executeEventCliJson, executeEventCliPlain } from './eventCli.js';
12
+ import { addOptionalArg, addOptionalNumberArg, nullToUndefined, } from './helpers.js';
13
+ import { getSubtaskProgress, parseSubtasks } from './subtaskUtils.js';
14
+ import { extractTags } from './tagUtils.js';
15
+ const VALID_ALARM_TYPES = ['display', 'audio', 'procedure', 'email'];
16
+ const isValidAlarmType = (value) => VALID_ALARM_TYPES.includes(value);
17
+ const mapAlarmType = (alarmType) => alarmType && isValidAlarmType(alarmType) ? alarmType : undefined;
18
+ /**
19
+ * `event` returns reminders without a top-level `locationTrigger` field —
20
+ * geofence triggers live inside the `alarms[].locationTrigger` shape. The
21
+ * downstream formatter / icon logic still keys off `reminder.locationTrigger`
22
+ * to render 📍, so derive the first alarm's location trigger here to preserve
23
+ * the existing read-side rendering. Alarms whose proximity is `'none'`
24
+ * (`EKAlarmProximityNone` — geofence configured but no fire condition) are
25
+ * skipped so they don't show up as misleading "Arriving at..." entries.
26
+ */
27
+ const deriveTopLevelLocationTrigger = (alarms) => {
28
+ if (!alarms || alarms.length === 0)
29
+ return null;
30
+ for (const alarm of alarms) {
31
+ if (alarm?.locationTrigger && alarm.locationTrigger.proximity !== 'none') {
32
+ return alarm.locationTrigger;
33
+ }
34
+ }
35
+ return null;
36
+ };
37
+ // Maps the wire-level proximity union (event emits 'enter' / 'leave' / 'none')
38
+ // onto the domain union ('enter' / 'leave'). 'none' returns undefined so the
39
+ // caller can skip surfacing an unconfigured trigger.
40
+ const mapProximity = (proximity) => proximity === 'leave' ? 'leave' : proximity === 'enter' ? 'enter' : undefined;
41
+ const mapList = (list) => ({
42
+ id: list.id,
43
+ title: list.title,
44
+ color: list.color ?? undefined,
45
+ });
46
+ class ReminderRepository {
47
+ mapReminder(reminder) {
48
+ const normalizedReminder = nullToUndefined(reminder, [
49
+ 'notes',
50
+ 'url',
51
+ 'dueDate',
52
+ 'startDate',
53
+ 'completionDate',
54
+ 'location',
55
+ 'timeZone',
56
+ 'creationDate',
57
+ 'lastModifiedDate',
58
+ 'externalId',
59
+ ]);
60
+ const mapRecurrenceRule = (rule) => ({
61
+ frequency: rule.frequency,
62
+ interval: rule.interval ?? 1,
63
+ endDate: rule.endDate ?? undefined,
64
+ occurrenceCount: rule.occurrenceCount ?? undefined,
65
+ daysOfWeek: rule.daysOfWeek ?? undefined,
66
+ daysOfMonth: rule.daysOfMonth ?? undefined,
67
+ monthsOfYear: rule.monthsOfYear ?? undefined,
68
+ });
69
+ if (reminder.recurrenceRules && reminder.recurrenceRules.length > 0) {
70
+ normalizedReminder.recurrenceRules =
71
+ reminder.recurrenceRules.map(mapRecurrenceRule);
72
+ }
73
+ const derivedLocationTrigger = reminder.locationTrigger ??
74
+ deriveTopLevelLocationTrigger(reminder.alarms);
75
+ if (derivedLocationTrigger) {
76
+ const proximity = mapProximity(derivedLocationTrigger.proximity);
77
+ if (proximity) {
78
+ normalizedReminder.locationTrigger = {
79
+ title: derivedLocationTrigger.title,
80
+ latitude: derivedLocationTrigger.latitude,
81
+ longitude: derivedLocationTrigger.longitude,
82
+ radius: derivedLocationTrigger.radius,
83
+ proximity,
84
+ };
85
+ }
86
+ }
87
+ if (reminder.alarms && reminder.alarms.length > 0) {
88
+ normalizedReminder.alarms = reminder.alarms
89
+ .filter((alarm) => alarm !== null)
90
+ .map((alarm) => {
91
+ const alarmProximity = alarm.locationTrigger
92
+ ? mapProximity(alarm.locationTrigger.proximity)
93
+ : undefined;
94
+ return {
95
+ relativeOffset: alarm.relativeOffset ?? undefined,
96
+ absoluteDate: alarm.absoluteDate ?? undefined,
97
+ alarmType: mapAlarmType(alarm.alarmType),
98
+ locationTrigger: alarm.locationTrigger && alarmProximity
99
+ ? {
100
+ title: alarm.locationTrigger.title,
101
+ latitude: alarm.locationTrigger.latitude,
102
+ longitude: alarm.locationTrigger.longitude,
103
+ radius: alarm.locationTrigger.radius,
104
+ proximity: alarmProximity,
105
+ }
106
+ : undefined,
107
+ };
108
+ });
109
+ }
110
+ const tags = extractTags(reminder.notes);
111
+ if (tags.length > 0) {
112
+ normalizedReminder.tags = tags;
113
+ }
114
+ const subtasks = parseSubtasks(reminder.notes);
115
+ if (subtasks.length > 0) {
116
+ normalizedReminder.subtasks = subtasks;
117
+ normalizedReminder.subtaskProgress = getSubtaskProgress(subtasks);
118
+ }
119
+ return normalizedReminder;
120
+ }
121
+ mapReminders(reminders) {
122
+ return reminders.map((reminder) => this.mapReminder(reminder));
123
+ }
124
+ // Reads every reminder (including completed) once so we can `.find` by id.
125
+ // `event reminders list` is the only event command that surfaces a reminder
126
+ // by id, since there's no equivalent of the old `read-by-id` action.
127
+ async readAllRemindersIncludingCompleted() {
128
+ return executeEventCliJson([
129
+ 'reminders',
130
+ 'list',
131
+ '--completed',
132
+ '--json',
133
+ ]);
134
+ }
135
+ async findReminderById(id) {
136
+ const all = await this.readAllRemindersIncludingCompleted();
137
+ const match = all.find((r) => r.id === id);
138
+ if (!match) {
139
+ throw new CliUserError(`Reminder with ID '${id}' not found.`);
140
+ }
141
+ return this.mapReminder(match);
142
+ }
143
+ async findReminders(filters = {}) {
144
+ const args = ['reminders', 'list'];
145
+ addOptionalArg(args, '--list', filters.list);
146
+ if (filters.showCompleted) {
147
+ args.push('--completed');
148
+ }
149
+ args.push('--json');
150
+ const reminders = await executeEventCliJson(args);
151
+ // Apply the cheap structural filters on raw JSON before `mapReminder`'s
152
+ // regex scans for tags / subtasks — important when the result set is
153
+ // large and the user wants a narrow slice (e.g. priority='high').
154
+ const preFiltered = prefilterReminderJsons(reminders, filters);
155
+ const normalized = this.mapReminders(preFiltered);
156
+ return applyReminderFilters(normalized, {
157
+ ...filters,
158
+ // `event` filters out completed reminders unless `--completed` is set;
159
+ // list filtering is done by event too. Priority/recurring/locationBased
160
+ // were already applied to JSON. Leave only search/dueWithin/tags for
161
+ // the post-map pass.
162
+ showCompleted: undefined,
163
+ list: undefined,
164
+ priority: undefined,
165
+ recurring: undefined,
166
+ locationBased: undefined,
167
+ });
168
+ }
169
+ async findAllLists() {
170
+ const lists = await executeEventCliJson([
171
+ 'reminders',
172
+ 'lists',
173
+ 'list',
174
+ '--json',
175
+ ]);
176
+ return lists.map(mapList);
177
+ }
178
+ async createReminder(data) {
179
+ const args = ['reminders', 'create', '--title', data.title];
180
+ addOptionalArg(args, '--list', data.list);
181
+ addOptionalArg(args, '--notes', data.notes);
182
+ addOptionalArg(args, '--url', data.url);
183
+ addOptionalArg(args, '--due', data.dueDate);
184
+ addOptionalNumberArg(args, '--priority', data.priority);
185
+ // Skip the AdvancedReminderEdit Shortcut path — tags / subtasks live in
186
+ // the notes field (see tagUtils / subtaskUtils).
187
+ args.push('--no-shortcuts', '--json');
188
+ return executeEventCliJson(args);
189
+ }
190
+ async updateReminder(data) {
191
+ // Cross-list moves and `isCompleted: false` rejected by the handler so
192
+ // we share its `findReminderById` fetch with the notes-rebuild path.
193
+ const args = ['reminders', 'update', '--id', data.id];
194
+ addOptionalArg(args, '--title', data.newTitle);
195
+ addOptionalArg(args, '--notes', data.notes);
196
+ addOptionalArg(args, '--url', data.url);
197
+ addOptionalArg(args, '--due', data.dueDate);
198
+ addOptionalArg(args, '--start', data.startDate);
199
+ addOptionalNumberArg(args, '--priority', data.priority);
200
+ if (data.isCompleted !== undefined) {
201
+ args.push('--completed', data.isCompleted ? 'true' : 'false');
202
+ }
203
+ args.push('--no-shortcuts', '--json');
204
+ return executeEventCliJson(args);
205
+ }
206
+ async deleteReminder(id) {
207
+ // `event reminders delete` prints a status line ("Reminder deleted
208
+ // successfully") instead of JSON. Use the plain-text executor so we don't
209
+ // attempt to JSON.parse the success message.
210
+ await executeEventCliPlain(['reminders', 'delete', '--id', id]);
211
+ }
212
+ async createReminderList(name) {
213
+ const listJson = await executeEventCliJson([
214
+ 'reminders',
215
+ 'lists',
216
+ 'create',
217
+ '--name',
218
+ name,
219
+ '--json',
220
+ ]);
221
+ return mapList(listJson);
222
+ }
223
+ /**
224
+ * Resolves a list name to its EventKit identifier. `event reminders lists
225
+ * update|delete` are keyed by id, not name, so we have to round-trip
226
+ * through `lists list` whenever a caller targets a list by name.
227
+ */
228
+ async resolveListIdByName(name) {
229
+ const lists = await executeEventCliJson([
230
+ 'reminders',
231
+ 'lists',
232
+ 'list',
233
+ '--json',
234
+ ]);
235
+ const match = lists.find((list) => list.title === name);
236
+ if (!match) {
237
+ throw new CliUserError(`List '${name}' not found.`);
238
+ }
239
+ return match.id;
240
+ }
241
+ async updateReminderList(currentName, newName) {
242
+ const id = await this.resolveListIdByName(currentName);
243
+ const listJson = await executeEventCliJson([
244
+ 'reminders',
245
+ 'lists',
246
+ 'update',
247
+ '--id',
248
+ id,
249
+ '--name',
250
+ newName,
251
+ '--json',
252
+ ]);
253
+ return mapList(listJson);
254
+ }
255
+ async deleteReminderList(name) {
256
+ const id = await this.resolveListIdByName(name);
257
+ await executeEventCliPlain(['reminders', 'lists', 'delete', '--id', id]);
258
+ }
259
+ }
260
+ export const reminderRepository = new ReminderRepository();
261
+ export { ReminderRepository };
262
+ //# sourceMappingURL=reminderRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reminderRepository.js","sourceRoot":"","sources":["../../src/utils/reminderRepository.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAaH,OAAO,EACL,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,CAAU,CAAC;AAE9E,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAsB,EAAE,CAC7D,iBAAuC,CAAC,QAAQ,CAAC,KAAe,CAAC,CAAC;AACrE,MAAM,YAAY,GAAG,CACnB,SAAoC,EACb,EAAE,CACzB,SAAS,IAAI,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;AAEnE;;;;;;;;GAQG;AACH,MAAM,6BAA6B,GAAG,CACpC,MAAsC,EACL,EAAE;IACnC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAChD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,EAAE,eAAe,IAAI,KAAK,CAAC,eAAe,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;YACzE,OAAO,KAAK,CAAC,eAAe,CAAC;QAC/B,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,+EAA+E;AAC/E,6EAA6E;AAC7E,qDAAqD;AACrD,MAAM,YAAY,GAAG,CACnB,SAAqC,EACN,EAAE,CACjC,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AAEhF,MAAM,OAAO,GAAG,CAAC,IAAc,EAAgB,EAAE,CAAC,CAAC;IACjD,EAAE,EAAE,IAAI,CAAC,EAAE;IACX,KAAK,EAAE,IAAI,CAAC,KAAK;IACjB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,SAAS;CAC/B,CAAC,CAAC;AAEH,MAAM,kBAAkB;IACd,WAAW,CAAC,QAAsB;QACxC,MAAM,kBAAkB,GAAG,eAAe,CAAC,QAAQ,EAAE;YACnD,OAAO;YACP,KAAK;YACL,SAAS;YACT,WAAW;YACX,gBAAgB;YAChB,UAAU;YACV,UAAU;YACV,cAAc;YACd,kBAAkB;YAClB,YAAY;SACb,CAAa,CAAC;QAEf,MAAM,iBAAiB,GAAG,CAAC,IAAwB,EAAE,EAAE,CAAC,CAAC;YACvD,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,CAAC;YAC5B,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,SAAS;YAClC,eAAe,EAAE,IAAI,CAAC,eAAe,IAAI,SAAS;YAClD,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,SAAS;YACxC,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,SAAS;YAC1C,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,SAAS;SAC7C,CAAC,CAAC;QAEH,IAAI,QAAQ,CAAC,eAAe,IAAI,QAAQ,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpE,kBAAkB,CAAC,eAAe;gBAChC,QAAQ,CAAC,eAAe,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;QACpD,CAAC;QAED,MAAM,sBAAsB,GAC1B,QAAQ,CAAC,eAAe;YACxB,6BAA6B,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACjD,IAAI,sBAAsB,EAAE,CAAC;YAC3B,MAAM,SAAS,GAAG,YAAY,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC;YACjE,IAAI,SAAS,EAAE,CAAC;gBACd,kBAAkB,CAAC,eAAe,GAAG;oBACnC,KAAK,EAAE,sBAAsB,CAAC,KAAK;oBACnC,QAAQ,EAAE,sBAAsB,CAAC,QAAQ;oBACzC,SAAS,EAAE,sBAAsB,CAAC,SAAS;oBAC3C,MAAM,EAAE,sBAAsB,CAAC,MAAM;oBACrC,SAAS;iBACV,CAAC;YACJ,CAAC;QACH,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClD,kBAAkB,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM;iBACxC,MAAM,CAAC,CAAC,KAAK,EAAsB,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC;iBACrD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;gBACb,MAAM,cAAc,GAAG,KAAK,CAAC,eAAe;oBAC1C,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,eAAe,CAAC,SAAS,CAAC;oBAC/C,CAAC,CAAC,SAAS,CAAC;gBACd,OAAO;oBACL,cAAc,EAAE,KAAK,CAAC,cAAc,IAAI,SAAS;oBACjD,YAAY,EAAE,KAAK,CAAC,YAAY,IAAI,SAAS;oBAC7C,SAAS,EAAE,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC;oBACxC,eAAe,EACb,KAAK,CAAC,eAAe,IAAI,cAAc;wBACrC,CAAC,CAAC;4BACE,KAAK,EAAE,KAAK,CAAC,eAAe,CAAC,KAAK;4BAClC,QAAQ,EAAE,KAAK,CAAC,eAAe,CAAC,QAAQ;4BACxC,SAAS,EAAE,KAAK,CAAC,eAAe,CAAC,SAAS;4BAC1C,MAAM,EAAE,KAAK,CAAC,eAAe,CAAC,MAAM;4BACpC,SAAS,EAAE,cAAc;yBAC1B;wBACH,CAAC,CAAC,SAAS;iBAChB,CAAC;YACJ,CAAC,CAAC,CAAC;QACP,CAAC;QAED,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACzC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,kBAAkB,CAAC,IAAI,GAAG,IAAI,CAAC;QACjC,CAAC;QAED,MAAM,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC/C,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,kBAAkB,CAAC,QAAQ,GAAG,QAAQ,CAAC;YACvC,kBAAkB,CAAC,eAAe,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QACpE,CAAC;QAED,OAAO,kBAAkB,CAAC;IAC5B,CAAC;IAEO,YAAY,CAAC,SAAyB;QAC5C,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,2EAA2E;IAC3E,4EAA4E;IAC5E,qEAAqE;IAC7D,KAAK,CAAC,kCAAkC;QAC9C,OAAO,mBAAmB,CAAiB;YACzC,WAAW;YACX,MAAM;YACN,aAAa;YACb,QAAQ;SACT,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,EAAU;QAC/B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,kCAAkC,EAAE,CAAC;QAC5D,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,YAAY,CAAC,qBAAqB,EAAE,cAAc,CAAC,CAAC;QAChE,CAAC;QACD,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,UAA2B,EAAE;QAC/C,MAAM,IAAI,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QACnC,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC3B,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEpB,MAAM,SAAS,GAAG,MAAM,mBAAmB,CAAiB,IAAI,CAAC,CAAC;QAElE,wEAAwE;QACxE,qEAAqE;QACrE,kEAAkE;QAClE,MAAM,WAAW,GAAG,sBAAsB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAC/D,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;QAElD,OAAO,oBAAoB,CAAC,UAAU,EAAE;YACtC,GAAG,OAAO;YACV,uEAAuE;YACvE,wEAAwE;YACxE,qEAAqE;YACrE,qBAAqB;YACrB,aAAa,EAAE,SAAS;YACxB,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE,SAAS;YACnB,SAAS,EAAE,SAAS;YACpB,aAAa,EAAE,SAAS;SACzB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,MAAM,KAAK,GAAG,MAAM,mBAAmB,CAAa;YAClD,WAAW;YACX,OAAO;YACP,MAAM;YACN,QAAQ;SACT,CAAC,CAAC;QACH,OAAO,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,IAAwB;QAC3C,MAAM,IAAI,GAAG,CAAC,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5D,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5C,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QACxC,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5C,oBAAoB,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxD,wEAAwE;QACxE,iDAAiD;QACjD,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;QACtC,OAAO,mBAAmB,CAAe,IAAI,CAAC,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,IAAwB;QAC3C,uEAAuE;QACvE,qEAAqE;QACrE,MAAM,IAAI,GAAG,CAAC,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QACtD,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC/C,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5C,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QACxC,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5C,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAChD,oBAAoB,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxD,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;YACnC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAChE,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;QACtC,OAAO,mBAAmB,CAAe,IAAI,CAAC,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,EAAU;QAC7B,mEAAmE;QACnE,0EAA0E;QAC1E,6CAA6C;QAC7C,MAAM,oBAAoB,CAAC,CAAC,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,IAAY;QACnC,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAW;YACnD,WAAW;YACX,OAAO;YACP,QAAQ;YACR,QAAQ;YACR,IAAI;YACJ,QAAQ;SACT,CAAC,CAAC;QACH,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC3B,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,mBAAmB,CAAC,IAAY;QAC5C,MAAM,KAAK,GAAG,MAAM,mBAAmB,CAAa;YAClD,WAAW;YACX,OAAO;YACP,MAAM;YACN,QAAQ;SACT,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;QACxD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,YAAY,CAAC,SAAS,IAAI,cAAc,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,KAAK,CAAC,EAAE,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,kBAAkB,CACtB,WAAmB,EACnB,OAAe;QAEf,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC;QACvD,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAW;YACnD,WAAW;YACX,OAAO;YACP,QAAQ;YACR,MAAM;YACN,EAAE;YACF,QAAQ;YACR,OAAO;YACP,QAAQ;SACT,CAAC,CAAC;QACH,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,IAAY;QACnC,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAChD,MAAM,oBAAoB,CAAC,CAAC,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;IAC3E,CAAC;CACF;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,kBAAkB,EAAE,CAAC;AAE3D,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * subtaskUtils.ts
3
+ * Utilities for handling subtasks stored in reminder notes
4
+ *
5
+ * Subtasks are stored in the notes field with a human-readable format:
6
+ * ---SUBTASKS---
7
+ * [ ] {id} Task title
8
+ * [x] {id} Completed task title
9
+ * ---END SUBTASKS---
10
+ *
11
+ * This format:
12
+ * - Is human-readable in the native Reminders app
13
+ * - Allows programmatic parsing and manipulation
14
+ * - Coexists with tags ([#tag] format) and user notes
15
+ */
16
+ import type { Subtask, SubtaskProgress } from '../types/index.js';
17
+ /**
18
+ * Generates a short unique ID (8 hex characters)
19
+ */
20
+ export declare function generateSubtaskId(): string;
21
+ /**
22
+ * Parses subtasks from notes content
23
+ * @param notes - The notes string that may contain subtasks
24
+ * @returns Array of subtasks in order
25
+ */
26
+ export declare function parseSubtasks(notes: string | null | undefined): Subtask[];
27
+ /**
28
+ * Serializes subtasks into the notes format
29
+ * @param subtasks - Array of subtasks to serialize
30
+ * @returns Formatted subtask section string (empty string if no subtasks)
31
+ */
32
+ export declare function serializeSubtasks(subtasks: Subtask[]): string;
33
+ /**
34
+ * Removes the subtask section from notes, returning clean content
35
+ * @param notes - The notes string with potential subtasks
36
+ * @returns Notes content without subtask section
37
+ */
38
+ export declare function stripSubtasks(notes: string | null | undefined): string;
39
+ /**
40
+ * Combines subtasks with notes content
41
+ * Tags are preserved and subtasks are appended at the end
42
+ * @param subtasks - Array of subtasks
43
+ * @param notes - Existing notes content (may have tags and/or existing subtasks)
44
+ * @returns Combined notes with subtasks appended
45
+ */
46
+ export declare function combineSubtasksAndNotes(subtasks: Subtask[], notes: string | undefined): string;
47
+ /**
48
+ * Adds a subtask to existing notes
49
+ * @param title - Title of the new subtask
50
+ * @param notes - Existing notes content
51
+ * @returns Updated notes with new subtask added
52
+ */
53
+ export declare function addSubtask(title: string, notes: string | undefined): {
54
+ notes: string;
55
+ subtask: Subtask;
56
+ };
57
+ /**
58
+ * Updates a subtask in existing notes
59
+ * @param subtaskId - ID of the subtask to update
60
+ * @param updates - Partial subtask updates
61
+ * @param notes - Existing notes content
62
+ * @returns Updated notes with subtask modified
63
+ */
64
+ export declare function updateSubtask(subtaskId: string, updates: {
65
+ title?: string;
66
+ isCompleted?: boolean;
67
+ }, notes: string | undefined): string;
68
+ /**
69
+ * Removes a subtask from existing notes
70
+ * @param subtaskId - ID of the subtask to remove
71
+ * @param notes - Existing notes content
72
+ * @returns Updated notes with subtask removed
73
+ */
74
+ export declare function removeSubtask(subtaskId: string, notes: string | undefined): string;
75
+ /**
76
+ * Toggles a subtask's completion status
77
+ * @param subtaskId - ID of the subtask to toggle
78
+ * @param notes - Existing notes content
79
+ * @returns Updated notes with subtask toggled
80
+ */
81
+ export declare function toggleSubtask(subtaskId: string, notes: string | undefined): {
82
+ notes: string;
83
+ subtask: Subtask;
84
+ };
85
+ /**
86
+ * Reorders subtasks based on provided ID order
87
+ * @param order - Array of subtask IDs in desired order
88
+ * @param notes - Existing notes content
89
+ * @returns Updated notes with subtasks reordered
90
+ */
91
+ export declare function reorderSubtasks(order: string[], notes: string | undefined): string;
92
+ /**
93
+ * Creates subtasks from an array of titles
94
+ * @param titles - Array of subtask titles
95
+ * @returns Array of new subtasks
96
+ */
97
+ export declare function createSubtasksFromTitles(titles: string[]): Subtask[];
98
+ /**
99
+ * Calculates subtask completion progress
100
+ * @param subtasks - Array of subtasks
101
+ * @returns Progress object with completed count, total, and percentage
102
+ */
103
+ export declare function getSubtaskProgress(subtasks: Subtask[]): SubtaskProgress;