@canonry/canonry 4.136.1 → 4.136.2

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 (25) hide show
  1. package/assets/assets/{AuditHistoryPanel-B73VrWNb.js → AuditHistoryPanel-DtUGiN21.js} +1 -1
  2. package/assets/assets/{BacklinksPage-CLVMg_qx.js → BacklinksPage-BGwtmwfz.js} +1 -1
  3. package/assets/assets/{ChartPrimitives-P81CbvWc.js → ChartPrimitives-Df2UC1g3.js} +1 -1
  4. package/assets/assets/{HistoryPage-IcDqWBYW.js → HistoryPage-AtaMVEUx.js} +1 -1
  5. package/assets/assets/{ProjectPage-DTivvVIl.js → ProjectPage-OlBjxXNx.js} +1 -1
  6. package/assets/assets/{RunRow-BTH3-Tml.js → RunRow-Cmf-5xVo.js} +1 -1
  7. package/assets/assets/{RunsPage-B0k5wjFN.js → RunsPage-BAPy9Yri.js} +1 -1
  8. package/assets/assets/{SettingsPage-DGnT3Vdr.js → SettingsPage-BXO7WpTo.js} +1 -1
  9. package/assets/assets/{TrafficPage-D79PC0bB.js → TrafficPage-IH-wiPu2.js} +1 -1
  10. package/assets/assets/{TrafficSourceDetailPage-BYEVjfFm.js → TrafficSourceDetailPage-CTTN-P52.js} +1 -1
  11. package/assets/assets/{arrow-left-DCdFYJK-.js → arrow-left-BwzAIeJ_.js} +1 -1
  12. package/assets/assets/{extract-error-message-bM0aUBUI.js → extract-error-message-BIcIoJLN.js} +1 -1
  13. package/assets/assets/index-BPpSrJY0.js +210 -0
  14. package/assets/assets/{trash-2-CEz2iXBv.js → trash-2-C50OeBAV.js} +1 -1
  15. package/assets/index.html +1 -1
  16. package/dist/{chunk-PIMWEIJ3.js → chunk-GGIRLRAY.js} +3042 -397
  17. package/dist/{chunk-YMF35LN5.js → chunk-MXR3GI5L.js} +54 -122
  18. package/dist/{chunk-OESGFFUO.js → chunk-SMFUCXHI.js} +1 -1
  19. package/dist/{chunk-YQ2RXMU2.js → chunk-YVMQWVNY.js} +91 -184
  20. package/dist/cli.js +4 -4
  21. package/dist/index.js +4 -4
  22. package/dist/{intelligence-service-BKLR57RQ.js → intelligence-service-BD6BHTEJ.js} +2 -2
  23. package/dist/mcp.js +2 -2
  24. package/package.json +10 -10
  25. package/assets/assets/index-CMuA-uEN.js +0 -210
