{"id":50969133,"url":"https://github.com/dalberto/imessage-history-skill","last_synced_at":"2026-06-19T00:01:43.567Z","repository":{"id":361041899,"uuid":"1238807854","full_name":"dalberto/imessage-history-skill","owner":"dalberto","description":"Ask your AI agent questions about your macOS iMessage history","archived":false,"fork":false,"pushed_at":"2026-05-28T23:57:13.000Z","size":404,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-29T01:24:37.785Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dalberto.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-14T13:25:31.000Z","updated_at":"2026-05-28T23:57:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dalberto/imessage-history-skill","commit_stats":null,"previous_names":["dalberto/imessage-history-skill"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/dalberto/imessage-history-skill","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dalberto%2Fimessage-history-skill","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dalberto%2Fimessage-history-skill/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dalberto%2Fimessage-history-skill/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dalberto%2Fimessage-history-skill/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dalberto","download_url":"https://codeload.github.com/dalberto/imessage-history-skill/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dalberto%2Fimessage-history-skill/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34511617,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-18T02:00:06.871Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-19T00:01:42.736Z","updated_at":"2026-06-19T00:01:43.528Z","avatar_url":"https://github.com/dalberto.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# imessage-history\n\nAsk your AI agent questions about your macOS iMessage history. Runs\nlocally, reads your chat database directly, and never leaves your\nmachine. Stdlib Python under the hood, zero install, zero dependencies.\n\n![A 1:1 iMessage dashboard generated by the skill — message share, response times, who-restarts-after-silence, and monthly volume. Generated locally; content-free.](assets/dashboard.png)\n\n*A dashboard the skill generated for a 1:1 thread (contact name anonymized). See [Dashboards](#dashboards).*\n\n## What you can do\n\nPull up exactly what was said, when, and by whom, across years of\nchats, including group chats with phones, emails, and SMS contacts\nmixed together. Useful for:\n\n- **Recall.** \"Where did Alex say to meet on Friday?\"\n- **Citation.** \"Pull the back-and-forth around the night we picked\n  the restaurant.\"\n- **Audit.** \"Every photo shared in the family chat last summer.\"\n- **Rhythm.** \"How often do I text my partner? When was our longest\n  quiet stretch?\"\n- **Dashboards.** \"Build me a dashboard of my texting patterns with my\n  brother\" — a self-contained HTML page of response times, who texts\n  first, streaks, and volume over time. Quantitative dashboards include\n  no message content; an optional narrative mode adds quoted highlights.\n\nThe Mac Messages app shows you one chat at a time and stops scrolling\nafter a few months. This skill searches the underlying database\nexhaustively, decodes the message format Apple actually uses today,\nand returns every match in a date range. No silent truncation, no\nhidden limits.\n\n## How to use it\n\nDrop the skill into any AI agent that follows the\n[Agent Skills spec][spec] — Claude Code, Cursor, Codex, and others\nall work. Then ask in plain English:\n\n```\nSummarize what the family chat said about Thanksgiving plans last fall.\nWhen did Alex first send me their new address?\nPull the back-and-forth around the night we picked the restaurant.\nHow active is the family group chat day by day?\nBuild me a dashboard of my texting patterns with Alex.\n```\n\nThe agent picks the right query, runs the script locally, and gives\nyou the answer. You don't memorize flags. You don't think about SQL.\n\n[spec]: https://agentskills.io/specification\n\n## Dashboards\n\nTurn a one-on-one thread into the dashboard at the top of this README —\nresponse times, who texts first, double-texting, conversational streaks, and\nvolume over time. Just ask:\n\n```\nBuild me a dashboard of my texting patterns with Alex.\nHow responsive is Alex compared to me? Make it a dashboard.\nVisualize my thread with Alex over the last year.\n```\n\nThe agent finds the 1:1 chat, computes the metrics locally, renders a\nself-contained HTML page, and opens it in your browser. The standard\ndashboard is **content-free** — only counts, timestamps, and message lengths,\nnever message text. Ask for a *\"field report\"* to add an interpretive layer\n(themes, a narrative arc, and quoted highlights); that variant embeds real\nquotes, so treat the file as private.\n\nDashboards are **1:1 only** (response time and who-restarts are inherently\npairwise). Under the hood it's two subcommands — `metrics` (numbers as JSON)\nand `dashboard` (the HTML):\n\n```fish\n# find the 1:1 chat_id, then render + open\npython3 scripts/imessage.py chats --participant \"Alex\"\npython3 scripts/imessage.py dashboard \u003cchat_id\u003e --theme light --out ~/Desktop/alex.html\nopen ~/Desktop/alex.html\n```\n\nPick a subset with `--modules`, switch to `--theme dark`, or pass\n`--annotations notes.json` for the narrative layer. Run\n`python3 scripts/imessage.py dashboard --help` for all flags.\n\n## Privacy \u0026 safety\n\nDesigned to be opened, used, and put away without anything leaving\nyour machine.\n\n- **Local only.** Reads `~/Library/Messages/chat.db` directly.\n  Nothing is uploaded, no API key is required, no telemetry is\n  collected. The script makes zero network calls.\n- **Read-only.** SQLite is opened in `mode=ro`. The script cannot\n  modify your messages even if it tried.\n- **No dependencies.** Stdlib Python only. No `pip install`, no\n  third-party libraries that could phone home.\n- **You control where output goes.** Results print to stdout. If you\n  pipe them into another tool (a cloud LLM, a pastebin, etc.), that's\n  an explicit choice. Be intentional.\n- **Custom contact names are gitignored.** If you create\n  `contacts.json` to rename a handle, it's excluded from version\n  control by default.\n- **Generated dashboards are gitignored.** Quantitative dashboards are\n  content-free, but narrative dashboards embed quoted messages, so\n  `*.html` output is excluded from version control by default. Share\n  generated files intentionally.\n\nYou'll need to grant **Full Disk Access** to whatever process runs\nthe script (Terminal, iTerm, Ghostty, VS Code, etc.) because\n`chat.db` lives in a TCC-protected location. See [Install](#install)\nbelow for the one-time setup.\n\n## Install\n\nPick whichever flow you prefer. All three install the same content.\n\n### `npx skills` (recommended)\n\nEasiest for newcomers. Works across Claude Code, Cursor, Codex via\nVercel's [`skills`][vercel-skills] CLI:\n\n```fish\nnpx skills add dalberto/imessage-history-skill\n# pin to a release:\nnpx skills add dalberto/imessage-history-skill#v0.1.0\n```\n\n[vercel-skills]: https://github.com/vercel-labs/skills\n\n### `git clone`\n\nDrop the repo straight into your AI agent's skills directory:\n\n```fish\ngit clone https://github.com/dalberto/imessage-history-skill ~/.claude/skills/imessage-history\n```\n\n(Substitute `~/.cursor/skills/` or wherever your agent looks.)\n\n### `dotagents` (declarative)\n\nIf you manage skills through [`@sentry/dotagents`][dotagents]:\n\n```fish\nnpx @sentry/dotagents add dalberto/imessage-history-skill imessage-history\n```\n\n[dotagents]: https://github.com/getsentry/dotagents\n\n### Grant Full Disk Access (one-time)\n\n1. Open **System Settings → Privacy \u0026 Security → Full Disk Access**.\n2. Add your terminal app (Terminal, iTerm, Ghostty, VS Code, etc.).\n3. Restart that terminal app after adding it.\n\nWithout Full Disk Access you'll get `unable to open database file`.\n\n## Custom contact names\n\nContacts are resolved automatically from your macOS Address Book, so\nmost people don't need to configure anything. If a handle isn't in\nAddress Book, or you want a nickname, copy `contacts.example.json` to\n`contacts.json` at the repo root:\n\n```json\n{\n  \"me\": \"YourName\",\n  \"+15551234567\": \"Alex Example\",\n  \"alex@example.com\": \"Alex Example\"\n}\n```\n\nThe `me` key controls how your own messages are labeled (default:\n`Me`). Everything else overrides or supplements Address Book. Keys\nuse the raw `handle.id` value from chat.db: phone numbers in\n`+15551234567` form, emails as-is.\n\n`contacts.json` is gitignored; `contacts.example.json` is the shipped\ntemplate. Pass `--contacts /path/to/file.json` to use a different\noverride file.\n\n## Under the hood\n\nThe AI agent is just a friendly front-end. Underneath, this skill is\na stdlib Python script (`scripts/imessage.py`, plus a sibling\n`scripts/dashboard.py` and an editable HTML/CSS shell at\n`templates/dashboard.html`, used only for rendering dashboards) that you\ncan also run directly:\n\n```fish\n# Find the family group chat\npython3 scripts/imessage.py chats --name \"family\"\n\n# How often do I text Alex? What's our longest quiet stretch?\npython3 scripts/imessage.py chats --participant \"Alex Example\"\npython3 scripts/imessage.py stats 42\n\n# Where did Alex say to meet on Friday?\npython3 scripts/imessage.py search -k \"Friday\" --from \"Alex Example\"\n\n# Did anyone in this chat recommend a dentist last fall?\npython3 scripts/imessage.py search --chat-id 42 -k \"dentist\" \\\n    --since 2025-09-01 --until 2025-12-01\n\n# Pull the thread around the night we picked the restaurant\npython3 scripts/imessage.py window 42 \"2026-04-12 19:30\" --before 5 --after 30\n\n# Every photo shared in the family chat since June\npython3 scripts/imessage.py attachments 42 --mime-like image --since 2025-06-01\n\n# That address Alex sent a while back, as JSON for piping\npython3 scripts/imessage.py search -k \"Bedford\" --from \"Alex Example\" --format ndjson\n\n# Build a dashboard of my texting patterns with Alex (1:1 chat)\npython3 scripts/imessage.py dashboard 42 --theme light --out alex.html\n```\n\nPhone numbers and emails are resolved to the names in your macOS\nAddress Book, so output reads `[2026-04-15 19:00:47] Alex Example:\n\"...\"` instead of `+15551234567`. Every content-returning subcommand\naccepts `--format {text,json,ndjson}`. Run\n`python3 scripts/imessage.py \u003csubcommand\u003e --help` for the full flag\nlist.\n\n### Subcommands\n\n| Subcommand | What it does |\n| --- | --- |\n| `chats` | List chats, filter by name / handle / participant, optionally count only within a date range. |\n| `participants` | Roster of a chat with per-chat message counts. |\n| `stats` | Per-sender counts, median length, weekday + hour histograms, longest dormancy. |\n| `search` | Keyword search, scoped or global. `-k` repeatable (OR by default, `--all` for AND). `-K` excludes. `--from` / `--not-from` filter by sender name or handle substring. `--regex` post-filters. |\n| `window` | All messages in a ±window around a timestamp. Reply-chain recovery. |\n| `dump` | Every message in a chat over a date range, with the same filters as `search`. |\n| `anchor-sweep` | Keyword search then auto-window and merge, returning contiguous passages with anchors marked. |\n| `attachments` | List attachments in a chat over a range. |\n| `reactions` | Surface tapbacks (normally filtered) with their target messages. |\n| `metrics` | 1:1 relationship metrics (response time, share, who-restarts, double-texting, streak, monthly volume) as JSON. Content-free. |\n| `dashboard` | Render a self-contained HTML dashboard for a 1:1 chat. `--theme light/dark`, `--out`, `--modules`, and `--annotations` for an optional narrative layer. |\n\n### Why this needs to be a script\n\n`chat.db` is a real SQLite database, but ad-hoc SQL against it is a\ntrap. Two gotchas catch most people:\n\n1. **`message.text` is mostly NULL on modern macOS.** Content lives\n   in an Apple-internal binary blob (`attributedBody`), and SQLite's\n   default `CAST` truncates at the first NUL byte. This script\n   decodes the blob in Python.\n2. **Convenience wrappers silently cap results.** Many tools apply a\n   `LIMIT` *after* the date filter. This script never does.\n\nFor the full schema map, decoding details, and other gotchas, read\n[`references/chatdb_schema.md`](references/chatdb_schema.md).\n\n## Limitations\n\n- **macOS only.** iMessage history uses macOS-specific paths and\n  schemas.\n- **Apple's internal schema.** The script depends on column names and\n  typedstream layout that Apple can change in major macOS updates.\n  Tested on macOS 13–14; later versions may need adjustments.\n- **Address Book schema drift.** The script reads\n  `AddressBook-v22.abcddb` via the `ZABCDRECORD` / `ZABCDPHONENUMBER`\n  / `ZABCDEMAILADDRESS` tables. Different macOS versions may vary.\n- **SMS / iMessage / RCS aren't distinguished** in output. The\n  `service` column is shown in `participants` but not filtered on.\n- **Phone normalization is US-friendly.** The last-10-digits strategy\n  works for NANP numbers. International numbers are compared by\n  normalized form (digits only).\n- **`text` fallback.** A handful of older messages may still have\n  `text` populated while `attributedBody` is NULL. The script reads\n  `text` when `attributedBody` decoding returns empty.\n\n## Evals\n\nTwo eval sets live under `evals/`:\n\n- **`trigger_evals.json`** — 20 should-trigger and should-not-trigger\n  queries for tuning the AI-agent description. Synthetic, runnable\n  by anyone.\n- **`evals.json`** — execution test cases. Uses `\u003cGROUP_NAME\u003e` /\n  `\u003cCONTACT_NAME\u003e` / `\u003cTOPIC\u003e` placeholders; substitute your own\n  values before running against your `chat.db`.\n\n## License\n\nMIT — see [`LICENSE`](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdalberto%2Fimessage-history-skill","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdalberto%2Fimessage-history-skill","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdalberto%2Fimessage-history-skill/lists"}