@karmaniverous/jeeves-scripts-core
    Preparing search index...

    meetings/

    Meeting extraction from three independent sources — Google Meet (via email), Fathom recordings, and Notion inbox — writing to a shared entity store. This domain is the exemplar for the entity pipeline pattern.

    Script Description
    extract.ts Scans the email cache for meeting-related threads, detects source (Google Meet, Fathom), creates meeting packages with metadata and artifacts. For each new meeting it enqueues the Gmail meeting label (plus an archive if the message is in the inbox and not labelled watch, unless emailConfig.meetings.archive is false; see the pipeline block) on email-updates through the email pipeline's label-actions.ts, so nothing is enqueued while the message's account is report-only (emailConfig.reportOnly or its own accounts[].reportOnly; see email). Those actions are deferred, not dropped: see reportOnly catch-up. Gemini transcripts are not fetched here; the package gets gemini_link.txt and fetch-notes.ts exports the Doc
    fetch-notes.ts Walks meeting directories, fetches Gemini doc transcripts (exported as the meeting's source mailbox, meeting.json sources[0].account) and Fathom transcripts for meetings that need them
    ingest-notion.ts Polls a Notion inbox database, fetches meeting content via browser extraction, stages artifacts locally, archives the inbox page. One page per run: --page-id <id> (alias --meeting-id), else the page id in the override file, else the oldest inbox page older than --min-age <hours> (default 4); --dry-run reports the page and writes nothing
    flowchart TD
    email["Email cache"] --> extract["extract.ts"]
    extract --> packages["meeting packages\n(silo/meetings/id/)"]
    packages --> fetch["fetch-notes.ts"]
    fetch --> gemini["Gemini docs\n(gog export)"]
    fetch --> fathom["Fathom transcripts\n(puppeteer / share page)"]

    notion["Notion inbox"] --> ingest["ingest-notion.ts\n(browser extract)"]
    ingest --> notionPkg["meeting packages\n(summary.txt, transcript.txt,\nmeeting.json)"]

    Each meeting lives in a directory under {silo}/meetings/{meetingId}/:

    • meeting.json — canonical metadata (Zod-validated via meeting-schema.ts). participants holds email addresses (they derive the meeting id); people lists the participants who are in the people config as { id, name } (config.md) and is absent when none are
    • transcript.txt — extracted transcript (from Gemini, Fathom, or Notion)
    • summary.txt — meeting summary (from Fathom or Notion)
    • gemini_link.txt — Google Meet Gemini doc URL
    • fathom-*.html — raw Fathom email HTML
    • Source email JSON files
    1. Google Meet (via email): Detects meeting invitations/summaries in email, extracts participants, creates packages with gemini_link.txt; fetch-notes.ts then exports the Gemini doc transcript via gog as the meeting's source mailbox
    2. Fathom (via email + share pages): Detects Fathom recording URLs in email bodies, fetches transcripts from public share pages using puppeteer-core with headless Chrome
    3. Notion (via API + browser): Queries a Notion inbox database, extracts content (Summary/Notes/Transcript tabs) via gateway browser tool, archives processed pages
    • Gmail OAuth via gog (email pipeline must be running for extract/fetch-notes)
    • For Notion ingestion: NOTION_API_KEY_PATH, and two refs in pipeline.refs (see the pipeline block): notion.inboxDataSourceId (the Inbox data source meetings-ingest-notion queries) and notion.archiveDatabaseId (the database each processed page is copied into)
    • Chrome installed (for Fathom share page extraction via puppeteer-core)
    Job Schedule
    meetings-extract Every 13 min
    meetings-fetch-notes Every 19 min
    meetings-ingest-notion Every 23 min (disabled by default)
    File Purpose
    lib/meeting-schema.ts Canonical meeting.json Zod schema, writeMeetingMeta(), sort timestamp computation
    lib/detect.ts Meeting detection — subject matching, title normalization, participant extraction, Fathom URL detection, meeting ID generation
    lib/email-actions.ts Gmail actions for a new meeting (meeting label, archive if in the inbox and not watched; archive switchable via emailConfig.meetings.archive), enqueued via enqueueEmailUpdates (the per-account report-only gate)
    lib/pending-actions.ts Enqueues a new meeting's Gmail actions, or defers them while its account is report-only (handleNewMeetingEmailActions), and catches them up once it is not (see reportOnly catch-up)
    lib/package.ts Creates/updates meeting package directories and artifacts, manages runner-state index
    lib/meetings-dirs.ts Discovers all meetings directories across silos via getEntityDirs()
    lib/doc-fetch.ts Fetches Google Doc transcripts via gog CLI for meetings with gemini_link.txt
    lib/fathom-extract.ts Extracts transcripts from fathom-*.html files into transcript.txt
    lib/fathom-share-fetch.ts Fetches summary/transcript from public Fathom share pages using puppeteer-core
    lib/chrome-path.ts The Chrome/Chromium executable for puppeteer-core (PUPPETEER_EXECUTABLE_PATH, CHROME_PATH, well-known paths)
    lib/fathom-share-dom.ts Pure DOM extraction helpers for Fathom share page content
    lib/fathom-share-ingest.ts Pipeline for Fathom share meetings needing transcript fetching
    lib/notion-api.ts Notion API HTTP helpers with authentication
    lib/notion-browser-extract.ts Extracts meeting content from Notion public pages via gateway browser tool
    lib/notion-blocks.ts Notion block building: paragraph and heading blocks, text chunks, batched append
    lib/notion-inbox-processor.ts Processes a Notion inbox page end-to-end: fetch, extract, stage, archive

    While a meeting's account is report-only (emailConfig.reportOnly is true, or the account's own accounts[].reportOnly is), extract.ts still packages its new meetings but enqueues none of their Gmail actions. Instead it records each new meeting as pending (runner state namespace meetings, collection pendingEmailActions, keyed by source key).

    Each run catches up to 20 pending meetings whose account is no longer report-only (CATCH_UP_PER_RUN in lib/pending-actions.ts), so a long report-only window drains gradually. Pending meetings of an account that is still report-only stay pending and do not count towards the 20:

    • Actions are built from the message's current labels in the thread cache. A message a human has since labelled watch, or already moved out of the inbox, is not archived. A message no longer in the cache gets the meeting label only.
    • The current emailConfig.meetings.archive setting applies.
    • Everything goes through enqueueEmailUpdates, the single per-account report-only gate. A meeting's pending record is removed once its actions are enqueued, so nothing is enqueued twice.

    Only meetings packaged while report-only after this behaviour was introduced are pending. Meetings packaged earlier have no record and are never caught up, so turning report-only off can't mass-archive old mail. A pending record that can't be read is moved, unchanged, to the pendingEmailActionsInvalid collection (same namespace) and named in a WARNING line in the run log. It is never retried: inspect it, then re-create or delete it by hand. To discard pending actions instead of applying them, delete the pendingEmailActions items in the meetings namespace before turning report-only off.