Cupertino 1.11.0
Version 1.11.0 ·
Added
apple_messages_count_messages— counting, grouped, on the Messages surface. Totals with a sent/received split, andgroupByover day, month, chat, handle or direction. It aggregates in SQL over every match, so "who do I message most" or "was August busier than July" costs one small result rather than a page of conversations tallied by hand — and past the firstlimit, tallying a listing does not merely cost more, it returns the wrong number.It is the counting half of Mail's query lane and deliberately not the rest. Projection was dropped:
selectpays on mail because a mail row is metadata around a small payload, while a message row's bulk is its text. Per-chat totals were already there, from theCOUNT()behindapple_messages_list_chats— so the groupings that ship are the ones that tool cannot answer, of whichhandleis the one worth having: a person can hold several conversations, and only this adds them together.There is no text filter, and that is a boundary rather than an omission. Message bodies from March 2026 onward live only in
attributedBody, which is whysearch_messagescarries a JavaScript decode pass. NoGROUP BYreaches inside a blob, so a counted text filter would be right for 2016-2025 and quietly wrong for everything since, with nothing on the result to say so. What this lane counts, it counts completely; anything about what was said stays with the search tool.Two traps are pinned by tests: tapbacks are excluded by default (2,788 rows that nobody typed), and every count is
COUNT(DISTINCT m."ROWID"), because the chat join is one row per (chat, message) pair and a message filed in two chats would otherwise count twice. Day and month buckets are LOCAL calendar dates — texting peaks after midnight, and a UTC bucket moves those messages into the previous day. Mail's buckets are still UTC;docs/messages.mdlists aligning them as open.
Changed
The footer's "More apps" column lists three siblings rather than one. Bastion, DevPulse and D1Explorer, each with a one-line blurb as its
title. A single link beside four full columns read as an unfinished list, and three is a short recommendation rather than a link farm: every one of these is anmgcrea.iosubdomain, and a full mesh between all nine would be the latter. The list is its ownFOOTER_APPSconstant, kept apart from theSIBLING_APPthat feeds the homepage card — the two are sized for different places and were drifting toward one shape that fits neither.
Fixed
The Sound pane advertised the Screen surface's tools. The Capabilities card does not keep a list of its own — it asks the server and renders the answer, which is what lets it demonstrate that a gated tool is never registered rather than refused later. For the two surfaces the app hosts itself it asked
ScreenServerfor all of them, so Sound's pane listedapple_screen_list_targetsandcupertino://screen/guide, and switching Allow microphone recording on madeapple_screen_capture_surfaceappear, because the gate was read by position rather than by ID. The write flag was accepted and then dropped, soapple_sound_set_volumecould not have shown up even once the right server answered.Only the card was wrong.
ServerHostdispatched correctly the whole time, so a real MCP client always reached the right server — which is also why nothing caught it:make screen-checkandmake sound-checkeach drive one server directly, and both servers were fine.Which server serves which surface, and what each one has to be told, now lives in one place (
InProcessServers) used by both the host and the card, andmake dispatch-checkasserts that every in-process surface answers under its own name with its ownapple_<id>_*tools andcupertino://<id>/*resources, under every combination of the write flag and its gates.docs/sound.mdnamed aplaytool that was never built, and leftdiagnosticsoff the same row.afplaywent with it: nothing shells out to it, so listing it as part of the free half's lane described a capability the surface does not have.