@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,253 @@
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 { webcrypto } from 'node:crypto';
17
+ // Subtask section markers
18
+ const SUBTASK_START = '---SUBTASKS---';
19
+ const SUBTASK_END = '---END SUBTASKS---';
20
+ // Regex to match the subtask section. Both markers are anchored to line
21
+ // boundaries so that user-supplied content that happens to contain the marker
22
+ // text mid-line cannot forge or truncate the structured section. `\r?\n`
23
+ // keeps the matcher tolerant of CRLF line endings that some tools (or a
24
+ // round-trip through AppleScript) can introduce.
25
+ const SUBTASK_SECTION_REGEX = /(?:^|\r?\n)---SUBTASKS---\r?\n([\s\S]*?)\r?\n---END SUBTASKS---(?=\r?\n|$)/;
26
+ // Regex to match individual subtask lines: [ ] {id} title or [x] {id} title.
27
+ // `\s*` at the end absorbs a trailing `\r` left behind when a CRLF-terminated
28
+ // block is split on `\n`.
29
+ const SUBTASK_LINE_REGEX = /^\[([ x])\]\s*\{([a-f0-9]+)\}\s*(.+?)\s*$/;
30
+ /**
31
+ * Generates a short unique ID (8 hex characters)
32
+ */
33
+ export function generateSubtaskId() {
34
+ const bytes = new Uint8Array(4);
35
+ webcrypto.getRandomValues(bytes);
36
+ return Array.from(bytes)
37
+ .map((b) => b.toString(16).padStart(2, '0'))
38
+ .join('');
39
+ }
40
+ /**
41
+ * Parses subtasks from notes content
42
+ * @param notes - The notes string that may contain subtasks
43
+ * @returns Array of subtasks in order
44
+ */
45
+ export function parseSubtasks(notes) {
46
+ if (!notes)
47
+ return [];
48
+ const match = notes.match(SUBTASK_SECTION_REGEX);
49
+ if (!match)
50
+ return [];
51
+ // Groups 1-3 are mandatory in SUBTASK_SECTION_REGEX / SUBTASK_LINE_REGEX,
52
+ // so non-null assertions on match indices are safe under noUncheckedIndexedAccess.
53
+ const subtaskContent = match[1];
54
+ // Split on either `\n` or `\r\n` so CRLF-terminated input round-trips.
55
+ const lines = subtaskContent.split(/\r?\n/).filter((line) => line.trim());
56
+ const subtasks = [];
57
+ for (const line of lines) {
58
+ const lineMatch = line.match(SUBTASK_LINE_REGEX);
59
+ if (lineMatch) {
60
+ subtasks.push({
61
+ id: lineMatch[2],
62
+ title: lineMatch[3].trim(),
63
+ isCompleted: lineMatch[1] === 'x',
64
+ });
65
+ }
66
+ }
67
+ return subtasks;
68
+ }
69
+ /**
70
+ * Serializes subtasks into the notes format
71
+ * @param subtasks - Array of subtasks to serialize
72
+ * @returns Formatted subtask section string (empty string if no subtasks)
73
+ */
74
+ export function serializeSubtasks(subtasks) {
75
+ if (!subtasks || subtasks.length === 0)
76
+ return '';
77
+ const lines = subtasks.map((subtask) => {
78
+ const checkbox = subtask.isCompleted ? '[x]' : '[ ]';
79
+ return `${checkbox} {${subtask.id}} ${subtask.title}`;
80
+ });
81
+ return `${SUBTASK_START}\n${lines.join('\n')}\n${SUBTASK_END}`;
82
+ }
83
+ /**
84
+ * Removes the subtask section from notes, returning clean content
85
+ * @param notes - The notes string with potential subtasks
86
+ * @returns Notes content without subtask section
87
+ */
88
+ export function stripSubtasks(notes) {
89
+ if (!notes)
90
+ return '';
91
+ return notes
92
+ .replace(SUBTASK_SECTION_REGEX, '')
93
+ .replace(/\n{3,}/g, '\n\n') // Collapse multiple newlines
94
+ .trim();
95
+ }
96
+ /**
97
+ * Combines subtasks with notes content
98
+ * Tags are preserved and subtasks are appended at the end
99
+ * @param subtasks - Array of subtasks
100
+ * @param notes - Existing notes content (may have tags and/or existing subtasks)
101
+ * @returns Combined notes with subtasks appended
102
+ */
103
+ export function combineSubtasksAndNotes(subtasks, notes) {
104
+ // Remove existing subtasks section from notes
105
+ const cleanNotes = stripSubtasks(notes);
106
+ const subtaskSection = serializeSubtasks(subtasks);
107
+ if (cleanNotes && subtaskSection) {
108
+ return `${cleanNotes}\n\n${subtaskSection}`;
109
+ }
110
+ else if (subtaskSection) {
111
+ return subtaskSection;
112
+ }
113
+ else {
114
+ return cleanNotes;
115
+ }
116
+ }
117
+ /**
118
+ * Adds a subtask to existing notes
119
+ * @param title - Title of the new subtask
120
+ * @param notes - Existing notes content
121
+ * @returns Updated notes with new subtask added
122
+ */
123
+ export function addSubtask(title, notes) {
124
+ const existingSubtasks = parseSubtasks(notes);
125
+ const newSubtask = {
126
+ id: generateSubtaskId(),
127
+ title: title.trim(),
128
+ isCompleted: false,
129
+ };
130
+ existingSubtasks.push(newSubtask);
131
+ return {
132
+ notes: combineSubtasksAndNotes(existingSubtasks, notes),
133
+ subtask: newSubtask,
134
+ };
135
+ }
136
+ /**
137
+ * Updates a subtask in existing notes
138
+ * @param subtaskId - ID of the subtask to update
139
+ * @param updates - Partial subtask updates
140
+ * @param notes - Existing notes content
141
+ * @returns Updated notes with subtask modified
142
+ */
143
+ export function updateSubtask(subtaskId, updates, notes) {
144
+ const subtasks = parseSubtasks(notes);
145
+ const index = subtasks.findIndex((s) => s.id === subtaskId);
146
+ if (index === -1) {
147
+ throw new Error(`Subtask with ID '${subtaskId}' not found.`);
148
+ }
149
+ // findIndex returned a valid index, so subtasks[index] is defined.
150
+ const subtask = subtasks[index];
151
+ if (updates.title !== undefined) {
152
+ subtask.title = updates.title.trim();
153
+ }
154
+ if (updates.isCompleted !== undefined) {
155
+ subtask.isCompleted = updates.isCompleted;
156
+ }
157
+ return combineSubtasksAndNotes(subtasks, notes);
158
+ }
159
+ /**
160
+ * Removes a subtask from existing notes
161
+ * @param subtaskId - ID of the subtask to remove
162
+ * @param notes - Existing notes content
163
+ * @returns Updated notes with subtask removed
164
+ */
165
+ export function removeSubtask(subtaskId, notes) {
166
+ const subtasks = parseSubtasks(notes);
167
+ const index = subtasks.findIndex((s) => s.id === subtaskId);
168
+ if (index === -1) {
169
+ throw new Error(`Subtask with ID '${subtaskId}' not found.`);
170
+ }
171
+ subtasks.splice(index, 1);
172
+ return combineSubtasksAndNotes(subtasks, notes);
173
+ }
174
+ /**
175
+ * Toggles a subtask's completion status
176
+ * @param subtaskId - ID of the subtask to toggle
177
+ * @param notes - Existing notes content
178
+ * @returns Updated notes with subtask toggled
179
+ */
180
+ export function toggleSubtask(subtaskId, notes) {
181
+ const subtasks = parseSubtasks(notes);
182
+ const index = subtasks.findIndex((s) => s.id === subtaskId);
183
+ if (index === -1) {
184
+ throw new Error(`Subtask with ID '${subtaskId}' not found.`);
185
+ }
186
+ // findIndex returned a valid index, so subtasks[index] is defined.
187
+ const subtask = subtasks[index];
188
+ subtask.isCompleted = !subtask.isCompleted;
189
+ return {
190
+ notes: combineSubtasksAndNotes(subtasks, notes),
191
+ subtask,
192
+ };
193
+ }
194
+ /**
195
+ * Reorders subtasks based on provided ID order
196
+ * @param order - Array of subtask IDs in desired order
197
+ * @param notes - Existing notes content
198
+ * @returns Updated notes with subtasks reordered
199
+ */
200
+ export function reorderSubtasks(order, notes) {
201
+ const subtasks = parseSubtasks(notes);
202
+ // Create a map for quick lookup
203
+ const subtaskMap = new Map(subtasks.map((s) => [s.id, s]));
204
+ // Validate all IDs exist
205
+ for (const id of order) {
206
+ if (!subtaskMap.has(id)) {
207
+ throw new Error(`Subtask with ID '${id}' not found.`);
208
+ }
209
+ }
210
+ // Check for missing IDs (IDs in subtasks but not in order)
211
+ const orderSet = new Set(order);
212
+ for (const subtask of subtasks) {
213
+ if (!orderSet.has(subtask.id)) {
214
+ throw new Error(`Reorder array is missing subtask ID '${subtask.id}'. All subtask IDs must be included.`);
215
+ }
216
+ }
217
+ // Reorder
218
+ const reorderedSubtasks = order.map((id) => {
219
+ const subtask = subtaskMap.get(id);
220
+ if (!subtask) {
221
+ throw new Error(`Subtask with ID '${id}' not found.`);
222
+ }
223
+ return subtask;
224
+ });
225
+ return combineSubtasksAndNotes(reorderedSubtasks, notes);
226
+ }
227
+ /**
228
+ * Creates subtasks from an array of titles
229
+ * @param titles - Array of subtask titles
230
+ * @returns Array of new subtasks
231
+ */
232
+ export function createSubtasksFromTitles(titles) {
233
+ return titles.map((title) => ({
234
+ id: generateSubtaskId(),
235
+ title: title.trim(),
236
+ isCompleted: false,
237
+ }));
238
+ }
239
+ /**
240
+ * Calculates subtask completion progress
241
+ * @param subtasks - Array of subtasks
242
+ * @returns Progress object with completed count, total, and percentage
243
+ */
244
+ export function getSubtaskProgress(subtasks) {
245
+ if (!subtasks || subtasks.length === 0) {
246
+ return { completed: 0, total: 0, percentage: 100 };
247
+ }
248
+ const completed = subtasks.filter((s) => s.isCompleted).length;
249
+ const total = subtasks.length;
250
+ const percentage = Math.round((completed / total) * 100);
251
+ return { completed, total, percentage };
252
+ }
253
+ //# sourceMappingURL=subtaskUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subtaskUtils.js","sourceRoot":"","sources":["../../src/utils/subtaskUtils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * tagUtils.ts
3
+ * Utilities for handling tags stored in reminder notes.
4
+ *
5
+ * Supports two tag formats:
6
+ * - Bracket format: [#tagname] (written by this MCP server)
7
+ * - Bare format: #tagname (used by Apple Reminders native UI)
8
+ *
9
+ * Writing always uses bracket format for unambiguous round-tripping.
10
+ * Reading parses both formats so native Apple Reminders tags are recognized.
11
+ */
12
+ /**
13
+ * Extracts tags from notes content, supporting both [#tag] and bare #tag formats
14
+ * @param notes - The notes string that may contain tags
15
+ * @returns Array of tag names (without # prefix), deduplicated and lowercased
16
+ */
17
+ export declare function extractTags(notes: string | null | undefined): string[];
18
+ /**
19
+ * Removes tag markers from notes, returning clean content.
20
+ * Strips both [#tag] bracket format and bare #tag format.
21
+ * @param notes - The notes string with potential tags
22
+ * @returns Notes content without tag markers
23
+ */
24
+ export declare function stripTags(notes: string | null | undefined): string;
25
+ /**
26
+ * Formats tags into the [#tag] format for storage
27
+ * @param tags - Array of tag names (with or without # prefix)
28
+ * @returns Formatted tag string
29
+ */
30
+ export declare function formatTags(tags: string[]): string;
31
+ /**
32
+ * Combines tags with notes content
33
+ * @param tags - Array of tag names
34
+ * @param notes - Notes content (may already have tags)
35
+ * @returns Combined notes with tags prepended
36
+ */
37
+ export declare function combineTagsAndNotes(tags: string[] | undefined, notes: string | undefined): string;
38
+ /**
39
+ * Adds tags to existing notes
40
+ * @param tagsToAdd - Tags to add
41
+ * @param notes - Existing notes content
42
+ * @returns Updated notes with added tags
43
+ */
44
+ export declare function addTagsToNotes(tagsToAdd: string[], notes: string | undefined): string;
45
+ /**
46
+ * Removes tags from existing notes
47
+ * @param tagsToRemove - Tags to remove
48
+ * @param notes - Existing notes content
49
+ * @returns Updated notes with specified tags removed
50
+ */
51
+ export declare function removeTagsFromNotes(tagsToRemove: string[], notes: string | undefined): string;
52
+ /**
53
+ * Checks if a reminder has all specified tags
54
+ * @param reminderTags - Tags the reminder has
55
+ * @param filterTags - Tags to check for
56
+ * @returns true if reminder has ALL filter tags
57
+ */
58
+ export declare function hasAllTags(reminderTags: string[] | undefined, filterTags: string[]): boolean;
@@ -0,0 +1,177 @@
1
+ /**
2
+ * tagUtils.ts
3
+ * Utilities for handling tags stored in reminder notes.
4
+ *
5
+ * Supports two tag formats:
6
+ * - Bracket format: [#tagname] (written by this MCP server)
7
+ * - Bare format: #tagname (used by Apple Reminders native UI)
8
+ *
9
+ * Writing always uses bracket format for unambiguous round-tripping.
10
+ * Reading parses both formats so native Apple Reminders tags are recognized.
11
+ */
12
+ // Regex to match tags in [#tag] bracket format
13
+ const BRACKET_TAG_REGEX = /\[#([^\]]+)\]/g;
14
+ // Regex to match bare #tag format (Apple Reminders native).
15
+ // Must be preceded by start-of-string or whitespace.
16
+ // Uses negative lookahead to exclude purely numeric tags (e.g. #42)
17
+ // while allowing digit-starting mixed tags (e.g. #1st, #2024q1, #2024年).
18
+ // Body uses Unicode property classes so CJK / Japanese / Korean / other
19
+ // non-Latin scripts work as tag characters (e.g. #雷蒙三十, #日本語, #한국어).
20
+ // The inner negative lookahead `(?![\p{L}\p{N}_-])` replaces the old `\b`,
21
+ // which broke for CJK because `\b` fires between ASCII digits and CJK letters
22
+ // (CJK chars count as `\W` in JS regex), causing `#2024年` to be rejected.
23
+ const BARE_TAG_REGEX = /(?:^|(?<=\s))#(?![0-9]+(?![\p{L}\p{N}_-]))([\p{L}\p{N}_-]+)/gmu;
24
+ /**
25
+ * Normalizes a tag by removing # prefix, trimming, and lowercasing
26
+ * @param tag - Tag string to normalize
27
+ * @returns Normalized tag string
28
+ */
29
+ function normalizeTag(tag) {
30
+ return tag.replace(/^#/, '').trim().toLowerCase();
31
+ }
32
+ /**
33
+ * Normalizes an array of tags
34
+ * @param tags - Array of tags to normalize
35
+ * @returns Array of normalized tags
36
+ */
37
+ function normalizeTags(tags) {
38
+ return tags.map(normalizeTag);
39
+ }
40
+ /**
41
+ * Extracts tags from notes content, supporting both [#tag] and bare #tag formats
42
+ * @param notes - The notes string that may contain tags
43
+ * @returns Array of tag names (without # prefix), deduplicated and lowercased
44
+ */
45
+ export function extractTags(notes) {
46
+ if (!notes)
47
+ return [];
48
+ const tags = new Set();
49
+ // Extract bracket-format tags: [#tagname] — group 1 is mandatory.
50
+ for (const match of notes.matchAll(BRACKET_TAG_REGEX)) {
51
+ const tag = match[1]?.trim().toLowerCase();
52
+ if (tag)
53
+ tags.add(tag);
54
+ }
55
+ // Extract bare-format tags: #tagname (Apple Reminders native) — group 1 is mandatory.
56
+ for (const match of notes.matchAll(BARE_TAG_REGEX)) {
57
+ const tag = match[1]?.trim().toLowerCase();
58
+ if (tag)
59
+ tags.add(tag);
60
+ }
61
+ return Array.from(tags);
62
+ }
63
+ /**
64
+ * Removes tag markers from notes, returning clean content.
65
+ * Strips both [#tag] bracket format and bare #tag format.
66
+ * @param notes - The notes string with potential tags
67
+ * @returns Notes content without tag markers
68
+ */
69
+ export function stripTags(notes) {
70
+ if (!notes)
71
+ return '';
72
+ return notes
73
+ .replace(BRACKET_TAG_REGEX, '')
74
+ .replace(BARE_TAG_REGEX, '')
75
+ .replace(/[ \t]{2,}/g, ' ') // Collapse multiple spaces
76
+ .replace(/^\s+/, '') // Trim leading whitespace
77
+ .replace(/\s+$/, '') // Trim trailing whitespace
78
+ .replace(/\n{3,}/g, '\n\n'); // Collapse multiple newlines
79
+ }
80
+ /**
81
+ * Formats tags into the [#tag] format for storage
82
+ * @param tags - Array of tag names (with or without # prefix)
83
+ * @returns Formatted tag string
84
+ */
85
+ export function formatTags(tags) {
86
+ if (!tags || tags.length === 0)
87
+ return '';
88
+ return tags
89
+ .map((tag) => {
90
+ const cleanTag = normalizeTag(tag);
91
+ return cleanTag ? `[#${cleanTag}]` : '';
92
+ })
93
+ .filter(Boolean)
94
+ .join(' ');
95
+ }
96
+ /**
97
+ * Combines tags with notes content
98
+ * @param tags - Array of tag names
99
+ * @param notes - Notes content (may already have tags)
100
+ * @returns Combined notes with tags prepended
101
+ */
102
+ export function combineTagsAndNotes(tags, notes) {
103
+ const existingTags = extractTags(notes);
104
+ const cleanNotes = stripTags(notes);
105
+ const mergedTags = tags ? [...tags, ...existingTags] : existingTags;
106
+ const allTags = [...new Set(normalizeTags(mergedTags))];
107
+ const formattedTags = formatTags(allTags);
108
+ if (formattedTags && cleanNotes) {
109
+ return `${formattedTags}\n${cleanNotes}`;
110
+ }
111
+ else if (formattedTags) {
112
+ return formattedTags;
113
+ }
114
+ else {
115
+ return cleanNotes;
116
+ }
117
+ }
118
+ /**
119
+ * Adds tags to existing notes
120
+ * @param tagsToAdd - Tags to add
121
+ * @param notes - Existing notes content
122
+ * @returns Updated notes with added tags
123
+ */
124
+ export function addTagsToNotes(tagsToAdd, notes) {
125
+ const existingTags = extractTags(notes);
126
+ const cleanNotes = stripTags(notes);
127
+ const normalizedNewTags = normalizeTags(tagsToAdd);
128
+ const allTags = [...new Set([...existingTags, ...normalizedNewTags])];
129
+ const formattedTags = formatTags(allTags);
130
+ if (formattedTags && cleanNotes) {
131
+ return `${formattedTags}\n${cleanNotes}`;
132
+ }
133
+ else if (formattedTags) {
134
+ return formattedTags;
135
+ }
136
+ else {
137
+ return cleanNotes;
138
+ }
139
+ }
140
+ /**
141
+ * Removes tags from existing notes
142
+ * @param tagsToRemove - Tags to remove
143
+ * @param notes - Existing notes content
144
+ * @returns Updated notes with specified tags removed
145
+ */
146
+ export function removeTagsFromNotes(tagsToRemove, notes) {
147
+ const existingTags = extractTags(notes);
148
+ const cleanNotes = stripTags(notes);
149
+ const normalizedRemove = normalizeTags(tagsToRemove);
150
+ const remainingTags = existingTags.filter((tag) => !normalizedRemove.includes(tag));
151
+ const formattedTags = formatTags(remainingTags);
152
+ if (formattedTags && cleanNotes) {
153
+ return `${formattedTags}\n${cleanNotes}`;
154
+ }
155
+ else if (formattedTags) {
156
+ return formattedTags;
157
+ }
158
+ else {
159
+ return cleanNotes;
160
+ }
161
+ }
162
+ /**
163
+ * Checks if a reminder has all specified tags
164
+ * @param reminderTags - Tags the reminder has
165
+ * @param filterTags - Tags to check for
166
+ * @returns true if reminder has ALL filter tags
167
+ */
168
+ export function hasAllTags(reminderTags, filterTags) {
169
+ if (!filterTags || filterTags.length === 0)
170
+ return true;
171
+ if (!reminderTags || reminderTags.length === 0)
172
+ return false;
173
+ const normalizedReminderTags = normalizeTags(reminderTags);
174
+ const normalizedFilterTags = normalizeTags(filterTags);
175
+ return normalizedFilterTags.every((tag) => normalizedReminderTags.includes(tag));
176
+ }
177
+ //# sourceMappingURL=tagUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tagUtils.js","sourceRoot":"","sources":["../../src/utils/tagUtils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * timeHelpers.ts
3
+ * Time formatting and context utilities for prompt templates
4
+ */
5
+ /**
6
+ * Time context information for prompts
7
+ */
8
+ export interface TimeContext {
9
+ /** Current date and time in ISO format (UTC) */
10
+ currentDateTime: string;
11
+ /** Current date in YYYY-MM-DD format (local timezone) */
12
+ currentDate: string;
13
+ /** Current time in HH:MM format (local timezone) */
14
+ currentTime: string;
15
+ /** Day of the week (Monday, Tuesday, etc.) */
16
+ dayOfWeek: string;
17
+ /** Whether it's currently working hours (9am-6pm) */
18
+ isWorkingHours: boolean;
19
+ /** Time of day description */
20
+ timeOfDay: 'morning' | 'afternoon' | 'evening' | 'night';
21
+ /** Formatted time description for prompts */
22
+ timeDescription: string;
23
+ }
24
+ /**
25
+ * Get comprehensive time context for prompt templates
26
+ */
27
+ export declare function getTimeContext(): TimeContext;
28
+ /**
29
+ * Format a relative time description for scheduling
30
+ */
31
+ export declare function formatRelativeTime(targetDate: Date): string;
32
+ /**
33
+ * Get fuzzy time suggestions based on current time
34
+ */
35
+ export declare function getFuzzyTimeSuggestions(): {
36
+ laterToday: string;
37
+ tomorrow: string;
38
+ endOfWeek: string;
39
+ nextWeek: string;
40
+ };
@@ -0,0 +1,136 @@
1
+ /**
2
+ * timeHelpers.ts
3
+ * Time formatting and context utilities for prompt templates
4
+ */
5
+ import { TIME } from './constants.js';
6
+ import { getDateStart, getTodayStart, getTomorrowStart } from './dateUtils.js';
7
+ /**
8
+ * Get comprehensive time context for prompt templates
9
+ */
10
+ export function getTimeContext() {
11
+ const now = new Date();
12
+ const today = getTodayStart();
13
+ // Use system local timezone for date formatting
14
+ // Format: YYYY-MM-DD in local timezone (not UTC)
15
+ const year = today.getFullYear();
16
+ const month = String(today.getMonth() + 1).padStart(2, '0');
17
+ const day = String(today.getDate()).padStart(2, '0');
18
+ const currentDate = `${year}-${month}-${day}`;
19
+ // Keep ISO format for full datetime
20
+ const currentDateTime = now.toISOString();
21
+ // Local time in HH:MM format
22
+ const currentTime = now.toTimeString().slice(0, 5); // HH:MM
23
+ // Day of week
24
+ const dayOfWeek = now.toLocaleDateString('en-US', { weekday: 'long' });
25
+ // Working hours check (9am-6pm)
26
+ const hour = now.getHours();
27
+ const isWorkingHours = hour >= TIME.WORKING_HOURS_START && hour < TIME.WORKING_HOURS_END;
28
+ // Time of day categorization
29
+ let timeOfDay;
30
+ if (hour >= TIME.MORNING_START && hour < TIME.NOON) {
31
+ timeOfDay = 'morning';
32
+ }
33
+ else if (hour >= TIME.NOON && hour < TIME.AFTERNOON_END) {
34
+ timeOfDay = 'afternoon';
35
+ }
36
+ else if (hour >= TIME.EVENING_START && hour < TIME.NIGHT_START) {
37
+ timeOfDay = 'evening';
38
+ }
39
+ else {
40
+ timeOfDay = 'night';
41
+ }
42
+ // Human-readable time description
43
+ const timeDescription = formatTimeDescription(now, dayOfWeek, timeOfDay, isWorkingHours);
44
+ return {
45
+ currentDateTime,
46
+ currentDate,
47
+ currentTime,
48
+ dayOfWeek,
49
+ isWorkingHours,
50
+ timeOfDay,
51
+ timeDescription,
52
+ };
53
+ }
54
+ const TIME_OF_DAY_LABELS = {
55
+ morning: ' (morning)',
56
+ afternoon: ' (afternoon)',
57
+ evening: ' (evening)',
58
+ night: ' (night)',
59
+ };
60
+ /**
61
+ * Create a human-readable time description for prompts
62
+ */
63
+ function formatTimeDescription(now, dayOfWeek, timeOfDay, isWorkingHours) {
64
+ const timeStr = now.toLocaleTimeString('en-US', {
65
+ hour: 'numeric',
66
+ minute: '2-digit',
67
+ hour12: true,
68
+ });
69
+ return `Current time: ${dayOfWeek} at ${timeStr}${TIME_OF_DAY_LABELS[timeOfDay] || ''}${isWorkingHours ? ' - working hours' : ' - outside working hours'}`;
70
+ }
71
+ /**
72
+ * Format a relative time description for scheduling
73
+ */
74
+ export function formatRelativeTime(targetDate) {
75
+ const today = getTodayStart();
76
+ const tomorrow = getTomorrowStart();
77
+ const targetDay = getDateStart(targetDate);
78
+ if (targetDay.getTime() === today.getTime()) {
79
+ return `today at ${targetDate.toLocaleTimeString('en-US', {
80
+ hour: 'numeric',
81
+ minute: '2-digit',
82
+ hour12: true,
83
+ })}`;
84
+ }
85
+ else if (targetDay.getTime() === tomorrow.getTime()) {
86
+ return `tomorrow at ${targetDate.toLocaleTimeString('en-US', {
87
+ hour: 'numeric',
88
+ minute: '2-digit',
89
+ hour12: true,
90
+ })}`;
91
+ }
92
+ else {
93
+ return targetDate.toLocaleDateString('en-US', {
94
+ weekday: 'short',
95
+ month: 'short',
96
+ day: 'numeric',
97
+ hour: 'numeric',
98
+ minute: '2-digit',
99
+ hour12: true,
100
+ });
101
+ }
102
+ }
103
+ /**
104
+ * Get fuzzy time suggestions based on current time
105
+ */
106
+ export function getFuzzyTimeSuggestions() {
107
+ const now = new Date();
108
+ const hour = now.getHours();
109
+ // Later today
110
+ const laterToday = new Date(now);
111
+ laterToday.setHours(Math.min(hour + TIME.LATER_TODAY_HOURS, TIME.END_OF_WEEK_HOUR), 0, 0, 0);
112
+ // Tomorrow morning
113
+ const tomorrow = getTomorrowStart();
114
+ tomorrow.setHours(TIME.DEFAULT_MORNING_HOUR, 0, 0, 0);
115
+ // End of week (Friday 5pm)
116
+ const endOfWeek = new Date(now);
117
+ const currentDay = now.getDay(); // 0 = Sunday, 6 = Saturday
118
+ const daysUntilFriday = currentDay === TIME.SUNDAY
119
+ ? TIME.FRIDAY
120
+ : currentDay <= TIME.FRIDAY
121
+ ? TIME.FRIDAY - currentDay
122
+ : 12 - currentDay;
123
+ endOfWeek.setDate(now.getDate() + daysUntilFriday);
124
+ endOfWeek.setHours(TIME.END_OF_WEEK_HOUR, 0, 0, 0);
125
+ // Next week
126
+ const nextWeek = new Date(now);
127
+ nextWeek.setDate(now.getDate() + 7);
128
+ nextWeek.setHours(TIME.DEFAULT_MORNING_HOUR, 0, 0, 0);
129
+ return {
130
+ laterToday: formatRelativeTime(laterToday),
131
+ tomorrow: formatRelativeTime(tomorrow),
132
+ endOfWeek: formatRelativeTime(endOfWeek),
133
+ nextWeek: formatRelativeTime(nextWeek),
134
+ };
135
+ }
136
+ //# sourceMappingURL=timeHelpers.js.map