---
name: meeting-recorder-transcripts
description: Use when the user asks Codex to inspect, summarize, search, quote, or answer questions from local Meeting Recorder transcripts, including latest meetings, named participants or speakers, dates, topics, pricing discussions, or product meetings. This skill reads only local Meeting Recorder files and helps resolve the correct macOS app data path.
metadata:
  short-description: Search local Meeting Recorder transcripts
---

# Meeting Recorder Transcripts

Use this skill to answer questions from local Meeting Recorder data. Keep the workflow local: read files from disk, search narrowly, and avoid copying entire transcripts into the final answer unless the user explicitly asks.

## Resolve The Data Path

Meeting Recorder stores data under macOS Application Support unless the user chose custom storage. Never hardcode a person's username or absolute home path. Resolve paths at runtime.

If the agent runs in a Linux sandbox, it may not have access to the host Mac filesystem or macOS `defaults`. In that case, first look for an explicit path from the user or environment variables. If no transcript folder is mounted into the sandbox, explain that the local Meeting Recorder folder must be made available to the agent before transcript questions can be answered.

Supported environment overrides:

```bash
MEETING_RECORDER_MEETINGS=/path/to/meetings
MEETING_RECORDER_RUNS=/path/to/runs
MEETING_RECORDER_ROOT=/path/to/MeetingRecorder
```

Default root:

```bash
$HOME/Library/Application Support/MeetingRecorder
```

Default canonical meetings directory:

```bash
$HOME/Library/Application Support/MeetingRecorder/meetings
```

Default historical runs directory:

```bash
$HOME/Library/Application Support/MeetingRecorder/runs
```

Custom storage settings live in macOS defaults for the release app under `dataStorageScope` and `dataStorageCustomFolderPath`. Only read those keys when the `defaults` command exists.

Resolve paths with portable shell logic:

```bash
default_root="$HOME/Library/Application Support/MeetingRecorder"

if [ -n "${MEETING_RECORDER_RUNS:-}" ]; then
  runs="$MEETING_RECORDER_RUNS"
  root="${MEETING_RECORDER_ROOT:-$(dirname "$runs")}"
  meetings="${MEETING_RECORDER_MEETINGS:-$root/meetings}"
elif [ -n "${MEETING_RECORDER_MEETINGS:-}" ]; then
  meetings="$MEETING_RECORDER_MEETINGS"
  root="${MEETING_RECORDER_ROOT:-$(dirname "$meetings")}"
  runs="$root/runs"
else
  scope=""
  custom=""
  if command -v defaults >/dev/null 2>&1; then
    scope="$(defaults read com.pieloup.meeting-recorder dataStorageScope 2>/dev/null || true)"
    custom="$(defaults read com.pieloup.meeting-recorder dataStorageCustomFolderPath 2>/dev/null || true)"
  fi

  if [ -n "${MEETING_RECORDER_ROOT:-}" ]; then
    root="$MEETING_RECORDER_ROOT"
    runs="$root/runs"
    meetings="$root/meetings"
  else
    case "$scope" in
      all)
        root="${custom:-$default_root}"
        runs="$root/runs"
        meetings="$root/meetings"
        ;;
      transcripts)
        root="$default_root"
        runs="${custom:-$default_root/runs}"
        meetings="$default_root/meetings"
        ;;
      *)
        root="$default_root"
        runs="$default_root/runs"
        meetings="$default_root/meetings"
        ;;
    esac
  fi
fi

printf 'root=%s\nruns=%s\nmeetings=%s\n' "$root" "$runs" "$meetings"
```

If the resolved folder does not exist, search likely mounted/local folders. Use `find` roots that exist in the current sandbox:

```bash
for base in "$HOME/Library/Application Support" "$HOME" /Users /mnt /host /workspace /workspaces; do
  [ -d "$base" ] || continue
  find "$base" -maxdepth 5 \( -path '*/MeetingRecorder/meetings' -o -path '*/MeetingRecorder/runs' \) -type d 2>/dev/null
done
```

If this finds nothing in a Linux sandbox, ask the user for the mounted transcript path or ask them to expose the macOS Meeting Recorder folder to the sandbox. Do not invent a username-specific path.

## File Layout

Completed meetings have a canonical transcript surface inside `meetings/<meeting-id>`. Prefer these files for agent questions:

- `metadata.json`: meeting title, audio path, recording status, and recording/import settings.
- `app_metadata.json`: latest successful processing title, audio path, language, diarization settings, and `meeting_id`.
- `result.json`: canonical structured transcript with `participants`, `segments`, `speakers`, `speaker_names`, profile assignments, and metadata.
- `transcript.txt`: canonical readable transcript. Newer exports may begin with a `Participants:` header.
- `job_state.json`: latest canonical processing status/progress.

Historical processing attempts remain in `runs`. Use them for legacy data, debugging, or when a meeting has no canonical transcript yet. Important run files:

- `app_metadata.json`: app title, audio path, language, diarization settings, optional `meeting_id`.
- `result.json`: structured transcript with `participants`, `segments`, `speakers`, `speaker_names`, profile assignments, and metadata.
- `transcript.txt`: readable transcript. Newer exports may begin with a `Participants:` header.
- `job_state.json`: status/progress.
- `events.jsonl`: processing events, sometimes including source audio path.

Draft or in-progress meetings may have only `meetings/<meeting-id>/metadata.json`. Use these only when the user asks about in-progress/imported meetings without transcripts.

## Query Workflow

