@leadbay/mcp 0.23.4 → 0.23.6

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,11 @@
1
1
  # Changelog — @leadbay/mcp
2
2
 
3
+ ## 0.23.6 — 2026-06-25
4
+
5
+ Geographic filter on Discover lenses (product#3759).
6
+
7
+ - **`leadbay_new_lens` / `leadbay_adjust_audience`** now accept `locations` / `exclude_locations` — a geographic dimension on the Discover lens, mirroring the sector path. Free text (`["Indre-et-Loire"]`, `["Texas"]`) auto-resolves via `/geo/search`, or pass admin-area ids directly. Resolution happens first: ambiguous/unresolved text returns `ambiguous_locations` and the lens is **not** mutated (no half-built lens). Resolved ids merge into the lens filter as a `location_ids` criterion. Unblocks the "scope a territory → net-new accounts there" cockpit workflow — geography was previously settable only on the Monitor side.
8
+
3
9
  ## 0.23.3 — 2026-06-24
4
10
 
5
11
  - **Release plumbing only** — no functional change. First release on the updated CI that also publishes fixed-name `leadbay-latest.dxt` / `.mcpb` assets, so the docs can link a stable `…/releases/latest/download/leadbay-latest.dxt` that always resolves to the current version.
package/dist/bin.js CHANGED
@@ -5804,7 +5804,7 @@ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible
5804
5804
  `;
5805
5805
  leadbay_adjust_audience = `## WHEN TO USE
5806
5806
 
5807
- Trigger phrases: "narrow the audience to <sector>", "add <sector> to my <name> lens", "remove <sector> from this lens", "only show me companies of <size>", "stop including <sector>", "broaden this lens to also include <sector>".
5807
+ Trigger phrases: "narrow the audience to <sector>", "add <sector> to my <name> lens", "remove <sector> from this lens", "only show me companies of <size>", "stop including <sector>", "broaden this lens to also include <sector>", "restrict this lens to <city/d\xE9partement/r\xE9gion/state>", "only companies in <place>", "exclude <region> from this lens".
5808
5808
 
5809
5809
  **Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
5810
5810
 
@@ -5815,7 +5815,7 @@ Prefer when: user wants to change an EXISTING lens's sectors/sizes. If the user
5815
5815
  Examples that SHOULD invoke this tool:
5816
5816
  - "Add fintech to my Joinery lens."
5817
5817
  - "Narrow my audience to manufacturing companies, 50\u2013500 people."
5818
- - "Stop including retail in this lens."
5818
+ - "Restrict my rep's lens to Indre-et-Loire."
5819
5819
 
5820
5820
  Examples that should NOT invoke this tool (sound similar, route elsewhere):
5821
5821
  - "Create a lens called Joinery for fintech."
@@ -5824,9 +5824,10 @@ Examples that should NOT invoke this tool (sound similar, route elsewhere):
5824
5824
 
5825
5825
  ## RENDER (quick)
5826
5826
 
5827
- On \`applied\`: confirm the lens edited (name) + the sectors/sizes added as
5828
- chips. On \`ambiguous_sectors\` / \`ambiguous_lens\` / \`lens_not_found\`: surface
5829
- the candidates and ask the user to pick, then re-call with the id/exact name.
5827
+ On \`applied\`: confirm the lens edited (name) + the sectors/sizes/locations
5828
+ added as chips. On \`ambiguous_sectors\` / \`ambiguous_locations\` /
5829
+ \`ambiguous_lens\` / \`lens_not_found\`: surface the candidates and ask the user
5830
+ to pick, then re-call with the id/exact name.
5830
5831
 
5831
5832
  ---
5832
5833
 
@@ -5834,7 +5835,9 @@ Restrict (or expand) the lens audience by sector / size. Free-text sectors are a
5834
5835
 
5835
5836
  **Targeting a lens \u2014 READ THIS.** By default this edits the user's ACTIVE lens. **If the user names a lens** ("add fintech to my **Joinery** lens", "in my Nordics lens, exclude retail"), you MUST pass \`lensName\` with that name (\`lensName:"Joinery"\`). Do NOT silently edit the active lens when a different one was named \u2014 that corrupts the wrong audience and is a top friction source. The name resolves against the user's lenses (case-insensitive, exact then unique-substring); it is edit-only and does NOT change which lens is active. An unmatched name returns \`status:"lens_not_found"\` with the lens list, and a name matching several returns \`status:"ambiguous_lens"\` with the candidates \u2014 surface them and re-call with the exact \`lensName\` or a \`lensId\`. Use \`leadbay_my_lenses\` if the user first wants to SEE or SWITCH lenses. To CREATE a brand-new lens, use \`leadbay_new_lens\` \u2014 not this tool.
5836
5837
 
5837
- WHEN TO USE: when the user wants to see different kinds of leads (sector / size / etc.).
5838
+ **Geography \u2014 scope a sales territory.** Pass \`locations\` (free text like \`["Indre-et-Loire"]\`, \`["Bavaria"]\`, \`["Austin"]\`, or admin-area ids) to restrict the lens to a region, and \`exclude_locations\` to carve one out. Free text auto-resolves via \`/geo/search\` across every admin level \u2014 city, county, *d\xE9partement*, *r\xE9gion*, state, country. Place names go in \`locations\`, **never** in \`sectors\` or \`refine_prompt\`. Unresolved/ambiguous text returns \`status:"ambiguous_locations"\` with candidates \u2014 surface them and re-call the chosen id via the SAME axis it came from: an INCLUDE pick \u2192 \`location_ids\`; an EXCLUDE pick \u2192 \`exclude_locations\` (**NOT** \`location_ids\`, which would include the area the user asked to exclude). The returned \`message\` names the right param per text. This is how a director scopes a rep's territory and then asks for net-new accounts there.
5839
+
5840
+ WHEN TO USE: when the user wants to see different kinds of leads (sector / size / geography / etc.).
5838
5841
 
5839
5842
  WHEN NOT TO USE: to refine BEYOND firmographics \u2014 that's leadbay_refine_prompt.
5840
5843
 
@@ -7382,7 +7385,7 @@ invent a tool that doesn't exist.
7382
7385
  `;
7383
7386
  leadbay_new_lens = `## WHEN TO USE
7384
7387
 
7385
- Trigger phrases: "create a lens", "create a new lens called <name>", "create a lens specialized in/into <X>", "make me a new audience for <X>", "set up a lens for <sector>", "new lens named <name>", "I want a lens just for <X>".
7388
+ Trigger phrases: "create a lens", "create a new lens called <name>", "create a lens specialized in/into <X>", "make me a new audience for <X>", "set up a lens for <sector>", "new lens named <name>", "I want a lens just for <X>", "create a lens for net-new accounts in <place>", "a lens scoped to <territory>".
7386
7389
 
7387
7390
  **Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
7388
7391
 
@@ -7393,7 +7396,7 @@ Prefer when: user wants a brand-new lens (create/make/set up, often 'specialized
7393
7396
  Examples that SHOULD invoke this tool:
7394
7397
  - "Create a lens called Joinery for the fintech sector."
7395
7398
  - "Make me a new audience for healthcare companies, 30\u2013300 people."
7396
- - "Set up a new lens named Nordics SaaS."
7399
+ - "Create a lens for net-new accounts in Indre-et-Loire."
7397
7400
 
7398
7401
  Examples that should NOT invoke this tool (sound similar, route elsewhere):
7399
7402
  - "Add fintech to my Joinery lens."
@@ -7406,7 +7409,8 @@ On \`preview\` (default \u2014 NOTHING created yet): show the lens that WILL be
7406
7409
  created (name + resolved sectors/sizes as chips) and ASK the user to confirm
7407
7410
  via ask_user_input_v0 ("Create this lens?" / "Change something"). Only on
7408
7411
  "yes" re-call with confirm:true. On \`created\`: confirm "Created **<name>**."
7409
- On \`ambiguous_sectors\`: surface the candidate sectors to pick from.
7412
+ On \`ambiguous_sectors\` / \`ambiguous_locations\`: surface the candidates to
7413
+ pick from.
7410
7414
 
7411
7415
  ---
7412
7416
 
@@ -7416,6 +7420,8 @@ Create a brand-new lens (saved audience) and apply its sector/size criteria. Clo
7416
7420
 
7417
7421
  **Sectors resolve first.** Free-text \`sectors\`/\`exclude_sectors\` are auto-resolved against the taxonomy. If any don't resolve, the tool returns \`status:"ambiguous_sectors"\` with the candidates and **does NOT create the lens** \u2014 so re-calling after picking the right sector won't leave orphan half-built lenses. To discover valid sector labels up front, use \`leadbay_list_sectors\`.
7418
7422
 
7423
+ **Geography \u2014 scope a territory.** Pass \`locations\` (free text like \`["Indre-et-Loire"]\`, \`["Bavaria"]\`, or admin-area ids) to scope the lens to a sales territory, and \`exclude_locations\` to carve one out. Free text auto-resolves via \`/geo/search\` across every admin level (city / county / *d\xE9partement* / *r\xE9gion* / state / country). Like sectors, locations resolve BEFORE the lens is created \u2014 unresolved/ambiguous text returns \`status:"ambiguous_locations"\` with candidates and **does NOT create the lens**. Re-call the chosen id via the SAME axis it came from: an INCLUDE pick \u2192 \`locations\`; an EXCLUDE pick \u2192 \`exclude_locations\` (**NOT** \`locations\`, which would include the area the user asked to exclude). This is how a director spins up a lens for a rep's zone to surface net-new accounts there.
7424
+
7419
7425
  **Does not switch the active lens.** The new lens is created but the user stays on their current one. Offer \`leadbay_my_lenses(switchToLensId=<new id>)\` as a next step if they want to start pulling from it.
7420
7426
 
7421
7427
  WHEN TO USE: when the user wants a NEW lens. Canonical phrasings: "create a lens called X", "make a new audience for Y", "set up a lens for <sector>".
@@ -20616,7 +20622,7 @@ async function resolveLensByName(client, name) {
20616
20622
  return { ok: false, reason: "ambiguous", matches: partial };
20617
20623
  return { ok: false, reason: "not_found", lenses: all };
20618
20624
  }
20619
- function mergeFilter(current, toAddSectors, toExcludeSectors, sizes) {
20625
+ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocations = [], toExcludeLocations = []) {
20620
20626
  const items = current?.lens_filter?.items ?? [];
20621
20627
  const item = items[0] ?? { criteria: [] };
20622
20628
  const criteria = item.criteria ? [...item.criteria] : [];
@@ -20648,6 +20654,34 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes) {
20648
20654
  });
20649
20655
  }
20650
20656
  }
20657
+ if (toAddLocations.length > 0) {
20658
+ const idx = criteria.findIndex((c) => c.type === "location_ids" && !c.is_excluded);
20659
+ if (idx >= 0) {
20660
+ const cur = criteria[idx];
20661
+ const merged = Array.from(/* @__PURE__ */ new Set([...cur.locations ?? [], ...toAddLocations]));
20662
+ criteria[idx] = { ...cur, locations: merged };
20663
+ } else {
20664
+ criteria.push({
20665
+ type: "location_ids",
20666
+ is_excluded: false,
20667
+ locations: toAddLocations
20668
+ });
20669
+ }
20670
+ }
20671
+ if (toExcludeLocations.length > 0) {
20672
+ const idx = criteria.findIndex((c) => c.type === "location_ids" && c.is_excluded);
20673
+ if (idx >= 0) {
20674
+ const cur = criteria[idx];
20675
+ const merged = Array.from(/* @__PURE__ */ new Set([...cur.locations ?? [], ...toExcludeLocations]));
20676
+ criteria[idx] = { ...cur, locations: merged };
20677
+ } else {
20678
+ criteria.push({
20679
+ type: "location_ids",
20680
+ is_excluded: true,
20681
+ locations: toExcludeLocations
20682
+ });
20683
+ }
20684
+ }
20651
20685
  if (sizes && sizes.length > 0) {
20652
20686
  const normalizedSizes = sizes.map((s) => ({
20653
20687
  min: s.min ?? 0,
@@ -20672,6 +20706,7 @@ var adjustAudience;
20672
20706
  var init_adjust_audience = __esm({
20673
20707
  "../core/dist/composite/adjust-audience.js"() {
20674
20708
  "use strict";
20709
+ init_geo_helpers();
20675
20710
  init_tool_descriptions_generated();
20676
20711
  adjustAudience = {
20677
20712
  name: "leadbay_adjust_audience",
@@ -20713,6 +20748,21 @@ var init_adjust_audience = __esm({
20713
20748
  },
20714
20749
  description: "Company size buckets, e.g. [{min:30,max:300}]"
20715
20750
  },
20751
+ locations: {
20752
+ type: "array",
20753
+ items: { type: "string" },
20754
+ description: "Geographic scope \u2014 free text (e.g. ['Indre-et-Loire', 'Bavaria', 'Austin']) or admin-area ids. Auto-resolved via /geo/search across all admin levels (city / county / d\xE9partement / r\xE9gion / state / country). Place names go HERE, never in sectors/keywords."
20755
+ },
20756
+ location_ids: {
20757
+ type: "array",
20758
+ items: { type: "string" },
20759
+ description: "Explicit admin-area ids (skips /geo/search resolution)"
20760
+ },
20761
+ exclude_locations: {
20762
+ type: "array",
20763
+ items: { type: "string" },
20764
+ description: "Locations to exclude (free text or ids)"
20765
+ },
20716
20766
  lensId: { type: "number", description: "Lens id (escape hatch)" },
20717
20767
  lensName: {
20718
20768
  type: "string",
@@ -20731,17 +20781,22 @@ var init_adjust_audience = __esm({
20731
20781
  },
20732
20782
  outputSchema: {
20733
20783
  type: "object",
20734
- description: "Return shapes: 'applied' on success; 'ambiguous_sectors' when free-text sectors matched multiple candidates (re-call with sector_ids); 'lens_not_found' / 'ambiguous_lens' when a lensName didn't resolve to exactly one lens (re-call with lensId or an exact lensName).",
20784
+ description: "Return shapes: 'applied' on success; 'ambiguous_sectors' when free-text sectors matched multiple candidates (re-call with sector_ids); 'ambiguous_locations' when free-text locations didn't resolve to one area \u2014 re-call with the chosen id via the SAME axis it came from (an include pick \u2192 location_ids; an EXCLUDE pick \u2192 exclude_locations, NOT location_ids, which would include it); 'lens_not_found' / 'ambiguous_lens' when a lensName didn't resolve to exactly one lens (re-call with lensId or an exact lensName).",
20735
20785
  properties: {
20736
20786
  status: {
20737
20787
  type: "string",
20738
- description: "'applied', 'ambiguous_sectors', 'lens_not_found', or 'ambiguous_lens'."
20788
+ description: "'applied', 'ambiguous_sectors', 'ambiguous_locations', 'lens_not_found', or 'ambiguous_lens'."
20739
20789
  },
20740
20790
  sector_ambiguities: {
20741
20791
  type: "array",
20742
20792
  description: "Per ambiguous text: {sector_text, matches:[{id, name, score}]}. Agent picks an id and re-calls.",
20743
20793
  items: { type: "object" }
20744
20794
  },
20795
+ location_ambiguities: {
20796
+ type: "array",
20797
+ description: "On 'ambiguous_locations': per text {location_text, matches:[{id, name, country, level, score}]}. Agent picks an id and re-calls via the SAME axis the text came from \u2014 an include text \u2192 location_ids; a text from exclude_locations \u2192 exclude_locations (NOT location_ids, which would include the area the user asked to exclude). The `message` field names the correct param per text.",
20798
+ items: { type: "object" }
20799
+ },
20745
20800
  lenses: {
20746
20801
  type: "array",
20747
20802
  description: "On 'lens_not_found': the user's lenses [{id, name}] to pick from.",
@@ -20825,9 +20880,45 @@ var init_adjust_audience = __esm({
20825
20880
  message: parts.join(" ")
20826
20881
  };
20827
20882
  }
20883
+ const includeLocTexts = [
20884
+ ...params.locations ?? [],
20885
+ ...params.location_ids ?? []
20886
+ ];
20887
+ const excludeLocTexts = params.exclude_locations ?? [];
20888
+ const includeLocRes = await resolveLocations(client, includeLocTexts);
20889
+ const excludeLocRes = await resolveLocations(client, excludeLocTexts);
20890
+ const locAmbiguities = [
20891
+ ...includeLocRes.ambiguities,
20892
+ ...excludeLocRes.ambiguities
20893
+ ];
20894
+ if (locAmbiguities.length > 0) {
20895
+ const incMatch = includeLocRes.ambiguities.filter((a) => a.matches.length > 0);
20896
+ const incNone = includeLocRes.ambiguities.filter((a) => a.matches.length === 0);
20897
+ const excMatch = excludeLocRes.ambiguities.filter((a) => a.matches.length > 0);
20898
+ const excNone = excludeLocRes.ambiguities.filter((a) => a.matches.length === 0);
20899
+ const quote = (as) => as.map((a) => `"${a.location_text}"`).join(", ");
20900
+ const parts = [];
20901
+ if (incNone.length > 0) {
20902
+ parts.push(`Couldn't find a location matching ${quote(incNone)}. Ask the user to rephrase, then re-call with location_ids=...`);
20903
+ }
20904
+ if (incMatch.length > 0) {
20905
+ parts.push(`${quote(incMatch)} matched multiple areas. Pick from the matches and re-call with location_ids=...`);
20906
+ }
20907
+ if (excNone.length > 0) {
20908
+ parts.push(`Couldn't find a location to exclude matching ${quote(excNone)}. Ask the user to rephrase, then re-call with exclude_locations=... (the chosen id).`);
20909
+ }
20910
+ if (excMatch.length > 0) {
20911
+ parts.push(`${quote(excMatch)} (to exclude) matched multiple areas. Pick from the matches and re-call with exclude_locations=... (the chosen id) \u2014 NOT location_ids, which would include it.`);
20912
+ }
20913
+ return {
20914
+ status: "ambiguous_locations",
20915
+ location_ambiguities: locAmbiguities,
20916
+ message: parts.join(" ")
20917
+ };
20918
+ }
20828
20919
  const lens = await client.request("GET", `/lenses/${startingLensId}`);
