@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.
- package/dist/client.d.ts +8 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +47 -0
- package/dist/client.js.map +1 -0
- package/dist/executor.d.ts +21 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +130 -0
- package/dist/executor.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/registry.d.ts +49 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +3104 -0
- package/dist/registry.js.map +1 -0
- package/dist/tools/enrich-company.d.ts +22 -0
- package/dist/tools/enrich-company.d.ts.map +1 -0
- package/dist/tools/enrich-company.js +21 -0
- package/dist/tools/enrich-company.js.map +1 -0
- package/dist/tools/enrich-email.d.ts +24 -0
- package/dist/tools/enrich-email.d.ts.map +1 -0
- package/dist/tools/enrich-email.js +19 -0
- package/dist/tools/enrich-email.js.map +1 -0
- package/dist/tools/enrich-emails.d.ts +31 -0
- package/dist/tools/enrich-emails.d.ts.map +1 -0
- package/dist/tools/enrich-emails.js +146 -0
- package/dist/tools/enrich-emails.js.map +1 -0
- package/dist/tools/enrich-person.d.ts +26 -0
- package/dist/tools/enrich-person.d.ts.map +1 -0
- package/dist/tools/enrich-person.js +23 -0
- package/dist/tools/enrich-person.js.map +1 -0
- package/dist/tools/fetch-page-content.d.ts +22 -0
- package/dist/tools/fetch-page-content.d.ts.map +1 -0
- package/dist/tools/fetch-page-content.js +32 -0
- package/dist/tools/fetch-page-content.js.map +1 -0
- package/dist/tools/find-email.d.ts +24 -0
- package/dist/tools/find-email.d.ts.map +1 -0
- package/dist/tools/find-email.js +19 -0
- package/dist/tools/find-email.js.map +1 -0
- package/dist/tools/find-emails.d.ts +31 -0
- package/dist/tools/find-emails.d.ts.map +1 -0
- package/dist/tools/find-emails.js +146 -0
- package/dist/tools/find-emails.js.map +1 -0
- package/dist/tools/find-influencers.d.ts +29 -0
- package/dist/tools/find-influencers.d.ts.map +1 -0
- package/dist/tools/find-influencers.js +30 -0
- package/dist/tools/find-influencers.js.map +1 -0
- package/dist/tools/find-people.d.ts +26 -0
- package/dist/tools/find-people.d.ts.map +1 -0
- package/dist/tools/find-people.js +61 -0
- package/dist/tools/find-people.js.map +1 -0
- package/dist/tools/find-phone.d.ts +24 -0
- package/dist/tools/find-phone.d.ts.map +1 -0
- package/dist/tools/find-phone.js +48 -0
- package/dist/tools/find-phone.js.map +1 -0
- package/dist/tools/find-signals.d.ts +26 -0
- package/dist/tools/find-signals.d.ts.map +1 -0
- package/dist/tools/find-signals.js +82 -0
- package/dist/tools/find-signals.js.map +1 -0
- package/dist/tools/search-ads.d.ts +33 -0
- package/dist/tools/search-ads.d.ts.map +1 -0
- package/dist/tools/search-ads.js +33 -0
- package/dist/tools/search-ads.js.map +1 -0
- package/dist/tools/search-companies.d.ts +42 -0
- package/dist/tools/search-companies.d.ts.map +1 -0
- package/dist/tools/search-companies.js +37 -0
- package/dist/tools/search-companies.js.map +1 -0
- package/dist/tools/search-jobs.d.ts +51 -0
- package/dist/tools/search-jobs.d.ts.map +1 -0
- package/dist/tools/search-jobs.js +64 -0
- package/dist/tools/search-jobs.js.map +1 -0
- package/dist/tools/search-places.d.ts +47 -0
- package/dist/tools/search-places.d.ts.map +1 -0
- package/dist/tools/search-places.js +42 -0
- package/dist/tools/search-places.js.map +1 -0
- package/dist/tools/search-reddit.d.ts +27 -0
- package/dist/tools/search-reddit.d.ts.map +1 -0
- package/dist/tools/search-reddit.js +30 -0
- package/dist/tools/search-reddit.js.map +1 -0
- package/dist/tools/search-seo.d.ts +37 -0
- package/dist/tools/search-seo.d.ts.map +1 -0
- package/dist/tools/search-seo.js +49 -0
- package/dist/tools/search-seo.js.map +1 -0
- package/dist/tools/search-web.d.ts +23 -0
- package/dist/tools/search-web.d.ts.map +1 -0
- package/dist/tools/search-web.js +20 -0
- package/dist/tools/search-web.js.map +1 -0
- package/dist/tools/verify-email.d.ts +20 -0
- package/dist/tools/verify-email.d.ts.map +1 -0
- package/dist/tools/verify-email.js +15 -0
- package/dist/tools/verify-email.js.map +1 -0
- package/package.json +28 -0
- package/src/client.ts +60 -0
- package/src/executor.ts +182 -0
- package/src/index.ts +155 -0
- package/src/registry.ts +3159 -0
- package/src/tools/enrich-company.ts +25 -0
- package/src/tools/enrich-person.ts +27 -0
- package/src/tools/fetch-page-content.ts +36 -0
- package/src/tools/find-email.ts +23 -0
- package/src/tools/find-emails.ts +190 -0
- package/src/tools/find-influencers.ts +34 -0
- package/src/tools/find-people.ts +69 -0
- package/src/tools/find-phone.ts +53 -0
- package/src/tools/find-signals.ts +93 -0
- package/src/tools/search-ads.ts +44 -0
- package/src/tools/search-companies.ts +41 -0
- package/src/tools/search-jobs.ts +73 -0
- package/src/tools/search-places.ts +52 -0
- package/src/tools/search-reddit.ts +34 -0
- package/src/tools/search-seo.ts +59 -0
- package/src/tools/search-web.ts +24 -0
- package/src/tools/verify-email.ts +19 -0
- package/test-ads-live.ts +77 -0
- package/test-company-live.ts +91 -0
- package/test-email-live.ts +171 -0
- package/test-influencers-live.ts +66 -0
- package/test-jobs-live.ts +69 -0
- package/test-linkupapi-live.ts +137 -0
- package/test-phone-live.ts +41 -0
- package/test-places-live.ts +89 -0
- package/test-reddit-live.ts +66 -0
- package/test-search-live.ts +79 -0
- package/test-seo-live.ts +68 -0
- package/test-web-live.ts +67 -0
- package/tests/client.test.ts +90 -0
- package/tests/executor.test.ts +83 -0
- package/tests/gtm/01-icp-to-emails.test.ts +43 -0
- package/tests/gtm/02-icp-bulk-emails.test.ts +38 -0
- package/tests/gtm/03-icp-to-phones.test.ts +39 -0
- package/tests/gtm/04-funding-signal-outreach.test.ts +42 -0
- package/tests/gtm/05-hiring-signal-decisionmakers.test.ts +41 -0
- package/tests/gtm/06-intent-signal-outreach.test.ts +44 -0
- package/tests/gtm/07-places-to-content.test.ts +50 -0
- package/tests/gtm/08-domain-to-account.test.ts +44 -0
- package/tests/gtm/09-linkedin-to-everything.test.ts +41 -0
- package/tests/gtm/10-jobs-vs-signals-routing.test.ts +38 -0
- package/tests/gtm/11-find-vs-enrich-routing.test.ts +39 -0
- package/tests/gtm/12-bogus-domain-graceful.test.ts +42 -0
- package/tests/gtm/13-private-linkedin-graceful.test.ts +44 -0
- package/tests/gtm/14-empty-handoff.test.ts +43 -0
- package/tests/gtm/15-seo-reddit-research.test.ts +38 -0
- package/tests/gtm/README.md +59 -0
- package/tests/gtm/harness.ts +217 -0
- package/tests/gtm/tools-bridge.ts +232 -0
- package/tests/gtm-scenarios.md +32 -0
- package/tests/live/smoke-report.ts +255 -0
- package/tests/live/smoke.test.ts +134 -0
- package/tests/registry-enrich-person.test.ts +447 -0
- package/tests/registry-fetch-page-content.test.ts +90 -0
- package/tests/registry-find-people.test.ts +467 -0
- package/tests/registry-find-signals.test.ts +470 -0
- package/tests/registry-linkupapi.test.ts +331 -0
- package/tests/registry-search-companies.test.ts +188 -0
- package/tests/registry-search-jobs.test.ts +116 -0
- package/tests/registry.test.ts +2210 -0
- package/tests/tools/enrich-company.test.ts +92 -0
- package/tests/tools/enrich-email.test.ts +94 -0
- package/tests/tools/enrich-emails.test.ts +271 -0
- package/tests/tools/enrich-person.test.ts +140 -0
- package/tests/tools/fetch-page-content.test.ts +108 -0
- package/tests/tools/find-influencers.test.ts +91 -0
- package/tests/tools/find-people.test.ts +344 -0
- package/tests/tools/find-phone.test.ts +100 -0
- package/tests/tools/find-signals.test.ts +110 -0
- package/tests/tools/search-ads.test.ts +182 -0
- package/tests/tools/search-companies.test.ts +58 -0
- package/tests/tools/search-jobs.test.ts +210 -0
- package/tests/tools/search-places.test.ts +114 -0
- package/tests/tools/search-reddit.test.ts +125 -0
- package/tests/tools/search-seo.test.ts +183 -0
- package/tests/tools/search-web.test.ts +79 -0
- package/tests/tools/verify-email.test.ts +68 -0
- package/tsconfig.json +17 -0
- 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)
|
package/test-seo-live.ts
ADDED
|
@@ -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)
|
package/test-web-live.ts
ADDED
|
@@ -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)
|