@leadbay/mcp 0.30.0 → 0.31.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 +142 -1
- package/dist/http-server.js +135 -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
|
@@ -8596,6 +8596,11 @@ WHEN NOT TO USE: when the user has named a specific lens \u2014 pass \`lensId\`
|
|
|
8596
8596
|
|
|
8597
8597
|
The active lens can change between calls (5-min cache + backend \`last_requested_lens\`). If a multi-step workflow depends on staying on one lens, **capture \`response.lens.id\` from the first response and pass it as the \`lensId\` argument on every subsequent Leadbay call** \u2014 including re-pulls, bulk qualifies, and research. (Field-name caveat: response nests it as \`lens.id\`; the parameter is \`lensId\`.) Re-pulling without \`lensId\` after a long-running tool may silently switch to a different lens and discard prior work.
|
|
8598
8598
|
|
|
8599
|
+
**EMPTY BATCH \u2014 route on \`empty_reason\`, never loop.** When \`leads\` is empty the response carries \`empty_reason: {code, message, retryable, criteria?, narrow_locations?}\`. \`retryable\` is the only field that decides what you do next:
|
|
8600
|
+
|
|
8601
|
+
- \`retryable: true\` (always \`code: "computing"\`) \u2014 the lens is still building. Say so, pull ONCE more in ~30s. Do not call it empty.
|
|
8602
|
+
- \`retryable: false\` \u2014 no amount of re-pulling, lens-switching or \`leadbay_extend_lens\` can produce leads on these criteria. **Stop calling tools.** Surface \`message\` to the user, name the criteria from \`criteria\` (and \`narrow_locations\` first when present \u2014 a city-scale geo scope is the usual culprit), and offer \`leadbay_adjust_audience\` to widen. A refill on a zero-candidate lens answers "queued", consumes no quota and delivers nothing, so retrying reads as progress while achieving none (product#3995).
|
|
8603
|
+
|
|
8599
8604
|
---
|
|
8600
8605
|
|
|
8601
8606
|
## RENDERING \u2014 markdown table, three columns, score-bar driven
|
|
@@ -16481,6 +16486,107 @@ var init_prepare_outreach = __esm({
|
|
|
16481
16486
|
}
|
|
16482
16487
|
});
|
|
16483
16488
|
|
|
16489
|
+
// ../core/dist/composite/_empty-lens-reason.js
|
|
16490
|
+
function criteriaOf(filter) {
|
|
16491
|
+
return filter?.lens_filter?.items?.flatMap((i) => i.criteria ?? []) ?? [];
|
|
16492
|
+
}
|
|
16493
|
+
function summariseCriteria(criteria) {
|
|
16494
|
+
const out = {};
|
|
16495
|
+
for (const c of criteria) {
|
|
16496
|
+
if (c.type === "sector_ids") {
|
|
16497
|
+
const key = c.is_excluded ? "excluded_sector_ids" : "sector_ids";
|
|
16498
|
+
out[key] = [...out[key] ?? [], ...c.sectors ?? []];
|
|
16499
|
+
} else if (c.type === "location_ids") {
|
|
16500
|
+
const key = c.is_excluded ? "excluded_location_ids" : "location_ids";
|
|
16501
|
+
out[key] = [...out[key] ?? [], ...c.locations ?? []];
|
|
16502
|
+
} else if (c.type === "size" && !c.is_excluded) {
|
|
16503
|
+
out.sizes = [...out.sizes ?? [], ...c.sizes ?? []];
|
|
16504
|
+
}
|
|
16505
|
+
}
|
|
16506
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
16507
|
+
}
|
|
16508
|
+
function narrowLocationsOf(filter, criteria) {
|
|
16509
|
+
const included = new Set(criteria.filter((c) => c.type === "location_ids" && !c.is_excluded).flatMap((c) => c.locations ?? []));
|
|
16510
|
+
if (included.size === 0)
|
|
16511
|
+
return [];
|
|
16512
|
+
const results = filter?.locations?.results ?? [];
|
|
16513
|
+
return results.filter((r) => typeof r.id === "string" && included.has(r.id) && typeof r.level === "number" && r.level >= CITY_LEVEL).map((r) => ({
|
|
16514
|
+
id: r.id,
|
|
16515
|
+
name: typeof r.name === "string" ? r.name : "",
|
|
16516
|
+
level: r.level
|
|
16517
|
+
}));
|
|
16518
|
+
}
|
|
16519
|
+
function narrowGeoSentence(narrow) {
|
|
16520
|
+
const names = narrow.map((n) => n.name).filter(Boolean);
|
|
16521
|
+
if (names.length === 0)
|
|
16522
|
+
return "";
|
|
16523
|
+
return ` Its geography is pinned to ${names.join(", ")} \u2014 a city-scale area or smaller, which on an empty lens is almost always the criterion to relax first.`;
|
|
16524
|
+
}
|
|
16525
|
+
async function diagnoseEmptyLens(client, lensId, computing) {
|
|
16526
|
+
if (computing.wishlist || computing.scores) {
|
|
16527
|
+
return {
|
|
16528
|
+
code: "computing",
|
|
16529
|
+
retryable: true,
|
|
16530
|
+
message: "This lens is still computing its leads. Pull again in ~30s \u2014 do NOT report it as empty yet."
|
|
16531
|
+
};
|
|
16532
|
+
}
|
|
16533
|
+
let row;
|
|
16534
|
+
try {
|
|
16535
|
+
const lenses = await client.request("GET", "/lenses");
|
|
16536
|
+
row = lenses.find((l) => String(l.id) === String(lensId));
|
|
16537
|
+
} catch {
|
|
16538
|
+
}
|
|
16539
|
+
let filter = null;
|
|
16540
|
+
try {
|
|
16541
|
+
filter = await client.request("GET", `/lenses/${lensId}/filter`);
|
|
16542
|
+
} catch {
|
|
16543
|
+
}
|
|
16544
|
+
const criteria = criteriaOf(filter);
|
|
16545
|
+
const summary = summariseCriteria(criteria);
|
|
16546
|
+
const narrow = narrowLocationsOf(filter, criteria);
|
|
16547
|
+
const geo = narrowGeoSentence(narrow);
|
|
16548
|
+
const extras = {
|
|
16549
|
+
...summary ? { criteria: summary } : {},
|
|
16550
|
+
...narrow.length > 0 ? { narrow_locations: narrow } : {}
|
|
16551
|
+
};
|
|
16552
|
+
if (row?.not_enough_lead_candidates) {
|
|
16553
|
+
return {
|
|
16554
|
+
code: "no_candidates",
|
|
16555
|
+
retryable: false,
|
|
16556
|
+
message: "This lens's criteria match no companies in the database, so it cannot fill." + geo + " Tell the user and offer to widen the audience (leadbay_adjust_audience) \u2014 extending or re-pulling will not help.",
|
|
16557
|
+
...extras
|
|
16558
|
+
};
|
|
16559
|
+
}
|
|
16560
|
+
if (row?.not_enough_new_leads) {
|
|
16561
|
+
return {
|
|
16562
|
+
code: "no_new_leads",
|
|
16563
|
+
retryable: false,
|
|
16564
|
+
message: "Every company matching this lens has already been delivered \u2014 there are no NEW leads left on these criteria. Tell the user; offer to widen the audience (leadbay_adjust_audience) or work the existing leads via leadbay_pull_followups.",
|
|
16565
|
+
...extras
|
|
16566
|
+
};
|
|
16567
|
+
}
|
|
16568
|
+
if (summary) {
|
|
16569
|
+
return {
|
|
16570
|
+
code: "audience_too_narrow",
|
|
16571
|
+
retryable: false,
|
|
16572
|
+
message: "This lens is finished computing and holds zero leads: its criteria intersect to nothing." + geo + " Tell the user which criteria are in play and offer to widen the audience (leadbay_adjust_audience). Do NOT call leadbay_extend_lens \u2014 a refill on a zero-candidate lens reports queued, consumes no quota, and delivers nothing.",
|
|
16573
|
+
...extras
|
|
16574
|
+
};
|
|
16575
|
+
}
|
|
16576
|
+
return {
|
|
16577
|
+
code: "unknown",
|
|
16578
|
+
retryable: false,
|
|
16579
|
+
message: "This lens is finished computing and holds zero leads, and carries no audience criteria that would explain it. Report this to the user rather than retrying; leadbay_report_friction is the way to flag it to the Leadbay team."
|
|
16580
|
+
};
|
|
16581
|
+
}
|
|
16582
|
+
var CITY_LEVEL;
|
|
16583
|
+
var init_empty_lens_reason = __esm({
|
|
16584
|
+
"../core/dist/composite/_empty-lens-reason.js"() {
|
|
16585
|
+
"use strict";
|
|
16586
|
+
CITY_LEVEL = 7;
|
|
16587
|
+
}
|
|
16588
|
+
});
|
|
16589
|
+
|
|
16484
16590
|
// ../core/dist/composite/pull-leads.js
|
|
16485
16591
|
function normalizeLinkedinPage3(v) {
|
|
16486
16592
|
if (v == null)
|
|
@@ -16561,6 +16667,7 @@ var init_pull_leads = __esm({
|
|
|
16561
16667
|
"../core/dist/composite/pull-leads.js"() {
|
|
16562
16668
|
"use strict";
|
|
16563
16669
|
init_agent_memory();
|
|
16670
|
+
init_empty_lens_reason();
|
|
16564
16671
|
init_tool_descriptions_generated();
|
|
16565
16672
|
pullLeads = {
|
|
16566
16673
|
name: "leadbay_pull_leads",
|
|
@@ -16626,6 +16733,34 @@ var init_pull_leads = __esm({
|
|
|
16626
16733
|
type: "boolean",
|
|
16627
16734
|
description: "True if scoring is still running."
|
|
16628
16735
|
},
|
|
16736
|
+
empty_reason: {
|
|
16737
|
+
type: ["object", "null"],
|
|
16738
|
+
description: "Why this LENS holds zero leads. null whenever leads were returned, and null when this page is empty only because it is past the end of a non-empty lens. `retryable` is the field to route on: true ONLY on code=computing (pull again in ~30s). On every other code re-pulling and leadbay_extend_lens are both futile \u2014 a refill on a zero-candidate lens answers 'queued', consumes no quota and delivers nothing \u2014 so surface `message` to the user and offer leadbay_adjust_audience instead of retrying.",
|
|
16739
|
+
properties: {
|
|
16740
|
+
code: {
|
|
16741
|
+
type: "string",
|
|
16742
|
+
description: "computing | no_candidates | no_new_leads | audience_too_narrow | unknown"
|
|
16743
|
+
},
|
|
16744
|
+
message: {
|
|
16745
|
+
type: "string",
|
|
16746
|
+
description: "The line to surface to the user."
|
|
16747
|
+
},
|
|
16748
|
+
retryable: {
|
|
16749
|
+
type: "boolean",
|
|
16750
|
+
description: "True only while the lens is still computing. False means no amount of re-pulling or extending can produce leads."
|
|
16751
|
+
},
|
|
16752
|
+
criteria: {
|
|
16753
|
+
type: "object",
|
|
16754
|
+
description: "The lens criteria in play \u2014 what the user would have to relax. Present when the lens carries any."
|
|
16755
|
+
},
|
|
16756
|
+
narrow_locations: {
|
|
16757
|
+
type: "array",
|
|
16758
|
+
description: "Include-locations that resolved to a city-scale area or smaller ({id, name, level}). On an empty lens, name these first: this is the fingerprint of a whole-country location that fell through to a same-named village (product#3951).",
|
|
16759
|
+
items: { type: "object" }
|
|
16760
|
+
}
|
|
16761
|
+
},
|
|
16762
|
+
required: ["code", "message", "retryable"]
|
|
16763
|
+
},
|
|
16629
16764
|
next_steps: {
|
|
16630
16765
|
type: ["object", "null"],
|
|
16631
16766
|
description: "Ready-made NEXT STEPS for the host's choice widget. Each option has a SHORT `label` (\u22645 words, fits AskUserQuestion's label cap on Claude cowork/Claude Code) and a full `description`. For AskUserQuestion (cowork/Claude Code) pass each option as {label, description}. For ask_user_input_v0 (Claude chat/ChatGPT, string-only options) use the `description` as the option string. Use these VERBATIM, in order \u2014 do NOT re-derive, reword, or render as prose when a widget tool exists. options[0] is the artifact offer (build the lead triage board) whenever the batch is non-empty; options[1] is the enrich offer (kind:enrich_top_leads \u2014 route it to leadbay_enrich_titles scoped to the leadIds JUST shown (pass leads[].id + the pinned lens.id) with NO titles, so it runs the no-spend discovery preview; quota is only spent after the user picks titles + confirms channels on a follow-up call). When the batch is empty but the lens is still computing (computing_wishlist/computing_scores true), this carries a 'Re-pull in ~30s' option (kind:repull_computing) plus 'Refine audience' \u2014 render the widget so the user waits rather than seeing 'no leads.' null only when the batch is empty AND nothing is computing (a genuinely empty / over-narrow lens).",
|
|
@@ -16726,6 +16861,11 @@ var init_pull_leads = __esm({
|
|
|
16726
16861
|
computingWishlist: res.computing_wishlist,
|
|
16727
16862
|
computingScores: res.computing_scores
|
|
16728
16863
|
});
|
|
16864
|
+
const lensIsEmpty = leadCount === 0 && (res.pagination?.total ?? 0) === 0;
|
|
16865
|
+
const emptyReason = lensIsEmpty ? await diagnoseEmptyLens(client, lensId, {
|
|
16866
|
+
wishlist: res.computing_wishlist,
|
|
16867
|
+
scores: res.computing_scores
|
|
16868
|
+
}) : null;
|
|
16729
16869
|
return withAgentMemoryMeta(client, {
|
|
16730
16870
|
lens: { id: lensId },
|
|
16731
16871
|
leads: res.items.map((lead) => ({
|
|
@@ -16737,6 +16877,7 @@ var init_pull_leads = __esm({
|
|
|
16737
16877
|
next_page: nextPage,
|
|
16738
16878
|
computing_wishlist: res.computing_wishlist,
|
|
16739
16879
|
computing_scores: res.computing_scores,
|
|
16880
|
+
empty_reason: emptyReason,
|
|
16740
16881
|
next_steps: nextSteps,
|
|
16741
16882
|
_meta: {
|
|
16742
16883
|
region: client.region,
|
|
@@ -31730,7 +31871,7 @@ var OAUTH_BASE_URLS = {
|
|
|
31730
31871
|
fr: "https://staging.api.leadbay.app"
|
|
31731
31872
|
}
|
|
31732
31873
|
};
|
|
31733
|
-
var VERSION = "0.
|
|
31874
|
+
var VERSION = "0.31.0";
|
|
31734
31875
|
var HELP = `
|
|
31735
31876
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
31736
31877
|
|
package/dist/http-server.js
CHANGED
|
@@ -10924,6 +10924,11 @@ WHEN NOT TO USE: when the user has named a specific lens \u2014 pass \`lensId\`
|
|
|
10924
10924
|
|
|
10925
10925
|
The active lens can change between calls (5-min cache + backend \`last_requested_lens\`). If a multi-step workflow depends on staying on one lens, **capture \`response.lens.id\` from the first response and pass it as the \`lensId\` argument on every subsequent Leadbay call** \u2014 including re-pulls, bulk qualifies, and research. (Field-name caveat: response nests it as \`lens.id\`; the parameter is \`lensId\`.) Re-pulling without \`lensId\` after a long-running tool may silently switch to a different lens and discard prior work.
|
|
10926
10926
|
|
|
10927
|
+
**EMPTY BATCH \u2014 route on \`empty_reason\`, never loop.** When \`leads\` is empty the response carries \`empty_reason: {code, message, retryable, criteria?, narrow_locations?}\`. \`retryable\` is the only field that decides what you do next:
|
|
10928
|
+
|
|
10929
|
+
- \`retryable: true\` (always \`code: "computing"\`) \u2014 the lens is still building. Say so, pull ONCE more in ~30s. Do not call it empty.
|
|
10930
|
+
- \`retryable: false\` \u2014 no amount of re-pulling, lens-switching or \`leadbay_extend_lens\` can produce leads on these criteria. **Stop calling tools.** Surface \`message\` to the user, name the criteria from \`criteria\` (and \`narrow_locations\` first when present \u2014 a city-scale geo scope is the usual culprit), and offer \`leadbay_adjust_audience\` to widen. A refill on a zero-candidate lens answers "queued", consumes no quota and delivers nothing, so retrying reads as progress while achieving none (product#3995).
|
|
10931
|
+
|
|
10927
10932
|
---
|
|
10928
10933
|
|
|
10929
10934
|
## RENDERING \u2014 markdown table, three columns, score-bar driven
|
|
@@ -18328,6 +18333,101 @@ var prepareOutreach = {
|
|
|
18328
18333
|
}
|
|
18329
18334
|
};
|
|
18330
18335
|
|
|
18336
|
+
// ../core/dist/composite/_empty-lens-reason.js
|
|
18337
|
+
var CITY_LEVEL = 7;
|
|
18338
|
+
function criteriaOf(filter) {
|
|
18339
|
+
return filter?.lens_filter?.items?.flatMap((i) => i.criteria ?? []) ?? [];
|
|
18340
|
+
}
|
|
18341
|
+
function summariseCriteria(criteria) {
|
|
18342
|
+
const out = {};
|
|
18343
|
+
for (const c of criteria) {
|
|
18344
|
+
if (c.type === "sector_ids") {
|
|
18345
|
+
const key = c.is_excluded ? "excluded_sector_ids" : "sector_ids";
|
|
18346
|
+
out[key] = [...out[key] ?? [], ...c.sectors ?? []];
|
|
18347
|
+
} else if (c.type === "location_ids") {
|
|
18348
|
+
const key = c.is_excluded ? "excluded_location_ids" : "location_ids";
|
|
18349
|
+
out[key] = [...out[key] ?? [], ...c.locations ?? []];
|
|
18350
|
+
} else if (c.type === "size" && !c.is_excluded) {
|
|
18351
|
+
out.sizes = [...out.sizes ?? [], ...c.sizes ?? []];
|
|
18352
|
+
}
|
|
18353
|
+
}
|
|
18354
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
18355
|
+
}
|
|
18356
|
+
function narrowLocationsOf(filter, criteria) {
|
|
18357
|
+
const included = new Set(criteria.filter((c) => c.type === "location_ids" && !c.is_excluded).flatMap((c) => c.locations ?? []));
|
|
18358
|
+
if (included.size === 0)
|
|
18359
|
+
return [];
|
|
18360
|
+
const results = filter?.locations?.results ?? [];
|
|
18361
|
+
return results.filter((r) => typeof r.id === "string" && included.has(r.id) && typeof r.level === "number" && r.level >= CITY_LEVEL).map((r) => ({
|
|
18362
|
+
id: r.id,
|
|
18363
|
+
name: typeof r.name === "string" ? r.name : "",
|
|
18364
|
+
level: r.level
|
|
18365
|
+
}));
|
|
18366
|
+
}
|
|
18367
|
+
function narrowGeoSentence(narrow) {
|
|
18368
|
+
const names = narrow.map((n) => n.name).filter(Boolean);
|
|
18369
|
+
if (names.length === 0)
|
|
18370
|
+
return "";
|
|
18371
|
+
return ` Its geography is pinned to ${names.join(", ")} \u2014 a city-scale area or smaller, which on an empty lens is almost always the criterion to relax first.`;
|
|
18372
|
+
}
|
|
18373
|
+
async function diagnoseEmptyLens(client, lensId, computing) {
|
|
18374
|
+
if (computing.wishlist || computing.scores) {
|
|
18375
|
+
return {
|
|
18376
|
+
code: "computing",
|
|
18377
|
+
retryable: true,
|
|
18378
|
+
message: "This lens is still computing its leads. Pull again in ~30s \u2014 do NOT report it as empty yet."
|
|
18379
|
+
};
|
|
18380
|
+
}
|
|
18381
|
+
let row;
|
|
18382
|
+
try {
|
|
18383
|
+
const lenses = await client.request("GET", "/lenses");
|
|
18384
|
+
row = lenses.find((l) => String(l.id) === String(lensId));
|
|
18385
|
+
} catch {
|
|
18386
|
+
}
|
|
18387
|
+
let filter = null;
|
|
18388
|
+
try {
|
|
18389
|
+
filter = await client.request("GET", `/lenses/${lensId}/filter`);
|
|
18390
|
+
} catch {
|
|
18391
|
+
}
|
|
18392
|
+
const criteria = criteriaOf(filter);
|
|
18393
|
+
const summary = summariseCriteria(criteria);
|
|
18394
|
+
const narrow = narrowLocationsOf(filter, criteria);
|
|
18395
|
+
const geo = narrowGeoSentence(narrow);
|
|
18396
|
+
const extras = {
|
|
18397
|
+
...summary ? { criteria: summary } : {},
|
|
18398
|
+
...narrow.length > 0 ? { narrow_locations: narrow } : {}
|
|
18399
|
+
};
|
|
18400
|
+
if (row?.not_enough_lead_candidates) {
|
|
18401
|
+
return {
|
|
18402
|
+
code: "no_candidates",
|
|
18403
|
+
retryable: false,
|
|
18404
|
+
message: "This lens's criteria match no companies in the database, so it cannot fill." + geo + " Tell the user and offer to widen the audience (leadbay_adjust_audience) \u2014 extending or re-pulling will not help.",
|
|
18405
|
+
...extras
|
|
18406
|
+
};
|
|
18407
|
+
}
|
|
18408
|
+
if (row?.not_enough_new_leads) {
|
|
18409
|
+
return {
|
|
18410
|
+
code: "no_new_leads",
|
|
18411
|
+
retryable: false,
|
|
18412
|
+
message: "Every company matching this lens has already been delivered \u2014 there are no NEW leads left on these criteria. Tell the user; offer to widen the audience (leadbay_adjust_audience) or work the existing leads via leadbay_pull_followups.",
|
|
18413
|
+
...extras
|
|
18414
|
+
};
|
|
18415
|
+
}
|
|
18416
|
+
if (summary) {
|
|
18417
|
+
return {
|
|
18418
|
+
code: "audience_too_narrow",
|
|
18419
|
+
retryable: false,
|
|
18420
|
+
message: "This lens is finished computing and holds zero leads: its criteria intersect to nothing." + geo + " Tell the user which criteria are in play and offer to widen the audience (leadbay_adjust_audience). Do NOT call leadbay_extend_lens \u2014 a refill on a zero-candidate lens reports queued, consumes no quota, and delivers nothing.",
|
|
18421
|
+
...extras
|
|
18422
|
+
};
|
|
18423
|
+
}
|
|
18424
|
+
return {
|
|
18425
|
+
code: "unknown",
|
|
18426
|
+
retryable: false,
|
|
18427
|
+
message: "This lens is finished computing and holds zero leads, and carries no audience criteria that would explain it. Report this to the user rather than retrying; leadbay_report_friction is the way to flag it to the Leadbay team."
|
|
18428
|
+
};
|
|
18429
|
+
}
|
|
18430
|
+
|
|
18331
18431
|
// ../core/dist/composite/pull-leads.js
|
|
18332
18432
|
function normalizeLinkedinPage3(v) {
|
|
18333
18433
|
if (v == null)
|
|
@@ -18467,6 +18567,34 @@ var pullLeads = {
|
|
|
18467
18567
|
type: "boolean",
|
|
18468
18568
|
description: "True if scoring is still running."
|
|
18469
18569
|
},
|
|
18570
|
+
empty_reason: {
|
|
18571
|
+
type: ["object", "null"],
|
|
18572
|
+
description: "Why this LENS holds zero leads. null whenever leads were returned, and null when this page is empty only because it is past the end of a non-empty lens. `retryable` is the field to route on: true ONLY on code=computing (pull again in ~30s). On every other code re-pulling and leadbay_extend_lens are both futile \u2014 a refill on a zero-candidate lens answers 'queued', consumes no quota and delivers nothing \u2014 so surface `message` to the user and offer leadbay_adjust_audience instead of retrying.",
|
|
18573
|
+
properties: {
|
|
18574
|
+
code: {
|
|
18575
|
+
type: "string",
|
|
18576
|
+
description: "computing | no_candidates | no_new_leads | audience_too_narrow | unknown"
|
|
18577
|
+
},
|
|
18578
|
+
message: {
|
|
18579
|
+
type: "string",
|
|
18580
|
+
description: "The line to surface to the user."
|
|
18581
|
+
},
|
|
18582
|
+
retryable: {
|
|
18583
|
+
type: "boolean",
|
|
18584
|
+
description: "True only while the lens is still computing. False means no amount of re-pulling or extending can produce leads."
|
|
18585
|
+
},
|
|
18586
|
+
criteria: {
|
|
18587
|
+
type: "object",
|
|
18588
|
+
description: "The lens criteria in play \u2014 what the user would have to relax. Present when the lens carries any."
|
|
18589
|
+
},
|
|
18590
|
+
narrow_locations: {
|
|
18591
|
+
type: "array",
|
|
18592
|
+
description: "Include-locations that resolved to a city-scale area or smaller ({id, name, level}). On an empty lens, name these first: this is the fingerprint of a whole-country location that fell through to a same-named village (product#3951).",
|
|
18593
|
+
items: { type: "object" }
|
|
18594
|
+
}
|
|
18595
|
+
},
|
|
18596
|
+
required: ["code", "message", "retryable"]
|
|
18597
|
+
},
|
|
18470
18598
|
next_steps: {
|
|
18471
18599
|
type: ["object", "null"],
|
|
18472
18600
|
description: "Ready-made NEXT STEPS for the host's choice widget. Each option has a SHORT `label` (\u22645 words, fits AskUserQuestion's label cap on Claude cowork/Claude Code) and a full `description`. For AskUserQuestion (cowork/Claude Code) pass each option as {label, description}. For ask_user_input_v0 (Claude chat/ChatGPT, string-only options) use the `description` as the option string. Use these VERBATIM, in order \u2014 do NOT re-derive, reword, or render as prose when a widget tool exists. options[0] is the artifact offer (build the lead triage board) whenever the batch is non-empty; options[1] is the enrich offer (kind:enrich_top_leads \u2014 route it to leadbay_enrich_titles scoped to the leadIds JUST shown (pass leads[].id + the pinned lens.id) with NO titles, so it runs the no-spend discovery preview; quota is only spent after the user picks titles + confirms channels on a follow-up call). When the batch is empty but the lens is still computing (computing_wishlist/computing_scores true), this carries a 'Re-pull in ~30s' option (kind:repull_computing) plus 'Refine audience' \u2014 render the widget so the user waits rather than seeing 'no leads.' null only when the batch is empty AND nothing is computing (a genuinely empty / over-narrow lens).",
|
|
@@ -18567,6 +18695,11 @@ var pullLeads = {
|
|
|
18567
18695
|
computingWishlist: res.computing_wishlist,
|
|
18568
18696
|
computingScores: res.computing_scores
|
|
18569
18697
|
});
|
|
18698
|
+
const lensIsEmpty = leadCount === 0 && (res.pagination?.total ?? 0) === 0;
|
|
18699
|
+
const emptyReason = lensIsEmpty ? await diagnoseEmptyLens(client, lensId, {
|
|
18700
|
+
wishlist: res.computing_wishlist,
|
|
18701
|
+
scores: res.computing_scores
|
|
18702
|
+
}) : null;
|
|
18570
18703
|
return withAgentMemoryMeta(client, {
|
|
18571
18704
|
lens: { id: lensId },
|
|
18572
18705
|
leads: res.items.map((lead) => ({
|
|
@@ -18578,6 +18711,7 @@ var pullLeads = {
|
|
|
18578
18711
|
next_page: nextPage,
|
|
18579
18712
|
computing_wishlist: res.computing_wishlist,
|
|
18580
18713
|
computing_scores: res.computing_scores,
|
|
18714
|
+
empty_reason: emptyReason,
|
|
18581
18715
|
next_steps: nextSteps,
|
|
18582
18716
|
_meta: {
|
|
18583
18717
|
region: client.region,
|
|
@@ -28307,7 +28441,7 @@ function parseWriteEnv(env = process.env) {
|
|
|
28307
28441
|
}
|
|
28308
28442
|
|
|
28309
28443
|
// src/http-server.ts
|
|
28310
|
-
var VERSION = true ? "0.
|
|
28444
|
+
var VERSION = true ? "0.31.0" : "0.0.0-dev";
|
|
28311
28445
|
var PORT = Number(process.env.PORT ?? 8080);
|
|
28312
28446
|
var HOST = process.env.HOST ?? "0.0.0.0";
|
|
28313
28447
|
var logger = {
|
package/dist/installer-gui.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leadbay/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"mcpName": "io.github.leadbay/leadbay-mcp",
|
|
5
5
|
"description": "Model Context Protocol (MCP) server for Leadbay — AI lead discovery, qualification, and enrichment for Claude Desktop, Cursor, and Claude Code.",
|
|
6
6
|
"type": "module",
|