1. Resolve `meetings` and `runs`.
2. Search canonical meeting directories first. Prefer meetings that contain `result.json` or `transcript.txt`.
3. List candidate meeting directories sorted by modification time for "latest".
4. Match meeting intent against `metadata.json`, `app_metadata.json`, participants, speaker names, audio filename, and transcript text.
5. Use canonical `result.json` participants for speaker descriptions and `segments` for speaker-specific answers/timestamps.
6. Use canonical `transcript.txt` for broad summaries when speaker attribution is less important.
7. Fall back to `runs` only when no canonical meeting transcript matches, when the user asks about processing history, or for older installs that have not materialized meeting transcripts.
8. Cite meeting title/date and include timestamps for specific claims when useful.

## Useful Commands

List latest canonical meetings with portable Python:

```bash
python3 - "$meetings" <<'PY'
import datetime as dt
import pathlib
import sys

meetings = pathlib.Path(sys.argv[1]).expanduser()
items = []
for path in meetings.iterdir() if meetings.exists() else []:
    if path.is_dir():
        modified = path.stat().st_mtime
        stamp = dt.datetime.fromtimestamp(modified).isoformat(timespec="seconds")
        items.append((modified, stamp, str(path)))
for _, stamp, path in sorted(items, reverse=True)[:20]:
    print(f"{stamp}\t{path}")
PY
```

Show title and participants for recent canonical meetings with portable Python:

```bash
python3 - "$meetings" <<'PY'
import datetime as dt
import json
import pathlib
import sys

meetings = pathlib.Path(sys.argv[1]).expanduser()
rows = []
for path in meetings.iterdir() if meetings.exists() else []:
    if not path.is_dir():
        continue
    metadata = {}
    app_metadata = {}
    result = {}
    try:
        metadata = json.loads((path / "metadata.json").read_text())
    except Exception:
        pass
    try:
        app_metadata = json.loads((path / "app_metadata.json").read_text())
    except Exception:
        pass
    try:
        result = json.loads((path / "result.json").read_text())
    except Exception:
        pass
    participants = result.get("participants") or []
    names = result.get("speaker_names") or {}
    speakers = sorted({
        p.get("display_name") or p.get("speaker")
        for p in participants
        if p.get("display_name") or p.get("speaker")
    })
    if not speakers:
        speakers = sorted({names.get(s.get("speaker"), s.get("speaker", "")) for s in result.get("speakers", []) if s.get("speaker")})
    modified = path.stat().st_mtime
    stamp = dt.datetime.fromtimestamp(modified).isoformat(timespec="seconds")
    title = metadata.get("title") or app_metadata.get("title") or path.name
    rows.append((modified, stamp, title, ", ".join(speakers), str(path)))
for _, stamp, title, speakers, path in sorted(rows, reverse=True)[:20]:
    print(f"{stamp}\t{title}\t{speakers}\t{path}")
PY
```

Search meeting titles and transcripts:

```bash
rg -i "john|product|pricing" "$meetings" -g 'metadata.json' -g 'app_metadata.json' -g 'transcript.txt' -g 'result.json'
```

Extract speaker-specific segments:

```bash
jq -r '
  (.speaker_names // {}) as $names |
  .segments[] |
  {speaker: ($names[.speaker] // .speaker), start, end, text} |
  select(.speaker | test("Ashley"; "i")) |
  "[\(.start | floor)s-\(.end | floor)s] \(.speaker): \(.text)"
' "$meeting/result.json"
```

Search a speaker for a topic:

```bash
jq -r '
  (.speaker_names // {}) as $names |
  .segments[] |
  {speaker: ($names[.speaker] // .speaker), start, end, text} |
  select((.speaker | test("Ashley"; "i")) and (.text | test("pricing|options"; "i"))) |
  "[\(.start | floor)s-\(.end | floor)s] \(.speaker): \(.text)"
' "$meeting/result.json"
```

If `jq` is unavailable, use Python or Node to parse JSON rather than regexing JSON. Prefer the Python examples when working in cross-platform sandboxes.

## Interpreting Dates

Prefer these date signals in order:

1. Meeting directory modification time.
2. Canonical `job_state.json`, or fallback run `events.jsonl` timestamps if present.
3. Audio file creation/modification time from `app_metadata.json` or `result.json.metadata.audio`.

For "latest meeting with John", first filter candidate meetings by title/speaker/transcript matches for `John`, then choose the newest by meeting directory modification time.

For "latest product meeting", filter title/audio/transcript for `product`, then choose the newest matching meeting.

## Participants And Speaker Names

Prefer `result.json.participants` when present. It may include speaker descriptions from local speaker profiles:

```json
{
  "participants": [
    {
      "speaker": "SPEAKER_00",
      "display_name": "Ashley",
      "profile_id": "...",
      "description": "PM on billing, owns pricing decisions.",
      "source": "speaker_profile"
    }
  ]
}
```

Use `speaker` to join participants to transcript `segments`. Use `description` as background context, not as something the person said in the meeting.

Raw segment speakers may be `SPEAKER_00`, `SPEAKER_01`, etc. Always map through `speaker_names` when present:

```json
{
  "speaker_names": {
    "SPEAKER_00": "Ashley"
  }
}
```

When a user asks what a person said, search both the resolved human name and raw speaker label. If the speaker name is ambiguous, report the ambiguity and show the candidate speaker labels/names.

## Answering Style

For summaries, synthesize from relevant transcript sections and mention the meeting title/date used.

For factual questions, provide the short answer first, then quote or paraphrase the smallest relevant snippets with timestamps.

For uncertain matches, say which meetings were searched and why the selected one appears to be the best match.

Do not expose sensitive transcript content beyond what is needed to answer the user's question.
