@coldiq/mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/dist/client.d.ts +8 -0
  2. package/dist/client.d.ts.map +1 -0
  3. package/dist/client.js +47 -0
  4. package/dist/client.js.map +1 -0
  5. package/dist/executor.d.ts +21 -0
  6. package/dist/executor.d.ts.map +1 -0
  7. package/dist/executor.js +130 -0
  8. package/dist/executor.js.map +1 -0
  9. package/dist/index.d.ts +3 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +49 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/registry.d.ts +49 -0
  14. package/dist/registry.d.ts.map +1 -0
  15. package/dist/registry.js +3104 -0
  16. package/dist/registry.js.map +1 -0
  17. package/dist/tools/enrich-company.d.ts +22 -0
  18. package/dist/tools/enrich-company.d.ts.map +1 -0
  19. package/dist/tools/enrich-company.js +21 -0
  20. package/dist/tools/enrich-company.js.map +1 -0
  21. package/dist/tools/enrich-email.d.ts +24 -0
  22. package/dist/tools/enrich-email.d.ts.map +1 -0
  23. package/dist/tools/enrich-email.js +19 -0
  24. package/dist/tools/enrich-email.js.map +1 -0
  25. package/dist/tools/enrich-emails.d.ts +31 -0
  26. package/dist/tools/enrich-emails.d.ts.map +1 -0
  27. package/dist/tools/enrich-emails.js +146 -0
  28. package/dist/tools/enrich-emails.js.map +1 -0
  29. package/dist/tools/enrich-person.d.ts +26 -0
  30. package/dist/tools/enrich-person.d.ts.map +1 -0
  31. package/dist/tools/enrich-person.js +23 -0
  32. package/dist/tools/enrich-person.js.map +1 -0
  33. package/dist/tools/fetch-page-content.d.ts +22 -0
  34. package/dist/tools/fetch-page-content.d.ts.map +1 -0
  35. package/dist/tools/fetch-page-content.js +32 -0
  36. package/dist/tools/fetch-page-content.js.map +1 -0
  37. package/dist/tools/find-email.d.ts +24 -0
  38. package/dist/tools/find-email.d.ts.map +1 -0
  39. package/dist/tools/find-email.js +19 -0
  40. package/dist/tools/find-email.js.map +1 -0
  41. package/dist/tools/find-emails.d.ts +31 -0
  42. package/dist/tools/find-emails.d.ts.map +1 -0
  43. package/dist/tools/find-emails.js +146 -0
  44. package/dist/tools/find-emails.js.map +1 -0
  45. package/dist/tools/find-influencers.d.ts +29 -0
  46. package/dist/tools/find-influencers.d.ts.map +1 -0
  47. package/dist/tools/find-influencers.js +30 -0
  48. package/dist/tools/find-influencers.js.map +1 -0
  49. package/dist/tools/find-people.d.ts +26 -0
  50. package/dist/tools/find-people.d.ts.map +1 -0
  51. package/dist/tools/find-people.js +61 -0
  52. package/dist/tools/find-people.js.map +1 -0
  53. package/dist/tools/find-phone.d.ts +24 -0
  54. package/dist/tools/find-phone.d.ts.map +1 -0
  55. package/dist/tools/find-phone.js +48 -0
  56. package/dist/tools/find-phone.js.map +1 -0
  57. package/dist/tools/find-signals.d.ts +26 -0
  58. package/dist/tools/find-signals.d.ts.map +1 -0
  59. package/dist/tools/find-signals.js +82 -0
  60. package/dist/tools/find-signals.js.map +1 -0
  61. package/dist/tools/search-ads.d.ts +33 -0
  62. package/dist/tools/search-ads.d.ts.map +1 -0
  63. package/dist/tools/search-ads.js +33 -0
  64. package/dist/tools/search-ads.js.map +1 -0
  65. package/dist/tools/search-companies.d.ts +42 -0
  66. package/dist/tools/search-companies.d.ts.map +1 -0
  67. package/dist/tools/search-companies.js +37 -0
  68. package/dist/tools/search-companies.js.map +1 -0
  69. package/dist/tools/search-jobs.d.ts +51 -0
  70. package/dist/tools/search-jobs.d.ts.map +1 -0
  71. package/dist/tools/search-jobs.js +64 -0
  72. package/dist/tools/search-jobs.js.map +1 -0
  73. package/dist/tools/search-places.d.ts +47 -0
  74. package/dist/tools/search-places.d.ts.map +1 -0
  75. package/dist/tools/search-places.js +42 -0
  76. package/dist/tools/search-places.js.map +1 -0
  77. package/dist/tools/search-reddit.d.ts +27 -0
  78. package/dist/tools/search-reddit.d.ts.map +1 -0
  79. package/dist/tools/search-reddit.js +30 -0
  80. package/dist/tools/search-reddit.js.map +1 -0
  81. package/dist/tools/search-seo.d.ts +37 -0
  82. package/dist/tools/search-seo.d.ts.map +1 -0
  83. package/dist/tools/search-seo.js +49 -0
  84. package/dist/tools/search-seo.js.map +1 -0
  85. package/dist/tools/search-web.d.ts +23 -0
  86. package/dist/tools/search-web.d.ts.map +1 -0
  87. package/dist/tools/search-web.js +20 -0
  88. package/dist/tools/search-web.js.map +1 -0
  89. package/dist/tools/verify-email.d.ts +20 -0
  90. package/dist/tools/verify-email.d.ts.map +1 -0
  91. package/dist/tools/verify-email.js +15 -0
  92. package/dist/tools/verify-email.js.map +1 -0
  93. package/package.json +28 -0
  94. package/src/client.ts +60 -0
  95. package/src/executor.ts +182 -0
  96. package/src/index.ts +155 -0
  97. package/src/registry.ts +3159 -0
  98. package/src/tools/enrich-company.ts +25 -0
  99. package/src/tools/enrich-person.ts +27 -0
  100. package/src/tools/fetch-page-content.ts +36 -0
  101. package/src/tools/find-email.ts +23 -0
  102. package/src/tools/find-emails.ts +190 -0
  103. package/src/tools/find-influencers.ts +34 -0
  104. package/src/tools/find-people.ts +69 -0
  105. package/src/tools/find-phone.ts +53 -0
  106. package/src/tools/find-signals.ts +93 -0
  107. package/src/tools/search-ads.ts +44 -0
  108. package/src/tools/search-companies.ts +41 -0
  109. package/src/tools/search-jobs.ts +73 -0
  110. package/src/tools/search-places.ts +52 -0
  111. package/src/tools/search-reddit.ts +34 -0
  112. package/src/tools/search-seo.ts +59 -0
  113. package/src/tools/search-web.ts +24 -0
  114. package/src/tools/verify-email.ts +19 -0
  115. package/test-ads-live.ts +77 -0
  116. package/test-company-live.ts +91 -0
  117. package/test-email-live.ts +171 -0
  118. package/test-influencers-live.ts +66 -0
  119. package/test-jobs-live.ts +69 -0
  120. package/test-linkupapi-live.ts +137 -0
  121. package/test-phone-live.ts +41 -0
  122. package/test-places-live.ts +89 -0
  123. package/test-reddit-live.ts +66 -0
  124. package/test-search-live.ts +79 -0
  125. package/test-seo-live.ts +68 -0
  126. package/test-web-live.ts +67 -0
  127. package/tests/client.test.ts +90 -0
  128. package/tests/executor.test.ts +83 -0
  129. package/tests/gtm/01-icp-to-emails.test.ts +43 -0
  130. package/tests/gtm/02-icp-bulk-emails.test.ts +38 -0
  131. package/tests/gtm/03-icp-to-phones.test.ts +39 -0
  132. package/tests/gtm/04-funding-signal-outreach.test.ts +42 -0
  133. package/tests/gtm/05-hiring-signal-decisionmakers.test.ts +41 -0
  134. package/tests/gtm/06-intent-signal-outreach.test.ts +44 -0
  135. package/tests/gtm/07-places-to-content.test.ts +50 -0
  136. package/tests/gtm/08-domain-to-account.test.ts +44 -0
  137. package/tests/gtm/09-linkedin-to-everything.test.ts +41 -0
  138. package/tests/gtm/10-jobs-vs-signals-routing.test.ts +38 -0
  139. package/tests/gtm/11-find-vs-enrich-routing.test.ts +39 -0
  140. package/tests/gtm/12-bogus-domain-graceful.test.ts +42 -0
  141. package/tests/gtm/13-private-linkedin-graceful.test.ts +44 -0
  142. package/tests/gtm/14-empty-handoff.test.ts +43 -0
  143. package/tests/gtm/15-seo-reddit-research.test.ts +38 -0
  144. package/tests/gtm/README.md +59 -0
  145. package/tests/gtm/harness.ts +217 -0
  146. package/tests/gtm/tools-bridge.ts +232 -0
  147. package/tests/gtm-scenarios.md +32 -0
  148. package/tests/live/smoke-report.ts +255 -0
  149. package/tests/live/smoke.test.ts +134 -0
  150. package/tests/registry-enrich-person.test.ts +447 -0
  151. package/tests/registry-fetch-page-content.test.ts +90 -0
  152. package/tests/registry-find-people.test.ts +467 -0
  153. package/tests/registry-find-signals.test.ts +470 -0
  154. package/tests/registry-linkupapi.test.ts +331 -0
  155. package/tests/registry-search-companies.test.ts +188 -0
  156. package/tests/registry-search-jobs.test.ts +116 -0
  157. package/tests/registry.test.ts +2210 -0
  158. package/tests/tools/enrich-company.test.ts +92 -0
  159. package/tests/tools/enrich-email.test.ts +94 -0
  160. package/tests/tools/enrich-emails.test.ts +271 -0
  161. package/tests/tools/enrich-person.test.ts +140 -0
  162. package/tests/tools/fetch-page-content.test.ts +108 -0
  163. package/tests/tools/find-influencers.test.ts +91 -0
  164. package/tests/tools/find-people.test.ts +344 -0
  165. package/tests/tools/find-phone.test.ts +100 -0
  166. package/tests/tools/find-signals.test.ts +110 -0
  167. package/tests/tools/search-ads.test.ts +182 -0
  168. package/tests/tools/search-companies.test.ts +58 -0
  169. package/tests/tools/search-jobs.test.ts +210 -0
  170. package/tests/tools/search-places.test.ts +114 -0
  171. package/tests/tools/search-reddit.test.ts +125 -0
  172. package/tests/tools/search-seo.test.ts +183 -0
  173. package/tests/tools/search-web.test.ts +79 -0
  174. package/tests/tools/verify-email.test.ts +68 -0
  175. package/tsconfig.json +17 -0
  176. package/vitest.config.ts +7 -0
