@leadbay/mcp 0.11.0 → 0.12.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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog — @leadbay/mcp
2
2
 
3
+ ## 0.12.0 — 2026-05-21
4
+
5
+ Campaign and field-sales workflow release.
6
+
7
+ - **Campaign workflows**: adds campaign creation/listing, add-leads, progression summaries, and a `leadbay_campaign_call_sheet` composite that returns phone-ready, LinkedIn-ready, and map-ready lead/contact payloads.
8
+ - **Agent routing + skills**: adds the `leadbay_work_campaign`, `leadbay_plan_tour_in_city`, and `leadbay_setup_team_prospecting` prompt/skill flows so agents start with readiness checks, route to the right workflow tool, and keep outreach reporting grounded in verified user action.
9
+ - **Progression accuracy**: contacted/already-contacted summaries now use outreach/prospecting signals instead of treating contact coverage as outreach completion.
10
+ - **Coverage**: adds workflow audits, prompt-eval coverage for `leadbay_work_campaign`, live campaign smoke coverage, and focused unit tests for the new campaign progression/call-sheet composites.
11
+ - **Pin bumps**: every `@leadbay/mcp@0.11` install/runtime reference in docs, generated client config, DXT, and Claude plugin metadata is now `@0.12`.
12
+
3
13
  ## 0.11.0 — 2026-05-20
4
14
 
5
15
  In-server auto-update flow: the MCP server now self-polls GitHub releases (24h throttle, ETag-aware, in-flight guarded) and surfaces an `update_available` block on `leadbay_account_status` when a newer version is published — both at boot AND on every tool call, so long-running Claude Desktop sessions still pick up new releases without restart.
package/README.md CHANGED
@@ -24,7 +24,7 @@ A Model Context Protocol server that lets Claude Desktop, Cursor, Claude Code, a
24
24
  ## 1. Install (one command)
25
25
 
26
26
  ```bash
27
- npx -y @leadbay/mcp@0.11 install --email you@yourcompany.com --region us
27
+ npx -y @leadbay/mcp@0.12 install --email you@yourcompany.com --region us
28
28
  # (you'll be prompted for your password — it's not echoed)
29
29
  ```
30
30
 
@@ -67,14 +67,14 @@ Claude Desktop 2026 ships the DXT (Desktop Extension) system — the legacy `cla
67
67
 
68
68
  If you installed Node from the official [nodejs.org](https://nodejs.org) `.pkg`, `/usr/local/lib/node_modules` is root-owned. Any of these works:
69
69
 
70
- - **Use `npx` (recommended, no global install):** all examples above use `npx -y @leadbay/mcp@0.11 ...` — no global install needed.
70
+ - **Use `npx` (recommended, no global install):** all examples above use `npx -y @leadbay/mcp@0.12 ...` — no global install needed.
71
71
  - **`sudo npm install -g @leadbay/mcp`** (enter your macOS password).
72
72
  - **Use a Node version manager** — [nvm](https://github.com/nvm-sh/nvm), [volta](https://volta.sh), [fnm](https://github.com/Schniz/fnm). They install Node under your home directory, so `npm install -g` works without sudo.
73
73
 
74
74
  ### If you'd rather mint a token without auto-install
75
75
 
76
76
  ```bash
77
- npx -y @leadbay/mcp@0.11 login \
77
+ npx -y @leadbay/mcp@0.12 login \
78
78
  --email you@yourcompany.com \
79
79
  --region us
80
80
  ```
@@ -92,7 +92,7 @@ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) o
92
92
  "mcpServers": {
93
93
  "leadbay": {
94
94
  "command": "npx",
95
- "args": ["-y", "@leadbay/mcp@0.11"],
95
+ "args": ["-y", "@leadbay/mcp@0.12"],
96
96
  "env": {
97
97
  "LEADBAY_TOKEN": "<paste-token-from-step-1>",
98
98
  "LEADBAY_REGION": "us"
@@ -113,7 +113,7 @@ In Cursor settings, add the MCP server:
113
113
  "mcp.servers": {
114
114
  "leadbay": {
115
115
  "command": "npx",
116
- "args": ["-y", "@leadbay/mcp@0.11"],
116
+ "args": ["-y", "@leadbay/mcp@0.12"],
117
117
  "env": { "LEADBAY_TOKEN": "<paste-token>", "LEADBAY_REGION": "us" }
118
118
  }
119
119
  }
@@ -126,7 +126,7 @@ In Cursor settings, add the MCP server:
126
126
  claude mcp add leadbay --scope user \
127
127
  --env LEADBAY_TOKEN=<paste-token> \
128
128
  --env LEADBAY_REGION=us \
129
- -- npx -y @leadbay/mcp@0.11
129
+ -- npx -y @leadbay/mcp@0.12
130
130
  ```
131
131
 
132
132
  > **`--scope user`** registers Leadbay globally for your account (visible from any project). Without it, `claude mcp add` defaults to project-local scope and the server only appears in conversations opened from the directory where you ran the command.
@@ -138,7 +138,7 @@ claude mcp add leadbay --scope user \
138
138
  Before starting Claude, run:
139
139
 
140
140
  ```bash
141
- LEADBAY_TOKEN=<paste-token> npx -y @leadbay/mcp@0.11 doctor
141
+ LEADBAY_TOKEN=<paste-token> npx -y @leadbay/mcp@0.12 doctor
142
142
  ```
143
143
 
144
144
  Expected output:
@@ -366,14 +366,14 @@ The user's literal text replaces `verification.ref` in the outreach record, and
366
366
  | `No enrichment credits remaining` | Out of quota | Contact Leadbay support to extend quota |
367
367
  | Claude Desktop "loading forever" on first use | `npx` cold-start fetching the package | First run takes ~10s. Prefer `npm install -g @leadbay/mcp` for faster startup. |
368
368
  | Claude Desktop doesn't show Leadbay tools | Server crashed at startup | Check `~/Library/Logs/Claude/mcp*.log` (macOS) or `%APPDATA%\Claude\logs\mcp*.log` (Windows). |
