@elizaos/plugin-imessage 2.0.3-beta.2 → 2.0.3-beta.4

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 (58) hide show
  1. package/dist/accounts.d.ts +135 -0
  2. package/dist/accounts.d.ts.map +1 -0
  3. package/dist/accounts.js +209 -0
  4. package/dist/accounts.js.map +1 -0
  5. package/dist/api/bluebubbles-routes.d.ts +10 -0
  6. package/dist/api/bluebubbles-routes.d.ts.map +1 -0
  7. package/dist/api/bluebubbles-routes.js +132 -0
  8. package/dist/api/bluebubbles-routes.js.map +1 -0
  9. package/dist/api/imessage-routes.d.ts +68 -0
  10. package/dist/api/imessage-routes.d.ts.map +1 -0
  11. package/dist/api/imessage-routes.js +228 -0
  12. package/dist/api/imessage-routes.js.map +1 -0
  13. package/dist/chatdb-reader.d.ts +240 -0
  14. package/dist/chatdb-reader.d.ts.map +1 -0
  15. package/dist/chatdb-reader.js +667 -0
  16. package/dist/chatdb-reader.js.map +1 -0
  17. package/dist/config.d.ts +60 -0
  18. package/dist/config.d.ts.map +1 -0
  19. package/dist/config.js +8 -0
  20. package/dist/config.js.map +1 -0
  21. package/dist/connector-account-provider.d.ts +18 -0
  22. package/dist/connector-account-provider.d.ts.map +1 -0
  23. package/dist/connector-account-provider.js +83 -0
  24. package/dist/connector-account-provider.js.map +1 -0
  25. package/dist/contacts-reader.d.ts +141 -0
  26. package/dist/contacts-reader.d.ts.map +1 -0
  27. package/dist/contacts-reader.js +359 -0
  28. package/dist/contacts-reader.js.map +1 -0
  29. package/dist/data-routes.d.ts +21 -0
  30. package/dist/data-routes.d.ts.map +1 -0
  31. package/dist/data-routes.js +280 -0
  32. package/dist/data-routes.js.map +1 -0
  33. package/dist/index.d.ts +24 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +83 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/providers/index.d.ts +4 -0
  38. package/dist/providers/index.d.ts.map +1 -0
  39. package/dist/providers/index.js +5 -0
  40. package/dist/providers/index.js.map +1 -0
  41. package/dist/rpc.d.ts +206 -0
  42. package/dist/rpc.d.ts.map +1 -0
  43. package/dist/rpc.js +393 -0
  44. package/dist/rpc.js.map +1 -0
  45. package/dist/service.d.ts +264 -0
  46. package/dist/service.d.ts.map +1 -0
  47. package/dist/service.js +1705 -0
  48. package/dist/service.js.map +1 -0
  49. package/dist/setup-routes.d.ts +26 -0
  50. package/dist/setup-routes.d.ts.map +1 -0
  51. package/dist/setup-routes.js +139 -0
  52. package/dist/setup-routes.js.map +1 -0
  53. package/dist/types.d.ts +192 -0
  54. package/dist/types.d.ts.map +1 -0
  55. package/dist/types.js +138 -0
  56. package/dist/types.js.map +1 -0
  57. package/package.json +4 -3
  58. package/registry-entry.json +103 -0