@@ -0,0 +1,66 @@
1
+ import { initClient } from './src/client.js'
2
+ import { findInfluencersHandler } from './src/tools/find-influencers.js'
3
+
4
+ const API_URL = 'https://api.coldiq.com'
5
+ const API_KEY = 'ciq_test_3797d4ddc5542ceb'
6
+
7
+ initClient(API_URL, API_KEY)
8
+
9
+ async function run(label: string, input: Record<string, unknown>) {
10
+ console.log(`\n${'─'.repeat(60)}`)
11
+ console.log(`TEST: ${label}`)
12
+ console.log(`INPUT: ${JSON.stringify(input)}`)
13
+ const result = await findInfluencersHandler(input)
14
+ const parsed = JSON.parse(result.content[0].text)
15
+ if (result.isError) {
16
+ console.log(`RESULT: ERROR — ${parsed.error}`)
17
+ if (parsed.providers_tried?.length) {
18
+ console.log(`PROVIDERS TRIED: ${parsed.providers_tried.map((p: { id: string }) => p.id).join(' → ')}`)
19
+ }
20
+ return
21
+ }
22
+ const meta = parsed._meta
23
+ const data = parsed.data as Record<string, unknown>
24
+ const creators = (data.accounts as unknown[] | undefined) ?? (data.creators as unknown[] | undefined) ?? []
25
+ console.log(`RESULT: provider=${meta.provider} latency=${meta.latencyMs}ms creators=${creators.length}`)
26
+ }
27
+
28
+ async function main() {
29
+ // Test 1: Instagram + ai_search → discovery
30
+ await run('Instagram + ai_search (discovery expected)', {
31
+ platform: 'instagram',
32
+ ai_search: 'fitness influencers who post about nutrition',
33
+ limit: 5,
34
+ })
35
+
36
+ // Test 2: YouTube + location filter → discovery
37
+ await run('YouTube + location filter (discovery expected)', {
38
+ platform: 'youtube',
39
+ location: ['United States'],
40
+ limit: 5,
41
+ })
42
+
43
+ // Test 3: TikTok + handle → influencers_similar runs first
44
+ await run('TikTok + handle (influencers_similar expected first)', {
45
+ platform: 'tiktok',
46
+ handle: 'charlidamelio',
47
+ limit: 5,
48
+ })
49
+
50
+ // Test 4: Twitter + sort_by=engagement_rate → discovery
51
+ await run('Twitter + sort_by=engagement_rate (discovery expected)', {
52
+ platform: 'twitter',
53
+ sort_by: 'engagement_rate',
54
+ sort_order: 'desc',
55
+ limit: 5,
56
+ })
57
+
58
+ // Test 5: handle + platform=instagram → influencers_similar tried first
59
+ await run('Instagram + handle (influencers_similar tried first)', {
60
+ platform: 'instagram',
61
+ handle: 'cristiano',
62
+ limit: 5,
63
+ })
64
+ }
65
+
66
+ main().catch(console.error)
@@ -0,0 +1,69 @@
1
+ import { initClient } from './src/client.js'
2
+ import { searchJobsHandler } from './src/tools/search-jobs.js'
3
+
4
+ const API_URL = 'https://api.coldiq.com'
5
+ const API_KEY = 'ciq_test_3797d4ddc5542ceb'
6
+
7
+ initClient(API_URL, API_KEY)
8
+
9
+ async function run(label: string, input: Record<string, unknown>) {
10
+ console.log(`\n${'─'.repeat(60)}`)
11
+ console.log(`TEST: ${label}`)
12
+ console.log(`INPUT: ${JSON.stringify(input)}`)
13
+ const result = await searchJobsHandler(input)
14
+ const parsed = JSON.parse(result.content[0].text)
15
+ if (result.isError) {
16
+ console.log(`RESULT: ERROR — ${parsed.error}`)
17
+ if (parsed.providers_tried?.length) {
18
+ console.log(`PROVIDERS TRIED: ${parsed.providers_tried.map((p: { id: string }) => p.id).join(' → ')}`)
19
+ }
20
+ return
21
+ }
22
+ const meta = parsed._meta
23
+ const jobs = (parsed.data as { jobs: Array<Record<string, unknown>> }).jobs ?? []
24
+ console.log(`RESULT: provider=${meta.provider} latency=${meta.latencyMs}ms jobs=${jobs.length}`)
25
+ for (const j of jobs.slice(0, 3)) {
26
+ console.log(` • ${j.title ?? '-'} @ ${j.organization ?? j.company ?? '-'} — ${j.location ?? j.locations_derived ?? '-'}`)
27
+ }
28
+ }
29
+
30
+ async function main() {
31
+ // Test 1: Generic title search — no provider-specific filter, Career Site expected first
32
+ await run('Sales engineer in US (Career Site expected)', {
33
+ title_keywords: ['sales engineer'],
34
+ locations: ['United States'],
35
+ limit: 10,
36
+ time_range: '7d',
37
+ })
38
+
39
+ // Test 2: ATS-pinned — only Career Site can serve ats_slugs, LinkedIn skipped
40
+ await run('Greenhouse customers hiring SDRs (Career Site only)', {
41
+ title_keywords: ['sdr', 'sales development representative'],
42
+ ats_slugs: ['greenhouse'],
43
+ limit: 10,
44
+ })
45
+
46
+ // Test 3: Seniority + industry — LinkedIn-only filters, Career Site skipped
47
+ await run('Director-level roles in Software (LinkedIn only)', {
48
+ title_keywords: ['director of sales'],
49
+ industries: ['Software Development'],
50
+ seniority_levels: ['Director'],
51
+ limit: 10,
52
+ })
53
+
54
+ // Test 4: Employee-size gate — LinkedIn-only filter, Career Site skipped
55
+ await run('Mid-market AE roles 100-1000 employees (LinkedIn only)', {
56
+ title_keywords: ['account executive'],
57
+ min_employees: 100,
58
+ max_employees: 1000,
59
+ limit: 10,
60
+ })
61
+
62
+ // Test 5: Bogus title — should exhaust both providers
63
+ await run('Unmatchable title (expect waterfall failure)', {
64
+ title_keywords: ['zzznotarealjobzzz12345'],
65
+ limit: 10,
66
+ })
67
+ }
68
+
69
+ main().catch(console.error)
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Live integration tests for the new and updated Linkup API MCP providers.
3
+ *
4
+ * Tests are kept minimal to limit credit spend:
5
+ * - enrich_person (name path): michel@coldiq.com context
6
+ * - enrich_person (email path): reverse lookup
7
+ * - verify_email: full waterfall — Linkup fires at p4 if prior 3 fail
8
+ * - enrich_company: domain + URL paths — Linkup at p13/14
9
+ * - find_people: keyword-only (no company filters) — Linkup at p5
10
+ * - search_companies (is_hiring): Linkup at p16 as last resort
11
+ */
12
+
13
+ import { initClient } from './src/client.js'
14
+ import { enrichPersonHandler } from './src/tools/enrich-person.js'
15
+ import { verifyEmailHandler } from './src/tools/verify-email.js'
16
+ import { enrichCompanyHandler } from './src/tools/enrich-company.js'
17
+ import { findPeopleHandler } from './src/tools/find-people.js'
18
+ import { searchCompaniesHandler } from './src/tools/search-companies.js'
19
+
20
+ const API_URL = 'https://api.coldiq.com'
21
+ const API_KEY = 'ciq_test_3797d4ddc5542ceb'
22
+
23
+ initClient(API_URL, API_KEY)
24
+
25
+ type Result = { content: Array<{ type: string; text: string }>; isError?: boolean }
26
+
27
+ function parse(r: Result) {
28
+ try {
29
+ return JSON.parse(r.content[0].text)
30
+ } catch {
31
+ return r.content[0].text
32
+ }
33
+ }
34
+
35
+ function print(label: string, r: Result) {
36
+ const d = parse(r)
37
+ const ok = !r.isError
38
+ const provider = d?._meta?.provider ?? d?._meta?.tried ?? '—'
39
+ const summary = ok
40
+ ? `✅ provider=${provider}`
41
+ : `❌ error: ${JSON.stringify(d).slice(0, 120)}`
42
+ console.log(`\n${label}`)
43
+ console.log(` ${summary}`)
44
+ if (ok && d?.data) {
45
+ const preview = JSON.stringify(d.data).slice(0, 200)
46
+ console.log(` data: ${preview}`)
47
+ }
48
+ }
49
+
50
+ async function run() {
51
+ console.log('=== Linkup MCP Integration Tests ===\n')
52
+
53
+ // ─── 1. enrich_person — name + company path ───────────────────────────────
54
+ // New capability; Linkup is the only p1 provider — should always hit Linkup.
55
+ {
56
+ const r = await enrichPersonHandler({
57
+ first_name: 'Michel',
58
+ last_name: 'Lieben',
59
+ company_name: 'ColdIQ',
60
+ }) as Result
61
+ print('1. enrich_person (name+company) → linkupapi-profile-enrich', r)
62
+ }
63
+
64
+ // ─── 2. enrich_person — email reverse path ────────────────────────────────
65
+ // isApplicable on linkupapi-email-reverse fires when email is present.
66
+ // linkupapi-profile-enrich is skipped (no first_name).
67
+ {
68
+ const r = await enrichPersonHandler({
69
+ email: 'michel@coldiq.com',
70
+ }) as Result
71
+ print('2. enrich_person (email) → linkupapi-email-reverse', r)
72
+ }
73
+
74
+ // ─── 3. verify_email — full waterfall ─────────────────────────────────────
75
+ // Findymail/IcyPeas/Instantly at p1-p3, Linkup at p4.
76
+ // Report whichever provider wins.
77
+ {
78
+ const r = await verifyEmailHandler({ email: 'michel@coldiq.com' }) as Result
79
+ print('3. verify_email (waterfall — Linkup p4 fallback)', r)
80
+ }
81
+
82
+ // ─── 4. enrich_company — domain path ──────────────────────────────────────
83
+ // 12 providers before Linkup; report the winner.
84
+ {
85
+ const r = await enrichCompanyHandler({ domain: 'coldiq.com' }) as Result
86
+ print('4. enrich_company (domain) — Linkup at p13 fallback', r)
87
+ }
88
+
89
+ // ─── 5. enrich_company — LinkedIn URL path ────────────────────────────────
90
+ {
91
+ const r = await enrichCompanyHandler({
92
+ linkedin_url: 'https://www.linkedin.com/company/coldiq/',
93
+ }) as Result
94
+ print('5. enrich_company (linkedin_url) — Linkup at p14 fallback', r)
95
+ }
96
+
97
+ // ─── 6. find_people — keyword-only, no company filters ───────────────────
98
+ // LeadsFactory/Apollo/PDL/CompanyEnrich fire first (p1-p4).
99
+ // Linkup search-profiles fires at p5 if none return results.
100
+ // Without company identifiers, early providers often return nothing.
101
+ {
102
+ const r = await findPeopleHandler({
103
+ job_titles: ['CEO'],
104
+ keywords: 'ColdIQ',
105
+ limit: 5,
106
+ }) as Result
107
+ print('6. find_people (keyword-only) — Linkup search-profiles p5 fallback', r)
108
+ }
109
+
110
+ // ─── 7. search_companies — funding filter + keyword ───────────────────────
111
+ // TheirStack (p4) fires first for funding_stages. Linkup-fundraising at p15.
112
+ {
113
+ const r = await searchCompaniesHandler({
114
+ funding_stages: ['series_b'],
115
+ keywords: ['SaaS'],
116
+ industries: ['Software'],
117
+ limit: 5,
118
+ }) as Result
119
+ print('7. search_companies (funding) — Linkup-fundraising p15 fallback', r)
120
+ }
121
+
122
+ // ─── 8. search_companies — is_hiring ──────────────────────────────────────
123
+ // LimaData handles is_hiring at p9. Linkup-hiring at p16.
124
+ {
125
+ const r = await searchCompaniesHandler({
126
+ is_hiring: true,
127
+ industries: ['Software'],
128
+ locations: ['United States'],
129
+ limit: 5,
130
+ }) as Result
131
+ print('8. search_companies (is_hiring) — Linkup-hiring p16 last resort', r)
132
+ }
133
+
134
+ console.log('\n=== Done ===')
135
+ }
136
+
137
+ run().catch(console.error)
@@ -0,0 +1,41 @@
1
+ import { initClient } from './src/client.js'
2
+ import { findPhoneHandler } from './src/tools/find-phone.js'
3
+
4
+ const API_URL = 'https://api.coldiq.com'
5
+ const API_KEY = 'ciq_test_3797d4ddc5542ceb'
6
+
7
+ initClient(API_URL, API_KEY)
8
+
9
+ async function run(label: string, input: Record<string, unknown>) {
10
+ console.log(`\n${'─'.repeat(60)}`)
11
+ console.log(`TEST: ${label}`)
12
+ console.log(`INPUT: ${JSON.stringify(input)}`)
13
+ const result = await findPhoneHandler(input)
14
+ const parsed = JSON.parse(result.content[0].text)
15
+ if (result.isError) {
16
+ console.log(`RESULT: ERROR — ${parsed.error}`)
17
+ console.log(`PROVIDERS TRIED: ${parsed.providers_tried?.join(' → ')}`)
18
+ } else {
19
+ const meta = parsed._meta
20
+ const data = parsed.data
21
+ const phone = data?.phone ?? data?.phones?.[0] ?? data?.data?.[0]?.[0] ?? '(none)'
22
+ console.log(`RESULT: provider=${meta.provider} latency=${meta.latencyMs}ms`)
23
+ console.log(`PHONE: ${phone}`)
24
+ }
25
+ }
26
+
27
+ async function main() {
28
+ // Scenario 1: LinkedIn URL only — Findymail first, falls through if no record
29
+ await run('LinkedIn URL only (Michel Lieben)', {
30
+ linkedin_url: 'https://www.linkedin.com/in/michel-lieben',
31
+ })
32
+
33
+ // Scenario 2: Name + domain only — Findymail skipped, LimaData/AI Ark run
34
+ await run('Name + domain only (no LinkedIn URL)', {
35
+ first_name: 'Michel',
36
+ last_name: 'Lieben',
37
+ company_domain: 'coldiq.com',
38
+ })
39
+ }
40
+
41
+ main().catch(console.error)
@@ -0,0 +1,89 @@
1
+ import { initClient } from './src/client.js'
2
+ import { searchPlacesHandler } from './src/tools/search-places.js'
3
+
4
+ const API_URL = 'https://api.coldiq.com'
5
+ const API_KEY = 'ciq_test_3797d4ddc5542ceb'
6
+
7
+ initClient(API_URL, API_KEY)
8
+
9
+ async function run(label: string, input: Record<string, unknown>) {
10
+ console.log(`\n${'─'.repeat(60)}`)
11
+ console.log(`TEST: ${label}`)
12
+ console.log(`INPUT: ${JSON.stringify(input)}`)
13
+ const result = await searchPlacesHandler(input)
14
+ const parsed = JSON.parse(result.content[0].text)
15
+ if (result.isError) {
16
+ console.log(`RESULT: ERROR — ${parsed.error}`)
17
+ if (parsed.providers_tried?.length) {
18
+ console.log(`PROVIDERS TRIED: ${parsed.providers_tried.map((p: { id: string }) => p.id).join(' → ')}`)
19
+ }
20
+ return
21
+ }
22
+ const meta = parsed._meta
23
+ const places = (parsed.data as { places?: Array<Record<string, unknown>> }).places
24
+ ?? (Array.isArray(parsed.data) ? parsed.data : [])
25
+ console.log(`RESULT: provider=${meta.provider} latency=${meta.latencyMs}ms places=${places.length}`)
26
+ for (const p of places.slice(0, 3)) {
27
+ console.log(` • ${p.title ?? p.name ?? p.business_name ?? '-'} — ${p.address ?? p.city ?? '-'}`)
28
+ }
29
+ }
30
+
31
+ async function main() {
32
+ // Test 1: Country=US + query → Openmart wins (sync, fast)
33
+ await run('US query (Openmart expected)', {
34
+ query: 'coffee shop',
35
+ country: 'US',
36
+ city: 'New York',
37
+ limit: 5,
38
+ })
39
+
40
+ // Test 2: Country=FR (outside Openmart whitelist) → Google Maps
41
+ await run('FR query (Google Maps expected)', {
42
+ query: 'boulangerie',
43
+ country: 'FR',
44
+ city: 'Paris',
45
+ limit: 5,
46
+ })
47
+
48
+ // Test 3: start_urls present → Google Maps only
49
+ await run('Maps URL (Google Maps expected)', {
50
+ start_urls: ['https://www.google.com/maps/search/dentists+in+Austin'],
51
+ limit: 5,
52
+ })
53
+
54
+ // Test 4: Pin provider=openmart with structured filter + query
55
+ await run('Openmart pinned (filters)', {
56
+ provider: 'openmart',
57
+ query: 'restaurant',
58
+ country: 'US',
59
+ state: 'CA',
60
+ ownership_type: 'INDEPENDENT',
61
+ min_overall_rating: 4,
62
+ limit: 5,
63
+ })
64
+
65
+ // Test 5: Pin provider=google_maps with opening hours
66
+ await run('Google Maps pinned (opening hours)', {
67
+ provider: 'google_maps',
68
+ query: 'pizza',
69
+ city: 'Chicago',
70
+ include_opening_hours: true,
71
+ limit: 5,
72
+ })
73
+
74
+ // Test 6: Bare query → Openmart attempts first (no country gate)
75
+ await run('Bare query waterfall', {
76
+ query: 'plumber',
77
+ limit: 5,
78
+ })
79
+
80
+ // Test 7: Non-US Openmart-supported country (AU) — Openmart handles it
81
+ await run('AU query (Openmart expected)', {
82
+ query: 'cafe',
83
+ country: 'AU',
84
+ city: 'Sydney',
85
+ limit: 5,
86
+ })
87
+ }
88
+
89
+ main().catch(console.error)
@@ -0,0 +1,66 @@
1
+ import { initClient } from './src/client.js'
2
+ import { searchRedditHandler } from './src/tools/search-reddit.js'
3
+
4
+ const API_URL = 'https://api.coldiq.com'
5
+ const API_KEY = 'ciq_test_3797d4ddc5542ceb'
6
+
7
+ initClient(API_URL, API_KEY)
8
+
9
+ async function run(label: string, input: Record<string, unknown>) {
10
+ console.log(`\n${'─'.repeat(60)}`)
11
+ console.log(`TEST: ${label}`)
12
+ console.log(`INPUT: ${JSON.stringify(input)}`)
13
+ const result = await searchRedditHandler(input)
14
+ const parsed = JSON.parse(result.content[0].text)
15
+ if (result.isError) {
16
+ console.log(`RESULT: ERROR — ${parsed.error}`)
17
+ if (parsed.providers_tried?.length) {
18
+ console.log(`PROVIDERS TRIED: ${parsed.providers_tried.map((p: { id: string }) => p.id).join(' → ')}`)
19
+ }
20
+ return
21
+ }
22
+ const meta = parsed._meta
23
+ const items = ((parsed.data as Record<string, unknown>).items as unknown[] | undefined) ?? []
24
+ console.log(`RESULT: provider=${meta.provider} latency=${meta.latencyMs}ms items=${items.length}`)
25
+ for (const item of (items as Array<Record<string, unknown>>).slice(0, 3)) {
26
+ console.log(` • ${item.title ?? item.body ?? '-'}`)
27
+ }
28
+ }
29
+
30
+ async function main() {
31
+ // Test 1: query='B2B sales tools' → posts
32
+ await run('Query: B2B sales tools (posts)', {
33
+ query: 'B2B sales tools',
34
+ limit: 5,
35
+ })
36
+
37
+ // Test 2: query + sort=top + time=month
38
+ await run('Query + sort=top + time=month', {
39
+ query: 'CRM software review',
40
+ sort: 'top',
41
+ time: 'month',
42
+ limit: 5,
43
+ })
44
+
45
+ // Test 3: start_urls with a subreddit URL
46
+ await run('start_urls: r/sales subreddit', {
47
+ start_urls: ['https://www.reddit.com/r/sales/'],
48
+ limit: 5,
49
+ })
50
+
51
+ // Test 4: type=comments on a topic
52
+ await run('type=comments on sales topic', {
53
+ query: 'outbound sales',
54
+ type: 'comments',
55
+ limit: 5,
56
+ })
57
+
58
+ // Test 5: query + include_comments=true
59
+ await run('query + include_comments=true', {
60
+ query: 'B2B SaaS startup',
61
+ include_comments: true,
62
+ limit: 5,
63
+ })
64
+ }
65
+
66
+ main().catch(console.error)
@@ -0,0 +1,79 @@
1
+ import { initClient } from './src/client.js'
2
+ import { searchCompaniesHandler } from './src/tools/search-companies.js'
3
+
4
+ const API_URL = 'https://api.coldiq.com'
5
+ const API_KEY = 'ciq_test_3797d4ddc5542ceb'
6
+
7
+ initClient(API_URL, API_KEY)
8
+
9
+ async function run(label: string, input: Record<string, unknown>) {
10
+ console.log(`\n${'─'.repeat(60)}`)
11
+ console.log(`TEST: ${label}`)
12
+ console.log(`INPUT: ${JSON.stringify(input)}`)
13
+ const result = await searchCompaniesHandler(input)
14
+ const parsed = JSON.parse(result.content[0].text)
15
+ if (result.isError) {
16
+ console.log(`RESULT: ERROR — ${parsed.error}`)
17
+ console.log(`PROVIDERS TRIED: ${parsed.providers_tried?.join(' → ')}`)
18
+ } else {
19
+ const meta = parsed._meta
20
+ const data = parsed.data
21
+ console.log(`RESULT: provider=${meta.provider} latency=${meta.latencyMs}ms`)
22
+ // Find the orgs/companies array in the response
23
+ const orgs: Record<string, unknown>[] =
24
+ data?.organizations ?? data?.data ?? data?.companies ?? data?.results ?? []
25
+ console.log(`COMPANIES (${orgs.length}):`)
26
+ for (const o of orgs.slice(0, 3)) {
27
+ const name = o.name ?? o.company_name ?? '-'
28
+ const country = o.country ?? o.hq_country ?? o.location?.country ?? '-'
29
+ const emp = o.estimated_num_employees ?? o.employee_count ?? o.headcount ?? '-'
30
+ const year = o.founded_year ?? o.founded ?? '-'
31
+ console.log(` • ${name} | country=${country} | emp=${emp} | founded=${year}`)
32
+ }
33
+ }
34
+ }
35
+
36
+ async function main() {
37
+ // Test 1: Strict firmographic search — should hit CompanyEnrich/FullEnrich/PDL, NOT Apollo
38
+ await run('Strict firmographic (min_founded_year set → Apollo must be skipped)', {
39
+ keywords: ['B2B SaaS'],
40
+ countries: ['FR'],
41
+ min_employees: 50,
42
+ max_employees: 200,
43
+ min_founded_year: 2015,
44
+ limit: 5,
45
+ })
46
+
47
+ // Test 2: Same without founded_year — Apollo eligible as fallback
48
+ await run('Loose search (no founded_year → Apollo eligible)', {
49
+ keywords: ['B2B SaaS'],
50
+ countries: ['FR'],
51
+ min_employees: 50,
52
+ max_employees: 200,
53
+ limit: 3,
54
+ })
55
+
56
+ // Test 3: Technology filter — TheirStack should fire, Apollo must be skipped
57
+ await run('Tech filter (technologies → TheirStack fires, Apollo skipped)', {
58
+ technologies: ['Salesforce'],
59
+ countries: ['US'],
60
+ limit: 3,
61
+ })
62
+
63
+ // Test 4: Funding stage — Wiza + TheirStack eligible, Apollo skipped
64
+ await run('Funding filter (funding_stages → Apollo skipped)', {
65
+ industries: ['SaaS'],
66
+ funding_stages: ['series_a'],
67
+ limit: 3,
68
+ })
69
+
70
+ // Test 5: is_hiring flag — LimaData picks this up
71
+ await run('Hiring signal (is_hiring: true)', {
72
+ keywords: ['fintech'],
73
+ countries: ['US'],
74
+ is_hiring: true,
75
+ limit: 3,
76
+ })
77
+ }
78
+
79
+ main().catch(console.error)
@@ -0,0 +1,68 @@
1
+ import { initClient } from './src/client.js'
2
+ import { searchSeoHandler } from './src/tools/search-seo.js'
3
+
4
+ const API_URL = 'https://api.coldiq.com'
5
+ const API_KEY = 'ciq_test_3797d4ddc5542ceb'
6
+
7
+ initClient(API_URL, API_KEY)
8
+
9
+ async function run(label: string, input: Record<string, unknown>) {
10
+ console.log(`\n${'─'.repeat(60)}`)
11
+ console.log(`TEST: ${label}`)
12
+ console.log(`INPUT: ${JSON.stringify(input)}`)
13
+ const result = await searchSeoHandler(input)
14
+ const parsed = JSON.parse(result.content[0].text)
15
+ if (result.isError) {
16
+ console.log(`RESULT: ERROR — ${parsed.error}`)
17
+ if (parsed.providers_tried?.length) {
18
+ console.log(`PROVIDERS TRIED: ${parsed.providers_tried.map((p: { id: string }) => p.id).join(' → ')}`)
19
+ }
20
+ return
21
+ }
22
+ const meta = parsed._meta
23
+ console.log(`RESULT: provider=${meta.provider} latency=${meta.latencyMs}ms`)
24
+ }
25
+
26
+ async function main() {
27
+ // Test 1: category=keywords
28
+ await run('keywords: CRM software search volume', {
29
+ category: 'keywords',
30
+ keywords: ['CRM software'],
31
+ location: 'United States',
32
+ })
33
+
34
+ // Test 2: category=serp
35
+ await run('serp: best B2B CRM Google', {
36
+ category: 'serp',
37
+ keyword: 'best B2B CRM',
38
+ engine: 'google',
39
+ limit: 5,
40
+ })
41
+
42
+ // Test 3: category=backlinks
43
+ await run('backlinks: salesforce.com summary', {
44
+ category: 'backlinks',
45
+ target: 'salesforce.com',
46
+ })
47
+
48
+ // Test 4: category=domain (technologies)
49
+ await run('domain: hubspot.com technologies', {
50
+ category: 'domain',
51
+ target: 'hubspot.com',
52
+ })
53
+
54
+ // Test 5: category=labs (rank-overview default)
55
+ await run('labs: pipedrive.com rank-overview', {
56
+ category: 'labs',
57
+ target: 'pipedrive.com',
58
+ })
59
+
60
+ // Test 6: category=page lighthouse
61
+ await run('page: coldiq.com lighthouse', {
62
+ category: 'page',
63
+ url: 'https://coldiq.com',
64
+ page_action: 'lighthouse',
65
+ })
66
+ }
67
+
68
+ main().catch(console.error)
@@ -0,0 +1,67 @@
1
+ import { initClient } from './src/client.js'
2
+ import { searchWebHandler } from './src/tools/search-web.js'
3
+
4
+ const API_URL = 'https://api.coldiq.com'
5
+ const API_KEY = 'ciq_test_3797d4ddc5542ceb'
6
+
7
+ initClient(API_URL, API_KEY)
8
+
9
+ async function run(label: string, input: Record<string, unknown>) {
10
+ console.log(`\n${'─'.repeat(60)}`)
11
+ console.log(`TEST: ${label}`)
12
+ console.log(`INPUT: ${JSON.stringify(input)}`)
13
+ const result = await searchWebHandler(input)
14
+ const parsed = JSON.parse(result.content[0].text)
15
+ if (result.isError) {
16
+ console.log(`RESULT: ERROR — ${parsed.error}`)
17
+ if (parsed.providers_tried?.length) {
18
+ console.log(`PROVIDERS TRIED: ${parsed.providers_tried.map((p: { id: string }) => p.id).join(' → ')}`)
19
+ }
20
+ } else {
21
+ const meta = parsed._meta
22
+ const data = parsed.data as Record<string, unknown>
23
+ console.log(`RESULT: provider=${meta.provider} latency=${meta.latencyMs}ms`)
24
+ const items =
25
+ (data.organic as Record<string, unknown>[] | undefined) ??
26
+ (data.results as Record<string, unknown>[] | undefined) ??
27
+ (data.data as Record<string, unknown>[] | undefined) ??
28
+ []
29
+ console.log(`HITS (${items.length}):`)
30
+ for (const r of items.slice(0, 3)) {
31
+ const title = r.title ?? r.name ?? '-'
32
+ const url = r.url ?? r.link ?? '-'
33
+ console.log(` • ${title} — ${url}`)
34
+ }
35
+ }
36
+ }
37
+
38
+ async function main() {
39
+ // Test 1: Plain web search — should hit Serper first
40
+ await run('General query (Serper expected)', {
41
+ query: 'ColdIQ AI sales tools',
42
+ num_results: 5,
43
+ country: 'us',
44
+ })
45
+
46
+ // Test 2: Neural search — Exa hoisted to #1
47
+ await run('Neural query (Exa expected)', {
48
+ query: 'companies using AI to automate outbound sales',
49
+ num_results: 5,
50
+ search_type: 'neural',
51
+ })
52
+
53
+ // Test 3: Country-targeted query
54
+ await run('FR-targeted query (Serper expected)', {
55
+ query: 'startups B2B SaaS Paris',
56
+ num_results: 5,
57
+ country: 'fr',
58
+ })
59
+
60
+ // Test 4: News-style query — exercises top providers
61
+ await run('News query', {
62
+ query: 'Stripe latest fundraising 2025',
63
+ num_results: 5,
64
+ })
65
+ }
66
+
67
+ main().catch(console.error)