@coldiq/mcp 0.4.3 → 0.4.5

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.
Files changed (48) hide show
  1. package/dist/index.js +2 -0
  2. package/dist/index.js.map +1 -1
  3. package/dist/registry.d.ts.map +1 -1
  4. package/dist/registry.js +26 -102
  5. package/dist/registry.js.map +1 -1
  6. package/dist/tools/cancel-bulk-job.d.ts +14 -0
  7. package/dist/tools/cancel-bulk-job.d.ts.map +1 -0
  8. package/dist/tools/cancel-bulk-job.js +18 -0
  9. package/dist/tools/cancel-bulk-job.js.map +1 -0
  10. package/dist/tools/enrich-company-bulk.d.ts.map +1 -1
  11. package/dist/tools/enrich-company-bulk.js +2 -1
  12. package/dist/tools/enrich-company-bulk.js.map +1 -1
  13. package/dist/tools/enrich-person-bulk.d.ts.map +1 -1
  14. package/dist/tools/enrich-person-bulk.js +2 -1
  15. package/dist/tools/enrich-person-bulk.js.map +1 -1
  16. package/dist/tools/find-emails-bulk.d.ts.map +1 -1
  17. package/dist/tools/find-emails-bulk.js +2 -1
  18. package/dist/tools/find-emails-bulk.js.map +1 -1
  19. package/dist/tools/find-phone-bulk.d.ts.map +1 -1
  20. package/dist/tools/find-phone-bulk.js +2 -1
  21. package/dist/tools/find-phone-bulk.js.map +1 -1
  22. package/dist/tools/get-bulk-job.d.ts.map +1 -1
  23. package/dist/tools/get-bulk-job.js +3 -2
  24. package/dist/tools/get-bulk-job.js.map +1 -1
  25. package/dist/utils/provider-resolver.d.ts.map +1 -1
  26. package/dist/utils/provider-resolver.js +4 -10
  27. package/dist/utils/provider-resolver.js.map +1 -1
  28. package/dist/utils/selection-insight.d.ts.map +1 -1
  29. package/dist/utils/selection-insight.js +6 -8
  30. package/dist/utils/selection-insight.js.map +1 -1
  31. package/package.json +1 -1
  32. package/src/index.ts +2 -0
  33. package/src/registry.ts +27 -98
  34. package/src/tools/cancel-bulk-job.ts +22 -0
  35. package/src/tools/enrich-company-bulk.ts +2 -1
  36. package/src/tools/enrich-person-bulk.ts +2 -1
  37. package/src/tools/find-emails-bulk.ts +2 -1
  38. package/src/tools/find-phone-bulk.ts +2 -1
  39. package/src/tools/get-bulk-job.ts +3 -2
  40. package/src/utils/provider-resolver.ts +4 -10
  41. package/src/utils/selection-insight.ts +6 -8
  42. package/tests/bulk-tools.test.ts +17 -0
  43. package/tests/provider-availability.test.ts +4 -4
  44. package/tests/registry-polling.test.ts +4 -4
  45. package/tests/registry.test.ts +32 -81
  46. package/tests/tools/list-data-sources.test.ts +1 -1
  47. package/tests/utils/provider-resolver.test.ts +7 -7
  48. package/tests/utils/selection-insight.test.ts +2 -2
@@ -7,7 +7,8 @@ export const enrichCompanyBulkDescription =
7
7
  'Enrich up to 5,000 companies (size, industry, funding, technologies, social profiles) in one ASYNC job. Returns a job_id immediately — this does NOT return firmographics inline. ' +
8
8
  'Poll get_bulk_job(job_id) until status is "done" (drains over a few minutes), then page through rows. ' +
9
9
  'Each row runs the same managed waterfall as enrich_company; provide at least one of domain, linkedin_url, or name per company. ' +
10
- 'Charged only per row a provider enriches (misses are free). For a single company, use enrich_company instead.'
10
+ 'Charged only per row a provider enriches (misses are free). For a single company, use enrich_company instead. ' +
11
+ 'Call cancel_bulk_job to stop a running job early — already-processed rows stay charged, un-started rows are never billed.'
11
12
 
