@leadbay/mcp 0.23.5 → 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 +13 -0
- package/dist/bin.js +331 -65
- package/dist/http-server.js +329 -65
- package/dist/installer-electron.js +226 -71
- package/dist/installer-gui.js +225 -70
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -5804,7 +5804,7 @@ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible
|
|
|
5804
5804
|
`;
|
|
5805
5805
|
leadbay_adjust_audience = `## WHEN TO USE
|
|
5806
5806
|
|
|
5807
|
-
Trigger phrases: "narrow the audience to <sector>", "add <sector> to my <name> lens", "remove <sector> from this lens", "only show me companies of <size>", "stop including <sector>", "broaden this lens to also include <sector>".
|
|
5807
|
+
Trigger phrases: "narrow the audience to <sector>", "add <sector> to my <name> lens", "remove <sector> from this lens", "only show me companies of <size>", "stop including <sector>", "broaden this lens to also include <sector>", "restrict this lens to <city/d\xE9partement/r\xE9gion/state>", "only companies in <place>", "exclude <region> from this lens".
|
|
5808
5808
|
|
|
5809
5809
|
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
5810
5810
|
|
|
@@ -5815,7 +5815,7 @@ Prefer when: user wants to change an EXISTING lens's sectors/sizes. If the user
|
|
|
5815
5815
|
Examples that SHOULD invoke this tool:
|
|
5816
5816
|
- "Add fintech to my Joinery lens."
|
|
5817
5817
|
- "Narrow my audience to manufacturing companies, 50\u2013500 people."
|
|
5818
|
-
- "
|
|
5818
|
+
- "Restrict my rep's lens to Indre-et-Loire."
|
|
5819
5819
|
|
|
5820
5820
|
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
5821
5821
|
- "Create a lens called Joinery for fintech."
|
|
@@ -5824,9 +5824,10 @@ Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
|
5824
5824
|
|
|
5825
5825
|
## RENDER (quick)
|
|
5826
5826
|
|
|
5827
|
-
On \`applied\`: confirm the lens edited (name) + the sectors/sizes
|
|
5828
|
-
chips. On \`ambiguous_sectors\` / \`
|
|
5829
|
-
the candidates and ask the user
|
|
5827
|
+
On \`applied\`: confirm the lens edited (name) + the sectors/sizes/locations
|
|
5828
|
+
added as chips. On \`ambiguous_sectors\` / \`ambiguous_locations\` /
|
|
5829
|
+
\`ambiguous_lens\` / \`lens_not_found\`: surface the candidates and ask the user
|
|
5830
|
+
to pick, then re-call with the id/exact name.
|
|
5830
5831
|
|
|
5831
5832
|
---
|
|
5832
5833
|
|
|
@@ -5834,7 +5835,9 @@ Restrict (or expand) the lens audience by sector / size. Free-text sectors are a
|
|
|
5834
5835
|
|
|
5835
5836
|
**Targeting a lens \u2014 READ THIS.** By default this edits the user's ACTIVE lens. **If the user names a lens** ("add fintech to my **Joinery** lens", "in my Nordics lens, exclude retail"), you MUST pass \`lensName\` with that name (\`lensName:"Joinery"\`). Do NOT silently edit the active lens when a different one was named \u2014 that corrupts the wrong audience and is a top friction source. The name resolves against the user's lenses (case-insensitive, exact then unique-substring); it is edit-only and does NOT change which lens is active. An unmatched name returns \`status:"lens_not_found"\` with the lens list, and a name matching several returns \`status:"ambiguous_lens"\` with the candidates \u2014 surface them and re-call with the exact \`lensName\` or a \`lensId\`. Use \`leadbay_my_lenses\` if the user first wants to SEE or SWITCH lenses. To CREATE a brand-new lens, use \`leadbay_new_lens\` \u2014 not this tool.
|
|
5836
5837
|
|
|
5837
|
-
|
|
5838
|
+
**Geography \u2014 scope a sales territory.** Pass \`locations\` (free text like \`["Indre-et-Loire"]\`, \`["Bavaria"]\`, \`["Austin"]\`, or admin-area ids) to restrict the lens to a region, and \`exclude_locations\` to carve one out. Free text auto-resolves via \`/geo/search\` across every admin level \u2014 city, county, *d\xE9partement*, *r\xE9gion*, state, country. Place names go in \`locations\`, **never** in \`sectors\` or \`refine_prompt\`. Unresolved/ambiguous text returns \`status:"ambiguous_locations"\` with candidates \u2014 surface them and re-call the chosen id via the SAME axis it came from: an INCLUDE pick \u2192 \`location_ids\`; an EXCLUDE pick \u2192 \`exclude_locations\` (**NOT** \`location_ids\`, which would include the area the user asked to exclude). The returned \`message\` names the right param per text. This is how a director scopes a rep's territory and then asks for net-new accounts there.
|
|
5839
|
+
|
|
5840
|
+
WHEN TO USE: when the user wants to see different kinds of leads (sector / size / geography / etc.).
|
|
5838
5841
|
|
|
5839
5842
|
WHEN NOT TO USE: to refine BEYOND firmographics \u2014 that's leadbay_refine_prompt.
|
|
5840
5843
|
|
|
@@ -6114,7 +6117,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
6114
6117
|
|
|
6115
6118
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
6116
6119
|
|---|---|---|
|
|
6117
|
-
| \`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. |
|
|
6118
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") |
|
|
6119
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. |
|
|
6120
6123
|
|
|
@@ -6432,7 +6435,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
6432
6435
|
|
|
6433
6436
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
6434
6437
|
|---|---|---|
|
|
6435
|
-
| \`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. |
|
|
6436
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") |
|
|
6437
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. |
|
|
6438
6441
|
|
|
@@ -6688,7 +6691,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
6688
6691
|
|
|
6689
6692
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
6690
6693
|
|---|---|---|
|
|
6691
|
-
| \`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. |
|
|
6692
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") |
|
|
6693
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. |
|
|
6694
6697
|
|
|
@@ -7304,7 +7307,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
7304
7307
|
|
|
7305
7308
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
7306
7309
|
|---|---|---|
|
|
7307
|
-
| \`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. |
|
|
7308
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") |
|
|
7309
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. |
|
|
7310
7313
|
|
|
@@ -7382,7 +7385,7 @@ invent a tool that doesn't exist.
|
|
|
7382
7385
|
`;
|
|
7383
7386
|
leadbay_new_lens = `## WHEN TO USE
|
|
7384
7387
|
|
|
7385
|
-
Trigger phrases: "create a lens", "create a new lens called <name>", "create a lens specialized in/into <X>", "make me a new audience for <X>", "set up a lens for <sector>", "new lens named <name>", "I want a lens just for <X>".
|
|
7388
|
+
Trigger phrases: "create a lens", "create a new lens called <name>", "create a lens specialized in/into <X>", "make me a new audience for <X>", "set up a lens for <sector>", "new lens named <name>", "I want a lens just for <X>", "create a lens for net-new accounts in <place>", "a lens scoped to <territory>".
|
|
7386
7389
|
|
|
7387
7390
|
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
7388
7391
|
|
|
@@ -7393,7 +7396,7 @@ Prefer when: user wants a brand-new lens (create/make/set up, often 'specialized
|
|
|
7393
7396
|
Examples that SHOULD invoke this tool:
|
|
7394
7397
|
- "Create a lens called Joinery for the fintech sector."
|
|
7395
7398
|
- "Make me a new audience for healthcare companies, 30\u2013300 people."
|
|
7396
|
-
- "
|
|
7399
|
+
- "Create a lens for net-new accounts in Indre-et-Loire."
|
|
7397
7400
|
|
|
7398
7401
|
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
7399
7402
|
- "Add fintech to my Joinery lens."
|
|
@@ -7406,7 +7409,8 @@ On \`preview\` (default \u2014 NOTHING created yet): show the lens that WILL be
|
|
|
7406
7409
|
created (name + resolved sectors/sizes as chips) and ASK the user to confirm
|
|
7407
7410
|
via ask_user_input_v0 ("Create this lens?" / "Change something"). Only on
|
|
7408
7411
|
"yes" re-call with confirm:true. On \`created\`: confirm "Created **<name>**."
|
|
7409
|
-
On \`ambiguous_sectors\`: surface the
|
|
7412
|
+
On \`ambiguous_sectors\` / \`ambiguous_locations\`: surface the candidates to
|
|
7413
|
+
pick from.
|
|
7410
7414
|
|
|
7411
7415
|
---
|
|
7412
7416
|
|
|
@@ -7416,6 +7420,8 @@ Create a brand-new lens (saved audience) and apply its sector/size criteria. Clo
|
|
|
7416
7420
|
|
|
7417
7421
|
**Sectors resolve first.** Free-text \`sectors\`/\`exclude_sectors\` are auto-resolved against the taxonomy. If any don't resolve, the tool returns \`status:"ambiguous_sectors"\` with the candidates and **does NOT create the lens** \u2014 so re-calling after picking the right sector won't leave orphan half-built lenses. To discover valid sector labels up front, use \`leadbay_list_sectors\`.
|
|
7418
7422
|
|
|
7423
|
+
**Geography \u2014 scope a territory.** Pass \`locations\` (free text like \`["Indre-et-Loire"]\`, \`["Bavaria"]\`, or admin-area ids) to scope the lens to a sales territory, and \`exclude_locations\` to carve one out. Free text auto-resolves via \`/geo/search\` across every admin level (city / county / *d\xE9partement* / *r\xE9gion* / state / country). Like sectors, locations resolve BEFORE the lens is created \u2014 unresolved/ambiguous text returns \`status:"ambiguous_locations"\` with candidates and **does NOT create the lens**. Re-call the chosen id via the SAME axis it came from: an INCLUDE pick \u2192 \`locations\`; an EXCLUDE pick \u2192 \`exclude_locations\` (**NOT** \`locations\`, which would include the area the user asked to exclude). This is how a director spins up a lens for a rep's zone to surface net-new accounts there.
|
|
7424
|
+
|
|
7419
7425
|
**Does not switch the active lens.** The new lens is created but the user stays on their current one. Offer \`leadbay_my_lenses(switchToLensId=<new id>)\` as a next step if they want to start pulling from it.
|
|
7420
7426
|
|
|
7421
7427
|
WHEN TO USE: when the user wants a NEW lens. Canonical phrasings: "create a lens called X", "make a new audience for Y", "set up a lens for <sector>".
|
|
@@ -7433,7 +7439,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
7433
7439
|
|
|
7434
7440
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
7435
7441
|
|---|---|---|
|
|
7436
|
-
| \`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. |
|
|
7437
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") |
|
|
7438
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. |
|
|
7439
7445
|
|
|
@@ -7567,11 +7573,11 @@ email. Do NOT paste the email body into chat prose alongside.
|
|
|
7567
7573
|
|
|
7568
7574
|
---
|
|
7569
7575
|
|
|
7570
|
-
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.
|
|
7571
7577
|
|
|
7572
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\`.
|
|
7573
7579
|
|
|
7574
|
-
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.
|
|
7575
7581
|
|
|
7576
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.
|
|
7577
7583
|
|
|
@@ -7586,7 +7592,7 @@ WHEN NOT TO USE: across many leads \u2014 use leadbay_enrich_titles for bulk; fo
|
|
|
7586
7592
|
|
|
7587
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.
|
|
7588
7594
|
|
|
7589
|
-
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\`.
|
|
7590
7596
|
|
|
7591
7597
|
## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
|
|
7592
7598
|
|
|
@@ -7596,7 +7602,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
7596
7602
|
|
|
7597
7603
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
7598
7604
|
|---|---|---|
|
|
7599
|
-
| \`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. |
|
|
7600
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") |
|
|
7601
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. |
|
|
7602
7608
|
|
|
@@ -7925,7 +7931,7 @@ Trigger phrases: "show me leads", "show me new leads", "show me today's leads",
|
|
|
7925
7931
|
|
|
7926
7932
|
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
7927
7933
|
|
|
7928
|
-
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\`.
|
|
7929
7935
|
|
|
7930
7936
|
Prefer when: fresh Discover leads; if a lens is named, pass \`lensId\` and pin it
|
|
7931
7937
|
|
|
@@ -7936,6 +7942,7 @@ Examples that SHOULD invoke this tool:
|
|
|
7936
7942
|
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
7937
7943
|
- "Which leads should I follow up with this week?"
|
|
7938
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?"
|
|
7939
7946
|
- "Show me leads I should reach out to today."
|
|
7940
7947
|
- "Who should I get back to today?"
|
|
7941
7948
|
- "Leads I should contact today."
|
|
@@ -9133,7 +9140,7 @@ WHEN NOT TO USE: the user wants a lead list (leadbay_pull_leads / leadbay_pull_f
|
|
|
9133
9140
|
`;
|
|
9134
9141
|
leadbay_tour_plan = `## WHEN TO USE
|
|
9135
9142
|
|
|
9136
|
-
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".
|
|
9137
9144
|
|
|
9138
9145
|
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
9139
9146
|
|
|
@@ -9143,6 +9150,7 @@ Prefer when: user wants known accounts plus new discoveries in one geographic it
|
|
|
9143
9150
|
|
|
9144
9151
|
Examples that SHOULD invoke this tool:
|
|
9145
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?"
|
|
9146
9154
|
- "Plan my tour next Tuesday in Lyon: known accounts plus discoveries."
|
|
9147
9155
|
- "Build a mixed itinerary for Berlin \u2014 I want both follow-ups and fresh leads."
|
|
9148
9156
|
|
|
@@ -9153,16 +9161,18 @@ Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
|
9153
9161
|
|
|
9154
9162
|
## RENDER (quick)
|
|
9155
9163
|
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
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.
|
|
9163
9171
|
|
|
9164
9172
|
---
|
|
9165
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
|
+
|
|
9166
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."*
|
|
9167
9177
|
|
|
9168
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\`.
|
|
@@ -9173,21 +9183,30 @@ Build a single-call mixed-mode itinerary for a field sales tour. Combines \`lead
|
|
|
9173
9183
|
|
|
9174
9184
|
---
|
|
9175
9185
|
|
|
9176
|
-
## 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:
|
|
9191
|
+
|
|
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.
|
|
9194
|
+
|
|
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.
|
|
9177
9196
|
|
|
9178
|
-
|
|
9197
|
+
For reference, the badge the server already applied to each \`notes\`:
|
|
9179
9198
|
|
|
9180
|
-
- \`\u2605 Customer\` \u2014 Monitor lead with
|
|
9181
|
-
- \`\u2605 Qualified\` \u2014 Monitor lead with a high
|
|
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.
|
|
9182
9201
|
- \`\u2726 New\` \u2014 Discover lead from \`discover_leads\`.
|
|
9183
9202
|
|
|
9184
|
-
|
|
9203
|
+
Example of a server-built \`notes\` string you pass through unchanged:
|
|
9185
9204
|
|
|
9186
9205
|
\`\`\`
|
|
9187
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.
|
|
9188
9207
|
\`\`\`
|
|
9189
9208
|
|
|
9190
|
-
|
|
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.
|
|
9191
9210
|
|
|
9192
9211
|
## Chat prose AFTER the widget (where markdown DOES render)
|
|
9193
9212
|
|
|
@@ -9195,9 +9214,17 @@ Group the leads into THREE sections (Customers / Qualified Prospects / New Disco
|
|
|
9195
9214
|
|
|
9196
9215
|
If the user said something like "3+3+3", honor that split. If \`followups_count\` returned fewer Customers than asked, fill from Qualified.
|
|
9197
9216
|
|
|
9198
|
-
## 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)
|
|
9218
|
+
|
|
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:
|
|
9199
9220
|
|
|
9200
|
-
|
|
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.
|
|
9201
9228
|
|
|
9202
9229
|
## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
|
|
9203
9230
|
|
|
@@ -9207,7 +9234,7 @@ Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can rou
|
|
|
9207
9234
|
|
|
9208
9235
|
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
9209
9236
|
|---|---|---|
|
|
9210
|
-
| \`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. |
|
|
9211
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") |
|
|
9212
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. |
|
|
9213
9240
|
|
|
@@ -9226,7 +9253,7 @@ WHEN TO USE: the user signals a *mixed* tour-planning intent \u2014 they want bo
|
|
|
9226
9253
|
|
|
9227
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).
|
|
9228
9255
|
|
|
9229
|
-
**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.
|
|
9230
9257
|
|
|
9231
9258
|
---
|
|
9232
9259
|
`;
|
|
@@ -15051,6 +15078,54 @@ function cityMatches(lead, cityHint) {
|
|
|
15051
15078
|
const haystacks = [loc.city, loc.state, loc.country, loc.full].filter((v) => typeof v === "string").map((v) => v.toLowerCase());
|
|
15052
15079
|
return haystacks.some((h) => h.includes(hint) || hint.includes(h));
|
|
15053
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
|
+
}
|
|
15054
15129
|
var DEFAULT_FOLLOWUPS_COUNT, DEFAULT_DISCOVER_COUNT, DISCOVER_OVER_PULL, tourPlan;
|
|
15055
15130
|
var init_tour_plan = __esm({
|
|
15056
15131
|
"../core/dist/composite/tour-plan.js"() {
|
|
@@ -15112,6 +15187,20 @@ var init_tour_plan = __esm({
|
|
|
15112
15187
|
type: "string",
|
|
15113
15188
|
description: "Human-readable summary of the client-side geo filter applied to Discover leads (e.g. 'matched 3/30 by city/state')."
|
|
15114
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
|
+
},
|
|
15115
15204
|
status: {
|
|
15116
15205
|
type: "string",
|
|
15117
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."
|
|
@@ -15128,7 +15217,7 @@ var init_tour_plan = __esm({
|
|
|
15128
15217
|
}
|
|
15129
15218
|
}
|
|
15130
15219
|
},
|
|
15131
|
-
required: ["monitor_leads", "discover_leads"]
|
|
15220
|
+
required: ["monitor_leads", "discover_leads", "map_locations"]
|
|
15132
15221
|
},
|
|
15133
15222
|
execute: async (client, params, ctx) => {
|
|
15134
15223
|
const followupsCount = params.followups_count ?? DEFAULT_FOLLOWUPS_COUNT;
|
|
@@ -15150,6 +15239,12 @@ var init_tour_plan = __esm({
|
|
|
15150
15239
|
monitor_leads: [],
|
|
15151
15240
|
discover_leads: [],
|
|
15152
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
|
+
},
|
|
15153
15248
|
city: params.city ?? null,
|
|
15154
15249
|
city_id: params.city_id ?? null,
|
|
15155
15250
|
_meta: {
|
|
@@ -15176,6 +15271,7 @@ var init_tour_plan = __esm({
|
|
|
15176
15271
|
monitor_leads: monitorLeads,
|
|
15177
15272
|
discover_leads: discoverLeads2,
|
|
15178
15273
|
discover_filter_note: filterNote,
|
|
15274
|
+
...buildMap(monitorLeads, discoverLeads2),
|
|
15179
15275
|
_meta: {
|
|
15180
15276
|
region: client.region,
|
|
15181
15277
|
latency_ms: client.lastMeta?.latency_ms ?? null
|
|
@@ -20616,7 +20712,7 @@ async function resolveLensByName(client, name) {
|
|
|
20616
20712
|
return { ok: false, reason: "ambiguous", matches: partial };
|
|
20617
20713
|
return { ok: false, reason: "not_found", lenses: all };
|
|
20618
20714
|
}
|
|
20619
|
-
function mergeFilter(current, toAddSectors, toExcludeSectors, sizes) {
|
|
20715
|
+
function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocations = [], toExcludeLocations = []) {
|
|
20620
20716
|
const items = current?.lens_filter?.items ?? [];
|
|
20621
20717
|
const item = items[0] ?? { criteria: [] };
|
|
20622
20718
|
const criteria = item.criteria ? [...item.criteria] : [];
|
|
@@ -20648,6 +20744,34 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes) {
|
|
|
20648
20744
|
});
|
|
20649
20745
|
}
|
|
20650
20746
|
}
|
|
20747
|
+
if (toAddLocations.length > 0) {
|
|
20748
|
+
const idx = criteria.findIndex((c) => c.type === "location_ids" && !c.is_excluded);
|
|
20749
|
+
if (idx >= 0) {
|
|
20750
|
+
const cur = criteria[idx];
|
|
20751
|
+
const merged = Array.from(/* @__PURE__ */ new Set([...cur.locations ?? [], ...toAddLocations]));
|
|
20752
|
+
criteria[idx] = { ...cur, locations: merged };
|
|
20753
|
+
} else {
|
|
20754
|
+
criteria.push({
|
|
20755
|
+
type: "location_ids",
|
|
20756
|
+
is_excluded: false,
|
|
20757
|
+
locations: toAddLocations
|
|
20758
|
+
});
|
|
20759
|
+
}
|
|
20760
|
+
}
|
|
20761
|
+
if (toExcludeLocations.length > 0) {
|
|
20762
|
+
const idx = criteria.findIndex((c) => c.type === "location_ids" && c.is_excluded);
|
|
20763
|
+
if (idx >= 0) {
|
|
20764
|
+
const cur = criteria[idx];
|
|
20765
|
+
const merged = Array.from(/* @__PURE__ */ new Set([...cur.locations ?? [], ...toExcludeLocations]));
|
|
20766
|
+
criteria[idx] = { ...cur, locations: merged };
|
|
20767
|
+
} else {
|
|
20768
|
+
criteria.push({
|
|
20769
|
+
type: "location_ids",
|
|
20770
|
+
is_excluded: true,
|
|
20771
|
+
locations: toExcludeLocations
|
|
20772
|
+
});
|
|
20773
|
+
}
|
|
20774
|
+
}
|
|
20651
20775
|
if (sizes && sizes.length > 0) {
|
|
20652
20776
|
const normalizedSizes = sizes.map((s) => ({
|
|
20653
20777
|
min: s.min ?? 0,
|
|
@@ -20672,6 +20796,7 @@ var adjustAudience;
|
|
|
20672
20796
|
var init_adjust_audience = __esm({
|
|
20673
20797
|
"../core/dist/composite/adjust-audience.js"() {
|
|
20674
20798
|
"use strict";
|
|
20799
|
+
init_geo_helpers();
|
|
20675
20800
|
init_tool_descriptions_generated();
|
|
20676
20801
|
adjustAudience = {
|
|
20677
20802
|
name: "leadbay_adjust_audience",
|
|
@@ -20713,6 +20838,21 @@ var init_adjust_audience = __esm({
|
|
|
20713
20838
|
},
|
|
20714
20839
|
description: "Company size buckets, e.g. [{min:30,max:300}]"
|
|
20715
20840
|
},
|
|
20841
|
+
locations: {
|
|
20842
|
+
type: "array",
|
|
20843
|
+
items: { type: "string" },
|
|
20844
|
+
description: "Geographic scope \u2014 free text (e.g. ['Indre-et-Loire', 'Bavaria', 'Austin']) or admin-area ids. Auto-resolved via /geo/search across all admin levels (city / county / d\xE9partement / r\xE9gion / state / country). Place names go HERE, never in sectors/keywords."
|
|
20845
|
+
},
|
|
20846
|
+
location_ids: {
|
|
20847
|
+
type: "array",
|
|
20848
|
+
items: { type: "string" },
|
|
20849
|
+
description: "Explicit admin-area ids (skips /geo/search resolution)"
|
|
20850
|
+
},
|
|
20851
|
+
exclude_locations: {
|
|
20852
|
+
type: "array",
|
|
20853
|
+
items: { type: "string" },
|
|
20854
|
+
description: "Locations to exclude (free text or ids)"
|
|
20855
|
+
},
|
|
20716
20856
|
lensId: { type: "number", description: "Lens id (escape hatch)" },
|
|
20717
20857
|
lensName: {
|
|
20718
20858
|
type: "string",
|
|
@@ -20731,17 +20871,22 @@ var init_adjust_audience = __esm({
|
|
|
20731
20871
|
},
|
|
20732
20872
|
outputSchema: {
|
|
20733
20873
|
type: "object",
|
|
20734
|
-
description: "Return shapes: 'applied' on success; 'ambiguous_sectors' when free-text sectors matched multiple candidates (re-call with sector_ids); 'lens_not_found' / 'ambiguous_lens' when a lensName didn't resolve to exactly one lens (re-call with lensId or an exact lensName).",
|
|
20874
|
+
description: "Return shapes: 'applied' on success; 'ambiguous_sectors' when free-text sectors matched multiple candidates (re-call with sector_ids); 'ambiguous_locations' when free-text locations didn't resolve to one area \u2014 re-call with the chosen id via the SAME axis it came from (an include pick \u2192 location_ids; an EXCLUDE pick \u2192 exclude_locations, NOT location_ids, which would include it); 'lens_not_found' / 'ambiguous_lens' when a lensName didn't resolve to exactly one lens (re-call with lensId or an exact lensName).",
|
|
20735
20875
|
properties: {
|
|
20736
20876
|
status: {
|
|
20737
20877
|
type: "string",
|
|
20738
|
-
description: "'applied', 'ambiguous_sectors', 'lens_not_found', or 'ambiguous_lens'."
|
|
20878
|
+
description: "'applied', 'ambiguous_sectors', 'ambiguous_locations', 'lens_not_found', or 'ambiguous_lens'."
|
|
20739
20879
|
},
|
|
20740
20880
|
sector_ambiguities: {
|
|
20741
20881
|
type: "array",
|
|
20742
20882
|
description: "Per ambiguous text: {sector_text, matches:[{id, name, score}]}. Agent picks an id and re-calls.",
|
|
20743
20883
|
items: { type: "object" }
|
|
20744
20884
|
},
|
|
20885
|
+
location_ambiguities: {
|
|
20886
|
+
type: "array",
|
|
20887
|
+
description: "On 'ambiguous_locations': per text {location_text, matches:[{id, name, country, level, score}]}. Agent picks an id and re-calls via the SAME axis the text came from \u2014 an include text \u2192 location_ids; a text from exclude_locations \u2192 exclude_locations (NOT location_ids, which would include the area the user asked to exclude). The `message` field names the correct param per text.",
|
|
20888
|
+
items: { type: "object" }
|
|
20889
|
+
},
|
|
20745
20890
|
lenses: {
|
|
20746
20891
|
type: "array",
|
|
20747
20892
|
description: "On 'lens_not_found': the user's lenses [{id, name}] to pick from.",
|
|
@@ -20825,9 +20970,45 @@ var init_adjust_audience = __esm({
|
|
|
20825
20970
|
message: parts.join(" ")
|
|
20826
20971
|
};
|
|
20827
20972
|
}
|
|
20973
|
+
const includeLocTexts = [
|
|
20974
|
+
...params.locations ?? [],
|
|
20975
|
+
...params.location_ids ?? []
|
|
20976
|
+
];
|
|
20977
|
+
const excludeLocTexts = params.exclude_locations ?? [];
|
|
20978
|
+
const includeLocRes = await resolveLocations(client, includeLocTexts);
|
|
20979
|
+
const excludeLocRes = await resolveLocations(client, excludeLocTexts);
|
|
20980
|
+
const locAmbiguities = [
|
|
20981
|
+
...includeLocRes.ambiguities,
|
|
20982
|
+
...excludeLocRes.ambiguities
|
|
20983
|
+
];
|
|
20984
|
+
if (locAmbiguities.length > 0) {
|
|
20985
|
+
const incMatch = includeLocRes.ambiguities.filter((a) => a.matches.length > 0);
|
|
20986
|
+
const incNone = includeLocRes.ambiguities.filter((a) => a.matches.length === 0);
|
|
20987
|
+
const excMatch = excludeLocRes.ambiguities.filter((a) => a.matches.length > 0);
|
|
20988
|
+
const excNone = excludeLocRes.ambiguities.filter((a) => a.matches.length === 0);
|
|
20989
|
+
const quote = (as) => as.map((a) => `"${a.location_text}"`).join(", ");
|
|
20990
|
+
const parts = [];
|
|
20991
|
+
if (incNone.length > 0) {
|
|
20992
|
+
parts.push(`Couldn't find a location matching ${quote(incNone)}. Ask the user to rephrase, then re-call with location_ids=...`);
|
|
20993
|
+
}
|
|
20994
|
+
if (incMatch.length > 0) {
|
|
20995
|
+
parts.push(`${quote(incMatch)} matched multiple areas. Pick from the matches and re-call with location_ids=...`);
|
|
20996
|
+
}
|
|
20997
|
+
if (excNone.length > 0) {
|
|
20998
|
+
parts.push(`Couldn't find a location to exclude matching ${quote(excNone)}. Ask the user to rephrase, then re-call with exclude_locations=... (the chosen id).`);
|
|
20999
|
+
}
|
|
21000
|
+
if (excMatch.length > 0) {
|
|
21001
|
+
parts.push(`${quote(excMatch)} (to exclude) matched multiple areas. Pick from the matches and re-call with exclude_locations=... (the chosen id) \u2014 NOT location_ids, which would include it.`);
|
|
21002
|
+
}
|
|
21003
|
+
return {
|
|
21004
|
+
status: "ambiguous_locations",
|
|
21005
|
+
location_ambiguities: locAmbiguities,
|
|
21006
|
+
message: parts.join(" ")
|
|
21007
|
+
};
|
|
21008
|
+
}
|
|
20828
21009
|
const lens = await client.request("GET", `/lenses/${startingLensId}`);
|
|
20829
21010
|
const currentFilter = await client.request("GET", `/lenses/${startingLensId}/filter`);
|
|
20830
|
-
const merged = mergeFilter(currentFilter, includeRes.resolved, excludeRes.resolved, params.sizes);
|
|
21011
|
+
const merged = mergeFilter(currentFilter, includeRes.resolved, excludeRes.resolved, params.sizes, includeLocRes.resolved, excludeLocRes.resolved);
|
|
20831
21012
|
const mergedBody = filterWriteBody(merged);
|
|
20832
21013
|
const isDefault = lens.is_default || lens.default;
|
|
20833
21014
|
const isUserLevel = lens.user_id != null;
|
|
@@ -21548,6 +21729,7 @@ var init_new_lens = __esm({
|
|
|
21548
21729
|
"../core/dist/composite/new-lens.js"() {
|
|
21549
21730
|
"use strict";
|
|
21550
21731
|
init_adjust_audience();
|
|
21732
|
+
init_geo_helpers();
|
|
21551
21733
|
init_tool_descriptions_generated();
|
|
21552
21734
|
EMPTY_FILTER = {
|
|
21553
21735
|
lens_filter: { items: [{ criteria: [] }] },
|
|
@@ -21586,6 +21768,16 @@ var init_new_lens = __esm({
|
|
|
21586
21768
|
},
|
|
21587
21769
|
description: "Company size buckets, e.g. [{min:30,max:300}]."
|
|
21588
21770
|
},
|
|
21771
|
+
locations: {
|
|
21772
|
+
type: "array",
|
|
21773
|
+
items: { type: "string" },
|
|
21774
|
+
description: "Geographic scope \u2014 free text (e.g. ['Indre-et-Loire', 'Bavaria']) or admin-area ids. Auto-resolved via /geo/search across all admin levels (city / county / d\xE9partement / r\xE9gion / state / country). Scopes the lens to a sales territory."
|
|
21775
|
+
},
|
|
21776
|
+
exclude_locations: {
|
|
21777
|
+
type: "array",
|
|
21778
|
+
items: { type: "string" },
|
|
21779
|
+
description: "Locations to exclude \u2014 free text or ids."
|
|
21780
|
+
},
|
|
21589
21781
|
base: {
|
|
21590
21782
|
type: "number",
|
|
21591
21783
|
description: "Lens id to clone from. Defaults to the active/default lens."
|
|
@@ -21601,12 +21793,12 @@ var init_new_lens = __esm({
|
|
|
21601
21793
|
},
|
|
21602
21794
|
outputSchema: {
|
|
21603
21795
|
type: "object",
|
|
21604
|
-
description: "'preview' (default, NOTHING created \u2014 confirm with the user then re-call with confirm:true); 'created' on success; 'ambiguous_sectors' when free-text sectors didn't resolve (re-call with
|
|
21796
|
+
description: "'preview' (default, NOTHING created \u2014 confirm with the user then re-call with confirm:true); 'created' on success; 'ambiguous_sectors' / 'ambiguous_locations' when free-text sectors / locations didn't resolve (re-call with ids \u2014 the lens was NOT created).",
|
|
21605
21797
|
properties: {
|
|
21606
|
-
status: { type: "string", description: "'preview', 'created', 'ambiguous_sectors', or 'orphan_created' (filter write failed + cleanup failed)." },
|
|
21798
|
+
status: { type: "string", description: "'preview', 'created', 'ambiguous_sectors', 'ambiguous_locations', or 'orphan_created' (filter write failed + cleanup failed)." },
|
|
21607
21799
|
will_create: {
|
|
21608
21800
|
type: "object",
|
|
21609
|
-
description: "On 'preview': what WILL be created \u2014 {name, description, sectors, exclude_sectors, sizes}. Nothing has been written yet."
|
|
21801
|
+
description: "On 'preview': what WILL be created \u2014 {name, description, sectors, exclude_sectors, sizes, locations, exclude_locations}. Nothing has been written yet."
|
|
21610
21802
|
},
|
|
21611
21803
|
filter_preview: { type: "object", description: "On 'preview': the FilterPayload that would be applied." },
|
|
21612
21804
|
lens: {
|
|
@@ -21618,6 +21810,11 @@ var init_new_lens = __esm({
|
|
|
21618
21810
|
description: "On 'ambiguous_sectors': per text {sector_text, matches:[{id,name,score}]}.",
|
|
21619
21811
|
items: { type: "object" }
|
|
21620
21812
|
},
|
|
21813
|
+
location_ambiguities: {
|
|
21814
|
+
type: "array",
|
|
21815
|
+
description: "On 'ambiguous_locations': per text {location_text, matches:[{id,name,country,level,score}]}. Re-call the chosen id via the SAME axis the text came from \u2014 an include text \u2192 locations; a text from exclude_locations \u2192 exclude_locations (NOT locations, which would include the area the user asked to exclude). The `message` field names the correct param per text.",
|
|
21816
|
+
items: { type: "object" }
|
|
21817
|
+
},
|
|
21621
21818
|
filter_applied: { type: "object", description: "On 'created': the FilterPayload POSTed to the new lens." },
|
|
21622
21819
|
message: { type: "string" },
|
|
21623
21820
|
_meta: { type: "object" }
|
|
@@ -21644,7 +21841,38 @@ var init_new_lens = __esm({
|
|
|
21644
21841
|
message: parts.join(" ")
|
|
21645
21842
|
};
|
|
21646
21843
|
}
|
|
21647
|
-
const
|
|
21844
|
+
const includeLocRes = await resolveLocations(client, params.locations ?? []);
|
|
21845
|
+
const excludeLocRes = await resolveLocations(client, params.exclude_locations ?? []);
|
|
21846
|
+
const locAmbiguities = [
|
|
21847
|
+
...includeLocRes.ambiguities,
|
|
21848
|
+
...excludeLocRes.ambiguities
|
|
21849
|
+
];
|
|
21850
|
+
if (locAmbiguities.length > 0) {
|
|
21851
|
+
const incMatch = includeLocRes.ambiguities.filter((a) => a.matches.length > 0);
|
|
21852
|
+
const incNone = includeLocRes.ambiguities.filter((a) => a.matches.length === 0);
|
|
21853
|
+
const excMatch = excludeLocRes.ambiguities.filter((a) => a.matches.length > 0);
|
|
21854
|
+
const excNone = excludeLocRes.ambiguities.filter((a) => a.matches.length === 0);
|
|
21855
|
+
const quote = (as) => as.map((a) => `"${a.location_text}"`).join(", ");
|
|
21856
|
+
const parts = [];
|
|
21857
|
+
if (incNone.length > 0) {
|
|
21858
|
+
parts.push(`Couldn't find a location matching ${quote(incNone)}. Pick a known area and re-call via locations (lens not yet created).`);
|
|
21859
|
+
}
|
|
21860
|
+
if (incMatch.length > 0) {
|
|
21861
|
+
parts.push(`${quote(incMatch)} matched multiple areas. Pick from the matches and re-call with the chosen id in locations.`);
|
|
21862
|
+
}
|
|
21863
|
+
if (excNone.length > 0) {
|
|
21864
|
+
parts.push(`Couldn't find a location to exclude matching ${quote(excNone)}. Pick a known area and re-call via exclude_locations (lens not yet created).`);
|
|
21865
|
+
}
|
|
21866
|
+
if (excMatch.length > 0) {
|
|
21867
|
+
parts.push(`${quote(excMatch)} (to exclude) matched multiple areas. Pick from the matches and re-call with the chosen id in exclude_locations \u2014 NOT locations, which would include it.`);
|
|
21868
|
+
}
|
|
21869
|
+
return {
|
|
21870
|
+
status: "ambiguous_locations",
|
|
21871
|
+
location_ambiguities: locAmbiguities,
|
|
21872
|
+
message: parts.join(" ")
|
|
21873
|
+
};
|
|
21874
|
+
}
|
|
21875
|
+
const merged = mergeFilter(EMPTY_FILTER, includeRes.resolved, excludeRes.resolved, params.sizes, includeLocRes.resolved, excludeLocRes.resolved);
|
|
21648
21876
|
if (params.confirm !== true) {
|
|
21649
21877
|
return {
|
|
21650
21878
|
status: "preview",
|
|
@@ -21653,7 +21881,9 @@ var init_new_lens = __esm({
|
|
|
21653
21881
|
description: params.description ?? null,
|
|
21654
21882
|
sectors: includeRes.resolved,
|
|
21655
21883
|
exclude_sectors: excludeRes.resolved,
|
|
21656
|
-
sizes: merged.lens_filter.items[0].criteria.find((c) => c.type === "size") ?? null
|
|
21884
|
+
sizes: merged.lens_filter.items[0].criteria.find((c) => c.type === "size") ?? null,
|
|
21885
|
+
locations: includeLocRes.resolved,
|
|
21886
|
+
exclude_locations: excludeLocRes.resolved
|
|
21657
21887
|
},
|
|
21658
21888
|
filter_preview: merged,
|
|
21659
21889
|
message: `About to create "${params.name}". Confirm with the user, then re-call with confirm:true.`,
|
|
@@ -23526,44 +23756,80 @@ GATE \u2014 DEFER TO TOOL RENDERING. When you call a Leadbay composite that ship
|
|
|
23526
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.
|
|
23527
23757
|
|
|
23528
23758
|
|
|
23529
|
-
# 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.
|
|
23530
23767
|
|
|
23531
|
-
|
|
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.
|
|
23532
23773
|
|
|
23533
|
-
|
|
23774
|
+
# PHASE 2 \u2014 BUILD THE ITINERARY
|
|
23534
23775
|
|
|
23535
|
-
|
|
23536
|
-
|
|
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\`.
|
|
23777
|
+
|
|
23778
|
+
Split the returned \`monitor_leads\` into two buckets client-side using their engagement-history fields:
|
|
23779
|
+
|
|
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).
|
|
23537
23782
|
|
|
23538
23783
|
\`discover_leads\` are the **New** bucket.
|
|
23539
23784
|
|
|
23540
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.
|
|
23541
23786
|
|
|
23542
|
-
# PHASE
|
|
23787
|
+
# PHASE 3 \u2014 PRESENT THE ITINERARY + OFFER THE MAP
|
|
23788
|
+
|
|
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.
|
|
23790
|
+
|
|
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)
|
|
23543
23798
|
|
|
23544
|
-
|
|
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:
|
|
23545
23800
|
|
|
23546
|
-
|
|
23547
|
-
|
|
23548
|
-
|
|
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
|
+
\`\`\`
|
|
23549
23815
|
|
|
23550
|
-
|
|
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.
|
|
23551
23817
|
|
|
23552
|
-
|
|
23818
|
+
Coordinate-less leads are already omitted from \`map_locations\`; footnote them with \`map_summary.leads_without_coords\` ("+ N leads without coordinates").
|
|
23553
23819
|
|
|
23554
|
-
# PHASE
|
|
23820
|
+
# PHASE 5 \u2014 DRAFT IN-AREA OUTREACH (optional, ask first)
|
|
23555
23821
|
|
|
23556
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).
|
|
23557
23823
|
|
|
23558
23824
|
Serialize the prepare_outreach calls (max 3 in parallel \u2014 see the long-running-tools rule).
|
|
23559
23825
|
|
|
23560
|
-
# PHASE
|
|
23826
|
+
# PHASE 6 \u2014 PERSIST AS A CAMPAIGN (optional, ask first)
|
|
23561
23827
|
|
|
23562
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\`).
|
|
23563
23829
|
|
|
23564
23830
|
If I declined the campaign step, end the turn \u2014 the map + drafts are enough for an ad-hoc trip.
|
|
23565
23831
|
|
|
23566
|
-
# PHASE
|
|
23832
|
+
# PHASE 7 \u2014 STOP
|
|
23567
23833
|
|
|
23568
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.
|
|
23569
23835
|
`;
|
|
@@ -24162,7 +24428,7 @@ var PROMPT_META = {
|
|
|
24162
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)'] },
|
|
24163
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"] },
|
|
24164
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"] },
|
|
24165
|
-
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"] },
|
|
24166
24432
|
leadbay_prospecting_overview: { "name": "leadbay_prospecting_overview", "short_description": `Orientation for working with Leadbay from any host \u2014 discovery vs.
|
|
24167
24433
|
follow-up, the outreach loop, outcome recording, imports, pushback /
|
|
24168
24434
|
snooze, and the connected-outreach-tool registry. Trigger when the
|
|
@@ -24184,7 +24450,7 @@ var PROMPT_CATALOG_BULLETS = {
|
|
|
24184
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\`.`,
|
|
24185
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.`,
|
|
24186
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.`,
|
|
24187
|
-
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.`,
|
|
24188
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".`,
|
|
24189
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.`,
|
|
24190
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.`,
|
|
@@ -27451,7 +27717,7 @@ var OAUTH_BASE_URLS = {
|
|
|
27451
27717
|
fr: "https://staging.api.leadbay.app"
|
|
27452
27718
|
}
|
|
27453
27719
|
};
|
|
27454
|
-
var VERSION = "0.23.
|
|
27720
|
+
var VERSION = "0.23.7";
|
|
27455
27721
|
var HELP = `
|
|
27456
27722
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
27457
27723
|
|