gtm-now 0.10.4 → 0.10.6

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/index.js CHANGED
@@ -204,7 +204,8 @@ var init_config = __esm({
204
204
  GTM_INSTANTLY_API_KEY: "instantly",
205
205
  GTM_SMARTLEAD_API_KEY: "smartlead",
206
206
  GTM_HARVEST_API_KEY: "harvest",
207
- GTM_STORELEADS_API_KEY: "storeleads"
207
+ GTM_STORELEADS_API_KEY: "storeleads",
208
+ GTM_EXA_API_KEY: "exa"
208
209
  };
209
210
  ENV_WORKSPACE_MAP = {
210
211
  GTM_PLUSVIBE_WORKSPACE_ID: "plusvibe"
@@ -10390,6 +10391,154 @@ var init_harvest = __esm({
10390
10391
  }
10391
10392
  });
10392
10393
 
10394
+ // src/providers/adapters/exa.ts
10395
+ function extractDomain(url) {
10396
+ try {
10397
+ return new URL(url).hostname.replace(/^www\./, "");
10398
+ } catch {
10399
+ return url.replace(/^https?:\/\//, "").replace(/^www\./, "").split("/")[0] ?? url;
10400
+ }
10401
+ }
10402
+ function cleanTitle(title) {
10403
+ return title.replace(/\s*[-|]\s*[^-|]*$/, "").trim() || title;
10404
+ }
10405
+ function truncate(text, maxLength) {
10406
+ if (!text) return void 0;
10407
+ if (text.length <= maxLength) return text;
10408
+ return text.slice(0, maxLength - 3) + "...";
10409
+ }
10410
+ var BASE_URL2, REQUEST_TIMEOUT_MS2, ExaAdapter;
10411
+ var init_exa = __esm({
10412
+ "src/providers/adapters/exa.ts"() {
10413
+ "use strict";
10414
+ init_dist();
10415
+ BASE_URL2 = "https://api.exa.ai";
10416
+ REQUEST_TIMEOUT_MS2 = 15e3;
10417
+ ExaAdapter = class {
10418
+ name = "exa";
10419
+ capabilities = ["find_companies"];
10420
+ notes = 'Best for: semantic/neural company search. "Companies like Stripe but for healthcare" works. Meaning-based, not keyword matching.';
10421
+ apiKey;
10422
+ constructor(apiKey) {
10423
+ this.apiKey = apiKey;
10424
+ }
10425
+ async checkCredentials() {
10426
+ try {
10427
+ const res = await this.post("/search", {
10428
+ query: "test",
10429
+ type: "neural",
10430
+ category: "company",
10431
+ numResults: 1
10432
+ });
10433
+ return res.ok;
10434
+ } catch (error) {
10435
+ logError("exa:checkCredentials", error instanceof Error ? error : new Error(String(error)));
10436
+ return false;
10437
+ }
10438
+ }
10439
+ // ─── CompanyFinder ──────────────────────────────────
10440
+ async findCompanies(query) {
10441
+ const response = await this.routeQuery(query);
10442
+ const data = await response.json();
10443
+ const results = Array.isArray(data.results) ? data.results : [];
10444
+ return results.map((r) => this.mapResult(r));
10445
+ }
10446
+ // ─── Query Routing ──────────────────────────────────
10447
+ async routeQuery(query) {
10448
+ if (query.domain && !query.icp_text && !query.domains?.length) {
10449
+ return this.searchSingleDomain(query.domain, query);
10450
+ }
10451
+ if (query.domains?.length) {
10452
+ return this.findSimilar(query.domains, query);
10453
+ }
10454
+ return this.searchByIcp(query.icp_text ?? "", query);
10455
+ }
10456
+ async searchByIcp(icpText, query) {
10457
+ const body = {
10458
+ query: icpText,
10459
+ type: "neural",
10460
+ category: "company",
10461
+ numResults: query.limit ?? 25,
10462
+ contents: {
10463
+ text: { maxCharacters: 500 },
10464
+ summary: { query: "What does this company do?" }
10465
+ }
10466
+ };
10467
+ if (query.negate_domains?.length) {
10468
+ body.excludeDomains = query.negate_domains;
10469
+ }
10470
+ return this.post("/search", body);
10471
+ }
10472
+ async findSimilar(domains, query) {
10473
+ const primaryDomain = domains[0];
10474
+ const url = primaryDomain.includes("://") ? primaryDomain : `https://${primaryDomain}`;
10475
+ const body = {
10476
+ url,
10477
+ numResults: query.limit ?? 25,
10478
+ category: "company",
10479
+ contents: {
10480
+ text: { maxCharacters: 500 },
10481
+ summary: { query: "What does this company do?" }
10482
+ }
10483
+ };
10484
+ if (domains.length > 1) {
10485
+ body.includeDomains = domains.slice(1);
10486
+ }
10487
+ if (query.negate_domains?.length) {
10488
+ body.excludeDomains = query.negate_domains;
10489
+ }
10490
+ return this.post("/findSimilar", body);
10491
+ }
10492
+ async searchSingleDomain(domain, query) {
10493
+ const body = {
10494
+ query: domain,
10495
+ type: "neural",
10496
+ category: "company",
10497
+ includeDomains: [domain],
10498
+ numResults: 1,
10499
+ contents: {
10500
+ text: { maxCharacters: 1e3 },
10501
+ summary: { query: "What does this company do and what industry is it in?" }
10502
+ }
10503
+ };
10504
+ if (query.negate_domains?.length) {
10505
+ body.excludeDomains = query.negate_domains;
10506
+ }
10507
+ return this.post("/search", body);
10508
+ }
10509
+ // ─── Result Mapping ─────────────────────────────────
10510
+ mapResult(result) {
10511
+ return {
10512
+ domain: extractDomain(result.url),
10513
+ name: cleanTitle(result.title ?? extractDomain(result.url)),
10514
+ description: result.summary ?? truncate(result.text, 300),
10515
+ score: result.score,
10516
+ sources: [this.name]
10517
+ };
10518
+ }
10519
+ // ─── HTTP ───────────────────────────────────────────
10520
+ async post(path2, body) {
10521
+ const res = await fetch(`${BASE_URL2}${path2}`, {
10522
+ method: "POST",
10523
+ headers: {
10524
+ "Content-Type": "application/json",
10525
+ "x-api-key": this.apiKey
10526
+ },
10527
+ body: JSON.stringify(body),
10528
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS2)
10529
+ });
10530
+ if (!res.ok) {
10531
+ const text = await res.text().catch(() => "");
10532
+ throw Object.assign(new Error(`Exa API error ${res.status}: ${text || res.statusText}`), {
10533
+ statusCode: res.status === 429 ? 429 : 502
10534
+ });
10535
+ }
10536
+ return res;
10537
+ }
10538
+ };
10539
+ }
10540
+ });
10541
+
10393
10542
  // src/providers/adapters/index.ts
10394
10543
  var init_adapters = __esm({
10395
10544
  "src/providers/adapters/index.ts"() {
@@ -10406,6 +10555,7 @@ var init_adapters = __esm({
10406
10555
  init_smartlead();
10407
10556
  init_storeleads();
10408
10557
  init_harvest();
10558
+ init_exa();
10409
10559
  }
10410
10560
  });
10411
10561
 
@@ -10436,6 +10586,8 @@ function createAdapter(name, config) {
10436
10586
  return config.api_key ? new StoreLeadsAdapter(config.api_key) : null;
10437
10587
  case "harvest":
10438
10588
  return config.api_key ? new HarvestApiAdapter(config.api_key) : null;
10589
+ case "exa":
10590
+ return config.api_key ? new ExaAdapter(config.api_key) : null;
10439
10591
  default:
10440
10592
  return null;
10441
10593
  }
@@ -10589,6 +10741,17 @@ var init_manifests = __esm({
10589
10741
  quirks: ["free plan limitation"]
10590
10742
  }
10591
10743
  },
10744
+ exa: {
10745
+ name: "exa",
10746
+ capabilities: ["find_companies"],
10747
+ auto_selectable: false,
10748
+ find_companies: {
10749
+ max_limit: 100,
10750
+ default_limit: 25,
10751
+ supported_params: ["icp_text", "domains", "domain", "negate_domains"],
10752
+ quirks: ["semantic/neural search", "no employee or industry data", "premium provider"]
10753
+ }
10754
+ },
10592
10755
  prospeo: {
10593
10756
  name: "prospeo",
10594
10757
  capabilities: ["find_people"],
@@ -10621,7 +10784,7 @@ var init_prospect = __esm({
10621
10784
  prospectTools = [
10622
10785
  {
10623
10786
  name: "gtm_find_companies",
10624
- description: "Discover companies matching ICP criteria via DiscoLike, Apollo, or StoreLeads. Supports lookalike discovery, ICP text, tech stack filtering. Use gtm_tool_help for full parameter details.",
10787
+ description: 'Discover companies matching ICP criteria via DiscoLike, Apollo, StoreLeads, or Exa (semantic/neural search). Use provider: "exa" for meaning-based search that understands intent, not just keywords.',
10625
10788
  inputSchema: {
10626
10789
  type: "object",
10627
10790
  properties: {
@@ -11363,7 +11526,15 @@ For DTC/e-commerce brands, use provider: "storeleads" \u2014 it has revenue esti
11363
11526
 
11364
11527
  Tip: DiscoLike lookalike mode is extremely powerful. If you have 2-3 example companies, use the domains parameter to find hundreds of similar ones.
11365
11528
 
11366
- Single domain lookup: passing just domain (no icp_text, domains, category, tech_stack, or country) does a direct company profile fetch with rich data (growth signals, engagement metrics, tech stack from vendors). This is for researching ONE specific company, not discovery.`,
11529
+ Single domain lookup: passing just domain (no icp_text, domains, category, tech_stack, or country) does a direct company profile fetch with rich data (growth signals, engagement metrics, tech stack from vendors). This is for researching ONE specific company, not discovery.
11530
+
11531
+ Exa (provider: "exa") \u2014 semantic/neural search:
11532
+ - Meaning-based ICP matching: "B2B SaaS companies selling to healthcare, not healthtech vendors" actually works
11533
+ - Best for: nuanced ICP descriptions where keyword matching fails
11534
+ - Uses icp_text for semantic search, domains for lookalike discovery
11535
+ - Does NOT return employee count, industry, or tech stack \u2014 use for discovery, then enrich with DiscoLike or Apollo
11536
+ - Must be explicitly selected with provider: "exa" (not auto-selected)
11537
+ - Requires GTM_EXA_API_KEY env var`,
11367
11538
  gtm_find_people: `Find contacts at companies by job title, company, or domain. Returns name, title, email (when available), phone, and LinkedIn URL.
11368
11539
 
11369
11540
  Powered by DiscoLike Contacts, Apollo, or BetterContact depending on configuration.