mcp-macos 2.0.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 (149) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +360 -0
  3. package/bin/dev.cjs +10 -0
  4. package/bin/run.cjs +15 -0
  5. package/dist/config/index.d.ts +36 -0
  6. package/dist/config/index.js +127 -0
  7. package/dist/config/index.js.map +1 -0
  8. package/dist/config/schema.d.ts +161 -0
  9. package/dist/config/schema.js +45 -0
  10. package/dist/config/schema.js.map +1 -0
  11. package/dist/index.d.ts +11 -0
  12. package/dist/index.js +75 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/server/handlers.d.ts +10 -0
  15. package/dist/server/handlers.js +43 -0
  16. package/dist/server/handlers.js.map +1 -0
  17. package/dist/server/promptAbstractions.d.ts +74 -0
  18. package/dist/server/promptAbstractions.js +150 -0
  19. package/dist/server/promptAbstractions.js.map +1 -0
  20. package/dist/server/prompts.d.ts +8 -0
  21. package/dist/server/prompts.js +480 -0
  22. package/dist/server/prompts.js.map +1 -0
  23. package/dist/server/server.d.ts +29 -0
  24. package/dist/server/server.js +52 -0
  25. package/dist/server/server.js.map +1 -0
  26. package/dist/server/transports/http/auth.d.ts +34 -0
  27. package/dist/server/transports/http/auth.js +148 -0
  28. package/dist/server/transports/http/auth.js.map +1 -0
  29. package/dist/server/transports/http/health.d.ts +35 -0
  30. package/dist/server/transports/http/health.js +93 -0
  31. package/dist/server/transports/http/health.js.map +1 -0
  32. package/dist/server/transports/http/index.d.ts +43 -0
  33. package/dist/server/transports/http/index.js +141 -0
  34. package/dist/server/transports/http/index.js.map +1 -0
  35. package/dist/server/transports/http/middleware.d.ts +53 -0
  36. package/dist/server/transports/http/middleware.js +133 -0
  37. package/dist/server/transports/http/middleware.js.map +1 -0
  38. package/dist/tools/definitions.d.ts +10 -0
  39. package/dist/tools/definitions.js +633 -0
  40. package/dist/tools/definitions.js.map +1 -0
  41. package/dist/tools/handlers/calendarHandlers.d.ts +11 -0
  42. package/dist/tools/handlers/calendarHandlers.js +123 -0
  43. package/dist/tools/handlers/calendarHandlers.js.map +1 -0
  44. package/dist/tools/handlers/contactsHandlers.d.ts +17 -0
  45. package/dist/tools/handlers/contactsHandlers.js +397 -0
  46. package/dist/tools/handlers/contactsHandlers.js.map +1 -0
  47. package/dist/tools/handlers/index.d.ts +11 -0
  48. package/dist/tools/handlers/index.js +12 -0
  49. package/dist/tools/handlers/index.js.map +1 -0
  50. package/dist/tools/handlers/listHandlers.d.ts +10 -0
  51. package/dist/tools/handlers/listHandlers.js +40 -0
  52. package/dist/tools/handlers/listHandlers.js.map +1 -0
  53. package/dist/tools/handlers/mailHandlers.d.ts +11 -0
  54. package/dist/tools/handlers/mailHandlers.js +301 -0
  55. package/dist/tools/handlers/mailHandlers.js.map +1 -0
  56. package/dist/tools/handlers/messagesHandlers.d.ts +17 -0
  57. package/dist/tools/handlers/messagesHandlers.js +350 -0
  58. package/dist/tools/handlers/messagesHandlers.js.map +1 -0
  59. package/dist/tools/handlers/notesHandlers.d.ts +12 -0
  60. package/dist/tools/handlers/notesHandlers.js +305 -0
  61. package/dist/tools/handlers/notesHandlers.js.map +1 -0
  62. package/dist/tools/handlers/reminderHandlers.d.ts +10 -0
  63. package/dist/tools/handlers/reminderHandlers.js +96 -0
  64. package/dist/tools/handlers/reminderHandlers.js.map +1 -0
  65. package/dist/tools/handlers/shared.d.ts +51 -0
  66. package/dist/tools/handlers/shared.js +107 -0
  67. package/dist/tools/handlers/shared.js.map +1 -0
  68. package/dist/tools/index.d.ts +10 -0
  69. package/dist/tools/index.js +125 -0
  70. package/dist/tools/index.js.map +1 -0
  71. package/dist/types/index.d.ts +265 -0
  72. package/dist/types/index.js +67 -0
  73. package/dist/types/index.js.map +1 -0
  74. package/dist/types/prompts.d.ts +84 -0
  75. package/dist/types/prompts.js +6 -0
  76. package/dist/types/prompts.js.map +1 -0
  77. package/dist/types/repository.d.ts +102 -0
  78. package/dist/types/repository.js +6 -0
  79. package/dist/types/repository.js.map +1 -0
  80. package/dist/utils/binaryValidator.d.ts +52 -0
  81. package/dist/utils/binaryValidator.js +152 -0
  82. package/dist/utils/binaryValidator.js.map +1 -0
  83. package/dist/utils/calendarRepository.d.ts +25 -0
  84. package/dist/utils/calendarRepository.js +100 -0
  85. package/dist/utils/calendarRepository.js.map +1 -0
  86. package/dist/utils/cliExecutor.d.ts +28 -0
  87. package/dist/utils/cliExecutor.js +196 -0
  88. package/dist/utils/cliExecutor.js.map +1 -0
  89. package/dist/utils/constants.d.ts +96 -0
  90. package/dist/utils/constants.js +97 -0
  91. package/dist/utils/constants.js.map +1 -0
  92. package/dist/utils/contactResolver.d.ts +142 -0
  93. package/dist/utils/contactResolver.js +386 -0
  94. package/dist/utils/contactResolver.js.map +1 -0
  95. package/dist/utils/dateFiltering.d.ts +22 -0
  96. package/dist/utils/dateFiltering.js +72 -0
  97. package/dist/utils/dateFiltering.js.map +1 -0
  98. package/dist/utils/dateUtils.d.ts +20 -0
  99. package/dist/utils/dateUtils.js +36 -0
  100. package/dist/utils/dateUtils.js.map +1 -0
  101. package/dist/utils/errorHandling.d.ts +30 -0
  102. package/dist/utils/errorHandling.js +101 -0
  103. package/dist/utils/errorHandling.js.map +1 -0
  104. package/dist/utils/helpers.d.ts +35 -0
  105. package/dist/utils/helpers.js +59 -0
  106. package/dist/utils/helpers.js.map +1 -0
  107. package/dist/utils/jxaExecutor.d.ts +47 -0
  108. package/dist/utils/jxaExecutor.js +194 -0
  109. package/dist/utils/jxaExecutor.js.map +1 -0
  110. package/dist/utils/logging.d.ts +31 -0
  111. package/dist/utils/logging.js +98 -0
  112. package/dist/utils/logging.js.map +1 -0
  113. package/dist/utils/permissionPrompt.d.ts +16 -0
  114. package/dist/utils/permissionPrompt.js +42 -0
  115. package/dist/utils/permissionPrompt.js.map +1 -0
  116. package/dist/utils/preflight.d.ts +30 -0
  117. package/dist/utils/preflight.js +196 -0
  118. package/dist/utils/preflight.js.map +1 -0
  119. package/dist/utils/projectUtils.d.ts +11 -0
  120. package/dist/utils/projectUtils.js +76 -0
  121. package/dist/utils/projectUtils.js.map +1 -0
  122. package/dist/utils/reminderDateParser.d.ts +8 -0
  123. package/dist/utils/reminderDateParser.js +77 -0
  124. package/dist/utils/reminderDateParser.js.map +1 -0
  125. package/dist/utils/reminderRepository.d.ts +23 -0
  126. package/dist/utils/reminderRepository.js +91 -0
  127. package/dist/utils/reminderRepository.js.map +1 -0
  128. package/dist/utils/sqliteContactReader.d.ts +51 -0
  129. package/dist/utils/sqliteContactReader.js +216 -0
  130. package/dist/utils/sqliteContactReader.js.map +1 -0
  131. package/dist/utils/sqliteMailReader.d.ts +97 -0
  132. package/dist/utils/sqliteMailReader.js +310 -0
  133. package/dist/utils/sqliteMailReader.js.map +1 -0
  134. package/dist/utils/sqliteMessageReader.d.ts +71 -0
  135. package/dist/utils/sqliteMessageReader.js +400 -0
  136. package/dist/utils/sqliteMessageReader.js.map +1 -0
  137. package/dist/utils/timeHelpers.d.ts +40 -0
  138. package/dist/utils/timeHelpers.js +136 -0
  139. package/dist/utils/timeHelpers.js.map +1 -0
  140. package/dist/utils/timezone.d.ts +24 -0
  141. package/dist/utils/timezone.js +39 -0
  142. package/dist/utils/timezone.js.map +1 -0
  143. package/dist/validation/schemas.d.ts +610 -0
  144. package/dist/validation/schemas.js +354 -0
  145. package/dist/validation/schemas.js.map +1 -0
  146. package/package.json +97 -0
  147. package/scripts/build-swift.mjs +86 -0
  148. package/src/swift/EventKitCLI.swift +778 -0
  149. package/src/swift/Info.plist +38 -0