@@ -248,6 +248,8 @@ import {
248
248
  gscUrlInspectionDtoSchema,
249
249
  hasLocationLabel,
250
250
  healthSnapshotDtoSchema,
251
+ hostMatchesAnyDomain,
252
+ hostMatchesDomain,
251
253
  hostOf,
252
254
  indexingRequestResponseDtoSchema,
253
255
  internalError,
@@ -336,6 +338,9 @@ import {
336
338
  summarizeCheckResults,
337
339
  surfaceClassFromCompetitorType,
338
340
  surfaceClassLabel,
341
+ textContainsAnyBrandAlias,
342
+ textContainsBrandAlias,
343
+ textContainsDomain,
339
344
  trafficBackfillResponseSchema,
340
345
  trafficConnectVercelRequestSchema,
341
346
  trafficConnectWordpressRequestSchema,
@@ -368,7 +373,7 @@ import {
368
373
  wordpressSchemaDeployResultDtoSchema,
369
374
  wordpressSchemaStatusResultDtoSchema,
370
375
  wordpressStatusDtoSchema
371
- } from "./chunk-PIMWEIJ3.js";
376
+ } from "./chunk-GGIRLRAY.js";
372
377
 
373
378
  // src/intelligence-service.ts
374
379
  import { eq as eq44, desc as desc21, asc as asc9, and as and35, ne as ne6, or as or10, inArray as inArray15, gte as gte12, lte as lte8 } from "drizzle-orm";
@@ -5466,24 +5471,19 @@ function groupInsights(insights2, keyFn = (i) => JSON.stringify([i.query, i.prov
5466
5471
  var TRANSACTIONAL_RE = /\b(?:buy|price|pricing|cost|hire|near me|services?|agency|consultant|company)\b/i;
5467
5472
  var INFORMATIONAL_RE = /\b(?:what|how|why|when|guide|tutorial|vs|versus|alternatives?|examples?|definition)\b/i;
5468
5473
  var MIN_BRAND_TOKEN_LENGTH = 3;
5469
- function compact(value) {
5470
- return value.toLowerCase().replace(/[^a-z0-9]/g, "");
5471
- }
5472
5474
  function buildBrandTokens(canonicalDomain, brandNames = []) {
5473
5475
  const seen = /* @__PURE__ */ new Set();
5474
- const stem = canonicalDomain.toLowerCase().replace(/\.[a-z]{2,}$/, "");
5475
- const stemCompact = compact(stem);
5476
+ const stemCompact = brandKeyFromText(brandLabelFromDomain(canonicalDomain));
5476
5477
  if (stemCompact.length >= MIN_BRAND_TOKEN_LENGTH) seen.add(stemCompact);
5477
5478
  for (const name of brandNames) {
5478
5479
  if (!name) continue;
5479
- const nameCompact = compact(name);
5480
+ const nameCompact = brandKeyFromText(name);
5480
5481
  if (nameCompact.length >= MIN_BRAND_TOKEN_LENGTH) seen.add(nameCompact);
5481
5482
  }
5482
5483
  return [...seen];
5483
5484
  }
5484
5485
  function categorizeQueryByIntent(query, brandTokens) {
5485
- const compactQuery = compact(query);
5486
- if (brandTokens.length > 0 && brandTokens.some((t) => compactQuery.includes(t))) {
5486
+ if (textContainsAnyBrandAlias(query, brandTokens)) {
5487
5487
  return "brand";
5488
5488
  }
5489
5489
  if (TRANSACTIONAL_RE.test(query)) return "lead-gen";
@@ -5551,12 +5551,7 @@ function buildCitationScorecard(snapshots, queryLookup) {
5551
5551
 
5552
5552
  // ../intelligence/src/domain-matching.ts
5553
5553
  function citedDomainBelongsToProject(citedDomain, projectDomains) {
5554
- const candidate = normalizeProjectDomain(citedDomain);
5555
- for (const domain of projectDomains) {
5556
- const normalized = normalizeProjectDomain(domain);
5557
- if (candidate === normalized || candidate.endsWith(`.${normalized}`)) return true;
5558
- }
5559
- return false;
5554
+ return hostMatchesAnyDomain(citedDomain, projectDomains);
5560
5555
  }
5561
5556
 
5562
5557
  // ../intelligence/src/competitor-landscape.ts
@@ -5578,11 +5573,8 @@ function buildCompetitorLandscape(snapshots, competitorDomains, projectDomains,
5578
5573
  entry.count++;
5579
5574
  if (q) entry.queries.add(q);
5580
5575
  }
5581
- const competitorNorm = normalizeUrlDomain(competitor);
5582
5576
  for (const gs of snap.groundingSources) {
5583
- const host = normalizeUrlDomain(extractHostFromUri(gs.uri));
5584
- if (!host) continue;
5585
- if (host === competitorNorm || host.endsWith(`.${competitorNorm}`)) {
5577
+ if (hostMatchesDomain(gs.uri, competitor)) {
5586
5578
  const entry = competitorMap.get(competitor);
5587
5579
  const pageQueries = entry.pages.get(gs.uri) ?? /* @__PURE__ */ new Set();
5588
5580
  if (q) pageQueries.add(q);
@@ -5616,16 +5608,6 @@ function buildCompetitorLandscape(snapshots, competitorDomains, projectDomains,
5616
5608
  competitorRows.sort((a, b) => b.citationCount - a.citationCount);
5617
5609
  return { projectCitationCount, competitors: competitorRows };
5618
5610
  }
5619
- function normalizeUrlDomain(domain) {
5620
- return domain.toLowerCase().replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/$/, "");
5621
- }
5622
- function extractHostFromUri(uri) {
5623
- try {
5624
- return new URL(uri).hostname;
5625
- } catch {
5626
- return "";
5627
- }
5628
- }
5629
5611
 
5630
5612
  // ../intelligence/src/mention-landscape.ts
5631
5613
  function buildMentionLandscape(snapshots, competitorDomains, projectBrandNames, projectDomains, queryLookup) {
@@ -6043,10 +6025,9 @@ function buildCompetitorPressureScore(snapshots, competitorDomains, totalTracked
6043
6025
  trend: []
6044
6026
  };
6045
6027
  }
6046
- const competitorSet = new Set(competitorDomains);
6047
6028
  let overlapCount = 0;
6048
6029
  for (const snap of snapshots) {
6049
- if (snap.competitorOverlap.some((d) => competitorSet.has(d))) {
6030
+ if (snap.competitorOverlap.some((domain) => hostMatchesAnyDomain(domain, competitorDomains))) {
6050
6031
  overlapCount++;
6051
6032
  }
6052
6033
  }
@@ -6071,7 +6052,7 @@ function buildOverviewCompetitors(snapshots, competitors2, queryLookup) {
6071
6052
  return competitors2.map((competitor, index2) => {
6072
6053
  const citedQuerySet = /* @__PURE__ */ new Set();
6073
6054
  for (const snap of snapshots) {
6074
- if (snap.competitorOverlap.includes(competitor.domain) || snap.citedDomains.includes(competitor.domain)) {
6055
+ if (snap.competitorOverlap.some((domain) => hostMatchesDomain(domain, competitor.domain)) || snap.citedDomains.some((domain) => hostMatchesDomain(domain, competitor.domain))) {
6075
6056
  if (snap.queryId) citedQuerySet.add(snap.queryId);
6076
6057
  }
6077
6058
  }
@@ -6218,15 +6199,22 @@ function buildMentionShare(snapshots, options) {
6218
6199
  let projectMentionSnapshots = 0;
6219
6200
  let snapshotsWithAnswerText = 0;
6220
6201
  const competitorCounts = /* @__PURE__ */ new Map();
6221
- for (const c of options.competitors) competitorCounts.set(c.domain, 0);
6202
+ const competitorAliases = /* @__PURE__ */ new Map();
6203
+ for (const competitor of options.competitors) {
6204
+ competitorCounts.set(competitor.domain, 0);
6205
+ competitorAliases.set(
6206
+ competitor.domain,
6207
+ competitor.brandTokens.filter((alias) => brandKeyFromText(alias).length >= 3)
6208
+ );
6209
+ }
6222
6210
  for (const snap of snapshots) {
6223
6211
  const text2 = snap.answerText ?? "";
6224
6212
  if (text2.length === 0) continue;
6225
6213
  snapshotsWithAnswerText++;
6226
6214
  if (snap.projectMentioned) projectMentionSnapshots++;
6227
- const answerBrandKey = brandKeyFromText(text2);
6228
6215
  for (const competitor of options.competitors) {
6229
- if (competitorMentioned(text2, answerBrandKey, competitor.brandTokens)) {
6216
+ const aliases = competitorAliases.get(competitor.domain) ?? [];
6217
+ if (textContainsAnyBrandAlias(text2, aliases)) {
6230
6218
  competitorCounts.set(competitor.domain, (competitorCounts.get(competitor.domain) ?? 0) + 1);
6231
6219
  }
6232
6220
  }
@@ -6269,16 +6257,6 @@ function mentionShareTone(score) {
6269
6257
  if (score >= 25) return "caution";
6270
6258
  return "negative";
6271
6259
  }
6272
- function competitorMentioned(text2, answerBrandKey, brandTokens) {
6273
- for (const token of brandTokens) {
6274
- if (token.length < 3) continue;
6275
- const escaped = token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6276
- if (new RegExp(`\\b${escaped}\\b`, "i").test(text2)) return true;
6277
- const tokenBrandKey = brandKeyFromText(token);
6278
- if (tokenBrandKey.length >= 3 && answerBrandKey.includes(tokenBrandKey)) return true;
6279
- }
6280
- return false;
6281
- }
6282
6260
  function describe(parts) {
6283
6261
  const { score, projectMentionSnapshots, competitorMentionSnapshots, perCompetitor } = parts;
6284
6262
  if (projectMentionSnapshots === 0 && competitorMentionSnapshots === 0) {
@@ -9779,14 +9757,7 @@ async function analyticsRoutes(app) {
9779
9757
  }
9780
9758
  var ANCHOR_SWEEP_SCAN_LIMIT = 60;
9781
9759
  function isProviderInfraDomain(uri) {
9782
- try {
9783
- const host = new URL(uri).hostname.toLowerCase();
9784
- for (const blocked of AI_PROVIDER_INFRA_DOMAINS) {
9785
- if (host === blocked || host.endsWith(`.${blocked}`)) return true;
9786
- }
9787
- } catch {
9788
- }
9789
- return false;
9760
+ return hostMatchesAnyDomain(uri, AI_PROVIDER_INFRA_DOMAINS);
9790
9761
  }
9791
9762
  function parseGroundingSources(rawResponse) {
9792
9763
  const parsed = parseJsonColumn(rawResponse, {});
@@ -10218,12 +10189,12 @@ import { and as and9, eq as eq13, desc as desc5, inArray as inArray6 } from "dri
10218
10189
  var RECENT_RUNS_WINDOW = 5;
10219
10190
  function loadOrchestratorInput(db, project, locationFilter = void 0) {
10220
10191
  const projectId = project.id;
10221
- const ownDomain = normalizeDomain(project.canonicalDomain);
10192
+ const ownDomain = hostOf(project.canonicalDomain) ?? "";
10222
10193
  const ownedDomains = project.ownedDomains ?? [];
10223
- const ourDomains = /* @__PURE__ */ new Set([ownDomain, ...ownedDomains.map(normalizeDomain)]);
10194
+ const ourDomains = /* @__PURE__ */ new Set([ownDomain, ...ownedDomains.map((domain) => hostOf(domain) ?? "")]);
10224
10195
  const trackedQueries = listQueries(db, projectId);
10225
10196
  const candidateQueryStrings = trackedQueries.filter(isBlogShapedQuery);
10226
- const trackedCompetitors = listCompetitorDomains(db, projectId).map(normalizeDomain);
10197
+ const trackedCompetitors = listCompetitorDomains(db, projectId).map((domain) => hostOf(domain) ?? "");
10227
10198
  const competitorSet = new Set(trackedCompetitors);
10228
10199
  const recentRunIds = listRecentAnswerVisibilityRunIds(db, projectId, RECENT_RUNS_WINDOW, locationFilter);
10229
10200
  const latestRunId = recentRunIds[0] ?? "";
@@ -10264,7 +10235,7 @@ function loadOrchestratorInput(db, project, locationFilter = void 0) {
10264
10235
  }
10265
10236
  function loadDomainClasses(db, projectId) {
10266
10237
  const rows = db.select({ domain: domainClassifications.domain, competitorType: domainClassifications.competitorType }).from(domainClassifications).where(eq13(domainClassifications.projectId, projectId)).all();
10267
- return new Map(rows.map((r) => [normalizeDomain(r.domain), r.competitorType]));
10238
+ return new Map(rows.map((r) => [hostOf(r.domain) ?? "", r.competitorType]));
10268
10239
  }
10269
10240
  function buildQueryIntentModifiers(project, locationFilter) {
10270
10241
  if (locationFilter === void 0 || locationFilter === null) return [];
@@ -10483,21 +10454,22 @@ function aggregateCandidate(opts) {
10483
10454
  const isLatestRun = snap.runId === opts.latestRunId;
10484
10455
  const competitorOverlap = snap.competitorOverlap;
10485
10456
  for (const domain of competitorOverlap) {
10486
- const normalized = normalizeDomain(domain);
10487
- if (!opts.competitorSet.has(normalized)) continue;
10488
- competitorTally.set(normalized, (competitorTally.get(normalized) ?? 0) + 1);
10457
+ const normalized = hostOf(domain) ?? "";
10458
+ const competitor = findMatchingDomain(normalized, opts.competitorSet);
10459
+ if (!competitor) continue;
10460
+ competitorTally.set(competitor, (competitorTally.get(competitor) ?? 0) + 1);
10489
10461
  }
10490
10462
  const grounding = extractGroundingSources(snap.rawResponse);
10491
10463
  for (const g of grounding) {
10492
- const domain = normalizeDomain(extractHostFromUri2(g.uri));
10464
+ const domain = hostOf(g.uri) ?? "";
10493
10465
  if (!domain) continue;
10494
- if (opts.ourDomains.has(domain)) {
10466
+ if (findMatchingDomain(domain, opts.ourDomains)) {
10495
10467
  if (isLatestRun) ourCitedInLatestRun = true;
10496
10468
  recordGroundingHit(ourGroundingTally, g, domain, snap.provider);
10497
10469
  continue;
10498
10470
  }
10499
10471
  citedSurfaceTally.set(domain, (citedSurfaceTally.get(domain) ?? 0) + 1);
10500
- if (!opts.competitorSet.has(domain)) continue;
10472
+ if (!findMatchingDomain(domain, opts.competitorSet)) continue;
10501
10473
  recordGroundingHit(competitorGroundingTally, g, domain, snap.provider);
10502
10474
  }
10503
10475
  }
@@ -10519,6 +10491,9 @@ function aggregateCandidate(opts) {
10519
10491
  runsOfHistory: new Set(opts.snapshots.map((s) => s.runId)).size
10520
10492
  };
10521
10493
  }
10494
+ function findMatchingDomain(candidate, domains) {
10495
+ return [...domains].find((domain) => hostMatchesDomain(candidate, domain));
10496
+ }
10522
10497
  function recordGroundingHit(tally, g, domain, provider) {
10523
10498
  const existing = tally.get(g.uri);
10524
10499
  if (existing) {
@@ -10571,16 +10546,6 @@ function extractGroundingSources(rawResponse) {
10571
10546
  }
10572
10547
  return [];
10573
10548
  }
10574
- function extractHostFromUri2(uri) {
10575
- try {
10576
- return new URL(uri).hostname;
10577
- } catch {
10578
- return "";
10579
- }
10580
- }
10581
- function normalizeDomain(domain) {
10582
- return domain.toLowerCase().replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/$/, "");
10583
- }
10584
10549
  function extractPath2(url) {
10585
10550
  if (!url) return "";
10586
10551
  const trimmed = url.trim();
@@ -14943,10 +14908,7 @@ function stableUnique(values, normalize) {
14943
14908
  return result;
14944
14909
  }
14945
14910
  function matchesHost(value, marketingHosts) {
14946
- const normalized = normalizeHost(value);
14947
- return marketingHosts.some(
14948
- (candidate) => normalized === candidate || normalized.endsWith(`.${candidate}`)
14949
- );
14911
+ return hostMatchesAnyDomain(value, marketingHosts);
14950
14912
  }
14951
14913
  function normalizeLandingPage(value) {
14952
14914
  return normalizeUrlPath(value) ?? UNKNOWN_LANDING_PAGE;
@@ -15016,10 +14978,10 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
15016
14978
  const conditions = [];
15017
14979
  if (parsedHostScope.data === "marketing") {
15018
14980
  const lowerHost = sql9`lower(${hostColumn})`;
15019
- const normalizedHost2 = sql9`case when ${lowerHost} like 'www.%' then substr(${lowerHost}, 5) else ${lowerHost} end`;
14981
+ const normalizedHost = sql9`case when ${lowerHost} like 'www.%' then substr(${lowerHost}, 5) else ${lowerHost} end`;
15020
14982
  conditions.push(or4(...marketingHosts.flatMap((host) => [
15021
- sql9`${normalizedHost2} = ${host}`,
15022
- sql9`${normalizedHost2} like ${`%.${host}`}`
14983
+ sql9`${normalizedHost} = ${host}`,
14984
+ sql9`${normalizedHost} like ${`%.${host}`}`
15023
14985
  ])));
15024
14986
  }
15025
14987
  if (pathPrefix && pathPrefix !== "/") {
@@ -15276,11 +15238,7 @@ var normalizedPath = (value) => {
15276
15238
  return normalized.split("?")[0] || "/";
15277
15239
  };
15278
15240
  var inRange = (date, startDate, endDate) => date >= startDate && date <= endDate;
15279
- var normalizedHost = (value) => value.trim().toLowerCase().replace(/^www\./, "");
15280
- var matchesMarketingHost = (value, marketingHosts) => {
15281
- const hostname = normalizedHost(value);
15282
- return marketingHosts.some((candidate) => hostname === candidate || hostname.endsWith(`.${candidate}`));
15283
- };
15241
+ var matchesMarketingHost = (value, marketingHosts) => hostMatchesAnyDomain(value, marketingHosts);
15284
15242
  var pageMatchesMarketingHost = (value, marketingHosts) => {
15285
15243
  return matchesMarketingHost(hostOf(value) ?? "", marketingHosts);
15286
15244
  };
@@ -15360,7 +15318,9 @@ function buildOrganicEvidence(db, projectName, periodDays) {
15360
15318
  const nativeAcquisition = measurement.acquisition;
15361
15319
  const nativeAcquisitionActive = nativeAcquisition.status !== "never-synced";
15362
15320
  const marketingHosts = measurement.filters.marketingHosts;
15363
- const normalizedMarketingHosts = [...new Set(marketingHosts.map(normalizedHost).filter(Boolean))];
15321
+ const normalizedMarketingHosts = [...new Set(
15322
+ marketingHosts.map((host) => hostOf(host)).filter((host) => host !== null)
15323
+ )];
15364
15324
  const nativeStartDate = nativeAcquisition.periods[0]?.startDate;
15365
15325
  const nativeEndDate = nativeAcquisition.periods.at(-1)?.endDate;
15366
15326
  const gscCoverage = aggregateCoverage(db.select({
@@ -15813,7 +15773,7 @@ async function citationRoutes(app) {
15813
15773
  queryId: s.queryId,
15814
15774
  runCreatedAt: runCreatedAt.get(s.runId) ?? s.createdAt
15815
15775
  }));
15816
- const projectCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq19(competitors.projectId, project.id)).all().map((c) => normalizeDomain2(c.domain)).filter((d) => d.length > 0);
15776
+ const projectCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq19(competitors.projectId, project.id)).all().map((c) => hostOf(c.domain) ?? "").filter((d) => d.length > 0);
15817
15777
  const response = computeCitationVisibility({
15818
15778
  queries: projectQueries.map((q) => ({ id: q.id, query: q.query })),
15819
15779
  snapshots,
@@ -15887,18 +15847,19 @@ function computeCitationVisibility(input) {
15887
15847
  totalProviders: providers.length
15888
15848
  });
15889
15849
  }
15890
- const competitorSet = new Set(competitorDomains);
15891
15850
  const competitorGaps = [];
15892
15851
  const queryById = new Map(qs.map((q) => [q.id, q.query]));
15893
15852
  for (const snap of latestByPair.values()) {
15894
15853
  if (citationStateToCited(snap.citationState)) continue;
15895
- if (competitorSet.size === 0) continue;
15854
+ if (competitorDomains.length === 0) continue;
15896
15855
  const cited = snap.citedDomains;
15897
15856
  const overlap = snap.competitorOverlap;
15898
15857
  const candidates = new Set(
15899
- [...cited, ...overlap].map((d) => normalizeDomain2(d)).filter((d) => d.length > 0)
15858
+ [...cited, ...overlap].map((d) => hostOf(d) ?? "").filter((d) => d.length > 0)
15859
+ );
15860
+ const citingCompetitors = competitorDomains.filter(
15861
+ (competitor) => [...candidates].some((candidate) => hostMatchesDomain(candidate, competitor))
15900
15862
  );
15901
- const citingCompetitors = Array.from(candidates).filter((d) => competitorSet.has(d));
15902
15863
  if (citingCompetitors.length === 0) continue;
15903
15864
  competitorGaps.push({
15904
15865
  queryId: snap.queryId,
@@ -15940,9 +15901,6 @@ function computeCitationVisibility(input) {
15940
15901
  status: "ready"
15941
15902
  };
15942
15903
  }
15943
- function normalizeDomain2(domain) {
15944
- return domain.toLowerCase().trim().replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/$/, "");
15945
- }
15946
15904
 
15947
15905
  // ../api-routes/src/visibility-stats.ts
15948
15906
  import { and as and16, desc as desc9, eq as eq20, inArray as inArray9 } from "drizzle-orm";
@@ -15952,9 +15910,6 @@ var VISIBILITY_COMPARE_MIN_RUNS = 5;
15952
15910
  function basketPairKey(queryId, provider) {
15953
15911
  return JSON.stringify([queryId, provider]);
15954
15912
  }
15955
- function citedHostMatches(citedHost, competitorHost) {
15956
- return citedHost === competitorHost || citedHost.endsWith(`.${competitorHost}`);
15957
- }
15958
15913
  function restrict(snapshots, attribution, pairs) {
15959
15914
  const out = [];
15960
15915
  for (const snap of snapshots) {
@@ -16035,7 +15990,7 @@ function countPeriod(snaps, competitors2) {
16035
15990
  if (competitorHosts.length > 0 && snap.citedDomains.length > 0) {
16036
15991
  const citedHosts = snap.citedDomains.map((d) => hostOf(d)).filter((h) => h !== null && h.length > 0);
16037
15992
  for (const compHost of competitorHosts) {
16038
- if (citedHosts.some((ch) => citedHostMatches(ch, compHost))) competitorCited += 1;
15993
+ if (citedHosts.some((ch) => hostMatchesDomain(ch, compHost))) competitorCited += 1;
16039
15994
  }
16040
15995
  }
16041
15996
  }
@@ -24844,11 +24799,9 @@ function stableStringify3(value) {
24844
24799
 
24845
24800
  // ../api-routes/src/google.ts
24846
24801
  function isOnProjectDomain(url, projectDomain) {
24847
- try {
24848
- return new URL(url).hostname.toLowerCase().replace(/^www\./, "") === projectDomain;
24849
- } catch {
24850
- return false;
24851
- }
24802
+ const urlHost = hostOf(url);
24803
+ const projectHost = hostOf(projectDomain);
24804
+ return urlHost !== null && projectHost !== null && urlHost === projectHost;
24852
24805
  }
24853
24806
  function scopesForConnectionType(type) {
24854
24807
  switch (type) {
@@ -24870,12 +24823,10 @@ function isSitemapOwnedByProperty(sitemapUrl, propertyId, canonicalDomain) {
24870
24823
  if (sitemap.protocol !== "http:" && sitemap.protocol !== "https:") return false;
24871
24824
  if (/^sc-domain:/i.test(propertyId)) {
24872
24825
  const domain = propertyId.slice("sc-domain:".length).toLowerCase();
24873
- const host = sitemap.hostname.toLowerCase();
24874
- return host === domain || host.endsWith(`.${domain}`);
24826
+ return hostMatchesDomain(sitemap.hostname, domain);
24875
24827
  }
24876
24828
  if (/^\d+$/.test(propertyId)) {
24877
- const domain = normalizeProjectDomain(canonicalDomain);
24878
- return sitemap.hostname.toLowerCase().replace(/^www\./, "") === domain;
24829
+ return hostOf(sitemap.hostname) === hostOf(canonicalDomain);
24879
24830
  }
24880
24831
  try {
24881
24832
  const property = new URL(propertyId);
@@ -38436,9 +38387,7 @@ function normalizeHost2(host) {
38436
38387
  return host.trim().toLowerCase().replace(/^www\./, "");
38437
38388
  }
38438
38389
  function hostMatches(host, domain) {
38439
- const normalizedHost2 = normalizeHost2(host);
38440
- const normalizedDomain = normalizeHost2(domain);
38441
- return normalizedHost2 === normalizedDomain || normalizedHost2.endsWith(`.${normalizedDomain}`);
38390
+ return hostMatchesDomain(host, domain);
38442
38391
  }
38443
38392
  function utmTokenMatchesDomain(utmSource, domain) {
38444
38393
  if (hostMatches(utmSource, domain)) return true;
@@ -38447,12 +38396,7 @@ function utmTokenMatchesDomain(utmSource, domain) {
38447
38396
  return Boolean(firstLabel) && normalizedUtm === firstLabel;
38448
38397
  }
38449
38398
  function hostFromUrl(value) {
38450
- if (!value) return null;
38451
- try {
38452
- return normalizeHost2(new URL(value).hostname);
38453
- } catch {
38454
- return null;
38455
- }
38399
+ return hostOf(value);
38456
38400
  }
38457
38401
  function utmSourceFromQuery(queryString) {
38458
38402
  if (!queryString) return null;
@@ -38622,13 +38566,9 @@ function sessionWindowBucket(value, windowMs) {
38622
38566
  if (Number.isNaN(date.getTime())) return value;
38623
38567
  return new Date(Math.floor(date.getTime() / windowMs) * windowMs).toISOString();
38624
38568
  }
38625
- function normalizeHost3(host) {
38626
- if (!host) return null;
38627
- return host.trim().toLowerCase().replace(/^www\./, "") || null;
38628
- }
38629
38569
  function sameHost(a, b) {
38630
- const normalizedA = normalizeHost3(a);
38631
- const normalizedB = normalizeHost3(b);
38570
+ const normalizedA = hostOf(a);
38571
+ const normalizedB = hostOf(b);
38632
38572
  return !!normalizedA && !!normalizedB && normalizedA === normalizedB;
38633
38573
  }
38634
38574
  function pathFromSameOriginReferer(event) {
@@ -43888,7 +43828,7 @@ function upsertDomainClassifications(db, projectId, sessionId, competitorMap) {
43888
43828
  if (competitorMap.length === 0) return;
43889
43829
  const now = (/* @__PURE__ */ new Date()).toISOString();
43890
43830
  for (const entry of competitorMap) {
43891
- const domain = normalizeDomain(entry.domain);
43831
+ const domain = hostOf(entry.domain) ?? "";
43892
43832
  if (!domain) continue;
43893
43833
  db.insert(domainClassifications).values({
43894
43834
  id: crypto31.randomUUID(),
@@ -44555,38 +44495,20 @@ function createLogger(module) {
44555
44495
  }
44556
44496
 
44557
44497
  // src/citation-utils.ts
44558
- function domainMatches(domain, canonicalDomain) {
44559
- const normalized = normalizeProjectDomain(canonicalDomain);
44560
- const d = normalizeProjectDomain(domain);
44561
- return d === normalized || d.endsWith(`.${normalized}`);
44562
- }
44563
44498
  function pickProjectCitedDomain(citedDomains, projectDomains) {
44564
44499
  for (const cited of citedDomains) {
44565
- if (projectDomains.some((pd) => domainMatches(cited, pd))) return cited;
44500
+ if (projectDomains.some((pd) => hostMatchesDomain(cited, pd))) return cited;
44566
44501
  }
44567
44502
  return void 0;
44568
44503
  }
44569
44504
  function determineCitationState(normalized, domains) {
44570
44505
  for (const canonicalDomain of domains) {
44571
- const bareDomain = normalizeProjectDomain(canonicalDomain);
44572
- if (normalized.citedDomains.some((d) => domainMatches(d, bareDomain))) {
44506
+ if (normalized.citedDomains.some((d) => hostMatchesDomain(d, canonicalDomain))) {
44573
44507
  return "cited";
44574
44508
  }
44575
- const lowerDomain = bareDomain.toLowerCase();
44576
44509
  for (const source of normalized.groundingSources) {
44577
- try {
44578
- const uri = source.uri.toLowerCase();
44579
- if (lowerDomain.includes(".") && uri.includes(lowerDomain)) {
44580
- return "cited";
44581
- }
44582
- } catch {
44583
- }
44584
- if (source.title) {
44585
- const titleLower = source.title.toLowerCase().replace(/^www\./, "");
44586
- if (titleLower === lowerDomain || titleLower.endsWith(`.${lowerDomain}`)) {
44587
- return "cited";
44588
- }
44589
- }
44510
+ if (hostMatchesDomain(source.uri, canonicalDomain)) return "cited";
44511
+ if (source.title && hostMatchesDomain(source.title, canonicalDomain)) return "cited";
44590
44512
  }
44591
44513
  }
44592
44514
  return "not-cited";
@@ -44595,27 +44517,25 @@ function computeCompetitorOverlap(normalized, competitorDomains) {
44595
44517
  const overlapSet = /* @__PURE__ */ new Set();
44596
44518
  for (const d of normalized.citedDomains) {
44597
44519
  for (const cd of competitorDomains) {
44598
- if (domainMatches(d, cd)) {
44520
+ if (hostMatchesDomain(d, cd)) {
44599
44521
  overlapSet.add(cd);
44600
44522
  }
44601
44523
  }
44602
44524
  }
44603
44525
  for (const source of normalized.groundingSources) {
44604
- const uri = source.uri.toLowerCase();
44605
44526
  for (const cd of competitorDomains) {
44606
- if (uri.includes(cd.toLowerCase())) {
44527
+ if (hostMatchesDomain(source.uri, cd)) {
44607
44528
  overlapSet.add(cd);
44608
44529
  }
44609
44530
  }
44610
44531
  }
44611
44532
  if (normalized.answerText) {
44612
- const lowerAnswer = normalized.answerText.toLowerCase();
44613
44533
  for (const cd of competitorDomains) {
44614
- if (lowerAnswer.includes(cd.toLowerCase())) {
44534
+ if (textContainsDomain(normalized.answerText, cd)) {
44615
44535
  overlapSet.add(cd);
44616
44536
  }
44617
44537
  const brand = brandLabelFromDomain(cd);
44618
- if (brand.length >= 4 && new RegExp(`\\b${escapeRegExp5(brand)}\\b`, "i").test(lowerAnswer)) {
44538
+ if (brandKeyFromText(brand).length >= 4 && textContainsBrandAlias(normalized.answerText, brand)) {
44619
44539
  overlapSet.add(cd);
44620
44540
  }
44621
44541
  }
@@ -44626,27 +44546,24 @@ function computeCitedCompetitorDomains(citedDomains, competitorDomains) {
44626
44546
  const citedCompetitors = /* @__PURE__ */ new Set();
44627
44547
  for (const citedDomain of citedDomains) {
44628
44548
  for (const competitorDomain of competitorDomains) {
44629
- if (domainMatches(citedDomain, competitorDomain)) citedCompetitors.add(competitorDomain);
44549
+ if (hostMatchesDomain(citedDomain, competitorDomain)) citedCompetitors.add(competitorDomain);
44630
44550
  }
44631
44551
  }
44632
44552
  return [...citedCompetitors];
44633
44553
  }
44634
- function escapeRegExp5(value) {
44635
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
44636
- }
44637
44554
  function extractRecommendedCompetitors(answerText, ownDomains, citedDomains, competitorDomains, ownBrandNames = []) {
44638
44555
  if (!answerText || answerText.length < 20) return [];
44639
- const ownBrandKeys = new Set(
44640
- ownDomains.flatMap((domain) => collectBrandKeysFromDomain(domain))
44556
+ const ownBrandAliases = new Set(
44557
+ ownDomains.flatMap((domain) => collectBrandAliasesFromDomain(domain))
44641
44558
  );
44642
44559
  for (const name of ownBrandNames) {
44643
- const key = brandKeyFromText(name);
44644
- if (key.length >= 4) ownBrandKeys.add(key);
44560
+ if (brandKeyFromText(name).length >= 4) ownBrandAliases.add(name);
44645
44561
  }
44646
- const knownCompetitorKeys = new Set(
44647
- [...citedDomains, ...competitorDomains].flatMap((domain) => collectBrandKeysFromDomain(domain)).filter((key) => !ownBrandKeys.has(key))
44562
+ const ownKeys = new Set([...ownBrandAliases].map(brandKeyFromText));
44563
+ const knownCompetitorAliases = new Set(
44564
+ [...citedDomains, ...competitorDomains].flatMap((domain) => collectBrandAliasesFromDomain(domain)).filter((alias) => !ownKeys.has(brandKeyFromText(alias)))
44648
44565
  );
44649
- if (knownCompetitorKeys.size === 0) return [];
44566
+ if (knownCompetitorAliases.size === 0) return [];
44650
44567
  const candidatePatterns = [
44651
44568
  /^\s*(?:[-*]|\d+\.)\s+(?:\*\*)?([A-Z0-9][A-Za-z0-9][\w\s.&',/()-]{1,50}?)(?:\*\*)?\s*[:\u2014\u2013-]/gm,
44652
44569
  /\*\*([A-Z0-9][A-Za-z0-9][\w\s.&',/()-]{1,50})\*\*/g,
@@ -44696,8 +44613,8 @@ function extractRecommendedCompetitors(answerText, ownDomains, citedDomains, com
44696
44613
  if (!candidateKey) continue;
44697
44614
  if (genericKeys.has(candidateKey)) continue;
44698
44615
  if (candidate.split(/\s+/).length > 6) continue;
44699
- if (matchesBrandKey(candidateKey, ownBrandKeys)) continue;
44700
- if (!matchesBrandKey(candidateKey, knownCompetitorKeys)) continue;
44616
+ if (matchesBrandAlias(candidate, ownBrandAliases)) continue;
44617
+ if (!matchesBrandAlias(candidate, knownCompetitorAliases)) continue;
44701
44618
  if (!seen.has(candidateKey)) seen.set(candidateKey, candidate);
44702
44619
  }
44703
44620
  }
@@ -44706,27 +44623,17 @@ function extractRecommendedCompetitors(answerText, ownDomains, citedDomains, com
44706
44623
  function cleanCandidateName(candidate) {
44707
44624
  return candidate.replace(/^[\s"'`]+|[\s"'`.,:;!?]+$/g, "").replace(/\s+/g, " ").trim();
44708
44625
  }
44709
- function collectBrandKeysFromDomain(domain) {
44626
+ function collectBrandAliasesFromDomain(domain) {
44710
44627
  const reg = registrableDomain(domain);
44711
- if (!reg) {
44712
- const hostname = normalizeProjectDomain(domain).split("/")[0] ?? "";
44713
- const fallback = hostname.replace(/[^a-z0-9]/gi, "").toLowerCase();
44714
- return fallback.length >= 4 ? [fallback] : [];
44715
- }
44716
- const keys = /* @__PURE__ */ new Set();
44717
- const fullKey = reg.replace(/[^a-z0-9]/gi, "").toLowerCase();
44718
- if (fullKey.length >= 4) keys.add(fullKey);
44719
- const brand = brandLabelFromDomain(reg).replace(/[^a-z0-9]/gi, "").toLowerCase();
44720
- if (brand.length >= 4) keys.add(brand);
44721
- return [...keys];
44722
- }
44723
- function matchesBrandKey(candidateKey, brandKeys) {
44724
- for (const brandKey of brandKeys) {
44725
- if (candidateKey === brandKey) return true;
44726
- if (candidateKey.startsWith(brandKey) || candidateKey.endsWith(brandKey)) return true;
44727
- if (brandKey.startsWith(candidateKey) || brandKey.endsWith(candidateKey)) return true;
44728
- }
44729
- return false;
44628
+ if (!reg) return [];
44629
+ const aliases = /* @__PURE__ */ new Set();
44630
+ if (brandKeyFromText(reg).length >= 4) aliases.add(reg);
44631
+ const brand = brandLabelFromDomain(reg);
44632
+ if (brandKeyFromText(brand).length >= 4) aliases.add(brand);
44633
+ return [...aliases];
44634
+ }
44635
+ function matchesBrandAlias(candidate, aliases) {
44636
+ return [...aliases].some((alias) => textContainsBrandAlias(candidate, alias));
44730
44637
  }
44731
44638
 
44732
44639
  // src/intelligence-service.ts
package/dist/cli.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  setTelemetrySource,
30
30
  showFirstRunNotice,
31
31
  trackEvent
32
- } from "./chunk-YMF35LN5.js";
32
+ } from "./chunk-MXR3GI5L.js";
33
33
  import {
34
34
  CliError,
35
35
  EXIT_SYSTEM_ERROR,
@@ -46,7 +46,7 @@ import {
46
46
  saveConfig,
47
47
  saveConfigPatch,
48
48
  usageError
49
- } from "./chunk-OESGFFUO.js";
49
+ } from "./chunk-SMFUCXHI.js";
50
50
  import {
51
51
  apiKeys,
52
52
  createClient,
@@ -54,7 +54,7 @@ import {
54
54
  projects,
55
55
  queries,
56
56
  renderReportHtml
57
- } from "./chunk-YQ2RXMU2.js";
57
+ } from "./chunk-YVMQWVNY.js";
58
58
  import {
59
59
  AdsDeliverySnapshotStatuses,
60
60
  AdsHistoricalCampaignRollupStatuses,
@@ -100,7 +100,7 @@ import {
100
100
  providerQuotaPolicySchema,
101
101
  resolveProviderInput,
102
102
  winnabilityClassSchema
103
- } from "./chunk-PIMWEIJ3.js";
103
+ } from "./chunk-GGIRLRAY.js";
104
104
 
105
105
  // src/cli.ts
106
106
  import { pathToFileURL } from "url";
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-YMF35LN5.js";
3
+ } from "./chunk-MXR3GI5L.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-OESGFFUO.js";
7
- import "./chunk-YQ2RXMU2.js";
8
- import "./chunk-PIMWEIJ3.js";
6
+ } from "./chunk-SMFUCXHI.js";
7
+ import "./chunk-YVMQWVNY.js";
8
+ import "./chunk-GGIRLRAY.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-YQ2RXMU2.js";
4
- import "./chunk-PIMWEIJ3.js";
3
+ } from "./chunk-YVMQWVNY.js";
4
+ import "./chunk-GGIRLRAY.js";
5
5
  export {
6
6
  IntelligenceService
7
7
  };