369
- | Claude Code can't find Leadbay in a new conversation | MCP server installed at project scope (default before 0.3.0) | Re-run with `--scope user`: `claude mcp remove leadbay && claude mcp add leadbay --scope user --env LEADBAY_TOKEN=… --env LEADBAY_REGION=us -- npx -y @leadbay/mcp@0.11` |
369
+ | Claude Code can't find Leadbay in a new conversation | MCP server installed at project scope (default before 0.3.0) | Re-run with `--scope user`: `claude mcp remove leadbay && claude mcp add leadbay --scope user --env LEADBAY_TOKEN=… --env LEADBAY_REGION=us -- npx -y @leadbay/mcp@0.12` |
370
370
  | Agent reports "tool not found" for `refine_prompt` / `adjust_audience` etc. | Pre-0.3.0 install with `LEADBAY_MCP_WRITE` unset (writes were off) | Either re-run `npx @leadbay/mcp install` or remove `LEADBAY_MCP_WRITE=0` from your client config (writes are on by default in 0.3.0+) |
371
371
 
372
372
  ## 5. Upgrade & rotation
373
373
 
374
- **Upgrade**: change the pinned minor in your config, e.g. `"@leadbay/mcp@0.2"` → `"@leadbay/mcp@0.11"`, then restart the client. **0.3.0 enables composite write tools by default** — see [MIGRATION.md](./MIGRATION.md). See also the [changelog](https://github.com/leadbay/leadclaw/releases).
374
+ **Upgrade**: change the pinned minor in your config, e.g. `"@leadbay/mcp@0.2"` → `"@leadbay/mcp@0.12"`, then restart the client. **0.3.0 enables composite write tools by default** — see [MIGRATION.md](./MIGRATION.md). See also the [changelog](https://github.com/leadbay/leadclaw/releases).
375
375
 
376
- **Rotate token**: re-run `npx -y @leadbay/mcp@0.11 install --email you@yourcompany.com --region us` (or `login`) — the new session token replaces the old one in your MCP client config, and logging in again invalidates the prior session on most session backends.
376
+ **Rotate token**: re-run `npx -y @leadbay/mcp@0.12 install --email you@yourcompany.com --region us` (or `login`) — the new session token replaces the old one in your MCP client config, and logging in again invalidates the prior session on most session backends.
377
377
 
378
378
  ## 6. Advanced
379
379
 
@@ -486,7 +486,7 @@ After your first authenticated call, your PostHog `distinctId` is set to your Le
486
486
  "mcpServers": {
487
487
  "leadbay": {
488
488
  "command": "npx",
489
- "args": ["-y", "@leadbay/mcp@0.11"],
489
+ "args": ["-y", "@leadbay/mcp@0.12"],
490
490
  "env": {
491
491
  "LEADBAY_TOKEN": "u.…",
492
492
  "LEADBAY_REGION": "us",
package/dist/bin.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  granularReadTools,
10
10
  granularWriteTools,
11
11
  resolveRegion
12
- } from "./chunk-MZZMIZXA.js";
12
+ } from "./chunk-J2Y4LCFM.js";
13
13
 
14
14
  // src/bin.ts
15
15
  import { realpathSync } from "fs";
@@ -361,6 +361,55 @@ After I answer, call \`leadbay_report_outreach({lead_id: '{{arg:lead_id}}', note
361
361
  # PHASE 3 \u2014 CONFIRM
362
362
  Tell me the outreach was logged, name the verification.source used, and surface the response's \`outreach_id\` if present so I can refer back to it.
363
363
  `;
364
+ var leadbay_plan_tour_in_city = `
365
+ Plan a field sales tour for me in **{{arg:city}}**{{arg:date_paren}}.
366
+
367
+ 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.
368
+
369
+ 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.
370
+
371
+
372
+ # PHASE 1 \u2014 BUILD THE ITINERARY
373
+
374
+ Call \`leadbay_tour_plan({city: "{{arg:city}}"})\` with the default counts (6 follow-ups + 6 discover). If the response is \`status: "ambiguous_locations"\`, surface the candidates and ask me to pick one, then re-call with \`city_id\`.
375
+
376
+ Split the returned \`monitor_leads\` into two buckets client-side using \`last_monitor_action\`:
377
+
378
+ - **Customers** \u2014 leads with any \`last_monitor_action\` history (CONTACTED, MEETING_BOOKED, etc.). Treat as known accounts with prior engagement.
379
+ - **Qualified prospects** \u2014 leads with high \`ai_agent_lead_score\` (or \`score\`) but no recent action.
380
+
381
+ \`discover_leads\` are the **New** bucket.
382
+
383
+ Aim for a 3+3+3 split if possible. If the customers bucket has fewer than 3, fill from qualified. If discover_filter_note indicates a low match ratio for the city, mention it: "Only N/30 fresh leads matched your city" \u2014 better honest than padded.
384
+
385
+ # PHASE 2 \u2014 RENDER THE MAP
386
+
387
+ Route the union of \`monitor_leads + discover_leads\` into \`places_map_display_v0\` (when the host exposes it). Per-lead \`notes\` string:
388
+
389
+ - \`\u2605 Customer \u2014 <one-sentence sector + why-now>. Reach <name>, <role>: <bare phone>, <bare email>.\`
390
+ - \`\u2605 Qualified \u2014 <one-sentence>. Reach <name>...\`
391
+ - \`\u2726 New \u2014 <one-sentence>. Reach <name>...\`
392
+
393
+ Skip leads with \`location.pos === null\` (no coordinates \u2192 no pin) \u2014 list them as "+ N leads without coordinates" below the widget.
394
+
395
+ Below the widget, emit a chat-prose summary grouped by mode (Customers / Qualified / New), with LinkedIn-linked contact name + bare phone/email pills per lead. Use the canonical \`linking/contact-linkedin\` rules.
396
+
397
+ # PHASE 3 \u2014 DRAFT IN-AREA OUTREACH (optional, ask first)
398
+
399
+ After the map, ask me ONCE: "Want me to draft 'I'll be in {{arg:city}}{{arg:date_paren}}' outreach for the top accounts?" If I say yes, for each of the top 3 leads (1 Customer / 1 Qualified / 1 New), call \`leadbay_prepare_outreach(leadId)\` and route the draft through \`message_compose_v1\` with a single variant labeled "In-area visit" \u2014 body opens with the visit context, references the AI-summary angle, ends with a clear ask (15-min coffee / on-site stopover).
400
+
401
+ Serialize the prepare_outreach calls (max 3 in parallel \u2014 see the long-running-tools rule).
402
+
403
+ # PHASE 4 \u2014 PERSIST AS A CAMPAIGN (optional, ask first)
404
+
405
+ After drafts, ask me ONCE: "Save these 9 accounts as a campaign called '**{{arg:city}} Tour{{arg:date_dash}}**'?" If I say yes, call \`leadbay_create_campaign({lead_ids: [...all_nine_lead_ids], name: "{{arg:city}} Tour{{arg:date_dash}}"})\`. Surface the returned \`id\` + \`name\` as a confirmation line, and offer the NEXT STEPS chip "View progression" (which routes to \`leadbay_campaign_progression\`).
406
+
407
+ If I declined the campaign step, end the turn \u2014 the map + drafts are enough for an ad-hoc trip.
408
+
409
+ # PHASE 5 \u2014 STOP
410
+
411
+ Done. The map is the surface; the drafts are the action; the campaign is the persistence layer for managerial follow-up after the trip.
412
+ `;
364
413
  var leadbay_prospecting_overview = `
365
414
  # Leadbay Prospecting \u2014 Orientation
366
415
 
@@ -736,6 +785,160 @@ Place a 2\u20133 sentence summary ABOVE the card with:
736
785
 
737
786
  The card itself handles the signal callouts (\`\u{1F4C8} business signals\`, \`\u{1F4A1} prospecting clues\`). Do NOT re-narrate signals in prose above the card \u2014 that's what the card sections are for. Be honest about uncertainty: if any field is missing from tool responses, say "not surfaced by qualification" rather than guessing.
738
787
  `;
788
+ var leadbay_setup_team_prospecting = `
789
+ Set up manager-led prospecting for me: turn the audience into a lens, validate candidates, then persist as named campaigns.
790
+
791
+ Audience: **{{arg:audience}}**
792
+ {{arg:rep_split_block}}
793
+
794
+ 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.
795
+
796
+ 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.
797
+
798
+
799
+ # PHASE 1 \u2014 INTERPRET INTENT INTO A LENS
800
+
801
+ Call \`leadbay_refine_prompt({user_prompt: "{{arg:audience}}"})\`. This handles the clarification protocol natively \u2014 if the system needs more info (e.g. industry disambiguation, geography precision), it returns \`status: "clarification_needed"\` with options. Surface those to me; on my answer, re-call \`leadbay_refine_prompt\` until the prompt converges.
802
+
803
+ When the prompt has converged, call \`leadbay_create_lens({user_prompt: <refined>, name: "<short descriptive name>"})\` to create a draft lens, then \`leadbay_promote_lens({lensId})\` to make it the active lens.
804
+
805
+ # PHASE 2 \u2014 PULL + VALIDATE CANDIDATES
806
+
807
+ Call \`leadbay_pull_leads({count: 20, lensId: <the new lens id>})\` to surface the top 20 candidates from the freshly-created lens. Render with the canonical \`pull_leads\` table layout.
808
+
809
+ Ask me ONCE: "Want me to deep-research the top N for validation?" If yes, call \`leadbay_research_lead_by_id\` serialized over the top 3-5 (one at a time, max 3 in parallel per the long-running-tools rule). Surface a research summary per lead.
810
+
811
+ Then ask me ONCE: "Which of these should we drop?" If I name leads to drop, exclude them from the working set. The remaining is the validated set.
812
+
813
+ # PHASE 3 \u2014 DECIDE THE CAMPAIGN SHAPE
814
+
815
+ If I provided a \`rep_split\` ("one campaign per rep: John gets Tulsa, Sarah gets OKC"), partition the validated leads accordingly. If I didn't, ask ONCE: "Create one campaign for the whole batch, or split per rep / region / sector?" \u2014 surface 2-4 options via \`ask_user_input_v0\` when available, else as a bulleted list.
816
+
817
+ For each campaign-shape decision, derive a name. Templates:
818
+ - Whole batch: \`"<lens-name> \u2013 <YYYY-MM-DD>"\`
819
+ - Per rep: \`"<lens-name> \u2013 <RepName>"\`
820
+ - Per region: \`"<lens-name> \u2013 <RegionName>"\`
821
+
822
+ # PHASE 4 \u2014 PERSIST
823
+
824
+ For each campaign-shape partition, call \`leadbay_create_campaign({lead_ids: [...partition], name: "<derived>"})\`. Surface the returned \`id\` + \`name\` per campaign as a confirmation line.
825
+
826
+ # PHASE 5 \u2014 BE HONEST ABOUT SCOPE
827
+
828
+ Once the campaigns are created, surface this caveat in plain prose:
829
+
830
+ > Campaign visibility is currently scoped to the user who CREATED the campaign \u2014 the reps won't see these in their own MCP \`leadbay_list_campaigns\` calls. They CAN see them in the web UI at app.leadbay.ai \u2192 Campaigns. Cross-user MCP visibility would need backend work; flag this as a #3630 US3 product gap if your reps work primarily through MCP.
831
+
832
+ End with a NEXT STEPS chip via \`ask_user_input_v0\`: "View progression on one of these now?" \u2192 routes to \`leadbay_campaign_progression\`.
833
+
834
+ # PHASE 6 \u2014 STOP
835
+
836
+ Done. The lens is live, the validated cohort is persisted as named campaigns, and the manager knows where the cross-user-visibility gap is.
837
+ `;
838
+ var leadbay_work_campaign = `
839
+ Work my **{{arg:campaign_or_default}}** campaign as an outreach session{{arg:mode_paren}}.
840
+
841
+ 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.
842
+
843
+ 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.
844
+
845
+
846
+ # PHASE 0 \u2014 PICK THE CAMPAIGN
847
+
848
+ If I gave you a name or id, resolve it. Otherwise call \`leadbay_list_campaigns()\` and surface the active campaigns as a \`single_select\` via \`ask_user_input_v0\` (cap at 4 \u2014 sort by \`updated_at\` desc, archived hidden):
849
+
850
+ > Which campaign do you want to work?
851
+ > - <Name 1> \xB7 <N leads> \xB7 last touched <date>
852
+ > - <Name 2> \xB7 <N leads> \xB7 last touched <date>
853
+ > - \u2026
854
+
855
+ When the user picks, capture the \`campaign_id\`. If \`{{arg:campaign}}\` is a name, fuzzy-match against \`campaigns[].campaign.name\`. On ambiguous matches, surface a \`single_select\` instead of guessing.
856
+
857
+ # PHASE 1 \u2014 FETCH + ASSESS READINESS (the load-bearing phase)
858
+
859
+ Call \`leadbay_campaign_call_sheet({campaign_id})\`. The response carries \`summary\` + \`readiness\` \u2014 use them to figure out what the user CAN actually do today, then PROPOSE the right session mode rather than auto-rendering.
860
+
861
+ **Read the summary numbers**:
862
+ - \`total_leads\`, \`total_contacts\`
863
+ - \`leads_with_phone\` \u2014 can call from this many leads
864
+ - \`leads_with_email\` \u2014 can email this many
865
+ - \`leads_with_coords\` \u2014 can map this many
866
+ - \`leads_without_contacts\` \u2014 these need enrichment before any outreach is possible
867
+ - \`leads_already_contacted\` \u2014 these have prior touches; the rep may want to skip them for cold work
868
+
869
+ **Read the \`readiness\` booleans** (pre-computed thresholds):
870
+ - \`ready_for_calling\` (phone coverage \u226560%) \u2014 call session viable
871
+ - \`ready_for_emailing\` (email coverage \u226560%) \u2014 email session viable
872
+ - \`needs_enrichment\` (\u226530% no-contacts OR both phone+email coverage <40%) \u2014 enrichment recommended first
873
+ - \`travel_friendly\` (\u22655 geocoded leads AND coord coverage \u226560%) \u2014 map mode worth proposing
874
+
875
+ **One-line situation report** (always emit BEFORE the proposal):
876
+
877
+ \`\`\`
878
+ \u{1F4CB} <total_leads> leads \xB7 \u{1F4DE} <leads_with_phone> with a phone \xB7 \u2709 <leads_with_email> with an email \xB7 \u{1F5FA} <leads_with_coords> with coords \xB7 \u{1F534} <leads_without_contacts> need enrichment \xB7 \u2705 <leads_already_contacted> already touched
879
+ \`\`\`
880
+
881
+ **Then PROPOSE the right modes via \`ask_user_input_v0\`** (2-4 options, sorted by what makes the most sense for THIS campaign's data):
882
+
883
+ - "\u{1F4DE} Start calling now" \u2014 IF \`ready_for_calling\`. Top option when phones are there.
884
+ - "\u2709 Email session instead" \u2014 IF \`ready_for_emailing\` AND \`email_ratio > phone_ratio\`. Don't surface this when calling is more obvious.
885
+ - "\u{1F527} Enrich titles first" \u2014 IF \`needs_enrichment\`. Top option when most leads have no contacts. Phrase as "<N> leads have no reachable contact yet \u2014 enrich titles before we start?" so the user understands the cost.
886
+ - "\u{1F5FA} View on a map" \u2014 IF \`travel_friendly\` **AND** the user hasn't previously signaled disinterest in maps (check your conversation memory; if you've seen the user dismiss map renders before in this session or saved a "no maps" preference, drop this option).
887
+
888
+ If the MCP prompt argument \`mode\` was actually supplied, skip the proposal and jump to the matching mode below. If \`mode\` was omitted, do not treat \`call_sheet\` as implicit user consent \u2014 propose first.
889
+
890
+ # PHASE 2A \u2014 CALL-SHEET MODE (default after "\u{1F4DE} Start calling now")
891
+
892
+ Render per the \`leadbay_campaign_call_sheet\` RENDERING block \u2014 one CARD per lead with the 4-col contact table (Contact / Phone / Role / Recent). The phone in column 2 MUST be \`[bare](tel:URL)\` (use \`contact.phone_tel_url\` verbatim \u2014 the composite has already canonicalized it). The contact name in column 1 MUST be \`[Name](linkedin_url)\`. Email stacks under the name when present (\`\u2709 [email](mailto_url)\`). Recent stacks \`\u{1F4DD} last note\` + \`\u{1F4DE} last_action_headline\`.
893
+
894
+ End the turn with the standby line:
895
+
896
+ > Ready to start calling. Tell me what happened after each call \u2014 I'll record the note + outcome.
897
+
898
+ # PHASE 2B \u2014 EMAIL-SHEET MODE (after "\u2709 Email session instead")
899
+
900
+ Same data, slightly different render emphasis: drop the Phone column, put \`\u2709 [email](mailto_url)\` as column 2. Below each lead's table, generate a SUGGESTED short email draft per the next-step \u2014 but DON'T send. Drafts are for the user to copy-paste / send themselves.
901
+
902
+ # PHASE 2C \u2014 ENRICH-FIRST MODE (after "\u{1F527} Enrich titles first")
903
+
904
+ Extract \`leadIds\` from \`sheet.leads[].lead_id\`, then call \`leadbay_enrich_titles({leadIds, \u2026})\` (consult its description for titles / email / phone selection; do not pass \`campaign_id\`, because that is not part of the tool schema). Surface progress to the user. When complete, automatically loop back to Phase 1 (re-fetch the call sheet, re-assess readiness, re-propose).
905
+
906
+ # PHASE 2D \u2014 MAP MODE (after "\u{1F5FA} View on a map")
907
+
908
+ Pass \`response.map_locations\` directly to \`places_map_display_v0\` \u2014 the composite has already built the per-pin notes string with the top contact's phone inline. After the widget, emit the standard 4-col card list anyway so the rich detail is still scannable.
909
+
910
+ # PHASE 3 \u2014 RECORD OUTCOMES, ONE AT A TIME (after the user starts dictating)
911
+
912
+ When the user says something like *"Called Bree, voicemail, trying again Tuesday"* or *"Talked to John, wants pricing sent next week"*, parse:
913
+
914
+ 1. **Which lead** \u2014 by company name OR contact name (cross-reference with the cards you just rendered).
915
+ 2. **The note** \u2014 the user's exact words about what happened (the SDR's voice \u2014 don't paraphrase).
916
+ 3. **The outcome** \u2014 pick ONE of these four epilogue values based on what the user said:
917
+ - \`STILL_CHASING\` \u2014 pursuing, no decision yet ("trying again", "they'll get back to me")
918
+ - \`COULD_NOT_REACH_STILL_TRYING\` \u2014 voicemail, no answer, wrong number, gatekeeper blocked
919
+ - \`INTEREST_VALIDATED_OR_MEETING_PLANED\` \u2014 meeting booked, quote requested, "send me more info"
920
+ - \`NOT_INTERESTED_LOST\` \u2014 declined, "not now", "not a fit", "remove from list"
921
+
922
+ Call \`leadbay_report_outreach({lead_id, note: <user's words>, epilogue_status: <picked>, verification: {source: "user_confirmed", ref: <user's exact words verbatim>}})\`. Confirm in ONE line: *"\u2705 Logged: <Company> \u2192 <epilogue>. Next?"*
923
+
924
+ Then wait for the next dictation. Don't ask "anything else?" \u2014 just acknowledge and wait.
925
+
926
+ # PHASE 4 \u2014 STOP
927
+
928
+ When the user says "done" / "that's it" / "wrapping up" / similar, surface a session summary chip:
929
+
930
+ > Session complete \u2014 N calls logged: X meetings booked \xB7 Y still chasing \xB7 Z couldn't reach \xB7 W declined.
931
+
932
+ Optional: offer to review the \`leadbay_campaign_progression\` for the same campaign to see the updated counts.
933
+
934
+ # Iron laws
935
+
936
+ - The \`verification\` field on \`leadbay_report_outreach\` is REQUIRED. For calls (no message id), always use \`{source: "user_confirmed", ref: <user's verbatim words>}\`. Skipping it is forbidden; fabricating a gmail_message_id for a call is forbidden.
937
+ - ONE call \u2192 ONE \`leadbay_report_outreach\` invocation. Don't batch; each call has its own note + outcome.
938
+ - Map mode is OPT-IN, never automatic. The user invokes it via the proposal options or by passing \`mode=map\`.
939
+ - If you've seen the user dismiss / dislike map renders earlier in the session, don't propose map mode again.
940
+ - If the user dictates an outcome that doesn't cleanly map to one of the four epilogue values, ASK ONCE before guessing.
941
+ `;
739
942
  var PROMPT_META = {
740
943
  leadbay_daily_check_in: { "name": "leadbay_daily_check_in", "short_description": `Run the canonical daily check-in: account state, fresh batch, triage
741
944
  top 10, deep-dive every promising one, offer contact enrichment. The
@@ -755,6 +958,7 @@ anything that implies pre-existing pipeline context.
755
958
  `, "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)'] },
756
959
  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"] },
757
960
  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"] },
961
+ leadbay_plan_tour_in_city: { "name": "leadbay_plan_tour_in_city", "short_description": "Plan a field sales tour: in one flow, surface follow-ups + fresh\nDiscover leads in the target city via `leadbay_tour_plan`, render\nto a map, draft in-area outreach via `leadbay_prepare_outreach`,\nand optionally persist the selected accounts as a named campaign\nvia `leadbay_create_campaign`. Closes #3630 US1 end-to-end.\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"] },
758
962
  leadbay_prospecting_overview: { "name": "leadbay_prospecting_overview", "short_description": `Orientation for working with Leadbay from any host \u2014 discovery vs.
759
963
  follow-up, the outreach loop, outcome recording, imports, pushback /
760
964
  snooze, and the connected-outreach-tool registry. Trigger when the
@@ -764,7 +968,9 @@ should I follow up on" to "I'll send via lemlist".
764
968
  `, "arguments": [], "expected_calls": ["leadbay_account_status", "leadbay_pull_leads", "leadbay_pull_followups", "leadbay_research_lead_by_id", "leadbay_research_lead_by_name_fuzzy", "leadbay_prepare_outreach", "leadbay_report_outreach", "leadbay_set_pushback", "leadbay_remove_pushback", "leadbay_bulk_qualify_leads", "leadbay_enrich_titles", "leadbay_import_leads", "leadbay_add_note", "leadbay_adjust_audience"], "failure_modes": ['Drives outreach without asking the user "how did it go?" afterwards \u2014 leaving prospecting_actions and epilogue_status stale', 'Says "epilogue" in user-facing dialogue instead of "outcome"', 'Says "Monitor" in user-facing dialogue instead of "follow-ups"', 'Treats a "not now / next quarter" reply as a note instead of routing through the pushback mechanism', "Drafts outreach in a generic format when the user has a connected sequencer (lemlist, Outreach.io, etc.) that has its own idiom", "Re-pulls leads without passing the captured lensId, allowing a backend lens shift to discard prior work", "Skips the STOP byproduct in any multi-step workflow it triggers", "Calls leadbay_pull_leads (Discover wishlist) for a follow-up query, or leadbay_pull_followups (Monitor view) for a discovery query \u2014 the two entry points read from different backend tables; the right orchestrators are leadbay_daily_check_in (discovery) and leadbay_followup_check_in (follow-up)"] },
765
969
  leadbay_qualify_top_n: { "name": "leadbay_qualify_top_n", "short_description": "Bulk-qualify the top N un-qualified leads in the active lens. Uses\nleadbay_bulk_qualify_leads with a sensible default budget.\n", "arguments": [{ "name": "count", "description": "How many leads to qualify (default 10, max 25). Higher counts may take 5+ minutes.", "required": false }], "expected_calls": ["leadbay_bulk_qualify_leads", "leadbay_qualify_status", "leadbay_pull_leads", "leadbay_research_lead_by_id"], "failure_modes": ["Picks a count larger than the user asked for (or larger than the max 25)", "Glosses over still-running leads in the summary instead of naming them", "Recommends a lead from the existing qualified pool instead of one from this batch's actual results", 'Replaces the canonical pull_leads table with prose when rendering the newly-qualified batch (the per-tool RENDERING block is the structural contract; "standouts" commentary sits above it)', "Expands the qualify-status sentence into a card or table instead of the one-line status-inline render"] },
766
970
  leadbay_refine_audience: { "name": "leadbay_refine_audience", "short_description": "Refine the kind of leads Leadbay surfaces beyond firmographics, with a\nfree-text instruction. Handles the clarification round-trip if the new\nprompt is ambiguous.\n", "arguments": [{ "name": "instruction", "description": "The refinement (e.g. 'focus on hospitals running their own IT'). Set to plain English.", "required": true }], "expected_calls": ["leadbay_refine_prompt", "leadbay_account_status"], "failure_modes": ["Calls leadbay_answer_clarification on the user's behalf instead of surfacing the clarification verbatim", "Glosses over the clarification options instead of presenting them as offered", "Promises immediate effect when status='applied' actually triggers an async intelligence recompute"] },
767
- leadbay_research_a_domain: { "name": "leadbay_research_a_domain", "short_description": "Import a company by domain and run deep qualification + research in one\npass. Use when a colleague mentions a name and you want everything Leadbay\nknows about it.\n", "arguments": [{ "name": "domain", "description": "The company's primary domain (e.g. 'acme.com'). Protocol/path are stripped.", "required": true }], "expected_calls": ["leadbay_import_and_qualify", "leadbay_research_lead_by_id"], "failure_modes": ["Fabricates qualification answers not present in any tool response", "Reports certainty about fit when qualification didn't actually run (e.g. quota_blocked)", "Skips the research step after import completes", "Renders the research_lead_by_id result as a freeform narrative instead of the canonical research-company-card layout (the card with header score bar, pill row, signal sections, contacts table is the structural contract; commentary belongs ABOVE or BELOW it)", "Enumerates every imported lead in prose instead of the terse single-record summary from the import-result rendering snippet"] }
971
+ leadbay_research_a_domain: { "name": "leadbay_research_a_domain", "short_description": "Import a company by domain and run deep qualification + research in one\npass. Use when a colleague mentions a name and you want everything Leadbay\nknows about it.\n", "arguments": [{ "name": "domain", "description": "The company's primary domain (e.g. 'acme.com'). Protocol/path are stripped.", "required": true }], "expected_calls": ["leadbay_import_and_qualify", "leadbay_research_lead_by_id"], "failure_modes": ["Fabricates qualification answers not present in any tool response", "Reports certainty about fit when qualification didn't actually run (e.g. quota_blocked)", "Skips the research step after import completes", "Renders the research_lead_by_id result as a freeform narrative instead of the canonical research-company-card layout (the card with header score bar, pill row, signal sections, contacts table is the structural contract; commentary belongs ABOVE or BELOW it)", "Enumerates every imported lead in prose instead of the terse single-record summary from the import-result rendering snippet"] },
972
+ leadbay_setup_team_prospecting: { "name": "leadbay_setup_team_prospecting", "short_description": "Manager-led prospecting setup: conversationally turn a natural-language\naudience ask into a Leadbay lens, validate the candidate leads, and\npersist them as one or more named campaigns the rep(s) can work\nthrough. Closes #3630 US3 end-to-end (within the current\ncreator-scoped campaign visibility model).\n", "arguments": [{ "name": "audience", "description": "Natural-language audience description (e.g. 'plumbing companies with 10-50 employees in Seine-Maritime'). The lens-creation step (`leadbay_refine_prompt` \u2192 `leadbay_create_lens`) interprets it.", "required": true }, { "name": "rep_split", "description": "Optional: how to split the validated leads into per-rep campaigns. Free text \u2014 e.g. 'split by city' or 'one campaign per rep: John gets Tulsa, Sarah gets OKC'.", "required": false }], "expected_calls": ["leadbay_refine_prompt", "leadbay_create_lens", "leadbay_promote_lens", "leadbay_pull_leads", "leadbay_research_lead_by_id", "leadbay_create_campaign", "leadbay_add_leads_to_campaign"], "failure_modes": ["Skips the validation step \u2014 creates a campaign of unvetted leads from a freshly-created lens without giving the manager a chance to drop weak fits", "Creates ONE campaign for all reps without asking about the split \u2014 the user explicitly mentioned per-rep distribution and the prompt should honor it", "Pretends the backend supports cross-user assignment \u2014 campaigns are owned by the caller (creator-scoped). Surface this honestly instead of fabricating an assignment model", "Asks ALL clarifying questions inline before tool calls \u2014 instead, run the lens refinement loop with `leadbay_refine_prompt` which handles the clarification protocol natively"] },
973
+ leadbay_work_campaign: { "name": "leadbay_work_campaign", "short_description": "Work a campaign as a real outreach session: pick the campaign,\nassess what the user has (phones / emails / coords), then PROPOSE\nthe right session mode (call sheet, email sheet, enrich titles\nfirst, map). After they pick, render \u2014 and as they dictate\noutcomes per lead, record both note + epilogue via\n`leadbay_report_outreach` in one round trip.\n", "arguments": [{ "name": "campaign", "description": "Campaign name (fuzzy match against your own campaigns) or campaign UUID. Omit to list and pick interactively.", "required": false }, { "name": "mode", "description": "Optional: skip the readiness-assessment proposal and jump directly into 'call_sheet' / 'email_sheet' / 'map' / 'enrich_first'. Omit (recommended) and let the prompt propose based on the data.", "required": false }], "expected_calls": ["leadbay_list_campaigns", "leadbay_campaign_call_sheet", "leadbay_enrich_titles", "leadbay_report_outreach"], "failure_modes": ["Renders the call sheet immediately without proposing the right mode \u2014 if 60% of leads have no contacts, calling is futile; enrich first. Always assess `readiness` first.", "Auto-renders the map widget without asking \u2014 maps are intrusive when the user just wants to scroll a list. Map mode is a proposed option, not a default.", "Proposes map mode after the user has previously said they don't like maps \u2014 check conversation memory before adding 'View on a map' to the options list.", "Calls `leadbay_campaign_progression` instead of `leadbay_campaign_call_sheet` \u2014 progression has counts but no phones / LinkedIn / call-ready data; the user can't actually dial from progression rows.", "Renders contacts WITHOUT making the phone number a `[bare](tel:URL)` link \u2014 on mobile that breaks one-tap calling, which is the whole point of the cheat sheet.", "Records outreach WITHOUT epilogue_status \u2014 leaves the lead's pipeline state unchanged; the rep then sees the same lead surfaced again next session.", "Records outreach WITHOUT verification \u2014 verification.source/ref is REQUIRED. For calls, pass `{source: 'user_confirmed', ref: <user's exact words>}`.", "Loops through ALL leads in a 50-lead campaign before recording any outreach \u2014 the call-then-record loop must be per-lead, not batched."] }
768
974
  };
769
975
  var PROMPT_CATALOG_HEADER = `This server exposes the following workflow prompts via \`prompts/list\` and \`prompts/get\`. Some MCP clients render them as slash commands; if your client does not, you (the agent) should invoke them directly via \`prompts/get\` when the user's request matches one of the triggers described below.`;
770
976
  var PROMPT_CATALOG_BULLETS = {
@@ -772,10 +978,13 @@ var PROMPT_CATALOG_BULLETS = {
772
978
  leadbay_followup_check_in: `- \`leadbay_followup_check_in\`: Run the canonical follow-up check-in: surface KNOWN leads from the Monitor view that need re-engagement today, ranked by AI urgency, with the canonical pull_followups table layout. Trigger when the user asks "follow up", "already known leads", "leads I haven't contacted", "leads in [city]", "before my trip", "this week", "this month", "what's overdue", "who should I re-engage", or anything that implies pre-existing pipeline context.`,
773
979
  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.`,
774
980
  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.`,
981
+ leadbay_plan_tour_in_city: `- \`leadbay_plan_tour_in_city\` (required args: city; optional args: date): Plan a field sales tour: in one flow, surface follow-ups + fresh Discover leads in the target city via \`leadbay_tour_plan\`, render to a map, draft in-area outreach via \`leadbay_prepare_outreach\`, and optionally persist the selected accounts as a named campaign via \`leadbay_create_campaign\`. Closes #3630 US1 end-to-end.`,
775
982
  leadbay_prospecting_overview: `- \`leadbay_prospecting_overview\`: Orientation for working with Leadbay from any host \u2014 discovery vs. follow-up, the outreach loop, outcome recording, imports, pushback / snooze, and the connected-outreach-tool registry. Trigger when the conversation involves Leadbay leads, prospecting, pipeline, follow-up, outreach, or lens / ICP \u2014 anything from "show me my leads" to "what should I follow up on" to "I'll send via lemlist".`,
776
983
  leadbay_qualify_top_n: `- \`leadbay_qualify_top_n\` (optional args: count): Bulk-qualify the top N un-qualified leads in the active lens. Uses leadbay_bulk_qualify_leads with a sensible default budget.`,
777
984
  leadbay_refine_audience: `- \`leadbay_refine_audience\` (required args: instruction): Refine the kind of leads Leadbay surfaces beyond firmographics, with a free-text instruction. Handles the clarification round-trip if the new prompt is ambiguous.`,
778
- leadbay_research_a_domain: `- \`leadbay_research_a_domain\` (required args: domain): Import a company by domain and run deep qualification + research in one pass. Use when a colleague mentions a name and you want everything Leadbay knows about it.`
985
+ leadbay_research_a_domain: `- \`leadbay_research_a_domain\` (required args: domain): Import a company by domain and run deep qualification + research in one pass. Use when a colleague mentions a name and you want everything Leadbay knows about it.`,
986
+ leadbay_setup_team_prospecting: `- \`leadbay_setup_team_prospecting\` (required args: audience; optional args: rep_split): Manager-led prospecting setup: conversationally turn a natural-language audience ask into a Leadbay lens, validate the candidate leads, and persist them as one or more named campaigns the rep(s) can work through. Closes #3630 US3 end-to-end (within the current creator-scoped campaign visibility model).`,
987
+ leadbay_work_campaign: `- \`leadbay_work_campaign\` (optional args: campaign, mode): Work a campaign as a real outreach session: pick the campaign, assess what the user has (phones / emails / coords), then PROPOSE the right session mode (call sheet, email sheet, enrich titles first, map). After they pick, render \u2014 and as they dictate outcomes per lead, record both note + epilogue via \`leadbay_report_outreach\` in one round trip.`
779
988
  };
780
989
 
781
990
  // src/prompts.ts
@@ -886,6 +1095,80 @@ var CATALOG = [
886
1095
  )
887
1096
  ]
888
1097
  },
1098
+ {
1099
+ name: "leadbay_plan_tour_in_city",
1100
+ description: PROMPT_META.leadbay_plan_tour_in_city.short_description,
1101
+ arguments: [
1102
+ {
1103
+ name: "city",
1104
+ description: "City or region the user is visiting (e.g. 'Limoges', 'Bay Area'). Used as the geo filter for both Monitor and Discover lookups.",
1105
+ required: true
1106
+ },
1107
+ {
1108
+ name: "date",
1109
+ description: "When the visit is (e.g. 'May 24', 'next Thursday'). Surfaced in the outreach drafts as 'I'll be in <city> on <date>'.",
1110
+ required: false
1111
+ }
1112
+ ],
1113
+ render: (args) => [
1114
+ userMessage(
1115
+ substitutePlaceholders(leadbay_plan_tour_in_city, {
1116
+ city: args.city ?? "<missing>",
1117
+ date_paren: args.date ? ` on ${args.date}` : "",
1118
+ date_dash: args.date ? ` \u2013 ${args.date}` : ""
1119
+ })
1120
+ )
1121
+ ]
1122
+ },
1123
+ {
1124
+ name: "leadbay_setup_team_prospecting",
1125
+ description: PROMPT_META.leadbay_setup_team_prospecting.short_description,
1126
+ arguments: [
1127
+ {
1128
+ name: "audience",
1129
+ description: "Natural-language audience description (e.g. 'plumbing companies with 10-50 employees in Seine-Maritime').",
1130
+ required: true
1131
+ },
1132
+ {
1133
+ name: "rep_split",
1134
+ description: "Optional: how to split validated leads into per-rep campaigns. Free text (e.g. 'split by city', 'one campaign per rep').",
1135
+ required: false
1136
+ }
1137
+ ],
1138
+ render: (args) => [
1139
+ userMessage(
1140
+ substitutePlaceholders(leadbay_setup_team_prospecting, {
1141
+ audience: args.audience ?? "<missing>",
1142
+ rep_split_block: args.rep_split ? `Rep split preference: **${args.rep_split}**
1143
+ ` : ""
1144
+ })
1145
+ )
1146
+ ]
1147
+ },
1148
+ {
1149
+ name: "leadbay_work_campaign",
1150
+ description: PROMPT_META.leadbay_work_campaign.short_description,
1151
+ arguments: [
1152
+ {
1153
+ name: "campaign",
1154
+ description: "Campaign name (fuzzy match) or campaign UUID. Omit to list and pick interactively.",
1155
+ required: false
1156
+ },
1157
+ {
1158
+ name: "mode",
1159
+ description: "Optional: skip readiness proposal and jump to 'call_sheet', 'email_sheet', 'map', or 'enrich_first'. Omit to let the prompt propose based on campaign data.",
1160
+ required: false
1161
+ }
1162
+ ],
1163
+ render: (args) => [
1164
+ userMessage(
1165
+ substitutePlaceholders(leadbay_work_campaign, {
1166
+ campaign_or_default: args.campaign ?? "<pick from the list>",
1167
+ mode_paren: args.mode ? ` (mode: ${args.mode})` : ""
1168
+ })
1169
+ )
1170
+ ]
1171
+ },
889
1172
  {
890
1173
  name: "leadbay_qualify_top_n",
891
1174
  description: PROMPT_META.leadbay_qualify_top_n.short_description,
@@ -2259,7 +2542,7 @@ async function createDefaultUpdateStateStore(opts = {}) {
2259
2542
 
2260
2543
  // src/bin.ts
2261
2544
  import { createRequire } from "module";
2262
- var VERSION = "0.11.0";
2545
+ var VERSION = "0.12.0";
2263
2546
  var HELP = `
2264
2547
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
2265
2548
 
@@ -2308,7 +2591,7 @@ EXAMPLE Claude Desktop config (~/Library/Application Support/Claude/claude_deskt
2308
2591
  "mcpServers": {
2309
2592
  "leadbay": {
2310
2593
  "command": "npx",
2311
- "args": ["-y", "@leadbay/mcp@0.11"],
2594
+ "args": ["-y", "@leadbay/mcp@0.12"],
2312
2595
  "env": {
2313
2596
  "LEADBAY_TOKEN": "lb_...",
2314
2597
  "LEADBAY_REGION": "us",
@@ -2609,7 +2892,7 @@ async function runLogin(args) {
2609
2892
  let result;
2610
2893
  try {
2611
2894
  if (pinnedRegion && !allowFallback) {
2612
- const { REGIONS } = await import("./dist-JZ2FLLN6.js");
2895
+ const { REGIONS } = await import("./dist-YYVFSDMH.js");
2613
2896
  const baseUrl = REGIONS[pinnedRegion];
2614
2897
  const c = createClient({ region: pinnedRegion });
2615
2898
  const token = await loginAt(baseUrl, email, password);
@@ -2628,7 +2911,7 @@ async function runLogin(args) {
2628
2911
  mcpServers: {
2629
2912
  leadbay: {
2630
2913
  command: "npx",
2631
- args: ["-y", "@leadbay/mcp@0.11"],
2914
+ args: ["-y", "@leadbay/mcp@0.12"],
2632
2915
  env: {
2633
2916
  LEADBAY_TOKEN: result.token,
2634
2917
  LEADBAY_REGION: result.region
@@ -2668,7 +2951,7 @@ Or for Claude Code (token included \u2014 same warning applies):
2668
2951
  claude mcp add leadbay --scope user \\
2669
2952
  --env LEADBAY_TOKEN=${result.token} \\
2670
2953
  --env LEADBAY_REGION=${result.region} \\
2671
- -- npx -y @leadbay/mcp@0.11
2954
+ -- npx -y @leadbay/mcp@0.12
2672
2955
 
2673
2956
  Restart your MCP client to pick up the new server.
2674
2957
  `
@@ -2774,7 +3057,7 @@ For Claude Code, run:
2774
3057
  claude mcp add leadbay --scope user \\
2775
3058
  --env LEADBAY_TOKEN=$(jq -r .mcpServers.leadbay.env.LEADBAY_TOKEN ${quotedPath}) \\
2776
3059
  --env LEADBAY_REGION=${result.region} \\
2777
- -- npx -y @leadbay/mcp@0.11
3060
+ -- npx -y @leadbay/mcp@0.12
2778
3061
  `
2779
3062
  );
2780
3063
  }
@@ -2954,7 +3237,7 @@ function buildClaudeCodeAddArgs(token, region, includeWrite, telemetryEnabled) {
2954
3237
  `LEADBAY_TELEMETRY_ENABLED=${telemetryEnabled ? "true" : "false"}`
2955
3238
  ];
2956
3239
  if (!includeWrite) args.push("--env", `LEADBAY_MCP_WRITE=0`);
2957
- args.push("--", "npx", "-y", "@leadbay/mcp@0.11");
3240
+ args.push("--", "npx", "-y", "@leadbay/mcp@0.12");
2958
3241
  return args;
2959
3242
  }
2960
3243
  async function installInClaudeCode(token, region, includeWrite, telemetryEnabled) {
@@ -3004,7 +3287,7 @@ async function installInJsonConfig(configPath, token, region, includeWrite, tele
3004
3287
  if (!includeWrite) env.LEADBAY_MCP_WRITE = "0";
3005
3288
  parsed.mcpServers.leadbay = {
3006
3289
  command: "npx",
3007
- args: ["-y", "@leadbay/mcp@0.11"],
3290
+ args: ["-y", "@leadbay/mcp@0.12"],
3008
3291
  env
3009
3292
  };
3010
3293
  const tmp = configPath + ".tmp";
@@ -3108,7 +3391,7 @@ leadbay-mcp install \u2014 detected MCP clients on this machine:
3108
3391
  let region;
3109
3392
  try {
3110
3393
  if (pinnedRegion && !allowFallback) {
3111
- const { REGIONS } = await import("./dist-JZ2FLLN6.js");
3394
+ const { REGIONS } = await import("./dist-YYVFSDMH.js");
3112
3395
  const baseUrl = REGIONS[pinnedRegion];
3113
3396
  token = await loginAt(baseUrl, email, password);
3114
3397
  region = pinnedRegion;
@@ -3181,7 +3464,7 @@ leadbay-mcp install \u2014 detected MCP clients on this machine:
3181
3464
  process.stderr.write(
3182
3465
  `
3183
3466
  The token was written into client config files but never printed to your terminal.
3184
- Verify with: LEADBAY_TOKEN=$(...) npx -y @leadbay/mcp@0.11 doctor
3467
+ Verify with: LEADBAY_TOKEN=$(...) npx -y @leadbay/mcp@0.12 doctor
3185
3468
  Restart your MCP client(s) to pick up the new server.
3186
3469
  If you ever leak the token, run \`leadbay-mcp login --email <you> --region <us|fr>\` to mint a fresh one (which invalidates the prior session).
3187
3470
  `