20829
20920
  const currentFilter = await client.request("GET", `/lenses/${startingLensId}/filter`);
20830
- const merged = mergeFilter(currentFilter, includeRes.resolved, excludeRes.resolved, params.sizes);
20921
+ const merged = mergeFilter(currentFilter, includeRes.resolved, excludeRes.resolved, params.sizes, includeLocRes.resolved, excludeLocRes.resolved);
20831
20922
  const mergedBody = filterWriteBody(merged);
20832
20923
  const isDefault = lens.is_default || lens.default;
20833
20924
  const isUserLevel = lens.user_id != null;
@@ -21548,6 +21639,7 @@ var init_new_lens = __esm({
21548
21639
  "../core/dist/composite/new-lens.js"() {
21549
21640
  "use strict";
21550
21641
  init_adjust_audience();
21642
+ init_geo_helpers();
21551
21643
  init_tool_descriptions_generated();
21552
21644
  EMPTY_FILTER = {
21553
21645
  lens_filter: { items: [{ criteria: [] }] },
@@ -21586,6 +21678,16 @@ var init_new_lens = __esm({
21586
21678
  },
21587
21679
  description: "Company size buckets, e.g. [{min:30,max:300}]."
21588
21680
  },
21681
+ locations: {
21682
+ type: "array",
21683
+ items: { type: "string" },
21684
+ description: "Geographic scope \u2014 free text (e.g. ['Indre-et-Loire', 'Bavaria']) or admin-area ids. Auto-resolved via /geo/search across all admin levels (city / county / d\xE9partement / r\xE9gion / state / country). Scopes the lens to a sales territory."
21685
+ },
21686
+ exclude_locations: {
21687
+ type: "array",
21688
+ items: { type: "string" },
21689
+ description: "Locations to exclude \u2014 free text or ids."
21690
+ },
21589
21691
  base: {
21590
21692
  type: "number",
21591
21693
  description: "Lens id to clone from. Defaults to the active/default lens."
@@ -21601,12 +21703,12 @@ var init_new_lens = __esm({
21601
21703
  },
21602
21704
  outputSchema: {
21603
21705
  type: "object",
21604
- description: "'preview' (default, NOTHING created \u2014 confirm with the user then re-call with confirm:true); 'created' on success; 'ambiguous_sectors' when free-text sectors didn't resolve (re-call with sector ids \u2014 the lens was NOT created).",
21706
+ description: "'preview' (default, NOTHING created \u2014 confirm with the user then re-call with confirm:true); 'created' on success; 'ambiguous_sectors' / 'ambiguous_locations' when free-text sectors / locations didn't resolve (re-call with ids \u2014 the lens was NOT created).",
21605
21707
  properties: {
21606
- status: { type: "string", description: "'preview', 'created', 'ambiguous_sectors', or 'orphan_created' (filter write failed + cleanup failed)." },
21708
+ status: { type: "string", description: "'preview', 'created', 'ambiguous_sectors', 'ambiguous_locations', or 'orphan_created' (filter write failed + cleanup failed)." },
21607
21709
  will_create: {
21608
21710
  type: "object",
21609
- description: "On 'preview': what WILL be created \u2014 {name, description, sectors, exclude_sectors, sizes}. Nothing has been written yet."
21711
+ description: "On 'preview': what WILL be created \u2014 {name, description, sectors, exclude_sectors, sizes, locations, exclude_locations}. Nothing has been written yet."
21610
21712
  },
21611
21713
  filter_preview: { type: "object", description: "On 'preview': the FilterPayload that would be applied." },
21612
21714
  lens: {
@@ -21618,6 +21720,11 @@ var init_new_lens = __esm({
21618
21720
  description: "On 'ambiguous_sectors': per text {sector_text, matches:[{id,name,score}]}.",
21619
21721
  items: { type: "object" }
21620
21722
  },
21723
+ location_ambiguities: {
21724
+ type: "array",
21725
+ description: "On 'ambiguous_locations': per text {location_text, matches:[{id,name,country,level,score}]}. Re-call the chosen id via the SAME axis the text came from \u2014 an include text \u2192 locations; a text from exclude_locations \u2192 exclude_locations (NOT locations, which would include the area the user asked to exclude). The `message` field names the correct param per text.",
21726
+ items: { type: "object" }
21727
+ },
21621
21728
  filter_applied: { type: "object", description: "On 'created': the FilterPayload POSTed to the new lens." },
21622
21729
  message: { type: "string" },
21623
21730
  _meta: { type: "object" }
@@ -21644,7 +21751,38 @@ var init_new_lens = __esm({
21644
21751
  message: parts.join(" ")
21645
21752
  };
21646
21753
  }
21647
- const merged = mergeFilter(EMPTY_FILTER, includeRes.resolved, excludeRes.resolved, params.sizes);
21754
+ const includeLocRes = await resolveLocations(client, params.locations ?? []);
21755
+ const excludeLocRes = await resolveLocations(client, params.exclude_locations ?? []);
21756
+ const locAmbiguities = [
21757
+ ...includeLocRes.ambiguities,
21758
+ ...excludeLocRes.ambiguities
21759
+ ];
21760
+ if (locAmbiguities.length > 0) {
21761
+ const incMatch = includeLocRes.ambiguities.filter((a) => a.matches.length > 0);
21762
+ const incNone = includeLocRes.ambiguities.filter((a) => a.matches.length === 0);
21763
+ const excMatch = excludeLocRes.ambiguities.filter((a) => a.matches.length > 0);
21764
+ const excNone = excludeLocRes.ambiguities.filter((a) => a.matches.length === 0);
21765
+ const quote = (as) => as.map((a) => `"${a.location_text}"`).join(", ");
21766
+ const parts = [];
21767
+ if (incNone.length > 0) {
21768
+ parts.push(`Couldn't find a location matching ${quote(incNone)}. Pick a known area and re-call via locations (lens not yet created).`);
21769
+ }
21770
+ if (incMatch.length > 0) {
21771
+ parts.push(`${quote(incMatch)} matched multiple areas. Pick from the matches and re-call with the chosen id in locations.`);
21772
+ }
21773
+ if (excNone.length > 0) {
21774
+ parts.push(`Couldn't find a location to exclude matching ${quote(excNone)}. Pick a known area and re-call via exclude_locations (lens not yet created).`);
21775
+ }
21776
+ if (excMatch.length > 0) {
21777
+ parts.push(`${quote(excMatch)} (to exclude) matched multiple areas. Pick from the matches and re-call with the chosen id in exclude_locations \u2014 NOT locations, which would include it.`);
21778
+ }
21779
+ return {
21780
+ status: "ambiguous_locations",
21781
+ location_ambiguities: locAmbiguities,
21782
+ message: parts.join(" ")
21783
+ };
21784
+ }
21785
+ const merged = mergeFilter(EMPTY_FILTER, includeRes.resolved, excludeRes.resolved, params.sizes, includeLocRes.resolved, excludeLocRes.resolved);
21648
21786
  if (params.confirm !== true) {
21649
21787
  return {
21650
21788
  status: "preview",
@@ -21653,7 +21791,9 @@ var init_new_lens = __esm({
21653
21791
  description: params.description ?? null,
21654
21792
  sectors: includeRes.resolved,
21655
21793
  exclude_sectors: excludeRes.resolved,
21656
- sizes: merged.lens_filter.items[0].criteria.find((c) => c.type === "size") ?? null
21794
+ sizes: merged.lens_filter.items[0].criteria.find((c) => c.type === "size") ?? null,
21795
+ locations: includeLocRes.resolved,
21796
+ exclude_locations: excludeLocRes.resolved
21657
21797
  },
21658
21798
  filter_preview: merged,
21659
21799
  message: `About to create "${params.name}". Confirm with the user, then re-call with confirm:true.`,
@@ -26059,7 +26199,7 @@ ${url}
26059
26199
 
26060
26200
  // installer/install-shared.ts
26061
26201
  import { spawn } from "child_process";
26062
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
26202
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
26063
26203
  import { join as join3 } from "path";
26064
26204
  import { homedir as homedir3 } from "os";
26065
26205
  var HOSTED_MCP_URL = "https://leadbay-mcp-prod.fly.dev/mcp";
@@ -26116,6 +26256,15 @@ async function windowsStoreAppInstalled(packageName, appName) {
26116
26256
  child.on("error", () => resolve(false));
26117
26257
  });
26118
26258
  }
26259
+ function isClaudeStorePackagePresent(localAppData) {
26260
+ const packagesDir = join3(localAppData, "Packages");
26261
+ if (!existsSync2(packagesDir)) return false;
26262
+ try {
26263
+ return readdirSync2(packagesDir).some((name) => /^Claude_/i.test(name));
26264
+ } catch {
26265
+ return false;
26266
+ }
26267
+ }
26119
26268
  async function isClaudeDesktopInstalled(home) {
26120
26269
  if (process.platform === "darwin") {
26121
26270
  return existsSync2("/Applications/Claude.app") || existsSync2(home + "/Applications/Claude.app");
@@ -26124,12 +26273,15 @@ async function isClaudeDesktopInstalled(home) {
26124
26273
  const local = process.env.LOCALAPPDATA ?? home + "/AppData/Local";
26125
26274
  const programFiles = process.env.ProgramFiles;
26126
26275
  const programFilesX86 = process.env["ProgramFiles(x86)"];
26127
- return [
26276
+ const exeInstalled = [
26128
26277
  local + "/Programs/Claude/Claude.exe",
26129
26278
  local + "/Claude/Claude.exe",
26130
26279
  programFiles ? programFiles + "/Claude/Claude.exe" : null,
26131
26280
  programFilesX86 ? programFilesX86 + "/Claude/Claude.exe" : null
26132
26281
  ].some((candidate) => candidate !== null && existsSync2(candidate));
26282
+ if (exeInstalled) return true;
26283
+ if (isClaudeStorePackagePresent(local)) return true;
26284
+ return await windowsStoreAppInstalled("AnthropicPBC.Claude", "Claude");
26133
26285
  }
26134
26286
  const desktopBin = await findOnPath("claude-desktop");
26135
26287
  if (desktopBin) return true;
@@ -26936,7 +27088,7 @@ import { createHash as createHash5, randomBytes } from "crypto";
26936
27088
  import { createServer } from "http";
26937
27089
  import { request as httpsRequestRaw } from "https";
26938
27090
  import { spawn as spawn3 } from "child_process";
26939
- import { readdirSync as readdirSync2, existsSync as existsSync3 } from "fs";
27091
+ import { readdirSync as readdirSync3, existsSync as existsSync3 } from "fs";
26940
27092
  var LEADBAY_LOOPBACK_PORTS = [51789, 51790, 51791, 51792];
26941
27093
  var BrowserOpenFailedError = class extends Error {
26942
27094
  authorizeUrl;
@@ -27272,7 +27424,7 @@ function browserLaunchEnv(debug) {
27272
27424
  }
27273
27425
  if (!env.WAYLAND_DISPLAY && runtimeDir) {
27274
27426
  try {
27275
- const sock = readdirSync2(runtimeDir).find((f) => /^wayland-\d+$/.test(f));
27427
+ const sock = readdirSync3(runtimeDir).find((f) => /^wayland-\d+$/.test(f));
27276
27428
  if (sock) {
27277
27429
  env.WAYLAND_DISPLAY = sock;
27278
27430
  debug?.(`browserLaunchEnv: injected WAYLAND_DISPLAY=${sock}`);
@@ -27289,7 +27441,7 @@ function browserLaunchEnv(debug) {
27289
27441
  }
27290
27442
  if (!env.DISPLAY) {
27291
27443
  try {
27292
- const x = readdirSync2("/tmp/.X11-unix").map((f) => f.match(/^X(\d+)$/)?.[1]).filter((n) => !!n).sort((a, b) => Number(a) - Number(b))[0];
27444
+ const x = readdirSync3("/tmp/.X11-unix").map((f) => f.match(/^X(\d+)$/)?.[1]).filter((n) => !!n).sort((a, b) => Number(a) - Number(b))[0];
27293
27445
  env.DISPLAY = x !== void 0 ? `:${x}` : ":0";
27294
27446
  } catch {
27295
27447
  env.DISPLAY = ":0";
@@ -27300,7 +27452,7 @@ function browserLaunchEnv(debug) {
27300
27452
  try {
27301
27453
  let xauth;
27302
27454
  if (runtimeDir) {
27303
- const cookie = readdirSync2(runtimeDir).find(
27455
+ const cookie = readdirSync3(runtimeDir).find(
27304
27456
  (f) => /^\.mutter-Xwaylandauth\./.test(f)
27305
27457
  );
27306
27458
  if (cookie) xauth = `${runtimeDir}/${cookie}`;
@@ -27439,7 +27591,7 @@ var OAUTH_BASE_URLS = {
27439
27591
  fr: "https://staging.api.leadbay.app"
27440
27592
  }
27441
27593
  };
27442
- var VERSION = "0.23.4";
27594
+ var VERSION = "0.23.6";
27443
27595
  var HELP = `
27444
27596
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
27445
27597