12
13
  export const enrichCompanyBulkSchema = {
13
14
  inputs: z
@@ -7,7 +7,8 @@ export const enrichPersonBulkDescription =
7
7
  'Enrich up to 5,000 people (LinkedIn profile + professional details) in one ASYNC job. Returns a job_id immediately — this does NOT return profiles inline. ' +
8
8
  'Poll get_bulk_job(job_id) until status is "done" (drains over a few minutes), then page through rows. ' +
9
9
  'Each row runs the same managed waterfall as enrich_person; provide an email, a linkedin_url, or name + company/domain per person. ' +
10
- 'Charged only per row a provider enriches (misses are free). For a single person, use enrich_person instead.'
10
+ 'Charged only per row a provider enriches (misses are free). For a single person, use enrich_person instead. ' +
11
+ 'Call cancel_bulk_job to stop a running job early — already-processed rows stay charged, un-started rows are never billed.'
11
12
 
12
13
  export const enrichPersonBulkSchema = {
13
14
  inputs: z
@@ -13,7 +13,8 @@ export const findEmailsBulkDescription =
13
13
  'result carries a `confidence` (high/medium/unknown) — treat medium/unknown as unverified and run ' +
14
14
  'verify_emails_bulk before sending, or you risk bounces. A located email the provider itself flags as a low-confidence ' +
15
15
  'pattern guess comes back as verdict `guessed` instead of `found` and is FREE — never send a `guessed` email unverified. ' +
16
- 'For 50 or fewer people that you want back inline, use find_emails instead.'
16
+ 'For 50 or fewer people that you want back inline, use find_emails instead. ' +
17
+ 'Started a job that is too big or wrong? Call cancel_bulk_job to stop it — already-processed rows stay charged, un-started rows are never billed.'
17
18
 
18
19
  export const findEmailsBulkSchema = {
19
20
  people: z
@@ -7,7 +7,8 @@ export const findPhoneBulkDescription =
7
7
  'Find direct/mobile phone numbers for up to 5,000 people in one ASYNC job. Returns a job_id immediately — this does NOT return numbers inline. ' +
8
8
  'Poll get_bulk_job(job_id) until status is "done" (drains over a few minutes), then page through rows. ' +
9
9
  'Each row runs the same managed waterfall as find_phone; provide a linkedin_url, or first+last name with company_domain/company_name. ' +
10
- 'Charged only per row a provider returns a number (misses are free). Phone lookups are relatively expensive — scope the list deliberately. For a single person, use find_phone instead.'
10
+ 'Charged only per row a provider returns a number (misses are free). Phone lookups are relatively expensive — scope the list deliberately. For a single person, use find_phone instead. ' +
11
+ 'Overshot the list? Call cancel_bulk_job to stop it immediately — already-processed rows stay charged, un-started rows are never billed.'
11
12
 
12
13
  export const findPhoneBulkSchema = {
13
14
  inputs: z
@@ -5,14 +5,15 @@ export const getBulkJobName = 'get_bulk_job'
5
5
 
6
6
  export const getBulkJobDescription =
7
7
  'Poll any async bulk job (verify_emails_bulk, find_emails_bulk, enrich_person_bulk, enrich_company_bulk, find_phone_bulk). ' +
8
- 'Returns { status, items_total, items_processed, summary, coverage, credits_reserved, credits_charged, page }. Poll until status is "done" (or "timed_out"). ' +
8
+ 'Returns { status, items_total, items_processed, summary, coverage, credits_reserved, credits_charged, page }. Poll until status is TERMINAL — one of "done", "timed_out", "failed", or "cancelled" (a job someone stopped with cancel_bulk_job); stop polling on any of these. ' +
9
9
  'Use the `summary` per-verdict counts for totals — do NOT expect all rows inline: rows come one page at a time (default 100). ' +
10
10
  'For find jobs, verdict `guessed` means an email was located but the provider marked it a low-confidence pattern guess — it is NOT billed; verify it before sending. ' +
11
11
  'The `coverage` block {conclusive, skipped, unverifiable, failed, pending} tells you how much was truly resolved: `skipped` rows (per-row ' +
12
12
  '`result.reason` chunk_failed/not_returned/timed_out) were never conclusively checked and are safe to re-submit; `failed` is a hard error (retry unlikely to help). ' +
13
13
  '`credits_charged` (null until settled) is the actual spend; `credits_reserved` is the worst-case hold. Each row echoes its submitted `input`, ' +
14
14
  'so you can map verdicts back to your submissions without tracking order. To page, OMIT cursor for the first page, then call again with ' +
15
- 'cursor = page.next_cursor until next_cursor is null.'
15
+ 'cursor = page.next_cursor until next_cursor is null. ' +
16
+ 'To STOP a running find/enrich/phone job early (before it spends more credits), call cancel_bulk_job — already-processed rows stay charged, un-started rows are never billed.'
16
17
 
17
18
  export const getBulkJobSchema = {
18
19
  job_id: z.number().int().describe('The job_id returned by a *_bulk submit tool.'),
@@ -8,16 +8,12 @@ import { fuzzyMatch } from './fuzzy.js'
8
8
  // Order = auto-route execution order: bulk providers first (Steps 1-3), then
9
9
  // the single-find_email fallback providers used for stragglers (Step 4).
10
10
  export const CONFIGURED_FIND_EMAILS_PROVIDERS = [
11
- 'limadata-work-email',
11
+ 'findymail',
12
12
  'leadmagic',
13
13
  'prospeo',
14
- 'blitzapi',
15
- 'limadata-work-email-linkedin',
14
+ 'wiza',
16
15
  'apollo-people-match',
17
- 'findymail',
18
- 'fullenrich',
19
- 'pdl-person-enrich',
20
- 'linkupapi',
16
+ 'limadata-work-email',
21
17
  'icypeas',
22
18
  ]
23
19
 
@@ -223,10 +219,8 @@ const GATED_DESCRIPTIONS: Partial<Record<Capability, Partial<Record<string, Gate
223
219
  find_email: {
224
220
  findymail: { kind: 'requires', fields: 'first_name and (domain or company_name)' },
225
221
  'limadata-work-email': { kind: 'requires', fields: 'first_name, last_name, and domain' },
226
- blitzapi: { kind: 'requires', fields: 'linkedin_url' },
227
- 'limadata-work-email-linkedin': { kind: 'requires', fields: 'linkedin_url' },
222
+ wiza: { kind: 'requires', fields: 'linkedin_url or (first_name, last_name, and domain/company_name)' },
228
223
  'apollo-people-match': { kind: 'requires', fields: 'linkedin_url or (first_name, last_name, and domain/company_name)' },
229
- 'pdl-person-enrich': { kind: 'requires', fields: 'linkedin_url or (first_name, last_name, and domain/company_name)' },
230
224
  },
231
225
  // -------------------------------------------------------------------------
232
226
  verify_email: {},
@@ -73,23 +73,21 @@ const STRENGTHS: Partial<Record<InsightCapability, Record<string, string>>> = {
73
73
  },
74
74
  find_email: {
75
75
  prospeo: 'strong work-email coverage',
76
- fullenrich: 'strong international email coverage, especially across Europe',
77
76
  findymail: 'high-accuracy verified work emails',
77
+ wiza: 'live SMTP-verified email reveals (premium)',
78
78
  icypeas: 'broad email-finding coverage',
79
79
  'limadata-work-email': 'name-and-domain work-email lookup',
80
- blitzapi: 'LinkedIn-URL–based email finding',
81
- 'limadata-work-email-linkedin': 'LinkedIn-URL–based work-email lookup',
82
- linkupapi: 'LinkedIn-sourced email finding',
80
+ leadmagic: 'pay-per-result email finding with employment verification',
81
+ 'apollo-people-match': 'person-matched current-employer emails (recovers job movers)',
83
82
  },
84
83
  find_emails: {
85
84
  prospeo: 'strong work-email coverage',
86
- fullenrich: 'strong international email coverage, especially across Europe',
87
85
  findymail: 'high-accuracy verified work emails',
86
+ wiza: 'live SMTP-verified email reveals (premium)',
88
87
  icypeas: 'broad email-finding coverage',
89
88
  'limadata-work-email': 'name-and-domain work-email lookup',
90
- blitzapi: 'LinkedIn-URL–based email finding',
91
- 'limadata-work-email-linkedin': 'LinkedIn-URL–based work-email lookup',
92
- linkupapi: 'LinkedIn-sourced email finding',
89
+ leadmagic: 'pay-per-result email finding with employment verification',
90
+ 'apollo-people-match': 'person-matched current-employer emails (recovers job movers)',
93
91
  },
94
92
  verify_email: {
95
93
  findymail: 'reliable deliverability verification',
@@ -8,6 +8,7 @@ vi.mock('../src/client.js', () => ({
8
8
  import { verifyEmailsBulkHandler } from '../src/tools/verify-emails-bulk.js'
9
9
  import { findEmailsBulkHandler } from '../src/tools/find-emails-bulk.js'
10
10
  import { getBulkJobHandler } from '../src/tools/get-bulk-job.js'
11
+ import { cancelBulkJobHandler } from '../src/tools/cancel-bulk-job.js'
11
12
 
12
13
  beforeEach(() => {
13
14
  callApiMock.mockReset()
@@ -45,4 +46,20 @@ describe('bulk MCP tools', () => {
45
46
  const res = await verifyEmailsBulkHandler({ emails: ['a@x.com'] })
46
47
  expect(res.isError).toBe(true)
47
48
  })
49
+
50
+ it('cancel_bulk_job POSTs job_id in the path (empty body) to the cancel endpoint', async () => {
51
+ callApiMock.mockResolvedValue({ ok: true, status: 200, data: { job_id: 42, status: 'cancelled', items_total: 50, items_processed: 12 }, headers: {} })
52
+ const res = await cancelBulkJobHandler({ job_id: 42 })
53
+ const [method, path, body] = callApiMock.mock.calls[0]
54
+ expect(method).toBe('POST')
55
+ expect(path).toBe('/jobs/42/cancel')
56
+ expect(body).toEqual({})
57
+ expect(JSON.parse(res.content[0].text)).toMatchObject({ status: 'cancelled' })
58
+ })
59
+
60
+ it('cancel_bulk_job surfaces isError on a 400 (not cancellable)', async () => {
61
+ callApiMock.mockResolvedValue({ ok: false, status: 400, data: { error: "can't be cancelled" }, headers: {} })
62
+ const res = await cancelBulkJobHandler({ job_id: 42 })
63
+ expect(res.isError).toBe(true)
64
+ })
48
65
  })
@@ -8,7 +8,6 @@ import { findEmailsBulkSchema } from '../src/tools/find-emails-bulk.js'
8
8
  import { getEndpointDetailsSchema } from '../src/tools/get-endpoint-details.js'
9
9
  import { listDataSourcesHandler } from '../src/tools/list-data-sources.js'
10
10
  import {
11
- CONFIGURED_FIND_EMAILS_PROVIDERS,
12
11
  FIND_EMAILS_PROVIDERS,
13
12
  getProvidersForCapability,
14
13
  resolvePreferredProviders,
@@ -44,14 +43,15 @@ describe('standalone MCP provider availability', () => {
44
43
  })
45
44
 
46
45
  it('keeps dormant definitions testable but removes them from every active waterfall', () => {
47
- expect(getConfiguredProviders('find_email').some((provider) => provider.id === 'pdl-person-enrich')).toBe(true)
48
- expect(CONFIGURED_FIND_EMAILS_PROVIDERS).toContain('pdl-person-enrich')
46
+ // pdl-person-enrich left find_email entirely (2026-07-24 reorder); the dormant
47
+ // PDL definition still lives in find_people for when the account is re-enabled.
48
+ expect(getConfiguredProviders('find_people').some((provider) => provider.id === 'pdl')).toBe(true)
49
49
 
50
50
  for (const capability of CAPABILITIES) {
51
51
  expect(getProviders(capability).every((provider) => isProviderEnabled(provider.id))).toBe(true)
52
52
  }
53
53
  expect(FIND_EMAILS_PROVIDERS.every(isProviderEnabled)).toBe(true)
54
- expect(getProvidersForCapability('find_emails')).not.toContain('pdl-person-enrich')
54
+ expect(getProvidersForCapability('find_people')).not.toContain('pdl')
55
55
  })
56
56
 
57
57
  it('rejects an explicit disabled-provider pin without advertising it as available', () => {
@@ -30,11 +30,11 @@ describe('MCP polling interval schedules', () => {
30
30
  expect(evalPollSchedule(fn, 300_000).length).toBeGreaterThanOrEqual(10)
31
31
  })
32
32
 
33
- it('FullEnrich find-email: 2.5s interval fits inside 60s timeout', () => {
33
+ it('Wiza find-email: 2.5s interval fits inside 60s timeout', () => {
34
34
  const providers = getProviders('find_email')
35
- const fe = providers.find((p) => p.id === 'fullenrich')!
36
- expect(fe?.async).toBeDefined()
37
- expect(fe.async!.pollIntervalMs).toBe(2500)
35
+ const wiza = providers.find((p) => p.id === 'wiza')!
36
+ expect(wiza?.async).toBeDefined()
37
+ expect(wiza.async!.pollIntervalMs).toBe(2500)
38
38
  // timeoutMs = 60_000 → at least 24 polls
39
39
  expect(60_000 / 2500).toBeGreaterThanOrEqual(24)
40
40
  })
@@ -651,59 +651,51 @@ describe('registry', () => {
651
651
  expect(prospeo.hasResult({ email: null })).toBe(false)
652
652
  })
653
653
 
654
- it('FullEnrich maps to batch-of-1 with contact.emails field', () => {
654
+ it('Wiza maps a linkedin_url to a partial (email-only) individual reveal', () => {
655
655
  const providers = getProviders('find_email')
656
- const fe = providers.find((p) => p.id === 'fullenrich')!
657
- const result = fe.mapParams({
658
- first_name: 'Michel',
659
- last_name: 'Lieben',
660
- domain: 'coldiq.com',
661
- linkedin_url: 'https://linkedin.com/in/michel-lieben',
662
- })
656
+ const wiza = providers.find((p) => p.id === 'wiza')!
657
+ const result = wiza.mapParams({ linkedin_url: 'https://linkedin.com/in/michel-lieben' })
663
658
  expect(result.body).toEqual({
664
- name: 'mcp-enrich',
665
- data: [{
666
- first_name: 'Michel',
667
- last_name: 'Lieben',
668
- domain: 'coldiq.com',
669
- linkedin_url: 'https://linkedin.com/in/michel-lieben',
670
- enrich_fields: ['contact.emails'],
671
- }],
659
+ individual_reveal: { profile_url: 'https://linkedin.com/in/michel-lieben' },
660
+ enrichment_level: 'partial',
661
+ email_options: { accept_work: true, accept_personal: false },
672
662
  })
673
663
  })
674
664
 
675
- it('FullEnrich hasResult detects email in data[0].emails array', () => {
665
+ it('Wiza maps name+domain when no linkedin_url is supplied', () => {
676
666
  const providers = getProviders('find_email')
677
- const fe = providers.find((p) => p.id === 'fullenrich')!
678
- expect(fe.hasResult({ status: 'DONE', data: [{ emails: ['michel@coldiq.com'] }] })).toBe(true)
679
- expect(fe.hasResult({ status: 'DONE', data: [{ emails: [] }] })).toBe(false)
680
- expect(fe.hasResult({ status: 'DONE', data: [] })).toBe(false)
681
- expect(fe.hasResult({ status: 'FAILED', data: [] })).toBe(false)
667
+ const wiza = providers.find((p) => p.id === 'wiza')!
668
+ const result = wiza.mapParams({ first_name: 'Michel', last_name: 'Lieben', domain: 'coldiq.com' })
669
+ expect((result.body as Record<string, unknown>).individual_reveal).toEqual({
670
+ full_name: 'Michel Lieben', domain: 'coldiq.com',
671
+ })
682
672
  })
683
673
 
684
- it('FullEnrich has async config with correct poll endpoint', () => {
674
+ it('Wiza hasResult reads the reveal envelope; async polls the reveal id', () => {
685
675
  const providers = getProviders('find_email')
686
- const fe = providers.find((p) => p.id === 'fullenrich')!
687
- expect(fe.async).toBeDefined()
688
- expect(fe.async!.pollEndpoint('abc-123')).toBe('/fullenrich/contact/enrich/bulk/abc-123')
689
- expect(fe.async!.isComplete({ status: 'DONE' })).toBe(true)
690
- expect(fe.async!.isComplete({ status: 'FAILED' })).toBe(true)
691
- expect(fe.async!.isComplete({ status: 'IN_PROGRESS' })).toBe(false)
692
- expect(fe.async!.extractId({ enrichment_id: 'abc-123' })).toBe('abc-123')
693
- })
694
-
695
- it('FullEnrich is priority 8 and LinkupAPI is priority 10 in the server-mirrored chain', () => {
676
+ const wiza = providers.find((p) => p.id === 'wiza')!
677
+ expect(wiza.hasResult({ data: { is_complete: true, email: 'michel@coldiq.com' } })).toBe(true)
678
+ expect(wiza.hasResult({ data: { is_complete: true, email: null } })).toBe(false)
679
+ expect(wiza.async).toBeDefined()
680
+ expect(wiza.async!.extractId({ data: { id: 12345 } })).toBe('12345')
681
+ expect(wiza.async!.pollEndpoint('12345')).toBe('/wiza/individual-reveals/12345')
682
+ expect(wiza.async!.isComplete({ data: { is_complete: true } })).toBe(true)
683
+ expect(wiza.async!.isComplete({ data: { status: 'failed' } })).toBe(true)
684
+ expect(wiza.async!.isComplete({ data: { is_complete: false } })).toBe(false)
685
+ })
686
+
687
+ it('Wiza is priority 4 (premium slot after the verifying trio)', () => {
696
688
  const providers = getProviders('find_email')
697
- const fe = providers.find((p) => p.id === 'fullenrich')!
698
- const lu = providers.find((p) => p.id === 'linkupapi')!
699
- expect(fe.priority).toBe(8)
700
- expect(lu.priority).toBe(10)
689
+ const wiza = providers.find((p) => p.id === 'wiza')!
690
+ expect(wiza.priority).toBe(4)
701
691
  })
702
692
 
703
- it('leads with limadata-work-email, then leadmagic (mirrors the server order)', () => {
693
+ it('mirrors the server order: findymail → leadmagic prospeo wiza → apollo → limadata → icypeas', () => {
704
694
  const ids = getProviders('find_email').map((p) => p.id)
705
- expect(ids[0]).toBe('limadata-work-email')
706
- expect(ids[1]).toBe('leadmagic')
695
+ expect(ids).toEqual([
696
+ 'findymail', 'leadmagic', 'prospeo', 'wiza',
697
+ 'apollo-people-match', 'limadata-work-email', 'icypeas',
698
+ ])
707
699
  })
708
700
 
709
701
  it('Lima Data work-email maps full_name + company_domain', () => {
@@ -727,47 +719,6 @@ describe('registry', () => {
727
719
  expect(ld.hasResult({})).toBe(false)
728
720
  })
729
721
 
730
- it('BlitzAPI maps person_linkedin_url from linkedin_url input', () => {
731
- const providers = getProviders('find_email')
732
- const blitz = providers.find((p) => p.id === 'blitzapi')!
733
- const result = blitz.mapParams({
734
- linkedin_url: 'https://linkedin.com/in/michel-lieben',
735
- })
736
- expect(result.body).toEqual({ person_linkedin_url: 'https://linkedin.com/in/michel-lieben' })
737
- })
738
-
739
- it('BlitzAPI isApplicable requires linkedin_url', () => {
740
- const providers = getProviders('find_email')
741
- const blitz = providers.find((p) => p.id === 'blitzapi')!
742
- expect(blitz.isApplicable!({})).toBe(false)
743
- expect(blitz.isApplicable!({ first_name: 'Michel', domain: 'coldiq.com' })).toBe(false)
744
- expect(blitz.isApplicable!({ linkedin_url: 'https://...' })).toBe(true)
745
- })
746
-
747
- it('BlitzAPI hasResult checks top-level email', () => {
748
- const providers = getProviders('find_email')
749
- const blitz = providers.find((p) => p.id === 'blitzapi')!
750
- expect(blitz.hasResult({ email: 'michel@coldiq.com' })).toBe(true)
751
- expect(blitz.hasResult({ email: null })).toBe(false)
752
- })
753
-
754
- it('Lima Data work-email-linkedin maps linkedin_url passthrough', () => {
755
- const providers = getProviders('find_email')
756
- const ld = providers.find((p) => p.id === 'limadata-work-email-linkedin')!
757
- const result = ld.mapParams({
758
- linkedin_url: 'https://linkedin.com/in/michel-lieben',
759
- })
760
- expect(result.body).toEqual({ linkedin_url: 'https://linkedin.com/in/michel-lieben' })
761
- })
762
-
763
- it('Lima Data work-email-linkedin isApplicable requires linkedin_url', () => {
764
- const providers = getProviders('find_email')
765
- const ld = providers.find((p) => p.id === 'limadata-work-email-linkedin')!
766
- expect(ld.isApplicable!({})).toBe(false)
767
- expect(ld.isApplicable!({ first_name: 'Michel', domain: 'coldiq.com' })).toBe(false)
768
- expect(ld.isApplicable!({ linkedin_url: 'https://...' })).toBe(true)
769
- })
770
-
771
722
  it('Lima Data work-email isApplicable requires first_name + last_name + domain', () => {
772
723
  const providers = getProviders('find_email')
773
724
  const ld = providers.find((p) => p.id === 'limadata-work-email')!
@@ -25,7 +25,7 @@ describe('list_data_sources handler', () => {
25
25
  expect(names).toContain('TheirStack')
26
26
 
27
27
  const emails = rowFor(caps, 'find_email')!
28
- expect(emails.sources.map((s) => s.name)).toContain('FullEnrich')
28
+ expect(emails.sources.map((s) => s.name)).toContain('Wiza')
29
29
  })
30
30
 
31
31
  it('never emits a raw slug or "Apify" in any source name', async () => {
@@ -79,11 +79,11 @@ describe('resolvePreferredProviders', () => {
79
79
 
80
80
  describe('Scenario C — gated provider', () => {
81
81
  it('requires gate: message says "requires X, which wasn\'t provided"', () => {
82
- // blitzapi for find_email requires linkedin_url
83
- const r = resolvePreferredProviders('find_email', { first_name: 'Michel', domain: 'coldiq.com' }, ['blitzapi'])
82
+ // wiza for find_email requires linkedin_url or full name + company signal
83
+ const r = resolvePreferredProviders('find_email', { first_name: 'Michel', domain: 'coldiq.com' }, ['wiza'])
84
84
  expect(r.ok).toBe(false)
85
85
  if (!r.ok) {
86
- expect(r.error.error).toContain("'blitzapi' requires linkedin_url, which wasn't provided")
86
+ expect(r.error.error).toContain("'wiza' requires linkedin_url or (first_name, last_name, and domain/company_name), which wasn't provided")
87
87
  expect(r.error.error).toContain('ColdIQ will automatically pick the best tool')
88
88
  }
89
89
  })
@@ -104,14 +104,14 @@ describe('resolvePreferredProviders', () => {
104
104
  })
105
105
 
106
106
  it('succeeds when gated provider inputs are satisfied', () => {
107
- // blitzapi requires linkedin_url
107
+ // wiza accepts a linkedin_url alone
108
108
  const r = resolvePreferredProviders(
109
109
  'find_email',
110
110
  { linkedin_url: 'https://www.linkedin.com/in/michel-lieben' },
111
- ['blitzapi'],
111
+ ['wiza'],
112
112
  )
113
113
  expect(r.ok).toBe(true)
114
- if (r.ok) expect(r.providers).toEqual(['blitzapi'])
114
+ if (r.ok) expect(r.providers).toEqual(['wiza'])
115
115
  })
116
116
  })
117
117
 
@@ -138,7 +138,7 @@ describe('resolvePreferredProviders', () => {
138
138
  })
139
139
 
140
140
  it('errors for provider not in find_emails', () => {
141
- const r = resolvePreferredProviders('find_emails', {}, ['wiza'])
141
+ const r = resolvePreferredProviders('find_emails', {}, ['fullenrich'])
142
142
  expect(r.ok).toBe(false)
143
143
  })
144
144
  })
@@ -35,11 +35,11 @@ describe('buildSelectionInsight', () => {
35
35
  it('states coverage without claiming it was the upfront best fit', () => {
36
36
  const r = buildSelectionInsight(
37
37
  'find_email',
38
- 'fullenrich',
38
+ 'limadata-work-email',
39
39
  { domain: 'coldiq.com', first_name: 'Michel' },
40
40
  { wasFallback: true, pinnedByUser: false },
41
41
  )
42
- expect(r!.insight).toContain('FullEnrich')
42
+ expect(r!.insight).toContain('Lima Data')
43
43
  expect(r!.insight).toContain('returned the match here')
44
44
  // never the confident "Routed to" framing on a fallthrough
45
45
  expect(r!.insight.startsWith('Routed to')).toBe(false)