@@ -0,0 +1,71 @@
1
+ /**
2
+ * sqliteMessageReader.ts
3
+ * Reads Messages data from ~/Library/Messages/chat.db via SQLite.
4
+ * Requires Full Disk Access to be granted in System Settings.
5
+ */
6
+ export declare class SqliteAccessError extends Error {
7
+ readonly isPermissionError: boolean;
8
+ constructor(message: string, isPermissionError: boolean);
9
+ }
10
+ /**
11
+ * Date range filter for SQLite message queries.
12
+ * Dates are converted to Apple Core Data timestamps (nanoseconds since 2001-01-01).
13
+ */
14
+ export interface DateRange {
15
+ startDate?: string;
16
+ endDate?: string;
17
+ }
18
+ /**
19
+ * Parses a date string and converts it to Apple Core Data timestamp (nanoseconds since 2001-01-01).
20
+ * Supports: 'YYYY-MM-DD', 'YYYY-MM-DD HH:mm:ss', ISO 8601.
21
+ * Returns null if the date string is invalid.
22
+ */
23
+ export declare function dateToAppleTimestamp(dateStr: string): number | null;
24
+ /**
25
+ * Builds SQL WHERE clause fragment for date range filtering.
26
+ * Uses pre-computed numeric Apple timestamps (safe from injection).
27
+ * Returns empty string if no date filtering is needed.
28
+ */
29
+ export declare function buildDateFilter(dateRange?: DateRange, messageAlias?: string): string;
30
+ export interface ReadMessageResult {
31
+ id: string;
32
+ text: string;
33
+ sender: string;
34
+ date: string;
35
+ isFromMe: boolean;
36
+ }
37
+ export interface ReadChatResult {
38
+ id: string;
39
+ name: string;
40
+ participants: string[];
41
+ lastMessage: string;
42
+ lastDate: string;
43
+ }
44
+ /**
45
+ * Read messages from a specific chat by chat ID (the guid like "iMessage;-;+1234567890").
46
+ */
47
+ export declare function readChatMessages(chatId: string, limit: number, offset: number, dateRange?: DateRange): Promise<ReadMessageResult[]>;
48
+ /**
49
+ * Search messages across all chats by text content.
50
+ * Searches both the text column and attributedBody for matches.
51
+ */
52
+ export declare function searchMessages(searchTerm: string, limit: number, dateRange?: DateRange): Promise<Array<ReadMessageResult & {
53
+ chatId: string;
54
+ chatName: string;
55
+ }>>;
56
+ /**
57
+ * List all chats from the Messages database.
58
+ * Returns chats with their last message and participants.
59
+ * When dateRange is provided, only returns chats that have messages within
60
+ * the date range, and last_message/last_date reflect the most recent message
61
+ * within the range.
62
+ */
63
+ export declare function listChats(limit: number, offset: number, dateRange?: DateRange, search?: string): Promise<ReadChatResult[]>;
64
+ /**
65
+ * Read messages from handles (phone numbers or emails).
66
+ * Used for reverse lookup by contact name.
67
+ */
68
+ export declare function readMessagesByHandles(handles: string[], limit: number, dateRange?: DateRange): Promise<Array<ReadMessageResult & {
69
+ chatId: string;
70
+ chatName: string;
71
+ }>>;
@@ -0,0 +1,400 @@
1
+ /**
2
+ * sqliteMessageReader.ts
3
+ * Reads Messages data from ~/Library/Messages/chat.db via SQLite.
4
+ * Requires Full Disk Access to be granted in System Settings.
5
+ */
6
+ import { execFile } from 'node:child_process';
7
+ import { homedir } from 'node:os';
8
+ import { join } from 'node:path';
9
+ import { createFdaHint } from './errorHandling.js';
10
+ const CHAT_DB_PATH = join(homedir(), 'Library', 'Messages', 'chat.db');
11
+ export class SqliteAccessError extends Error {
12
+ constructor(message, isPermissionError) {
13
+ super(message);
14
+ this.isPermissionError = isPermissionError;
15
+ this.name = 'SqliteAccessError';
16
+ }
17
+ }
18
+ function runSqlite(query, timeoutMs = 15000) {
19
+ return new Promise((resolve, reject) => {
20
+ execFile('/usr/bin/sqlite3', ['-json', CHAT_DB_PATH, query], { timeout: timeoutMs, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
21
+ if (error) {
22
+ const msg = stderr || error.message;
23
+ if (msg.includes('authorization denied') ||
24
+ msg.includes('unable to open')) {
25
+ reject(new SqliteAccessError(`Cannot access Messages database. ${createFdaHint('Messages')}`, true));
26
+ return;
27
+ }
28
+ reject(new SqliteAccessError(`SQLite error: ${msg}`, false));
29
+ return;
30
+ }
31
+ resolve(stdout.trim());
32
+ });
33
+ });
34
+ }
35
+ function parseSqliteJson(output) {
36
+ if (!output)
37
+ return [];
38
+ try {
39
+ return JSON.parse(output);
40
+ }
41
+ catch {
42
+ return [];
43
+ }
44
+ }
45
+ /**
46
+ * Extracts plain text from a hex-encoded NSKeyedArchiver attributedBody blob.
47
+ * Apple stores rich text messages in this format instead of the plain text column.
48
+ *
49
+ * The blob format after NSString marker is typically:
50
+ * - Control bytes (class info)
51
+ * - '+' byte (0x2B) indicating string type
52
+ * - Length byte
53
+ * - UTF-8 text content
54
+ */
55
+ function extractTextFromAttributedBody(hexString) {
56
+ if (!hexString)
57
+ return null;
58
+ try {
59
+ const buffer = Buffer.from(hexString, 'hex');
60
+ // Find "NSString" marker
61
+ const nsStringMarker = Buffer.from('NSString');
62
+ const markerIndex = buffer.indexOf(nsStringMarker);
63
+ if (markerIndex === -1)
64
+ return null;
65
+ // Search for the '+' marker followed by length byte, then text
66
+ const searchStart = markerIndex + nsStringMarker.length;
67
+ let pos = searchStart;
68
+ // Skip control bytes until we find '+' (0x2B) which indicates string data
69
+ while (pos < buffer.length && buffer[pos] !== 0x2b) {
70
+ pos++;
71
+ }
72
+ if (pos >= buffer.length - 2)
73
+ return null;
74
+ // Skip '+' marker and length byte
75
+ pos += 2;
76
+ // Now we're at the start of the actual text
77
+ const textStart = pos;
78
+ // Find where the text ends (control char 0x86 or 0x84 typically marks end)
79
+ let textEnd = textStart;
80
+ while (textEnd < buffer.length) {
81
+ const byte = buffer[textEnd];
82
+ // Stop at blob markers that indicate end of string content
83
+ if (byte === 0x86 || byte === 0x84)
84
+ break;
85
+ textEnd++;
86
+ }
87
+ if (textEnd > textStart) {
88
+ const text = buffer.subarray(textStart, textEnd).toString('utf8');
89
+ // Clean up any trailing non-printable control chars
90
+ let end = text.length;
91
+ while (end > 0) {
92
+ const code = text.charCodeAt(end - 1);
93
+ if ((code >= 0x20 && code < 0x7f) || code > 0x9f)
94
+ break;
95
+ end--;
96
+ }
97
+ return text.slice(0, end).trim();
98
+ }
99
+ return null;
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ }
105
+ /**
106
+ * Gets the display text for a message, falling back to attributedBody extraction.
107
+ * Returns '[Attachment]' for attachment-only messages.
108
+ */
109
+ function getMessageText(text, attributedBodyHex, attachmentCount) {
110
+ // Try plain text first
111
+ if (text?.trim()) {
112
+ return text;
113
+ }
114
+ // Try extracting from attributedBody
115
+ const extracted = extractTextFromAttributedBody(attributedBodyHex);
116
+ if (extracted?.trim()) {
117
+ return extracted;
118
+ }
119
+ // If there's an attachment but no text, indicate that
120
+ if (attachmentCount > 0) {
121
+ return '[Attachment]';
122
+ }
123
+ return '';
124
+ }
125
+ /**
126
+ * Converts Apple's Core Data timestamp (nanoseconds since 2001-01-01) to ISO string.
127
+ */
128
+ function appleTimestampToISO(timestamp) {
129
+ if (!timestamp)
130
+ return '';
131
+ // Apple timestamps in chat.db are nanoseconds since 2001-01-01
132
+ const appleEpoch = new Date('2001-01-01T00:00:00Z').getTime();
133
+ const ms = appleEpoch + timestamp / 1000000;
134
+ return new Date(ms).toISOString();
135
+ }
136
+ /**
137
+ * Apple epoch: 2001-01-01T00:00:00Z in Unix milliseconds.
138
+ */
139
+ const APPLE_EPOCH_MS = new Date('2001-01-01T00:00:00Z').getTime();
140
+ /**
141
+ * Parses a date string and converts it to Apple Core Data timestamp (nanoseconds since 2001-01-01).
142
+ * Supports: 'YYYY-MM-DD', 'YYYY-MM-DD HH:mm:ss', ISO 8601.
143
+ * Returns null if the date string is invalid.
144
+ */
145
+ export function dateToAppleTimestamp(dateStr) {
146
+ // Try parsing as-is (handles ISO 8601 and 'YYYY-MM-DDTHH:mm:ss')
147
+ let d = new Date(dateStr);
148
+ // If that didn't work, try 'YYYY-MM-DD HH:mm:ss' by replacing space with T
149
+ if (Number.isNaN(d.getTime())) {
150
+ d = new Date(dateStr.replace(' ', 'T'));
151
+ }
152
+ if (Number.isNaN(d.getTime())) {
153
+ return null;
154
+ }
155
+ // Convert Unix ms to Apple nanoseconds
156
+ const unixMs = d.getTime();
157
+ return (unixMs - APPLE_EPOCH_MS) * 1000000;
158
+ }
159
+ /**
160
+ * Builds SQL WHERE clause fragment for date range filtering.
161
+ * Uses pre-computed numeric Apple timestamps (safe from injection).
162
+ * Returns empty string if no date filtering is needed.
163
+ */
164
+ export function buildDateFilter(dateRange, messageAlias = 'm') {
165
+ if (!dateRange)
166
+ return '';
167
+ const clauses = [];
168
+ if (dateRange.startDate) {
169
+ const ts = dateToAppleTimestamp(dateRange.startDate);
170
+ if (ts !== null) {
171
+ clauses.push(`${messageAlias}.date >= ${ts}`);
172
+ }
173
+ }
174
+ if (dateRange.endDate) {
175
+ const ts = dateToAppleTimestamp(dateRange.endDate);
176
+ if (ts !== null) {
177
+ clauses.push(`${messageAlias}.date <= ${ts}`);
178
+ }
179
+ }
180
+ if (clauses.length === 0)
181
+ return '';
182
+ return clauses.map((c) => `AND ${c}`).join(' ');
183
+ }
184
+ /**
185
+ * Read messages from a specific chat by chat ID (the guid like "iMessage;-;+1234567890").
186
+ */
187
+ export async function readChatMessages(chatId, limit, offset, dateRange) {
188
+ const escapedId = chatId.replace(/'/g, "''");
189
+ const dateFilter = buildDateFilter(dateRange);
190
+ const query = `
191
+ SELECT m.ROWID, m.text, m.is_from_me, m.date,
192
+ COALESCE(h.id, '') as handle_id,
193
+ hex(m.attributedBody) as attributedBody_hex,
194
+ (SELECT COUNT(*) FROM message_attachment_join maj WHERE maj.message_id = m.ROWID) as attachment_count
195
+ FROM message m
196
+ LEFT JOIN handle h ON m.handle_id = h.ROWID
197
+ JOIN chat_message_join cmj ON cmj.message_id = m.ROWID
198
+ JOIN chat c ON c.ROWID = cmj.chat_id
199
+ WHERE c.guid = '${escapedId}'
200
+ ${dateFilter}
201
+ ORDER BY m.date DESC
202
+ LIMIT ${limit} OFFSET ${offset}
203
+ `;
204
+ const output = await runSqlite(query);
205
+ const rows = parseSqliteJson(output);
206
+ return rows.reverse().map((row) => ({
207
+ id: String(row.ROWID),
208
+ text: getMessageText(row.text, row.attributedBody_hex, row.attachment_count),
209
+ sender: row.is_from_me ? 'me' : row.handle_id || 'unknown',
210
+ date: appleTimestampToISO(row.date),
211
+ isFromMe: row.is_from_me === 1,
212
+ }));
213
+ }
214
+ /**
215
+ * Search messages across all chats by text content.
216
+ * Searches both the text column and attributedBody for matches.
217
+ */
218
+ export async function searchMessages(searchTerm, limit, dateRange) {
219
+ const escapedTerm = searchTerm.replace(/'/g, "''");
220
+ const dateFilter = buildDateFilter(dateRange);
221
+ const query = `
222
+ SELECT m.ROWID, m.text, m.is_from_me, m.date,
223
+ COALESCE(h.id, '') as handle_id,
224
+ c.guid as chat_guid,
225
+ COALESCE(c.display_name, '') as chat_name,
226
+ hex(m.attributedBody) as attributedBody_hex,
227
+ (SELECT COUNT(*) FROM message_attachment_join maj WHERE maj.message_id = m.ROWID) as attachment_count
228
+ FROM message m
229
+ LEFT JOIN handle h ON m.handle_id = h.ROWID
230
+ JOIN chat_message_join cmj ON cmj.message_id = m.ROWID
231
+ JOIN chat c ON c.ROWID = cmj.chat_id
232
+ WHERE (m.text LIKE '%${escapedTerm}%'
233
+ OR CAST(m.attributedBody AS TEXT) LIKE '%${escapedTerm}%')
234
+ ${dateFilter}
235
+ ORDER BY m.date DESC
236
+ LIMIT ${limit}
237
+ `;
238
+ const output = await runSqlite(query, 30000);
239
+ const rows = parseSqliteJson(output);
240
+ return rows.map((row) => ({
241
+ id: String(row.ROWID),
242
+ text: getMessageText(row.text, row.attributedBody_hex, row.attachment_count),
243
+ sender: row.is_from_me ? 'me' : row.handle_id || 'unknown',
244
+ date: appleTimestampToISO(row.date),
245
+ isFromMe: row.is_from_me === 1,
246
+ chatId: row.chat_guid,
247
+ chatName: row.chat_name || row.chat_guid,
248
+ }));
249
+ }
250
+ /**
251
+ * List all chats from the Messages database.
252
+ * Returns chats with their last message and participants.
253
+ * When dateRange is provided, only returns chats that have messages within
254
+ * the date range, and last_message/last_date reflect the most recent message
255
+ * within the range.
256
+ */
257
+ export async function listChats(limit, offset, dateRange, search) {
258
+ const dateFilter = buildDateFilter(dateRange);
259
+ // Build optional search filter for chat name or participant handle
260
+ let searchFilter = '';
261
+ if (search) {
262
+ const escapedSearch = search.replace(/'/g, "''").toLowerCase();
263
+ searchFilter = `
264
+ AND (
265
+ LOWER(COALESCE(c.display_name, '')) LIKE '%${escapedSearch}%'
266
+ OR EXISTS (
267
+ SELECT 1 FROM chat_handle_join chj
268
+ JOIN handle h ON h.ROWID = chj.handle_id
269
+ WHERE chj.chat_id = c.ROWID
270
+ AND LOWER(h.id) LIKE '%${escapedSearch}%'
271
+ )
272
+ )`;
273
+ }
274
+ // Query chats with their last message and participant handles.
275
+ // When date filtering is active, subqueries also apply the date filter
276
+ // so that last_message/last_date reflect the most recent message in range,
277
+ // and chats with no messages in range are excluded (last_date IS NULL).
278
+ const query = `
279
+ SELECT
280
+ c.guid as chat_guid,
281
+ COALESCE(c.display_name, '') as display_name,
282
+ (
283
+ SELECT GROUP_CONCAT(h.id, ', ')
284
+ FROM chat_handle_join chj
285
+ JOIN handle h ON h.ROWID = chj.handle_id
286
+ WHERE chj.chat_id = c.ROWID
287
+ ) as participants,
288
+ (
289
+ SELECT m.text
290
+ FROM message m
291
+ JOIN chat_message_join cmj ON cmj.message_id = m.ROWID
292
+ WHERE cmj.chat_id = c.ROWID
293
+ ${dateFilter}
294
+ ORDER BY m.date DESC
295
+ LIMIT 1
296
+ ) as last_message,
297
+ (
298
+ SELECT hex(m.attributedBody)
299
+ FROM message m
300
+ JOIN chat_message_join cmj ON cmj.message_id = m.ROWID
301
+ WHERE cmj.chat_id = c.ROWID
302
+ ${dateFilter}
303
+ ORDER BY m.date DESC
304
+ LIMIT 1
305
+ ) as last_message_attr_hex,
306
+ (
307
+ SELECT (SELECT COUNT(*) FROM message_attachment_join maj WHERE maj.message_id = m.ROWID)
308
+ FROM message m
309
+ JOIN chat_message_join cmj ON cmj.message_id = m.ROWID
310
+ WHERE cmj.chat_id = c.ROWID
311
+ ${dateFilter}
312
+ ORDER BY m.date DESC
313
+ LIMIT 1
314
+ ) as last_message_attach_count,
315
+ (
316
+ SELECT m.date
317
+ FROM message m
318
+ JOIN chat_message_join cmj ON cmj.message_id = m.ROWID
319
+ WHERE cmj.chat_id = c.ROWID
320
+ ${dateFilter}
321
+ ORDER BY m.date DESC
322
+ LIMIT 1
323
+ ) as last_date
324
+ FROM chat c
325
+ WHERE last_date IS NOT NULL
326
+ ${searchFilter}
327
+ ORDER BY last_date DESC
328
+ LIMIT ${limit} OFFSET ${offset}
329
+ `;
330
+ const output = await runSqlite(query, 15000);
331
+ const rows = parseSqliteJson(output);
332
+ return rows.map((row) => {
333
+ const participants = row.participants
334
+ ? row.participants.split(', ').filter((p) => p.trim())
335
+ : [];
336
+ const name = row.display_name || participants.join(', ') || row.chat_guid || 'Unknown';
337
+ const lastMessage = getMessageText(row.last_message, row.last_message_attr_hex, row.last_message_attach_count ?? 0);
338
+ return {
339
+ id: row.chat_guid,
340
+ name,
341
+ participants,
342
+ lastMessage: lastMessage.substring(0, 100),
343
+ lastDate: row.last_date ? appleTimestampToISO(row.last_date) : '',
344
+ };
345
+ });
346
+ }
347
+ /**
348
+ * Read messages from handles (phone numbers or emails).
349
+ * Used for reverse lookup by contact name.
350
+ */
351
+ export async function readMessagesByHandles(handles, limit, dateRange) {
352
+ if (handles.length === 0) {
353
+ return [];
354
+ }
355
+ // Build OR conditions for each handle
356
+ // We need to normalize handles to match Messages DB format
357
+ const handleConditions = handles
358
+ .map((h) => {
359
+ const escaped = h.replace(/'/g, "''");
360
+ // Messages DB stores handles like "+15551234567" or "email@example.com"
361
+ // We'll match by suffix for phone numbers (to handle country code variations)
362
+ // and exact match for emails
363
+ if (h.includes('@')) {
364
+ return `h.id = '${escaped}'`;
365
+ }
366
+ // For phone numbers, match last 10 digits using LIKE
367
+ const last10 = h.slice(-10);
368
+ return `h.id LIKE '%${last10}'`;
369
+ })
370
+ .join(' OR ');
371
+ const dateFilter = buildDateFilter(dateRange);
372
+ const query = `
373
+ SELECT m.ROWID, m.text, m.is_from_me, m.date,
374
+ COALESCE(h.id, '') as handle_id,
375
+ c.guid as chat_guid,
376
+ COALESCE(c.display_name, '') as chat_name,
377
+ hex(m.attributedBody) as attributedBody_hex,
378
+ (SELECT COUNT(*) FROM message_attachment_join maj WHERE maj.message_id = m.ROWID) as attachment_count
379
+ FROM message m
380
+ LEFT JOIN handle h ON m.handle_id = h.ROWID
381
+ JOIN chat_message_join cmj ON cmj.message_id = m.ROWID
382
+ JOIN chat c ON c.ROWID = cmj.chat_id
383
+ WHERE (${handleConditions})
384
+ ${dateFilter}
385
+ ORDER BY m.date DESC
386
+ LIMIT ${limit}
387
+ `;
388
+ const output = await runSqlite(query, 30000);
389
+ const rows = parseSqliteJson(output);
390
+ return rows.map((row) => ({
391
+ id: String(row.ROWID),
392
+ text: getMessageText(row.text, row.attributedBody_hex, row.attachment_count),
393
+ sender: row.is_from_me ? 'me' : row.handle_id || 'unknown',
394
+ date: appleTimestampToISO(row.date),
395
+ isFromMe: row.is_from_me === 1,
396
+ chatId: row.chat_guid,
397
+ chatName: row.chat_name || row.chat_guid,
398
+ }));
399
+ }
400
+ //# sourceMappingURL=sqliteMessageReader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqliteMessageReader.js","sourceRoot":"","sources":["../../src/utils/sqliteMessageReader.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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeHelpers.js","sourceRoot":"","sources":["../../src/utils/timeHelpers.ts"],"names":[],"mappings":"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"}