Repository navigation
Releases: danielhopkins/apple-tools
Release list
apple-tools 26.924.0
Fixes
apple mail composeno longer fails silently when something killsosascript. Under cmux 0.64.25 on macOS 27.2, cmux SIGTERMs anyosascriptthat inherits its bundle identifier (manaflow-ai/cmux#13839, fix in #13845). The error now names the signal and the known cause.apple mail statusreportsprobe_killedand does not tell you to restart a healthy Mail. The Contacts note write names the signal too.
Plugins
- Every dated record now has a
datefield: health workouts and samples, and dawarich visits and GPS points. - Each plugin subcommand's
--helpends with its JSON fields and units. apple --helplists the enabled plugins with a description.- The
apple-toolsskill has a Plugins section. Lab results are inapple health clinical --type labs.
App
- A Search pane that runs the same search as the CLI.
- A wrapped note no longer draws upside down on macOS 27.
26.918.0
Health, from the phone
healthplugin + AppleTools Health iPhone app. There is no Health data on a Mac, so an iPhone app with the HealthKit entitlement reads it and writes into its iCloud container: daily totals for 14 types, sleep stages, workouts with routes, every raw reading of 21 heart and body types, and clinical records (labs, immunizations, medications, …) as FHIR. The app keeps a log;apple health logshows it on the Mac.- A raw store an agent can query:
apple health trend,samples,clinical, andsql(read-only SELECT). Labs and immunizations are index records. - Workouts are presence.
whereaboutsreads a workout with a route at weight 0.90;placescounts workouts that started within 250 m of a place (win the app). - Places named from the address book ("Jon's home", "Work") and grouped by zoom on the map.
- The
Apple Tools Health Exportshortcut ships as a fallback beside the plugin.
26.917.0
Somebody else's camera is off the map. A place only the iCloud Shared Library knows — a relative's photos on a trip the user did not take — is left off the Places pane's map, list and count; the legend says how many. apple-index places still reports them under photo_days_shared, apart from the user's own photo_days.
🤖 Generated with Claude Code
26.916.2
Dallas. Photo places now carry the city, county and state from Apple's placemark, so a search for a metro name finds the days spent in its suburbs. The Places pane has a filter over name and address. And the map no longer erases a region: beyond the top 400 places, one dot is drawn wherever no drawn place lies within 25 km — three photo days in Texas were at rank ~1,200 and drew nothing.
Dawarich in the Places pane. Green for a place only a plugin knows, purple for any place more than one source knows, and a plugin's stays counted in their own unit beside days photographed and Maps arrivals.
🤖 Generated with Claude Code
26.916.1
Dawarich re-runs its visit detection and mints new ids for the same stay. The index kept the old copies beside the new ones, so one lunch read as five stays. The plugin's refresh now reconciles every cycle, and whereabouts folds a stay that shares a start and a length with one already seen. Otherwise identical to 26.916.0.
🤖 Generated with Claude Code
26.916.0
Plugins in the app window
An enabled plugin's row in the Sources pane now opens onto its own panel: whether the server answers, its version and account, every configuration key as a field (a secret never shows its value), and Disable. A plugin that is installed and not enabled is listed under the table with Enable, which stays off until the required keys are set. Everything goes through apple plugins, so the window and the CLI cannot disagree.
apple-index whereabouts
Where you were, day by day, from every source that knows: a Maps arrival, a Dawarich stay with its duration, a photo day, a calendar pin. Every place names its sources and carries a belief from 0 to 1 — a noisy-OR over one weight per source, and every weight is printed. A calendar event alone is a plan, never a presence. A photo day from somebody else's camera is evidence they were there, not you. Consecutive away days are a trip.
apple-index whereabouts --since 7
apple-index whereabouts --from 2026-01-01 --to 2026-09-01 | grep ^trip
"Where did I go this week" is not a search, and search --since returned nothing for it; this is the command that answers it.
dawarich
A stay's Dawarich confidence now rides in the index record, so whereabouts can weigh it.
🤖 Generated with Claude Code
26.914.1
The app now carries NSLocalNetworkUsageDescription, so it can ask for the Local Network grant a plugin needs to reach a server on the LAN. Without the grant macOS 15+ answers No route to host, and the first scheduled dawarich ingest inside the app failed that way while the same command passed in a terminal. Recorded in docs/apple-plugins.md.
Tools are unchanged from 26.914.0.
🤖 Generated with Claude Code
26.914.0
Plugins
A plugin is an external executable, apple-plugin-<name>, that reads something that is not an Apple store and joins the tools on equal terms: apple <name> … runs it, apple status reports it, and apple-index indexes what it returns. Nothing runs until apple plugins enable <name>, and a plugin that makes a connection declares its hosts, which apple plugins list and apple status print. Secrets go to the Keychain, never to a file.
apple plugins list
apple plugins config dawarich url=https://… api_key=…
apple plugins enable dawarich
dawarich, the first plugin
Reads a self-hosted Dawarich server: visits with a start and an end (Maps records the start alone), places, and the GPS track.
apple dawarich visits --since 30 --json # duration_seconds on every row
apple dawarich points --from "2026-09-10 14:00" --to "2026-09-10 16:00"
Measured on a real server: every visit is suggested, so that is the default; 28% have no coordinate. A suggested visit never anchors a places merge — on the first ingest, 1,651 guesses out-weighed 1,649 photo days and renamed the user's home after a house number.
Full contract in docs/apple-plugins.md. The app stages the manager and each plugin, and refreshes an enabled plugin on its schedule.
🤖 Generated with Claude Code
v26.903.0
Calendar: an event can say whether it shows as busy or free
--availability on apple calendar add and apple calendar edit writes the
field Calendar.app calls "Show As". It takes busy, free, tentative or
unavailable. events/show --json report it as availability.
apple calendar add "Focus block" --start "tomorrow 9am" --availability free
apple calendar edit <id> --availability free
🛑 A calendar does not carry every value, and EventKit accepts one it does not
carry. Measured 2026-09-02 on a Google calDAV calendar carrying busy, free:
writing tentative reported success and read back as tentative from the local
store, for a state the account does not hold. So add and edit refuse a value
outside the calendar's set, naming what it carries. calendars --json now
reports availabilities per calendar.
Index: the files source reads PDF, Word and PowerPoint
kind names the format — note, file, pdf, docx, pptx.
- Word and PowerPoint are read in process: a
.docxis a zip of XML. - 🛑 PDF needs a binary,
index/doctext, shipped here. A missing one is a
warning, not a failure, and the count of skipped PDFs is named on stderr. - 🛑 A scan cannot be indexed. 20 of 159 PDFs on the development machine have no
text layer, and they are reported rather than stored as empty records. - 🛑
doctextreports broken-looking text and never drops it. Pages that look
like wreckage are often lists.
apple-index stats now nests this source: a roots array beside containers,
per configured folder. The app window draws it.
v26.831.0 — an all-day event needs a flag to become a timed one
Calendar: crossing the all-day boundary
isAllDay is a shape, not a display option. Setting a start with a clock time
on an all-day event left the flag true, and EventKit pinned the dates straight
back to midnight and 23:59:59 — save returned true, nothing errored, and the
read-back reported start is 00:00, expected 18:30, a symptom that reads like a
lost write. The only route left was to delete the event and create it again.
apple calendar edit <id> --start "18:30" # all-day -> 18:30, one hour long
apple calendar edit <id> --timed --start "18:30" --end "21:00"
apple calendar edit <id> --all-day # the other direction
- A
--startcarrying a clock time on an all-day event converts it, and says
so on stderr.--all-dayand--timedstate it outright. --timedwith nothing to hang a time on is refused. An all-day event's start
is midnight, and inventing a midnight event nobody asked for is worse than an
error.- A conversion with no
--endruns one hour, the same defaultadduses. An
all-day end of 23:59:59 is the end of the day, not an end time. - A bare date still means "no time given", so moving an all-day event to
another day leaves it all day. - The read-back now checks
isAllDayitself, so a refused conversion reports
"the event is still all-day" rather than two date mismatches.
--start on its own now keeps the event's length, the way dragging one in
any calendar app does. The old end stayed put, so a start moved later landed
after it and the request was refused with "end time must be at or after the
start time" — naming an end the caller never passed. Moving an all-day event to
the next day failed every time.
Nine new live tests cover it, in TestAllDayConversion.