@leadbay/mcp 0.38.2 → 0.39.1
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 +20 -0
- package/dist/bin.js +409 -124
- package/dist/http-server.js +302 -55
- package/dist/installer-electron.js +1 -1
- package/dist/installer-gui.js +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1369,12 +1369,12 @@ var init_ws_client = __esm({
|
|
|
1369
1369
|
this.pingTimer = setInterval(() => this.sendPing(), PING_INTERVAL_MS);
|
|
1370
1370
|
});
|
|
1371
1371
|
ws.addEventListener("message", (ev) => {
|
|
1372
|
-
const
|
|
1373
|
-
if (!
|
|
1372
|
+
const text3 = typeof ev.data === "string" ? ev.data : String(ev.data ?? "");
|
|
1373
|
+
if (!text3)
|
|
1374
1374
|
return;
|
|
1375
1375
|
let msg;
|
|
1376
1376
|
try {
|
|
1377
|
-
msg = JSON.parse(
|
|
1377
|
+
msg = JSON.parse(text3);
|
|
1378
1378
|
} catch {
|
|
1379
1379
|
this.logger?.warn?.("notifications.ws non_json_frame");
|
|
1380
1380
|
return;
|
|
@@ -2843,7 +2843,7 @@ measured:
|
|
|
2843
2843
|
\`employees_max: number\`. FLAT numbers \u2014 nested \`employees: {min, max}\`
|
|
2844
2844
|
exists only in RESULT payloads. \`example_lead.employees\` does not filter.
|
|
2845
2845
|
\`locations\` take city/state/region names ("Dallas, TX", "\xCEle-de-France");
|
|
2846
|
-
a country
|
|
2846
|
+
never a country: this workspace's own is dropped, any other is refused.
|
|
2847
2847
|
7. **Prefer \`example_lead\` over \`query\`.** Query matches topic *vocabulary*:
|
|
2848
2848
|
"gyms that need durable flooring" surfaced flooring VENDORS, 0 delivered.
|
|
2849
2849
|
Use \`query\` only for signal an example can't express.
|
|
@@ -3913,8 +3913,31 @@ server-side: past the 30-min wall clock a job reads \`completed_partial\`
|
|
|
3913
3913
|
delivered leads via leadbay_qualify_leads \`prior_deliveries\`). A 404 means
|
|
3914
3914
|
unknown job or another org's job.
|
|
3915
3915
|
|
|
3916
|
+
\`compact: true\` (pass it whenever \`next_poll\` carries it) returns the rows of
|
|
3917
|
+
a free identity pass, 100 at a time from \`offset\`, instead of leads.
|
|
3918
|
+
|
|
3916
3919
|
---
|
|
3917
3920
|
|
|
3921
|
+
## RENDERING \u2014 identity pass (\`rows[]\`)
|
|
3922
|
+
|
|
3923
|
+
\`rows[]\` replaces \`leads[]\`: one row per company, in the user's order. One
|
|
3924
|
+
table: **Company** (\`input\`, plus \`name\` when it differs) \xB7 **Website** \xB7
|
|
3925
|
+
**LinkedIn**. Skipped rows stay in it: \`not_in_universe\` \u2192 "not in Leadbay",
|
|
3926
|
+
\`low_confidence_identity\` \u2192 "several matches \u2014 add a city or website".
|
|
3927
|
+
|
|
3928
|
+
Then ONE coverage line from \`summary\`: found \`resolved\` of \`rows_total\` \xB7
|
|
3929
|
+
\`with_website\` with a website \xB7 \`with_linkedin\` with a LinkedIn \xB7
|
|
3930
|
+
\`ambiguous\` unclear \xB7 \`not_found\` not found. When most rows lack what the
|
|
3931
|
+
user asked for, that line is the answer: say so, and do not look the
|
|
3932
|
+
missing ones up one by one.
|
|
3933
|
+
|
|
3934
|
+
Done job with \`next_poll.offset\`: say "showing N of \`rows_total\`" and offer
|
|
3935
|
+
the rest via \`leadbay_lead_job_status(job_id, compact: true, offset)\`.
|
|
3936
|
+
|
|
3937
|
+
\`file\` set (local install only): every row is saved there as a CSV. Give the
|
|
3938
|
+
user that path.
|
|
3939
|
+
|
|
3940
|
+
|
|
3918
3941
|
## RENDERING \u2014 delivery table + honest funnel line
|
|
3919
3942
|
|
|
3920
3943
|
Render delivered leads (\`leads[]\`, i.e. items with status \`delivered\` or
|
|
@@ -5157,13 +5180,14 @@ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible
|
|
|
5157
5180
|
`;
|
|
5158
5181
|
leadbay_qualify_leads = `## WHEN TO USE
|
|
5159
5182
|
|
|
5160
|
-
Trigger phrases: "qualify these companies", "vet this list", "which of these fit our ICP", "score these websites / accounts", "get me the right contact at these companies", "re-qualify what you delivered last week".
|
|
5183
|
+
Trigger phrases: "for each of these companies, give me the website / LinkedIn", "qualify these companies", "vet this list", "which of these fit our ICP", "score these websites / accounts", "get me the right contact at these companies", "re-qualify what you delivered last week".
|
|
5161
5184
|
|
|
5162
5185
|
Do NOT use for: "find me new leads / companies that <profile>" \u2192 \`leadbay_find_new_leads\`; "qualify the top N of my lens batch" \u2192 \`leadbay_bulk_qualify_leads\`; "import this CSV file" \u2192 \`leadbay_import_leads\`; "tell me about <one company> in depth" \u2192 \`leadbay_research_lead_by_name_fuzzy\`; "add emails to the contacts I selected" \u2192 \`leadbay_enrich_titles\`.
|
|
5163
5186
|
|
|
5164
5187
|
Prefer when: the user points at SPECIFIC companies (ids, websites, names, a pasted list, "what you found yesterday") and wants fit verdicts and/or the right person to talk to.
|
|
5165
5188
|
|
|
5166
5189
|
Examples that SHOULD invoke this tool:
|
|
5190
|
+
- "For each of these 300 brokers, give me the website and LinkedIn, nothing else."
|
|
5167
5191
|
- "Here are 60 restaurant websites from my Austin sweep \u2014 which fit, and who's the owner?"
|
|
5168
5192
|
- "Re-qualify last week's delivery and get phone numbers for the good ones."
|
|
5169
5193
|
- "Vet these 12 accounts from my spreadsheet against our criteria."
|
|
@@ -5175,7 +5199,8 @@ Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
|
5175
5199
|
|
|
5176
5200
|
## RENDER (quick)
|
|
5177
5201
|
|
|
5178
|
-
|
|
5202
|
+
Identity pass (\`rows[]\`): one table Company / Website / LinkedIn, then the
|
|
5203
|
+
coverage line. Otherwise a 3-col table for delivered items (fit bar + company / why-fits \u226420 words /
|
|
5179
5204
|
contact + channels) in returned order, then a compact Ref \u2192 Outcome table
|
|
5180
5205
|
for skipped refs (not_in_universe, low_confidence_identity, ... in plain
|
|
5181
5206
|
words), then the honest funnel line. Full algorithm below.
|
|
@@ -5190,6 +5215,14 @@ when asked \u2014 enriched with verified channels. Answers arrive per-item from
|
|
|
5190
5215
|
job; this tool polls up to \`wait_seconds\` (default 45) and hands off to
|
|
5191
5216
|
\`leadbay_lead_job_status\` when the batch needs longer.
|
|
5192
5217
|
|
|
5218
|
+
**A list of companies is ONE call, never a loop** of
|
|
5219
|
+
\`leadbay_research_lead_by_name_fuzzy\`: "for each of these companies give me
|
|
5220
|
+
X", a pasted list, a file. Identity only (in Leadbay or not, website,
|
|
5221
|
+
LinkedIn) \u2192 \`qualify: false\`: free, no \`confirm\`, returns \`rows[]\` and
|
|
5222
|
+
coverage counts. A fit question over the list ("which of these do
|
|
5223
|
+
factoring") keeps \`qualify: true\` and its quote. Over 500: one call per 500,
|
|
5224
|
+
each with its own \`request_id\`.
|
|
5225
|
+
|
|
5193
5226
|
**Refs are flexible; outcomes are per-item.** \`lead_refs\` accepts any mix of
|
|
5194
5227
|
\`lead_id\`, \`website\`, \`name\`(+\`location\`), or a stable \`contact_id\` from a
|
|
5195
5228
|
prior result (enrichment then targets exactly that person, never a re-match).
|
|
@@ -5226,6 +5259,26 @@ Retry-After beyond \u2014 wait, don't hammer), 30-min job wall clock.
|
|
|
5226
5259
|
|
|
5227
5260
|
---
|
|
5228
5261
|
|
|
5262
|
+
## RENDERING \u2014 identity pass (\`rows[]\`)
|
|
5263
|
+
|
|
5264
|
+
\`rows[]\` replaces \`leads[]\`: one row per company, in the user's order. One
|
|
5265
|
+
table: **Company** (\`input\`, plus \`name\` when it differs) \xB7 **Website** \xB7
|
|
5266
|
+
**LinkedIn**. Skipped rows stay in it: \`not_in_universe\` \u2192 "not in Leadbay",
|
|
5267
|
+
\`low_confidence_identity\` \u2192 "several matches \u2014 add a city or website".
|
|
5268
|
+
|
|
5269
|
+
Then ONE coverage line from \`summary\`: found \`resolved\` of \`rows_total\` \xB7
|
|
5270
|
+
\`with_website\` with a website \xB7 \`with_linkedin\` with a LinkedIn \xB7
|
|
5271
|
+
\`ambiguous\` unclear \xB7 \`not_found\` not found. When most rows lack what the
|
|
5272
|
+
user asked for, that line is the answer: say so, and do not look the
|
|
5273
|
+
missing ones up one by one.
|
|
5274
|
+
|
|
5275
|
+
Done job with \`next_poll.offset\`: say "showing N of \`rows_total\`" and offer
|
|
5276
|
+
the rest via \`leadbay_lead_job_status(job_id, compact: true, offset)\`.
|
|
5277
|
+
|
|
5278
|
+
\`file\` set (local install only): every row is saved there as a CSV. Give the
|
|
5279
|
+
user that path.
|
|
5280
|
+
|
|
5281
|
+
|
|
5229
5282
|
## RENDERING \u2014 delivery table + honest funnel line
|
|
5230
5283
|
|
|
5231
5284
|
Render delivered leads (\`leads[]\`, i.e. items with status \`delivered\` or
|
|
@@ -5380,6 +5433,8 @@ Pick the 2-3 options that match what actually happened:
|
|
|
5380
5433
|
| Items skipped \`low_confidence_identity\` | "Pick the right match" (show \`resolution.alternatives\`) | leadbay_qualify_leads with the chosen lead_id |
|
|
5381
5434
|
| Contacts delivered without channels | "Get verified emails/phones for the keepers (uses quota \u2014 say so first)" | leadbay_qualify_leads(lead_refs with contact_id, channels) |
|
|
5382
5435
|
| Disqualified with evidence | "Review why \u2014 adjust qualification questions if the criteria are off" | leadbay_get_qualification_questions |
|
|
5436
|
+
| Identity pass, list over 500 | "Run the next 500" | leadbay_qualify_leads(next 500, qualify: false) |
|
|
5437
|
+
| Identity pass, \`summary.without_website\` > 0. LAST option, never run unasked | "N have no website in Leadbay. Web research can look for one. It uses your plan's quota and may find none." | leadbay_qualify_leads(lead_refs: [{lead_id}] of rows without website, qualify: true) \u2192 quote first, confirm on the user's yes |
|
|
5383
5438
|
`;
|
|
5384
5439
|
leadbay_qualify_status = `Retrieve the current state of a bulk_qualify_leads or import_and_qualify launch. Which ids to pass depends on which tool launched it, because only one of them creates a qualification job on the backend:
|
|
5385
5440
|
|
|
@@ -5858,9 +5913,9 @@ out?"\`
|
|
|
5858
5913
|
|
|
5859
5914
|
Trigger phrases: "look up <Company>", "research <Company>", "what do we know about <Company>".
|
|
5860
5915
|
|
|
5861
|
-
Do NOT use for: "picked row with leadId" \u2192 \`leadbay_research_lead_by_id\`; "draft outreach for <Contact>" \u2192 \`leadbay_prepare_outreach\`.
|
|
5916
|
+
Do NOT use for: "picked row with leadId" \u2192 \`leadbay_research_lead_by_id\`; "draft outreach for <Contact>" \u2192 \`leadbay_prepare_outreach\`; "for each of these companies\u2026" \u2192 \`leadbay_qualify_leads\`.
|
|
5862
5917
|
|
|
5863
|
-
Prefer when:
|
|
5918
|
+
Prefer when: ONE company name or domain in prose, no Leadbay id yet \u2014 always pass \`website\` if a domain was mentioned
|
|
5864
5919
|
|
|
5865
5920
|
Examples that SHOULD invoke this tool:
|
|
5866
5921
|
- "Look up Acme Corp for me."
|
|
@@ -5870,7 +5925,7 @@ Examples that SHOULD invoke this tool:
|
|
|
5870
5925
|
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
5871
5926
|
- "Tell me about that lead I just picked."
|
|
5872
5927
|
- "Draft outreach to Acme's CTO."
|
|
5873
|
-
- "
|
|
5928
|
+
- "Websites and LinkedIn for these 200 companies."
|
|
5874
5929
|
|
|
5875
5930
|
---
|
|
5876
5931
|
|
|
@@ -5884,15 +5939,13 @@ list" into an answer. With only a contact email, pass \`email\`: the company
|
|
|
5884
5939
|
domain is derived from it, consumer mailboxes ignored.
|
|
5885
5940
|
|
|
5886
5941
|
When the registry cannot pick one company it returns \`{resolution:
|
|
5887
|
-
"ambiguous", query, candidates:[
|
|
5888
|
-
|
|
5942
|
+
"ambiguous", query, candidates:[\u2026]}\` instead of a card. Ask which one; never
|
|
5943
|
+
guess from \`score\`.
|
|
5889
5944
|
|
|
5890
5945
|
\`LEAD_NOT_FOUND\` is not a dead end: its hint names the field that would have
|
|
5891
5946
|
found it \u2014 \`website\` or \`registry_number\`, both params. Ask for it and call
|
|
5892
5947
|
again. Do not offer an import before asking.
|
|
5893
5948
|
|
|
5894
|
-
Offer \`_meta.match_candidates\` when present.
|
|
5895
|
-
|
|
5896
5949
|
---
|
|
5897
5950
|
|
|
5898
5951
|
## RENDERING \u2014 single-record research card, mode-adaptive
|
|
@@ -7199,7 +7252,7 @@ measured:
|
|
|
7199
7252
|
\`employees_max: number\`. FLAT numbers \u2014 nested \`employees: {min, max}\`
|
|
7200
7253
|
exists only in RESULT payloads. \`example_lead.employees\` does not filter.
|
|
7201
7254
|
\`locations\` take city/state/region names ("Dallas, TX", "\xCEle-de-France");
|
|
7202
|
-
a country
|
|
7255
|
+
never a country: this workspace's own is dropped, any other is refused.
|
|
7203
7256
|
7. **Prefer \`example_lead\` over \`query\`.** Query matches topic *vocabulary*:
|
|
7204
7257
|
"gyms that need durable flooring" surfaced flooring VENDORS, 0 delivered.
|
|
7205
7258
|
Use \`query\` only for signal an example can't express.
|
|
@@ -7665,12 +7718,28 @@ var init_list_lenses = __esm({
|
|
|
7665
7718
|
}
|
|
7666
7719
|
});
|
|
7667
7720
|
|
|
7721
|
+
// ../core/dist/interactions.js
|
|
7722
|
+
function reportLeadInteractions(client, lensId, leadIds, types, logger) {
|
|
7723
|
+
if (leadIds.length === 0 || types.length === 0)
|
|
7724
|
+
return;
|
|
7725
|
+
const events = leadIds.flatMap((leadId) => types.map((type) => ({ type, lead_id: leadId, lens_id: String(lensId) })));
|
|
7726
|
+
void client.request("POST", "/interactions", events).catch((err) => {
|
|
7727
|
+
logger?.warn?.(`interactions: ${types.join("+")} for ${leadIds.length} lead(s) not recorded: ${err?.message ?? err?.code ?? err}`);
|
|
7728
|
+
});
|
|
7729
|
+
}
|
|
7730
|
+
var init_interactions = __esm({
|
|
7731
|
+
"../core/dist/interactions.js"() {
|
|
7732
|
+
"use strict";
|
|
7733
|
+
}
|
|
7734
|
+
});
|
|
7735
|
+
|
|
7668
7736
|
// ../core/dist/tools/discover-leads.js
|
|
7669
7737
|
var discoverLeads;
|
|
7670
7738
|
var init_discover_leads = __esm({
|
|
7671
7739
|
"../core/dist/tools/discover-leads.js"() {
|
|
7672
7740
|
"use strict";
|
|
7673
7741
|
init_tool_descriptions_generated();
|
|
7742
|
+
init_interactions();
|
|
7674
7743
|
discoverLeads = {
|
|
7675
7744
|
name: "leadbay_discover_leads",
|
|
7676
7745
|
annotations: {
|
|
@@ -7699,11 +7768,12 @@ var init_discover_leads = __esm({
|
|
|
7699
7768
|
},
|
|
7700
7769
|
additionalProperties: false
|
|
7701
7770
|
},
|
|
7702
|
-
execute: async (client, params) => {
|
|
7771
|
+
execute: async (client, params, ctx) => {
|
|
7703
7772
|
const lensId = params.lensId ?? await client.resolveDefaultLens();
|
|
7704
7773
|
const page = params.page ?? 0;
|
|
7705
7774
|
const count = Math.min(params.count ?? 20, 50);
|
|
7706
7775
|
const res = await client.request("GET", `/lenses/${lensId}/leads/wishlist?count=${count}&page=${page}&contacts=true`);
|
|
7776
|
+
reportLeadInteractions(client, lensId, res.items.map((lead) => lead.id), ["LEAD_SEEN"], ctx?.logger);
|
|
7707
7777
|
const totalPages = res.pagination?.pages ?? 0;
|
|
7708
7778
|
const currentPage = res.pagination?.page ?? page;
|
|
7709
7779
|
const hasMore = currentPage < totalPages - 1;
|
|
@@ -7752,6 +7822,7 @@ var init_get_lead_profile = __esm({
|
|
|
7752
7822
|
"../core/dist/tools/get-lead-profile.js"() {
|
|
7753
7823
|
"use strict";
|
|
7754
7824
|
init_tool_descriptions_generated();
|
|
7825
|
+
init_interactions();
|
|
7755
7826
|
getLeadProfile = {
|
|
7756
7827
|
name: "leadbay_get_lead_profile",
|
|
7757
7828
|
annotations: {
|
|
@@ -7799,13 +7870,9 @@ var init_get_lead_profile = __esm({
|
|
|
7799
7870
|
},
|
|
7800
7871
|
required: ["lead", "contacts"]
|
|
7801
7872
|
},
|
|
7802
|
-
execute: async (client, params) => {
|
|
7873
|
+
execute: async (client, params, ctx) => {
|
|
7803
7874
|
const lensId = params.lensId ?? await client.resolveDefaultLens();
|
|
7804
|
-
|
|
7805
|
-
{ type: "LEAD_SEEN", leadId: params.leadId, lensId: String(lensId) },
|
|
7806
|
-
{ type: "LEAD_CLICKED", leadId: params.leadId, lensId: String(lensId) }
|
|
7807
|
-
]).catch(() => {
|
|
7808
|
-
});
|
|
7875
|
+
reportLeadInteractions(client, lensId, [params.leadId], ["LEAD_SEEN", "LEAD_CLICKED"], ctx?.logger);
|
|
7809
7876
|
const [leadResult, qualResult, contactsResult, paidContactsResult, webFetchResult] = await Promise.allSettled([
|
|
7810
7877
|
client.request("GET", `/lenses/${lensId}/leads/${params.leadId}`),
|
|
7811
7878
|
client.request("GET", `/leads/${params.leadId}/ai_agent_responses`),
|
|
@@ -14238,6 +14305,7 @@ var init_pull_leads = __esm({
|
|
|
14238
14305
|
"../core/dist/composite/pull-leads.js"() {
|
|
14239
14306
|
"use strict";
|
|
14240
14307
|
init_lead_order();
|
|
14308
|
+
init_interactions();
|
|
14241
14309
|
init_empty_lens_reason();
|
|
14242
14310
|
init_tool_descriptions_generated();
|
|
14243
14311
|
pullLeads = {
|
|
@@ -14376,6 +14444,9 @@ var init_pull_leads = __esm({
|
|
|
14376
14444
|
return resolvedOrder.error;
|
|
14377
14445
|
const orderQs = resolvedOrder.order ? `&order=${encodeURIComponent(resolvedOrder.order)}` : "";
|
|
14378
14446
|
const res = await client.request("GET", `/lenses/${lensId}/leads/wishlist?count=${count}&page=${page}&contacts=true${orderQs}`);
|
|
14447
|
+
if (params._reportSeen !== false) {
|
|
14448
|
+
reportLeadInteractions(client, lensId, res.items.map((lead) => lead.id), ["LEAD_SEEN"], ctx?.logger);
|
|
14449
|
+
}
|
|
14379
14450
|
const summaries = await Promise.all(res.items.map(async (lead) => {
|
|
14380
14451
|
try {
|
|
14381
14452
|
const r = await client.request("GET", `/leads/${lead.id}/ai_agent_responses`);
|
|
@@ -14469,12 +14540,12 @@ var init_pull_leads = __esm({
|
|
|
14469
14540
|
});
|
|
14470
14541
|
|
|
14471
14542
|
// ../core/dist/composite/_geo-helpers.js
|
|
14472
|
-
function expandAlias(
|
|
14473
|
-
const key =
|
|
14474
|
-
return CITY_ALIASES[key] ??
|
|
14543
|
+
function expandAlias(text3) {
|
|
14544
|
+
const key = text3.trim().toLowerCase();
|
|
14545
|
+
return CITY_ALIASES[key] ?? text3;
|
|
14475
14546
|
}
|
|
14476
|
-
function scoreMatch(
|
|
14477
|
-
const t =
|
|
14547
|
+
function scoreMatch(text3, match) {
|
|
14548
|
+
const t = text3.trim().toLowerCase();
|
|
14478
14549
|
const n = match.name.trim().toLowerCase();
|
|
14479
14550
|
if (n === t)
|
|
14480
14551
|
return 1;
|
|
@@ -14497,8 +14568,8 @@ async function resolveLocations(client, texts) {
|
|
|
14497
14568
|
const resolved = [...direct];
|
|
14498
14569
|
const ambiguities = [];
|
|
14499
14570
|
for (const originalText of free) {
|
|
14500
|
-
const
|
|
14501
|
-
const path = `/geo/search?q=${encodeURIComponent(
|
|
14571
|
+
const text3 = expandAlias(originalText);
|
|
14572
|
+
const path = `/geo/search?q=${encodeURIComponent(text3)}`;
|
|
14502
14573
|
let response;
|
|
14503
14574
|
try {
|
|
14504
14575
|
response = await client.request("GET", path);
|
|
@@ -14537,7 +14608,7 @@ async function resolveLocations(client, texts) {
|
|
|
14537
14608
|
name: r.name,
|
|
14538
14609
|
country: r.country,
|
|
14539
14610
|
level: r.level,
|
|
14540
|
-
score: scoreMatch(
|
|
14611
|
+
score: scoreMatch(text3, r)
|
|
14541
14612
|
})).sort((a, b) => {
|
|
14542
14613
|
if (b.score !== a.score)
|
|
14543
14614
|
return b.score - a.score;
|
|
@@ -14953,6 +15024,7 @@ var init_tour_plan = __esm({
|
|
|
14953
15024
|
"use strict";
|
|
14954
15025
|
init_pull_followups();
|
|
14955
15026
|
init_pull_leads();
|
|
15027
|
+
init_interactions();
|
|
14956
15028
|
init_country_guard();
|
|
14957
15029
|
init_tool_descriptions_generated();
|
|
14958
15030
|
DEFAULT_FOLLOWUPS_COUNT = 6;
|
|
@@ -15088,7 +15160,7 @@ var init_tour_plan = __esm({
|
|
|
15088
15160
|
city_id: params.city_id,
|
|
15089
15161
|
count: followupsCount
|
|
15090
15162
|
}, ctx),
|
|
15091
|
-
pullLeads.execute(client, { count: DISCOVER_OVER_PULL }, ctx)
|
|
15163
|
+
pullLeads.execute(client, { count: DISCOVER_OVER_PULL, _reportSeen: false }, ctx)
|
|
15092
15164
|
]);
|
|
15093
15165
|
if (followupsResult.status === "fulfilled") {
|
|
15094
15166
|
const r = followupsResult.value;
|
|
@@ -15124,6 +15196,10 @@ var init_tour_plan = __esm({
|
|
|
15124
15196
|
}
|
|
15125
15197
|
const filtered = rawDiscover.filter((l) => cityMatches(l, params.city));
|
|
15126
15198
|
const discoverLeads2 = filtered.slice(0, discoverCount);
|
|
15199
|
+
const pulledLensId = leadsResult.status === "fulfilled" ? leadsResult.value?.lens?.id : null;
|
|
15200
|
+
if (pulledLensId != null) {
|
|
15201
|
+
reportLeadInteractions(client, pulledLensId, discoverLeads2.map((l) => l.id), ["LEAD_SEEN"], ctx?.logger);
|
|
15202
|
+
}
|
|
15127
15203
|
const filterNote = params.city ? `Matched ${filtered.length}/${rawDiscover.length} Discover leads to '${params.city}'; returning top ${discoverLeads2.length}.` : `No city filter applied; returning top ${discoverLeads2.length} Discover leads.`;
|
|
15128
15204
|
return {
|
|
15129
15205
|
city: params.city ?? null,
|
|
@@ -15807,9 +15883,9 @@ ${firm.short_description}`);
|
|
|
15807
15883
|
out.push(`### ${sec.section_emoji ?? ""} ${label}`.trim());
|
|
15808
15884
|
const entries = Array.isArray(sec.entries) ? sec.entries : [];
|
|
15809
15885
|
for (const e of entries.slice(0, 5)) {
|
|
15810
|
-
const
|
|
15886
|
+
const text3 = e.text ?? e.summary ?? JSON.stringify(e).slice(0, 200);
|
|
15811
15887
|
const hot = e.hot === true ? " \u{1F525}" : "";
|
|
15812
|
-
out.push(`- ${
|
|
15888
|
+
out.push(`- ${text3}${hot}`);
|
|
15813
15889
|
}
|
|
15814
15890
|
if (entries.length > 5)
|
|
15815
15891
|
out.push(`- _${entries.length - 5} more \u2026_`);
|
|
@@ -15890,6 +15966,7 @@ var init_research_lead_by_id = __esm({
|
|
|
15890
15966
|
"../core/dist/composite/research-lead-by-id.js"() {
|
|
15891
15967
|
"use strict";
|
|
15892
15968
|
init_web_fetch_helpers();
|
|
15969
|
+
init_interactions();
|
|
15893
15970
|
init_tool_descriptions_generated();
|
|
15894
15971
|
researchLeadById = {
|
|
15895
15972
|
name: "leadbay_research_lead_by_id",
|
|
@@ -16134,11 +16211,7 @@ var init_research_lead_by_id = __esm({
|
|
|
16134
16211
|
}
|
|
16135
16212
|
const lensId = params.lensId ?? await client.resolveDefaultLens();
|
|
16136
16213
|
const leadId = params.leadId;
|
|
16137
|
-
|
|
16138
|
-
{ type: "LEAD_SEEN", leadId, lensId: String(lensId) },
|
|
16139
|
-
{ type: "LEAD_CLICKED", leadId, lensId: String(lensId) }
|
|
16140
|
-
]).catch(() => {
|
|
16141
|
-
});
|
|
16214
|
+
reportLeadInteractions(client, lensId, [leadId], ["LEAD_SEEN", "LEAD_CLICKED"], _ctx?.logger);
|
|
16142
16215
|
const [profileR, qualR, contactsR, webFetchR, activitiesR, orgContactsR] = await Promise.allSettled([
|
|
16143
16216
|
client.request("GET", `/lenses/${lensId}/leads/${leadId}`),
|
|
16144
16217
|
client.request("GET", `/leads/${leadId}/ai_agent_responses`),
|
|
@@ -17181,6 +17254,7 @@ var init_get_lead_custom_fields = __esm({
|
|
|
17181
17254
|
"../core/dist/composite/get-lead-custom-fields.js"() {
|
|
17182
17255
|
"use strict";
|
|
17183
17256
|
init_tool_descriptions_generated();
|
|
17257
|
+
init_interactions();
|
|
17184
17258
|
getLeadCustomFields = {
|
|
17185
17259
|
name: "leadbay_get_lead_custom_fields",
|
|
17186
17260
|
annotations: {
|
|
@@ -17224,11 +17298,7 @@ var init_get_lead_custom_fields = __esm({
|
|
|
17224
17298
|
},
|
|
17225
17299
|
execute: async (client, params, ctx) => {
|
|
17226
17300
|
const lensId = params.lensId ?? await client.resolveDefaultLens();
|
|
17227
|
-
|
|
17228
|
-
{ type: "LEAD_SEEN", leadId: params.leadId, lensId: String(lensId) },
|
|
17229
|
-
{ type: "LEAD_CLICKED", leadId: params.leadId, lensId: String(lensId) }
|
|
17230
|
-
]).catch(() => {
|
|
17231
|
-
});
|
|
17301
|
+
reportLeadInteractions(client, lensId, [params.leadId], ["LEAD_SEEN", "LEAD_CLICKED"], ctx?.logger);
|
|
17232
17302
|
const lead = await client.request("GET", `/lenses/${lensId}/leads/${params.leadId}`);
|
|
17233
17303
|
const entries = lead.custom_fields ?? [];
|
|
17234
17304
|
const needsCatalog = entries.some((e) => !e?.field?.name);
|
|
@@ -20702,8 +20772,8 @@ function tokens(s) {
|
|
|
20702
20772
|
return [];
|
|
20703
20773
|
return s.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
20704
20774
|
}
|
|
20705
|
-
function bestMatches(
|
|
20706
|
-
const want = new Set(tokens(
|
|
20775
|
+
function bestMatches(text3, taxonomy) {
|
|
20776
|
+
const want = new Set(tokens(text3));
|
|
20707
20777
|
if (want.size === 0)
|
|
20708
20778
|
return [];
|
|
20709
20779
|
const ranked = taxonomy.map((s) => {
|
|
@@ -20732,12 +20802,12 @@ async function resolveSectors(client, texts, ctx) {
|
|
|
20732
20802
|
}
|
|
20733
20803
|
const resolved = [...direct];
|
|
20734
20804
|
const ambiguities = [];
|
|
20735
|
-
for (const
|
|
20736
|
-
const matches = bestMatches(
|
|
20805
|
+
for (const text3 of free) {
|
|
20806
|
+
const matches = bestMatches(text3, taxonomy);
|
|
20737
20807
|
if (matches.length === 1 || matches.length >= 2 && matches[0].score >= 0.66 && matches[0].score - matches[1].score >= 0.34) {
|
|
20738
20808
|
resolved.push(matches[0].id);
|
|
20739
20809
|
} else {
|
|
20740
|
-
ambiguities.push({ sector_text:
|
|
20810
|
+
ambiguities.push({ sector_text: text3, matches });
|
|
20741
20811
|
}
|
|
20742
20812
|
}
|
|
20743
20813
|
return { resolved, ambiguities };
|
|
@@ -22066,6 +22136,7 @@ var init_find_new_leads = __esm({
|
|
|
22066
22136
|
"../core/dist/composite/find-new-leads.js"() {
|
|
22067
22137
|
"use strict";
|
|
22068
22138
|
init_mcp_job_helpers();
|
|
22139
|
+
init_country_guard();
|
|
22069
22140
|
init_tool_descriptions_generated();
|
|
22070
22141
|
DEFAULT_WAIT_SECONDS = 45;
|
|
22071
22142
|
findNewLeads = {
|
|
@@ -22190,6 +22261,21 @@ var init_find_new_leads = __esm({
|
|
|
22190
22261
|
"channels",
|
|
22191
22262
|
"exclude_lead_ids"
|
|
22192
22263
|
]);
|
|
22264
|
+
const homeCountry = detectCountryLocations(params.filters?.locations, "filters.locations", client.region).filter((hit) => hit.kind === "home_country");
|
|
22265
|
+
let countryNote;
|
|
22266
|
+
if (homeCountry.length > 0) {
|
|
22267
|
+
const dropped = new Set(homeCountry.map((hit) => hit.value));
|
|
22268
|
+
const raw = params.filters.locations;
|
|
22269
|
+
const rest = (Array.isArray(raw) ? raw : [raw]).filter((v) => !dropped.has(v));
|
|
22270
|
+
params = {
|
|
22271
|
+
...params,
|
|
22272
|
+
filters: { ...params.filters, locations: rest.length > 0 ? rest : void 0 }
|
|
22273
|
+
};
|
|
22274
|
+
const removed = [...dropped].map((v) => `"${v}"`).join(", ");
|
|
22275
|
+
countryNote = {
|
|
22276
|
+
note: rest.length > 0 ? `Removed ${removed} from filters.locations: a country is never a location filter. The search covers ${rest.map((v) => `"${v}"`).join(", ")} only.` : `Removed ${removed} from filters.locations: this workspace holds ${homeCountry[0].country} companies only, so the search already covers all of it.`
|
|
22277
|
+
};
|
|
22278
|
+
}
|
|
22193
22279
|
rejectCountryLocations(params.filters?.locations, client.region);
|
|
22194
22280
|
rejectMalformedExclusions(params.exclude_lead_ids);
|
|
22195
22281
|
rejectOversizedExclusions(params.exclude_lead_ids);
|
|
@@ -22267,6 +22353,7 @@ var init_find_new_leads = __esm({
|
|
|
22267
22353
|
return {
|
|
22268
22354
|
dry_run: true,
|
|
22269
22355
|
...forecast,
|
|
22356
|
+
...countryNote,
|
|
22270
22357
|
region: client.region
|
|
22271
22358
|
};
|
|
22272
22359
|
}
|
|
@@ -22287,6 +22374,7 @@ var init_find_new_leads = __esm({
|
|
|
22287
22374
|
estimated_cost: forecast?.estimated_cost ?? null,
|
|
22288
22375
|
items_requested: forecast?.items_requested ?? null,
|
|
22289
22376
|
hint: vetoed ? "confirm:false vetoed the run \u2014 nothing was submitted. Re-call with confirm:true to proceed, or drop qualify/channels for a free search." : "Tell the user what will run and that it uses their plan's quota (no amounts, no money), get an explicit go-ahead, then re-call with confirm:true. For a free search instead: omit qualify and channels.",
|
|
22377
|
+
...countryNote,
|
|
22290
22378
|
region: client.region
|
|
22291
22379
|
};
|
|
22292
22380
|
}
|
|
@@ -22341,6 +22429,7 @@ var init_find_new_leads = __esm({
|
|
|
22341
22429
|
since: snapshot.next_since ?? null,
|
|
22342
22430
|
suggested_wait_seconds: done ? 0 : 60
|
|
22343
22431
|
},
|
|
22432
|
+
...countryNote,
|
|
22344
22433
|
region: client.region
|
|
22345
22434
|
};
|
|
22346
22435
|
}
|
|
@@ -22348,6 +22437,144 @@ var init_find_new_leads = __esm({
|
|
|
22348
22437
|
}
|
|
22349
22438
|
});
|
|
22350
22439
|
|
|
22440
|
+
// ../core/dist/composite/_identity-rows.js
|
|
22441
|
+
function text(v) {
|
|
22442
|
+
return typeof v === "string" && v.trim() ? v.trim() : null;
|
|
22443
|
+
}
|
|
22444
|
+
function toRow(item) {
|
|
22445
|
+
const asked = item.ref?.requested_as ?? {};
|
|
22446
|
+
const row = {
|
|
22447
|
+
input_indexes: item.ref?.input_indexes ?? null,
|
|
22448
|
+
input: text(asked.name) ?? text(asked.website) ?? text(asked.lead_id) ?? text(asked.contact_id),
|
|
22449
|
+
status: item.status
|
|
22450
|
+
};
|
|
22451
|
+
if (item.status_reason)
|
|
22452
|
+
row.status_reason = item.status_reason;
|
|
22453
|
+
const lead = item.lead;
|
|
22454
|
+
if (!lead)
|
|
22455
|
+
return row;
|
|
22456
|
+
const company = lead.company ?? {};
|
|
22457
|
+
row.lead_id = lead.lead_id ?? item.ref?.lead_id ?? void 0;
|
|
22458
|
+
row.name = company.name;
|
|
22459
|
+
row.website = text(company.website);
|
|
22460
|
+
row.linkedin = text(company.socials?.linkedin);
|
|
22461
|
+
return row;
|
|
22462
|
+
}
|
|
22463
|
+
function identityRows(items) {
|
|
22464
|
+
const first = (i) => {
|
|
22465
|
+
const idx = i.ref?.input_indexes;
|
|
22466
|
+
return Array.isArray(idx) && idx.length > 0 ? Math.min(...idx) : Number.MAX_SAFE_INTEGER;
|
|
22467
|
+
};
|
|
22468
|
+
return [...items].sort((a, b) => first(a) - first(b) || a.seq - b.seq).map(toRow);
|
|
22469
|
+
}
|
|
22470
|
+
function identityCoverage(rows) {
|
|
22471
|
+
let resolved = 0;
|
|
22472
|
+
let ambiguous = 0;
|
|
22473
|
+
let notFound = 0;
|
|
22474
|
+
let otherSkipped = 0;
|
|
22475
|
+
let withWebsite = 0;
|
|
22476
|
+
let withLinkedin = 0;
|
|
22477
|
+
for (const r of rows) {
|
|
22478
|
+
if (r.status !== "skipped") {
|
|
22479
|
+
resolved += 1;
|
|
22480
|
+
if (r.website)
|
|
22481
|
+
withWebsite += 1;
|
|
22482
|
+
if (r.linkedin)
|
|
22483
|
+
withLinkedin += 1;
|
|
22484
|
+
} else if (r.status_reason === "low_confidence_identity")
|
|
22485
|
+
ambiguous += 1;
|
|
22486
|
+
else if (r.status_reason === "not_in_universe")
|
|
22487
|
+
notFound += 1;
|
|
22488
|
+
else
|
|
22489
|
+
otherSkipped += 1;
|
|
22490
|
+
}
|
|
22491
|
+
return {
|
|
22492
|
+
resolved,
|
|
22493
|
+
ambiguous,
|
|
22494
|
+
not_found: notFound,
|
|
22495
|
+
other_skipped: otherSkipped,
|
|
22496
|
+
with_website: withWebsite,
|
|
22497
|
+
with_linkedin: withLinkedin,
|
|
22498
|
+
without_website: resolved - withWebsite
|
|
22499
|
+
};
|
|
22500
|
+
}
|
|
22501
|
+
function identityCsv(rows) {
|
|
22502
|
+
return synthesizeCsv(CSV_HEADER, rows.map((r) => ({
|
|
22503
|
+
row: (r.input_indexes ?? []).map((i) => i + 1).join(";"),
|
|
22504
|
+
input: r.input ?? "",
|
|
22505
|
+
status: r.status,
|
|
22506
|
+
reason: r.status_reason ?? "",
|
|
22507
|
+
company: r.name ?? "",
|
|
22508
|
+
website: r.website ?? "",
|
|
22509
|
+
linkedin: r.linkedin ?? "",
|
|
22510
|
+
lead_id: r.lead_id ?? ""
|
|
22511
|
+
})));
|
|
22512
|
+
}
|
|
22513
|
+
async function saveIdentityFile(ctx, jobId, snapshot, items) {
|
|
22514
|
+
if (!ctx?.saveFile)
|
|
22515
|
+
return {};
|
|
22516
|
+
if (!TERMINAL_JOB_STATES.has(snapshot.job.state))
|
|
22517
|
+
return {};
|
|
22518
|
+
if (snapshot.items_truncated || items.length === 0)
|
|
22519
|
+
return {};
|
|
22520
|
+
try {
|
|
22521
|
+
const name = `leadbay-companies-${jobId.slice(0, 8)}.csv`;
|
|
22522
|
+
return { file: await ctx.saveFile(name, identityCsv(identityRows(items))) };
|
|
22523
|
+
} catch (e) {
|
|
22524
|
+
return { file_error: e instanceof Error ? e.message : String(e) };
|
|
22525
|
+
}
|
|
22526
|
+
}
|
|
22527
|
+
function readOffset(v) {
|
|
22528
|
+
const n = typeof v === "string" ? Number(v) : v;
|
|
22529
|
+
return typeof n === "number" && Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
|
|
22530
|
+
}
|
|
22531
|
+
function identityAnswer(jobId, snapshot, items, offset) {
|
|
22532
|
+
const all = identityRows(items);
|
|
22533
|
+
const rows = all.slice(offset, offset + IDENTITY_ROWS_PER_RESULT);
|
|
22534
|
+
const end = offset + rows.length;
|
|
22535
|
+
const done = TERMINAL_JOB_STATES.has(snapshot.job.state);
|
|
22536
|
+
const truncated = snapshot.items_truncated ?? false;
|
|
22537
|
+
const nextOffset = !done || truncated ? 0 : end < all.length ? end : null;
|
|
22538
|
+
return {
|
|
22539
|
+
summary: {
|
|
22540
|
+
...identityCoverage(all),
|
|
22541
|
+
stop_reason: snapshot.funnel?.stop_reason ?? null
|
|
22542
|
+
},
|
|
22543
|
+
rows,
|
|
22544
|
+
rows_total: all.length,
|
|
22545
|
+
rows_offset: offset,
|
|
22546
|
+
items_truncated: truncated,
|
|
22547
|
+
scope_notes: snapshot.explain?.scope_notes ?? [],
|
|
22548
|
+
still_running: !done,
|
|
22549
|
+
next_poll: nextOffset === null ? null : {
|
|
22550
|
+
tool: "leadbay_lead_job_status",
|
|
22551
|
+
job_id: jobId,
|
|
22552
|
+
compact: true,
|
|
22553
|
+
offset: nextOffset,
|
|
22554
|
+
suggested_wait_seconds: done ? 0 : 60
|
|
22555
|
+
}
|
|
22556
|
+
};
|
|
22557
|
+
}
|
|
22558
|
+
var IDENTITY_ROWS_PER_RESULT, CSV_HEADER;
|
|
22559
|
+
var init_identity_rows = __esm({
|
|
22560
|
+
"../core/dist/composite/_identity-rows.js"() {
|
|
22561
|
+
"use strict";
|
|
22562
|
+
init_mcp_job_helpers();
|
|
22563
|
+
init_import_leads();
|
|
22564
|
+
IDENTITY_ROWS_PER_RESULT = 100;
|
|
22565
|
+
CSV_HEADER = [
|
|
22566
|
+
"row",
|
|
22567
|
+
"input",
|
|
22568
|
+
"status",
|
|
22569
|
+
"reason",
|
|
22570
|
+
"company",
|
|
22571
|
+
"website",
|
|
22572
|
+
"linkedin",
|
|
22573
|
+
"lead_id"
|
|
22574
|
+
];
|
|
22575
|
+
}
|
|
22576
|
+
});
|
|
22577
|
+
|
|
22351
22578
|
// ../core/dist/composite/qualify-leads.js
|
|
22352
22579
|
function normalizeLeadRefs(refs) {
|
|
22353
22580
|
if (!Array.isArray(refs))
|
|
@@ -22388,7 +22615,7 @@ function rejectMalformedLeadRefs(refs) {
|
|
|
22388
22615
|
hint: "Each ref is an object whose fields are STRINGS \u2014 {lead_id} | {website} | {name, location?} | {contact_id}. A bare string is accepted and reshaped; null, numbers, arrays and non-string field values are not. Fix or drop those entries and re-call."
|
|
22389
22616
|
};
|
|
22390
22617
|
}
|
|
22391
|
-
function
|
|
22618
|
+
function text2(value) {
|
|
22392
22619
|
if (typeof value !== "string")
|
|
22393
22620
|
return null;
|
|
22394
22621
|
const v = value.trim().toLowerCase();
|
|
@@ -22396,7 +22623,7 @@ function text(value) {
|
|
|
22396
22623
|
}
|
|
22397
22624
|
function derivedRequestId(params) {
|
|
22398
22625
|
const refs = canonicalSet((params.lead_refs ?? []).map((r) => {
|
|
22399
|
-
const website =
|
|
22626
|
+
const website = text2(r.website);
|
|
22400
22627
|
return [
|
|
22401
22628
|
// UUIDs are case-insensitive to the backend, so an uppercase id and
|
|
22402
22629
|
// its lowercase form are the same lead and must share a key.
|
|
@@ -22407,8 +22634,8 @@ function derivedRequestId(params) {
|
|
|
22407
22634
|
// AND to one key. Fall back to the trimmed/lowercased raw value when
|
|
22408
22635
|
// it is not domain-shaped, rather than dropping the field.
|
|
22409
22636
|
website ? normalizeDomain(website) ?? website : null,
|
|
22410
|
-
|
|
22411
|
-
|
|
22637
|
+
text2(r.name),
|
|
22638
|
+
text2(r.location)
|
|
22412
22639
|
];
|
|
22413
22640
|
}));
|
|
22414
22641
|
const shape = {
|
|
@@ -22452,6 +22679,7 @@ var init_qualify_leads = __esm({
|
|
|
22452
22679
|
"use strict";
|
|
22453
22680
|
init_mcp_job_helpers();
|
|
22454
22681
|
init_import_leads();
|
|
22682
|
+
init_identity_rows();
|
|
22455
22683
|
init_tool_descriptions_generated();
|
|
22456
22684
|
DEFAULT_WAIT_SECONDS2 = 45;
|
|
22457
22685
|
LEAD_REF_FIELDS = [
|
|
@@ -22569,6 +22797,7 @@ var init_qualify_leads = __esm({
|
|
|
22569
22797
|
const buysChannels = (params.channels?.length ?? 0) > 0;
|
|
22570
22798
|
const buysQualification = qualify !== false;
|
|
22571
22799
|
const isPaid = buysQualification || buysChannels;
|
|
22800
|
+
const identityOnly = !isPaid && (params.contact_titles?.length ?? 0) === 0 && params.prior_deliveries == null;
|
|
22572
22801
|
const vetoed = confirm === false;
|
|
22573
22802
|
const consented = !vetoed && confirm === true;
|
|
22574
22803
|
const requestId = presentRequestId(params.request_id) ?? (isPaid ? derivedRequestId(params) : void 0);
|
|
@@ -22617,6 +22846,25 @@ var init_qualify_leads = __esm({
|
|
|
22617
22846
|
const done = TERMINAL_JOB_STATES.has(snapshot.job.state);
|
|
22618
22847
|
const indexed = submit.duplicate ?? false ? remapInputIndexes(snapshot.items, params.lead_refs) : { items: snapshot.items, remapped: true };
|
|
22619
22848
|
const view = { ...snapshot, items: indexed.items };
|
|
22849
|
+
if (identityOnly) {
|
|
22850
|
+
const answer = identityAnswer(submit.job_id, snapshot, view.items, 0);
|
|
22851
|
+
return {
|
|
22852
|
+
job_id: submit.job_id,
|
|
22853
|
+
request_id: requestId ?? null,
|
|
22854
|
+
duplicate_submit: submit.duplicate ?? false,
|
|
22855
|
+
state: snapshot.job.state,
|
|
22856
|
+
done,
|
|
22857
|
+
...answer,
|
|
22858
|
+
summary: {
|
|
22859
|
+
refs_submitted: params.lead_refs?.length ?? 0,
|
|
22860
|
+
items_requested: submit.items_requested,
|
|
22861
|
+
...answer.summary
|
|
22862
|
+
},
|
|
22863
|
+
input_indexes_remapped: submit.duplicate ?? false ? indexed.remapped : null,
|
|
22864
|
+
...await saveIdentityFile(ctx, submit.job_id, snapshot, view.items),
|
|
22865
|
+
region: client.region
|
|
22866
|
+
};
|
|
22867
|
+
}
|
|
22620
22868
|
return {
|
|
22621
22869
|
job_id: submit.job_id,
|
|
22622
22870
|
// Echo the key actually sent, so a retry can reuse it verbatim.
|
|
@@ -22685,6 +22933,7 @@ var init_lead_job_status = __esm({
|
|
|
22685
22933
|
"../core/dist/composite/lead-job-status.js"() {
|
|
22686
22934
|
"use strict";
|
|
22687
22935
|
init_mcp_job_helpers();
|
|
22936
|
+
init_identity_rows();
|
|
22688
22937
|
init_tool_descriptions_generated();
|
|
22689
22938
|
leadJobStatus = {
|
|
22690
22939
|
name: "leadbay_lead_job_status",
|
|
@@ -22714,6 +22963,14 @@ var init_lead_job_status = __esm({
|
|
|
22714
22963
|
wait_seconds: {
|
|
22715
22964
|
type: "number",
|
|
22716
22965
|
description: "0 (default) = instant snapshot. >0 = keep polling up to this many seconds until the job is terminal \u2014 use ~60 when the user asked to wait for results."
|
|
22966
|
+
},
|
|
22967
|
+
compact: {
|
|
22968
|
+
type: "boolean",
|
|
22969
|
+
description: "true = one row per company (name, website, LinkedIn) in the order of the user's list, plus coverage counts, instead of full leads. Pass it when next_poll carries it (a leadbay_qualify_leads identity pass). Reads the whole job; since is ignored."
|
|
22970
|
+
},
|
|
22971
|
+
offset: {
|
|
22972
|
+
type: "number",
|
|
22973
|
+
description: "With compact: the first row to return (default 0). Each result carries at most 100 rows; take the next value from next_poll.offset."
|
|
22717
22974
|
}
|
|
22718
22975
|
},
|
|
22719
22976
|
required: ["job_id"],
|
|
@@ -22721,8 +22978,19 @@ var init_lead_job_status = __esm({
|
|
|
22721
22978
|
},
|
|
22722
22979
|
execute: async (client, params, ctx) => {
|
|
22723
22980
|
const waitSeconds = clampWaitSeconds(params.wait_seconds, 0);
|
|
22724
|
-
const
|
|
22981
|
+
const compact = params.compact === true || params.compact === "true";
|
|
22982
|
+
const since = compact ? void 0 : params.since;
|
|
22983
|
+
const snapshot = waitSeconds > 0 ? await waitForJob(client, params.job_id, waitSeconds, ctx, void 0, since, params.limit) : await collectJobSnapshot(client, params.job_id, since, params.limit, ctx?.signal);
|
|
22725
22984
|
const done = TERMINAL_JOB_STATES.has(snapshot.job.state);
|
|
22985
|
+
if (compact) {
|
|
22986
|
+
return {
|
|
22987
|
+
job_id: params.job_id,
|
|
22988
|
+
state: snapshot.job.state,
|
|
22989
|
+
done,
|
|
22990
|
+
...identityAnswer(params.job_id, snapshot, snapshot.items, readOffset(params.offset)),
|
|
22991
|
+
region: client.region
|
|
22992
|
+
};
|
|
22993
|
+
}
|
|
22726
22994
|
const { leads, skipped } = splitItems(snapshot);
|
|
22727
22995
|
return {
|
|
22728
22996
|
job_id: params.job_id,
|
|
@@ -24164,8 +24432,8 @@ var init_send_feedback = __esm({
|
|
|
24164
24432
|
}
|
|
24165
24433
|
},
|
|
24166
24434
|
execute: async (client, params, ctx) => {
|
|
24167
|
-
const
|
|
24168
|
-
if (!
|
|
24435
|
+
const text3 = typeof params.message === "string" ? params.message.trim() : "";
|
|
24436
|
+
if (!text3) {
|
|
24169
24437
|
return {
|
|
24170
24438
|
error: true,
|
|
24171
24439
|
code: "BAD_INPUT",
|
|
@@ -24173,7 +24441,7 @@ var init_send_feedback = __esm({
|
|
|
24173
24441
|
hint: "Ask the user what they'd like to tell the Leadbay team, then call again with their words in `message`."
|
|
24174
24442
|
};
|
|
24175
24443
|
}
|
|
24176
|
-
const message =
|
|
24444
|
+
const message = text3.length > MESSAGE_MAX2 ? `${text3.slice(0, MESSAGE_MAX2 - 1)}\u2026` : text3;
|
|
24177
24445
|
if (!ctx?.sendFeedback) {
|
|
24178
24446
|
return {
|
|
24179
24447
|
sent: false,
|
|
@@ -24774,7 +25042,7 @@ var init_dist = __esm({
|
|
|
24774
25042
|
init_dist();
|
|
24775
25043
|
import { realpathSync } from "fs";
|
|
24776
25044
|
import { fileURLToPath } from "url";
|
|
24777
|
-
import { basename } from "path";
|
|
25045
|
+
import { basename as basename2 } from "path";
|
|
24778
25046
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24779
25047
|
|
|
24780
25048
|
// src/server.ts
|
|
@@ -27678,8 +27946,8 @@ var PROMPT_CATALOG_BULLETS = {
|
|
|
27678
27946
|
};
|
|
27679
27947
|
|
|
27680
27948
|
// src/prompts.ts
|
|
27681
|
-
function userMessage(
|
|
27682
|
-
return { role: "user", content: { type: "text", text:
|
|
27949
|
+
function userMessage(text3) {
|
|
27950
|
+
return { role: "user", content: { type: "text", text: text3 } };
|
|
27683
27951
|
}
|
|
27684
27952
|
function substitutePlaceholders(body, substitutions) {
|
|
27685
27953
|
let out = body;
|
|
@@ -29378,6 +29646,7 @@ ${url}
|
|
|
29378
29646
|
// RAW value: the friction redaction above is an ANALYTICS control, and
|
|
29379
29647
|
// must not change in-process tool behaviour.
|
|
29380
29648
|
triggered_by: rawTriggeredBy,
|
|
29649
|
+
saveFile: opts.saveFile,
|
|
29381
29650
|
// Route leadbay_send_feedback to Sentry's feedback inbox (same place
|
|
29382
29651
|
// the web app's form lands). NOOP_TELEMETRY returns false, so the
|
|
29383
29652
|
// tool reports honestly when telemetry is off.
|
|
@@ -29620,11 +29889,24 @@ ${url}
|
|
|
29620
29889
|
return server;
|
|
29621
29890
|
}
|
|
29622
29891
|
|
|
29892
|
+
// src/local-files.ts
|
|
29893
|
+
import { existsSync as existsSync2 } from "fs";
|
|
29894
|
+
import { writeFile } from "fs/promises";
|
|
29895
|
+
import { homedir } from "os";
|
|
29896
|
+
import { basename, join as join2 } from "path";
|
|
29897
|
+
async function saveToDownloads(name, content, home = homedir()) {
|
|
29898
|
+
const downloads = join2(home, "Downloads");
|
|
29899
|
+
const dir = existsSync2(downloads) ? downloads : home;
|
|
29900
|
+
const path = join2(dir, basename(name));
|
|
29901
|
+
await writeFile(path, content, "utf8");
|
|
29902
|
+
return path;
|
|
29903
|
+
}
|
|
29904
|
+
|
|
29623
29905
|
// installer/install-shared.ts
|
|
29624
29906
|
import { spawn } from "child_process";
|
|
29625
|
-
import { existsSync as
|
|
29626
|
-
import { join as
|
|
29627
|
-
import { homedir } from "os";
|
|
29907
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
29908
|
+
import { join as join3 } from "path";
|
|
29909
|
+
import { homedir as homedir2 } from "os";
|
|
29628
29910
|
var HOSTED_MCP_URL_CHATGPT = "https://mcp.leadbay.app/chatgpt/mcp";
|
|
29629
29911
|
function formatInstallOsLabel(platform = process.platform, arch = process.arch) {
|
|
29630
29912
|
const name = platform === "darwin" ? "macOS" : platform === "win32" ? "Windows" : platform === "linux" ? "Linux" : platform;
|
|
@@ -29632,15 +29914,15 @@ function formatInstallOsLabel(platform = process.platform, arch = process.arch)
|
|
|
29632
29914
|
}
|
|
29633
29915
|
function detectClaudeDesktopMode(claudeSupportDir) {
|
|
29634
29916
|
const markers = [];
|
|
29635
|
-
const legacy =
|
|
29636
|
-
if (
|
|
29917
|
+
const legacy = existsSync3(join3(claudeSupportDir, "claude_desktop_config.json"));
|
|
29918
|
+
if (existsSync3(join3(claudeSupportDir, "Claude Extensions"))) {
|
|
29637
29919
|
markers.push("Claude Extensions/");
|
|
29638
29920
|
}
|
|
29639
|
-
if (
|
|
29921
|
+
if (existsSync3(join3(claudeSupportDir, "extensions-installations.json"))) {
|
|
29640
29922
|
markers.push("extensions-installations.json");
|
|
29641
29923
|
}
|
|
29642
|
-
const cfgPath =
|
|
29643
|
-
if (
|
|
29924
|
+
const cfgPath = join3(claudeSupportDir, "config.json");
|
|
29925
|
+
if (existsSync3(cfgPath)) {
|
|
29644
29926
|
try {
|
|
29645
29927
|
const raw = readFileSync2(cfgPath, "utf8");
|
|
29646
29928
|
const parsed = JSON.parse(raw);
|
|
@@ -29680,8 +29962,8 @@ async function windowsStoreAppInstalled(packageName, appName) {
|
|
|
29680
29962
|
});
|
|
29681
29963
|
}
|
|
29682
29964
|
function isClaudeStorePackagePresent(localAppData) {
|
|
29683
|
-
const packagesDir =
|
|
29684
|
-
if (!
|
|
29965
|
+
const packagesDir = join3(localAppData, "Packages");
|
|
29966
|
+
if (!existsSync3(packagesDir)) return false;
|
|
29685
29967
|
try {
|
|
29686
29968
|
return readdirSync2(packagesDir).some((name) => /^Claude_/i.test(name));
|
|
29687
29969
|
} catch {
|
|
@@ -29690,7 +29972,7 @@ function isClaudeStorePackagePresent(localAppData) {
|
|
|
29690
29972
|
}
|
|
29691
29973
|
async function isClaudeDesktopInstalled(home) {
|
|
29692
29974
|
if (process.platform === "darwin") {
|
|
29693
|
-
return
|
|
29975
|
+
return existsSync3("/Applications/Claude.app") || existsSync3(home + "/Applications/Claude.app");
|
|
29694
29976
|
}
|
|
29695
29977
|
if (process.platform === "win32") {
|
|
29696
29978
|
const local = process.env.LOCALAPPDATA ?? home + "/AppData/Local";
|
|
@@ -29701,18 +29983,18 @@ async function isClaudeDesktopInstalled(home) {
|
|
|
29701
29983
|
local + "/Claude/Claude.exe",
|
|
29702
29984
|
programFiles ? programFiles + "/Claude/Claude.exe" : null,
|
|
29703
29985
|
programFilesX86 ? programFilesX86 + "/Claude/Claude.exe" : null
|
|
29704
|
-
].some((candidate) => candidate !== null &&
|
|
29986
|
+
].some((candidate) => candidate !== null && existsSync3(candidate));
|
|
29705
29987
|
if (exeInstalled) return true;
|
|
29706
29988
|
if (isClaudeStorePackagePresent(local)) return true;
|
|
29707
29989
|
return await windowsStoreAppInstalled("AnthropicPBC.Claude", "Claude");
|
|
29708
29990
|
}
|
|
29709
29991
|
const desktopBin = await findOnPath("claude-desktop");
|
|
29710
29992
|
if (desktopBin) return true;
|
|
29711
|
-
return
|
|
29993
|
+
return existsSync3(home + "/.local/share/applications/claude-desktop.desktop") || existsSync3("/usr/share/applications/claude-desktop.desktop") || existsSync3("/opt/Claude/Claude") || existsSync3("/opt/Claude/claude") || existsSync3("/opt/claude/claude");
|
|
29712
29994
|
}
|
|
29713
29995
|
async function isChatGptDesktopInstalled(home) {
|
|
29714
29996
|
if (process.platform === "darwin") {
|
|
29715
|
-
return
|
|
29997
|
+
return existsSync3("/Applications/ChatGPT.app") || existsSync3(home + "/Applications/ChatGPT.app");
|
|
29716
29998
|
}
|
|
29717
29999
|
if (process.platform === "win32") {
|
|
29718
30000
|
const local = process.env.LOCALAPPDATA ?? home + "/AppData/Local";
|
|
@@ -29725,7 +30007,7 @@ async function isChatGptDesktopInstalled(home) {
|
|
|
29725
30007
|
programFiles ? programFiles + "/ChatGPT/ChatGPT.exe" : null,
|
|
29726
30008
|
programFilesX86 ? programFilesX86 + "/OpenAI/ChatGPT/ChatGPT.exe" : null,
|
|
29727
30009
|
programFilesX86 ? programFilesX86 + "/ChatGPT/ChatGPT.exe" : null
|
|
29728
|
-
].some((candidate) => candidate !== null &&
|
|
30010
|
+
].some((candidate) => candidate !== null && existsSync3(candidate));
|
|
29729
30011
|
return exeInstalled || await windowsStoreAppInstalled("OpenAI.ChatGPT-Desktop", "ChatGPT");
|
|
29730
30012
|
}
|
|
29731
30013
|
return false;
|
|
@@ -29733,16 +30015,16 @@ async function isChatGptDesktopInstalled(home) {
|
|
|
29733
30015
|
async function isCursorInstalled(home) {
|
|
29734
30016
|
const cursorBin = await findOnPath("cursor");
|
|
29735
30017
|
if (cursorBin) return true;
|
|
29736
|
-
if (process.platform === "darwin") return
|
|
30018
|
+
if (process.platform === "darwin") return existsSync3("/Applications/Cursor.app");
|
|
29737
30019
|
if (process.platform === "win32") {
|
|
29738
30020
|
const local = process.env.LOCALAPPDATA ?? `${home}\\AppData\\Local`;
|
|
29739
|
-
return
|
|
30021
|
+
return existsSync3(`${local}\\Programs\\Cursor\\Cursor.exe`);
|
|
29740
30022
|
}
|
|
29741
|
-
return
|
|
30023
|
+
return existsSync3("/usr/share/applications/cursor.desktop") || existsSync3("/opt/Cursor/cursor");
|
|
29742
30024
|
}
|
|
29743
30025
|
async function detectClients() {
|
|
29744
30026
|
const out = [];
|
|
29745
|
-
const home =
|
|
30027
|
+
const home = homedir2();
|
|
29746
30028
|
const claudeBin = await findOnPath("claude");
|
|
29747
30029
|
if (claudeBin) {
|
|
29748
30030
|
out.push({ id: "claude-code", label: "Claude Code", detail: `${claudeBin} mcp add ...` });
|
|
@@ -29761,7 +30043,7 @@ async function detectClients() {
|
|
|
29761
30043
|
out.push({
|
|
29762
30044
|
id: "cursor",
|
|
29763
30045
|
label: "Cursor",
|
|
29764
|
-
detail:
|
|
30046
|
+
detail: existsSync3(cursorPath) ? cursorPath : `${cursorPath} (will be created)`,
|
|
29765
30047
|
configPath: cursorPath
|
|
29766
30048
|
});
|
|
29767
30049
|
}
|
|
@@ -29850,11 +30132,11 @@ async function uninstallFromClaudeCode() {
|
|
|
29850
30132
|
// installer/install-json-config.ts
|
|
29851
30133
|
async function installInJsonConfig(configPath, token, region, includeWrite, telemetryEnabled, localBinPath) {
|
|
29852
30134
|
try {
|
|
29853
|
-
const { readFileSync: readFileSync3, writeFileSync, existsSync:
|
|
30135
|
+
const { readFileSync: readFileSync3, writeFileSync, existsSync: existsSync5, mkdirSync, statSync } = await import("fs");
|
|
29854
30136
|
const { dirname: dirname2 } = await import("path");
|
|
29855
30137
|
let parsed = {};
|
|
29856
30138
|
let preserved = {};
|
|
29857
|
-
const existed =
|
|
30139
|
+
const existed = existsSync5(configPath);
|
|
29858
30140
|
if (existed) {
|
|
29859
30141
|
const raw = readFileSync3(configPath, "utf8");
|
|
29860
30142
|
try {
|
|
@@ -29904,8 +30186,8 @@ function stripJsonMcpEntry(existing) {
|
|
|
29904
30186
|
}
|
|
29905
30187
|
async function uninstallFromJsonConfig(configPath) {
|
|
29906
30188
|
try {
|
|
29907
|
-
const { existsSync:
|
|
29908
|
-
if (!
|
|
30189
|
+
const { existsSync: existsSync5, readFileSync: readFileSync3, writeFileSync } = await import("fs");
|
|
30190
|
+
if (!existsSync5(configPath)) return { ok: true, message: "config not found \u2014 nothing to do" };
|
|
29909
30191
|
const existing = readFileSync3(configPath, "utf8");
|
|
29910
30192
|
const { content, changed } = stripJsonMcpEntry(existing);
|
|
29911
30193
|
if (!changed) return { ok: true, message: "leadbay entry not present" };
|
|
@@ -29988,10 +30270,10 @@ function stripShellExportBlock(existing) {
|
|
|
29988
30270
|
}
|
|
29989
30271
|
async function installInCodexConfig(configPath, includeWrite, telemetryEnabled, localBinPath) {
|
|
29990
30272
|
try {
|
|
29991
|
-
const { readFileSync: readFileSync3, writeFileSync, existsSync:
|
|
30273
|
+
const { readFileSync: readFileSync3, writeFileSync, existsSync: existsSync5, mkdirSync, statSync, renameSync, chmodSync } = await import("fs");
|
|
29992
30274
|
const { dirname: dirname2 } = await import("path");
|
|
29993
30275
|
let existing = "";
|
|
29994
|
-
const existed =
|
|
30276
|
+
const existed = existsSync5(configPath);
|
|
29995
30277
|
if (existed) {
|
|
29996
30278
|
existing = readFileSync3(configPath, "utf8");
|
|
29997
30279
|
} else {
|
|
@@ -30037,15 +30319,15 @@ async function appendShellExports(token, region, includeWrite, telemetryEnabled)
|
|
|
30037
30319
|
}
|
|
30038
30320
|
return { ok: true, message: "env exported with setx; restart Codex/terminal" };
|
|
30039
30321
|
}
|
|
30040
|
-
const { existsSync:
|
|
30322
|
+
const { existsSync: existsSync5, readFileSync: readFileSync3, writeFileSync, renameSync } = await import("fs");
|
|
30041
30323
|
const os = await import("os");
|
|
30042
30324
|
const home = os.homedir();
|
|
30043
|
-
const preferred = [`${home}/.zshrc`, `${home}/.bashrc`].filter((path) =>
|
|
30325
|
+
const preferred = [`${home}/.zshrc`, `${home}/.bashrc`].filter((path) => existsSync5(path));
|
|
30044
30326
|
const paths = preferred.length ? preferred : [`${home}/.profile`];
|
|
30045
30327
|
const block = buildShellExportBlock(token, region, includeWrite, telemetryEnabled);
|
|
30046
30328
|
const updated = [];
|
|
30047
30329
|
for (const path of paths) {
|
|
30048
|
-
const existing =
|
|
30330
|
+
const existing = existsSync5(path) ? readFileSync3(path, "utf8") : "";
|
|
30049
30331
|
const merged = mergeShellExportBlock(existing, block);
|
|
30050
30332
|
if (!merged.changed) continue;
|
|
30051
30333
|
const tmp = `${path}.leadbay.tmp`;
|
|
@@ -30063,8 +30345,8 @@ async function appendShellExports(token, region, includeWrite, telemetryEnabled)
|
|
|
30063
30345
|
}
|
|
30064
30346
|
async function uninstallFromCodexConfig(configPath) {
|
|
30065
30347
|
try {
|
|
30066
|
-
const { existsSync:
|
|
30067
|
-
if (!
|
|
30348
|
+
const { existsSync: existsSync5, readFileSync: readFileSync3, writeFileSync } = await import("fs");
|
|
30349
|
+
if (!existsSync5(configPath)) return { ok: true, message: "config not found \u2014 nothing to do" };
|
|
30068
30350
|
const existing = readFileSync3(configPath, "utf8");
|
|
30069
30351
|
const { content, changed } = stripCodexBlock(existing);
|
|
30070
30352
|
if (!changed) return { ok: true, message: "leadbay block not present" };
|
|
@@ -30076,11 +30358,11 @@ async function uninstallFromCodexConfig(configPath) {
|
|
|
30076
30358
|
}
|
|
30077
30359
|
async function uninstallShellExports() {
|
|
30078
30360
|
try {
|
|
30079
|
-
const { existsSync:
|
|
30361
|
+
const { existsSync: existsSync5, readFileSync: readFileSync3, writeFileSync, renameSync } = await import("fs");
|
|
30080
30362
|
const os = await import("os");
|
|
30081
30363
|
const home = os.homedir();
|
|
30082
30364
|
const candidates = [`${home}/.zshrc`, `${home}/.bashrc`, `${home}/.profile`].filter(
|
|
30083
|
-
(p) =>
|
|
30365
|
+
(p) => existsSync5(p)
|
|
30084
30366
|
);
|
|
30085
30367
|
const updated = [];
|
|
30086
30368
|
for (const p of candidates) {
|
|
@@ -30105,17 +30387,17 @@ async function uninstallShellExports() {
|
|
|
30105
30387
|
var DXT_EXTENSION_ID = "local.dxt.leadbay.leadbay";
|
|
30106
30388
|
async function removeDxtExtension(claudeSupportDir) {
|
|
30107
30389
|
try {
|
|
30108
|
-
const { existsSync:
|
|
30109
|
-
const { join:
|
|
30110
|
-
const extensionDir =
|
|
30111
|
-
const registryPath =
|
|
30390
|
+
const { existsSync: existsSync5, readFileSync: readFileSync3, writeFileSync, rmSync } = await import("fs");
|
|
30391
|
+
const { join: join4 } = await import("path");
|
|
30392
|
+
const extensionDir = join4(claudeSupportDir, "Claude Extensions", DXT_EXTENSION_ID);
|
|
30393
|
+
const registryPath = join4(claudeSupportDir, "extensions-installations.json");
|
|
30112
30394
|
let removedDir = false;
|
|
30113
30395
|
let removedEntry = false;
|
|
30114
|
-
if (
|
|
30396
|
+
if (existsSync5(extensionDir)) {
|
|
30115
30397
|
rmSync(extensionDir, { recursive: true, force: true });
|
|
30116
30398
|
removedDir = true;
|
|
30117
30399
|
}
|
|
30118
|
-
if (
|
|
30400
|
+
if (existsSync5(registryPath)) {
|
|
30119
30401
|
try {
|
|
30120
30402
|
const raw = readFileSync3(registryPath, "utf8");
|
|
30121
30403
|
const parsed = JSON.parse(raw);
|
|
@@ -30142,8 +30424,8 @@ async function removeDxtExtension(claudeSupportDir) {
|
|
|
30142
30424
|
}
|
|
30143
30425
|
|
|
30144
30426
|
// installer/install-wizard.ts
|
|
30145
|
-
function ansi(
|
|
30146
|
-
return enabled ? `\x1B[${code}m${
|
|
30427
|
+
function ansi(text3, code, enabled) {
|
|
30428
|
+
return enabled ? `\x1B[${code}m${text3}\x1B[0m` : text3;
|
|
30147
30429
|
}
|
|
30148
30430
|
function parseInstallSelection(input, clientCount) {
|
|
30149
30431
|
const normalized = input.trim().toLowerCase();
|
|
@@ -30323,7 +30605,7 @@ import {
|
|
|
30323
30605
|
} from "fs/promises";
|
|
30324
30606
|
import { constants as fsConstants } from "fs";
|
|
30325
30607
|
import { dirname, resolve as resolvePath } from "path";
|
|
30326
|
-
import { homedir as
|
|
30608
|
+
import { homedir as homedir3 } from "os";
|
|
30327
30609
|
function emptyState() {
|
|
30328
30610
|
return {
|
|
30329
30611
|
last_check_time: 0,
|
|
@@ -30359,7 +30641,7 @@ var UpdateStateStore = class {
|
|
|
30359
30641
|
}
|
|
30360
30642
|
validatePath(p) {
|
|
30361
30643
|
if (this.allowUnsafePath) return;
|
|
30362
|
-
const home = resolvePath(
|
|
30644
|
+
const home = resolvePath(homedir3());
|
|
30363
30645
|
if (p !== home && !p.startsWith(home + "/") && !p.startsWith(home + "\\")) {
|
|
30364
30646
|
throw new Error(
|
|
30365
30647
|
`UpdateStateStore: path ${p} is outside $HOME (${home}). Set LEADBAY_UPDATE_STATE_PATH_UNSAFE=1 to override.`
|
|
@@ -30487,7 +30769,7 @@ async function openTmpFileExclusive(path) {
|
|
|
30487
30769
|
async function createDefaultUpdateStateStore(opts = {}) {
|
|
30488
30770
|
const env = opts.env ?? process.env;
|
|
30489
30771
|
const allowUnsafePath = env.LEADBAY_UPDATE_STATE_PATH_UNSAFE === "1";
|
|
30490
|
-
const path = env.LEADBAY_UPDATE_STATE_PATH ?? resolvePath(
|
|
30772
|
+
const path = env.LEADBAY_UPDATE_STATE_PATH ?? resolvePath(homedir3(), ".leadbay", "update-state.json");
|
|
30491
30773
|
try {
|
|
30492
30774
|
const store = new UpdateStateStore({
|
|
30493
30775
|
backend: "file",
|
|
@@ -30511,7 +30793,7 @@ import { createHash as createHash5, randomBytes } from "crypto";
|
|
|
30511
30793
|
import { createServer } from "http";
|
|
30512
30794
|
import { request as httpsRequestRaw } from "https";
|
|
30513
30795
|
import { spawn as spawn3 } from "child_process";
|
|
30514
|
-
import { readdirSync as readdirSync3, existsSync as
|
|
30796
|
+
import { readdirSync as readdirSync3, existsSync as existsSync4 } from "fs";
|
|
30515
30797
|
var LEADBAY_LOOPBACK_PORTS = [51789, 51790, 51791, 51792];
|
|
30516
30798
|
var BrowserOpenFailedError = class extends Error {
|
|
30517
30799
|
authorizeUrl;
|
|
@@ -30844,11 +31126,11 @@ function browserLaunchEnv(debug) {
|
|
|
30844
31126
|
const env = { ...process.env };
|
|
30845
31127
|
if (process.platform !== "linux") return env;
|
|
30846
31128
|
let runtimeDir = env.XDG_RUNTIME_DIR;
|
|
30847
|
-
if (!runtimeDir || !
|
|
31129
|
+
if (!runtimeDir || !existsSync4(runtimeDir)) {
|
|
30848
31130
|
try {
|
|
30849
31131
|
const uid = process.getuid?.();
|
|
30850
31132
|
const candidate = uid !== void 0 ? `/run/user/${uid}` : void 0;
|
|
30851
|
-
if (candidate &&
|
|
31133
|
+
if (candidate && existsSync4(candidate)) {
|
|
30852
31134
|
runtimeDir = candidate;
|
|
30853
31135
|
env.XDG_RUNTIME_DIR = candidate;
|
|
30854
31136
|
debug?.(`browserLaunchEnv: injected XDG_RUNTIME_DIR=${candidate}`);
|
|
@@ -30868,7 +31150,7 @@ function browserLaunchEnv(debug) {
|
|
|
30868
31150
|
}
|
|
30869
31151
|
if (!env.DBUS_SESSION_BUS_ADDRESS && runtimeDir) {
|
|
30870
31152
|
const busPath = `${runtimeDir}/bus`;
|
|
30871
|
-
if (
|
|
31153
|
+
if (existsSync4(busPath)) {
|
|
30872
31154
|
env.DBUS_SESSION_BUS_ADDRESS = `unix:path=${busPath}`;
|
|
30873
31155
|
debug?.(`browserLaunchEnv: injected DBUS_SESSION_BUS_ADDRESS=unix:path=${busPath}`);
|
|
30874
31156
|
}
|
|
@@ -30891,7 +31173,7 @@ function browserLaunchEnv(debug) {
|
|
|
30891
31173
|
);
|
|
30892
31174
|
if (cookie) xauth = `${runtimeDir}/${cookie}`;
|
|
30893
31175
|
}
|
|
30894
|
-
if (!xauth && env.HOME &&
|
|
31176
|
+
if (!xauth && env.HOME && existsSync4(`${env.HOME}/.Xauthority`)) {
|
|
30895
31177
|
xauth = `${env.HOME}/.Xauthority`;
|
|
30896
31178
|
}
|
|
30897
31179
|
if (xauth) {
|
|
@@ -31054,7 +31336,7 @@ var OAUTH_BASE_URLS = {
|
|
|
31054
31336
|
fr: "https://staging.api.leadbay.app"
|
|
31055
31337
|
}
|
|
31056
31338
|
};
|
|
31057
|
-
var VERSION = "0.
|
|
31339
|
+
var VERSION = "0.39.1";
|
|
31058
31340
|
var HELP = `
|
|
31059
31341
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
31060
31342
|
|
|
@@ -31154,9 +31436,9 @@ function exitWithTokenError() {
|
|
|
31154
31436
|
function hydrateEnvFromCredentialsFile() {
|
|
31155
31437
|
if (process.env.LEADBAY_TOKEN) return false;
|
|
31156
31438
|
try {
|
|
31157
|
-
const { existsSync:
|
|
31439
|
+
const { existsSync: existsSync5, readFileSync: readFileSync3 } = require_("node:fs");
|
|
31158
31440
|
const { path } = resolveOAuthBootstrapCredentialsPath();
|
|
31159
|
-
if (!
|
|
31441
|
+
if (!existsSync5(path)) return false;
|
|
31160
31442
|
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
31161
31443
|
const env = parsed?.mcpServers?.leadbay?.env;
|
|
31162
31444
|
if (!env || typeof env !== "object") return false;
|
|
@@ -31177,9 +31459,9 @@ function hydrateEnvFromCredentialsFile() {
|
|
|
31177
31459
|
function resolveOAuthBootstrapCredentialsPath() {
|
|
31178
31460
|
const resolved = resolveDefaultCredentialsPath();
|
|
31179
31461
|
if (process.env.LEADBAY_OAUTH_STAGING !== "1") return resolved;
|
|
31180
|
-
const { dirname: dirname2, join:
|
|
31462
|
+
const { dirname: dirname2, join: join4 } = require_("node:path");
|
|
31181
31463
|
return {
|
|
31182
|
-
path:
|
|
31464
|
+
path: join4(dirname2(resolved.path), "credentials.staging.json"),
|
|
31183
31465
|
legacy: resolved.legacy
|
|
31184
31466
|
};
|
|
31185
31467
|
}
|
|
@@ -31189,20 +31471,20 @@ var bootstrapFailureMessage;
|
|
|
31189
31471
|
function bootstrapDebug(msg) {
|
|
31190
31472
|
try {
|
|
31191
31473
|
const { appendFileSync, mkdirSync } = require_("node:fs");
|
|
31192
|
-
const { join:
|
|
31193
|
-
const { homedir:
|
|
31194
|
-
const dir =
|
|
31474
|
+
const { join: join4 } = require_("node:path");
|
|
31475
|
+
const { homedir: homedir4 } = require_("node:os");
|
|
31476
|
+
const dir = join4(homedir4(), ".leadbay");
|
|
31195
31477
|
mkdirSync(dir, { recursive: true });
|
|
31196
31478
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
31197
|
-
appendFileSync(
|
|
31479
|
+
appendFileSync(join4(dir, "oauth-bootstrap-debug.log"), `${ts} [pid ${process.pid}] ${msg}
|
|
31198
31480
|
`);
|
|
31199
31481
|
} catch {
|
|
31200
31482
|
}
|
|
31201
31483
|
}
|
|
31202
31484
|
function oauthClientCachePath() {
|
|
31203
|
-
const { join:
|
|
31204
|
-
const { homedir:
|
|
31205
|
-
return
|
|
31485
|
+
const { join: join4 } = require_("node:path");
|
|
31486
|
+
const { homedir: homedir4 } = require_("node:os");
|
|
31487
|
+
return join4(homedir4(), ".leadbay", "oauth-client.json");
|
|
31206
31488
|
}
|
|
31207
31489
|
function getCachedOAuthClientId(authServerBaseUrl, port) {
|
|
31208
31490
|
try {
|
|
@@ -31529,8 +31811,8 @@ function parseFlag(args, name) {
|
|
|
31529
31811
|
function hasFlag(args, name) {
|
|
31530
31812
|
return args.some((a) => a === `--${name}`);
|
|
31531
31813
|
}
|
|
31532
|
-
function ansi2(
|
|
31533
|
-
return enabled ? `\x1B[${code}m${
|
|
31814
|
+
function ansi2(text3, code, enabled) {
|
|
31815
|
+
return enabled ? `\x1B[${code}m${text3}\x1B[0m` : text3;
|
|
31534
31816
|
}
|
|
31535
31817
|
function isInteractiveInstall() {
|
|
31536
31818
|
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
@@ -31750,8 +32032,8 @@ Restart your MCP client to pick up the new server.
|
|
|
31750
32032
|
usingLegacyPath = resolved.legacy;
|
|
31751
32033
|
}
|
|
31752
32034
|
try {
|
|
31753
|
-
const { existsSync:
|
|
31754
|
-
if (
|
|
32035
|
+
const { existsSync: existsSync5, readFileSync: readFileSync3 } = await import("fs");
|
|
32036
|
+
if (existsSync5(targetPath) && !force) {
|
|
31755
32037
|
let existing;
|
|
31756
32038
|
try {
|
|
31757
32039
|
existing = JSON.parse(readFileSync3(targetPath, "utf8"));
|
|
@@ -32279,6 +32561,9 @@ async function main() {
|
|
|
32279
32561
|
version: VERSION,
|
|
32280
32562
|
telemetry,
|
|
32281
32563
|
updateStateStore,
|
|
32564
|
+
// This process runs on the user's machine, so a tool can save a file
|
|
32565
|
+
// they will find. The hosted server (http-server.ts) does not pass this.
|
|
32566
|
+
saveFile: saveToDownloads,
|
|
32282
32567
|
// Non-blocking OAuth bootstrap gate. Read per tool call: once the
|
|
32283
32568
|
// background flow lands the token (client.isAuthenticated → true) this
|
|
32284
32569
|
// reports done and tools execute. While waiting it surfaces the live
|
|
@@ -32361,7 +32646,7 @@ var isEntrypoint = (() => {
|
|
|
32361
32646
|
try {
|
|
32362
32647
|
const entry = process.argv[1];
|
|
32363
32648
|
if (!entry) return false;
|
|
32364
|
-
const entryName =
|
|
32649
|
+
const entryName = basename2(entry).toLowerCase();
|
|
32365
32650
|
if (entryName !== "bin.js" && entryName !== "leadbay-mcp" && entryName !== "index.js") return false;
|
|
32366
32651
|
const self = fileURLToPath(import.meta.url);
|
|
32367
32652
|
return realpathSync(self) === realpathSync(entry);
|