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.
- package/LICENSE +21 -0
- package/README.md +360 -0
- package/bin/dev.cjs +10 -0
- package/bin/run.cjs +15 -0
- package/dist/config/index.d.ts +36 -0
- package/dist/config/index.js +127 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +161 -0
- package/dist/config/schema.js +45 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +75 -0
- package/dist/index.js.map +1 -0
- package/dist/server/handlers.d.ts +10 -0
- package/dist/server/handlers.js +43 -0
- package/dist/server/handlers.js.map +1 -0
- package/dist/server/promptAbstractions.d.ts +74 -0
- package/dist/server/promptAbstractions.js +150 -0
- package/dist/server/promptAbstractions.js.map +1 -0
- package/dist/server/prompts.d.ts +8 -0
- package/dist/server/prompts.js +480 -0
- package/dist/server/prompts.js.map +1 -0
- package/dist/server/server.d.ts +29 -0
- package/dist/server/server.js +52 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/transports/http/auth.d.ts +34 -0
- package/dist/server/transports/http/auth.js +148 -0
- package/dist/server/transports/http/auth.js.map +1 -0
- package/dist/server/transports/http/health.d.ts +35 -0
- package/dist/server/transports/http/health.js +93 -0
- package/dist/server/transports/http/health.js.map +1 -0
- package/dist/server/transports/http/index.d.ts +43 -0
- package/dist/server/transports/http/index.js +141 -0
- package/dist/server/transports/http/index.js.map +1 -0
- package/dist/server/transports/http/middleware.d.ts +53 -0
- package/dist/server/transports/http/middleware.js +133 -0
- package/dist/server/transports/http/middleware.js.map +1 -0
- package/dist/tools/definitions.d.ts +10 -0
- package/dist/tools/definitions.js +633 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/handlers/calendarHandlers.d.ts +11 -0
- package/dist/tools/handlers/calendarHandlers.js +123 -0
- package/dist/tools/handlers/calendarHandlers.js.map +1 -0
- package/dist/tools/handlers/contactsHandlers.d.ts +17 -0
- package/dist/tools/handlers/contactsHandlers.js +397 -0
- package/dist/tools/handlers/contactsHandlers.js.map +1 -0
- package/dist/tools/handlers/index.d.ts +11 -0
- package/dist/tools/handlers/index.js +12 -0
- package/dist/tools/handlers/index.js.map +1 -0
- package/dist/tools/handlers/listHandlers.d.ts +10 -0
- package/dist/tools/handlers/listHandlers.js +40 -0
- package/dist/tools/handlers/listHandlers.js.map +1 -0
- package/dist/tools/handlers/mailHandlers.d.ts +11 -0
- package/dist/tools/handlers/mailHandlers.js +301 -0
- package/dist/tools/handlers/mailHandlers.js.map +1 -0
- package/dist/tools/handlers/messagesHandlers.d.ts +17 -0
- package/dist/tools/handlers/messagesHandlers.js +350 -0
- package/dist/tools/handlers/messagesHandlers.js.map +1 -0
- package/dist/tools/handlers/notesHandlers.d.ts +12 -0
- package/dist/tools/handlers/notesHandlers.js +305 -0
- package/dist/tools/handlers/notesHandlers.js.map +1 -0
- package/dist/tools/handlers/reminderHandlers.d.ts +10 -0
- package/dist/tools/handlers/reminderHandlers.js +96 -0
- package/dist/tools/handlers/reminderHandlers.js.map +1 -0
- package/dist/tools/handlers/shared.d.ts +51 -0
- package/dist/tools/handlers/shared.js +107 -0
- package/dist/tools/handlers/shared.js.map +1 -0
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.js +125 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types/index.d.ts +265 -0
- package/dist/types/index.js +67 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/prompts.d.ts +84 -0
- package/dist/types/prompts.js +6 -0
- package/dist/types/prompts.js.map +1 -0
- package/dist/types/repository.d.ts +102 -0
- package/dist/types/repository.js +6 -0
- package/dist/types/repository.js.map +1 -0
- package/dist/utils/binaryValidator.d.ts +52 -0
- package/dist/utils/binaryValidator.js +152 -0
- package/dist/utils/binaryValidator.js.map +1 -0
- package/dist/utils/calendarRepository.d.ts +25 -0
- package/dist/utils/calendarRepository.js +100 -0
- package/dist/utils/calendarRepository.js.map +1 -0
- package/dist/utils/cliExecutor.d.ts +28 -0
- package/dist/utils/cliExecutor.js +196 -0
- package/dist/utils/cliExecutor.js.map +1 -0
- package/dist/utils/constants.d.ts +96 -0
- package/dist/utils/constants.js +97 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/contactResolver.d.ts +142 -0
- package/dist/utils/contactResolver.js +386 -0
- package/dist/utils/contactResolver.js.map +1 -0
- package/dist/utils/dateFiltering.d.ts +22 -0
- package/dist/utils/dateFiltering.js +72 -0
- package/dist/utils/dateFiltering.js.map +1 -0
- package/dist/utils/dateUtils.d.ts +20 -0
- package/dist/utils/dateUtils.js +36 -0
- package/dist/utils/dateUtils.js.map +1 -0
- package/dist/utils/errorHandling.d.ts +30 -0
- package/dist/utils/errorHandling.js +101 -0
- package/dist/utils/errorHandling.js.map +1 -0
- package/dist/utils/helpers.d.ts +35 -0
- package/dist/utils/helpers.js +59 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/jxaExecutor.d.ts +47 -0
- package/dist/utils/jxaExecutor.js +194 -0
- package/dist/utils/jxaExecutor.js.map +1 -0
- package/dist/utils/logging.d.ts +31 -0
- package/dist/utils/logging.js +98 -0
- package/dist/utils/logging.js.map +1 -0
- package/dist/utils/permissionPrompt.d.ts +16 -0
- package/dist/utils/permissionPrompt.js +42 -0
- package/dist/utils/permissionPrompt.js.map +1 -0
- package/dist/utils/preflight.d.ts +30 -0
- package/dist/utils/preflight.js +196 -0
- package/dist/utils/preflight.js.map +1 -0
- package/dist/utils/projectUtils.d.ts +11 -0
- package/dist/utils/projectUtils.js +76 -0
- package/dist/utils/projectUtils.js.map +1 -0
- package/dist/utils/reminderDateParser.d.ts +8 -0
- package/dist/utils/reminderDateParser.js +77 -0
- package/dist/utils/reminderDateParser.js.map +1 -0
- package/dist/utils/reminderRepository.d.ts +23 -0
- package/dist/utils/reminderRepository.js +91 -0
- package/dist/utils/reminderRepository.js.map +1 -0
- package/dist/utils/sqliteContactReader.d.ts +51 -0
- package/dist/utils/sqliteContactReader.js +216 -0
- package/dist/utils/sqliteContactReader.js.map +1 -0
- package/dist/utils/sqliteMailReader.d.ts +97 -0
- package/dist/utils/sqliteMailReader.js +310 -0
- package/dist/utils/sqliteMailReader.js.map +1 -0
- package/dist/utils/sqliteMessageReader.d.ts +71 -0
- package/dist/utils/sqliteMessageReader.js +400 -0
- package/dist/utils/sqliteMessageReader.js.map +1 -0
- package/dist/utils/timeHelpers.d.ts +40 -0
- package/dist/utils/timeHelpers.js +136 -0
- package/dist/utils/timeHelpers.js.map +1 -0
- package/dist/utils/timezone.d.ts +24 -0
- package/dist/utils/timezone.js +39 -0
- package/dist/utils/timezone.js.map +1 -0
- package/dist/validation/schemas.d.ts +610 -0
- package/dist/validation/schemas.js +354 -0
- package/dist/validation/schemas.js.map +1 -0
- package/package.json +97 -0
- package/scripts/build-swift.mjs +86 -0
- package/src/swift/EventKitCLI.swift +778 -0
- 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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"}
|