@leadbay/mcp 0.23.6 → 0.23.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/bin.js +173 -47
- package/dist/http-server.js +173 -47
- package/dist/installer-electron.js +1 -1
- package/dist/installer-gui.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog — @leadbay/mcp
|
|
2
2
|
|
|
3
|
+
## 0.23.7 — 2026-06-25
|
|
4
|
+
|
|
5
|
+
Field-sales tour always renders the map (product#3779).
|
|
6
|
+
|
|
7
|
+
- **`leadbay_plan_tour_in_city` / `leadbay_tour_plan`** — a plain-language tour intent ("I'm visiting Jacksonville in 3 days — who should I go see?") now deterministically routes to the tour tool (no longer leaks to `leadbay_pull_leads`), builds the mixed tour (known accounts + fresh prospects), and **proactively offers to plot it on a map** — every run, without the user asking. On acceptance it renders via `places_map_display_v0` (or the place-card carousel on hosts without the widget).
|
|
8
|
+
- **Server-shaped `map_locations[]`** — leads are pre-shaped server-side (`{name, address, latitude, longitude, notes}`, badge in `notes`) plus a `map_summary`, so the agent never hand-builds the widget payload and passes coordinates through verbatim (no fabricated pins/addresses). Each stop is badged ★ Customer / ★ Qualified / ✦ New from real history fields (`epilogue_status` / `last_prospecting_action_at` / `last_monitor_action_at`). Guards against a `"null"`-string contact name.
|
|
9
|
+
|
|
3
10
|
## 0.23.6 — 2026-06-25
|
|
4
11
|
|
|
5
12
|
Geographic filter on Discover lenses (product#3759).
|
package/dist/bin.js
CHANGED
|
@@ -6117,7 +6117,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
6117
6117
|
|
|
6118
6118
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
6119
6119
|
|---|---|---|
|
|
6120
|
-
| \`places_map_display_v0\` (Claude) |
|
|
6120
|
+
| \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
|
|
6121
6121
|
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
6122
6122
|
| \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
|
|
6123
6123
|
|
|
@@ -6435,7 +6435,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
6435
6435
|
|
|
6436
6436
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
6437
6437
|
|---|---|---|
|
|
6438
|
-
| \`places_map_display_v0\` (Claude) |
|
|
6438
|
+
| \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
|
|
6439
6439
|
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
6440
6440
|
| \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
|
|
6441
6441
|
|
|
@@ -6691,7 +6691,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
6691
6691
|
|
|
6692
6692
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
6693
6693
|
|---|---|---|
|
|
6694
|
-
| \`places_map_display_v0\` (Claude) |
|
|
6694
|
+
| \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
|
|
6695
6695
|
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
6696
6696
|
| \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
|
|
6697
6697
|
|
|
@@ -7307,7 +7307,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
7307
7307
|
|
|
7308
7308
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
7309
7309
|
|---|---|---|
|
|
7310
|
-
| \`places_map_display_v0\` (Claude) |
|
|
7310
|
+
| \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
|
|
7311
7311
|
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
7312
7312
|
| \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
|
|
7313
7313
|
|
|
@@ -7439,7 +7439,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
7439
7439
|
|
|
7440
7440
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
7441
7441
|
|---|---|---|
|
|
7442
|
-
| \`places_map_display_v0\` (Claude) |
|
|
7442
|
+
| \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
|
|
7443
7443
|
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
7444
7444
|
| \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
|
|
7445
7445
|
|
|
@@ -7573,11 +7573,11 @@ email. Do NOT paste the email body into chat prose alongside.
|
|
|
7573
7573
|
|
|
7574
7574
|
---
|
|
7575
7575
|
|
|
7576
|
-
Prepare a single-lead outreach brief: the full \`lead\` block (score, \`split_ai_summary\`, \`location\`, \`size\`, \`phone_numbers\`, \`website\`, \`description\`, \`social_urls\`, \`social_presence\`), the \`recommended_contact\`
|
|
7576
|
+
Prepare a single-lead outreach brief: the full \`lead\` block (score, \`split_ai_summary\`, \`location\`, \`size\`, \`phone_numbers\`, \`website\`, \`description\`, \`social_urls\`, \`social_presence\`), the \`recommended_contact\` in post-enrichment shape (\`contact_id\`, names, \`job_title\`, \`email\`, \`phone_number\`, \`linkedin_page\`, \`is_org_contact\` \u2014 nulls where not yet enriched), \`additional_contacts_count\`, and an \`enrichment\` block describing async state.
|
|
7577
7577
|
|
|
7578
7578
|
Optionally trigger contact enrichment in-flight with \`enrich:true\`. Enrichment is async (~60s). **Self-polling pattern (no separate tool needed):** re-call \`leadbay_prepare_outreach(leadId)\` without \`enrich\`; check \`enrichment.complete\`. When \`complete: true\`, the recommended contact now carries \`email\` and/or \`phone_number\`.
|
|
7579
7579
|
|
|
7580
|
-
The first call
|
|
7580
|
+
The first call on a lead records a \`LEAD_VIEWED\`-style prospecting action server-side (deduped, one per lead per session) so it ages out of the Discover "new" view.
|
|
7581
7581
|
|
|
7582
7582
|
IRON LAW \u2014 OUTCOME AFTER OUTREACH. The moment the user reports outreach happened ("I sent it", "she didn't pick up", "left a voicemail", "they replied", a forwarded email thread, a calendar invite), you MUST (1) call leadbay_report_outreach with verification (gmail_message_id, calendar_event_id, or the user's literal one-sentence confirmation as user_confirmed.ref) AND (2) ask the user about the outcome and set epilogue_status to one of the 4 canonical values: EPILOGUE_INTEREST_VALIDATED_OR_MEETING_PLANED ("Meeting booked"), EPILOGUE_COULD_NOT_REACH_STILL_TRYING ("Trying to reach"), EPILOGUE_NOT_INTERESTED_LOST ("Not interested"), EPILOGUE_STILL_CHASING ("In progress"). Use the user-facing labels in dialogue ("What's the outcome \u2014 meeting booked, trying to reach, not interested, or in progress?"); never say "epilogue" out loud. Skipping this step silently de-ranks every future follow-up suggestion because pull_followups depends on honest, current outcomes.
|
|
7583
7583
|
|
|
@@ -7592,7 +7592,7 @@ WHEN NOT TO USE: across many leads \u2014 use leadbay_enrich_titles for bulk; fo
|
|
|
7592
7592
|
|
|
7593
7593
|
Route every draft through \`message_compose_v1\` (Claude's email composer). Above it, emit ONE short markdown context paragraph: score callout + sector fit + linked contact name + bare phone/email pills. Do NOT paste the email body into chat prose alongside \u2014 the composer IS the visual.
|
|
7594
7594
|
|
|
7595
|
-
Variant shape: 1\u20133 entries. Labels describe **strategy** ("Push for alignment", "Reference the M&A signal"
|
|
7595
|
+
Variant shape: 1\u20133 entries. Labels describe **strategy** ("Push for alignment", "Reference the M&A signal"), not tone. \`kind: "email"\` requires \`subject\`; phone/call openers use \`kind: "other"\` with the opener in \`body\`.
|
|
7596
7596
|
|
|
7597
7597
|
## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
|
|
7598
7598
|
|
|
@@ -7602,7 +7602,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
7602
7602
|
|
|
7603
7603
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
7604
7604
|
|---|---|---|
|
|
7605
|
-
| \`places_map_display_v0\` (Claude) |
|
|
7605
|
+
| \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
|
|
7606
7606
|
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
7607
7607
|
| \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
|
|
7608
7608
|
|
|
@@ -7931,7 +7931,7 @@ Trigger phrases: "show me leads", "show me new leads", "show me today's leads",
|
|
|
7931
7931
|
|
|
7932
7932
|
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
7933
7933
|
|
|
7934
|
-
Do NOT use for: "leads I should follow up with" \u2192 \`leadbay_pull_followups\`; "I'm going to <city>" \u2192 \`
|
|
7934
|
+
Do NOT use for: "leads I should follow up with" \u2192 \`leadbay_pull_followups\`; "I'm going to <city>" \u2192 \`leadbay_tour_plan\`; "I'm in <city> next week \u2014 who's worth meeting" \u2192 \`leadbay_tour_plan\`; "who should I meet in <city>" \u2192 \`leadbay_tour_plan\`; "visiting <city> \u2014 who's worth meeting / seeing" \u2192 \`leadbay_tour_plan\`; "leads I should reach out to" \u2192 \`leadbay_pull_followups\`; "leads to get back to" \u2192 \`leadbay_pull_followups\`; "leads to contact today" \u2192 \`leadbay_pull_followups\`; "should I contact" \u2192 \`leadbay_pull_followups\`; "reconnect with" \u2192 \`leadbay_pull_followups\`; "re-engage" \u2192 \`leadbay_pull_followups\`.
|
|
7935
7935
|
|
|
7936
7936
|
Prefer when: fresh Discover leads; if a lens is named, pass \`lensId\` and pin it
|
|
7937
7937
|
|
|
@@ -7942,6 +7942,7 @@ Examples that SHOULD invoke this tool:
|
|
|
7942
7942
|
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
7943
7943
|
- "Which leads should I follow up with this week?"
|
|
7944
7944
|
- "I'm flying to Berlin Thursday \u2014 who should I meet?"
|
|
7945
|
+
- "I'm in San Francisco next Tuesday \u2014 who's worth meeting?"
|
|
7945
7946
|
- "Show me leads I should reach out to today."
|
|
7946
7947
|
- "Who should I get back to today?"
|
|
7947
7948
|
- "Leads I should contact today."
|
|
@@ -9139,7 +9140,7 @@ WHEN NOT TO USE: the user wants a lead list (leadbay_pull_leads / leadbay_pull_f
|
|
|
9139
9140
|
`;
|
|
9140
9141
|
leadbay_tour_plan = `## WHEN TO USE
|
|
9141
9142
|
|
|
9142
|
-
Trigger phrases: "visiting <city> in <N> days", "field tour in <city>", "plan a tour in <city>", "who should I meet in <city>", "customers plus prospects in <city>", "tour itinerary".
|
|
9143
|
+
Trigger phrases: "visiting <city> in <N> days", "I'm in <city> next week / Tuesday \u2014 who's worth meeting", "I'm going to <city> \u2014 who should I see", "who's worth meeting in <city>", "field tour in <city>", "plan a tour in <city>", "who should I meet in <city>", "customers plus prospects in <city>", "tour itinerary".
|
|
9143
9144
|
|
|
9144
9145
|
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
9145
9146
|
|
|
@@ -9149,6 +9150,7 @@ Prefer when: user wants known accounts plus new discoveries in one geographic it
|
|
|
9149
9150
|
|
|
9150
9151
|
Examples that SHOULD invoke this tool:
|
|
9151
9152
|
- "I'm flying to Limoges in 4 days \u2014 give me 3 customers, 3 qualified prospects, and 3 new high-potential."
|
|
9153
|
+
- "I'm in San Francisco next Tuesday. Who's worth meeting?"
|
|
9152
9154
|
- "Plan my tour next Tuesday in Lyon: known accounts plus discoveries."
|
|
9153
9155
|
- "Build a mixed itinerary for Berlin \u2014 I want both follow-ups and fresh leads."
|
|
9154
9156
|
|
|
@@ -9159,16 +9161,18 @@ Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
|
9159
9161
|
|
|
9160
9162
|
## RENDER (quick)
|
|
9161
9163
|
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
|
|
9164
|
+
Present the leads grouped by mode (\u2605 Customer / \u2605 Qualified / \u2726 New),
|
|
9165
|
+
then ALWAYS offer to plot them on a map ("Want me to put these on a
|
|
9166
|
+
map?"). On yes (or if the user asked for a map up front), render it:
|
|
9167
|
+
pass \`map_locations\` verbatim into \`places_map_display_v0\`, or on a
|
|
9168
|
+
host without the widget emit one \`### Company \xB7 City, State\` place-card
|
|
9169
|
+
block per lead so the host's address carousel renders. Never a flat
|
|
9170
|
+
prose paragraph. Full recipe below.
|
|
9169
9171
|
|
|
9170
9172
|
---
|
|
9171
9173
|
|
|
9174
|
+
**BEFORE YOU CALL \u2014 clarify scope with a tap-to-answer question.** A field tour is high-intent, so unless the user already said it, ASK first (via \`ask_user_input_v0\` / \`AskUserQuestion\` \u2014 whichever is in your tool set) and wait for the answer before calling this tool: (1) **Who to include?** \u2014 "Mix \u2014 known accounts + fresh prospects" (default) / "Only my known accounts" / "Only fresh new prospects"; (2) **Enrich contacts with no phone/email?** \u2014 "Yes, enrich the top stops" / "No, just plan". Map answers to args: only-known \u2192 \`discover_count: 0\`; only-new \u2192 \`followups_count: 0\`; mix \u2192 defaults; enrich=yes \u2192 after planning, run \`leadbay_enrich_titles\` / \`leadbay_prepare_outreach(enrich:true)\` on top stops missing a channel. Skip the question only if the request already states scope ("plan a tour of just my new leads" \u2192 only-new, don't ask). Do NOT skip straight to building + mapping without this.
|
|
9175
|
+
|
|
9172
9176
|
Build a single-call mixed-mode itinerary for a field sales tour. Combines \`leadbay_pull_followups\` (Monitor leads in the city \u2014 known accounts) with \`leadbay_pull_leads\` (Discover wishlist \u2014 new prospects, then client-side filtered by city) so the agent can answer the canonical #3630 US1 ask: *"I'm visiting Limoges in 4 days \u2014 propose 3 customers + 3 qualified prospects + 3 new high-potential discoveries."*
|
|
9173
9177
|
|
|
9174
9178
|
**Geo resolution** is identical to \`leadbay_followups_map\`: pass \`city\` (any admin level \u2014 city, state, country, region \u2014 the \`/geo/search\` resolver picks the best match), or a pre-resolved \`city_id\`. Ambiguous matches surface as \`status: "ambiguous_locations"\` + \`location_ambiguities[]\`; pick an id and re-call with \`city_id\`.
|
|
@@ -9179,21 +9183,30 @@ Build a single-call mixed-mode itinerary for a field sales tour. Combines \`lead
|
|
|
9179
9183
|
|
|
9180
9184
|
---
|
|
9181
9185
|
|
|
9182
|
-
## RENDER \u2014
|
|
9186
|
+
## RENDER \u2014 present the leads, then OFFER the map (render on yes)
|
|
9187
|
+
|
|
9188
|
+
First present the leads grouped by mode as a tight per-lead list (badge + company + city + best contact). Then **ALWAYS offer the map** as the next step \u2014 "Want me to put these on a map?" \u2014 and render it only when the user accepts (or asked for a map in their original message). The map is mandatory to *offer* on every tour; it is *rendered* on acceptance.
|
|
9189
|
+
|
|
9190
|
+
When rendering, use the **two-step host flow** \u2014 this is what produces a real interactive street map instead of a schematic neighborhood scatter:
|
|
9183
9191
|
|
|
9184
|
-
|
|
9192
|
+
1. **\`places_search\` first, once per lead.** For each entry in \`map_locations\`, call \`places_search\` with a natural query built from the lead's \`name\` + \`address\` (the full street address, e.g. "Brooklyn Brewery, 79 N 11th St, Brooklyn, NY"). This resolves a real \`place_id\` + verified coordinates/ratings from the host's place index. Run them up front (resolve all stops first), as the host's own guidance prescribes.
|
|
9193
|
+
2. **\`places_map_display_v0\` second**, fed the resolved places. Use **Itinerary mode** for a tour (multi-stop, route order, optional arrival times / stop durations) \u2014 a field-sales day IS an itinerary; Markers mode is only for a plain "show me X on a map". Carry each lead's mode badge (\u2605/\u2726) + one-line pitch from \`map_locations[i].notes\` into the place's note.
|
|
9185
9194
|
|
|
9186
|
-
|
|
9187
|
-
|
|
9195
|
+
Do NOT skip \`places_search\` and push raw \`latitude\`/\`longitude\` straight into the display widget \u2014 that's exactly what falls back to the schematic relative scatter (dots by neighborhood, no streets). The \`map_locations\` array already gives you everything \`places_search\` needs (\`name\`, full \`address\`, \`latitude\`, \`longitude\`, badge-tagged \`notes\`); do NOT shorten the address to just the city.
|
|
9196
|
+
|
|
9197
|
+
For reference, the badge the server already applied to each \`notes\`:
|
|
9198
|
+
|
|
9199
|
+
- \`\u2605 Customer\` \u2014 Monitor lead with prior engagement history (\`epilogue_status\`, \`last_prospecting_action_at\`, or \`last_monitor_action_at\` set).
|
|
9200
|
+
- \`\u2605 Qualified\` \u2014 Monitor lead with a high score but no recent action.
|
|
9188
9201
|
- \`\u2726 New\` \u2014 Discover lead from \`discover_leads\`.
|
|
9189
9202
|
|
|
9190
|
-
|
|
9203
|
+
Example of a server-built \`notes\` string you pass through unchanged:
|
|
9191
9204
|
|
|
9192
9205
|
\`\`\`
|
|
9193
9206
|
\u2726 New \u2014 Strong mid-size hardware distributor fit. Reach Marie Dupont, Sales Director: +33 5 55 12 34 56, m.dupont@example.fr.
|
|
9194
9207
|
\`\`\`
|
|
9195
9208
|
|
|
9196
|
-
|
|
9209
|
+
Leads without coordinates are already omitted from \`map_locations\`. Use \`map_summary.leads_without_coords\` to footnote "+ N leads without coordinates" below the widget \u2014 no need to re-count.
|
|
9197
9210
|
|
|
9198
9211
|
## Chat prose AFTER the widget (where markdown DOES render)
|
|
9199
9212
|
|
|
@@ -9201,9 +9214,17 @@ Group the leads into THREE sections (Customers / Qualified Prospects / New Disco
|
|
|
9201
9214
|
|
|
9202
9215
|
If the user said something like "3+3+3", honor that split. If \`followups_count\` returned fewer Customers than asked, fill from Qualified.
|
|
9203
9216
|
|
|
9204
|
-
## RENDER \u2014 fallback for hosts without \`places_map_display_v0\`
|
|
9217
|
+
## RENDER \u2014 fallback for hosts without \`places_map_display_v0\` (still a map, never a prose list)
|
|
9205
9218
|
|
|
9206
|
-
|
|
9219
|
+
When the widget isn't in your tool set (e.g. Claude Desktop), you MUST still produce a map by emitting one place-card block per \`map_locations\` entry in EXACTLY this shape \u2014 the \`### Company \xB7 City, State\` heading with the address is what makes the host auto-detect it and render its own Google-Place-card carousel:
|
|
9220
|
+
|
|
9221
|
+
\`\`\`
|
|
9222
|
+
### <Company Name> \xB7 <City>, <State>
|
|
9223
|
+
|
|
9224
|
+
<\u2605 Customer | \u2605 Qualified | \u2726 New> \u2014 <one-sentence fit>. Reach **[<Contact>](<LinkedIn URL>)**, <role>. \u260E <bare phone>.
|
|
9225
|
+
\`\`\`
|
|
9226
|
+
|
|
9227
|
+
Group the blocks by mode (Customers \u2192 Qualified \u2192 New). A flat narrative paragraph ("Brooklyn Brewery \u2014 Broadway, 10018 (Midtown). Contact: \u2026") does NOT auto-detect into cards and is WRONG \u2014 the per-lead heading with the city is mandatory.
|
|
9207
9228
|
|
|
9208
9229
|
## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
|
|
9209
9230
|
|
|
@@ -9213,7 +9234,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
9213
9234
|
|
|
9214
9235
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
9215
9236
|
|---|---|---|
|
|
9216
|
-
| \`places_map_display_v0\` (Claude) |
|
|
9237
|
+
| \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
|
|
9217
9238
|
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
9218
9239
|
| \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
|
|
9219
9240
|
|
|
@@ -9232,7 +9253,7 @@ WHEN TO USE: the user signals a *mixed* tour-planning intent \u2014 they want bo
|
|
|
9232
9253
|
|
|
9233
9254
|
WHEN NOT TO USE: if the user only wants follow-ups (use \`leadbay_followups_map\`), only wants new leads (use \`leadbay_pull_leads\`), wants research on one specific account (\`leadbay_research_lead_by_id\`), or wants to persist the tour as a campaign artifact (chain into \`leadbay_create_campaign\` after this).
|
|
9234
9255
|
|
|
9235
|
-
**Response envelope**: \`{city, city_id, monitor_leads, discover_leads, discover_filter_note, _meta}\` on happy path; \`{status: "ambiguous_locations", location_ambiguities, ...}\` when the passed \`city\` matched multiple admin areas.
|
|
9256
|
+
**Response envelope**: \`{city, city_id, monitor_leads, discover_leads, discover_filter_note, map_locations, map_summary, _meta}\` on happy path; \`{status: "ambiguous_locations", location_ambiguities, ...}\` when the passed \`city\` matched multiple admin areas.
|
|
9236
9257
|
|
|
9237
9258
|
---
|
|
9238
9259
|
`;
|
|
@@ -15057,6 +15078,54 @@ function cityMatches(lead, cityHint) {
|
|
|
15057
15078
|
const haystacks = [loc.city, loc.state, loc.country, loc.full].filter((v) => typeof v === "string").map((v) => v.toLowerCase());
|
|
15058
15079
|
return haystacks.some((h) => h.includes(hint) || hint.includes(h));
|
|
15059
15080
|
}
|
|
15081
|
+
function toMapLocation(lead, mode) {
|
|
15082
|
+
const pos = lead?.location?.pos;
|
|
15083
|
+
const valid = Array.isArray(pos) && pos.length === 2 && pos.every((n) => typeof n === "number");
|
|
15084
|
+
if (!valid)
|
|
15085
|
+
return null;
|
|
15086
|
+
const loc = lead.location;
|
|
15087
|
+
const c = lead.recommended_contact;
|
|
15088
|
+
const cleanName = (v) => typeof v === "string" && v.trim() && v.trim().toLowerCase() !== "null" ? v.trim() : "";
|
|
15089
|
+
const fullName = c ? [cleanName(c.first_name), cleanName(c.last_name)].filter(Boolean).join(" ") : "";
|
|
15090
|
+
const role = cleanName(c?.job_title) ? `, ${cleanName(c?.job_title)}` : "";
|
|
15091
|
+
const angle = lead.split_ai_summary?.next_step ?? lead.split_ai_summary?.approach_angle ?? lead.short_description ?? "Worth a visit";
|
|
15092
|
+
let reach;
|
|
15093
|
+
if (c && fullName && c.phone_number) {
|
|
15094
|
+
reach = `Reach ${fullName}${role}: ${c.phone_number}${c.email ? `, ${c.email}` : ""}.`;
|
|
15095
|
+
} else if (c && fullName && c.email) {
|
|
15096
|
+
reach = `Reach ${fullName}${role}: ${c.email}.`;
|
|
15097
|
+
} else if (c && fullName) {
|
|
15098
|
+
reach = `Reach ${fullName} (enrich a channel).`;
|
|
15099
|
+
} else {
|
|
15100
|
+
reach = "Enrich a contact to reach this account.";
|
|
15101
|
+
}
|
|
15102
|
+
const notes = `${mode} \u2014 ${angle}. ${reach}`.slice(0, 280);
|
|
15103
|
+
return {
|
|
15104
|
+
name: lead.name,
|
|
15105
|
+
address: loc.full ?? [loc.city, loc.state, loc.country].filter(Boolean).join(", "),
|
|
15106
|
+
latitude: pos[0],
|
|
15107
|
+
longitude: pos[1],
|
|
15108
|
+
notes
|
|
15109
|
+
};
|
|
15110
|
+
}
|
|
15111
|
+
function hasMonitorHistory(lead) {
|
|
15112
|
+
return Boolean(lead?.epilogue_status || lead?.last_prospecting_action_at || lead?.last_monitor_action_at);
|
|
15113
|
+
}
|
|
15114
|
+
function buildMap(monitorLeads, discoverLeads2) {
|
|
15115
|
+
const mapLocations = [
|
|
15116
|
+
...monitorLeads.map((l) => toMapLocation(l, hasMonitorHistory(l) ? "\u2605 Customer" : "\u2605 Qualified")),
|
|
15117
|
+
...discoverLeads2.map((l) => toMapLocation(l, "\u2726 New"))
|
|
15118
|
+
].filter((m) => m !== null);
|
|
15119
|
+
const totalLeads = monitorLeads.length + discoverLeads2.length;
|
|
15120
|
+
return {
|
|
15121
|
+
map_locations: mapLocations,
|
|
15122
|
+
map_summary: {
|
|
15123
|
+
total_leads: totalLeads,
|
|
15124
|
+
leads_with_coords: mapLocations.length,
|
|
15125
|
+
leads_without_coords: totalLeads - mapLocations.length
|
|
15126
|
+
}
|
|
15127
|
+
};
|
|
15128
|
+
}
|
|
15060
15129
|
var DEFAULT_FOLLOWUPS_COUNT, DEFAULT_DISCOVER_COUNT, DISCOVER_OVER_PULL, tourPlan;
|
|
15061
15130
|
var init_tour_plan = __esm({
|
|
15062
15131
|
"../core/dist/composite/tour-plan.js"() {
|
|
@@ -15118,6 +15187,20 @@ var init_tour_plan = __esm({
|
|
|
15118
15187
|
type: "string",
|
|
15119
15188
|
description: "Human-readable summary of the client-side geo filter applied to Discover leads (e.g. 'matched 3/30 by city/state')."
|
|
15120
15189
|
},
|
|
15190
|
+
map_locations: {
|
|
15191
|
+
type: "array",
|
|
15192
|
+
description: "Pre-shaped entries for `places_map_display_v0` \u2014 pass each one verbatim ({name, address, latitude, longitude, notes}); the mode badge (\u2605 Customer / \u2605 Qualified / \u2726 New) is already in `notes`. Do NOT reshape or re-derive from `location.pos`. One entry per lead with valid coordinates; coordinate-less leads are omitted and counted in `map_summary`.",
|
|
15193
|
+
items: { type: "object" }
|
|
15194
|
+
},
|
|
15195
|
+
map_summary: {
|
|
15196
|
+
type: "object",
|
|
15197
|
+
description: "Deterministic coverage counts so the agent can footnote '+ N leads without coordinates' without re-counting.",
|
|
15198
|
+
properties: {
|
|
15199
|
+
total_leads: { type: "number" },
|
|
15200
|
+
leads_with_coords: { type: "number" },
|
|
15201
|
+
leads_without_coords: { type: "number" }
|
|
15202
|
+
}
|
|
15203
|
+
},
|
|
15121
15204
|
status: {
|
|
15122
15205
|
type: "string",
|
|
15123
15206
|
description: "'ambiguous_locations' when the passed `city` matched multiple admin areas \u2014 pick an id from location_ambiguities and re-call with city_id."
|
|
@@ -15134,7 +15217,7 @@ var init_tour_plan = __esm({
|
|
|
15134
15217
|
}
|
|
15135
15218
|
}
|
|
15136
15219
|
},
|
|
15137
|
-
required: ["monitor_leads", "discover_leads"]
|
|
15220
|
+
required: ["monitor_leads", "discover_leads", "map_locations"]
|
|
15138
15221
|
},
|
|
15139
15222
|
execute: async (client, params, ctx) => {
|
|
15140
15223
|
const followupsCount = params.followups_count ?? DEFAULT_FOLLOWUPS_COUNT;
|
|
@@ -15156,6 +15239,12 @@ var init_tour_plan = __esm({
|
|
|
15156
15239
|
monitor_leads: [],
|
|
15157
15240
|
discover_leads: [],
|
|
15158
15241
|
discover_filter_note: "City was ambiguous; pick an id and re-call to proceed.",
|
|
15242
|
+
map_locations: [],
|
|
15243
|
+
map_summary: {
|
|
15244
|
+
total_leads: 0,
|
|
15245
|
+
leads_with_coords: 0,
|
|
15246
|
+
leads_without_coords: 0
|
|
15247
|
+
},
|
|
15159
15248
|
city: params.city ?? null,
|
|
15160
15249
|
city_id: params.city_id ?? null,
|
|
15161
15250
|
_meta: {
|
|
@@ -15182,6 +15271,7 @@ var init_tour_plan = __esm({
|
|
|
15182
15271
|
monitor_leads: monitorLeads,
|
|
15183
15272
|
discover_leads: discoverLeads2,
|
|
15184
15273
|
discover_filter_note: filterNote,
|
|
15274
|
+
...buildMap(monitorLeads, discoverLeads2),
|
|
15185
15275
|
_meta: {
|
|
15186
15276
|
region: client.region,
|
|
15187
15277
|
latency_ms: client.lastMeta?.latency_ms ?? null
|
|
@@ -23666,44 +23756,80 @@ GATE \u2014 DEFER TO TOOL RENDERING. When you call a Leadbay composite that ship
|
|
|
23666
23756
|
If the prompt's body and the tool's RENDERING appear to conflict, the tool's RENDERING wins for the structural layout; the prompt's voice wins for the commentary that surrounds it.
|
|
23667
23757
|
|
|
23668
23758
|
|
|
23669
|
-
# PHASE 1 \u2014
|
|
23759
|
+
# PHASE 1 \u2014 CLARIFY SCOPE FIRST (ask before building)
|
|
23760
|
+
|
|
23761
|
+
Before calling \`leadbay_tour_plan\`, ask me what the tour should cover \u2014 a field tour is high-intent and the scope changes what lands on the map. Use your host's interactive question widget (\`ask_user_input_v0\` in Claude chat \u2014 \`{questions:[{question, type:"single_select", options:[\u2026]}]}\`; or \`AskUserQuestion\` in cowork / Claude Code \u2014 \`{questions:[{question, header, multiSelect, options:[{label, description}]}]}\`). Ask at most 2 short tap-to-answer questions:
|
|
23762
|
+
|
|
23763
|
+
1. **Who to include?** \u2014 options: "Mix \u2014 known accounts + fresh prospects" (default), "Only my known accounts (follow-ups)", "Only fresh new prospects".
|
|
23764
|
+
2. **Enrich contacts that have no phone/email?** \u2014 options: "Yes, enrich the top stops", "No, just plan the tour".
|
|
23765
|
+
|
|
23766
|
+
Skip a question only if I already answered it in my request (e.g. "plan a tour of just my new leads in NYC" \u2192 who-to-include is already "only new"; don't re-ask). If I gave the full scope up front, skip PHASE 1 entirely and go to PHASE 2.
|
|
23767
|
+
|
|
23768
|
+
Map my answers to the \`leadbay_tour_plan\` call:
|
|
23769
|
+
- **Mix** \u2192 defaults (\`followups_count: 6, discover_count: 6\`).
|
|
23770
|
+
- **Only known accounts** \u2192 \`discover_count: 0\`.
|
|
23771
|
+
- **Only new prospects** \u2192 \`followups_count: 0\`.
|
|
23772
|
+
- **Enrich = yes** \u2192 after the tour is planned (PHASE 2), call \`leadbay_enrich_titles\` / \`leadbay_prepare_outreach(enrich:true)\` on the top stops that have no \`recommended_contact\` channel, before drafting outreach.
|
|
23773
|
+
|
|
23774
|
+
# PHASE 2 \u2014 BUILD THE ITINERARY
|
|
23670
23775
|
|
|
23671
|
-
Call \`leadbay_tour_plan({city: "{{arg:city}}"
|
|
23776
|
+
Call \`leadbay_tour_plan({city: "{{arg:city}}", \u2026scope from PHASE 1})\`. If the response is \`status: "ambiguous_locations"\`, surface the candidates and ask me to pick one, then re-call with \`city_id\`.
|
|
23672
23777
|
|
|
23673
|
-
Split the returned \`monitor_leads\` into two buckets client-side using
|
|
23778
|
+
Split the returned \`monitor_leads\` into two buckets client-side using their engagement-history fields:
|
|
23674
23779
|
|
|
23675
|
-
- **Customers** \u2014 leads with any \`
|
|
23676
|
-
- **Qualified prospects** \u2014 leads with high \`ai_agent_lead_score\` (or \`score\`) but
|
|
23780
|
+
- **Customers** \u2014 leads with prior engagement history: any of \`epilogue_status\`, \`last_prospecting_action_at\`, or \`last_monitor_action_at\` is set. Treat as known accounts with prior interaction.
|
|
23781
|
+
- **Qualified prospects** \u2014 leads with a high \`ai_agent_lead_score\` (or \`score\`) but none of those history fields set (scored, not yet worked).
|
|
23677
23782
|
|
|
23678
23783
|
\`discover_leads\` are the **New** bucket.
|
|
23679
23784
|
|
|
23680
23785
|
Aim for a 3+3+3 split if possible. If the customers bucket has fewer than 3, fill from qualified. If discover_filter_note indicates a low match ratio for the city, mention it: "Only N/30 fresh leads matched your city" \u2014 better honest than padded.
|
|
23681
23786
|
|
|
23682
|
-
# PHASE
|
|
23787
|
+
# PHASE 3 \u2014 PRESENT THE ITINERARY + OFFER THE MAP
|
|
23683
23788
|
|
|
23684
|
-
|
|
23789
|
+
Show the planned tour as a concise per-lead list grouped by mode (Customers \u2192 Qualified \u2192 New), each line carrying its \`\u2605\`/\`\u2726\` badge, the company, city, and best contact. Keep it tight \u2014 this is the summary, not the map.
|
|
23685
23790
|
|
|
23686
|
-
|
|
23687
|
-
|
|
23688
|
-
|
|
23791
|
+
Then **ALWAYS offer the map as the immediate next step** \u2014 a tour is inherently geographic, so the map is the natural payoff. End the turn with a single clear proposal:
|
|
23792
|
+
|
|
23793
|
+
> **Want me to put these <N> stops on a map?** I'll plot them so you can see the route for your <city> day.
|
|
23794
|
+
|
|
23795
|
+
Make it a genuine yes/no offer (route it through \`ask_user_input_v0\` / \`AskUserQuestion\` when that's in your tool set, so it's a tappable choice; otherwise the one-line question above). Do NOT render the map yet, and do NOT bury the offer \u2014 proposing the map is mandatory on every tour, for BOTH "plan a prospecting tour in <city>" and "who's worth meeting in <city>" phrasings. If the user already said "show me on a map" / "give me the map" in their request, skip the offer and go straight to PHASE 4 (render).
|
|
23796
|
+
|
|
23797
|
+
# PHASE 4 \u2014 RENDER THE MAP (when the user accepts, or asked for it up front)
|
|
23798
|
+
|
|
23799
|
+
When the user says yes (or asked for the map in their original message), render it now. Two ways \u2014 you MUST do one, never a flat prose paragraph:
|
|
23800
|
+
|
|
23801
|
+
**Path A \u2014 \`places_search\` + \`places_map_display_v0\` are in your tool set.** Use the **two-step** host flow (this is what draws a real street map, not a schematic scatter):
|
|
23802
|
+
|
|
23803
|
+
1. **\`places_search\` first, once per lead** \u2014 query = each \`map_locations\` entry's \`name\` + full street \`address\` (e.g. "Brooklyn Brewery, 79 N 11th St, Brooklyn, NY"). Resolve all stops up front to get real \`place_id\`s + verified coords/ratings.
|
|
23804
|
+
2. **\`places_map_display_v0\` second**, fed those resolved places, in **Itinerary mode** (a tour is a multi-stop route \u2014 show order, optionally arrival times / stop durations). Carry each lead's \u2605/\u2726 badge + one-line pitch from its \`notes\` into the place note.
|
|
23805
|
+
|
|
23806
|
+
Do NOT push raw \`latitude\`/\`longitude\` straight into the display widget without \`places_search\` \u2014 that degrades to the neighborhood-dot scatter. \`map_locations\` already carries \`name\`, full \`address\`, coords, and badge-tagged \`notes\`; don't shorten the address to the city.
|
|
23807
|
+
|
|
23808
|
+
**Path B \u2014 the widget is NOT in your tool set (e.g. Claude Desktop).** Emit one **place-card block per lead** in EXACTLY this shape so the chat host auto-detects the address and renders its own Google-Place-card map carousel \u2014 the \`### Company \xB7 City, State\` heading + address is what triggers it:
|
|
23809
|
+
|
|
23810
|
+
\`\`\`
|
|
23811
|
+
### <Company Name> \xB7 <City>, <State>
|
|
23812
|
+
|
|
23813
|
+
<\u2605 Customer | \u2605 Qualified | \u2726 New> \u2014 <one-sentence why-it-fits>. Reach **[<Contact name>](<LinkedIn URL>)**, <role>. \u260E <bare phone>.
|
|
23814
|
+
\`\`\`
|
|
23689
23815
|
|
|
23690
|
-
|
|
23816
|
+
One block per lead in \`map_locations\`, grouped by mode. Pull company, city/state, badge, and contact straight from the tool response. A flat paragraph like "Brooklyn Brewery \u2014 Broadway, 10018 (Midtown). Best contact: \u2026" does NOT auto-detect and is WRONG \u2014 the per-lead heading with the city is mandatory.
|
|
23691
23817
|
|
|
23692
|
-
|
|
23818
|
+
Coordinate-less leads are already omitted from \`map_locations\`; footnote them with \`map_summary.leads_without_coords\` ("+ N leads without coordinates").
|
|
23693
23819
|
|
|
23694
|
-
# PHASE
|
|
23820
|
+
# PHASE 5 \u2014 DRAFT IN-AREA OUTREACH (optional, ask first)
|
|
23695
23821
|
|
|
23696
23822
|
After the map, ask me ONCE: "Want me to draft 'I'll be in {{arg:city}}{{arg:date_paren}}' outreach for the top accounts?" If I say yes, for each of the top 3 leads (1 Customer / 1 Qualified / 1 New), call \`leadbay_prepare_outreach(leadId)\` and route the draft through \`message_compose_v1\` with a single variant labeled "In-area visit" \u2014 body opens with the visit context, references the AI-summary angle, ends with a clear ask (15-min coffee / on-site stopover).
|
|
23697
23823
|
|
|
23698
23824
|
Serialize the prepare_outreach calls (max 3 in parallel \u2014 see the long-running-tools rule).
|
|
23699
23825
|
|
|
23700
|
-
# PHASE
|
|
23826
|
+
# PHASE 6 \u2014 PERSIST AS A CAMPAIGN (optional, ask first)
|
|
23701
23827
|
|
|
23702
23828
|
After drafts, ask me ONCE: "Save these 9 accounts as a campaign called '**{{arg:city}} Tour{{arg:date_dash}}**'?" If I say yes, call \`leadbay_create_campaign({lead_ids: [...all_nine_lead_ids], name: "{{arg:city}} Tour{{arg:date_dash}}"})\`. Surface the returned \`id\` + \`name\` as a confirmation line, and offer the NEXT STEPS chip "View progression" (which routes to \`leadbay_campaign_progression\`).
|
|
23703
23829
|
|
|
23704
23830
|
If I declined the campaign step, end the turn \u2014 the map + drafts are enough for an ad-hoc trip.
|
|
23705
23831
|
|
|
23706
|
-
# PHASE
|
|
23832
|
+
# PHASE 7 \u2014 STOP
|
|
23707
23833
|
|
|
23708
23834
|
Done. The map is the surface; the drafts are the action; the campaign is the persistence layer for managerial follow-up after the trip.
|
|
23709
23835
|
`;
|
|
@@ -24302,7 +24428,7 @@ var PROMPT_META = {
|
|
|
24302
24428
|
leadbay_followup_check_in: { "name": "leadbay_followup_check_in", "short_description": 'Follow-up check-in: surface KNOWN leads from the Monitor view needing\nre-engagement. Trigger on "follow up", "already known leads", "what\'s\noverdue", "before my trip", "who should I re-engage". Do NOT trigger on\n"show me today\'s leads", "my morning check-in", "run my check-in",\n"I do this every day", "every morning" \u2014 those go to\n`leadbay_daily_check_in`.\n', "arguments": [], "expected_calls": ["leadbay_pull_followups", "leadbay_research_lead_by_id", "leadbay_prepare_outreach"], "failure_modes": ["Calls leadbay_pull_leads (the Discover entry point) instead of leadbay_pull_followups \u2014 these are different data sources; the Discover queue does NOT contain Monitor's known-but-cold pipeline", 'Iterates pages of leadbay_pull_leads filtering by engagement_count to "fake" a follow-up view (a real bug observed in 0.9.0 \u2014 the right move is to call pull_followups directly)', "Replaces the canonical pull_followups table layout with prose per row (the per-tool RENDERING block is the structural contract; commentary belongs above or below)", 'Skips the cross-mode pivot offer at the end ("Want to see NEW leads from your wishlist instead?" routes to leadbay_pull_leads)'] },
|
|
24303
24429
|
leadbay_import_file: { "name": "leadbay_import_file", "short_description": "Import a user-supplied CSV/file into Leadbay through five phases with\nevidence gates \u2014 scan, derive, resolve identities, preserve & commit,\nthen optionally qualify and report. The job is to maximize how many\nrows the Leadbay system actually ingests and matches.\n", "arguments": [{ "name": "file", "description": "Path or user-visible name of the CSV/file to import. If omitted, use the file the user attached or referenced.", "required": false }, { "name": "instruction", "description": 'Additional user goal, e.g. "then qualify the leads", "preserve owner phone as a custom field", or "only import restaurants in Manhattan".', "required": false }], "expected_calls": ["leadbay_resolve_import_rows", "leadbay_list_mappable_fields", "leadbay_create_custom_field", "leadbay_import_leads", "leadbay_import_and_qualify", "leadbay_add_note", "leadbay_import_status"], "failure_modes": ["Picks LEADBAY_ID from score alone, name-only, fuzzy-name-only, root-domain-only, brand-only, postcode-only, or city-only evidence", "Drops meaningful business notes or CRM record links instead of preserving them as custom fields or lead notes", "Treats a consumer mailbox domain (gmail.com, hotmail.com, ...) as the company domain", "Skips deriving company_domain from a business email when no website column exists (this kills match rate)", "Skips the COLUMN PRESERVATION PLAN byproduct before importing", "Skips the DECISION LOG byproduct before writing LEADBAY_ID", "Returns the imported records WITHOUT writing LEADBAY_ID values back into the user's file (leaves the user no audit trail of what matched)", "Fabricates leadIds, contact emails, or mapping IDs not present in the file or a tool response"] },
|
|
24304
24430
|
leadbay_log_outreach: { "name": "leadbay_log_outreach", "short_description": "Log outreach (an email I sent, a call I made, a meeting I had) on a\nspecific lead. Captures verification so the SDR pipeline trusts the entry.\n", "arguments": [{ "name": "lead_id", "description": "The lead UUID. Get it from leadbay_pull_leads or leadbay_research_lead_by_id.", "required": true }, { "name": "summary", "description": "1-2 sentences describing what I did (e.g. 'Sent intro email to CTO citing recent Hornsea contract').", "required": true }], "expected_calls": ["leadbay_report_outreach"], "failure_modes": ["Calls leadbay_report_outreach without first collecting a verification source", "Fabricates a gmail_message_id or calendar_event_id (the human team treats verification as canonical)", "Records outreach to a different lead_id than the one the user supplied", "Skips the dry_run step when the user is unsure what would be sent"] },
|
|
24305
|
-
leadbay_plan_tour_in_city: { "name": "leadbay_plan_tour_in_city", "short_description":
|
|
24431
|
+
leadbay_plan_tour_in_city: { "name": "leadbay_plan_tour_in_city", "short_description": 'Use whenever the user names a city they\'ll be in and asks who to see\n\u2014 "I\'m in SF next Tuesday, who\'s worth meeting?", "I\'m going to Berlin\n\u2014 who should I visit?", "plan my <city> tour". Any in-person/visit\nintent tied to a place routes here, NOT to `leadbay_pull_leads`. It\nsurfaces follow-ups + fresh Discover leads in the city via\n`leadbay_tour_plan`, ALWAYS offers to plot them on a map (rendering it\non yes), then offers outreach drafts + campaign persistence.\n', "arguments": [{ "name": "city", "description": "City or region the user is visiting (e.g. 'Limoges', 'Bay Area'). Used as the geo filter for both Monitor and Discover lookups.", "required": true }, { "name": "date", "description": "When the visit is (e.g. 'May 24', 'next Thursday'). Surfaced in the outreach drafts as 'I'll be in <city> on <date>'.", "required": false }], "expected_calls": ["leadbay_tour_plan", "leadbay_research_lead_by_id", "leadbay_prepare_outreach", "leadbay_create_campaign"], "failure_modes": ["Calls leadbay_followups_map (Monitor-only) instead of leadbay_tour_plan \u2014 loses the Discover (fresh-lead) half that the user explicitly asked for", "Calls leadbay_pull_leads then drops the geo filter \u2014 returns the lens-wide wishlist instead of city-relevant fresh leads", 'Skips the campaign-persist step ("would you like to save these as a tour?") \u2014 leaves the rep with a one-shot map but no follow-up artifact', "Creates a campaign WITHOUT asking the user first \u2014 the persist step is high-intent; offer it, don't assume", "Fabricates lead_ids when seeding the campaign instead of using the ids returned by tour_plan"] },
|
|
24306
24432
|
leadbay_prospecting_overview: { "name": "leadbay_prospecting_overview", "short_description": `Orientation for working with Leadbay from any host \u2014 discovery vs.
|
|
24307
24433
|
follow-up, the outreach loop, outcome recording, imports, pushback /
|
|
24308
24434
|
snooze, and the connected-outreach-tool registry. Trigger when the
|
|
@@ -24324,7 +24450,7 @@ var PROMPT_CATALOG_BULLETS = {
|
|
|
24324
24450
|
leadbay_followup_check_in: `- \`leadbay_followup_check_in\`: Follow-up check-in: surface KNOWN leads from the Monitor view needing re-engagement. Trigger on "follow up", "already known leads", "what's overdue", "before my trip", "who should I re-engage". Do NOT trigger on "show me today's leads", "my morning check-in", "run my check-in", "I do this every day", "every morning" \u2014 those go to \`leadbay_daily_check_in\`.`,
|
|
24325
24451
|
leadbay_import_file: `- \`leadbay_import_file\` (optional args: file, instruction): Import a user-supplied CSV/file into Leadbay through five phases with evidence gates \u2014 scan, derive, resolve identities, preserve & commit, then optionally qualify and report. The job is to maximize how many rows the Leadbay system actually ingests and matches.`,
|
|
24326
24452
|
leadbay_log_outreach: `- \`leadbay_log_outreach\` (required args: lead_id, summary): Log outreach (an email I sent, a call I made, a meeting I had) on a specific lead. Captures verification so the SDR pipeline trusts the entry.`,
|
|
24327
|
-
leadbay_plan_tour_in_city: `- \`leadbay_plan_tour_in_city\` (required args: city; optional args: date):
|
|
24453
|
+
leadbay_plan_tour_in_city: `- \`leadbay_plan_tour_in_city\` (required args: city; optional args: date): Use whenever the user names a city they'll be in and asks who to see \u2014 "I'm in SF next Tuesday, who's worth meeting?", "I'm going to Berlin \u2014 who should I visit?", "plan my <city> tour". Any in-person/visit intent tied to a place routes here, NOT to \`leadbay_pull_leads\`. It surfaces follow-ups + fresh Discover leads in the city via \`leadbay_tour_plan\`, ALWAYS offers to plot them on a map (rendering it on yes), then offers outreach drafts + campaign persistence.`,
|
|
24328
24454
|
leadbay_prospecting_overview: `- \`leadbay_prospecting_overview\`: Orientation for working with Leadbay from any host \u2014 discovery vs. follow-up, the outreach loop, outcome recording, imports, pushback / snooze, and the connected-outreach-tool registry. Trigger when the conversation involves Leadbay leads, prospecting, pipeline, follow-up, outreach, or lens / ICP \u2014 anything from "show me my leads" to "what should I follow up on" to "I'll send via lemlist".`,
|
|
24329
24455
|
leadbay_qualify_top_n: `- \`leadbay_qualify_top_n\` (optional args: count): Bulk-qualify the top N un-qualified leads in the active lens. Uses leadbay_bulk_qualify_leads with a sensible default budget.`,
|
|
24330
24456
|
leadbay_refine_audience: `- \`leadbay_refine_audience\` (required args: instruction): Refine the kind of leads Leadbay surfaces beyond firmographics, with a free-text instruction. Handles the clarification round-trip if the new prompt is ambiguous.`,
|
|
@@ -27591,7 +27717,7 @@ var OAUTH_BASE_URLS = {
|
|
|
27591
27717
|
fr: "https://staging.api.leadbay.app"
|
|
27592
27718
|
}
|
|
27593
27719
|
};
|
|
27594
|
-
var VERSION = "0.23.
|
|
27720
|
+
var VERSION = "0.23.7";
|
|
27595
27721
|
var HELP = `
|
|
27596
27722
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
27597
27723
|
|
package/dist/http-server.js
CHANGED
|
@@ -625,44 +625,80 @@ GATE \u2014 DEFER TO TOOL RENDERING. When you call a Leadbay composite that ship
|
|
|
625
625
|
If the prompt's body and the tool's RENDERING appear to conflict, the tool's RENDERING wins for the structural layout; the prompt's voice wins for the commentary that surrounds it.
|
|
626
626
|
|
|
627
627
|
|
|
628
|
-
# PHASE 1 \u2014
|
|
628
|
+
# PHASE 1 \u2014 CLARIFY SCOPE FIRST (ask before building)
|
|
629
629
|
|
|
630
|
-
|
|
630
|
+
Before calling \`leadbay_tour_plan\`, ask me what the tour should cover \u2014 a field tour is high-intent and the scope changes what lands on the map. Use your host's interactive question widget (\`ask_user_input_v0\` in Claude chat \u2014 \`{questions:[{question, type:"single_select", options:[\u2026]}]}\`; or \`AskUserQuestion\` in cowork / Claude Code \u2014 \`{questions:[{question, header, multiSelect, options:[{label, description}]}]}\`). Ask at most 2 short tap-to-answer questions:
|
|
631
631
|
|
|
632
|
-
|
|
632
|
+
1. **Who to include?** \u2014 options: "Mix \u2014 known accounts + fresh prospects" (default), "Only my known accounts (follow-ups)", "Only fresh new prospects".
|
|
633
|
+
2. **Enrich contacts that have no phone/email?** \u2014 options: "Yes, enrich the top stops", "No, just plan the tour".
|
|
633
634
|
|
|
634
|
-
|
|
635
|
-
|
|
635
|
+
Skip a question only if I already answered it in my request (e.g. "plan a tour of just my new leads in NYC" \u2192 who-to-include is already "only new"; don't re-ask). If I gave the full scope up front, skip PHASE 1 entirely and go to PHASE 2.
|
|
636
|
+
|
|
637
|
+
Map my answers to the \`leadbay_tour_plan\` call:
|
|
638
|
+
- **Mix** \u2192 defaults (\`followups_count: 6, discover_count: 6\`).
|
|
639
|
+
- **Only known accounts** \u2192 \`discover_count: 0\`.
|
|
640
|
+
- **Only new prospects** \u2192 \`followups_count: 0\`.
|
|
641
|
+
- **Enrich = yes** \u2192 after the tour is planned (PHASE 2), call \`leadbay_enrich_titles\` / \`leadbay_prepare_outreach(enrich:true)\` on the top stops that have no \`recommended_contact\` channel, before drafting outreach.
|
|
642
|
+
|
|
643
|
+
# PHASE 2 \u2014 BUILD THE ITINERARY
|
|
644
|
+
|
|
645
|
+
Call \`leadbay_tour_plan({city: "{{arg:city}}", \u2026scope from PHASE 1})\`. If the response is \`status: "ambiguous_locations"\`, surface the candidates and ask me to pick one, then re-call with \`city_id\`.
|
|
646
|
+
|
|
647
|
+
Split the returned \`monitor_leads\` into two buckets client-side using their engagement-history fields:
|
|
648
|
+
|
|
649
|
+
- **Customers** \u2014 leads with prior engagement history: any of \`epilogue_status\`, \`last_prospecting_action_at\`, or \`last_monitor_action_at\` is set. Treat as known accounts with prior interaction.
|
|
650
|
+
- **Qualified prospects** \u2014 leads with a high \`ai_agent_lead_score\` (or \`score\`) but none of those history fields set (scored, not yet worked).
|
|
636
651
|
|
|
637
652
|
\`discover_leads\` are the **New** bucket.
|
|
638
653
|
|
|
639
654
|
Aim for a 3+3+3 split if possible. If the customers bucket has fewer than 3, fill from qualified. If discover_filter_note indicates a low match ratio for the city, mention it: "Only N/30 fresh leads matched your city" \u2014 better honest than padded.
|
|
640
655
|
|
|
641
|
-
# PHASE
|
|
656
|
+
# PHASE 3 \u2014 PRESENT THE ITINERARY + OFFER THE MAP
|
|
657
|
+
|
|
658
|
+
Show the planned tour as a concise per-lead list grouped by mode (Customers \u2192 Qualified \u2192 New), each line carrying its \`\u2605\`/\`\u2726\` badge, the company, city, and best contact. Keep it tight \u2014 this is the summary, not the map.
|
|
659
|
+
|
|
660
|
+
Then **ALWAYS offer the map as the immediate next step** \u2014 a tour is inherently geographic, so the map is the natural payoff. End the turn with a single clear proposal:
|
|
661
|
+
|
|
662
|
+
> **Want me to put these <N> stops on a map?** I'll plot them so you can see the route for your <city> day.
|
|
663
|
+
|
|
664
|
+
Make it a genuine yes/no offer (route it through \`ask_user_input_v0\` / \`AskUserQuestion\` when that's in your tool set, so it's a tappable choice; otherwise the one-line question above). Do NOT render the map yet, and do NOT bury the offer \u2014 proposing the map is mandatory on every tour, for BOTH "plan a prospecting tour in <city>" and "who's worth meeting in <city>" phrasings. If the user already said "show me on a map" / "give me the map" in their request, skip the offer and go straight to PHASE 4 (render).
|
|
665
|
+
|
|
666
|
+
# PHASE 4 \u2014 RENDER THE MAP (when the user accepts, or asked for it up front)
|
|
667
|
+
|
|
668
|
+
When the user says yes (or asked for the map in their original message), render it now. Two ways \u2014 you MUST do one, never a flat prose paragraph:
|
|
669
|
+
|
|
670
|
+
**Path A \u2014 \`places_search\` + \`places_map_display_v0\` are in your tool set.** Use the **two-step** host flow (this is what draws a real street map, not a schematic scatter):
|
|
671
|
+
|
|
672
|
+
1. **\`places_search\` first, once per lead** \u2014 query = each \`map_locations\` entry's \`name\` + full street \`address\` (e.g. "Brooklyn Brewery, 79 N 11th St, Brooklyn, NY"). Resolve all stops up front to get real \`place_id\`s + verified coords/ratings.
|
|
673
|
+
2. **\`places_map_display_v0\` second**, fed those resolved places, in **Itinerary mode** (a tour is a multi-stop route \u2014 show order, optionally arrival times / stop durations). Carry each lead's \u2605/\u2726 badge + one-line pitch from its \`notes\` into the place note.
|
|
642
674
|
|
|
643
|
-
|
|
675
|
+
Do NOT push raw \`latitude\`/\`longitude\` straight into the display widget without \`places_search\` \u2014 that degrades to the neighborhood-dot scatter. \`map_locations\` already carries \`name\`, full \`address\`, coords, and badge-tagged \`notes\`; don't shorten the address to the city.
|
|
644
676
|
|
|
645
|
-
|
|
646
|
-
- \`\u2605 Qualified \u2014 <one-sentence>. Reach <name>...\`
|
|
647
|
-
- \`\u2726 New \u2014 <one-sentence>. Reach <name>...\`
|
|
677
|
+
**Path B \u2014 the widget is NOT in your tool set (e.g. Claude Desktop).** Emit one **place-card block per lead** in EXACTLY this shape so the chat host auto-detects the address and renders its own Google-Place-card map carousel \u2014 the \`### Company \xB7 City, State\` heading + address is what triggers it:
|
|
648
678
|
|
|
649
|
-
|
|
679
|
+
\`\`\`
|
|
680
|
+
### <Company Name> \xB7 <City>, <State>
|
|
681
|
+
|
|
682
|
+
<\u2605 Customer | \u2605 Qualified | \u2726 New> \u2014 <one-sentence why-it-fits>. Reach **[<Contact name>](<LinkedIn URL>)**, <role>. \u260E <bare phone>.
|
|
683
|
+
\`\`\`
|
|
684
|
+
|
|
685
|
+
One block per lead in \`map_locations\`, grouped by mode. Pull company, city/state, badge, and contact straight from the tool response. A flat paragraph like "Brooklyn Brewery \u2014 Broadway, 10018 (Midtown). Best contact: \u2026" does NOT auto-detect and is WRONG \u2014 the per-lead heading with the city is mandatory.
|
|
650
686
|
|
|
651
|
-
|
|
687
|
+
Coordinate-less leads are already omitted from \`map_locations\`; footnote them with \`map_summary.leads_without_coords\` ("+ N leads without coordinates").
|
|
652
688
|
|
|
653
|
-
# PHASE
|
|
689
|
+
# PHASE 5 \u2014 DRAFT IN-AREA OUTREACH (optional, ask first)
|
|
654
690
|
|
|
655
691
|
After the map, ask me ONCE: "Want me to draft 'I'll be in {{arg:city}}{{arg:date_paren}}' outreach for the top accounts?" If I say yes, for each of the top 3 leads (1 Customer / 1 Qualified / 1 New), call \`leadbay_prepare_outreach(leadId)\` and route the draft through \`message_compose_v1\` with a single variant labeled "In-area visit" \u2014 body opens with the visit context, references the AI-summary angle, ends with a clear ask (15-min coffee / on-site stopover).
|
|
656
692
|
|
|
657
693
|
Serialize the prepare_outreach calls (max 3 in parallel \u2014 see the long-running-tools rule).
|
|
658
694
|
|
|
659
|
-
# PHASE
|
|
695
|
+
# PHASE 6 \u2014 PERSIST AS A CAMPAIGN (optional, ask first)
|
|
660
696
|
|
|
661
697
|
After drafts, ask me ONCE: "Save these 9 accounts as a campaign called '**{{arg:city}} Tour{{arg:date_dash}}**'?" If I say yes, call \`leadbay_create_campaign({lead_ids: [...all_nine_lead_ids], name: "{{arg:city}} Tour{{arg:date_dash}}"})\`. Surface the returned \`id\` + \`name\` as a confirmation line, and offer the NEXT STEPS chip "View progression" (which routes to \`leadbay_campaign_progression\`).
|
|
662
698
|
|
|
663
699
|
If I declined the campaign step, end the turn \u2014 the map + drafts are enough for an ad-hoc trip.
|
|
664
700
|
|
|
665
|
-
# PHASE
|
|
701
|
+
# PHASE 7 \u2014 STOP
|
|
666
702
|
|
|
667
703
|
Done. The map is the surface; the drafts are the action; the campaign is the persistence layer for managerial follow-up after the trip.
|
|
668
704
|
`;
|
|
@@ -1261,7 +1297,7 @@ var PROMPT_META = {
|
|
|
1261
1297
|
leadbay_followup_check_in: { "name": "leadbay_followup_check_in", "short_description": 'Follow-up check-in: surface KNOWN leads from the Monitor view needing\nre-engagement. Trigger on "follow up", "already known leads", "what\'s\noverdue", "before my trip", "who should I re-engage". Do NOT trigger on\n"show me today\'s leads", "my morning check-in", "run my check-in",\n"I do this every day", "every morning" \u2014 those go to\n`leadbay_daily_check_in`.\n', "arguments": [], "expected_calls": ["leadbay_pull_followups", "leadbay_research_lead_by_id", "leadbay_prepare_outreach"], "failure_modes": ["Calls leadbay_pull_leads (the Discover entry point) instead of leadbay_pull_followups \u2014 these are different data sources; the Discover queue does NOT contain Monitor's known-but-cold pipeline", 'Iterates pages of leadbay_pull_leads filtering by engagement_count to "fake" a follow-up view (a real bug observed in 0.9.0 \u2014 the right move is to call pull_followups directly)', "Replaces the canonical pull_followups table layout with prose per row (the per-tool RENDERING block is the structural contract; commentary belongs above or below)", 'Skips the cross-mode pivot offer at the end ("Want to see NEW leads from your wishlist instead?" routes to leadbay_pull_leads)'] },
|
|
1262
1298
|
leadbay_import_file: { "name": "leadbay_import_file", "short_description": "Import a user-supplied CSV/file into Leadbay through five phases with\nevidence gates \u2014 scan, derive, resolve identities, preserve & commit,\nthen optionally qualify and report. The job is to maximize how many\nrows the Leadbay system actually ingests and matches.\n", "arguments": [{ "name": "file", "description": "Path or user-visible name of the CSV/file to import. If omitted, use the file the user attached or referenced.", "required": false }, { "name": "instruction", "description": 'Additional user goal, e.g. "then qualify the leads", "preserve owner phone as a custom field", or "only import restaurants in Manhattan".', "required": false }], "expected_calls": ["leadbay_resolve_import_rows", "leadbay_list_mappable_fields", "leadbay_create_custom_field", "leadbay_import_leads", "leadbay_import_and_qualify", "leadbay_add_note", "leadbay_import_status"], "failure_modes": ["Picks LEADBAY_ID from score alone, name-only, fuzzy-name-only, root-domain-only, brand-only, postcode-only, or city-only evidence", "Drops meaningful business notes or CRM record links instead of preserving them as custom fields or lead notes", "Treats a consumer mailbox domain (gmail.com, hotmail.com, ...) as the company domain", "Skips deriving company_domain from a business email when no website column exists (this kills match rate)", "Skips the COLUMN PRESERVATION PLAN byproduct before importing", "Skips the DECISION LOG byproduct before writing LEADBAY_ID", "Returns the imported records WITHOUT writing LEADBAY_ID values back into the user's file (leaves the user no audit trail of what matched)", "Fabricates leadIds, contact emails, or mapping IDs not present in the file or a tool response"] },
|
|
1263
1299
|
leadbay_log_outreach: { "name": "leadbay_log_outreach", "short_description": "Log outreach (an email I sent, a call I made, a meeting I had) on a\nspecific lead. Captures verification so the SDR pipeline trusts the entry.\n", "arguments": [{ "name": "lead_id", "description": "The lead UUID. Get it from leadbay_pull_leads or leadbay_research_lead_by_id.", "required": true }, { "name": "summary", "description": "1-2 sentences describing what I did (e.g. 'Sent intro email to CTO citing recent Hornsea contract').", "required": true }], "expected_calls": ["leadbay_report_outreach"], "failure_modes": ["Calls leadbay_report_outreach without first collecting a verification source", "Fabricates a gmail_message_id or calendar_event_id (the human team treats verification as canonical)", "Records outreach to a different lead_id than the one the user supplied", "Skips the dry_run step when the user is unsure what would be sent"] },
|
|
1264
|
-
leadbay_plan_tour_in_city: { "name": "leadbay_plan_tour_in_city", "short_description":
|
|
1300
|
+
leadbay_plan_tour_in_city: { "name": "leadbay_plan_tour_in_city", "short_description": 'Use whenever the user names a city they\'ll be in and asks who to see\n\u2014 "I\'m in SF next Tuesday, who\'s worth meeting?", "I\'m going to Berlin\n\u2014 who should I visit?", "plan my <city> tour". Any in-person/visit\nintent tied to a place routes here, NOT to `leadbay_pull_leads`. It\nsurfaces follow-ups + fresh Discover leads in the city via\n`leadbay_tour_plan`, ALWAYS offers to plot them on a map (rendering it\non yes), then offers outreach drafts + campaign persistence.\n', "arguments": [{ "name": "city", "description": "City or region the user is visiting (e.g. 'Limoges', 'Bay Area'). Used as the geo filter for both Monitor and Discover lookups.", "required": true }, { "name": "date", "description": "When the visit is (e.g. 'May 24', 'next Thursday'). Surfaced in the outreach drafts as 'I'll be in <city> on <date>'.", "required": false }], "expected_calls": ["leadbay_tour_plan", "leadbay_research_lead_by_id", "leadbay_prepare_outreach", "leadbay_create_campaign"], "failure_modes": ["Calls leadbay_followups_map (Monitor-only) instead of leadbay_tour_plan \u2014 loses the Discover (fresh-lead) half that the user explicitly asked for", "Calls leadbay_pull_leads then drops the geo filter \u2014 returns the lens-wide wishlist instead of city-relevant fresh leads", 'Skips the campaign-persist step ("would you like to save these as a tour?") \u2014 leaves the rep with a one-shot map but no follow-up artifact', "Creates a campaign WITHOUT asking the user first \u2014 the persist step is high-intent; offer it, don't assume", "Fabricates lead_ids when seeding the campaign instead of using the ids returned by tour_plan"] },
|
|
1265
1301
|
leadbay_prospecting_overview: { "name": "leadbay_prospecting_overview", "short_description": `Orientation for working with Leadbay from any host \u2014 discovery vs.
|
|
1266
1302
|
follow-up, the outreach loop, outcome recording, imports, pushback /
|
|
1267
1303
|
snooze, and the connected-outreach-tool registry. Trigger when the
|
|
@@ -1283,7 +1319,7 @@ var PROMPT_CATALOG_BULLETS = {
|
|
|
1283
1319
|
leadbay_followup_check_in: `- \`leadbay_followup_check_in\`: Follow-up check-in: surface KNOWN leads from the Monitor view needing re-engagement. Trigger on "follow up", "already known leads", "what's overdue", "before my trip", "who should I re-engage". Do NOT trigger on "show me today's leads", "my morning check-in", "run my check-in", "I do this every day", "every morning" \u2014 those go to \`leadbay_daily_check_in\`.`,
|
|
1284
1320
|
leadbay_import_file: `- \`leadbay_import_file\` (optional args: file, instruction): Import a user-supplied CSV/file into Leadbay through five phases with evidence gates \u2014 scan, derive, resolve identities, preserve & commit, then optionally qualify and report. The job is to maximize how many rows the Leadbay system actually ingests and matches.`,
|
|
1285
1321
|
leadbay_log_outreach: `- \`leadbay_log_outreach\` (required args: lead_id, summary): Log outreach (an email I sent, a call I made, a meeting I had) on a specific lead. Captures verification so the SDR pipeline trusts the entry.`,
|
|
1286
|
-
leadbay_plan_tour_in_city: `- \`leadbay_plan_tour_in_city\` (required args: city; optional args: date):
|
|
1322
|
+
leadbay_plan_tour_in_city: `- \`leadbay_plan_tour_in_city\` (required args: city; optional args: date): Use whenever the user names a city they'll be in and asks who to see \u2014 "I'm in SF next Tuesday, who's worth meeting?", "I'm going to Berlin \u2014 who should I visit?", "plan my <city> tour". Any in-person/visit intent tied to a place routes here, NOT to \`leadbay_pull_leads\`. It surfaces follow-ups + fresh Discover leads in the city via \`leadbay_tour_plan\`, ALWAYS offers to plot them on a map (rendering it on yes), then offers outreach drafts + campaign persistence.`,
|
|
1287
1323
|
leadbay_prospecting_overview: `- \`leadbay_prospecting_overview\`: Orientation for working with Leadbay from any host \u2014 discovery vs. follow-up, the outreach loop, outcome recording, imports, pushback / snooze, and the connected-outreach-tool registry. Trigger when the conversation involves Leadbay leads, prospecting, pipeline, follow-up, outreach, or lens / ICP \u2014 anything from "show me my leads" to "what should I follow up on" to "I'll send via lemlist".`,
|
|
1288
1324
|
leadbay_qualify_top_n: `- \`leadbay_qualify_top_n\` (optional args: count): Bulk-qualify the top N un-qualified leads in the active lens. Uses leadbay_bulk_qualify_leads with a sensible default budget.`,
|
|
1289
1325
|
leadbay_refine_audience: `- \`leadbay_refine_audience\` (required args: instruction): Refine the kind of leads Leadbay surfaces beyond firmographics, with a free-text instruction. Handles the clarification round-trip if the new prompt is ambiguous.`,
|
|
@@ -7210,7 +7246,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
7210
7246
|
|
|
7211
7247
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
7212
7248
|
|---|---|---|
|
|
7213
|
-
| \`places_map_display_v0\` (Claude) |
|
|
7249
|
+
| \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
|
|
7214
7250
|
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
7215
7251
|
| \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
|
|
7216
7252
|
|
|
@@ -7528,7 +7564,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
7528
7564
|
|
|
7529
7565
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
7530
7566
|
|---|---|---|
|
|
7531
|
-
| \`places_map_display_v0\` (Claude) |
|
|
7567
|
+
| \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
|
|
7532
7568
|
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
7533
7569
|
| \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
|
|
7534
7570
|
|
|
@@ -7784,7 +7820,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
7784
7820
|
|
|
7785
7821
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
7786
7822
|
|---|---|---|
|
|
7787
|
-
| \`places_map_display_v0\` (Claude) |
|
|
7823
|
+
| \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
|
|
7788
7824
|
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
7789
7825
|
| \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
|
|
7790
7826
|
|
|
@@ -8400,7 +8436,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
8400
8436
|
|
|
8401
8437
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
8402
8438
|
|---|---|---|
|
|
8403
|
-
| \`places_map_display_v0\` (Claude) |
|
|
8439
|
+
| \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
|
|
8404
8440
|
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
8405
8441
|
| \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
|
|
8406
8442
|
|
|
@@ -8532,7 +8568,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
8532
8568
|
|
|
8533
8569
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
8534
8570
|
|---|---|---|
|
|
8535
|
-
| \`places_map_display_v0\` (Claude) |
|
|
8571
|
+
| \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
|
|
8536
8572
|
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
8537
8573
|
| \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
|
|
8538
8574
|
|
|
@@ -8666,11 +8702,11 @@ email. Do NOT paste the email body into chat prose alongside.
|
|
|
8666
8702
|
|
|
8667
8703
|
---
|
|
8668
8704
|
|
|
8669
|
-
Prepare a single-lead outreach brief: the full \`lead\` block (score, \`split_ai_summary\`, \`location\`, \`size\`, \`phone_numbers\`, \`website\`, \`description\`, \`social_urls\`, \`social_presence\`), the \`recommended_contact\`
|
|
8705
|
+
Prepare a single-lead outreach brief: the full \`lead\` block (score, \`split_ai_summary\`, \`location\`, \`size\`, \`phone_numbers\`, \`website\`, \`description\`, \`social_urls\`, \`social_presence\`), the \`recommended_contact\` in post-enrichment shape (\`contact_id\`, names, \`job_title\`, \`email\`, \`phone_number\`, \`linkedin_page\`, \`is_org_contact\` \u2014 nulls where not yet enriched), \`additional_contacts_count\`, and an \`enrichment\` block describing async state.
|
|
8670
8706
|
|
|
8671
8707
|
Optionally trigger contact enrichment in-flight with \`enrich:true\`. Enrichment is async (~60s). **Self-polling pattern (no separate tool needed):** re-call \`leadbay_prepare_outreach(leadId)\` without \`enrich\`; check \`enrichment.complete\`. When \`complete: true\`, the recommended contact now carries \`email\` and/or \`phone_number\`.
|
|
8672
8708
|
|
|
8673
|
-
The first call
|
|
8709
|
+
The first call on a lead records a \`LEAD_VIEWED\`-style prospecting action server-side (deduped, one per lead per session) so it ages out of the Discover "new" view.
|
|
8674
8710
|
|
|
8675
8711
|
IRON LAW \u2014 OUTCOME AFTER OUTREACH. The moment the user reports outreach happened ("I sent it", "she didn't pick up", "left a voicemail", "they replied", a forwarded email thread, a calendar invite), you MUST (1) call leadbay_report_outreach with verification (gmail_message_id, calendar_event_id, or the user's literal one-sentence confirmation as user_confirmed.ref) AND (2) ask the user about the outcome and set epilogue_status to one of the 4 canonical values: EPILOGUE_INTEREST_VALIDATED_OR_MEETING_PLANED ("Meeting booked"), EPILOGUE_COULD_NOT_REACH_STILL_TRYING ("Trying to reach"), EPILOGUE_NOT_INTERESTED_LOST ("Not interested"), EPILOGUE_STILL_CHASING ("In progress"). Use the user-facing labels in dialogue ("What's the outcome \u2014 meeting booked, trying to reach, not interested, or in progress?"); never say "epilogue" out loud. Skipping this step silently de-ranks every future follow-up suggestion because pull_followups depends on honest, current outcomes.
|
|
8676
8712
|
|
|
@@ -8685,7 +8721,7 @@ WHEN NOT TO USE: across many leads \u2014 use leadbay_enrich_titles for bulk; fo
|
|
|
8685
8721
|
|
|
8686
8722
|
Route every draft through \`message_compose_v1\` (Claude's email composer). Above it, emit ONE short markdown context paragraph: score callout + sector fit + linked contact name + bare phone/email pills. Do NOT paste the email body into chat prose alongside \u2014 the composer IS the visual.
|
|
8687
8723
|
|
|
8688
|
-
Variant shape: 1\u20133 entries. Labels describe **strategy** ("Push for alignment", "Reference the M&A signal"
|
|
8724
|
+
Variant shape: 1\u20133 entries. Labels describe **strategy** ("Push for alignment", "Reference the M&A signal"), not tone. \`kind: "email"\` requires \`subject\`; phone/call openers use \`kind: "other"\` with the opener in \`body\`.
|
|
8689
8725
|
|
|
8690
8726
|
## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
|
|
8691
8727
|
|
|
@@ -8695,7 +8731,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
8695
8731
|
|
|
8696
8732
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
8697
8733
|
|---|---|---|
|
|
8698
|
-
| \`places_map_display_v0\` (Claude) |
|
|
8734
|
+
| \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
|
|
8699
8735
|
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
8700
8736
|
| \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
|
|
8701
8737
|
|
|
@@ -9024,7 +9060,7 @@ Trigger phrases: "show me leads", "show me new leads", "show me today's leads",
|
|
|
9024
9060
|
|
|
9025
9061
|
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
9026
9062
|
|
|
9027
|
-
Do NOT use for: "leads I should follow up with" \u2192 \`leadbay_pull_followups\`; "I'm going to <city>" \u2192 \`
|
|
9063
|
+
Do NOT use for: "leads I should follow up with" \u2192 \`leadbay_pull_followups\`; "I'm going to <city>" \u2192 \`leadbay_tour_plan\`; "I'm in <city> next week \u2014 who's worth meeting" \u2192 \`leadbay_tour_plan\`; "who should I meet in <city>" \u2192 \`leadbay_tour_plan\`; "visiting <city> \u2014 who's worth meeting / seeing" \u2192 \`leadbay_tour_plan\`; "leads I should reach out to" \u2192 \`leadbay_pull_followups\`; "leads to get back to" \u2192 \`leadbay_pull_followups\`; "leads to contact today" \u2192 \`leadbay_pull_followups\`; "should I contact" \u2192 \`leadbay_pull_followups\`; "reconnect with" \u2192 \`leadbay_pull_followups\`; "re-engage" \u2192 \`leadbay_pull_followups\`.
|
|
9028
9064
|
|
|
9029
9065
|
Prefer when: fresh Discover leads; if a lens is named, pass \`lensId\` and pin it
|
|
9030
9066
|
|
|
@@ -9035,6 +9071,7 @@ Examples that SHOULD invoke this tool:
|
|
|
9035
9071
|
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
9036
9072
|
- "Which leads should I follow up with this week?"
|
|
9037
9073
|
- "I'm flying to Berlin Thursday \u2014 who should I meet?"
|
|
9074
|
+
- "I'm in San Francisco next Tuesday \u2014 who's worth meeting?"
|
|
9038
9075
|
- "Show me leads I should reach out to today."
|
|
9039
9076
|
- "Who should I get back to today?"
|
|
9040
9077
|
- "Leads I should contact today."
|
|
@@ -10232,7 +10269,7 @@ WHEN NOT TO USE: the user wants a lead list (leadbay_pull_leads / leadbay_pull_f
|
|
|
10232
10269
|
`;
|
|
10233
10270
|
var leadbay_tour_plan = `## WHEN TO USE
|
|
10234
10271
|
|
|
10235
|
-
Trigger phrases: "visiting <city> in <N> days", "field tour in <city>", "plan a tour in <city>", "who should I meet in <city>", "customers plus prospects in <city>", "tour itinerary".
|
|
10272
|
+
Trigger phrases: "visiting <city> in <N> days", "I'm in <city> next week / Tuesday \u2014 who's worth meeting", "I'm going to <city> \u2014 who should I see", "who's worth meeting in <city>", "field tour in <city>", "plan a tour in <city>", "who should I meet in <city>", "customers plus prospects in <city>", "tour itinerary".
|
|
10236
10273
|
|
|
10237
10274
|
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
10238
10275
|
|
|
@@ -10242,6 +10279,7 @@ Prefer when: user wants known accounts plus new discoveries in one geographic it
|
|
|
10242
10279
|
|
|
10243
10280
|
Examples that SHOULD invoke this tool:
|
|
10244
10281
|
- "I'm flying to Limoges in 4 days \u2014 give me 3 customers, 3 qualified prospects, and 3 new high-potential."
|
|
10282
|
+
- "I'm in San Francisco next Tuesday. Who's worth meeting?"
|
|
10245
10283
|
- "Plan my tour next Tuesday in Lyon: known accounts plus discoveries."
|
|
10246
10284
|
- "Build a mixed itinerary for Berlin \u2014 I want both follow-ups and fresh leads."
|
|
10247
10285
|
|
|
@@ -10252,16 +10290,18 @@ Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
|
10252
10290
|
|
|
10253
10291
|
## RENDER (quick)
|
|
10254
10292
|
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10293
|
+
Present the leads grouped by mode (\u2605 Customer / \u2605 Qualified / \u2726 New),
|
|
10294
|
+
then ALWAYS offer to plot them on a map ("Want me to put these on a
|
|
10295
|
+
map?"). On yes (or if the user asked for a map up front), render it:
|
|
10296
|
+
pass \`map_locations\` verbatim into \`places_map_display_v0\`, or on a
|
|
10297
|
+
host without the widget emit one \`### Company \xB7 City, State\` place-card
|
|
10298
|
+
block per lead so the host's address carousel renders. Never a flat
|
|
10299
|
+
prose paragraph. Full recipe below.
|
|
10262
10300
|
|
|
10263
10301
|
---
|
|
10264
10302
|
|
|
10303
|
+
**BEFORE YOU CALL \u2014 clarify scope with a tap-to-answer question.** A field tour is high-intent, so unless the user already said it, ASK first (via \`ask_user_input_v0\` / \`AskUserQuestion\` \u2014 whichever is in your tool set) and wait for the answer before calling this tool: (1) **Who to include?** \u2014 "Mix \u2014 known accounts + fresh prospects" (default) / "Only my known accounts" / "Only fresh new prospects"; (2) **Enrich contacts with no phone/email?** \u2014 "Yes, enrich the top stops" / "No, just plan". Map answers to args: only-known \u2192 \`discover_count: 0\`; only-new \u2192 \`followups_count: 0\`; mix \u2192 defaults; enrich=yes \u2192 after planning, run \`leadbay_enrich_titles\` / \`leadbay_prepare_outreach(enrich:true)\` on top stops missing a channel. Skip the question only if the request already states scope ("plan a tour of just my new leads" \u2192 only-new, don't ask). Do NOT skip straight to building + mapping without this.
|
|
10304
|
+
|
|
10265
10305
|
Build a single-call mixed-mode itinerary for a field sales tour. Combines \`leadbay_pull_followups\` (Monitor leads in the city \u2014 known accounts) with \`leadbay_pull_leads\` (Discover wishlist \u2014 new prospects, then client-side filtered by city) so the agent can answer the canonical #3630 US1 ask: *"I'm visiting Limoges in 4 days \u2014 propose 3 customers + 3 qualified prospects + 3 new high-potential discoveries."*
|
|
10266
10306
|
|
|
10267
10307
|
**Geo resolution** is identical to \`leadbay_followups_map\`: pass \`city\` (any admin level \u2014 city, state, country, region \u2014 the \`/geo/search\` resolver picks the best match), or a pre-resolved \`city_id\`. Ambiguous matches surface as \`status: "ambiguous_locations"\` + \`location_ambiguities[]\`; pick an id and re-call with \`city_id\`.
|
|
@@ -10272,21 +10312,30 @@ Build a single-call mixed-mode itinerary for a field sales tour. Combines \`lead
|
|
|
10272
10312
|
|
|
10273
10313
|
---
|
|
10274
10314
|
|
|
10275
|
-
## RENDER \u2014
|
|
10315
|
+
## RENDER \u2014 present the leads, then OFFER the map (render on yes)
|
|
10316
|
+
|
|
10317
|
+
First present the leads grouped by mode as a tight per-lead list (badge + company + city + best contact). Then **ALWAYS offer the map** as the next step \u2014 "Want me to put these on a map?" \u2014 and render it only when the user accepts (or asked for a map in their original message). The map is mandatory to *offer* on every tour; it is *rendered* on acceptance.
|
|
10276
10318
|
|
|
10277
|
-
|
|
10319
|
+
When rendering, use the **two-step host flow** \u2014 this is what produces a real interactive street map instead of a schematic neighborhood scatter:
|
|
10278
10320
|
|
|
10279
|
-
|
|
10280
|
-
-
|
|
10321
|
+
1. **\`places_search\` first, once per lead.** For each entry in \`map_locations\`, call \`places_search\` with a natural query built from the lead's \`name\` + \`address\` (the full street address, e.g. "Brooklyn Brewery, 79 N 11th St, Brooklyn, NY"). This resolves a real \`place_id\` + verified coordinates/ratings from the host's place index. Run them up front (resolve all stops first), as the host's own guidance prescribes.
|
|
10322
|
+
2. **\`places_map_display_v0\` second**, fed the resolved places. Use **Itinerary mode** for a tour (multi-stop, route order, optional arrival times / stop durations) \u2014 a field-sales day IS an itinerary; Markers mode is only for a plain "show me X on a map". Carry each lead's mode badge (\u2605/\u2726) + one-line pitch from \`map_locations[i].notes\` into the place's note.
|
|
10323
|
+
|
|
10324
|
+
Do NOT skip \`places_search\` and push raw \`latitude\`/\`longitude\` straight into the display widget \u2014 that's exactly what falls back to the schematic relative scatter (dots by neighborhood, no streets). The \`map_locations\` array already gives you everything \`places_search\` needs (\`name\`, full \`address\`, \`latitude\`, \`longitude\`, badge-tagged \`notes\`); do NOT shorten the address to just the city.
|
|
10325
|
+
|
|
10326
|
+
For reference, the badge the server already applied to each \`notes\`:
|
|
10327
|
+
|
|
10328
|
+
- \`\u2605 Customer\` \u2014 Monitor lead with prior engagement history (\`epilogue_status\`, \`last_prospecting_action_at\`, or \`last_monitor_action_at\` set).
|
|
10329
|
+
- \`\u2605 Qualified\` \u2014 Monitor lead with a high score but no recent action.
|
|
10281
10330
|
- \`\u2726 New\` \u2014 Discover lead from \`discover_leads\`.
|
|
10282
10331
|
|
|
10283
|
-
|
|
10332
|
+
Example of a server-built \`notes\` string you pass through unchanged:
|
|
10284
10333
|
|
|
10285
10334
|
\`\`\`
|
|
10286
10335
|
\u2726 New \u2014 Strong mid-size hardware distributor fit. Reach Marie Dupont, Sales Director: +33 5 55 12 34 56, m.dupont@example.fr.
|
|
10287
10336
|
\`\`\`
|
|
10288
10337
|
|
|
10289
|
-
|
|
10338
|
+
Leads without coordinates are already omitted from \`map_locations\`. Use \`map_summary.leads_without_coords\` to footnote "+ N leads without coordinates" below the widget \u2014 no need to re-count.
|
|
10290
10339
|
|
|
10291
10340
|
## Chat prose AFTER the widget (where markdown DOES render)
|
|
10292
10341
|
|
|
@@ -10294,9 +10343,17 @@ Group the leads into THREE sections (Customers / Qualified Prospects / New Disco
|
|
|
10294
10343
|
|
|
10295
10344
|
If the user said something like "3+3+3", honor that split. If \`followups_count\` returned fewer Customers than asked, fill from Qualified.
|
|
10296
10345
|
|
|
10297
|
-
## RENDER \u2014 fallback for hosts without \`places_map_display_v0\`
|
|
10346
|
+
## RENDER \u2014 fallback for hosts without \`places_map_display_v0\` (still a map, never a prose list)
|
|
10347
|
+
|
|
10348
|
+
When the widget isn't in your tool set (e.g. Claude Desktop), you MUST still produce a map by emitting one place-card block per \`map_locations\` entry in EXACTLY this shape \u2014 the \`### Company \xB7 City, State\` heading with the address is what makes the host auto-detect it and render its own Google-Place-card carousel:
|
|
10298
10349
|
|
|
10299
|
-
|
|
10350
|
+
\`\`\`
|
|
10351
|
+
### <Company Name> \xB7 <City>, <State>
|
|
10352
|
+
|
|
10353
|
+
<\u2605 Customer | \u2605 Qualified | \u2726 New> \u2014 <one-sentence fit>. Reach **[<Contact>](<LinkedIn URL>)**, <role>. \u260E <bare phone>.
|
|
10354
|
+
\`\`\`
|
|
10355
|
+
|
|
10356
|
+
Group the blocks by mode (Customers \u2192 Qualified \u2192 New). A flat narrative paragraph ("Brooklyn Brewery \u2014 Broadway, 10018 (Midtown). Contact: \u2026") does NOT auto-detect into cards and is WRONG \u2014 the per-lead heading with the city is mandatory.
|
|
10300
10357
|
|
|
10301
10358
|
## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
|
|
10302
10359
|
|
|
@@ -10306,7 +10363,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
10306
10363
|
|
|
10307
10364
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
10308
10365
|
|---|---|---|
|
|
10309
|
-
| \`places_map_display_v0\` (Claude) |
|
|
10366
|
+
| \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
|
|
10310
10367
|
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
10311
10368
|
| \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
|
|
10312
10369
|
|
|
@@ -10325,7 +10382,7 @@ WHEN TO USE: the user signals a *mixed* tour-planning intent \u2014 they want bo
|
|
|
10325
10382
|
|
|
10326
10383
|
WHEN NOT TO USE: if the user only wants follow-ups (use \`leadbay_followups_map\`), only wants new leads (use \`leadbay_pull_leads\`), wants research on one specific account (\`leadbay_research_lead_by_id\`), or wants to persist the tour as a campaign artifact (chain into \`leadbay_create_campaign\` after this).
|
|
10327
10384
|
|
|
10328
|
-
**Response envelope**: \`{city, city_id, monitor_leads, discover_leads, discover_filter_note, _meta}\` on happy path; \`{status: "ambiguous_locations", location_ambiguities, ...}\` when the passed \`city\` matched multiple admin areas.
|
|
10385
|
+
**Response envelope**: \`{city, city_id, monitor_leads, discover_leads, discover_filter_note, map_locations, map_summary, _meta}\` on happy path; \`{status: "ambiguous_locations", location_ambiguities, ...}\` when the passed \`city\` matched multiple admin areas.
|
|
10329
10386
|
|
|
10330
10387
|
---
|
|
10331
10388
|
`;
|
|
@@ -15670,6 +15727,54 @@ function cityMatches(lead, cityHint) {
|
|
|
15670
15727
|
const haystacks = [loc.city, loc.state, loc.country, loc.full].filter((v) => typeof v === "string").map((v) => v.toLowerCase());
|
|
15671
15728
|
return haystacks.some((h) => h.includes(hint) || hint.includes(h));
|
|
15672
15729
|
}
|
|
15730
|
+
function toMapLocation(lead, mode) {
|
|
15731
|
+
const pos = lead?.location?.pos;
|
|
15732
|
+
const valid = Array.isArray(pos) && pos.length === 2 && pos.every((n) => typeof n === "number");
|
|
15733
|
+
if (!valid)
|
|
15734
|
+
return null;
|
|
15735
|
+
const loc = lead.location;
|
|
15736
|
+
const c = lead.recommended_contact;
|
|
15737
|
+
const cleanName = (v) => typeof v === "string" && v.trim() && v.trim().toLowerCase() !== "null" ? v.trim() : "";
|
|
15738
|
+
const fullName = c ? [cleanName(c.first_name), cleanName(c.last_name)].filter(Boolean).join(" ") : "";
|
|
15739
|
+
const role = cleanName(c?.job_title) ? `, ${cleanName(c?.job_title)}` : "";
|
|
15740
|
+
const angle = lead.split_ai_summary?.next_step ?? lead.split_ai_summary?.approach_angle ?? lead.short_description ?? "Worth a visit";
|
|
15741
|
+
let reach;
|
|
15742
|
+
if (c && fullName && c.phone_number) {
|
|
15743
|
+
reach = `Reach ${fullName}${role}: ${c.phone_number}${c.email ? `, ${c.email}` : ""}.`;
|
|
15744
|
+
} else if (c && fullName && c.email) {
|
|
15745
|
+
reach = `Reach ${fullName}${role}: ${c.email}.`;
|
|
15746
|
+
} else if (c && fullName) {
|
|
15747
|
+
reach = `Reach ${fullName} (enrich a channel).`;
|
|
15748
|
+
} else {
|
|
15749
|
+
reach = "Enrich a contact to reach this account.";
|
|
15750
|
+
}
|
|
15751
|
+
const notes = `${mode} \u2014 ${angle}. ${reach}`.slice(0, 280);
|
|
15752
|
+
return {
|
|
15753
|
+
name: lead.name,
|
|
15754
|
+
address: loc.full ?? [loc.city, loc.state, loc.country].filter(Boolean).join(", "),
|
|
15755
|
+
latitude: pos[0],
|
|
15756
|
+
longitude: pos[1],
|
|
15757
|
+
notes
|
|
15758
|
+
};
|
|
15759
|
+
}
|
|
15760
|
+
function hasMonitorHistory(lead) {
|
|
15761
|
+
return Boolean(lead?.epilogue_status || lead?.last_prospecting_action_at || lead?.last_monitor_action_at);
|
|
15762
|
+
}
|
|
15763
|
+
function buildMap(monitorLeads, discoverLeads2) {
|
|
15764
|
+
const mapLocations = [
|
|
15765
|
+
...monitorLeads.map((l) => toMapLocation(l, hasMonitorHistory(l) ? "\u2605 Customer" : "\u2605 Qualified")),
|
|
15766
|
+
...discoverLeads2.map((l) => toMapLocation(l, "\u2726 New"))
|
|
15767
|
+
].filter((m) => m !== null);
|
|
15768
|
+
const totalLeads = monitorLeads.length + discoverLeads2.length;
|
|
15769
|
+
return {
|
|
15770
|
+
map_locations: mapLocations,
|
|
15771
|
+
map_summary: {
|
|
15772
|
+
total_leads: totalLeads,
|
|
15773
|
+
leads_with_coords: mapLocations.length,
|
|
15774
|
+
leads_without_coords: totalLeads - mapLocations.length
|
|
15775
|
+
}
|
|
15776
|
+
};
|
|
15777
|
+
}
|
|
15673
15778
|
var tourPlan = {
|
|
15674
15779
|
name: "leadbay_tour_plan",
|
|
15675
15780
|
annotations: {
|
|
@@ -15721,6 +15826,20 @@ var tourPlan = {
|
|
|
15721
15826
|
type: "string",
|
|
15722
15827
|
description: "Human-readable summary of the client-side geo filter applied to Discover leads (e.g. 'matched 3/30 by city/state')."
|
|
15723
15828
|
},
|
|
15829
|
+
map_locations: {
|
|
15830
|
+
type: "array",
|
|
15831
|
+
description: "Pre-shaped entries for `places_map_display_v0` \u2014 pass each one verbatim ({name, address, latitude, longitude, notes}); the mode badge (\u2605 Customer / \u2605 Qualified / \u2726 New) is already in `notes`. Do NOT reshape or re-derive from `location.pos`. One entry per lead with valid coordinates; coordinate-less leads are omitted and counted in `map_summary`.",
|
|
15832
|
+
items: { type: "object" }
|
|
15833
|
+
},
|
|
15834
|
+
map_summary: {
|
|
15835
|
+
type: "object",
|
|
15836
|
+
description: "Deterministic coverage counts so the agent can footnote '+ N leads without coordinates' without re-counting.",
|
|
15837
|
+
properties: {
|
|
15838
|
+
total_leads: { type: "number" },
|
|
15839
|
+
leads_with_coords: { type: "number" },
|
|
15840
|
+
leads_without_coords: { type: "number" }
|
|
15841
|
+
}
|
|
15842
|
+
},
|
|
15724
15843
|
status: {
|
|
15725
15844
|
type: "string",
|
|
15726
15845
|
description: "'ambiguous_locations' when the passed `city` matched multiple admin areas \u2014 pick an id from location_ambiguities and re-call with city_id."
|
|
@@ -15737,7 +15856,7 @@ var tourPlan = {
|
|
|
15737
15856
|
}
|
|
15738
15857
|
}
|
|
15739
15858
|
},
|
|
15740
|
-
required: ["monitor_leads", "discover_leads"]
|
|
15859
|
+
required: ["monitor_leads", "discover_leads", "map_locations"]
|
|
15741
15860
|
},
|
|
15742
15861
|
execute: async (client, params, ctx) => {
|
|
15743
15862
|
const followupsCount = params.followups_count ?? DEFAULT_FOLLOWUPS_COUNT;
|
|
@@ -15759,6 +15878,12 @@ var tourPlan = {
|
|
|
15759
15878
|
monitor_leads: [],
|
|
15760
15879
|
discover_leads: [],
|
|
15761
15880
|
discover_filter_note: "City was ambiguous; pick an id and re-call to proceed.",
|
|
15881
|
+
map_locations: [],
|
|
15882
|
+
map_summary: {
|
|
15883
|
+
total_leads: 0,
|
|
15884
|
+
leads_with_coords: 0,
|
|
15885
|
+
leads_without_coords: 0
|
|
15886
|
+
},
|
|
15762
15887
|
city: params.city ?? null,
|
|
15763
15888
|
city_id: params.city_id ?? null,
|
|
15764
15889
|
_meta: {
|
|
@@ -15785,6 +15910,7 @@ var tourPlan = {
|
|
|
15785
15910
|
monitor_leads: monitorLeads,
|
|
15786
15911
|
discover_leads: discoverLeads2,
|
|
15787
15912
|
discover_filter_note: filterNote,
|
|
15913
|
+
...buildMap(monitorLeads, discoverLeads2),
|
|
15788
15914
|
_meta: {
|
|
15789
15915
|
region: client.region,
|
|
15790
15916
|
latency_ms: client.lastMeta?.latency_ms ?? null
|
|
@@ -23888,7 +24014,7 @@ function parseWriteEnv(env = process.env) {
|
|
|
23888
24014
|
}
|
|
23889
24015
|
|
|
23890
24016
|
// src/http-server.ts
|
|
23891
|
-
var VERSION = true ? "0.23.
|
|
24017
|
+
var VERSION = true ? "0.23.7" : "0.0.0-dev";
|
|
23892
24018
|
var PORT = Number(process.env.PORT ?? 8080);
|
|
23893
24019
|
var HOST = process.env.HOST ?? "0.0.0.0";
|
|
23894
24020
|
var sseSessions = /* @__PURE__ */ new Map();
|
package/dist/installer-gui.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leadbay/mcp",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.7",
|
|
4
4
|
"mcpName": "io.github.leadbay/leadbay-mcp",
|
|
5
5
|
"description": "Model Context Protocol (MCP) server for Leadbay — AI lead discovery, qualification, and enrichment for Claude Desktop, Cursor, and Claude Code.",
|
|
6
6
|
"type": "module",
|