@mgcrea/mcp-apple-messages 1.10.0 → 1.12.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/README.md +10 -1
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +86 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{server-BExbW9RO.js → server-CPDQqGEn.js} +280 -5
- package/dist/server-CPDQqGEn.js.map +1 -0
- package/package.json +3 -3
- package/dist/server-BExbW9RO.js.map +0 -1
package/README.md
CHANGED
|
@@ -66,7 +66,16 @@ That is a capability downgrade reported through `diagnostics`, never a throw.
|
|
|
66
66
|
|
|
67
67
|
## Tools
|
|
68
68
|
|
|
69
|
-
Read: `diagnostics`, `list_chats`, `list_messages`, `search_messages`, `get_message
|
|
69
|
+
Read: `diagnostics`, `list_chats`, `list_messages`, `search_messages`, `get_message`,
|
|
70
|
+
`count_messages`.
|
|
71
|
+
|
|
72
|
+
`count_messages` is the arithmetic lane: totals with a sent/received split, and `groupBy` for counts
|
|
73
|
+
per day, month, chat, handle or direction. It aggregates in SQL over every match, so "who do I
|
|
74
|
+
message most" costs one small result rather than a page of conversations tallied by hand — and
|
|
75
|
+
grouping by **handle** spans the several chats one person can occupy, which the per-chat totals on
|
|
76
|
+
`list_chats` cannot add together. Metadata only, deliberately: message bodies from 2026 onward live
|
|
77
|
+
in an archived blob that SQL cannot read, so a text filter here would return a number that looks
|
|
78
|
+
right and silently omits the recent half of the store.
|
|
70
79
|
|
|
71
80
|
Opt-in read: `find_codes`, behind `APPLE_MESSAGES_ALLOW_CODES` — see [Configuration](#configuration).
|
|
72
81
|
Off by default, and not covered by the write gate.
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { A as MESSAGES_SURFACE, V as BUILD_INFO, a as loadConfig, r as createServer } from "./server-
|
|
2
|
+
import { A as MESSAGES_SURFACE, V as BUILD_INFO, a as loadConfig, r as createServer } from "./server-CPDQqGEn.js";
|
|
3
3
|
import { runStdioServer } from "@mgcrea/mcp-apple-core";
|
|
4
4
|
//#region src/cli.ts
|
|
5
5
|
const LOG_PREFIX = "apple-messages-mcp";
|
package/dist/index.d.ts
CHANGED
|
@@ -133,6 +133,46 @@ type RangeQuery = {
|
|
|
133
133
|
includeReactions?: boolean | undefined;
|
|
134
134
|
limit: number;
|
|
135
135
|
};
|
|
136
|
+
/**
|
|
137
|
+
* What `countMessages` can group on.
|
|
138
|
+
*
|
|
139
|
+
* Metadata only, and that is the design rather than a first cut. Anything
|
|
140
|
+
* derived from message TEXT is unavailable to SQL here: from March 2026 onward
|
|
141
|
+
* the body lives only in `attributedBody`, which is why `search` carries a
|
|
142
|
+
* second decode pass. A `GROUP BY` over `text LIKE ?` would return a number
|
|
143
|
+
* shaped exactly like a correct one while missing every recent message, so this
|
|
144
|
+
* lane never offers one.
|
|
145
|
+
*/
|
|
146
|
+
declare const COUNT_GROUP_FIELDS: readonly ["day", "month", "chat", "handle", "direction"];
|
|
147
|
+
type CountGroupField = (typeof COUNT_GROUP_FIELDS)[number];
|
|
148
|
+
type CountQuery = {
|
|
149
|
+
chatGuid?: string | undefined;
|
|
150
|
+
/** Substring of a handle — a phone number or an email address. */
|
|
151
|
+
handle?: string | undefined;
|
|
152
|
+
direction?: "sent" | "received" | undefined;
|
|
153
|
+
fromApple?: number | undefined;
|
|
154
|
+
toApple?: number | undefined;
|
|
155
|
+
includeReactions?: boolean | undefined;
|
|
156
|
+
/** Caps the number of GROUPS, never the messages counted. */
|
|
157
|
+
limit: number;
|
|
158
|
+
};
|
|
159
|
+
type CountTotals = {
|
|
160
|
+
total: number;
|
|
161
|
+
/** Null when the store has no `is_from_me` column to split on. */
|
|
162
|
+
sent: number | null;
|
|
163
|
+
received: number | null;
|
|
164
|
+
firstAt: number | null;
|
|
165
|
+
lastAt: number | null;
|
|
166
|
+
};
|
|
167
|
+
type CountBucket = CountTotals & {
|
|
168
|
+
key: string | null;
|
|
169
|
+
label: string | null;
|
|
170
|
+
};
|
|
171
|
+
type CountGroups = {
|
|
172
|
+
groups: CountBucket[];
|
|
173
|
+
totalGroups: number;
|
|
174
|
+
totalRows: number;
|
|
175
|
+
};
|
|
136
176
|
declare class MessagesStore {
|
|
137
177
|
#private;
|
|
138
178
|
readonly db: DatabaseSync;
|
|
@@ -235,6 +275,21 @@ declare class MessagesStore {
|
|
|
235
275
|
handles: number;
|
|
236
276
|
attachments: number;
|
|
237
277
|
};
|
|
278
|
+
/**
|
|
279
|
+
* Count messages, optionally per group, over every match rather than a page.
|
|
280
|
+
*
|
|
281
|
+
* `limit` caps the number of GROUPS returned, never the messages counted —
|
|
282
|
+
* the same rule Mail's query lane is built on. An aggregate computed over a
|
|
283
|
+
* truncated page is not so much a wrong answer as a confidently wrong one:
|
|
284
|
+
* "my three busiest conversations" and "the three busiest among the twenty
|
|
285
|
+
* rows we happened to read" are the same shape, and nothing marks which you
|
|
286
|
+
* got. `totalRows` is therefore always reported.
|
|
287
|
+
*
|
|
288
|
+
* Every count is `COUNT(DISTINCT m."ROWID")`. The chat join is one row per
|
|
289
|
+
* (chat, message) pair, so a message filed in two chats would otherwise be
|
|
290
|
+
* counted twice in a total that is not itself per-chat.
|
|
291
|
+
*/
|
|
292
|
+
countMessages(q: CountQuery, field?: CountGroupField): CountTotals | CountGroups;
|
|
238
293
|
close(): void;
|
|
239
294
|
}
|
|
240
295
|
declare const introspect: (db: DatabaseSync) => StoreCapabilities;
|
|
@@ -351,6 +406,21 @@ type RenderedChat = {
|
|
|
351
406
|
messages: number;
|
|
352
407
|
lastMessageAt: string | null;
|
|
353
408
|
};
|
|
409
|
+
/** The counts every shape of `countMessages` reports, dates rendered. */
|
|
410
|
+
type RenderedCount = {
|
|
411
|
+
total: number;
|
|
412
|
+
sent: number | null;
|
|
413
|
+
received: number | null;
|
|
414
|
+
firstAt: string | null;
|
|
415
|
+
lastAt: string | null;
|
|
416
|
+
};
|
|
417
|
+
type RenderedCountGroup = RenderedCount & {
|
|
418
|
+
key: string | null;
|
|
419
|
+
/** A display form of `key`: a chat's name, a handle's contact. */
|
|
420
|
+
label: string | null;
|
|
421
|
+
/** Present only on chat groups, so the group can be read straight back. */
|
|
422
|
+
ref?: string;
|
|
423
|
+
};
|
|
354
424
|
declare class AppleMessagesClient {
|
|
355
425
|
#private;
|
|
356
426
|
constructor(opts: CreateClientOptions);
|
|
@@ -400,6 +470,21 @@ declare class AppleMessagesClient {
|
|
|
400
470
|
mimeType: string | null;
|
|
401
471
|
}>;
|
|
402
472
|
listChats(limit?: number): RenderedChat[];
|
|
473
|
+
/**
|
|
474
|
+
* Count messages, optionally grouped — the cheap-answer lane on this surface.
|
|
475
|
+
*
|
|
476
|
+
* Deliberately metadata-only. The expensive questions here are "how many" and
|
|
477
|
+
* "how many per X", and the store answers both in SQL without a message body
|
|
478
|
+
* ever reaching the context window. Anything text-derived stays with
|
|
479
|
+
* `searchMessages`, which has the decode pass that makes it complete.
|
|
480
|
+
*/
|
|
481
|
+
countMessages(q: Omit<CountQuery, "limit"> & {
|
|
482
|
+
limit?: number | undefined;
|
|
483
|
+
}, groupBy?: CountGroupField): RenderedCount | {
|
|
484
|
+
groups: RenderedCountGroup[];
|
|
485
|
+
totalGroups: number;
|
|
486
|
+
totalRows: number;
|
|
487
|
+
};
|
|
403
488
|
/**
|
|
404
489
|
* Send one message. A real one, to a real person, immediately.
|
|
405
490
|
*
|
|
@@ -838,7 +923,7 @@ type ToolContext = {
|
|
|
838
923
|
/**
|
|
839
924
|
* Register the Apple Messages tools.
|
|
840
925
|
*
|
|
841
|
-
*
|
|
926
|
+
* Six reads, always. Two writes, only when `allowWrites` is on — and on this
|
|
842
927
|
* surface the flag carries a permission claim as well as a safety one: with it
|
|
843
928
|
* off no Apple Event is ever sent, so no Automation grant is ever requested.
|
|
844
929
|
* What is needed either way is Full Disk Access, absolutely — see `diagnostics`.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/build-info.ts","../src/config.ts","../src/client/locate.ts","../src/client/store.ts","../src/client/messages.ts","../src/client/dates.ts","../src/client/errors.ts","../src/client/ref.ts","../src/client/typedstream.ts","../src/client/jxa/core.ts","../src/client/jxa/write.ts","../src/server.ts","../src/tools/index.ts"],"mappings":";;;;;;cAkBa,YAAY;;;;;;;;;;;;;;;;;;cCSnB,cAAY,EAAA;;;;;;;;;;;;;;;;;;;;GA4EP,EAAA,KAAA;KAEC,SAAS,EAAE,aAAa;cAEvB,aAAU,MAAS,OAAO,eAA2B;;;;;;;;;;;;;cC3FrD;;cAGA;KAaD,eAAe;EACzB;EACA;;EAEA;EACA;;cAGW,mBAAgB;cAQhB,cAAW;EACd;EAAgC;MACvC;;;cCiBU,gBAAa;KAMd;EACV;EACA,gBAAgB;EAChB,aAAa;EACb,eAAe;EACf,mBAAmB;EACnB;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;;EAEA;;EAEA;;KAGU;EACV;EACA;EACA;;EAEA;EACA;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA;;cAOW;;WACF,IAAI;WACJ;WACA,MAAM;EAEf,YAAY,IAAI,cAAc,cAAc,MAAM;;;;;;;;EAsGlD,MAAM,GAAG,aAAa;;;;;;;;;;;;;;EA6CtB,OAAO,eAAe,eAAe,6BAA2B;EA4ChE,OAAO,eAAe;;EActB,aAAa;IAAiB;IAAc;IAAe;;;;;;;;;;;;;;;;EAkC3D,eAAe;IACb;IACA;IACA;IACA;IACA;IACA;;;EA6BF,eAAe;IACb;IACA;IACA;IACA;IACA;;EAyBF,MAAM,gBAAgB;;EAKtB,WAAW,eAAe;;;;;;;;;;;;;;EAiB1B,gBAAgB,4BAA4B,iBAAa;;;;;;;;;;;;EAyBzD,UAAU,8BAA8B,oBAAoB,iBAAa;;EAqFzE;EAUA;IAAY;IAAkB;IAAe;IAAiB;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/build-info.ts","../src/config.ts","../src/client/locate.ts","../src/client/store.ts","../src/client/messages.ts","../src/client/dates.ts","../src/client/errors.ts","../src/client/ref.ts","../src/client/typedstream.ts","../src/client/jxa/core.ts","../src/client/jxa/write.ts","../src/server.ts","../src/tools/index.ts"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as MESSAGES_SURFACE, B as toAppleSeconds, C as locateStore, D as ChatNotFoundError, E as AppleMessagesError, F as SendTargetNotFoundError, I as CORE_DATA_EPOCH_OFFSET, L as appleSecondsSql, M as MessagesUnavailableError, N as SchemaDriftError, O as IndexUnavailableError, P as SendFailedError, R as fromAppleSeconds, S as defaultStorePath, T as PRELUDE, V as BUILD_INFO, _ as decodeMessageRef, a as loadConfig, b as ATTACHMENTS_RELATIVE, c as introspect, d as decodeAttributedBody, f as outline, g as decodeChatRef, h as MESSAGE_REF_VERSION, i as registerTools, j as MessageNotFoundError, k as MESSAGES_BUNDLE_ID, l as openStore, m as InvalidMessageRefError, n as SERVER_VERSION, o as AppleMessagesClient, p as CHAT_REF_VERSION, r as createServer, s as MessagesStore, t as SERVER_NAME, u as reactionLabel, v as encodeChatRef, w as SEND_MESSAGE, x as STORE_RELATIVE, y as encodeMessageRef, z as renderInstant } from "./server-
|
|
1
|
+
import { A as MESSAGES_SURFACE, B as toAppleSeconds, C as locateStore, D as ChatNotFoundError, E as AppleMessagesError, F as SendTargetNotFoundError, I as CORE_DATA_EPOCH_OFFSET, L as appleSecondsSql, M as MessagesUnavailableError, N as SchemaDriftError, O as IndexUnavailableError, P as SendFailedError, R as fromAppleSeconds, S as defaultStorePath, T as PRELUDE, V as BUILD_INFO, _ as decodeMessageRef, a as loadConfig, b as ATTACHMENTS_RELATIVE, c as introspect, d as decodeAttributedBody, f as outline, g as decodeChatRef, h as MESSAGE_REF_VERSION, i as registerTools, j as MessageNotFoundError, k as MESSAGES_BUNDLE_ID, l as openStore, m as InvalidMessageRefError, n as SERVER_VERSION, o as AppleMessagesClient, p as CHAT_REF_VERSION, r as createServer, s as MessagesStore, t as SERVER_NAME, u as reactionLabel, v as encodeChatRef, w as SEND_MESSAGE, x as STORE_RELATIVE, y as encodeMessageRef, z as renderInstant } from "./server-CPDQqGEn.js";
|
|
2
2
|
export { ATTACHMENTS_RELATIVE, AppleMessagesClient, AppleMessagesError, BUILD_INFO, CHAT_REF_VERSION, CORE_DATA_EPOCH_OFFSET, ChatNotFoundError, IndexUnavailableError, InvalidMessageRefError, MESSAGES_BUNDLE_ID, MESSAGES_SURFACE, MESSAGE_REF_VERSION, MessageNotFoundError, MessagesStore, MessagesUnavailableError, PRELUDE, SEND_MESSAGE, SERVER_NAME, SERVER_VERSION, STORE_RELATIVE, SchemaDriftError, SendFailedError, SendTargetNotFoundError, appleSecondsSql, createServer, decodeAttributedBody, decodeChatRef, decodeMessageRef, defaultStorePath, encodeChatRef, encodeMessageRef, fromAppleSeconds, introspect, loadConfig, locateStore, openStore, outline, reactionLabel, registerTools, renderInstant, toAppleSeconds };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AppleAutomationError, AppleAutomationError as AppleMessagesError, BaseConfigSchema, CORE_DATA_EPOCH_OFFSET, IndexUnavailableError, IndexUnavailableError as IndexUnavailableError$1, PreconditionError, SchemaDriftError, SchemaDriftError as SchemaDriftError$1, columnsOf, confirmArg, createOsascriptRunner, describeStore, escapeLike, extractCode, fail, fingerprintSchema, limitArg, ok, openReadOnly, parseBool, parseConfig, parseIntOpt, promptArg, readPackageIdentity, registerSurfaceResources, registerWorkflowPrompt, requiredPromptArg, resolveLimit, trimmed, withBusyRetry, wrap, wrapResult } from "@mgcrea/mcp-apple-core";
|
|
1
|
+
import { AppleAutomationError, AppleAutomationError as AppleMessagesError, BaseConfigSchema, CORE_DATA_EPOCH_OFFSET, IndexUnavailableError, IndexUnavailableError as IndexUnavailableError$1, PreconditionError, SchemaDriftError, SchemaDriftError as SchemaDriftError$1, columnsOf, confirmArg, createOsascriptRunner, describeAggregation, describeStore, escapeLike, extractCode, fail, fingerprintSchema, groupByArg, limitArg, ok, openReadOnly, parseBool, parseConfig, parseIntOpt, promptArg, readPackageIdentity, registerSurfaceResources, registerWorkflowPrompt, requiredPromptArg, resolveLimit, trimmed, withBusyRetry, wrap, wrapResult } from "@mgcrea/mcp-apple-core";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { basename, isAbsolute, join, resolve, sep } from "node:path";
|
|
@@ -13,8 +13,8 @@ const pkg = readPackageIdentity(new URL("../package.json", import.meta.url), {
|
|
|
13
13
|
const BUILD_INFO = {
|
|
14
14
|
name: pkg.name,
|
|
15
15
|
version: pkg.version,
|
|
16
|
-
gitCommit: "
|
|
17
|
-
gitCommitDate: "2026-09-
|
|
16
|
+
gitCommit: "25c3fa5",
|
|
17
|
+
gitCommitDate: "2026-09-03T18:51:51+02:00"
|
|
18
18
|
};
|
|
19
19
|
//#endregion
|
|
20
20
|
//#region src/client/dates.ts
|
|
@@ -59,6 +59,18 @@ const NANOSECOND_FLOOR = 0xe8d4a51000;
|
|
|
59
59
|
* integer reach `node:sqlite`'s value conversion.
|
|
60
60
|
*/
|
|
61
61
|
const appleSecondsSql = (column) => `CASE WHEN ${column} IS NULL THEN NULL WHEN ABS(CAST(${column} AS REAL)) > ${NANOSECOND_FLOOR} THEN CAST(${column} AS REAL) / 1000000000.0 ELSE CAST(${column} AS REAL) END`;
|
|
62
|
+
/**
|
|
63
|
+
* SQL that buckets an apple-date column into a LOCAL calendar day or month.
|
|
64
|
+
*
|
|
65
|
+
* Local, not UTC, and that is a deliberate divergence from Mail's equivalent in
|
|
66
|
+
* `packages/mail/src/client/envelope.ts`. "How many messages on the 3rd" is a
|
|
67
|
+
* question about the calendar the person was living in, and texting peaks late:
|
|
68
|
+
* with a UTC bucket, every message a user in CEST sends after 02:00 lands in the
|
|
69
|
+
* previous day. Mail's buckets are UTC and answering that is a separate call.
|
|
70
|
+
*
|
|
71
|
+
* `format` is a literal from this module's callers, never caller text.
|
|
72
|
+
*/
|
|
73
|
+
const appleDateBucketSql = (column, format) => `strftime('${format}', datetime(${appleSecondsSql(column)} + ${CORE_DATA_EPOCH_OFFSET}, 'unixepoch', 'localtime'))`;
|
|
62
74
|
/** Apple-seconds to a JS Date. Null in, null out. */
|
|
63
75
|
const fromAppleSeconds = (value) => {
|
|
64
76
|
if (value === null || !Number.isFinite(value) || value === 0) return null;
|
|
@@ -900,6 +912,23 @@ const reactionLabel = (type) => {
|
|
|
900
912
|
if (type >= 3e3 && type <= 3005) return `removed ${REACTION_LABELS[type - 1e3] ?? "reaction"}`;
|
|
901
913
|
return `reaction ${type}`;
|
|
902
914
|
};
|
|
915
|
+
/**
|
|
916
|
+
* What `countMessages` can group on.
|
|
917
|
+
*
|
|
918
|
+
* Metadata only, and that is the design rather than a first cut. Anything
|
|
919
|
+
* derived from message TEXT is unavailable to SQL here: from March 2026 onward
|
|
920
|
+
* the body lives only in `attributedBody`, which is why `search` carries a
|
|
921
|
+
* second decode pass. A `GROUP BY` over `text LIKE ?` would return a number
|
|
922
|
+
* shaped exactly like a correct one while missing every recent message, so this
|
|
923
|
+
* lane never offers one.
|
|
924
|
+
*/
|
|
925
|
+
const COUNT_GROUP_FIELDS = [
|
|
926
|
+
"day",
|
|
927
|
+
"month",
|
|
928
|
+
"chat",
|
|
929
|
+
"handle",
|
|
930
|
+
"direction"
|
|
931
|
+
];
|
|
903
932
|
const num = (v) => typeof v === "number" ? v : null;
|
|
904
933
|
const text = (v) => typeof v === "string" && v.length > 0 ? v : null;
|
|
905
934
|
const bool = (v) => v === 1 || v === true;
|
|
@@ -1282,6 +1311,150 @@ var MessagesStore = class {
|
|
|
1282
1311
|
attachments: this.caps.hasAttachments ? one(`SELECT COUNT(*) AS c FROM "attachment"`) : 0
|
|
1283
1312
|
};
|
|
1284
1313
|
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Count messages, optionally per group, over every match rather than a page.
|
|
1316
|
+
*
|
|
1317
|
+
* `limit` caps the number of GROUPS returned, never the messages counted —
|
|
1318
|
+
* the same rule Mail's query lane is built on. An aggregate computed over a
|
|
1319
|
+
* truncated page is not so much a wrong answer as a confidently wrong one:
|
|
1320
|
+
* "my three busiest conversations" and "the three busiest among the twenty
|
|
1321
|
+
* rows we happened to read" are the same shape, and nothing marks which you
|
|
1322
|
+
* got. `totalRows` is therefore always reported.
|
|
1323
|
+
*
|
|
1324
|
+
* Every count is `COUNT(DISTINCT m."ROWID")`. The chat join is one row per
|
|
1325
|
+
* (chat, message) pair, so a message filed in two chats would otherwise be
|
|
1326
|
+
* counted twice in a total that is not itself per-chat.
|
|
1327
|
+
*/
|
|
1328
|
+
countMessages(q, field) {
|
|
1329
|
+
const params = [];
|
|
1330
|
+
const from = this.#countFrom(q, field);
|
|
1331
|
+
const where = this.#countPredicate(q, params);
|
|
1332
|
+
const totals = this.#countExprs();
|
|
1333
|
+
if (!field) {
|
|
1334
|
+
const row = this.db.prepare(`SELECT ${totals} FROM "message" m ${from} WHERE ${where}`).get(...params);
|
|
1335
|
+
return this.#toTotals(row);
|
|
1336
|
+
}
|
|
1337
|
+
const { key, label } = this.#countGroupExpr(field);
|
|
1338
|
+
const countParams = [];
|
|
1339
|
+
const countWhere = this.#countPredicate(q, countParams);
|
|
1340
|
+
const totalsRow = this.db.prepare(`SELECT COUNT(DISTINCT m."ROWID") AS totalRows,
|
|
1341
|
+
COUNT(DISTINCT ${key}) + MAX(CASE WHEN ${key} IS NULL THEN 1 ELSE 0 END)
|
|
1342
|
+
AS totalGroups
|
|
1343
|
+
FROM "message" m ${from}
|
|
1344
|
+
WHERE ${countWhere}`).get(...countParams);
|
|
1345
|
+
return {
|
|
1346
|
+
groups: this.db.prepare(`SELECT ${key} AS key, ${label} AS label, ${totals}
|
|
1347
|
+
FROM "message" m ${from}
|
|
1348
|
+
WHERE ${where}
|
|
1349
|
+
GROUP BY ${key}
|
|
1350
|
+
-- Count first so \`limit\` yields a top-N; the key breaks ties, so
|
|
1351
|
+
-- running the same question twice cannot reshuffle equal groups.
|
|
1352
|
+
ORDER BY total DESC, key ASC
|
|
1353
|
+
LIMIT ${Math.max(1, Math.trunc(q.limit))}`).all(...params).map((r) => ({
|
|
1354
|
+
key: r.key === null ? null : String(r.key),
|
|
1355
|
+
label: text(r.label),
|
|
1356
|
+
...this.#toTotals(r)
|
|
1357
|
+
})),
|
|
1358
|
+
totalRows: Number(totalsRow.totalRows ?? 0),
|
|
1359
|
+
totalGroups: Number(totalsRow.totalGroups ?? 0)
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Join only what the question needs.
|
|
1364
|
+
*
|
|
1365
|
+
* Not an optimisation — a correctness choice. The chat join multiplies a
|
|
1366
|
+
* message by the chats it belongs to, so it is absent unless a chat is being
|
|
1367
|
+
* filtered on or grouped by.
|
|
1368
|
+
*/
|
|
1369
|
+
#countFrom(q, field) {
|
|
1370
|
+
const parts = [];
|
|
1371
|
+
if (q.handle || field === "handle") parts.push(`LEFT JOIN "handle" h ON h."ROWID" = m."handle_id"`);
|
|
1372
|
+
if (q.chatGuid || field === "chat") parts.push(`LEFT JOIN "chat_message_join" cmj ON cmj."message_id" = m."ROWID"`, `LEFT JOIN "chat" c ON c."ROWID" = cmj."chat_id"`);
|
|
1373
|
+
return parts.join("\n ");
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Shared by the totals and the grouped statement so the two cannot drift: a
|
|
1377
|
+
* filter that narrowed the listing but not its aggregate would make "sent to
|
|
1378
|
+
* this chat in August" quietly count the whole store.
|
|
1379
|
+
*/
|
|
1380
|
+
#countPredicate(q, params) {
|
|
1381
|
+
const m = this.caps.messageColumns;
|
|
1382
|
+
const where = [];
|
|
1383
|
+
if (q.chatGuid) {
|
|
1384
|
+
where.push(`c."guid" = ?`);
|
|
1385
|
+
params.push(q.chatGuid);
|
|
1386
|
+
}
|
|
1387
|
+
if (q.handle) {
|
|
1388
|
+
where.push(`h."id" LIKE ? ESCAPE '\\'`);
|
|
1389
|
+
params.push(`%${escapeLike(q.handle)}%`);
|
|
1390
|
+
}
|
|
1391
|
+
if (q.direction && m.has("is_from_me")) {
|
|
1392
|
+
where.push(`m."is_from_me" = ?`);
|
|
1393
|
+
params.push(q.direction === "sent" ? 1 : 0);
|
|
1394
|
+
}
|
|
1395
|
+
if (q.fromApple !== void 0 && m.has("date")) {
|
|
1396
|
+
where.push(`${appleSecondsSql("m.\"date\"")} >= ?`);
|
|
1397
|
+
params.push(q.fromApple);
|
|
1398
|
+
}
|
|
1399
|
+
if (q.toApple !== void 0 && m.has("date")) {
|
|
1400
|
+
where.push(`${appleSecondsSql("m.\"date\"")} < ?`);
|
|
1401
|
+
params.push(q.toApple);
|
|
1402
|
+
}
|
|
1403
|
+
if (!q.includeReactions && m.has("associated_message_type")) where.push(`(m."associated_message_type" IS NULL OR m."associated_message_type" = 0)`);
|
|
1404
|
+
return where.length ? where.join(" AND ") : "1 = 1";
|
|
1405
|
+
}
|
|
1406
|
+
/** The count columns every shape of this query selects. */
|
|
1407
|
+
#countExprs() {
|
|
1408
|
+
const m = this.caps.messageColumns;
|
|
1409
|
+
const split = m.has("is_from_me");
|
|
1410
|
+
return `COUNT(DISTINCT m."ROWID") AS total,
|
|
1411
|
+
${split ? `COUNT(DISTINCT CASE WHEN m."is_from_me" = 1 THEN m."ROWID" END)` : "NULL"} AS sent,
|
|
1412
|
+
${split ? `COUNT(DISTINCT CASE WHEN m."is_from_me" = 0 THEN m."ROWID" END)` : "NULL"} AS received,
|
|
1413
|
+
${m.has("date") ? appleSecondsSql("MIN(m.\"date\")") : "NULL"} AS firstAt,
|
|
1414
|
+
${m.has("date") ? appleSecondsSql("MAX(m.\"date\")") : "NULL"} AS lastAt`;
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* The column expression each groupable field aggregates on.
|
|
1418
|
+
*
|
|
1419
|
+
* A key here is the ONLY thing that reaches the statement — the tool matches
|
|
1420
|
+
* the caller's string against `COUNT_GROUP_FIELDS` and passes the matched
|
|
1421
|
+
* literal, so no caller-controlled text is ever interpolated. `label` goes
|
|
1422
|
+
* through MAX() because everything outside GROUP BY has to be aggregated, and
|
|
1423
|
+
* it is constant within a group anyway.
|
|
1424
|
+
*/
|
|
1425
|
+
#countGroupExpr(field) {
|
|
1426
|
+
switch (field) {
|
|
1427
|
+
case "day": return {
|
|
1428
|
+
key: appleDateBucketSql("m.\"date\"", "%Y-%m-%d"),
|
|
1429
|
+
label: "NULL"
|
|
1430
|
+
};
|
|
1431
|
+
case "month": return {
|
|
1432
|
+
key: appleDateBucketSql("m.\"date\"", "%Y-%m"),
|
|
1433
|
+
label: "NULL"
|
|
1434
|
+
};
|
|
1435
|
+
case "chat": return {
|
|
1436
|
+
key: `c."guid"`,
|
|
1437
|
+
label: `MAX(COALESCE(NULLIF(c."display_name", ''), c."chat_identifier"))`
|
|
1438
|
+
};
|
|
1439
|
+
case "handle": return {
|
|
1440
|
+
key: `h."id"`,
|
|
1441
|
+
label: "NULL"
|
|
1442
|
+
};
|
|
1443
|
+
case "direction": return {
|
|
1444
|
+
key: `CASE WHEN m."is_from_me" = 1 THEN 'sent' ELSE 'received' END`,
|
|
1445
|
+
label: "NULL"
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
#toTotals(r) {
|
|
1450
|
+
return {
|
|
1451
|
+
total: Number(r.total ?? 0),
|
|
1452
|
+
sent: num(r.sent),
|
|
1453
|
+
received: num(r.received),
|
|
1454
|
+
firstAt: num(r.firstAt),
|
|
1455
|
+
lastAt: num(r.lastAt)
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1285
1458
|
close() {
|
|
1286
1459
|
try {
|
|
1287
1460
|
this.db.close();
|
|
@@ -1319,6 +1492,14 @@ const openStore = (path, mode, logger) => {
|
|
|
1319
1492
|
};
|
|
1320
1493
|
//#endregion
|
|
1321
1494
|
//#region src/client/messages.ts
|
|
1495
|
+
/** Apple-seconds to ISO on the two date fields every count carries. */
|
|
1496
|
+
const renderCount = (c) => ({
|
|
1497
|
+
total: c.total,
|
|
1498
|
+
sent: c.sent,
|
|
1499
|
+
received: c.received,
|
|
1500
|
+
firstAt: renderInstant(c.firstAt),
|
|
1501
|
+
lastAt: renderInstant(c.lastAt)
|
|
1502
|
+
});
|
|
1322
1503
|
var AppleMessagesClient = class {
|
|
1323
1504
|
#config;
|
|
1324
1505
|
#logger;
|
|
@@ -1591,6 +1772,33 @@ var AppleMessagesClient = class {
|
|
|
1591
1772
|
}));
|
|
1592
1773
|
}
|
|
1593
1774
|
/**
|
|
1775
|
+
* Count messages, optionally grouped — the cheap-answer lane on this surface.
|
|
1776
|
+
*
|
|
1777
|
+
* Deliberately metadata-only. The expensive questions here are "how many" and
|
|
1778
|
+
* "how many per X", and the store answers both in SQL without a message body
|
|
1779
|
+
* ever reaching the context window. Anything text-derived stays with
|
|
1780
|
+
* `searchMessages`, which has the decode pass that makes it complete.
|
|
1781
|
+
*/
|
|
1782
|
+
countMessages(q, groupBy) {
|
|
1783
|
+
const result = this.#require().countMessages({
|
|
1784
|
+
...q,
|
|
1785
|
+
limit: resolveLimit(q.limit, this.#config.maxResults)
|
|
1786
|
+
}, groupBy);
|
|
1787
|
+
if (!("groups" in result)) return renderCount(result);
|
|
1788
|
+
const groups = result.groups;
|
|
1789
|
+
if (groupBy === "handle") this.#resolve(groups.map((g) => g.key));
|
|
1790
|
+
return {
|
|
1791
|
+
groups: groups.map((g) => ({
|
|
1792
|
+
key: g.key,
|
|
1793
|
+
label: groupBy === "handle" ? this.#correspondent(g.key).name ?? g.label : g.label,
|
|
1794
|
+
...groupBy === "chat" && g.key ? { ref: encodeChatRef(g.key) } : {},
|
|
1795
|
+
...renderCount(g)
|
|
1796
|
+
})),
|
|
1797
|
+
totalGroups: result.totalGroups,
|
|
1798
|
+
totalRows: result.totalRows
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
/**
|
|
1594
1802
|
* Pick the chat to address, using the store.
|
|
1595
1803
|
*
|
|
1596
1804
|
* Handles are matched the way `packages/contacts` measured them rather than by
|
|
@@ -1944,6 +2152,16 @@ an empty message history.
|
|
|
1944
2152
|
- **\`apple_messages_search_messages\`** searches across them.
|
|
1945
2153
|
- **\`apple_messages_get_message\`** returns one message with its tapbacks and
|
|
1946
2154
|
attachments.
|
|
2155
|
+
- **Anything that is a NUMBER** — how many, how many did I send, who do I write
|
|
2156
|
+
to most, which month was busiest — is \`apple_messages_count_messages\`, with
|
|
2157
|
+
\`groupBy\` for a breakdown by day, month, chat, handle or direction. It
|
|
2158
|
+
counts every match rather than a page, so never tally a listing by hand: past
|
|
2159
|
+
the first \`limit\` that answer is simply wrong. Group by **handle** rather
|
|
2160
|
+
than chat when the question is about a PERSON — one person can hold several
|
|
2161
|
+
conversations, and \`list_chats\` counts each separately.
|
|
2162
|
+
- It counts metadata only, and there is no text filter on it. "How many times
|
|
2163
|
+
did we say X" is not a question this surface can answer by counting: see the
|
|
2164
|
+
blob below.
|
|
1947
2165
|
|
|
1948
2166
|
## Handles are not names
|
|
1949
2167
|
|
|
@@ -2291,6 +2509,62 @@ Returning nothing is the normal result when no code has arrived. It does NOT mea
|
|
|
2291
2509
|
}));
|
|
2292
2510
|
};
|
|
2293
2511
|
//#endregion
|
|
2512
|
+
//#region src/tools/counts.ts
|
|
2513
|
+
/** ISO-8601 in, or a clear refusal. Same grammar as `list_messages`. */
|
|
2514
|
+
const parseBound$1 = (raw, field) => {
|
|
2515
|
+
if (raw === void 0) return void 0;
|
|
2516
|
+
const d = new Date(raw);
|
|
2517
|
+
if (Number.isNaN(d.getTime())) throw new Error(`Could not read ${field} from ${JSON.stringify(raw)}. Use ISO-8601: "2026-08-01" or "2026-08-01T09:00".`);
|
|
2518
|
+
return d;
|
|
2519
|
+
};
|
|
2520
|
+
/**
|
|
2521
|
+
* The cheap-answer lane on this surface: counts, and counts per group.
|
|
2522
|
+
*
|
|
2523
|
+
* Narrower than Mail's `apple_mail_query` on purpose. Mail's version also
|
|
2524
|
+
* projects rows, because a mail row is mostly metadata and dropping half of it
|
|
2525
|
+
* saves real tokens; a Messages row's bulk IS its text, and a message listing
|
|
2526
|
+
* without the text is rarely the question. What was actually missing here is
|
|
2527
|
+
* arithmetic: a sent/received split, per-period volume, and per-handle totals
|
|
2528
|
+
* across the several chats one person can occupy — none of which
|
|
2529
|
+
* `apple_messages_list_chats` can answer, and all of which otherwise mean
|
|
2530
|
+
* paging conversations into the context to tally them by hand.
|
|
2531
|
+
*
|
|
2532
|
+
* Metadata only, and that boundary is load-bearing rather than a first cut: see
|
|
2533
|
+
* `COUNT_GROUP_FIELDS` in `store.ts` for why a text-derived aggregate cannot be
|
|
2534
|
+
* offered on a store whose recent bodies live in a blob.
|
|
2535
|
+
*/
|
|
2536
|
+
const registerCountTools = (server, client) => {
|
|
2537
|
+
server.registerTool("apple_messages_count_messages", {
|
|
2538
|
+
description: "Count messages without reading them. Answers 'how many', 'how many did I send', 'who do I message most' and 'which month was busiest' as arithmetic the store does, rather than a page of conversations you tally by hand. Every result splits `sent` and `received`.\n\n`groupBy` counts per day, month, chat, handle or direction, over EVERY match rather than the first `limit` of them — `limit` caps how many groups come back. Grouping by handle is the one to reach for when a person has several conversations, since apple_messages_list_chats already gives you per-CHAT totals.\n\nCounts metadata only: there is no text filter here, because message bodies from 2026 onward live in an archived blob that SQL cannot see, and a count that quietly skipped them would look exactly like a correct one. Use apple_messages_search_messages for anything about what was said. Tapbacks are excluded unless you ask for them.",
|
|
2539
|
+
inputSchema: {
|
|
2540
|
+
chatRef: chatRefArg,
|
|
2541
|
+
handle: z.string().optional().describe("Only messages with this correspondent — a phone number or email, matched as a substring. Spans every chat they appear in, which is the difference from chatRef."),
|
|
2542
|
+
direction: z.enum(["sent", "received"]).optional().describe("Only what you sent, or only what you received. Omit for both."),
|
|
2543
|
+
from: fromArg,
|
|
2544
|
+
to: z.string().optional().describe("End of the window, ISO-8601, exclusive. Omit to count up to now."),
|
|
2545
|
+
includeReactions: includeReactionsArg,
|
|
2546
|
+
groupBy: groupByArg(COUNT_GROUP_FIELDS, "Day and month buckets are LOCAL calendar dates."),
|
|
2547
|
+
limit: limitArg
|
|
2548
|
+
},
|
|
2549
|
+
annotations: { readOnlyHint: true }
|
|
2550
|
+
}, async ({ chatRef, handle, direction, from, to, includeReactions, groupBy, limit }) => wrap(async () => {
|
|
2551
|
+
const window = client.window(parseBound$1(from, "from"), parseBound$1(to, "to"));
|
|
2552
|
+
const result = client.countMessages({
|
|
2553
|
+
...chatRef ? { chatGuid: decodeChatRef(chatRef) } : {},
|
|
2554
|
+
...handle === void 0 ? {} : { handle },
|
|
2555
|
+
...direction === void 0 ? {} : { direction },
|
|
2556
|
+
...window,
|
|
2557
|
+
...includeReactions === void 0 ? {} : { includeReactions },
|
|
2558
|
+
...limit === void 0 ? {} : { limit }
|
|
2559
|
+
}, groupBy);
|
|
2560
|
+
if (!("groups" in result)) return result;
|
|
2561
|
+
return describeAggregation(groupBy, result.groups.map(({ total, ...rest }) => ({
|
|
2562
|
+
...rest,
|
|
2563
|
+
count: total
|
|
2564
|
+
})), result);
|
|
2565
|
+
}));
|
|
2566
|
+
};
|
|
2567
|
+
//#endregion
|
|
2294
2568
|
//#region src/tools/messages.ts
|
|
2295
2569
|
/** ISO-8601 in, or a clear refusal. No relative grammar on this surface yet. */
|
|
2296
2570
|
const parseBound = (raw, field) => {
|
|
@@ -2360,7 +2634,7 @@ const registerMessageTools = (server, client) => {
|
|
|
2360
2634
|
/**
|
|
2361
2635
|
* Register the Apple Messages tools.
|
|
2362
2636
|
*
|
|
2363
|
-
*
|
|
2637
|
+
* Six reads, always. Two writes, only when `allowWrites` is on — and on this
|
|
2364
2638
|
* surface the flag carries a permission claim as well as a safety one: with it
|
|
2365
2639
|
* off no Apple Event is ever sent, so no Automation grant is ever requested.
|
|
2366
2640
|
* What is needed either way is Full Disk Access, absolutely — see `diagnostics`.
|
|
@@ -2383,6 +2657,7 @@ const registerTools = (server, client, ctx) => {
|
|
|
2383
2657
|
registerDiagnosticsTools(server, client);
|
|
2384
2658
|
registerChatTools(server, client);
|
|
2385
2659
|
registerMessageTools(server, client);
|
|
2660
|
+
registerCountTools(server, client);
|
|
2386
2661
|
if (ctx.allowCodes) registerCodeTools(server, client);
|
|
2387
2662
|
if (!ctx.allowWrites) return;
|
|
2388
2663
|
registerActionTools(server, client, ctx);
|
|
@@ -2432,4 +2707,4 @@ const createServer = (opts) => {
|
|
|
2432
2707
|
//#endregion
|
|
2433
2708
|
export { MESSAGES_SURFACE as A, toAppleSeconds as B, locateStore as C, ChatNotFoundError as D, AppleMessagesError as E, SendTargetNotFoundError as F, CORE_DATA_EPOCH_OFFSET as I, appleSecondsSql as L, MessagesUnavailableError as M, SchemaDriftError$1 as N, IndexUnavailableError$1 as O, SendFailedError as P, fromAppleSeconds as R, defaultStorePath as S, PRELUDE as T, BUILD_INFO as V, decodeMessageRef as _, loadConfig$1 as a, ATTACHMENTS_RELATIVE as b, introspect as c, decodeAttributedBody as d, outline as f, decodeChatRef as g, MESSAGE_REF_VERSION as h, registerTools as i, MessageNotFoundError as j, MESSAGES_BUNDLE_ID as k, openStore as l, InvalidMessageRefError as m, SERVER_VERSION as n, AppleMessagesClient as o, CHAT_REF_VERSION as p, createServer as r, MessagesStore as s, SERVER_NAME as t, reactionLabel as u, encodeChatRef as v, SEND_MESSAGE as w, STORE_RELATIVE as x, encodeMessageRef as y, renderInstant as z };
|
|
2434
2709
|
|
|
2435
|
-
//# sourceMappingURL=server-
|
|
2710
|
+
//# sourceMappingURL=server-CPDQqGEn.js.map
|