| name | icloud-calls |
| description | Extract phone call transcripts from Apple Notes and save them to the Obsidian vault with link-on-first-mentions processing. Batch processes all unprocessed transcripts, tracks history to avoid reprocessing. Use this skill whenever the user says "process call transcripts", "get my calls", "import call recordings", "phone transcripts", "icloud calls", "call transcript", "process my phone calls", "get transcripts from notes", or mentions Apple Notes call recordings or phone transcript import.
|
iCloud Calls
Extract phone call transcripts from Apple Notes (iOS 18.1+ call recording feature), process them with Kepano's "link on first mentions" strategy, and file them to the Obsidian vault.
Model routing
Delegate to the vault-operator agent (pinned to Sonnet). Wikilinking and filing are mechanical; Opus isn't needed. The main session only intervenes if the agent escalates an ambiguous entity match.
How Apple Notes Stores Call Recordings
iOS 18.1+ records phone calls and stores them in Apple Notes as an audio attachment (.m4a file) with a transcript generated on-device. Important facts:
- The transcript is NOT in the note body — it's metadata on the audio attachment
- The note body is just
<h1>Call Recording</h1> with the audio player embedded
- The "Call Recordings" folder visible in the Notes sidebar is a system smart folder — not accessible via AppleScript. Notes actually live in the "Notes" folder.
- Apple's Notes AppleScript API does not expose transcript text — only the note body and attachment names
- The only way to get the transcript text is via the UI: right-click the audio attachment → "Copy Transcript"
Extraction Approach: Clipboard-Based
Since Apple locks the transcript in the attachment, this skill uses a clipboard-based workflow:
- The list script identifies notes with audio attachments
- For each note, the extract script opens it in Apple Notes
- The user right-clicks the audio attachment → "Copy Transcript" (one manual step per call)
- The skill reads the transcript from the clipboard via
pbpaste
- Processing, linking, and vault filing are fully automated
Step 0: Preflight
Verify both systems are reachable. Run these in parallel:
obsidian vault
osascript -e 'tell application "Notes" to count of notes'
If obsidian vault fails, Obsidian isn't running — report the error and stop.
If the osascript fails, Apple Notes isn't accessible — report and stop.
Step 1: List & Filter Transcripts
List available call recordings
bash .claude/skills/icloud-calls/list-transcripts.sh
This scans all Apple Notes folders (except Recently Deleted) for notes with .m4a or .caf audio attachments. Output is TSV: note_id \t title \t creation_date \t folder_name \t attachment_name
If no output, report "No call recordings found in Apple Notes" and stop.
Filter against history
Read the history file:
obsidian read path="zz_config/skill_history/icloud-calls.md"
Parse the processed JSON block. Filter out transcripts whose note ID already appears in processed.
If all transcripts are already processed, report "No new transcripts to process" and stop.
Otherwise, show a summary table of what will be processed:
| Date | Title | Attachment |
|---|
| Apr 6, 2026 | Call Recording | Call Recording.m4a |
Step 2: Extract Each Transcript
Process one transcript at a time. For each unprocessed call:
2a. Open the note and get metadata
bash .claude/skills/icloud-calls/extract-transcript.sh "NOTE_ID" --metadata-only
This opens the note in Apple Notes and outputs metadata:
TITLE:Call Recording
DATE:Monday, April 6, 2026 at 09:31:22
ID:x-coredata://...
ATTACHMENTS:1
ATTACHMENT:Call Recording.m4a
---
2b. Ask the user to copy the transcript
The note is now visible in Apple Notes. Tell the user:
Please right-click the audio player in Apple Notes and select "Copy Transcript", then come back here.
Use AskUserQuestion and wait for the user to confirm they've copied it.
2c. Read from clipboard
bash .claude/skills/icloud-calls/extract-transcript.sh "NOTE_ID"
(Without --metadata-only, the script reads the clipboard via pbpaste and outputs the transcript between TRANSCRIPT_START and TRANSCRIPT_END markers.)
Alternatively, read the clipboard directly:
pbpaste
2d. Validate the transcript
Check that the clipboard content looks like a transcript:
- Should contain speaker labels (e.g., "You", "Lyndsi (appolloni) Stender", "Speaker 1")
- Should have multiple lines of dialogue
- Should NOT be empty or contain unrelated clipboard content
If validation fails, ask the user to try copying again.
2e. Parse the transcript
The "Copy Transcript" format from Apple Notes has this structure:
You
Then I can,
Lyndsi (appolloni) Stender
There
You
I
Lyndsi (appolloni) Stender
we
You
can throw it through
Parse this into structured turns:
- Lines that match known speaker patterns (name followed by content on next line)
- Group consecutive utterances by the same speaker
- Extract participant names from speaker labels
- Clean up fragmented utterances where possible (Apple's transcript can be choppy)
Step 3: Build Vault Vocabulary
Run the shared vocabulary script — a single obsidian eval call that returns all entities as JSON in ~0.2s:
bash .claude/skills/shared/build-vocab.sh
Returns { people: [...], projects: [...], areas: [...], tags: [{tag, count}] }. This replaces the old 4-call pattern.
Focus on proper nouns and domain terms — don't include generic words.
Cache this vocabulary for the duration of the batch run. Don't rebuild for each transcript.
Step 4: Apply "Link on First Mentions"
Kepano's concept: create a [[wikilink]] the first time each entity is mentioned in the note. Subsequent mentions stay as plain text. This creates a web of connections without cluttering the text with redundant links.
For each transcript:
-
Identify entities in the text:
- People mentioned (cross-reference against
3r/ppl/ names)
- Projects referenced (cross-reference against
1p/ names)
- Areas referenced (cross-reference against
2a/ names)
- Other proper nouns: companies, products, places, organizations
- Don't link common words, generic terms, or prepositions
-
Link first mentions:
- First occurrence of each unique entity →
[[Entity Name]]
- If the entity matches an existing vault note, use the exact note name:
[[Kirk Henderson]]
- If the entity doesn't have a vault note yet, still link it:
[[New Entity]] (creates a breadcrumb for future connection)
- All subsequent mentions of the same entity → plain text (no link)
-
Linking rules:
- Match case-insensitively but preserve the original text's casing in the display
- Use aliases if needed:
[[Kirk Henderson|Kirk]] when the transcript uses a short form
- Don't link inside speaker labels — link the name in the body text instead
- Don't over-link: if uncertain whether something is a meaningful entity, leave it as plain text
Step 5: Generate Summary and Action Items
For each transcript, generate:
- Quick Summary (2-4 sentences): What was the call about? Key decisions or outcomes.
- Action Items: Extract follow-ups, commitments, and tasks as
- [ ] checkbox items. Only create tasks for clear, actionable items — don't force it if the call was purely informational.
- Key Topics: A short list of main subjects discussed (useful for search/backlinks).
Step 6: Create Vault Note
For each processed transcript, create a vault note.
Note naming
Format: Call - [primary participant or topic] - YYYY-MM-DD.md
Examples:
Call - Lyndsi Stender - 2026-04-06.md
Call - Insurance Claim Follow-up - 2026-04-03.md
If multiple calls on the same day with the same participant, append the time: Call - Lyndsi Stender - 2026-04-06 0931.md
Create the note
The meeting_transcript_template uses Templater interactive prompts (tp.system.prompt), so construct the note content directly rather than using the template:
obsidian create path="0.inbox/Call - Participant - YYYY-MM-DD.md"
Then set frontmatter properties and append content:
obsidian property:set name="fileClass" value="meeting_transcipt" path="0.inbox/Call - Participant - YYYY-MM-DD.md"
obsidian property:set name="title" value="Call with Participant" path="0.inbox/Call - Participant - YYYY-MM-DD.md"
obsidian property:set name="date" value="YYYY-MM-DD" path="0.inbox/Call - Participant - YYYY-MM-DD.md"
obsidian property:set name="source" value="Apple Notes" path="0.inbox/Call - Participant - YYYY-MM-DD.md"
Do NOT set created — obsidian create auto-adds Created with a timestamp. Setting created via property:set creates a duplicate.
Do NOT set time — it's not a field in the meeting_transcipt fileClass.
Note: The fileClass is meeting_transcipt (missing 'r' — existing typo in vault's zz_config/fileClass/meeting_transcipt.md). Use this exact spelling so metadata-menu picks it up.
Then append the body:
obsidian append path="0.inbox/Call - Participant - YYYY-MM-DD.md" content="..."
Note body structure
# Call with [participant]
**Date**: YYYY-MM-DD
**Time**: HH:MM
**Participants**: [[Participant One]], Participant Two
---
## Summary and Action Items
### Quick Summary
[AI-generated 2-4 sentence summary]
### Action Items
- [ ] Action item one
- [ ] Action item two
### Key Topics
- Topic one
- Topic two
---
## Transcript
**You**: Then I can,
**[[Lyndsi Stender|Lyndsi]]**: There. We can throw it through.
**You**: Go. A transcription and get, I don't have to take notes.
**[[Lyndsi Stender|Lyndsi]]**: Perfect, perfect. I know, so much more efficient these days, right?
Frontmatter notes
source: "Apple Notes" marks provenance
created is today's date (when processed), date is the call's actual date
- Participants in the body text get wikilinks on first mention via the linking pass
Step 7: Update History
Read the current history file, then use Edit tool to update both JSON blocks:
-
processed block: Add each processed transcript's note ID with metadata:
{
"note-id": {
"title": "Call Recording",
"date": "2026-04-06",
"vault_path": "0.inbox/Call - Lyndsi Stender - 2026-04-06.md",
"processed_on": "2026-04-06"
}
}
-
log block: Append a run entry. Resolve hostname first via a hostname -s Bash call (don't hardcode the example value below — it changes per machine):
{"date": "2026-04-06", "host": "<literal-hostname>", "count": 1, "transcripts": ["Call Recording"]}
Step 8: Report
After processing all transcripts, show:
-
Summary table:
| Call | Date | Vault Path | Entities Linked |
|---|
| Call with Lyndsi | 2026-04-06 | 0.inbox/Call - Lyndsi Stender - 2026-04-06.md | 3 |
-
Total: X transcripts processed, Y vault notes created
-
Linked entities: List of all entities that were wikilinked (for user review)
-
Any issues: Transcripts that couldn't be extracted or had parsing problems
Edge Cases
- Clipboard contains wrong content: Validate that clipboard looks like a transcript (speaker labels, dialogue). If not, ask user to re-copy.
- No speaker labels in transcript: Some transcripts may be flat text. Process as-is — the user can add speaker labels manually.
- Very long transcripts (500+ lines): Consider delegating the link-on-first-mentions processing to a subagent to protect context.
- Multiple calls same day, same participant: Disambiguate with time in filename.
- Apple's choppy transcription: iOS call transcripts can fragment sentences across speaker turns. Where possible, merge consecutive turns by the same speaker.
- Note title collision: Before creating, check if a note with that name already exists in
0.inbox/ via obsidian search. If it does, append a numeric suffix.
- Multiple notes with same title: Not an issue — the extract script matches by note ID (from list-transcripts.sh), not by title.
Dependencies
obsidian CLI for all vault operations
osascript for Apple Notes access (macOS only)
pbpaste for clipboard reading
- Apple Notes with call recordings (iOS 18.1+ call recording, synced via iCloud)
- Shell scripts in
.claude/skills/icloud-calls/
- User interaction: one "Copy Transcript" click per call recording