@leadbay/mcp 0.28.0 → 0.29.0
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/dist/bin.js +935 -2
- package/dist/http-server.js +923 -1
- package/dist/installer-electron.js +1 -1
- package/dist/installer-gui.js +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -5307,6 +5307,7 @@ var init_composite_file_names = __esm({
|
|
|
5307
5307
|
"leadbay_followups_map",
|
|
5308
5308
|
"leadbay_get_lead_custom_fields",
|
|
5309
5309
|
"leadbay_get_qualification_questions",
|
|
5310
|
+
"leadbay_getting_started",
|
|
5310
5311
|
"leadbay_import_and_qualify",
|
|
5311
5312
|
"leadbay_import_leads",
|
|
5312
5313
|
"leadbay_import_status",
|
|
@@ -5634,7 +5635,7 @@ var init_notifications = __esm({
|
|
|
5634
5635
|
});
|
|
5635
5636
|
|
|
5636
5637
|
// ../core/dist/tool-descriptions.generated.js
|
|
5637
|
-
var leadbay_account_history, leadbay_account_status, leadbay_acknowledge_notification, leadbay_add_contact, leadbay_add_leads_to_campaign, leadbay_add_note, leadbay_adjust_audience, leadbay_agent_memory_capture, leadbay_agent_memory_recall, leadbay_agent_memory_review, leadbay_answer_clarification, leadbay_artifact_kit, leadbay_bulk_enrich_status, leadbay_bulk_qualify_leads, leadbay_campaign_call_sheet, leadbay_campaign_progression, leadbay_clear_selection, leadbay_clear_user_prompt, leadbay_create_campaign, leadbay_create_custom_field, leadbay_create_lens, leadbay_create_lens_draft, leadbay_create_topup_link, leadbay_delete_custom_field, leadbay_deselect_leads, leadbay_discover_leads, leadbay_dislike_lead, leadbay_dismiss_clarification, leadbay_enrich_contacts, leadbay_enrich_titles, leadbay_extend_lens, leadbay_followups_map, leadbay_get_clarification, leadbay_get_contacts, leadbay_get_enrichment_job_titles, leadbay_get_epilogue_responses, leadbay_get_lead_activities, leadbay_get_lead_custom_fields, leadbay_get_lead_notes, leadbay_get_lead_profile, leadbay_get_lens_filter, leadbay_get_lens_scoring, leadbay_get_prospecting_actions, leadbay_get_qualification_questions, leadbay_get_quota, leadbay_get_selection_ids, leadbay_get_taste_profile, leadbay_get_user_prompt, leadbay_get_web_fetch, leadbay_import_and_qualify, leadbay_import_leads, leadbay_import_status, leadbay_launch_bulk_enrichment, leadbay_like_lead, leadbay_list_campaigns, leadbay_list_lenses, leadbay_list_locations, leadbay_list_mappable_fields, leadbay_list_sectors, leadbay_login, leadbay_my_lenses, leadbay_new_lens, leadbay_open_billing_portal, leadbay_pick_clarification, leadbay_pin_contact, leadbay_prepare_outreach, leadbay_preview_bulk_enrichment, leadbay_promote_lens, leadbay_pull_followups, leadbay_pull_leads, leadbay_qualify_lead, leadbay_qualify_status, leadbay_recall_ordered_titles, leadbay_refine_prompt, leadbay_remove_contact, leadbay_remove_epilogue, leadbay_remove_leads_from_campaign, leadbay_remove_pushback, leadbay_report_friction, leadbay_report_outreach, leadbay_research_lead_by_id, leadbay_research_lead_by_name_fuzzy, leadbay_resolve_import_rows, leadbay_scan_portfolio_signals, leadbay_seed_candidates, leadbay_select_leads, leadbay_send_feedback, leadbay_set_active_lens, leadbay_set_epilogue_status, leadbay_set_pushback, leadbay_set_qualification_questions, leadbay_set_telemetry, leadbay_set_user_prompt, leadbay_team_activity, leadbay_tour_plan, leadbay_unpin_contact, leadbay_update_contact, leadbay_update_custom_field, leadbay_update_lens, leadbay_update_lens_filter;
|
|
5638
|
+
var leadbay_account_history, leadbay_account_status, leadbay_acknowledge_notification, leadbay_add_contact, leadbay_add_leads_to_campaign, leadbay_add_note, leadbay_adjust_audience, leadbay_agent_memory_capture, leadbay_agent_memory_recall, leadbay_agent_memory_review, leadbay_answer_clarification, leadbay_artifact_kit, leadbay_bulk_enrich_status, leadbay_bulk_qualify_leads, leadbay_campaign_call_sheet, leadbay_campaign_progression, leadbay_clear_selection, leadbay_clear_user_prompt, leadbay_create_campaign, leadbay_create_custom_field, leadbay_create_lens, leadbay_create_lens_draft, leadbay_create_topup_link, leadbay_delete_custom_field, leadbay_deselect_leads, leadbay_discover_leads, leadbay_dislike_lead, leadbay_dismiss_clarification, leadbay_enrich_contacts, leadbay_enrich_titles, leadbay_extend_lens, leadbay_followups_map, leadbay_get_clarification, leadbay_get_contacts, leadbay_get_enrichment_job_titles, leadbay_get_epilogue_responses, leadbay_get_lead_activities, leadbay_get_lead_custom_fields, leadbay_get_lead_notes, leadbay_get_lead_profile, leadbay_get_lens_filter, leadbay_get_lens_scoring, leadbay_get_prospecting_actions, leadbay_get_qualification_questions, leadbay_get_quota, leadbay_get_selection_ids, leadbay_get_taste_profile, leadbay_get_user_prompt, leadbay_get_web_fetch, leadbay_getting_started, leadbay_import_and_qualify, leadbay_import_leads, leadbay_import_status, leadbay_launch_bulk_enrichment, leadbay_like_lead, leadbay_list_campaigns, leadbay_list_lenses, leadbay_list_locations, leadbay_list_mappable_fields, leadbay_list_sectors, leadbay_login, leadbay_my_lenses, leadbay_new_lens, leadbay_open_billing_portal, leadbay_pick_clarification, leadbay_pin_contact, leadbay_prepare_outreach, leadbay_preview_bulk_enrichment, leadbay_promote_lens, leadbay_pull_followups, leadbay_pull_leads, leadbay_qualify_lead, leadbay_qualify_status, leadbay_recall_ordered_titles, leadbay_refine_prompt, leadbay_remove_contact, leadbay_remove_epilogue, leadbay_remove_leads_from_campaign, leadbay_remove_pushback, leadbay_report_friction, leadbay_report_outreach, leadbay_research_lead_by_id, leadbay_research_lead_by_name_fuzzy, leadbay_resolve_import_rows, leadbay_scan_portfolio_signals, leadbay_seed_candidates, leadbay_select_leads, leadbay_send_feedback, leadbay_set_active_lens, leadbay_set_epilogue_status, leadbay_set_pushback, leadbay_set_qualification_questions, leadbay_set_telemetry, leadbay_set_user_prompt, leadbay_team_activity, leadbay_tour_plan, leadbay_unpin_contact, leadbay_update_contact, leadbay_update_custom_field, leadbay_update_lens, leadbay_update_lens_filter;
|
|
5638
5639
|
var init_tool_descriptions_generated = __esm({
|
|
5639
5640
|
"../core/dist/tool-descriptions.generated.js"() {
|
|
5640
5641
|
"use strict";
|
|
@@ -7201,6 +7202,107 @@ WHEN NOT TO USE: to set/change the prompt \u2014 use leadbay_refine_prompt.
|
|
|
7201
7202
|
WHEN TO USE: when the agent already qualified this lead and wants the underlying research to reason from.
|
|
7202
7203
|
|
|
7203
7204
|
WHEN NOT TO USE: as the first read on a lead \u2014 the leadbay_research_lead_by_id composite bundles this with qualification answers and reshapes the dict into a stable array form.
|
|
7205
|
+
`;
|
|
7206
|
+
leadbay_getting_started = `## WHEN TO USE
|
|
7207
|
+
|
|
7208
|
+
Trigger phrases: "walk me through leadbay", "I'm new", "how do I use this", "getting started", "show me how this works", "give me a tour", "help me get started", "I just installed this".
|
|
7209
|
+
|
|
7210
|
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
7211
|
+
|
|
7212
|
+
Do NOT use for: "show me today's leads" \u2192 \`leadbay_pull_leads\`; "which audiences do I have" \u2192 \`leadbay_my_lenses\`; "where am I / what's my plan and quota" \u2192 \`leadbay_account_status\`.
|
|
7213
|
+
|
|
7214
|
+
Prefer when: the user has never used Leadbay, or asks to be SHOWN rather than told \u2014 the walkthrough runs real calls on their own account
|
|
7215
|
+
|
|
7216
|
+
Examples that SHOULD invoke this tool:
|
|
7217
|
+
- "Walk me through Leadbay."
|
|
7218
|
+
- "I'm new here \u2014 how do I use this?"
|
|
7219
|
+
- "Can you show me how this works?"
|
|
7220
|
+
|
|
7221
|
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
7222
|
+
- "Show me today's leads."
|
|
7223
|
+
- "Explain the difference between discovery and follow-up."
|
|
7224
|
+
- "Which of my lenses is active right now?"
|
|
7225
|
+
|
|
7226
|
+
## RENDER (quick)
|
|
7227
|
+
|
|
7228
|
+
Not a data table. Run the walkthrough ONE gate at a time: fire your host's
|
|
7229
|
+
choice widget with that step's forward option + exit, wait for the click, make that
|
|
7230
|
+
step's tool call, then advance. Never dump all four steps at once, and never
|
|
7231
|
+
render a gate as a prose question.
|
|
7232
|
+
|
|
7233
|
+
---
|
|
7234
|
+
|
|
7235
|
+
Returns the **guided first-run walkthrough** \u2014 a short script the agent drives so a brand-new user learns Leadbay by *doing*, not by reading. Makes no backend call and mutates nothing; the content is static and version-locked.
|
|
7236
|
+
|
|
7237
|
+
Every click in the walkthrough runs a real Leadbay call against the user's own account. By the end they have confirmed which account they're on, pulled today's leads, had a first email drafted to the best of them, and revealed the person to send it to. Every gate calls a real Leadbay tool \u2014 the tour ends where Leadbay's own value ends.
|
|
7238
|
+
|
|
7239
|
+
For orientation **prose** with no clicking \u2014 "explain how Leadbay works", "what's the difference between discovery and follow-up" \u2014 this tool is the wrong answer; that's the \`leadbay_prospecting_overview\` prompt.
|
|
7240
|
+
|
|
7241
|
+
And when the problem is **setup** rather than usage \u2014 the connector isn't installed, they can't sign in, their Leadbay tools aren't appearing, or they want to run this on another host \u2014 the walkthrough can't help either: it assumes a working connection, and step 1 is what proves it. Send them to the setup guide the manifest carries as \`docs_url\`: <https://docs.leadbay.app/doc/leadbay-mcp/quickstart>. Its \`docs_note\` names the only two moments the link should appear \u2014 that pre-check, and once at the closing. Never between gates.
|
|
7242
|
+
|
|
7243
|
+
## THE ONE-FORWARD-OPTION RULE
|
|
7244
|
+
|
|
7245
|
+
Every gate carries **exactly one way forward, plus a way out** \u2014 two options, never more: the action, and \`I'm done for now\`.
|
|
7246
|
+
|
|
7247
|
+
A first-run user doesn't yet know enough to choose between PATHS \u2014 a menu of alternatives makes them stall. One forward move makes the next step obvious, and the click is what teaches the tool. The exit keeps the tour from being a trap and satisfies the host widget's 2\u20134 option requirement: a lone option is rejected or silently degrades to prose. Never add a third option, and never turn the exit into an alternative route. Typing works too: if they type something off-script, abandon the walkthrough and serve what they asked.
|
|
7248
|
+
|
|
7249
|
+
## What it returns
|
|
7250
|
+
|
|
7251
|
+
\`\`\`
|
|
7252
|
+
{
|
|
7253
|
+
version, intro, one_option_rule, docs_url, docs_note,
|
|
7254
|
+
calendly_url, exit_offer,
|
|
7255
|
+
steps: [ { n, gate_label, gate_description, calls, args, ... } ],
|
|
7256
|
+
keep_going, stop
|
|
7257
|
+
}
|
|
7258
|
+
\`\`\`
|
|
7259
|
+
|
|
7260
|
+
Per step: \`gate_label\` / \`gate_description\` are the widget's forward option, \`calls\` is the tool to invoke on click (or \`null\`), and \`args\` is the literal argument shape. Render each \`gate_label\` verbatim \u2014 don't reword them.
|
|
7261
|
+
|
|
7262
|
+
| Step | Gate | Calls |
|
|
7263
|
+
|---|---|---|
|
|
7264
|
+
| 1 | Check my account | \`leadbay_account_status\` (no args) |
|
|
7265
|
+
| 2 | Pull today's leads | \`leadbay_pull_leads\` (no args) |
|
|
7266
|
+
| 3 | Draft the first email | \`leadbay_prepare_outreach\` \u2014 \`leadId\` ONLY, never \`enrich\` |
|
|
7267
|
+
| 4 | Find who to email | \`leadbay_enrich_titles\` \u2014 free preview, then a consented paid reveal |
|
|
7268
|
+
|
|
7269
|
+
Steps 1, 2 and 3 carry \`branches[]\`, and steps 3 and 4 carry \`spend\` (+ \`quota_note\` on 4). Every step also carries \`explain\` (say this BEFORE firing) and \`next_steps\` (\`{question, options[]}\` \u2014 already the widget's shape, map it verbatim).
|
|
7270
|
+
|
|
7271
|
+
When the user picks \`I'm done for now\`, don't just go quiet \u2014 **\`exit_offer\`** says what to do: one short line offering a 1:1 with Zoe (lens tuning, CRM wiring, automating the daily run) plus **\`calendly_url\`**, then stop. One sentence and the link, never a pitch, never a re-opened gate. Only on the EXIT click: if they left by *typing* a different request, skip it and serve what they asked.
|
|
7272
|
+
|
|
7273
|
+
The manifest also carries **\`keep_going\`**: the closing cheat-sheet of *what you want \u2192 what you say*. The buttons vanish when the tour ends, so render these rows as a small two-column table at the finish, phrases **verbatim**. Each one is lifted from that tool's own trigger list, so it genuinely routes \u2014 inventing or prettifying a phrase teaches the user something that won't work. Add \`docs_url\` beneath it as one plain link, for what the gates didn't cover.
|
|
7274
|
+
|
|
7275
|
+
## Three hard rules the manifest encodes
|
|
7276
|
+
|
|
7277
|
+
**Step 1 shows the real account, and is silent about two things.** The click is labelled *check my account status*, so deliver it: user + org, then the **full quota windows** the way the web app renders them \u2014 Daily / Weekly / Monthly with a \`\u25B0\u25B1\` gauge, % used, $ spent against the cap, resets countdown, and the per-resource breakdown. Never raw "credits". But apply the silence gate first: when \`quota\` is null, \`quota_error\` is set, or the org has \`unlimited_credits\`, say **nothing** about quota \u2014 never mention a 401, never suggest logging in again (the token is fine, the same response just read their account), and never announce "unlimited". And **never volunteer the lens**: the response withholds it unless the user asked, so there is nothing to report and no other tool to reach for. Both are pinned regressions (WORKFLOWS #30 / #31).
|
|
7278
|
+
|
|
7279
|
+
**Step 3 drafts, and spends nothing.** Call \`leadbay_prepare_outreach\` with \`leadId\` alone \u2014 **never \`enrich: true\`**, which launches a paid contact reveal off the back of a *draft* click. \`recommended_contact\` returns with \`email\`/\`phone\` null; that is expected, and it is the hook for step 4. Render through \`message_compose_v1\` (2\u20133 strategy-labelled variants), address it to the job TITLE \u2014 no name exists yet, and inventing one is fabrication \u2014 and never send it or offer to.
|
|
7280
|
+
|
|
7281
|
+
**Step 4 runs in two beats \u2014 free first, paid only on consent.** Scoped to the ONE lead step 3 drafted for. Beat 1 omits \`titles\` and returns \`mode:"discover"\`, the free list of job titles at that company; say plainly that nothing has been spent. Beat 2 names the title the draft is addressed to, states the cost BEFORE they decide (one contact, one credit), and only on confirmation calls again with \`titles\` + \`confirm:true\` + \`email:true\` \u2014 polled via \`leadbay_bulk_enrich_status\` until done, reporting only what actually resolved. The gate click bought the free look, not the reveal: never launch without an explicit confirm.
|
|
7282
|
+
|
|
7283
|
+
## Empty first batch is normal, not an error
|
|
7284
|
+
|
|
7285
|
+
A brand-new lens reads empty for the first minute while the backend computes its wishlist. When \`leadbay_pull_leads\` returns no leads but \`computing_wishlist\` / \`computing_scores\` is true, the lens is warming up: render that tool's own two-option warm-up payload verbatim and pause. **Never report "no leads found"** in that state.
|
|
7286
|
+
|
|
7287
|
+
## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
|
|
7288
|
+
|
|
7289
|
+
Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can route into. These ALWAYS produce a better UX than markdown tables / inline prose for the data shapes they support \u2014 they're tappable on mobile, persistent across turns, and integrate with the host's quick-actions.
|
|
7290
|
+
|
|
7291
|
+
**The Big Three** \u2014 when a tool result fits, route there:
|
|
7292
|
+
|
|
7293
|
+
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
7294
|
+
|---|---|---|
|
|
7295
|
+
| \`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. |
|
|
7296
|
+
| \`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") |
|
|
7297
|
+
| \`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. |
|
|
7298
|
+
|
|
7299
|
+
ChatGPT exposes the same routing pattern via \`_meta.openai/outputTemplate\`. We don't ship any custom widgets ourselves \u2014 this gate is exclusively about routing into the host's first-party widgets when the data shape fits.
|
|
7300
|
+
|
|
7301
|
+
**Rules:**
|
|
7302
|
+
- The widget IS the visual. Do NOT emit a markdown table or prose list of the same data alongside \u2014 that produces two competing UIs.
|
|
7303
|
+
- Pass identifiers (place_id, lead.id, contact_id) verbatim. Don't rewrite.
|
|
7304
|
+
- When the host doesn't expose the named widget, the agent falls back to the prose/table rendering the per-tool description already specifies. The directive is host-conditional; the fallback is automatic.
|
|
7305
|
+
- One short intro sentence in chat is enough \u2014 "Here are your 5 NYC follow-ups." Then route into the widget.
|
|
7204
7306
|
`;
|
|
7205
7307
|
leadbay_import_and_qualify = `Import + qualify leads in one call. Pass either \`domains: [{domain, name?}]\` (Mode A) OR \`records[]\` with \`mappings\` (Mode B). At least one mapped field must be LEADBAY_ID, CRM_ID, SIREN, LEAD_NAME, or LEAD_WEBSITE. Discover the org's mappable surface via \`leadbay_list_mappable_fields\`. For messy files, prefer the \`leadbay_import_file\` prompt which walks an agent through scan \u2192 resolve \u2192 preserve \u2192 commit phases.
|
|
7206
7308
|
|
|
@@ -17281,6 +17383,204 @@ var init_get_qualification_questions = __esm({
|
|
|
17281
17383
|
}
|
|
17282
17384
|
});
|
|
17283
17385
|
|
|
17386
|
+
// ../core/dist/composite/getting-started.js
|
|
17387
|
+
var ONE_OPTION_RULE, DOCS_QUICKSTART, DOCS_NOTE, ZOE_CALENDLY, EXIT_OFFER, EXIT_OPTION, INTRO, KEEP_GOING, STOP, GETTING_STARTED_MANIFEST, gettingStarted;
|
|
17388
|
+
var init_getting_started = __esm({
|
|
17389
|
+
"../core/dist/composite/getting-started.js"() {
|
|
17390
|
+
"use strict";
|
|
17391
|
+
init_tool_descriptions_generated();
|
|
17392
|
+
ONE_OPTION_RULE = "Every gate presents exactly ONE way forward, plus a way out \u2014 two options, never more: the action, and 'I'm done for now'. A first-run user does not yet know enough to choose between PATHS; one forward move makes the next step obvious, and the click is what teaches them the tool. The exit keeps the tour from being a trap, and satisfies the host widget's 2-4 option requirement \u2014 a lone option is rejected or silently degrades to prose, which kills the feature. Never add a third option, and never turn the exit into an alternative route ('show me my lenses instead'), which reintroduces the choice this rule removes. The gate IS the widget: never render it as a prose question \u2014 'say the word and I'll check it' is a defect, not a gate. Typing also works: if the user types something off-script, abandon the walkthrough and serve what they asked.";
|
|
17393
|
+
DOCS_QUICKSTART = "https://docs.leadbay.app/doc/leadbay-mcp/quickstart";
|
|
17394
|
+
DOCS_NOTE = "Surface this link in exactly TWO moments and nowhere else. (1) BEFORE the tour, when the user's problem is SETUP rather than usage \u2014 the connector isn't installed, they can't sign in, their Leadbay tools aren't appearing, or they want to run this on another host. The walkthrough cannot fix any of that: it assumes a working connection, and gate 1 is what proves it. Point them at the page instead of guessing at install steps. (2) At the CLOSING, as one plain link beside the keep_going cheat-sheet, for what the four gates didn't cover \u2014 installing on another machine, adding a teammate, signing in again later. NEVER paste it between gates: a link mid-tour is an invitation to leave the thing they're in the middle of doing.";
|
|
17395
|
+
ZOE_CALENDLY = "https://calendly.com/zoe-leadbay/demo-leadbay";
|
|
17396
|
+
EXIT_OFFER = "Picking 'I'm done for now' is ENDING B, and it has THREE beats in order: (1) one short line acknowledging the stop, (2) the keep_going cheat-sheet and the docs_url link, (3) the 1:1 offer \u2014 LAST, and REQUIRED. Beat 3 is the one that gets dropped: an agent that renders the cheat-sheet feels finished and stops, so the user who just stepped out never hears about the help that would have brought them back. An exit close WITHOUT the offer is incomplete. ONE SENTENCE and calendly_url, e.g. 'If you want a hand tuning this to your own market, Zoe on our team runs 1:1 sessions: <url>'. That length is the rule, not a suggestion: anything longer reads as a pitch. Do NOT enumerate everything Zoe could help with \u2014 that turns an offer into promotional copy, which is exactly what someone who just said they were done does not want. Never re-open the walkthrough, never re-fire the declined gate, and never argue for finishing the tour. If they instead left by TYPING something off-script that is ENDING C, not B \u2014 serve what they asked and skip the cheat-sheet, the link AND the offer.";
|
|
17397
|
+
EXIT_OPTION = {
|
|
17398
|
+
label: "I'm done for now",
|
|
17399
|
+
description: "Stop the walkthrough here.",
|
|
17400
|
+
kind: "walkthrough_exit"
|
|
17401
|
+
};
|
|
17402
|
+
INTRO = "Open with a SHORT paragraph \u2014 3-4 sentences, then the widget, all in your first message. Cover, in the user's own language and without jargon: (1) what Leadbay is \u2014 it brings you a fresh batch of companies worth selling to every day, rather than you hunting for them; (2) how it knows what to send \u2014 you describe who you sell to (that description is your LENS) and it goes and finds companies matching it, learning from what you engage with; (3) what this walkthrough will do \u2014 four quick steps, each one a real action on their own account, ending with leads in hand, a first email already written, and the person to send it to; (4) one line handing off to the first step, e.g. 'First, let's see which account you're on.' Then fire gate 1's widget immediately and stop. Keep it to a paragraph \u2014 do NOT walk through the four steps one at a time here (each gate explains itself when its turn arrives), and call no tool in the opening.";
|
|
17403
|
+
KEEP_GOING = [
|
|
17404
|
+
{ want: "Today's fresh leads", say: "Show me today's leads" },
|
|
17405
|
+
{ want: "Who to follow up with", say: "What should I follow up on" },
|
|
17406
|
+
{ want: "The story on one company", say: "Research <Company>" },
|
|
17407
|
+
{ want: "An email to a contact", say: "Draft outreach for <Contact>" },
|
|
17408
|
+
{ want: "Change who you target", say: "Narrow the audience to <sector>" },
|
|
17409
|
+
{ want: "Switch target audience", say: "Show me my lenses" }
|
|
17410
|
+
];
|
|
17411
|
+
STOP = "The walkthrough DRAFTS an email at gate 3 but never SENDS one. The draft stays in the chat for the user to read and judge; nothing leaves. Never send it, never offer to send it on their behalf, and never call leadbay_report_outreach \u2014 logging an outreach that never happened poisons the human team's pipeline. End by waiting for the user.";
|
|
17412
|
+
GETTING_STARTED_MANIFEST = {
|
|
17413
|
+
version: 1,
|
|
17414
|
+
intro: INTRO,
|
|
17415
|
+
one_option_rule: ONE_OPTION_RULE,
|
|
17416
|
+
docs_url: DOCS_QUICKSTART,
|
|
17417
|
+
docs_note: DOCS_NOTE,
|
|
17418
|
+
calendly_url: ZOE_CALENDLY,
|
|
17419
|
+
exit_offer: EXIT_OFFER,
|
|
17420
|
+
steps: [
|
|
17421
|
+
{
|
|
17422
|
+
n: 1,
|
|
17423
|
+
gate_label: "Check my account",
|
|
17424
|
+
gate_description: "Check my Leadbay account status.",
|
|
17425
|
+
explain: "The opening paragraph IS this gate's explanation \u2014 do not add another one. Just hand off in a line ('First, let's see which account you're on') and fire the widget in the SAME message. WHY IT'S USEFUL, if you say anything at all: this is where they can see at a glance how much they've used this week and what's left, so a batch that comes back small later has a visible reason. On click, the ANSWER is the account itself: user + org, then the full quota windows (see branches).",
|
|
17426
|
+
next_steps: {
|
|
17427
|
+
question: "Let's start with your account status.",
|
|
17428
|
+
options: [
|
|
17429
|
+
{
|
|
17430
|
+
label: "Check my account",
|
|
17431
|
+
description: "Check my Leadbay account status.",
|
|
17432
|
+
kind: "walkthrough_account_status"
|
|
17433
|
+
},
|
|
17434
|
+
EXIT_OPTION
|
|
17435
|
+
]
|
|
17436
|
+
},
|
|
17437
|
+
calls: "leadbay_account_status",
|
|
17438
|
+
args: {},
|
|
17439
|
+
branches: [
|
|
17440
|
+
{
|
|
17441
|
+
when: "quota is readable",
|
|
17442
|
+
then: "Show them their ACTUAL account \u2014 this is the payoff of the click. One line on who they're signed in as and their organization, then render the quota windows in full the way the web app does: Daily / Weekly / Monthly, each with a \u25B0\u25B1 gauge, % used, $ spent against the cap, and when it resets, plus the per-resource breakdown underneath. Follow the canonical quota-windows rendering (never raw 'credits'). A one-line 'you're connected as X' under-delivers on a button labelled 'check my account status'. THEN EXPLAIN IT in one or two plain lines \u2014 a first-run user has never seen these numbers and can't tell if they're good or bad: say what it counts (the AI work Leadbay does for them \u2014 researching companies and qualifying leads, not something they spend by clicking around) and why it matters (it paces how many fresh leads arrive; heavy use now means a bigger batch queued for next time, and it's where a smaller-than-expected batch would show its reason). Keep it to a sentence or two, don't walk through every resource row, and don't turn it into a pricing pitch."
|
|
17443
|
+
},
|
|
17444
|
+
{
|
|
17445
|
+
when: "quota is null, quota_error is set, or organization.unlimited_credits is true",
|
|
17446
|
+
then: "Say NOTHING about quota \u2014 no gauge, no 'unreadable', no 'unlimited', and skip the quota EXPLANATION too (there is nothing on screen to explain, and describing an absent gauge just confuses). A brand-new org often has no billing plan yet, so the quota read fails; that is not an error worth showing. Do not mention a 401, and above all do NOT tell the user to log in again or reconnect: their token is fine, the same response just read their account. Fall back to the short user + org line and move on. (WORKFLOWS #30.)"
|
|
17447
|
+
},
|
|
17448
|
+
{
|
|
17449
|
+
when: "always",
|
|
17450
|
+
then: "Do NOT volunteer the lens. The response deliberately withholds it unless the user asked, so there is nothing to report, and no other tool should be called to find it. The lens appears naturally at gate 2. (WORKFLOWS #31.)"
|
|
17451
|
+
}
|
|
17452
|
+
]
|
|
17453
|
+
},
|
|
17454
|
+
{
|
|
17455
|
+
n: 2,
|
|
17456
|
+
gate_label: "Pull today's leads",
|
|
17457
|
+
gate_description: "Pull today's leads from your lens.",
|
|
17458
|
+
explain: "Explain the LENS before firing: Leadbay keeps a lens \u2014 the description of who they sell to \u2014 and every day it finds fresh companies matching it. This click pulls today's batch. WHY IT'S USEFUL: it replaces the hour spent digging through directories and LinkedIn for someone worth calling \u2014 the list is already waiting, scored, when they sit down. And it gets sharper: the leads they like, contact or skip teach the lens what a good fit looks like, so tomorrow's batch is closer than today's.",
|
|
17459
|
+
next_steps: {
|
|
17460
|
+
question: "Now let's see today's leads. Ready?",
|
|
17461
|
+
options: [
|
|
17462
|
+
{
|
|
17463
|
+
label: "Pull today's leads",
|
|
17464
|
+
description: "Pull today's leads from your lens.",
|
|
17465
|
+
kind: "walkthrough_pull_leads"
|
|
17466
|
+
},
|
|
17467
|
+
EXIT_OPTION
|
|
17468
|
+
]
|
|
17469
|
+
},
|
|
17470
|
+
calls: "leadbay_pull_leads",
|
|
17471
|
+
args: {},
|
|
17472
|
+
pin: "lens.id \u2014 pass as an explicit lensId on every later step, so step 4 enriches the same lens the user just saw. Also pin the TOP-SCORING lead's id and name: gate 3 drafts to it, and gate 4 reveals its contact",
|
|
17473
|
+
branches: [
|
|
17474
|
+
{
|
|
17475
|
+
when: "leads.length > 0",
|
|
17476
|
+
then: "Render the canonical pull_leads table, then advance to gate 3."
|
|
17477
|
+
},
|
|
17478
|
+
{
|
|
17479
|
+
when: "leads.length === 0 && (computing_wishlist || computing_scores)",
|
|
17480
|
+
then: "The lens is still building \u2014 normal on a new account. Say so in the user's terms, then render the tool's own next_steps payload VERBATIM (it carries two options: 'Re-pull in ~30s' / 'Refine audience'). This is the ONE place a gate carries two options, because the server built the payload. On re-pull, wait ~30s and return to gate 2. NEVER say 'no leads found'."
|
|
17481
|
+
},
|
|
17482
|
+
{
|
|
17483
|
+
when: "leads.length === 0 && !computing_wishlist && !computing_scores",
|
|
17484
|
+
then: "The lens is genuinely empty or too narrow and next_steps is null. Say so honestly, offer to widen the audience, and end the walkthrough \u2014 there is nothing to enrich."
|
|
17485
|
+
}
|
|
17486
|
+
]
|
|
17487
|
+
},
|
|
17488
|
+
{
|
|
17489
|
+
n: 3,
|
|
17490
|
+
gate_label: "Draft the first email",
|
|
17491
|
+
gate_description: "Write a first email to the best company in today's batch.",
|
|
17492
|
+
explain: "Name the TOP-SCORING lead from gate 2 out loud, so the offer is about a real company and not an abstraction. Explain what's about to happen: Leadbay already worked out WHY this company fits them, so it can write the first email instead of leaving them at a blank page. WHY IT'S USEFUL: finding companies was never the hard part \u2014 writing the twentieth opener of the day is where prospecting actually dies. This turns a row in a table into something they could send in a minute. Say plainly that it only DRAFTS: nothing is sent, and they see it first.",
|
|
17493
|
+
next_steps: {
|
|
17494
|
+
question: "Want me to draft the first email to your top lead?",
|
|
17495
|
+
options: [
|
|
17496
|
+
{
|
|
17497
|
+
label: "Draft the first email",
|
|
17498
|
+
description: "Write a first email to the best company in today's batch. Nothing is sent.",
|
|
17499
|
+
kind: "walkthrough_draft_outreach"
|
|
17500
|
+
},
|
|
17501
|
+
EXIT_OPTION
|
|
17502
|
+
]
|
|
17503
|
+
},
|
|
17504
|
+
calls: "leadbay_prepare_outreach",
|
|
17505
|
+
args: {
|
|
17506
|
+
leadId: "<the highest-scoring lead id from step 2>"
|
|
17507
|
+
},
|
|
17508
|
+
forbidden_args: [
|
|
17509
|
+
"enrich \u2014 enrich:true launches a PAID contact reveal off the back of a DRAFT click. They agreed to see an email written, not to spend. Gate 4 is where the reveal gets asked for, explicitly and on its own terms."
|
|
17510
|
+
],
|
|
17511
|
+
spend: "This gate spends NOTHING. Call leadbay_prepare_outreach with leadId and nothing else. `recommended_contact` comes back in its post-enrichment shape with email and phone still null \u2014 that is EXPECTED, not a failure, and it is precisely the hook for gate 4: an email written, and nobody to send it to yet. Do not apologise for the missing contact, and do not reach for another tool to fill it in.",
|
|
17512
|
+
branches: [
|
|
17513
|
+
{
|
|
17514
|
+
when: "always",
|
|
17515
|
+
then: "Render the draft through message_compose_v1 \u2014 kind:'email', a summary_title naming the company, and 2-3 variants whose labels name the STRATEGY ('Lead with the growth signal', 'Ask about their current setup'), never the tone. Do NOT also paste the body into chat prose; the composer IS the answer. Address it to the recommended contact's JOB TITLE ('the Head of Operations at <Company>') \u2014 you do not have a name yet, and inventing one is fabrication. Say in one line what made this company the pick: its score and the fit reason from the lead's summary, so the draft reads as reasoned rather than generated."
|
|
17516
|
+
},
|
|
17517
|
+
{
|
|
17518
|
+
when: "the host exposes no message_compose_v1",
|
|
17519
|
+
then: "Fall back to the canonical prepare-outreach rendering: one short context line, then the subject and body as a quoted block. Same content, same no-name rule."
|
|
17520
|
+
}
|
|
17521
|
+
]
|
|
17522
|
+
},
|
|
17523
|
+
{
|
|
17524
|
+
n: 4,
|
|
17525
|
+
gate_label: "Find who to email",
|
|
17526
|
+
gate_description: "Reveal the person at that company to send the draft to.",
|
|
17527
|
+
explain: "Point straight at the gap the draft just opened: they have an email ready and nobody to send it to \u2014 it's addressed to a job title, not a person. That's what this step fixes. Explain what enrichment IS: Leadbay can find which roles exist at that company, then reveal the actual human and how to reach them. WHY IT'S USEFUL: they ask for the operations director by name instead of pitching whoever answers the switchboard \u2014 the difference between a conversation and a dead end. Say plainly that the first look is free, and that revealing the contact costs credits and needs their say-so.",
|
|
17528
|
+
next_steps: {
|
|
17529
|
+
question: "Want to find out who to send that email to?",
|
|
17530
|
+
options: [
|
|
17531
|
+
{
|
|
17532
|
+
label: "Find who to email",
|
|
17533
|
+
description: "See the roles at that company. Free \u2014 no contact details revealed yet.",
|
|
17534
|
+
kind: "walkthrough_enrich_titles"
|
|
17535
|
+
},
|
|
17536
|
+
EXIT_OPTION
|
|
17537
|
+
]
|
|
17538
|
+
},
|
|
17539
|
+
calls: "leadbay_enrich_titles",
|
|
17540
|
+
branches: [
|
|
17541
|
+
{
|
|
17542
|
+
when: "leadbay_enrich_titles is NOT in your tool set",
|
|
17543
|
+
then: "This is a read-only deployment (LEADBAY_MCP_WRITE=0) \u2014 the reveal tool simply is not registered. Do NOT fire this gate's widget, and do not hunt for another way to get contact details. Close the tour after gate 3 instead: say plainly that revealing contacts isn't enabled on this connection, that the draft they just watched being written is still theirs, and go to the closing. A gate whose tool cannot run is a dead end, and offering the button anyway is worse than ending one step early."
|
|
17544
|
+
}
|
|
17545
|
+
],
|
|
17546
|
+
args: {
|
|
17547
|
+
leadIds: "[<the ONE lead you drafted for at step 3>] \u2014 an ARRAY, always",
|
|
17548
|
+
lensId: "<the pinned lens id from step 2>"
|
|
17549
|
+
},
|
|
17550
|
+
spend: "TWO BEATS \u2014 free preview FIRST, the real reveal only after the user confirms. Beat 1: call leadbay_enrich_titles with the drafted lead's id + lensId and NO titles / NO confirm / NO email / NO phone. That returns mode:'discover' \u2014 the FREE list of job titles at that company. Say plainly that nothing has been spent yet. Beat 2: name the title the draft is addressed to, tell them BEFORE they decide what it costs (one credit per contact revealed \u2014 here that is ONE contact, one credit), and ask them to confirm. Only then call leadbay_enrich_titles AGAIN with leadIds: [<that lead id>] \u2014 ALWAYS the array, even for a single lead: `leadId` singular is not a key this tool reads, so it is dropped and the paid call falls back to the default wishlist selection, charging for the whole batch \u2014 plus the chosen title, confirm:true and email:true. Poll leadbay_bulk_enrich_status with the returned bulk_id until all_done (or the count plateaus), and report the contact that actually resolved. NEVER launch the reveal without an explicit confirm: silence is not consent, and neither is 'they clicked the gate'. If they decline, keep the draft and the title and move on \u2014 that is a normal outcome, not a failure.",
|
|
17551
|
+
quota_note: "After the reveal, close the loop on gate 1 in one line: one credit per contact revealed, so this cost one. Then say the thing that makes it land \u2014 the draft from gate 3 now has a real person and a real address to go to. Re-check leadbay_account_status if you want to show the moved windows. This is where gate 1's numbers stop being abstract: they just watched them move, and got something for it. Keep it to a line; no pricing pitch."
|
|
17552
|
+
}
|
|
17553
|
+
],
|
|
17554
|
+
keep_going: KEEP_GOING,
|
|
17555
|
+
stop: STOP
|
|
17556
|
+
};
|
|
17557
|
+
gettingStarted = {
|
|
17558
|
+
name: "leadbay_getting_started",
|
|
17559
|
+
annotations: {
|
|
17560
|
+
title: "Guided Leadbay walkthrough",
|
|
17561
|
+
readOnlyHint: true,
|
|
17562
|
+
destructiveHint: false,
|
|
17563
|
+
idempotentHint: true,
|
|
17564
|
+
openWorldHint: false
|
|
17565
|
+
},
|
|
17566
|
+
description: leadbay_getting_started,
|
|
17567
|
+
write: false,
|
|
17568
|
+
inputSchema: {
|
|
17569
|
+
type: "object",
|
|
17570
|
+
properties: {},
|
|
17571
|
+
additionalProperties: false
|
|
17572
|
+
},
|
|
17573
|
+
// No outputSchema by design — same trade-off as leadbay_artifact_kit:
|
|
17574
|
+
// declaring one enrolls the tool in the output-schema-conformance
|
|
17575
|
+
// drift-catcher (an existing test file we don't modify). The server still
|
|
17576
|
+
// emits the plain-object return as structuredContent.
|
|
17577
|
+
execute: async (_client, _params, _ctx) => {
|
|
17578
|
+
return structuredClone(GETTING_STARTED_MANIFEST);
|
|
17579
|
+
}
|
|
17580
|
+
};
|
|
17581
|
+
}
|
|
17582
|
+
});
|
|
17583
|
+
|
|
17284
17584
|
// ../core/dist/composite/set-qualification-questions.js
|
|
17285
17585
|
var setQualificationQuestions;
|
|
17286
17586
|
var init_set_qualification_questions = __esm({
|
|
@@ -23597,6 +23897,7 @@ __export(dist_exports, {
|
|
|
23597
23897
|
AgentMemorySourceSchema: () => AgentMemorySourceSchema,
|
|
23598
23898
|
AgentMemoryTombstoneSchema: () => AgentMemoryTombstoneSchema,
|
|
23599
23899
|
COMPOSITE_FILE_TOOL_NAMES: () => COMPOSITE_FILE_TOOL_NAMES,
|
|
23900
|
+
GETTING_STARTED_MANIFEST: () => GETTING_STARTED_MANIFEST,
|
|
23600
23901
|
InMemoryBulkStore: () => InMemoryBulkStore,
|
|
23601
23902
|
LeadbayClient: () => LeadbayClient,
|
|
23602
23903
|
LocalBulkStore: () => LocalBulkStore,
|
|
@@ -23669,6 +23970,7 @@ __export(dist_exports, {
|
|
|
23669
23970
|
getTasteProfile: () => getTasteProfile,
|
|
23670
23971
|
getUserPrompt: () => getUserPrompt,
|
|
23671
23972
|
getWebFetch: () => getWebFetch,
|
|
23973
|
+
gettingStarted: () => gettingStarted,
|
|
23672
23974
|
granularReadTools: () => granularReadTools,
|
|
23673
23975
|
granularTools: () => granularTools,
|
|
23674
23976
|
granularWriteTools: () => granularWriteTools,
|
|
@@ -23820,6 +24122,7 @@ var init_dist = __esm({
|
|
|
23820
24122
|
init_research_lead_by_id();
|
|
23821
24123
|
init_research_lead_by_name_fuzzy();
|
|
23822
24124
|
init_get_qualification_questions();
|
|
24125
|
+
init_getting_started();
|
|
23823
24126
|
init_set_qualification_questions();
|
|
23824
24127
|
init_get_lead_custom_fields();
|
|
23825
24128
|
init_account_history();
|
|
@@ -23847,6 +24150,7 @@ var init_dist = __esm({
|
|
|
23847
24150
|
init_send_feedback();
|
|
23848
24151
|
init_artifact_kit();
|
|
23849
24152
|
init_bulk_store();
|
|
24153
|
+
init_getting_started();
|
|
23850
24154
|
agentMemoryTools = [
|
|
23851
24155
|
agentMemoryRecall,
|
|
23852
24156
|
agentMemoryCapture,
|
|
@@ -23925,6 +24229,16 @@ var init_dist = __esm({
|
|
|
23925
24229
|
// is a first-session question, and the underlying get_taste_profile is
|
|
23926
24230
|
// ADVANCED-gated. Read-only; no MCP edit endpoint exists (issue #3768).
|
|
23927
24231
|
getQualificationQuestions,
|
|
24232
|
+
// Guided first-run walkthrough (issue #3952). ALWAYS exposed, read-only:
|
|
24233
|
+
// returns the six-gate script a brand-new user clicks through to learn
|
|
24234
|
+
// Leadbay by doing (check account → pull leads → draft the first email →
|
|
24235
|
+
// reveal who to send it to → CRM → schedule it).
|
|
24236
|
+
// Makes no backend call. In compositeReadTools so the tour is reachable on a
|
|
24237
|
+
// read-only (LEADBAY_MCP_WRITE=0) deployment — where gate 4's
|
|
24238
|
+
// leadbay_enrich_titles is NOT registered (it is write-gated), so the
|
|
24239
|
+
// manifest's gate-4 branch ends the tour after gate 3 rather than offering a
|
|
24240
|
+
// button whose tool cannot run.
|
|
24241
|
+
gettingStarted,
|
|
23928
24242
|
// Per-lead custom-field VALUES. ALWAYS exposed: complements the always-on
|
|
23929
24243
|
// list_mappable_fields (which returns DEFINITIONS only). The lead payload
|
|
23930
24244
|
// embeds each field's definition, so no catalog join is needed (issue #3768).
|
|
@@ -24491,6 +24805,606 @@ Render this acknowledgment VERBATIM as the last line of your message:
|
|
|
24491
24805
|
STOP \u2014 awaiting user decision. I will not take any further action until you tell me what to do next.
|
|
24492
24806
|
\`\`\`
|
|
24493
24807
|
|
|
24808
|
+
Do not propose a next action. Do not call any more tools. Hand control back to the user.
|
|
24809
|
+
`;
|
|
24810
|
+
var leadbay_getting_started2 = `
|
|
24811
|
+
## MEMORY
|
|
24812
|
+
|
|
24813
|
+
Before responding, glance at any \`_meta.agent_memory.summary\` returned by tool calls earlier in this session and reflect its top signals in your reasoning ("Filtering by your stated preference for healthcare"). After any material new signal from the user this conversation (sector, region, deal size, communication style, qualification rule, explicit retraction, or recurrence / scheduling preference such as "I do this every day" or "remind me every morning"), call \`leadbay_agent_memory_capture\` to persist it: \`source:"user_stated"\` if literal, \`source:"inferred"\` with confidence <=6 if inferred.
|
|
24814
|
+
|
|
24815
|
+
|
|
24816
|
+
Walk me through Leadbay. Treat these the same way: "I'm new here", "how do I
|
|
24817
|
+
use this?", "getting started", "show me how Leadbay works", "give me a tour",
|
|
24818
|
+
"I just installed this".
|
|
24819
|
+
|
|
24820
|
+
This is a GUIDED WALKTHROUGH, not an explainer. The user learns by clicking,
|
|
24821
|
+
and every click runs a real Leadbay call against their own account. By the end
|
|
24822
|
+
they will have actually checked their account, pulled leads, had a first email
|
|
24823
|
+
drafted to the best of them, and revealed the person to send it to.
|
|
24824
|
+
|
|
24825
|
+
If the user wants orientation PROSE without doing anything \u2014 "explain how
|
|
24826
|
+
Leadbay works", "what's the difference between discovery and follow-up" \u2014
|
|
24827
|
+
this is the wrong prompt. Use \`leadbay_prospecting_overview\` instead.
|
|
24828
|
+
|
|
24829
|
+
If their problem is **setup** rather than usage \u2014 the connector isn't installed
|
|
24830
|
+
yet, they can't sign in, their Leadbay tools aren't appearing, or they're asking
|
|
24831
|
+
how to run this on another host \u2014 this walkthrough cannot help them. It assumes
|
|
24832
|
+
a working connection, and GATE 1 is what proves it. Point them at the setup
|
|
24833
|
+
guide instead of guessing at install steps:
|
|
24834
|
+
<https://docs.leadbay.app/doc/leadbay-mcp/quickstart>
|
|
24835
|
+
|
|
24836
|
+
GATE \u2014 DEFER TO TOOL RENDERING. When you call a Leadbay composite that ships its own RENDERING block (every composite in 0.9.0+ does), render the response using that block's recipe verbatim \u2014 score bars, glyph palette, column order, hide-list, link priorities, all of it. Do NOT substitute prose, a numbered list, or a different column structure even when an orchestrating prompt's body suggests alternate framing. Prompt-specific commentary (motivational nudges, summaries, next-action recommendations) belongs ABOVE or BELOW the canonical table, never in place of it.
|
|
24837
|
+
|
|
24838
|
+
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.
|
|
24839
|
+
|
|
24840
|
+
|
|
24841
|
+
# Resilience rules for Leadbay long-running tools
|
|
24842
|
+
|
|
24843
|
+
These four rules apply to every Leadbay workflow that calls \`leadbay_pull_leads\`, \`leadbay_bulk_qualify_leads\`, \`leadbay_research_lead_by_id\`, \`leadbay_import_and_qualify\`, or \`leadbay_enrich_titles\`. **Treat timeouts and stream-closed errors as transient, not as signals to replan.**
|
|
24844
|
+
|
|
24845
|
+
## Rule 1 \u2014 Pin the lens
|
|
24846
|
+
|
|
24847
|
+
After your first \`leadbay_pull_leads\` call, capture \`response.lens.id\` into your working memory and **pass it explicitly as the \`lensId\` argument to every subsequent call** in this session \u2014 including any re-pulls, bulk qualifies, or research calls that accept it. (Field-name caveat: the response nests it as \`lens.id\`; the parameter on subsequent calls is \`lensId\`.) The active lens can shift between calls (5-minute client cache + backend \`last_requested_lens\` can change if the user touches the web UI). A lens shift mid-workflow throws away your top-10 work.
|
|
24848
|
+
|
|
24849
|
+
## Rule 2 \u2014 Prefer async for bulk operations
|
|
24850
|
+
|
|
24851
|
+
\`leadbay_bulk_qualify_leads\` and \`leadbay_import_and_qualify\` accept \`wait_for_completion:false\`, which returns \`{status:'running', qualify_id}\` immediately. Then poll \`leadbay_qualify_status\` (or \`leadbay_import_status\`) every ~10s until the job completes. **Use the async pattern by default** \u2014 the blocking default can exceed the MCP client's per-call timeout on large batches and produce a misleading \`"Request timed out"\` even though the server is still working.
|
|
24852
|
+
|
|
24853
|
+
## Rule 3 \u2014 Serialize \`leadbay_research_lead_by_id\` fan-out
|
|
24854
|
+
|
|
24855
|
+
\`leadbay_research_lead_by_id\` is composite and reads many sub-resources. Calling it on 10 leads in parallel can saturate the transport and produce \`"Tool permission stream closed"\` errors that look like permission failures but are really backpressure. **Call it sequentially**, or at most 3 in parallel. If one call fails with a stream/timeout error, retry that one call once before moving on; on a second failure, note the lead and continue \u2014 do not abandon the remaining leads.
|
|
24856
|
+
|
|
24857
|
+
## Rule 4 \u2014 Retry, don't replan
|
|
24858
|
+
|
|
24859
|
+
If a Leadbay tool returns \`"Request timed out"\`, \`"stream closed"\`, or any other transport-level error (distinct from a Leadbay-issued error payload), the work may still be running server-side. Do this in order:
|
|
24860
|
+
|
|
24861
|
+
1. For bulk tools \u2014 retry with \`wait_for_completion:false\` and poll the status tool with the returned id. Don't re-pull leads; that can shift the lens.
|
|
24862
|
+
2. For single-lead tools \u2014 retry the same call once. If it still fails, record the lead id and continue with the rest of the workflow.
|
|
24863
|
+
3. **Do not** switch strategies (e.g. "the endpoint is broken, let me re-pull from scratch"). The earlier work is still valid; the timeout was the wire.
|
|
24864
|
+
|
|
24865
|
+
If \`pull_leads\` itself fails and you have no prior batch, then yes \u2014 retry it, explicitly pass the lensId you captured (if any), and continue.
|
|
24866
|
+
|
|
24867
|
+
|
|
24868
|
+
# THE ONE-FORWARD-OPTION RULE \u2014 the structural contract of this walkthrough
|
|
24869
|
+
|
|
24870
|
+
Every gate presents **exactly ONE way forward, plus a way out**. Two options,
|
|
24871
|
+
never more:
|
|
24872
|
+
|
|
24873
|
+
1. **The action** \u2014 the single next step of the tour.
|
|
24874
|
+
2. **The exit** \u2014 \`I'm done for now\`, which ends the walkthrough politely.
|
|
24875
|
+
|
|
24876
|
+
This is deliberate. A first-run user does not yet know enough to choose between
|
|
24877
|
+
*paths* \u2014 a menu of alternatives makes them stall. One forward move makes the
|
|
24878
|
+
next step obvious, and the click is what teaches them the tool. The exit exists
|
|
24879
|
+
so the tour is never a trap, and because your host's choice widget requires 2\u20134
|
|
24880
|
+
options: a lone option is rejected or silently degrades to prose, which kills
|
|
24881
|
+
the whole feature.
|
|
24882
|
+
|
|
24883
|
+
**Never add a third option**, and never turn the exit into an alternative route
|
|
24884
|
+
("show me my lenses instead") \u2014 that reintroduces the choice this rule exists
|
|
24885
|
+
to remove.
|
|
24886
|
+
|
|
24887
|
+
**The gate IS the widget.** Call your host's choice widget with these two
|
|
24888
|
+
options. **Never render a gate as a prose question** \u2014 "say the word and I'll
|
|
24889
|
+
check it" is a defect, not a gate: the user gets no button and the walkthrough
|
|
24890
|
+
becomes a conversation they have to drive themselves.
|
|
24891
|
+
|
|
24892
|
+
**EVERY GATE IS TWO BEATS \u2014 EXPLAIN, THEN ASK.** This is a tutorial, so the
|
|
24893
|
+
user must understand what they're about to do *before* they click:
|
|
24894
|
+
|
|
24895
|
+
1. **Explain** \u2014 one or two plain sentences saying what this step does and why
|
|
24896
|
+
it matters. Never jargon. This is the teaching half; skipping it turns the
|
|
24897
|
+
walkthrough into a series of unexplained buttons.
|
|
24898
|
+
2. **Ask** \u2014 fire the widget. **Then STOP and wait for the click.**
|
|
24899
|
+
|
|
24900
|
+
**NEVER run a step's tool without firing its widget first and receiving the
|
|
24901
|
+
user's click.** Calling \`leadbay_pull_leads\` because the walkthrough "obviously
|
|
24902
|
+
goes there next" defeats the entire feature \u2014 the click IS the lesson. The one
|
|
24903
|
+
exception is when the user's own message already told you to do it (e.g. "walk
|
|
24904
|
+
me through it and just run everything"); then follow what they asked.
|
|
24905
|
+
|
|
24906
|
+
**Each gate ships its own widget payload \u2014 use it, don't rewrite it.** Every
|
|
24907
|
+
step in the manifest carries \`explain\` (what to say) and \`next_steps\`
|
|
24908
|
+
(\`{question, options[]}\`, already the widget's shape). Map \`next_steps\` into
|
|
24909
|
+
your host's widget VERBATIM \u2014 same question, same two options, same labels and
|
|
24910
|
+
descriptions. Do not reword them, do not merge two gates into one widget, and
|
|
24911
|
+
do not add a third option.
|
|
24912
|
+
|
|
24913
|
+
Typing works as an escape hatch too. If the user types
|
|
24914
|
+
something off-script ("actually just show me my lenses"), abandon the
|
|
24915
|
+
walkthrough and serve what they asked. Never re-fire a gate the user has
|
|
24916
|
+
already declined in prose.
|
|
24917
|
+
|
|
24918
|
+
**ALWAYS render NEXT STEPS via your host's next-step widget.** Use whichever is in your tool set \u2014 the NAME and SCHEMA differ: **\`ask_user_input_v0\`** (Claude chat / ChatGPT) takes plain-string options with \`type:"single_select"\`; **\`AskUserQuestion\`** (Claude cowork / Claude Code) takes object options \`{label, description}\` plus a required short \`header\` (\u226412 chars) and \`multiSelect\`, NO \`type\` field, and never add an "Other" option (the host adds it). Match the schema to the tool you actually have \u2014 the wrong schema fails silently and you fall back to prose. Prose bullets are the fallback ONLY when NEITHER widget exists. Any turn that would end with a choice must be the widget \u2014 the widget IS the question.
|
|
24919
|
+
|
|
24920
|
+
**If the tool result carries a \`next_steps\` object, that is the source of truth \u2014 use it directly.** Each option has a short \`.label\` (\u22645 words) and a full \`.description\`. Map \`next_steps.options[]\` into your host widget VERBATIM and in order: for \`AskUserQuestion\` (cowork / Claude Code) pass each as \`{label, description}\`; for \`ask_user_input_v0\` (Claude chat / ChatGPT, string options only) pass each option's \`.description\` as the string (it's the full sentence). Do NOT reword, reorder, drop, or prose-ify them \u2014 they're built deterministically by the server so the offer (incl. the artifact option at position 0) fires every time. Fall back to the table below only when there is NO \`next_steps\` field.
|
|
24921
|
+
|
|
24922
|
+
**One exception \u2014 skip the widget** when the user's original message contained a complete sequential instruction chain ("show me X and then do Y") AND all stated steps have been completed. In that case, end with STOP directly \u2014 the user stated their full plan and does not need a "what next?" prompt.
|
|
24923
|
+
- Skip example: "Show me today's leads and then research the top one for me." \u2192 after research completes, emit STOP without the widget.
|
|
24924
|
+
- Do NOT skip for: plain requests ("show me today's leads", "run my check-in"), recurring-language requests ("I do this every day"), or requests where only one action was stated.
|
|
24925
|
+
|
|
24926
|
+
Pick 2\u20134 rows from the (Observation, Suggest, Calls) table below most relevant to the response, then call your host's widget with ITS schema (per the schema rules above \u2014 wrong schema fails silently):
|
|
24927
|
+
- \`ask_user_input_v0\`: \`{questions:[{question,type:"single_select",options:["<Suggest 1>","<Suggest 2>"]}]}\`
|
|
24928
|
+
- \`AskUserQuestion\`: \`{questions:[{question,header:"Next step",multiSelect:false,options:[{label:"<\u22645 words>",description:"<Suggest 1>"}]}]}\`
|
|
24929
|
+
|
|
24930
|
+
User picks \u2192 call the matching \`Calls\` tool. Constraints: 2\u20134 mutually-exclusive options, AskUserQuestion labels \u22645 words (full text in \`description\`), max 3 questions. Table stays internal; never recite it.
|
|
24931
|
+
|
|
24932
|
+
---
|
|
24933
|
+
|
|
24934
|
+
|
|
24935
|
+
|
|
24936
|
+
# THE OPENING \u2014 SHORT, THEN STRAIGHT INTO GATE 1
|
|
24937
|
+
|
|
24938
|
+
**A short paragraph, then the widget** \u2014 3\u20134 sentences, all in your FIRST
|
|
24939
|
+
message. In the user's own language, no jargon, cover:
|
|
24940
|
+
|
|
24941
|
+
1. **What Leadbay is** \u2014 it brings you a fresh batch of companies worth
|
|
24942
|
+
selling to every day, rather than you going hunting for them.
|
|
24943
|
+
2. **How it knows what to send** \u2014 you describe who you sell to (that
|
|
24944
|
+
description is your **lens**), and it goes and finds companies matching it,
|
|
24945
|
+
getting sharper as you engage with what it sends.
|
|
24946
|
+
3. **What this walkthrough will do** \u2014 four quick steps, each a real action on
|
|
24947
|
+
their own account, ending with leads in hand, a first email already written,
|
|
24948
|
+
and the person to send it to.
|
|
24949
|
+
4. **One line handing off to the first step** \u2014 e.g. "First, let's see which
|
|
24950
|
+
account you're on."
|
|
24951
|
+
|
|
24952
|
+
Then **fire GATE 1's widget immediately, in the same message**, and stop.
|
|
24953
|
+
|
|
24954
|
+
Keep it to a paragraph. Do NOT walk through the four steps one at a time here
|
|
24955
|
+
\u2014 each gate explains itself when its turn arrives, and turning the opening
|
|
24956
|
+
into a syllabus buries the first button under text nobody reads.
|
|
24957
|
+
|
|
24958
|
+
Call no tool in the opening. The widget is the whole ask.
|
|
24959
|
+
|
|
24960
|
+
# GATE 1 \u2014 "Check my account"
|
|
24961
|
+
|
|
24962
|
+
The opening paragraph above IS this gate's explanation \u2014 don't add another one
|
|
24963
|
+
on top of it. Just hand off in a line and fire the widget.
|
|
24964
|
+
|
|
24965
|
+
**Why it's useful**, if you say anything at all: this is where they can see at
|
|
24966
|
+
a glance how much they've used this week and what's left \u2014 so a batch that
|
|
24967
|
+
comes back small later has a visible reason rather than feeling broken.
|
|
24968
|
+
|
|
24969
|
+
**Fire the widget** \u2014 question \`Let's start with your account status.\`, first option labelled \`Check my account\`, description \`Check my Leadbay account status.\` Second option: \`I'm done for now\` / \`Stop the walkthrough here.\` **Wait for the click.**
|
|
24970
|
+
|
|
24971
|
+
On click: call \`leadbay_account_status\` (it takes no arguments).
|
|
24972
|
+
|
|
24973
|
+
**Show them their actual account \u2014 this is the payoff of the click.** Lead with
|
|
24974
|
+
one line on who they're signed in as and their organization, then render their
|
|
24975
|
+
**quota windows in full**, exactly as the web app shows them: Daily / Weekly /
|
|
24976
|
+
Monthly, each with a \`\u25B0\u25B1\` gauge, % used, $ spent against the cap, and when it
|
|
24977
|
+
resets \u2014 plus the per-resource breakdown underneath. A one-line "you're
|
|
24978
|
+
connected as X" is an under-delivery: they clicked a button labelled *check my
|
|
24979
|
+
account status*, so show them the status.
|
|
24980
|
+
|
|
24981
|
+
## RENDERING \u2014 quota windows (percentage + $, like the frontend)
|
|
24982
|
+
|
|
24983
|
+
Mirror the Leadbay web quota widget: three windows side by side \u2014 **Daily**,
|
|
24984
|
+
**Weekly**, **Monthly** \u2014 each headlined by a **% used** gauge and a **$ spend /
|
|
24985
|
+
$ cap** figure, with a per-resource usage breakdown underneath. **Never speak in
|
|
24986
|
+
raw "credits"** for quota \u2014 the unit is a percentage and a dollar spend.
|
|
24987
|
+
|
|
24988
|
+
**Include the quota whenever it is readable** \u2014 as part of the default account
|
|
24989
|
+
answer, even when the user only asked "what account am I connected to?". The
|
|
24990
|
+
sole reason to omit it is the silence gate below (unreadable quota, or an
|
|
24991
|
+
unlimited account); it is NOT gated on the user explicitly asking for quota.
|
|
24992
|
+
|
|
24993
|
+
**Silence gate (check FIRST).** Render NOTHING about quota when any of these
|
|
24994
|
+
holds \u2014 do not mention quota at all, do not say "unreadable", never tell the user
|
|
24995
|
+
to reconnect:
|
|
24996
|
+
- \`quota\` is null, OR \`quota_error\` is set (a 401/403 backend quirk for plan-less
|
|
24997
|
+
orgs \u2014 the same token read user/org fine), OR
|
|
24998
|
+
- \`organization.unlimited_credits\` is true (internal/unlimited account \u2014 stay
|
|
24999
|
+
silent on quota; never announce "unlimited").
|
|
25000
|
+
|
|
25001
|
+
**Pick the group (for DISPLAY only).** Prefer \`quota.user\` (present for every
|
|
25002
|
+
caller). Use \`quota.org\` only when \`quota.user\` is absent (admins receive both \u2014
|
|
25003
|
+
still show the caller's own \`user\` view). Call the chosen group \`<group>\` below.
|
|
25004
|
+
|
|
25005
|
+
**Exception \u2014 lens-refill pre-checks read the refill row, ORG-first.** This
|
|
25006
|
+
user-preference is for the display gauge ONLY. When you pre-check the
|
|
25007
|
+
\`LENS_EXTRA_REFILL\` resource before \`leadbay_extend_lens\`, look for the row in
|
|
25008
|
+
**\`quota.org.resources[]\` first** (admins get the org group, and the refill
|
|
25009
|
+
quota is org-scoped there); when \`quota.org\` is absent \u2014 non-admin callers only
|
|
25010
|
+
receive the \`user\` group \u2014 fall back to **\`quota.user.resources[]\`**. Match the
|
|
25011
|
+
resource type case-insensitively (\`LENS_EXTRA_REFILL\` / \`lens_extra_refill\`).
|
|
25012
|
+
Skipping the \`user\` fallback for non-admins would make the row invisible even
|
|
25013
|
+
when the quota data exists, so the agent burns the write and hits the very 429
|
|
25014
|
+
this pre-check exists to avoid.
|
|
25015
|
+
|
|
25016
|
+
**Per window (fixed order: daily \u2192 weekly \u2192 monthly).** Match entries by
|
|
25017
|
+
\`window_type\` (\`"daily"\` / \`"weekly"\` / \`"monthly"\`).
|
|
25018
|
+
|
|
25019
|
+
**Headline \u2014 when \`<group>.spend[]\` has an entry for the window (the % gauge):**
|
|
25020
|
+
- \`pct = round(current_units / max_units \xD7 100)\` (both are dollar_cents).
|
|
25021
|
+
- \`$used = (current_units / 100).toFixed(2)\`, \`$cap = (max_units / 100).toFixed(2)\`.
|
|
25022
|
+
- 10-segment bar in a SINGLE inline-code span (backticks give it contrast):
|
|
25023
|
+
\`filled = round(pct / 10)\` clamped 0..10; \`bar = "\u25B0"\xD7filled + "\u25B1"\xD7(10 \u2212 filled)\`.
|
|
25024
|
+
Use ONLY \`\u25B0\`/\`\u25B1\` \u2014 do NOT use the \`\u2756\` glyph (that identity belongs to lead
|
|
25025
|
+
discovery, not quota).
|
|
25026
|
+
- Line: **\`<Window>\`** \`\` \`\u25B0\u25B0\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1\` \`\` \`<pct>% used \xB7 $<used> / $<cap> \xB7 resets <resets_at, relative>\`.
|
|
25027
|
+
e.g. \`**Daily** \` + \`\` \`\u25B0\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1\` \`\` + \` 7% used \xB7 $0.84 / $12.00 \xB7 resets in ~7 h\`.
|
|
25028
|
+
|
|
25029
|
+
**Fallback \u2014 when \`<group>.spend[]\` is empty** (internal / free orgs have no
|
|
25030
|
+
OVERALL_SPEND quota): no gauge. Render the per-window resource breakdown as a
|
|
25031
|
+
compact table instead \u2014 one row per resource in \`<group>.resources[]\` for that
|
|
25032
|
+
window: the friendly label + \`count\` (append \`/ <max_units>\` only when
|
|
25033
|
+
\`max_units\` is a number). This is the pre-existing behavior, preserved.
|
|
25034
|
+
|
|
25035
|
+
**Resource labels (look up case-insensitively \u2014 lower-case \`resource_type\`
|
|
25036
|
+
first).** Localize to \`user.language\` (FR canonical shown; English in parens):
|
|
25037
|
+
- \`llm_completion\` \u2192 **G\xE9n\xE9rations par IA** (AI generations)
|
|
25038
|
+
- \`ai_rescore\` \u2192 **Leads qualifi\xE9s** (qualified leads)
|
|
25039
|
+
- \`web_fetch\` \u2192 **Informations web** (web insights)
|
|
25040
|
+
- \`contact_enrichment_phone\` \u2192 **T\xE9l\xE9phones enrichis** (phones enriched)
|
|
25041
|
+
- \`contact_enrichment_email\` \u2192 **E-mails enrichis** (emails enriched)
|
|
25042
|
+
|
|
25043
|
+
Skip any resource type not in this map silently \u2014 never dump the raw
|
|
25044
|
+
\`resource_type\` string at the user.
|
|
25045
|
+
|
|
25046
|
+
**\`resets_at\`.** Show as a relative countdown ("resets in ~7 h", "resets in 3
|
|
25047
|
+
days"), computed against now \u2014 mirroring the widget's "r\xE9initialis\xE9 dans X". The
|
|
25048
|
+
raw value is an ISO-8601 timestamp.
|
|
25049
|
+
|
|
25050
|
+
**Top-up (optional, subordinate).** When \`quota.topup\` is present, you MAY add one
|
|
25051
|
+
small line below the windows: \`Top-up: $<remaining_cents/100> of $<total_credit_cents/100> left\`.
|
|
25052
|
+
Keep it secondary \u2014 the three window gauges are the headline. Omit when null.
|
|
25053
|
+
|
|
25054
|
+
**Legend** (once, below): \`\` \`\u25B0\` used \xB7 \`\u25B1\` remaining \`\`.
|
|
25055
|
+
|
|
25056
|
+
|
|
25057
|
+
**Then explain what they're looking at \u2014 one or two plain lines, no jargon.**
|
|
25058
|
+
A first-run user has never seen these numbers and won't know whether they're
|
|
25059
|
+
good, bad, or something to worry about. Say, in your own words:
|
|
25060
|
+
|
|
25061
|
+
- **What it counts** \u2014 the AI work Leadbay does on their behalf: researching
|
|
25062
|
+
companies on the web and qualifying leads against their criteria. Not
|
|
25063
|
+
"credits", and not something they spend by clicking around.
|
|
25064
|
+
- **Why it matters to them** \u2014 it paces how many fresh leads arrive. Heavy use
|
|
25065
|
+
now means Leadbay queues up a bigger batch for next time; and if a batch ever
|
|
25066
|
+
comes back smaller than expected, this is where they'd see why. Each window
|
|
25067
|
+
refills on its own at the reset time already shown.
|
|
25068
|
+
|
|
25069
|
+
Keep it to a sentence or two, in their language. Do NOT lecture, do NOT explain
|
|
25070
|
+
every resource row one by one, and do NOT turn this into a pricing pitch \u2014 if a
|
|
25071
|
+
window is genuinely exhausted the tool's own guidance covers wait-vs-top-up.
|
|
25072
|
+
|
|
25073
|
+
**When the silence gate above applies, skip this explanation too** \u2014 there is
|
|
25074
|
+
nothing on screen to explain, and describing an absent gauge just confuses.
|
|
25075
|
+
|
|
25076
|
+
**Two things this gate must NOT do** (both are pinned regressions):
|
|
25077
|
+
|
|
25078
|
+
- **Say nothing about quota when the silence gate above applies** \u2014 \`quota\` is
|
|
25079
|
+
null, \`quota_error\` is set, or the org has \`unlimited_credits\`. A brand-new
|
|
25080
|
+
org often has no billing plan yet, so the quota read fails. That is NOT an
|
|
25081
|
+
error worth showing: do not mention quota, do not mention a 401, and above
|
|
25082
|
+
all do NOT tell the user to log in again or reconnect \u2014 their token is fine,
|
|
25083
|
+
the very same response just read their account. In that case fall back to the
|
|
25084
|
+
short user + org line and move on to GATE 2 without comment.
|
|
25085
|
+
- **Do not volunteer the lens.** The response deliberately withholds the lens
|
|
25086
|
+
unless the user asked about it, so there is nothing to report. Don't reach
|
|
25087
|
+
for another tool to find it either. The lens shows up naturally at GATE 2.
|
|
25088
|
+
|
|
25089
|
+
# GATE 2 \u2014 "Pull today's leads"
|
|
25090
|
+
|
|
25091
|
+
**Explain first \u2014 this is where you teach the LENS.** Leadbay keeps a *lens*:
|
|
25092
|
+
their description of who they sell to. Every day it goes and finds fresh
|
|
25093
|
+
companies matching it. This click pulls today's batch.
|
|
25094
|
+
|
|
25095
|
+
**Why it's useful:** it replaces the hour spent digging through directories and
|
|
25096
|
+
LinkedIn looking for someone worth calling \u2014 the list is already waiting, and
|
|
25097
|
+
already scored, when they sit down. And it sharpens itself: the leads they
|
|
25098
|
+
like, contact or skip teach the lens what a good fit looks like, so tomorrow's
|
|
25099
|
+
batch lands closer than today's.
|
|
25100
|
+
|
|
25101
|
+
**Then fire the widget** \u2014 question \`Now let's see today's leads. Ready?\`, first option labelled \`Pull today's leads\`, description \`Pull today's leads from your lens.\` Second option: \`I'm done for now\` / \`Stop the walkthrough here.\` **Wait for the click.**
|
|
25102
|
+
|
|
25103
|
+
On click: call \`leadbay_pull_leads\` with **no arguments** (it resolves the
|
|
25104
|
+
user's default lens itself).
|
|
25105
|
+
|
|
25106
|
+
Capture \`lens.id\` from the response and pass it as an explicit \`lensId\` on
|
|
25107
|
+
every later call in this walkthrough, so gate 4 enriches the same lens the
|
|
25108
|
+
user just looked at. Pin the TOP-SCORING lead's id and name too \u2014 gate 3 drafts
|
|
25109
|
+
to it, and gate 4 reveals its contact.
|
|
25110
|
+
|
|
25111
|
+
Render the batch with the canonical layout:
|
|
25112
|
+
|
|
25113
|
+
## RENDERING \u2014 markdown table, three columns, score-bar driven
|
|
25114
|
+
|
|
25115
|
+
Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
|
|
25116
|
+
|
|
25117
|
+
## Score-bar (10-segment, inline-code wrapped)
|
|
25118
|
+
|
|
25119
|
+
Wrap a 10-glyph bar in a SINGLE inline-code span (backticks). The inline-code styling is what gives the bar contrast in most chat renderers \u2014 HTML \`<span>\` is stripped inside table cells.
|
|
25120
|
+
|
|
25121
|
+
Glyphs (use these exact characters; do not substitute):
|
|
25122
|
+
|
|
25123
|
+
- \`\u25B0\` \u2014 firmographic-only fill
|
|
25124
|
+
- \`\u2756\` \u2014 AI-booster cap (placed at the RIGHT END of the filled run, never the front)
|
|
25125
|
+
- \`\u25B1\` \u2014 empty
|
|
25126
|
+
|
|
25127
|
+
Computation:
|
|
25128
|
+
|
|
25129
|
+
\`\`\`
|
|
25130
|
+
total_filled = round(score / 10), clamped to 0..10
|
|
25131
|
+
ai_segments = round(qualification_summary.avg_qualification_boost / 3.3),
|
|
25132
|
+
clamped to [0, total_filled]
|
|
25133
|
+
normal_filled = total_filled \u2212 ai_segments
|
|
25134
|
+
bar = "\u25B0" \xD7 normal_filled
|
|
25135
|
+
+ "\u2756" \xD7 ai_segments
|
|
25136
|
+
+ "\u25B1" \xD7 (10 \u2212 total_filled)
|
|
25137
|
+
\`\`\`
|
|
25138
|
+
|
|
25139
|
+
If \`qualification_summary.answered == 0\` or \`avg_qualification_boost\` is null, set \`ai_segments = 0\` (no \u2756). Always wrap the bar in backticks. Print the legend \`\` \`\u25B0\` firmographic \xB7 \`\u2756\` AI booster cap \xB7 \`\u25B1\` unfilled \`\` once below the table.
|
|
25140
|
+
|
|
25141
|
+
|
|
25142
|
+
**Column 1 \u2014 Company**
|
|
25143
|
+
|
|
25144
|
+
- Line 1: the 10-segment score bar in inline-code backticks (see the score-bar snippet above for the algorithm).
|
|
25145
|
+
- Insert \`<br>\` between lines.
|
|
25146
|
+
- Line 2: linked company name + \` \xB7 \` + short location + \` \xB7 \` + compact size.
|
|
25147
|
+
- Link target: \`website\` (prefix \`https://\` if it's a bare hostname). Don't synthesize an app deep-link.
|
|
25148
|
+
- Location: shorten "City of New York" \u2192 "NYC"; otherwise "City ST"; state alone only when city missing.
|
|
25149
|
+
- Size: \`"Xk+"\` when \`size.min >= 1000\`, \`"min\u2013max"\` otherwise.
|
|
25150
|
+
|
|
25151
|
+
**Column 2 \u2014 Why it fits**
|
|
25152
|
+
|
|
25153
|
+
- One sentence, \u2264 20 words.
|
|
25154
|
+
- Synthesize from (in priority order, whichever is present) the lead's \`short_description\`, top 2 \`tags[].display_name\`, and the gist of \`qualification_summary.best_response_excerpt\`. The trim payload does NOT carry the longer \`description\` field \u2014 for that, agent must call \`leadbay_research_lead_by_id\` or \`leadbay_research_lead_by_name_fuzzy\`.
|
|
25155
|
+
- Do NOT append \`(boost N)\` \u2014 the \u2756 cap in column 1 already carries that signal.
|
|
25156
|
+
- No bullet lists, no line breaks inside the cell.
|
|
25157
|
+
|
|
25158
|
+
**Column 3 \u2014 Contact**
|
|
25159
|
+
|
|
25160
|
+
\`[Contact name](LINK) \xB7 short job title\`. The \`[Contact name](LINK)\` markdown link wrapping is mandatory \u2014 never render the name as plain text. See linking/contact-linkedin for the URL priority (real profile \u2192 constructed people-search) and the \xB0-flag fallback.
|
|
25161
|
+
|
|
25162
|
+
**Hide from the user (never include in any cell):** \`id\`, \`location.pos\`, \`location.country\` (unless city/state both missing), \`sector_id\`, \`is_hq\`, \`web_fetch_in_progress\`, \`enrichment_in_progress\`, \`highlighted_fields\`, \`custom_fields\`, \`contacts_count\` when 0, \`notes_count\` / \`epilogue_actions_count\` / \`prospecting_actions_count\` when 0, \`stale_at\`, \`deal_insights\`, \`social_presence\` booleans (except as the \xB0-flag signal), \`need_attention\` flags, any field whose value is the string \`"null"\`.
|
|
25163
|
+
|
|
25164
|
+
## Linking a contact's name
|
|
25165
|
+
|
|
25166
|
+
**MANDATORY: every contact name in your output \u2014 table cells, prose, headers, "Reach <Name>" callouts \u2014 MUST be wrapped in markdown link syntax \`[Name](URL)\`. Never render a contact name as bare text. A plain-text name is a broken contact card; the underlined name is the user's primary affordance for "take me to this person's profile". No "no URL available" exception \u2014 the search URL below is always constructable from name + company.**
|
|
25167
|
+
|
|
25168
|
+
URL priority (first applicable wins):
|
|
25169
|
+
|
|
25170
|
+
1. **Real profile** \u2014 \`contact.linkedin_page\` when it's a string starting with \`https://\` (the MCP coerces the legacy literal \`"null"\` string to real null before you see it).
|
|
25171
|
+
2. **Constructed people-search** \u2014 \`https://www.linkedin.com/search/results/people/?keywords=<First>+<Last>+<Company>\`. URL-encode params. Strip Inc / LLC / Corp / Ltd / GmbH / Co / S.A. / S.L. / PLC / AG / SAS / SARL suffixes from the company. Append a trailing \` \xB0\` to the rendered name ONLY when this fallback is in use AND \`social_presence.linkedin == false\`. Never append \`\xB0\` when a real \`linkedin_page\` was used.
|
|
25172
|
+
|
|
25173
|
+
Never link a person's name to the company's LinkedIn page (and vice versa) \u2014 the two surfaces are different and conflating them quietly degrades the workflow.
|
|
25174
|
+
|
|
25175
|
+
## Linking the company
|
|
25176
|
+
|
|
25177
|
+
Use the lead's \`website\` as the company-name link target \u2014 prefix \`https://\` if the value is a bare hostname. (The MCP does NOT synthesize a Leadbay-app deep-link URL; the team has not standardized one. Linking to \`website\` is always real data.)
|
|
25178
|
+
|
|
25179
|
+
When the response carries \`social_urls\` (the post-fix multi-platform URL block on rich-lead responses), render every non-null platform as a pill chip in the company-info row. Iterate over \`social_urls\`'s keys \u2014 never hardcode a fixed list \u2014 and emit each as \`[<platform-label>](<url>)\`. Skip platforms whose URL is null.
|
|
25180
|
+
|
|
25181
|
+
\`social_presence\` carries booleans for the same 6 platforms (crunchbase, facebook, instagram, linkedin, tiktok, twitter) \u2014 useful when you only care that the company has a profile somewhere. Use it as the \xB0-flag signal in the contact people-search fallback (see linking/contact-linkedin).
|
|
25182
|
+
|
|
25183
|
+
|
|
25184
|
+
|
|
25185
|
+
## Branch \u2014 the batch came back empty
|
|
25186
|
+
|
|
25187
|
+
A brand-new account often reads empty for the first minute while the backend
|
|
25188
|
+
computes the lens wishlist. Check \`computing_wishlist\` / \`computing_scores\`:
|
|
25189
|
+
|
|
25190
|
+
- **Either is true** \u2192 the lens is still building. Say exactly that, in the
|
|
25191
|
+
user's terms: "your lens is still building your first batch \u2014 that's normal
|
|
25192
|
+
on a new account, it takes about a minute." The tool's \`next_steps\` payload
|
|
25193
|
+
carries a **two-option** warm-up widget ("Re-pull in ~30s" / "Refine
|
|
25194
|
+
audience") \u2014 render it VERBATIM. This is the ONE place a gate carries two
|
|
25195
|
+
options, because the server built the payload and a re-pull genuinely has a
|
|
25196
|
+
real alternative. On "Re-pull in ~30s", wait ~30s and return to GATE 2.
|
|
25197
|
+
**NEVER say "no leads found."**
|
|
25198
|
+
- **Both false** \u2192 the lens is genuinely empty or too narrow, and \`next_steps\`
|
|
25199
|
+
is \`null\`. Say so honestly, offer to widen the audience, and end the
|
|
25200
|
+
walkthrough here. There is nothing to enrich.
|
|
25201
|
+
|
|
25202
|
+
# GATE 3 \u2014 "Draft the first email"
|
|
25203
|
+
|
|
25204
|
+
**Explain first \u2014 and name the company.** Take the TOP-SCORING lead from
|
|
25205
|
+
GATE 2 and say its name out loud, so this is an offer about a real company
|
|
25206
|
+
rather than an abstraction. Leadbay already worked out *why* that company fits
|
|
25207
|
+
them, so it can write the first email instead of leaving them at a blank page.
|
|
25208
|
+
|
|
25209
|
+
**Why it's useful:** finding companies was never the hard part. Writing the
|
|
25210
|
+
twentieth opener of the day is where prospecting actually dies. This turns a
|
|
25211
|
+
row in a table into something they could send in a minute.
|
|
25212
|
+
|
|
25213
|
+
Say plainly that this only **drafts** \u2014 nothing is sent, and they see it first.
|
|
25214
|
+
|
|
25215
|
+
**Then fire the widget** \u2014 question \`Want me to draft the first email to your top lead?\`, first option labelled \`Draft the first email\`, description \`Write a first email to the best company in today's batch. Nothing is sent.\` Second option: \`I'm done for now\` / \`Stop the walkthrough here.\` **Wait for the click.**
|
|
25216
|
+
|
|
25217
|
+
On click: call \`leadbay_prepare_outreach\` with \`leadId\` = the top lead's id,
|
|
25218
|
+
**and nothing else**.
|
|
25219
|
+
|
|
25220
|
+
**This gate spends NOTHING. Never pass \`enrich: true\`** \u2014 that launches a paid
|
|
25221
|
+
contact reveal off the back of a *draft* click. They agreed to see an email
|
|
25222
|
+
written, not to spend. GATE 4 is where the reveal gets asked for, on its own
|
|
25223
|
+
terms.
|
|
25224
|
+
|
|
25225
|
+
\`recommended_contact\` comes back in its post-enrichment shape with \`email\` and
|
|
25226
|
+
\`phone\` still **null**. That is expected, not a failure \u2014 and it's exactly the
|
|
25227
|
+
hook for the next gate: an email written, and nobody to send it to yet. Don't
|
|
25228
|
+
apologise for it, and don't reach for another tool to fill it in.
|
|
25229
|
+
|
|
25230
|
+
**Render the draft through \`message_compose_v1\`** \u2014 \`kind: "email"\`, a
|
|
25231
|
+
\`summary_title\` naming the company, and 2\u20133 variants whose labels name the
|
|
25232
|
+
**strategy** ("Lead with the growth signal", "Ask about their current setup"),
|
|
25233
|
+
never the tone. Do NOT also paste the body into chat prose; the composer *is*
|
|
25234
|
+
the answer. If the host exposes no composer, fall back to the canonical
|
|
25235
|
+
prepare-outreach layout: one context line, then subject + body as a quoted
|
|
25236
|
+
block.
|
|
25237
|
+
|
|
25238
|
+
**Address it to the job TITLE** \u2014 "the Head of Operations at <Company>". You do
|
|
25239
|
+
not have a name yet, and inventing one is fabrication.
|
|
25240
|
+
|
|
25241
|
+
Add one line on *why this company was the pick* \u2014 its score and the fit reason
|
|
25242
|
+
from the lead's summary \u2014 so the draft reads as reasoned rather than generated.
|
|
25243
|
+
|
|
25244
|
+
# GATE 4 \u2014 "Find who to email"
|
|
25245
|
+
|
|
25246
|
+
**Explain first \u2014 point at the gap the draft just opened.** They have an email
|
|
25247
|
+
ready and nobody to send it to: it's addressed to a job title, not a person.
|
|
25248
|
+
That's what this step fixes. Leadbay can find *which roles* exist at that
|
|
25249
|
+
company, then reveal the actual human and how to reach them.
|
|
25250
|
+
|
|
25251
|
+
**Why it's useful:** they ask for the operations director by name instead of
|
|
25252
|
+
pitching whoever answers the switchboard \u2014 the difference between a
|
|
25253
|
+
conversation and a dead end.
|
|
25254
|
+
|
|
25255
|
+
Say plainly that the first look is **free**, and that revealing the contact
|
|
25256
|
+
costs credits and needs their say-so.
|
|
25257
|
+
|
|
25258
|
+
**First, check \`leadbay_enrich_titles\` is in your tool set.** On a read-only
|
|
25259
|
+
deployment it is not registered, and a gate whose tool cannot run is a dead
|
|
25260
|
+
end. If it's missing: don't fire this widget, say plainly that revealing
|
|
25261
|
+
contacts isn't enabled on this connection, note the draft is still theirs, and
|
|
25262
|
+
go straight to the closing. Ending one step early beats offering a button that
|
|
25263
|
+
does nothing.
|
|
25264
|
+
|
|
25265
|
+
**Then fire the widget** \u2014 question \`Want to find out who to send that email to?\`, first option labelled \`Find who to email\`, description \`See the roles at that company. Free \u2014 no contact details revealed yet.\` Second option: \`I'm done for now\` / \`Stop the walkthrough here.\` **Wait for the click.**
|
|
25266
|
+
|
|
25267
|
+
This gate runs in **TWO BEATS**. Do not collapse them.
|
|
25268
|
+
|
|
25269
|
+
## BEAT 1 \u2014 the free look (spends nothing)
|
|
25270
|
+
|
|
25271
|
+
On click: call \`leadbay_enrich_titles\` with \`leadIds\` = **the one lead you
|
|
25272
|
+
drafted for at GATE 3** and \`lensId\` = the pinned lens id.
|
|
25273
|
+
|
|
25274
|
+
**This call must spend NOTHING.** Omit \`titles\` entirely: that returns
|
|
25275
|
+
\`mode:"discover"\`, the free preview of which job titles exist at that company.
|
|
25276
|
+
Do NOT pass \`titles\`, \`confirm=true\`, \`email=true\` or \`phone=true\` on this call
|
|
25277
|
+
\u2014 any one of them launches the paid reveal before the user has chosen anything.
|
|
25278
|
+
|
|
25279
|
+
Present the discovered titles and say plainly: "nothing spent yet."
|
|
25280
|
+
|
|
25281
|
+
## BEAT 2 \u2014 reveal the person the draft is for (spends credits)
|
|
25282
|
+
|
|
25283
|
+
Name the title the GATE 3 draft is addressed to, and tell them the cost
|
|
25284
|
+
**before** they decide: one credit per contact revealed \u2014 here that's **one
|
|
25285
|
+
contact, one credit**. Then ask them to confirm.
|
|
25286
|
+
|
|
25287
|
+
**Wait for an explicit confirmation.** Silence is not consent, and neither is
|
|
25288
|
+
"they clicked the gate earlier" \u2014 the gate click bought the free look, not the
|
|
25289
|
+
reveal.
|
|
25290
|
+
|
|
25291
|
+
Once confirmed, call \`leadbay_enrich_titles\` AGAIN with
|
|
25292
|
+
\`leadIds: [<the drafted lead's id>]\` \u2014 **the array, always, even for one lead**
|
|
25293
|
+
\u2014 plus the chosen \`titles\`, \`confirm: true\` and \`email: true\`. That's the real,
|
|
25294
|
+
paid reveal.
|
|
25295
|
+
|
|
25296
|
+
\`leadIds\` is the only key this tool reads for scope. A singular \`leadId\` is not
|
|
25297
|
+
a parameter: it is silently ignored, and the call then falls back to the
|
|
25298
|
+
account's **default wishlist selection** while \`confirm\`/\`email\` are set \u2014 so
|
|
25299
|
+
it would reveal and charge for the whole batch instead of the one lead the user
|
|
25300
|
+
agreed to.
|
|
25301
|
+
|
|
25302
|
+
It returns a \`bulk_id\` and runs async \u2014 poll \`leadbay_bulk_enrich_status\`
|
|
25303
|
+
with that id (\`include_contacts=true\`) until \`all_done\`, or until the resolved
|
|
25304
|
+
count plateaus across a few spaced polls. Then report the contact that actually
|
|
25305
|
+
resolved: name, title, and the email/phone that came back. Contacts sometimes
|
|
25306
|
+
don't resolve; say so honestly rather than implying success.
|
|
25307
|
+
|
|
25308
|
+
**Then close the loop** \u2014 one line: one credit per contact revealed, so this
|
|
25309
|
+
cost one. And say the thing that makes it land: the draft from GATE 3 now has a
|
|
25310
|
+
real person and a real address to go to. This is the moment GATE 1's quota
|
|
25311
|
+
numbers stop being abstract, because they just watched them move and got
|
|
25312
|
+
something for it. Don't turn it into a pricing pitch.
|
|
25313
|
+
|
|
25314
|
+
If they decline the reveal, that's fine \u2014 keep the draft and the title, and
|
|
25315
|
+
let it go without pushing \u2014 the tour is done either way.
|
|
25316
|
+
|
|
25317
|
+
# HOW THE TOUR ENDS \u2014 THREE ENDINGS, PICK THE RIGHT ONE
|
|
25318
|
+
|
|
25319
|
+
This is the ONLY place that says what to do when the walkthrough stops. There
|
|
25320
|
+
is no other closing section: work out which of these three happened, then do
|
|
25321
|
+
that one in full, in the order written.
|
|
25322
|
+
|
|
25323
|
+
**The buttons disappear when the walkthrough ends.** If it stops without
|
|
25324
|
+
telling the user what to *type*, they learned to click through a tutorial and
|
|
25325
|
+
nothing about using Leadbay tomorrow. That is what the cheat-sheet is for.
|
|
25326
|
+
|
|
25327
|
+
## ENDING A \u2014 they finished all four gates
|
|
25328
|
+
|
|
25329
|
+
1. Render the \`keep_going\` cheat-sheet (below).
|
|
25330
|
+
2. Then the setup-guide link (below).
|
|
25331
|
+
|
|
25332
|
+
## ENDING B \u2014 they picked \`I'm done for now\`
|
|
25333
|
+
|
|
25334
|
+
**All three beats, in this order. The offer is the LAST thing you say.**
|
|
25335
|
+
|
|
25336
|
+
1. One short line acknowledging the stop \u2014 "No problem, we'll leave it there."
|
|
25337
|
+
2. The \`keep_going\` cheat-sheet, then the setup-guide link (below).
|
|
25338
|
+
3. **The 1:1 offer \u2014 REQUIRED, and it goes last.** Ending B without it is
|
|
25339
|
+
incomplete: they stopped right before the setup work a call actually helps
|
|
25340
|
+
with, which makes this the one moment the offer is welcome rather than
|
|
25341
|
+
pushy. Say, in your own words, one sentence and the link:
|
|
25342
|
+
|
|
25343
|
+
> If you want a hand tuning this to your own market, Zoe on our team runs 1:1
|
|
25344
|
+
> sessions: <https://calendly.com/zoe-leadbay/demo-leadbay>
|
|
25345
|
+
|
|
25346
|
+
That length is the rule, not a suggestion \u2014 **one sentence**. Listing
|
|
25347
|
+
everything Zoe could help with turns an offer into promotional copy, which
|
|
25348
|
+
is exactly what a user who just said "I'm done" doesn't want.
|
|
25349
|
+
|
|
25350
|
+
Keep it to **one sentence and the link**. Never re-open the walkthrough,
|
|
25351
|
+
never re-fire the gate they just declined, and never argue for finishing the
|
|
25352
|
+
tour.
|
|
25353
|
+
|
|
25354
|
+
**On this path the offer is the last PROSE you write.** The STOP block below
|
|
25355
|
+
still closes the message \u2014 it is a machine marker, not something the user
|
|
25356
|
+
reads as content, so it does not displace the offer. What must never happen
|
|
25357
|
+
is the offer being dropped or pushed above the cheat-sheet to make room.
|
|
25358
|
+
|
|
25359
|
+
## ENDING C \u2014 they typed something off-script
|
|
25360
|
+
|
|
25361
|
+
Serve what they actually asked for. **No cheat-sheet, no setup link, no 1:1
|
|
25362
|
+
offer** \u2014 they're already off doing what they wanted, and any of it on top of
|
|
25363
|
+
their real question is exactly the interruption they were avoiding.
|
|
25364
|
+
|
|
25365
|
+
## The cheat-sheet (endings A and B)
|
|
25366
|
+
|
|
25367
|
+
Render the manifest's \`keep_going\` rows as a compact two-column markdown table,
|
|
25368
|
+
titled something like **"Next time, just ask"**. Keep the phrases VERBATIM \u2014
|
|
25369
|
+
each one is taken from that tool's own trigger list, so it's a phrase that
|
|
25370
|
+
genuinely routes. Do not invent extra rows, and do not reword the phrases into
|
|
25371
|
+
something that sounds nicer but doesn't match.
|
|
25372
|
+
|
|
25373
|
+
| What you want | Just say |
|
|
25374
|
+
|---|---|
|
|
25375
|
+
| Today's fresh leads | "Show me today's leads" |
|
|
25376
|
+
| Who to follow up with | "What should I follow up on" |
|
|
25377
|
+
| The story on one company | "Research <Company>" |
|
|
25378
|
+
| An email to a contact | "Draft outreach for <Contact>" |
|
|
25379
|
+
| Change who you target | "Narrow the audience to <sector>" |
|
|
25380
|
+
| Switch target audience | "Show me my lenses" |
|
|
25381
|
+
|
|
25382
|
+
Add one closing line in your own words: they don't need to remember exact
|
|
25383
|
+
wording \u2014 plain language works, and this is just a starting point.
|
|
25384
|
+
|
|
25385
|
+
## The setup guide (endings A and B)
|
|
25386
|
+
|
|
25387
|
+
One plain link, for the things the four gates didn't cover \u2014 installing Leadbay
|
|
25388
|
+
on another machine, adding a teammate, signing back in later:
|
|
25389
|
+
<https://docs.leadbay.app/doc/leadbay-mcp/quickstart>
|
|
25390
|
+
|
|
25391
|
+
**Once, here, and nowhere else.** Never drop that link between gates: a link
|
|
25392
|
+
mid-tour is an invitation to leave the thing they're in the middle of doing.
|
|
25393
|
+
|
|
25394
|
+
# STOP
|
|
25395
|
+
|
|
25396
|
+
IRON LAW \u2014 the walkthrough **drafts** an email at GATE 3 but never **sends**
|
|
25397
|
+
one. The draft stays in the chat for the user to read and judge; nothing
|
|
25398
|
+
leaves. Never send it, never offer to send it on their behalf, and never call
|
|
25399
|
+
\`leadbay_report_outreach\` \u2014 logging an outreach that never happened poisons the
|
|
25400
|
+
human team's pipeline.
|
|
25401
|
+
|
|
25402
|
+
Render this acknowledgment VERBATIM as the last line of your message:
|
|
25403
|
+
|
|
25404
|
+
\`\`\`
|
|
25405
|
+
STOP \u2014 awaiting user decision. I will not take any further action until you tell me what to do next.
|
|
25406
|
+
\`\`\`
|
|
25407
|
+
|
|
24494
25408
|
Do not propose a next action. Do not call any more tools. Hand control back to the user.
|
|
24495
25409
|
`;
|
|
24496
25410
|
var leadbay_import_file = `
|
|
@@ -25801,6 +26715,13 @@ var PROMPT_META = {
|
|
|
25801
26715
|
leadbay_daily_check_in: { "name": "leadbay_daily_check_in", "short_description": 'Morning DISCOVERY workflow \u2014 new leads from the lens wishlist. Trigger\non "show me leads", "what\'s new today", "let\'s prospect", "run my check-in",\n"my morning check-in", "I do this every day", "every morning". Recurrence\nlanguage always means this prompt. Do NOT trigger on follow-up phrasings\n("follow up", "before my trip") \u2014 those go to `leadbay_followup_check_in`.\n', "arguments": [], "expected_calls": ["leadbay_account_status", "leadbay_pull_leads", "leadbay_research_lead_by_id", "leadbay_bulk_qualify_leads", "leadbay_enrich_contacts"], "failure_modes": ["Calls leadbay_report_outreach without explicit user authorization", "Surfaces fewer than 10 leads when more are available, or fails to top up via leadbay_qualify_top_n when the batch is short", `Replaces the canonical pull_leads table layout with prose per row (the per-tool RENDERING block is the structural contract; "Today's nudges" goes above it, not in place of it)`, "Skips the nudge paragraph entirely \u2014 the table alone is fine but adding the nudge is the value-add", `Skips deep research on promising leads (Phase 4) \u2014 the agent must call leadbay_research_lead_by_id on each when the user's intent is to research specific leads; Phase 4 is intentionally skipped for batch-view requests ("show me today's leads", "run my morning check-in") per the Phase 4 skip gate`, "Triggers contact enrichment without asking the user first (it consumes quota)", "Skips the STOP byproduct and proposes next actions on its own", 'Fires 10 parallel leadbay_research_lead_by_id calls and treats "stream closed" errors as terminal \u2014 must serialize and retry singletons', "Re-pulls leadbay_pull_leads without passing the captured lensId, allowing a backend lens shift to discard the Phase 2 batch", 'Treats a "Request timed out" from leadbay_bulk_qualify_leads as terminal instead of retrying with wait_for_completion:false + qualify_status polling', 'Triggers on a follow-up query (e.g., "leads I should follow up with") that should have routed to `leadbay_followup_check_in` \u2014 the two entry points are different data sources (Discover wishlist vs Monitor view) per \xA71.6'] },
|
|
25802
26716
|
leadbay_extend_my_lens: { "name": "leadbay_extend_my_lens", "short_description": "Add more leads to the current lens on demand \u2014 for users whose appetite\nexceeds the standard daily fill. The agent picks seeds silently from\nwhat's already on the lens, fires the extra refill, and surfaces the\nqueue confirmation. The user never reviews the seed list.\n", "arguments": [{ "name": "extra_count", "description": "How many extra leads to add. Optional. Omit to use the backend default.", "required": false }], "expected_calls": ["leadbay_account_status", "leadbay_seed_candidates", "leadbay_extend_lens", "leadbay_pull_leads"], "failure_modes": ["Surfaces the seed candidate list to the user instead of picking silently \u2014 the user asked for MORE LEADS, not a candidate review meeting", "Skips the seeded path and calls `leadbay_extend_lens` with no `seed_lead_ids`, losing the bias signal the recommender needs", "On 429, silently retries instead of surfacing the three options (smaller / wait / upgrade) via your host's choice widget (`ask_user_input_v0` or `AskUserQuestion`)", "Forgets to pre-check `LENS_EXTRA_REFILL` quota in `leadbay_account_status` and burns a wasted API call", "Skips the post-queue pull-leads suggestion, so the user doesn't see what just got added"] },
|
|
25803
26717
|
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)'] },
|
|
26718
|
+
leadbay_getting_started: { "name": "leadbay_getting_started", "short_description": `Guided first-run walkthrough \u2014 four clicks that actually use Leadbay: check
|
|
26719
|
+
the account, pull today's leads, draft a first email to the top one, then
|
|
26720
|
+
reveal who to send it to. Use when the user is new or asks to be SHOWN how
|
|
26721
|
+
Leadbay works ("walk me through Leadbay", "I'm new", "how do I use this",
|
|
26722
|
+
"give me a tour"). Don't use it for orientation prose with no clicking \u2014
|
|
26723
|
+
that's leadbay_prospecting_overview.
|
|
26724
|
+
`, "arguments": [], "expected_calls": ["leadbay_account_status", "leadbay_pull_leads", "leadbay_prepare_outreach", "leadbay_enrich_titles", "leadbay_bulk_enrich_status"], "failure_modes": ['Presents a gate as prose ("let me know if you want me to pull your leads") instead of CALLING the host choice widget \u2014 the click IS the lesson, and prose turns the walkthrough into a lecture', "Runs a step's tool WITHOUT firing that step's widget first and waiting for the click \u2014 the walkthrough becomes an automated demo the user only watches, which is the exact opposite of learning by doing", "Fires the widget without the EXPLAIN beat, so the user gets an unexplained button and learns nothing about what a lens or an enrichment actually is", 'Answers gate 1 with a bare "you\'re connected as X at Y" when the quota IS readable \u2014 the user clicked a button labelled `check my account status`, so the quota windows (Daily/Weekly/Monthly gauges, % used, $ spent, resets) ARE the answer, not an optional extra', 'Renders quota as raw "credits" instead of the web app\'s percentage + dollar-spend gauges, or dumps raw `resource_type` strings the user has never seen', "Opens with a wall of text \u2014 previewing all four steps, explaining lenses up front, or writing several paragraphs before the first widget. The opening is TWO lines then the button; a first-run user wants to see it work, not read a syllabus", `Ends the first message without firing gate 1's widget, leaving the user to reply "ok" before anything happens`, "Rewrites the gate's own `next_steps` payload (its `question`, `label` or `description`) instead of mapping it into the widget verbatim, or merges two gates into a single multi-option widget", 'Fires a THIRD option, or turns the exit into an alternative route ("show me my lenses instead") \u2014 each gate carries exactly one forward action plus the `I\'m done for now` exit, never a menu of paths', `Fires a single-option widget \u2014 the host requires 2\u20134 options, so a lone option is rejected or silently degrades to prose ("say the word and I'll check it"), which is the exact defect this rule exists to prevent`, 'Launches the PAID reveal at gate 4 BEFORE the user has picked leads and confirmed \u2014 beat 1 must be the free `mode:"discover"` preview (no `titles`, no `confirm`, no `email`, no `phone`); the gate click bought the free look, not the reveal, and silence is never consent', "Stops at the free preview after the user DID pick leads and confirm \u2014 they asked for real contact details, so the second call must actually run with `confirm:true` and the chosen titles", "Reports the enrichment without polling `leadbay_bulk_enrich_status` to completion, so it claims contacts it never actually saw resolve", `Reveals contacts and never says what it cost \u2014 the user just spent credits and deserves the one-line "N contacts = N credits", which is also what makes gate 1's quota numbers concrete`, 'Reports "no leads" on an empty batch while `computing_wishlist` / `computing_scores` is true \u2014 the lens is still building; render the tool\'s own two-option warm-up widget verbatim and pause', "Rewords, reorders or prose-ifies the `next_steps` payload from `leadbay_pull_leads` instead of mapping `options[]` into the widget verbatim", "Runs all four steps in one turn without waiting for the user's click between gates \u2014 the walkthrough is a sequence of gates, not a script to recite", "Skips `leadbay_pull_leads` and jumps straight to enrichment, leaving gate 4 with no `leadIds` to scope", "Passes a singular `leadId` to `leadbay_enrich_titles` on the confirmed reveal \u2014 that key does not exist on this tool, so it is dropped and the paid call falls back to the whole default wishlist selection, charging for far more than the one lead the user agreed to. it is always the `leadIds` ARRAY, even for a single lead", "Drops the pinned `lens.id` between gates, so gate 4 enriches against a different lens than the one the user just saw", "Ends the completed walkthrough without the `keep_going` cheat-sheet \u2014 the buttons disappear with the tour, so a user who was never told what to TYPE learned to click a tutorial and nothing about using Leadbay tomorrow", "Invents phrases for the cheat-sheet, or rewords them into something that sounds nicer but doesn't match the tool's real triggers \u2014 teaching a phrase that doesn't route is worse than teaching none", "SENDS the gate 3 draft, or offers to send it \u2014 the walkthrough drafts and stops there; the email is the user's to judge, and nothing leaves the chat", "Passes `enrich:true` to `leadbay_prepare_outreach` at gate 3 \u2014 that launches a PAID contact reveal off the back of a DRAFT click, spending credits the user never agreed to", "Invents a contact NAME for the gate 3 draft \u2014 `recommended_contact` still has null email/name at that point, so the draft is addressed to the job TITLE; a fabricated name is the one thing that makes the whole draft untrustworthy", "Treats the null email at gate 3 as a failure \u2014 apologising for it, retrying, or calling another tool to fill it in. It is the setup for gate 4 \u2014 an email written, nobody to send it to yet", "Pastes the drafted email into chat prose alongside `message_compose_v1` instead of letting the composer BE the answer", "Enriches leads other than the one it drafted for at gate 3 \u2014 gate 4 reveals the person that email is going to, so it is scoped to that ONE lead, one contact, one credit", "Renders the cheat-sheet on the exit and stops there, dropping the 1:1 offer \u2014 the observed failure is that the agent feels finished once the table is on screen, so the user who just stepped out never hears about the help that would bring them back. ENDING B is not complete without the offer, and the offer goes LAST", "Treats the exit click as ENDING C (typed off-script) and closes in silence, or treats a typed request as ENDING B and buries their real answer under a cheat-sheet and a booking link", "Turns the exit offer into a pitch \u2014 several sentences, a re-opened gate, or an argument for finishing the tour. They said they were done; it is one line and a link", "Fires the 1:1 offer mid-tour, or at a user who left by TYPING a different request \u2014 a booking link on top of their real question is an interruption, not an offer", "Runs the four gates at a user whose actual problem is SETUP \u2014 the connector isn't installed, they can't sign in, or their Leadbay tools aren't appearing. The tour assumes a working connection and cannot fix any of it; the setup guide can", "Pastes the setup-guide link mid-tour, between gates, instead of once at the closing \u2014 a link in the middle of the walkthrough invites the user to leave the thing they're doing"] },
|
|
25804
26725
|
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"] },
|
|
25805
26726
|
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"] },
|
|
25806
26727
|
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"] },
|
|
@@ -25824,6 +26745,7 @@ var PROMPT_CATALOG_BULLETS = {
|
|
|
25824
26745
|
leadbay_daily_check_in: `- \`leadbay_daily_check_in\`: Morning DISCOVERY workflow \u2014 new leads from the lens wishlist. Trigger on "show me leads", "what's new today", "let's prospect", "run my check-in", "my morning check-in", "I do this every day", "every morning". Recurrence language always means this prompt. Do NOT trigger on follow-up phrasings ("follow up", "before my trip") \u2014 those go to \`leadbay_followup_check_in\`.`,
|
|
25825
26746
|
leadbay_extend_my_lens: `- \`leadbay_extend_my_lens\` (optional args: extra_count): Add more leads to the current lens on demand \u2014 for users whose appetite exceeds the standard daily fill. The agent picks seeds silently from what's already on the lens, fires the extra refill, and surfaces the queue confirmation. The user never reviews the seed list.`,
|
|
25826
26747
|
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\`.`,
|
|
26748
|
+
leadbay_getting_started: `- \`leadbay_getting_started\`: Guided first-run walkthrough \u2014 four clicks that actually use Leadbay: check the account, pull today's leads, draft a first email to the top one, then reveal who to send it to. Use when the user is new or asks to be SHOWN how Leadbay works ("walk me through Leadbay", "I'm new", "how do I use this", "give me a tour"). Don't use it for orientation prose with no clicking \u2014 that's leadbay_prospecting_overview.`,
|
|
25827
26749
|
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.`,
|
|
25828
26750
|
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.`,
|
|
25829
26751
|
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.`,
|
|
@@ -26104,6 +27026,15 @@ var CATALOG = [
|
|
|
26104
27026
|
)
|
|
26105
27027
|
];
|
|
26106
27028
|
}
|
|
27029
|
+
},
|
|
27030
|
+
{
|
|
27031
|
+
// Guided first-run walkthrough (issue #3952). No arguments — the tour is
|
|
27032
|
+
// the same for every new user, and asking a brand-new user to parameterize
|
|
27033
|
+
// their own onboarding defeats the point.
|
|
27034
|
+
name: "leadbay_getting_started",
|
|
27035
|
+
description: PROMPT_META.leadbay_getting_started.short_description,
|
|
27036
|
+
arguments: [],
|
|
27037
|
+
render: () => [userMessage(leadbay_getting_started2)]
|
|
26107
27038
|
}
|
|
26108
27039
|
];
|
|
26109
27040
|
function listPrompts() {
|
|
@@ -26992,6 +27923,7 @@ function buildScoringParagraph(has) {
|
|
|
26992
27923
|
}
|
|
26993
27924
|
return base;
|
|
26994
27925
|
}
|
|
27926
|
+
var FIRST_RUN_ROUTING = 'FIRST RUN \u2014 when the user asks to be SHOWN how Leadbay works ("walk me through Leadbay", "I\'m new", "how do I use this", "getting started", "give me a tour", "I just installed this"), invoke the `leadbay_getting_started` prompt via `prompts/get` and follow it. Do NOT improvise your own overview, tour, or summary of the product \u2014 it ships a five-gate walkthrough where each gate is a single-option choice widget the user clicks, so they learn by doing. Writing your own prose tour instead replaces the thing they asked for with a lecture.';
|
|
26995
27927
|
function buildStartHereParagraph(has) {
|
|
26996
27928
|
const base = "Start with leadbay_account_status to see the user's state, then leadbay_pull_leads to surface fresh leads. Use leadbay_research_lead_by_id to dig into one lead deeply (qualification answers, signals, contacts).";
|
|
26997
27929
|
const compositeNames = ["bulk_qualify_leads", "adjust_audience", "refine_prompt", "enrich_titles"].filter((n) => has(`leadbay_${n}`));
|
|
@@ -27087,6 +28019,7 @@ function buildServerInstructions(exposed) {
|
|
|
27087
28019
|
parts.push(QUOTA_TOPUP);
|
|
27088
28020
|
parts.push(TRANSIENT_401);
|
|
27089
28021
|
parts.push(buildScoringParagraph(has));
|
|
28022
|
+
parts.push(FIRST_RUN_ROUTING);
|
|
27090
28023
|
parts.push(buildStartHereParagraph(has));
|
|
27091
28024
|
parts.push(buildRhythmParagraph(has));
|
|
27092
28025
|
const updateParagraph = buildUpdateAvailableParagraph(has);
|
|
@@ -29226,7 +30159,7 @@ var OAUTH_BASE_URLS = {
|
|
|
29226
30159
|
fr: "https://staging.api.leadbay.app"
|
|
29227
30160
|
}
|
|
29228
30161
|
};
|
|
29229
|
-
var VERSION = "0.
|
|
30162
|
+
var VERSION = "0.29.0";
|
|
29230
30163
|
var HELP = `
|
|
29231
30164
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
29232
30165
|
|