@@ -0,0 +1,228 @@
1
+ /**
2
+ * iMessage connector HTTP routes.
3
+ *
4
+ * Exposes the @elizaos/plugin-imessage service state through Eliza's
5
+ * HTTP API so downstream UI layers (the dashboard, a future CLI, third-
6
+ * party integrations) can read and write against the macOS Messages.app
7
+ * world without each client having to go straight to chat.db or native
8
+ * macOS bridges.
9
+ *
10
+ * Routes served (all under `/api/imessage`):
11
+ *
12
+ * GET /api/imessage/status service health + cursor + counts
13
+ * GET /api/imessage/messages recent messages from chat.db
14
+ * GET /api/imessage/chats list of chats (DMs + groups)
15
+ * GET /api/imessage/contacts every contact with full detail
16
+ * POST /api/imessage/contacts create a new contact
17
+ * PATCH /api/imessage/contacts/:id update an existing contact
18
+ * DELETE /api/imessage/contacts/:id delete a contact
19
+ *
20
+ * Each handler pulls the IMessageService instance off the runtime via
21
+ * `runtime.getService("imessage")` and calls the public methods added
22
+ * in the plugin's patched branch. If the service isn't registered (the
23
+ * plugin isn't enabled, Eliza booted before it was loaded, etc.) we
24
+ * return 503 with a structured reason so the UI can render an
25
+ * informative empty state.
26
+ *
27
+ * Write endpoints (POST/PATCH/DELETE on contacts) touch the real macOS
28
+ * Contacts store through CNContactStore and require the Contacts privacy
29
+ * grant. Once granted, the permission is persistent across restarts.
30
+ */
31
+ const IMESSAGE_SERVICE_NAME = "imessage";
32
+ const MAX_BODY_BYTES = 256 * 1024; // Contacts payloads are tiny; cap aggressively.
33
+ function resolveService(state) {
34
+ if (!state.runtime)
35
+ return null;
36
+ const raw = state.runtime.getService(IMESSAGE_SERVICE_NAME);
37
+ return raw ?? null;
38
+ }
39
+ /**
40
+ * Extract the `:id` segment from a contact path like
41
+ * `/api/imessage/contacts/ABCD-EFGH-...`. Returns null if the path
42
+ * doesn't match. The id is URL-decoded since Apple Contacts ids are
43
+ * GUID-style and safe, but callers could URL-encode for paranoia.
44
+ */
45
+ function parseContactId(pathname) {
46
+ const prefix = "/api/imessage/contacts/";
47
+ if (!pathname.startsWith(prefix))
48
+ return null;
49
+ const rest = pathname.slice(prefix.length);
50
+ if (!rest)
51
+ return null;
52
+ return decodeURIComponent(rest);
53
+ }
54
+ /**
55
+ * Route handler entry point. Returns `true` when a route matched and
56
+ * the response has been written; returns `false` so the caller can
57
+ * continue to other route handlers (mirrors the handleWhatsAppRoute /
58
+ * handleWalletRoute pattern used elsewhere in this codebase).
59
+ */
60
+ export async function handleIMessageRoute(req, res, pathname, method, state, helpers) {
61
+ if (!pathname.startsWith("/api/imessage"))
62
+ return false;
63
+ const meta = { req, res, method, pathname };
64
+ // ── GET /api/imessage/status ──────────────────────────────────────
65
+ if (method === "GET" && pathname === "/api/imessage/status") {
66
+ const service = resolveService(state);
67
+ if (!service) {
68
+ helpers.json(res, {
69
+ available: false,
70
+ reason: "imessage service not registered",
71
+ });
72
+ return true;
73
+ }
74
+ helpers.json(res, {
75
+ available: true,
76
+ connected: service.isConnected(),
77
+ ...(service.getStatus?.() ?? {}),
78
+ });
79
+ return true;
80
+ }
81
+ // ── GET /api/imessage/messages?limit=N ────────────────────────────
82
+ if (method === "GET" && pathname === "/api/imessage/messages") {
83
+ const service = resolveService(state);
84
+ if (!service) {
85
+ helpers.error(res, "imessage service not registered", 503);
86
+ return true;
87
+ }
88
+ const url = new URL(req.url ?? pathname, "http://localhost");
89
+ const limitParam = url.searchParams.get("limit");
90
+ const limit = Math.min(Math.max(1, Number.parseInt(limitParam ?? "50", 10) || 50), 500);
91
+ try {
92
+ const messages = await service.getRecentMessages(limit);
93
+ helpers.json(res, { messages, count: messages.length });
94
+ }
95
+ catch (error) {
96
+ helpers.error(res, `failed to read messages: ${error instanceof Error ? error.message : String(error)}`, 500);
97
+ }
98
+ return true;
99
+ }
100
+ // ── GET /api/imessage/chats ───────────────────────────────────────
101
+ if (method === "GET" && pathname === "/api/imessage/chats") {
102
+ const service = resolveService(state);
103
+ if (!service) {
104
+ helpers.error(res, "imessage service not registered", 503);
105
+ return true;
106
+ }
107
+ try {
108
+ const chats = await service.getChats();
109
+ helpers.json(res, { chats, count: chats.length });
110
+ }
111
+ catch (error) {
112
+ helpers.error(res, `failed to read chats: ${error instanceof Error ? error.message : String(error)}`, 500);
113
+ }
114
+ return true;
115
+ }
116
+ // ── GET /api/imessage/contacts ────────────────────────────────────
117
+ if (method === "GET" && pathname === "/api/imessage/contacts") {
118
+ const service = resolveService(state);
119
+ if (!service) {
120
+ helpers.error(res, "imessage service not registered", 503);
121
+ return true;
122
+ }
123
+ try {
124
+ const contacts = await service.listAllContacts();
125
+ helpers.json(res, { contacts, count: contacts.length });
126
+ }
127
+ catch (error) {
128
+ helpers.error(res, `failed to read contacts: ${error instanceof Error ? error.message : String(error)}`, 500);
129
+ }
130
+ return true;
131
+ }
132
+ // ── POST /api/imessage/contacts ───────────────────────────────────
133
+ if (method === "POST" && pathname === "/api/imessage/contacts") {
134
+ const service = resolveService(state);
135
+ if (!service) {
136
+ helpers.error(res, "imessage service not registered", 503);
137
+ return true;
138
+ }
139
+ const body = await helpers.readJsonBody(req, res, { maxBytes: MAX_BODY_BYTES });
140
+ if (!body)
141
+ return true; // helpers.readJsonBody has already sent the error.
142
+ if (!body.firstName && !body.lastName && !body.phones?.length && !body.emails?.length) {
143
+ helpers.error(res, "at least one of firstName, lastName, phones, or emails is required", 400);
144
+ return true;
145
+ }
146
+ try {
147
+ const id = await service.addContact({
148
+ firstName: body.firstName,
149
+ lastName: body.lastName,
150
+ phones: body.phones,
151
+ emails: body.emails,
152
+ });
153
+ if (!id) {
154
+ helpers.error(res, "contact creation failed — see server logs. Common cause: Contacts write permission not granted yet.", 500);
155
+ return true;
156
+ }
157
+ helpers.json(res, { id, created: true }, 201);
158
+ }
159
+ catch (error) {
160
+ helpers.error(res, `addContact threw: ${error instanceof Error ? error.message : String(error)}`, 500);
161
+ }
162
+ return true;
163
+ }
164
+ // ── PATCH /api/imessage/contacts/:id ──────────────────────────────
165
+ if (method === "PATCH" && pathname.startsWith("/api/imessage/contacts/")) {
166
+ const id = parseContactId(pathname);
167
+ if (!id) {
168
+ helpers.error(res, "contact id is required in the path", 400);
169
+ return true;
170
+ }
171
+ const service = resolveService(state);
172
+ if (!service) {
173
+ helpers.error(res, "imessage service not registered", 503);
174
+ return true;
175
+ }
176
+ const body = await helpers.readJsonBody(req, res, { maxBytes: MAX_BODY_BYTES });
177
+ if (!body)
178
+ return true;
179
+ try {
180
+ const ok = await service.updateContact(id, {
181
+ firstName: body.firstName,
182
+ lastName: body.lastName,
183
+ addPhones: body.addPhones,
184
+ removePhones: body.removePhones,
185
+ addEmails: body.addEmails,
186
+ removeEmails: body.removeEmails,
187
+ });
188
+ if (!ok) {
189
+ helpers.error(res, "contact update failed — see server logs. Contact may not exist, or write permission may be denied.", 500);
190
+ return true;
191
+ }
192
+ helpers.json(res, { id, updated: true });
193
+ }
194
+ catch (error) {
195
+ helpers.error(res, `updateContact threw: ${error instanceof Error ? error.message : String(error)}`, 500);
196
+ }
197
+ return true;
198
+ }
199
+ // ── DELETE /api/imessage/contacts/:id ─────────────────────────────
200
+ if (method === "DELETE" && pathname.startsWith("/api/imessage/contacts/")) {
201
+ const id = parseContactId(pathname);
202
+ if (!id) {
203
+ helpers.error(res, "contact id is required in the path", 400);
204
+ return true;
205
+ }
206
+ const service = resolveService(state);
207
+ if (!service) {
208
+ helpers.error(res, "imessage service not registered", 503);
209
+ return true;
210
+ }
211
+ try {
212
+ const ok = await service.deleteContact(id);
213
+ if (!ok) {
214
+ helpers.error(res, "contact delete failed — see server logs. Contact may not exist, or write permission may be denied.", 500);
215
+ return true;
216
+ }
217
+ helpers.json(res, { id, deleted: true });
218
+ }
219
+ catch (error) {
220
+ helpers.error(res, `deleteContact threw: ${error instanceof Error ? error.message : String(error)}`, 500);
221
+ }
222
+ return true;
223
+ }
224
+ // Path starts with /api/imessage but none of the above matched.
225
+ void meta; // reserved for future telemetry spans
226
+ return false;
227
+ }
228
+ //# sourceMappingURL=imessage-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imessage-routes.js","sourceRoot":"","sources":["../../src/api/imessage-routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,240 @@
1
+ /**
2
+ * macOS chat.db reader for @elizaos/plugin-imessage.
3
+ *
4
+ * iMessage stores every message in a SQLite database at
5
+ * `~/Library/Messages/chat.db`. Reading it requires Full Disk Access on
6
+ * whichever process hosts the plugin (the Eliza agent, typically). This
7
+ * module opens that file read-only and exposes a single `fetchNewMessages`
8
+ * method the polling loop uses to walk forward by ROWID.
9
+ *
10
+ * ---
11
+ *
12
+ * Backend: runtime SQLite built-ins. Bun exposes `bun:sqlite`; Node 22+
13
+ * exposes `node:sqlite`. We normalize both to the small query surface this
14
+ * module needs so live chat.db reads keep working in test runners and under
15
+ * either runtime.
16
+ *
17
+ * Prior to this module, the plugin attempted to read messages by running
18
+ * AppleScript against Messages.app's `get messages` verb — a verb that
19
+ * does not exist in Messages.app's scripting dictionary. That code path
20
+ * silently returned an empty list on every poll, so inbound messages
21
+ * never reached the agent.
22
+ */
23
+ import type { IMessagePermissionAction } from "./types.js";
24
+ /**
25
+ * Default path to macOS's iMessage database. Requires Full Disk Access
26
+ * on whichever process opens it.
27
+ */
28
+ export declare const DEFAULT_CHAT_DB_PATH: string;
29
+ export declare const MACOS_FULL_DISK_ACCESS_SETTINGS_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles";
30
+ export interface ChatDbAccessIssue {
31
+ code: "sqlite_unavailable" | "open_failed";
32
+ path: string;
33
+ reason: string;
34
+ permissionAction: IMessagePermissionAction | null;
35
+ }
36
+ export declare function createFullDiskAccessAction(): IMessagePermissionAction;
37
+ /**
38
+ * Convert an Apple Cocoa date delta to JavaScript milliseconds since
39
+ * epoch. Handles both legacy (seconds) and modern (nanoseconds) storage.
40
+ */
41
+ export declare function appleDateToJsMs(appleDate: number | string | bigint): number;
42
+ /**
43
+ * Extract the plain UTF-8 text from an `attributedBody` BLOB.
44
+ *
45
+ * Modern macOS (~10.13+) stores message text as an `NSMutableAttributedString`
46
+ * serialised via Apple's legacy `typedstream` / NSArchiver format, because
47
+ * Messages.app wants to attach attributes (links, mentions, formatting)
48
+ * that plain text can't carry. Empirically, on a fresh macOS chat.db,
49
+ * ~97% of message rows have `text=NULL` and their actual readable content
50
+ * only exists in `attributedBody`. Reading chat.db without decoding this
51
+ * blob means being blind to almost every real message.
52
+ *
53
+ * Full typedstream parsing is complex (class inheritance chains, object
54
+ * references, multiple string encodings) and worth ~500 lines of code.
55
+ * The good news: Messages.app uses a narrow, stable subset for its own
56
+ * message text, and the text always appears after the same marker
57
+ * sequence: `NSString\x00\x01\x94\x84\x01\x2b` (class name, object flags,
58
+ * then `+` which is typedstream's "cstring" verb), followed by a length
59
+ * byte, followed by the UTF-8 bytes. For strings longer than 254 bytes
60
+ * typedstream escapes the length with `0x81` followed by a little-endian
61
+ * uint16 length, then the bytes. Everything else we don't care about.
62
+ *
63
+ * Verified against real blobs from a live chat.db — hit rate is ~100% on
64
+ * the messages checked, including short replies like "Yo" and longer
65
+ * messages with emoji. Returns null if no marker is found, which the
66
+ * caller uses as a signal to fall back to the raw `text` column or
67
+ * skip the row.
68
+ *
69
+ * References:
70
+ * - Apple's typedstream format: see darling-gnustep-base, GSTypedStream.m
71
+ * - imessage-exporter's Rust implementation (MIT) for the full parser
72
+ * - NSAttributedString serialisation in Cocoa Foundation
73
+ */
74
+ export declare function decodeAttributedBody(blob: Uint8Array | Buffer | null | undefined): string | null;
75
+ /** A single attachment attached to a message. */
76
+ export interface ChatDbAttachment {
77
+ /** chat.db `attachment.guid`. */
78
+ guid: string;
79
+ /** Filename as stored, if known. */
80
+ filename: string | null;
81
+ /** Apple UTI (e.g. `public.jpeg`, `com.apple.quicktime-movie`). */
82
+ uti: string | null;
83
+ /** Best-available MIME type (may be null for some UTIs). */
84
+ mimeType: string | null;
85
+ /** Total size in bytes. */
86
+ totalBytes: number | null;
87
+ /** True when the attachment is a Messages sticker. */
88
+ isSticker: boolean;
89
+ }
90
+ /**
91
+ * A reaction / tapback signal. iMessage stores reactions as their own
92
+ * `message` rows with a non-zero `associated_message_type` and an
93
+ * `associated_message_guid` pointing at the original message.
94
+ *
95
+ * Type codes (empirically from chat.db):
96
+ * 2000 = love, 2001 = like, 2002 = dislike, 2003 = laugh,
97
+ * 2004 = emphasis, 2005 = question, 2006 = sticker-reply.
98
+ * 3000+ = the matching "remove" for each.
99
+ */
100
+ export interface ChatDbReaction {
101
+ kind: "love" | "like" | "dislike" | "laugh" | "emphasis" | "question" | "sticker" | "unknown";
102
+ /** Whether this is an add (+) or remove (-) tapback. */
103
+ add: boolean;
104
+ /** Raw numeric type for callers that want the full taxonomy. */
105
+ rawType: number;
106
+ /** GUID of the message being reacted to. */
107
+ targetGuid: string;
108
+ /** Genmoji / custom emoji when the reaction is a sticker-like reaction. */
109
+ emoji: string | null;
110
+ }
111
+ /**
112
+ * A normalized iMessage, ready to be turned into an agent Memory. Unlike
113
+ * the first version of this reader, rows whose `text` column is NULL are
114
+ * NOT skipped — the reader falls back to decoding the `attributedBody`
115
+ * blob, which covers the ~97% of messages on modern macOS that store
116
+ * their text there. If both paths yield nothing, `text` is an empty
117
+ * string and the row is flagged via `kind`.
118
+ */
119
+ export interface ChatDbMessage {
120
+ /** chat.db `message.ROWID`. Monotonic, used as the polling cursor. */
121
+ rowId: number;
122
+ /** chat.db `message.guid`. Stable across devices, used for deduplication. */
123
+ guid: string;
124
+ /** Plain text of the message. May be the empty string for reactions, system events, or undecodable blobs. */
125
+ text: string;
126
+ /**
127
+ * Classification of the row. Most conversation turns are `"text"`.
128
+ * `"reaction"` rows carry no text — their payload is in `reaction`.
129
+ * `"system"` covers group add/remove/rename events.
130
+ */
131
+ kind: "text" | "reaction" | "system" | "other";
132
+ /** Sender identity: phone number (E.164) or email address. Empty for outbound. */
133
+ handle: string;
134
+ /** `chat.chat_identifier`. Stable room key for 1:1 and group conversations. */
135
+ chatId: string;
136
+ /** Classification derived from `chat.style` (45 = 1:1, 43 = group). */
137
+ chatType: "direct" | "group";
138
+ /** Group name if set by users; always null for 1:1 chats. */
139
+ displayName: string | null;
140
+ /** JavaScript milliseconds since epoch, converted from Apple's Cocoa date. */
141
+ timestamp: number;
142
+ /** True if the message was sent by the local Apple ID (the agent's account). */
143
+ isFromMe: boolean;
144
+ /** Delivery service as reported by Apple: `iMessage`, `SMS`, `RCS`, etc. */
145
+ service: string | null;
146
+ /** Sent flag — reliable for outbound, always 1 for delivered. */
147
+ isSent: boolean;
148
+ /** Delivered flag — Apple confirmed delivery to the recipient's device. */
149
+ isDelivered: boolean;
150
+ /** Read flag — the recipient opened the thread. Only meaningful for outbound. */
151
+ isRead: boolean;
152
+ /** When the recipient read this message, or 0 if unread/never. */
153
+ dateRead: number;
154
+ /** When the message was edited (modern macOS), or 0 if never edited. */
155
+ dateEdited: number;
156
+ /** When the message was unsent/retracted, or 0 if still live. */
157
+ dateRetracted: number;
158
+ /** GUID of the message this one is an inline reply to, if any. */
159
+ replyToGuid: string | null;
160
+ /** Reaction payload when `kind === "reaction"`. */
161
+ reaction: ChatDbReaction | null;
162
+ /** Zero or more attachments bound to this message. */
163
+ attachments: ChatDbAttachment[];
164
+ }
165
+ /**
166
+ * Read-only handle for a live chat.db. Created via {@link openChatDb}.
167
+ * The service owns the lifetime: opens on start, closes on stop.
168
+ */
169
+ /** A single chat (conversation room) as surfaced by `listChats`. */
170
+ export interface ChatDbChatSummary {
171
+ chatId: string;
172
+ chatType: "direct" | "group";
173
+ displayName: string | null;
174
+ serviceName: string | null;
175
+ participants: string[];
176
+ lastReadMessageTimestamp: number;
177
+ }
178
+ export interface ChatDbReader {
179
+ /**
180
+ * Fetch messages with ROWID strictly greater than `sinceRowId`, up to
181
+ * `limit` rows. Rows are returned in ascending ROWID order so the
182
+ * caller can advance its cursor to the last row's `rowId`.
183
+ *
184
+ * Returns an empty array when the database has no new messages or
185
+ * when the reader has been closed.
186
+ */
187
+ fetchNewMessages(sinceRowId: number, limit: number): ChatDbMessage[];
188
+ /**
189
+ * Return the largest `message.ROWID` currently in the database. Used
190
+ * on service start to seed the polling cursor at the tip so a fresh
191
+ * launch does not replay the entire backlog. Returns 0 on empty dbs
192
+ * or if the query fails.
193
+ */
194
+ getLatestRowId(): number;
195
+ /**
196
+ * Return the timestamp of the most recent outbound message authored by the
197
+ * local Apple account, converted to JavaScript epoch milliseconds.
198
+ */
199
+ getLatestOwnMessageTimestamp(): number | null;
200
+ /**
201
+ * Return the newest messages in chronological order, optionally scoped
202
+ * to a single chat identifier. Unlike `fetchNewMessages`, this is for
203
+ * ad-hoc inspection and UI reads rather than cursor-based polling.
204
+ */
205
+ listMessages(options?: {
206
+ chatId?: string;
207
+ limit?: number;
208
+ }): ChatDbMessage[];
209
+ /**
210
+ * List every chat the database knows about, joined with participant
211
+ * handles. Reads from `chat`, `chat_handle_join`, and `handle`. This
212
+ * is the replacement for the old AppleScript-based `getChats` path,
213
+ * which was slower and returned less data.
214
+ */
215
+ listChats(): ChatDbChatSummary[];
216
+ /** Close the underlying SQLite handle. Idempotent. */
217
+ close(): void;
218
+ }
219
+ interface ChatDbDiagnosticsLogger {
220
+ warn(message: string): void;
221
+ debug(message: string): void;
222
+ }
223
+ export interface OpenChatDbOptions {
224
+ diagnosticsLogger?: ChatDbDiagnosticsLogger;
225
+ }
226
+ export declare function getLastChatDbAccessIssue(dbPath?: string): ChatDbAccessIssue | null;
227
+ /**
228
+ * Open macOS chat.db read-only and return a reader bound to it.
229
+ *
230
+ * Returns `null` — and logs a human-readable reason — in every failure
231
+ * mode so the caller can degrade to send-only operation instead of
232
+ * crashing the runtime:
233
+ *
234
+ * - Not running under Bun (bun:sqlite built-in unavailable)
235
+ * - chat.db does not exist at the given path
236
+ * - chat.db exists but cannot be opened (missing Full Disk Access, etc.)
237
+ */
238
+ export declare function openChatDb(dbPath?: string, options?: OpenChatDbOptions): Promise<ChatDbReader | null>;
239
+ export {};
240
+ //# sourceMappingURL=chatdb-reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatdb-reader.d.ts","sourceRoot":"","sources":["../src/chatdb-reader.ts"],"names":[],"mappings":"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"}