--- name: meeting-to-deal description: Turn meeting transcripts into deal updates — key points, commitments, and next steps posted to matching CRM deals automatically. Auto-creates contacts for new attendees. Use when someone says "log my meetings to deals", "update CRM from calls", "what did I commit to in meetings", "sync notetaker", "log this meeting", "I just had a call with [company]", "what did I promise in that meeting", "add notes from today's calls", or "update the deal after my call". argument-hint: "[optional: time window — 'today', 'yesterday', 'last 7 days', or 'since 2026-05-20']" user-invocable: true allowed-tools: [Read, mcp__monday_crm__get_user_context, mcp__monday_crm__list_workspaces, mcp__monday_crm__search, mcp__monday_crm__get_board_info, mcp__monday_crm__get_column_type_info, mcp__monday_crm__get_board_items_page, mcp__monday_crm__get_notetaker_meetings, mcp__monday_crm__create_update, mcp__monday_crm__create_item, mcp__monday_crm__change_item_column_values, mcp__monday_crm__create_doc, mcp__monday_crm__create_notification, mcp__monday_crm__list_users_and_teams, mcp__monday_crm__all_monday_api] --- # Meeting to Opportunity Flow: **Trigger → Pull meetings → Match → Synthesize → Publish (α) → Auto-contact (β, opt-in) → Proactive next-step nudges (opt-in)**. ## Input - Optional: time window via argument (default = last 24h). - Optional: pre-declared mode (Default / Silent / Proactive). ## Output - **α (default):** One `Meeting recap` update per matched deal, summarizing key points + commitments + next steps. Body carries the `Generated by Claude · ` footer + `` comments. A `Meeting sync — ` doc lists what was matched, what wasn't, and unresolved attendees. - **β (Auto-contact, opt-in):** New items on the Contacts board for meeting attendees not already in CRM, linked back to the matched deal. Each new contact carries `Source = Claude` on a status column. - **Proactive extension:** `create_notification` to deal owner for commitments due this week. Stage column edits and last-touch updates are also OK in Proactive (or in Default with batched confirm) when a clear transition phrase is present in the transcript ("we agreed to move to procurement review", "they signed today"). Surfaced as a single batched plan: *" stage transitions detected. Apply all / review each / skip stage edits?"*. Amount-column writes still forbidden. ## Knowledge - Meeting → deal matching heuristic (§ Step 4). - Update body convention (§ Step 6). - Shared artifact conventions (§ Shared patterns). ## Tools (MCP) - `get_user_context` — user identity + email domain for filtering meetings and extracting `internal_domain`. - `get_notetaker_meetings` — recent meetings + transcripts. - `search` / `list_workspaces` / `get_board_info` — locate Deals + Contacts boards. - `get_board_items_page` — fetch active deals to match against. - `get_column_type_info` — resolve email/company columns by type. - `create_update` — write the recap onto the matched deal. - `create_item` — β auto-contact on the Contacts board. - `change_item_column_values` — populate the new contact's email + company columns, write `Source = Claude` on contacts, and apply confirmed stage / last-touch edits on matched deals. **Never** for amount columns. - `create_notification` — proactive next-step nudges. - `create_doc` — sync summary doc. - `all_monday_api` — escape hatch for connect-board column writes (linking contact ↔ deal). Note: connect-board writes via `all_monday_api` may fail silently — see Error handling reference. ## Cross-skill handoffs - **From workspace-builder / daily-briefing:** suggested as a way to keep deals fresh after a meeting-heavy day. - **To data-cleanup:** if >30% of attendees couldn't be matched, suggest running `data-cleanup` to clean contact data. - **To daily-briefing:** the synced updates feed tomorrow's brief. --- ## Step 0: Connector check + NoteTaker availability **Goal:** Confirm the monday connector works and detect whether NoteTaker is available. Also extract `internal_domain` for use in Steps 4–5. 1. Try `get_user_context`. On error → standard install prompt, stop. 2. Extract `internal_domain` from the user's email (e.g. `"kevin@monday.com"` → `"monday.com"`). Store for Steps 4–5. If email is absent from the response, `internal_domain` = `"monday.com"` (safe default). 3. Try `get_notetaker_meetings({ limit: 1 })`. 4. If NoteTaker returns results → set `notetaker_available: true`. Continue to Step 1. 5. If NoteTaker returns auth error, permission error, or empty-by-permission → set `notetaker_available: false`. **Do not stop.** The skill will collect meeting content from the user directly in Step 3. > **PAUSE**: do not write any `create_*` call before Step 6. --- ## Step 1: Detect mode - **Default** — bundle all writes (recaps + stage edits + last-touch updates + auto-contacts) into a single batched confirm before executing. - **Silent** — skip the batched confirm; β auto-contact still requires explicit opt-in. - **Proactive** — ask once: *"Proactive mode will: (a) write recaps to matched deals, (b) auto-create missing contacts, (c) apply stage/last-touch edits when transcripts are unambiguous, (d) notify deal owners on commitments due this week. Amount columns stay untouched. Approve all / review each batch / deny stage edits only?"*. Cache the choice. If undeclared, **Default**. Hard safety rail regardless of mode: **no deletes, no amount-column writes, no cross-workspace moves**. Stage transitions are bundled into a batched plan — never written one-off, never silently. --- ## Step 2: Resolve time window 1. If argument carries a phrase, parse: "today" / "yesterday" / "last N days" / "since YYYY-MM-DD". 2. Default = last 24h. 3. If no argument was provided AND 0 meetings are found (or on the very first run with no prior session context), print: *"Using last 24h (no window specified). Add 'yesterday', 'last 7 days', or 'since YYYY-MM-DD' to change the range."* 4. If parsed window > 14 days: ask the user to confirm (large windows produce noisy recaps). --- ## Step 3: Pull meetings ### 3a — NoteTaker path (`notetaker_available: true`) `get_notetaker_meetings({ since: , until: , limit: 50 })`. Cache transcripts + attendees + meeting metadata. Edge cases: - **0 meetings in window:** don't stop — fall through to Step 3b to let the user provide meeting content manually. Print: *"No NoteTaker meetings found in ``. You can paste meeting notes or describe what happened instead."* - **>50 meetings:** paginate up to 200, then cap and surface sampling note. - **Tool 429:** backoff 3x; on third failure fall through to Step 3b. ### 3b — Manual input path (`notetaker_available: false`, OR NoteTaker returned 0 meetings) Ask the user once: > *"No NoteTaker recordings available. How would you like to provide meeting notes?"* > - **(a) Paste the transcript or notes here** — I'll read them from your next message > - **(b) Point me to a file** — provide the path and I'll read it > - **(c) Describe what happened** — tell me in free text (attendees, key points, commitments, next steps) > - **(d) Skip — nothing to log right now** If **(d)** → stop with: *"No meetings logged. Run this skill again when you have notes to add."* For **(a)** / **(b)** / **(c)**: treat the user-provided content as a single meeting entry. Structure it as: - `title`: first line of the notes, or ask if blank - `date`: today (or extract from notes if mentioned) - `attendees`: extract from notes, or ask once - `transcript_text`: the full provided content Cache as one entry in `meetings[]` with `source: "manual"` and continue to Step 4. The matching and recap flow works identically — the transcript is just from the user's input instead of NoteTaker. --- ## Step 4: Resolve Deals + Contacts boards (Gather) Resolve the boards directly — this skill is self-contained: 1. **Deals board:** `get_user_context` → scan `favorites` + `relevantBoards` for names matching `deals|opportunities|pipeline|sales`. One candidate → use it; multiple → ask the user which one; zero → `list_workspaces` → `search("deal", BOARD)`. Still zero → degrade per step 4 below. 2. **Contacts board:** same pass for `contacts|people|leads` (only if β auto-contact may run). If Contacts board is missing and mode = Proactive and β was approved, surface a chat-level message **before** writing the sync doc: *"Auto-contact was approved but I couldn't find a Contacts board. Contacts not created. Run `/monday-crm:workspace-builder` to set one up."* Then continue with α recap only. 3. `get_board_info` on each. Resolve columns by type: - On Deals: `email` (if present), `text` company-name, `people` owner, `status` stage, `numbers` value, `date` last-touch. - On Contacts: `email`, `text` (or company-link `board_relation`), `phone`, `people` owner. 4. If Deals board missing entirely: degrade — skip α, write only the sync summary doc with "no Deals board found" header. Suggest `/monday-crm:workspace-builder`. --- ## Step 5: Match meetings → deals (Synthesize) **Goal:** For each meeting, find the single best matching deal — or flag it as unmatched. `internal_domain` was extracted in Step 0. Use it throughout this step in place of hard-coded `@monday.com` checks. For each meeting: 1. **Extract signals:** attendee emails (excluding `@` addresses), company names from email domains, meeting title keywords. 2. **Pull active deals** (Pass A from daily-briefing — non-Won/Lost), `limit: 500`. 3. **Score candidates:** - +5 per attendee email matching a deal's email column. - +3 per attendee email *domain* matching a deal's company-name column (case-insensitive substring). - +2 if meeting title contains a deal name (≥3 contiguous chars match). - **+1 if deal owner is a meeting attendee — only when ALL non-internal attendees are from `@` (i.e. purely internal meeting).** This signal is intentionally narrow: a deal owner being on an external sales call is expected and not a reliable match signal; it only carries weight for internal deal-review meetings where ownership is the strongest available signal. 4. **Pick the top score** if it's ≥5 AND beats the second-best by ≥3. Otherwise flag as **ambiguous** (multiple candidates) or **unmatched** (no candidate ≥5). 5. **Multi-deal meetings:** if top two candidates are within 2 points and both ≥5, flag as multi-match — write the recap to *both* deals with a header line "*Meeting also discussed: *". Edge cases: - Internal-only meetings (all attendees `@`) and no deal-name title match: skip (likely standup), log to summary doc as "skipped: internal-only". - Personal-meetings filter: skip meetings tagged 1:1 (no transcript content of CRM relevance). --- ## Step 6: Synthesize the recap (Synthesize) For each matched meeting, compose a structured recap from the transcript: - **Key points** (3–5 bullets, factual — what was discussed). - **Commitments made** (who → what → by when, parsed from "I'll send you...", "we'll follow up by...", "next week we..."). - **Open questions** (statements ending with `?` or signaled by "we need to figure out", "TBD"). - **Stage transition signal:** if transcript contains procurement / legal / contract / close phrases, propose a stage change. Mark each as `confidence: high|low` based on (a) explicit phrase ("we agreed to ", "signed today") = high, (b) inferred ("seems like they're ready for legal review") = low. Only `high` transitions enter the batched plan; `low` ones surface as text recommendations. Deal-side metadata lookup: pull current stage + value + close date for context (single `get_board_items_page` filtered to the matched item ID). --- ## Step 7: Publish α — per-deal recap (Publish) For each matched meeting + deal pair: 1. **Idempotency check:** Use `all_monday_api` with the following query pattern to scan existing updates on the deal item: ```graphql { items_by_id(ids: []) { updates(limit: 50) { body } } } ``` Walk the `body` of each returned update and check for both `` and ``. If a match is found → use `all_monday_api` to update that existing update's body; do not post a duplicate. > **Note:** `search` cannot search update bodies — always use `all_monday_api` items_by_id for this check. 2. `create_update({ itemId: , body: })`. 3. Update body shape: ```markdown # Meeting recap — * · m · attendees* ## Key points - ... ## Commitments - → (due ) ## Open questions - ... ## Stage signal <"applied: → " if the batched plan included it> OR OR "no stage change"> --- Generated by Claude · · run `/monday-crm:meeting-to-deal` to refresh. ``` ### Batched confirm (Default mode) Before any write, print a single plan: *"I'll write: recaps · stage edits (high-confidence only) · last-touch updates · new contacts. meetings unmatched (will list separately). low-confidence stage signals will surface as text only. Proceed? (yes / show plan in detail / no)."* If user picks "show plan in detail": 1. List **all** stage transitions by name: ` · → ` for every planned stage edit. 2. Show one example recap (the first deal). 3. Then confirm: *"Apply all? (yes / skip stage edits / no)"*. Never per-deal confirms beyond that. **Auto-contact cap warning:** If unmatched-attendee count > 25, add to the confirm message: *" new contacts detected (> 25 cap). I'll create 25 and add the rest to a review-list doc. Proceed?"* (e.g. "31 new contacts detected (> 25 cap). I'll create 25 and add the rest to a review-list doc. Proceed?") --- ## Step 8: β — auto-create missing contacts (opt-in) **Goal:** Don't make the user manually add every meeting attendee to Contacts. Only runs if mode = Proactive (option a) OR Default-mode user opts in at Step 7 confirm (added prompt: "+ create N missing contacts on Contacts board?"). For each unique attendee email not found on the Contacts board: 1. `create_item({ boardId: , itemName: "", groupId: "new" })`. 2. `change_item_column_values` to populate email, company (from email domain), and `Source = Claude` on the board's source/status column. If no source/status column exists, prompt the user once: *"Add a `Source` status column on Contacts so we can flag plugin-created records? (yes / skip)"*. If skipped, contacts are still created but source isn't tracked. 3. Link the new contact to the matched deal via the `board_relation` column using `all_monday_api`. If this call fails (connect-board writes can fail silently), log the contact as "contact created, deal link pending — set manually" in the sync doc rather than stopping. Do not retry silently. Cap at 25 contacts per run. Above that, batch into a review-list doc rather than creating silently. (Flag this at Step 7 confirm time — see auto-contact cap warning above.) --- ## Step 9: Proactive next-step nudges (opt-in) Only runs if mode = Proactive AND session-level approval was given. For commitments parsed in Step 6 with a parseable due-date within next 7 days: - `create_notification({ userId: , text: "Commitment due on : " })`. - If `create_notification` requires an `itemId`: use the **sync doc's item ID** (from Step 10's `create_doc` return value). Do **not** pin the notification to an arbitrary deal item when the doc covers cross-deal commitments. Hard rail: never write commitments to amount columns. Notifications only for proactive nudges (stage edits already handled in Step 7's batched plan, not here). If >20 nudges queued, batch into one `This week's commitments` doc (with `` in body) and notify once with a link to the doc. --- ## Step 10: Sync summary doc + close the loop `create_doc` (idempotent — same-day update-in-place): ```markdown # Meeting sync — ## Matched () - → () ## Multi-match () - → + ## Ambiguous () - — candidates: , — review and re-run with explicit deal ID ## Unmatched () - — attendees: — no matching deal. Suggest creating one. ## Skipped () - — internal-only, no CRM relevance ## Contacts created () - () → linked to ## Contacts pending manual link () - () — contact created, deal link pending — set manually --- Generated by Claude · ``` One-line chat summary: `Synced meetings to deals. unmatched, contacts created. Doc: .` --- ## Shared patterns - **No prefix on titles.** Findability via `Generated by Claude` footer + `` comment in update/doc bodies; `Source = Claude` on contact items. - **Embedded meeting-id comment** + skill-id comment for idempotency — search-and-update, never duplicate. - **Safety rail.** No deletes, no amount-column writes, no cross-workspace moves. Stage edits go through the batched plan; low-confidence transitions stay as text recommendations only. - **Type-based column resolution** for non-English boards. - **Batched confirms** (Step 7) — never per-meeting prompts. - **Internal-only meeting filter** — skips standups, 1:1s, retro meetings. - **`internal_domain` from Step 0** used throughout for internal/external attendee classification — not hard-coded to `@monday.com`. - **Skill-id comment uses legacy name `meeting-to-opportunity`** — kept for backward compatibility with existing updates/docs carrying ``. The skill's invocation name is `/monday-crm:meeting-to-deal`. --- ## Output template ### Per-deal recap (create_update body) ```markdown # Meeting recap — Quarterly Review — Acme Corp *Jun 14, 2026 · 45m · 4 attendees* ## Key points - Discussed Q3 expansion scope — 3 additional seats requested - Budget approved internally at Acme, pending procurement sign-off - Timeline: targeting signed contract by end of June ## Commitments - Sarah (Acme) → send updated SOW (due Jun 18) - Kevin (us) → schedule legal review call (due Jun 20) ## Open questions - Will Acme need SSO configured before go-live? - Multi-year discount structure not yet agreed ## Stage signal Applied: Discovery → Proposal (high confidence — "we agreed to move to procurement review") --- Generated by Claude · 2026-06-15T09:00:00Z · run `/monday-crm:meeting-to-deal` to refresh. ``` ### Sync summary doc ```markdown # Meeting sync — Jun 15 ## Matched (2) - Quarterly Review — Acme Corp → Acme Corp — Q3 expansion (https://monday.com/boards/...) - Demo follow-up — Beta Industries → Beta Industries — pilot (https://monday.com/boards/...) ## Unmatched (1) - Internal team standup — attendees: all @monday.com — skipped (internal-only) ## Contacts created (1) - Mike Chen (mike@acme.com) → linked to Acme Corp — Q3 expansion --- Generated by Claude · 2026-06-15T09:00:00Z ``` **Chat one-liner:** ``` Synced 2 meetings to 2 deals. 1 skipped (internal). 1 contact created. Doc: [url]. ``` --- ## Error handling reference | Failure | Behavior | |---|---| | Connector missing | Step 0 stops; print install link. | | NoteTaker unavailable | Step 3b: ask user for manual input — paste, file, or free-text description. | | 0 meetings in window (NoteTaker worked) | Step 3b fallback — prompt for manual input. | | No Deals board | Step 4 degrades — sync doc only, suggest `workspace-builder`. | | Ambiguous match (≥2 candidates within 2 points) | Skip the write; log to sync doc as "ambiguous". | | Idempotency hit (same meeting-id in update) | Update existing content, don't duplicate. | | 429 on `get_notetaker_meetings` or `create_update` | Backoff 3x; halt on third with retry message. | | Contacts board missing (β approved, Proactive mode) | Surface chat message before sync doc: "Auto-contact was approved but I couldn't find a Contacts board. Contacts not created. Run `/monday-crm:workspace-builder` to set one up." Continue with α recap. | | Contacts board missing (β not triggered) | Skip β; log "no Contacts board" once in sync doc. | | connect-boards write via `all_monday_api` fails | Skip link; log in sync doc as "contact created, deal link pending — set manually". | | Transcript empty / NoteTaker still processing | Skip that meeting; surface in sync doc as "transcript pending". | | Cross-board permission error | Skip just that deal/contact; continue with the rest. | --- ## Completion criteria - [ ] Step 0 (connector + NoteTaker) ran and passed. `internal_domain` extracted. - [ ] Each matched deal got at most one new (or one updated) recap update (verified via `all_monday_api` items_by_id update-body scan). - [ ] Every recap body carries `` + `` + `Generated by Claude` footer. - [ ] Sync summary doc exists (or printed to chat on failure). - [ ] Safety rail held: no deletes, no amount-column writes, no cross-workspace moves. - [ ] Stage edits (if any) ran inside the Step 7 batched plan; all stage transitions listed by name in "show plan in detail"; low-confidence transitions stayed as text only. - [ ] Contacts created (if β ran) carry `Source = Claude`; failed deal links logged in sync doc as "pending — set manually". - [ ] Auto-contact cap warning shown at confirm time if unmatched attendees > 25. - [ ] Notifications (if Proactive) pinned to sync doc item ID, not arbitrary deal items.