@canonry/canonry 4.136.1 → 4.137.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/README.md +2 -1
- package/assets/assets/{AuditHistoryPanel-B73VrWNb.js → AuditHistoryPanel-B6GEmlVM.js} +1 -1
- package/assets/assets/{BacklinksPage-CLVMg_qx.js → BacklinksPage-Cr3QegNo.js} +1 -1
- package/assets/assets/{ChartPrimitives-P81CbvWc.js → ChartPrimitives-rhsGzU1A.js} +1 -1
- package/assets/assets/{HistoryPage-IcDqWBYW.js → HistoryPage-B3HrxyPP.js} +1 -1
- package/assets/assets/ProjectPage-D3WiL3RI.js +8 -0
- package/assets/assets/{RunRow-BTH3-Tml.js → RunRow-MsV1RLPm.js} +1 -1
- package/assets/assets/{RunsPage-B0k5wjFN.js → RunsPage-BVlv0j3Y.js} +1 -1
- package/assets/assets/{SettingsPage-DGnT3Vdr.js → SettingsPage-Dfd-T4o9.js} +1 -1
- package/assets/assets/{TrafficPage-D79PC0bB.js → TrafficPage-Bl1jj73m.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-BYEVjfFm.js → TrafficSourceDetailPage-CinQcKmY.js} +1 -1
- package/assets/assets/{arrow-left-DCdFYJK-.js → arrow-left-CHU6s_Xn.js} +1 -1
- package/assets/assets/extract-error-message-BDdyZgyf.js +1 -0
- package/assets/assets/index-BNulljlk.js +213 -0
- package/assets/assets/{trash-2-CEz2iXBv.js → trash-2-BE5oH1Tj.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-PIMWEIJ3.js → chunk-7RF5EDUO.js} +3141 -400
- package/dist/{chunk-YQ2RXMU2.js → chunk-PBTB3FCN.js} +207 -195
- package/dist/{chunk-OESGFFUO.js → chunk-UVWI6VPP.js} +1 -1
- package/dist/{chunk-YMF35LN5.js → chunk-XEX5CLQW.js} +163 -133
- package/dist/cli.js +147 -33
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-BKLR57RQ.js → intelligence-service-JEFHUGVX.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +9 -9
- package/assets/assets/ProjectPage-DTivvVIl.js +0 -8
- package/assets/assets/extract-error-message-bM0aUBUI.js +0 -1
- 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,
|
|
@@ -262,6 +264,8 @@ import {
|
|
|
262
264
|
mentionStateFromAnswerMentioned,
|
|
263
265
|
missingDependency,
|
|
264
266
|
modelIdsEquivalent,
|
|
267
|
+
noProvider,
|
|
268
|
+
noQueries,
|
|
265
269
|
normalizeIdTokens,
|
|
266
270
|
normalizeModelId,
|
|
267
271
|
normalizeProjectAliases,
|
|
@@ -271,6 +275,7 @@ import {
|
|
|
271
275
|
notFound,
|
|
272
276
|
notImplemented,
|
|
273
277
|
notificationDtoSchema,
|
|
278
|
+
onboardingTelemetryEventSchema,
|
|
274
279
|
operationInProgress,
|
|
275
280
|
orderLocationsDefaultFirst,
|
|
276
281
|
organicEvidenceDtoSchema,
|
|
@@ -336,6 +341,10 @@ import {
|
|
|
336
341
|
summarizeCheckResults,
|
|
337
342
|
surfaceClassFromCompetitorType,
|
|
338
343
|
surfaceClassLabel,
|
|
344
|
+
telemetryEventAcceptedDtoSchema,
|
|
345
|
+
textContainsAnyBrandAlias,
|
|
346
|
+
textContainsBrandAlias,
|
|
347
|
+
textContainsDomain,
|
|
339
348
|
trafficBackfillResponseSchema,
|
|
340
349
|
trafficConnectVercelRequestSchema,
|
|
341
350
|
trafficConnectWordpressRequestSchema,
|
|
@@ -368,7 +377,7 @@ import {
|
|
|
368
377
|
wordpressSchemaDeployResultDtoSchema,
|
|
369
378
|
wordpressSchemaStatusResultDtoSchema,
|
|
370
379
|
wordpressStatusDtoSchema
|
|
371
|
-
} from "./chunk-
|
|
380
|
+
} from "./chunk-7RF5EDUO.js";
|
|
372
381
|
|
|
373
382
|
// src/intelligence-service.ts
|
|
374
383
|
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 +5475,19 @@ function groupInsights(insights2, keyFn = (i) => JSON.stringify([i.query, i.prov
|
|
|
5466
5475
|
var TRANSACTIONAL_RE = /\b(?:buy|price|pricing|cost|hire|near me|services?|agency|consultant|company)\b/i;
|
|
5467
5476
|
var INFORMATIONAL_RE = /\b(?:what|how|why|when|guide|tutorial|vs|versus|alternatives?|examples?|definition)\b/i;
|
|
5468
5477
|
var MIN_BRAND_TOKEN_LENGTH = 3;
|
|
5469
|
-
function compact(value) {
|
|
5470
|
-
return value.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
5471
|
-
}
|
|
5472
5478
|
function buildBrandTokens(canonicalDomain, brandNames = []) {
|
|
5473
5479
|
const seen = /* @__PURE__ */ new Set();
|
|
5474
|
-
const
|
|
5475
|
-
const stemCompact = compact(stem);
|
|
5480
|
+
const stemCompact = brandKeyFromText(brandLabelFromDomain(canonicalDomain));
|
|
5476
5481
|
if (stemCompact.length >= MIN_BRAND_TOKEN_LENGTH) seen.add(stemCompact);
|
|
5477
5482
|
for (const name of brandNames) {
|
|
5478
5483
|
if (!name) continue;
|
|
5479
|
-
const nameCompact =
|
|
5484
|
+
const nameCompact = brandKeyFromText(name);
|
|
5480
5485
|
if (nameCompact.length >= MIN_BRAND_TOKEN_LENGTH) seen.add(nameCompact);
|
|
5481
5486
|
}
|
|
5482
5487
|
return [...seen];
|
|
5483
5488
|
}
|
|
5484
5489
|
function categorizeQueryByIntent(query, brandTokens) {
|
|
5485
|
-
|
|
5486
|
-
if (brandTokens.length > 0 && brandTokens.some((t) => compactQuery.includes(t))) {
|
|
5490
|
+
if (textContainsAnyBrandAlias(query, brandTokens)) {
|
|
5487
5491
|
return "brand";
|
|
5488
5492
|
}
|
|
5489
5493
|
if (TRANSACTIONAL_RE.test(query)) return "lead-gen";
|
|
@@ -5551,12 +5555,7 @@ function buildCitationScorecard(snapshots, queryLookup) {
|
|
|
5551
5555
|
|
|
5552
5556
|
// ../intelligence/src/domain-matching.ts
|
|
5553
5557
|
function citedDomainBelongsToProject(citedDomain, projectDomains) {
|
|
5554
|
-
|
|
5555
|
-
for (const domain of projectDomains) {
|
|
5556
|
-
const normalized = normalizeProjectDomain(domain);
|
|
5557
|
-
if (candidate === normalized || candidate.endsWith(`.${normalized}`)) return true;
|
|
5558
|
-
}
|
|
5559
|
-
return false;
|
|
5558
|
+
return hostMatchesAnyDomain(citedDomain, projectDomains);
|
|
5560
5559
|
}
|
|
5561
5560
|
|
|
5562
5561
|
// ../intelligence/src/competitor-landscape.ts
|
|
@@ -5578,11 +5577,8 @@ function buildCompetitorLandscape(snapshots, competitorDomains, projectDomains,
|
|
|
5578
5577
|
entry.count++;
|
|
5579
5578
|
if (q) entry.queries.add(q);
|
|
5580
5579
|
}
|
|
5581
|
-
const competitorNorm = normalizeUrlDomain(competitor);
|
|
5582
5580
|
for (const gs of snap.groundingSources) {
|
|
5583
|
-
|
|
5584
|
-
if (!host) continue;
|
|
5585
|
-
if (host === competitorNorm || host.endsWith(`.${competitorNorm}`)) {
|
|
5581
|
+
if (hostMatchesDomain(gs.uri, competitor)) {
|
|
5586
5582
|
const entry = competitorMap.get(competitor);
|
|
5587
5583
|
const pageQueries = entry.pages.get(gs.uri) ?? /* @__PURE__ */ new Set();
|
|
5588
5584
|
if (q) pageQueries.add(q);
|
|
@@ -5616,16 +5612,6 @@ function buildCompetitorLandscape(snapshots, competitorDomains, projectDomains,
|
|
|
5616
5612
|
competitorRows.sort((a, b) => b.citationCount - a.citationCount);
|
|
5617
5613
|
return { projectCitationCount, competitors: competitorRows };
|
|
5618
5614
|
}
|
|
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
5615
|
|
|
5630
5616
|
// ../intelligence/src/mention-landscape.ts
|
|
5631
5617
|
function buildMentionLandscape(snapshots, competitorDomains, projectBrandNames, projectDomains, queryLookup) {
|
|
@@ -6043,10 +6029,9 @@ function buildCompetitorPressureScore(snapshots, competitorDomains, totalTracked
|
|
|
6043
6029
|
trend: []
|
|
6044
6030
|
};
|
|
6045
6031
|
}
|
|
6046
|
-
const competitorSet = new Set(competitorDomains);
|
|
6047
6032
|
let overlapCount = 0;
|
|
6048
6033
|
for (const snap of snapshots) {
|
|
6049
|
-
if (snap.competitorOverlap.some((
|
|
6034
|
+
if (snap.competitorOverlap.some((domain) => hostMatchesAnyDomain(domain, competitorDomains))) {
|
|
6050
6035
|
overlapCount++;
|
|
6051
6036
|
}
|
|
6052
6037
|
}
|
|
@@ -6071,7 +6056,7 @@ function buildOverviewCompetitors(snapshots, competitors2, queryLookup) {
|
|
|
6071
6056
|
return competitors2.map((competitor, index2) => {
|
|
6072
6057
|
const citedQuerySet = /* @__PURE__ */ new Set();
|
|
6073
6058
|
for (const snap of snapshots) {
|
|
6074
|
-
if (snap.competitorOverlap.
|
|
6059
|
+
if (snap.competitorOverlap.some((domain) => hostMatchesDomain(domain, competitor.domain)) || snap.citedDomains.some((domain) => hostMatchesDomain(domain, competitor.domain))) {
|
|
6075
6060
|
if (snap.queryId) citedQuerySet.add(snap.queryId);
|
|
6076
6061
|
}
|
|
6077
6062
|
}
|
|
@@ -6218,15 +6203,22 @@ function buildMentionShare(snapshots, options) {
|
|
|
6218
6203
|
let projectMentionSnapshots = 0;
|
|
6219
6204
|
let snapshotsWithAnswerText = 0;
|
|
6220
6205
|
const competitorCounts = /* @__PURE__ */ new Map();
|
|
6221
|
-
|
|
6206
|
+
const competitorAliases = /* @__PURE__ */ new Map();
|
|
6207
|
+
for (const competitor of options.competitors) {
|
|
6208
|
+
competitorCounts.set(competitor.domain, 0);
|
|
6209
|
+
competitorAliases.set(
|
|
6210
|
+
competitor.domain,
|
|
6211
|
+
competitor.brandTokens.filter((alias) => brandKeyFromText(alias).length >= 3)
|
|
6212
|
+
);
|
|
6213
|
+
}
|
|
6222
6214
|
for (const snap of snapshots) {
|
|
6223
6215
|
const text2 = snap.answerText ?? "";
|
|
6224
6216
|
if (text2.length === 0) continue;
|
|
6225
6217
|
snapshotsWithAnswerText++;
|
|
6226
6218
|
if (snap.projectMentioned) projectMentionSnapshots++;
|
|
6227
|
-
const answerBrandKey = brandKeyFromText(text2);
|
|
6228
6219
|
for (const competitor of options.competitors) {
|
|
6229
|
-
|
|
6220
|
+
const aliases = competitorAliases.get(competitor.domain) ?? [];
|
|
6221
|
+
if (textContainsAnyBrandAlias(text2, aliases)) {
|
|
6230
6222
|
competitorCounts.set(competitor.domain, (competitorCounts.get(competitor.domain) ?? 0) + 1);
|
|
6231
6223
|
}
|
|
6232
6224
|
}
|
|
@@ -6269,16 +6261,6 @@ function mentionShareTone(score) {
|
|
|
6269
6261
|
if (score >= 25) return "caution";
|
|
6270
6262
|
return "negative";
|
|
6271
6263
|
}
|
|
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
6264
|
function describe(parts) {
|
|
6283
6265
|
const { score, projectMentionSnapshots, competitorMentionSnapshots, perCompetitor } = parts;
|
|
6284
6266
|
if (projectMentionSnapshots === 0 && competitorMentionSnapshots === 0) {
|
|
@@ -7991,9 +7973,20 @@ async function runRoutes(app, opts) {
|
|
|
7991
7973
|
rawProviders.splice(0, rawProviders.length, ...normalized);
|
|
7992
7974
|
}
|
|
7993
7975
|
const providers = rawProviders?.length ? rawProviders : void 0;
|
|
7976
|
+
const shouldPreflight = trigger !== RunTriggers.probe && opts.getRunnableProviderNames !== void 0;
|
|
7977
|
+
const trackedRows = body.queries?.length || shouldPreflight ? app.db.select({ query: queries.query }).from(queries).where(eq8(queries.projectId, project.id)).all() : [];
|
|
7978
|
+
if (shouldPreflight) {
|
|
7979
|
+
const preflightError = answerVisibilityPreflightError({
|
|
7980
|
+
projectName: project.name,
|
|
7981
|
+
projectProviders: project.providers,
|
|
7982
|
+
requestedProviders: providers,
|
|
7983
|
+
runnableProviderNames: opts.getRunnableProviderNames(),
|
|
7984
|
+
trackedQueryCount: trackedRows.length
|
|
7985
|
+
});
|
|
7986
|
+
if (preflightError) throw preflightError;
|
|
7987
|
+
}
|
|
7994
7988
|
let scopedQueries = null;
|
|
7995
7989
|
if (body.queries?.length) {
|
|
7996
|
-
const trackedRows = app.db.select({ query: queries.query }).from(queries).where(eq8(queries.projectId, project.id)).all();
|
|
7997
7990
|
const tracked = new Set(trackedRows.map((r) => r.query));
|
|
7998
7991
|
const missing = body.queries.filter((q) => !tracked.has(q));
|
|
7999
7992
|
if (missing.length) {
|
|
@@ -8157,7 +8150,28 @@ async function runRoutes(app, opts) {
|
|
|
8157
8150
|
const providers = rawProviders?.length ? rawProviders : void 0;
|
|
8158
8151
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8159
8152
|
const results = [];
|
|
8153
|
+
const runnableProviderNames = opts.getRunnableProviderNames?.();
|
|
8160
8154
|
for (const project of allProjects) {
|
|
8155
|
+
if (runnableProviderNames) {
|
|
8156
|
+
const trackedQuery = app.db.select({ id: queries.id }).from(queries).where(eq8(queries.projectId, project.id)).limit(1).get();
|
|
8157
|
+
const preflightError = answerVisibilityPreflightError({
|
|
8158
|
+
projectName: project.name,
|
|
8159
|
+
projectProviders: project.providers,
|
|
8160
|
+
requestedProviders: providers,
|
|
8161
|
+
runnableProviderNames,
|
|
8162
|
+
trackedQueryCount: trackedQuery ? 1 : 0
|
|
8163
|
+
});
|
|
8164
|
+
if (preflightError) {
|
|
8165
|
+
results.push({
|
|
8166
|
+
projectName: project.name,
|
|
8167
|
+
projectId: project.id,
|
|
8168
|
+
status: "error",
|
|
8169
|
+
error: preflightError.message,
|
|
8170
|
+
errorCode: preflightError.code
|
|
8171
|
+
});
|
|
8172
|
+
continue;
|
|
8173
|
+
}
|
|
8174
|
+
}
|
|
8161
8175
|
const projectLocations = project.locations;
|
|
8162
8176
|
let resolvedLocation;
|
|
8163
8177
|
if (project.defaultLocation) {
|
|
@@ -8221,6 +8235,26 @@ async function runRoutes(app, opts) {
|
|
|
8221
8235
|
return reply.send(loadRunDetail(app, run));
|
|
8222
8236
|
});
|
|
8223
8237
|
}
|
|
8238
|
+
function answerVisibilityPreflightError(input) {
|
|
8239
|
+
if (input.trackedQueryCount === 0) {
|
|
8240
|
+
return noQueries(input.projectName);
|
|
8241
|
+
}
|
|
8242
|
+
const availableProviders = normalizeProviderNames(input.runnableProviderNames);
|
|
8243
|
+
const requestedProviders = normalizeProviderNames(input.requestedProviders ?? []);
|
|
8244
|
+
const projectProviders = normalizeProviderNames(input.projectProviders);
|
|
8245
|
+
const selectedProviders = requestedProviders.length > 0 ? requestedProviders : projectProviders.length > 0 ? projectProviders : availableProviders;
|
|
8246
|
+
const available = new Set(availableProviders);
|
|
8247
|
+
const runnableProviders = selectedProviders.filter((provider) => available.has(provider));
|
|
8248
|
+
if (runnableProviders.length > 0) return null;
|
|
8249
|
+
return noProvider(input.projectName, {
|
|
8250
|
+
availableProviders,
|
|
8251
|
+
selectedProviders,
|
|
8252
|
+
selectionSource: requestedProviders.length > 0 ? "request" : projectProviders.length > 0 ? "project" : "instance"
|
|
8253
|
+
});
|
|
8254
|
+
}
|
|
8255
|
+
function normalizeProviderNames(providerNames) {
|
|
8256
|
+
return [...new Set(providerNames.map((name) => name.trim().toLowerCase()).filter(Boolean))];
|
|
8257
|
+
}
|
|
8224
8258
|
function parseRunTriggerRequest(value) {
|
|
8225
8259
|
const result = runTriggerRequestSchema.safeParse(value);
|
|
8226
8260
|
if (result.success) return result.data;
|
|
@@ -9779,14 +9813,7 @@ async function analyticsRoutes(app) {
|
|
|
9779
9813
|
}
|
|
9780
9814
|
var ANCHOR_SWEEP_SCAN_LIMIT = 60;
|
|
9781
9815
|
function isProviderInfraDomain(uri) {
|
|
9782
|
-
|
|
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;
|
|
9816
|
+
return hostMatchesAnyDomain(uri, AI_PROVIDER_INFRA_DOMAINS);
|
|
9790
9817
|
}
|
|
9791
9818
|
function parseGroundingSources(rawResponse) {
|
|
9792
9819
|
const parsed = parseJsonColumn(rawResponse, {});
|
|
@@ -10218,12 +10245,12 @@ import { and as and9, eq as eq13, desc as desc5, inArray as inArray6 } from "dri
|
|
|
10218
10245
|
var RECENT_RUNS_WINDOW = 5;
|
|
10219
10246
|
function loadOrchestratorInput(db, project, locationFilter = void 0) {
|
|
10220
10247
|
const projectId = project.id;
|
|
10221
|
-
const ownDomain =
|
|
10248
|
+
const ownDomain = hostOf(project.canonicalDomain) ?? "";
|
|
10222
10249
|
const ownedDomains = project.ownedDomains ?? [];
|
|
10223
|
-
const ourDomains = /* @__PURE__ */ new Set([ownDomain, ...ownedDomains.map(
|
|
10250
|
+
const ourDomains = /* @__PURE__ */ new Set([ownDomain, ...ownedDomains.map((domain) => hostOf(domain) ?? "")]);
|
|
10224
10251
|
const trackedQueries = listQueries(db, projectId);
|
|
10225
10252
|
const candidateQueryStrings = trackedQueries.filter(isBlogShapedQuery);
|
|
10226
|
-
const trackedCompetitors = listCompetitorDomains(db, projectId).map(
|
|
10253
|
+
const trackedCompetitors = listCompetitorDomains(db, projectId).map((domain) => hostOf(domain) ?? "");
|
|
10227
10254
|
const competitorSet = new Set(trackedCompetitors);
|
|
10228
10255
|
const recentRunIds = listRecentAnswerVisibilityRunIds(db, projectId, RECENT_RUNS_WINDOW, locationFilter);
|
|
10229
10256
|
const latestRunId = recentRunIds[0] ?? "";
|
|
@@ -10264,7 +10291,7 @@ function loadOrchestratorInput(db, project, locationFilter = void 0) {
|
|
|
10264
10291
|
}
|
|
10265
10292
|
function loadDomainClasses(db, projectId) {
|
|
10266
10293
|
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) => [
|
|
10294
|
+
return new Map(rows.map((r) => [hostOf(r.domain) ?? "", r.competitorType]));
|
|
10268
10295
|
}
|
|
10269
10296
|
function buildQueryIntentModifiers(project, locationFilter) {
|
|
10270
10297
|
if (locationFilter === void 0 || locationFilter === null) return [];
|
|
@@ -10483,21 +10510,22 @@ function aggregateCandidate(opts) {
|
|
|
10483
10510
|
const isLatestRun = snap.runId === opts.latestRunId;
|
|
10484
10511
|
const competitorOverlap = snap.competitorOverlap;
|
|
10485
10512
|
for (const domain of competitorOverlap) {
|
|
10486
|
-
const normalized =
|
|
10487
|
-
|
|
10488
|
-
|
|
10513
|
+
const normalized = hostOf(domain) ?? "";
|
|
10514
|
+
const competitor = findMatchingDomain(normalized, opts.competitorSet);
|
|
10515
|
+
if (!competitor) continue;
|
|
10516
|
+
competitorTally.set(competitor, (competitorTally.get(competitor) ?? 0) + 1);
|
|
10489
10517
|
}
|
|
10490
10518
|
const grounding = extractGroundingSources(snap.rawResponse);
|
|
10491
10519
|
for (const g of grounding) {
|
|
10492
|
-
const domain =
|
|
10520
|
+
const domain = hostOf(g.uri) ?? "";
|
|
10493
10521
|
if (!domain) continue;
|
|
10494
|
-
if (opts.ourDomains
|
|
10522
|
+
if (findMatchingDomain(domain, opts.ourDomains)) {
|
|
10495
10523
|
if (isLatestRun) ourCitedInLatestRun = true;
|
|
10496
10524
|
recordGroundingHit(ourGroundingTally, g, domain, snap.provider);
|
|
10497
10525
|
continue;
|
|
10498
10526
|
}
|
|
10499
10527
|
citedSurfaceTally.set(domain, (citedSurfaceTally.get(domain) ?? 0) + 1);
|
|
10500
|
-
if (!opts.competitorSet
|
|
10528
|
+
if (!findMatchingDomain(domain, opts.competitorSet)) continue;
|
|
10501
10529
|
recordGroundingHit(competitorGroundingTally, g, domain, snap.provider);
|
|
10502
10530
|
}
|
|
10503
10531
|
}
|
|
@@ -10519,6 +10547,9 @@ function aggregateCandidate(opts) {
|
|
|
10519
10547
|
runsOfHistory: new Set(opts.snapshots.map((s) => s.runId)).size
|
|
10520
10548
|
};
|
|
10521
10549
|
}
|
|
10550
|
+
function findMatchingDomain(candidate, domains) {
|
|
10551
|
+
return [...domains].find((domain) => hostMatchesDomain(candidate, domain));
|
|
10552
|
+
}
|
|
10522
10553
|
function recordGroundingHit(tally, g, domain, provider) {
|
|
10523
10554
|
const existing = tally.get(g.uri);
|
|
10524
10555
|
if (existing) {
|
|
@@ -10571,16 +10602,6 @@ function extractGroundingSources(rawResponse) {
|
|
|
10571
10602
|
}
|
|
10572
10603
|
return [];
|
|
10573
10604
|
}
|
|
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
10605
|
function extractPath2(url) {
|
|
10585
10606
|
if (!url) return "";
|
|
10586
10607
|
const trimmed = url.trim();
|
|
@@ -14943,10 +14964,7 @@ function stableUnique(values, normalize) {
|
|
|
14943
14964
|
return result;
|
|
14944
14965
|
}
|
|
14945
14966
|
function matchesHost(value, marketingHosts) {
|
|
14946
|
-
|
|
14947
|
-
return marketingHosts.some(
|
|
14948
|
-
(candidate) => normalized === candidate || normalized.endsWith(`.${candidate}`)
|
|
14949
|
-
);
|
|
14967
|
+
return hostMatchesAnyDomain(value, marketingHosts);
|
|
14950
14968
|
}
|
|
14951
14969
|
function normalizeLandingPage(value) {
|
|
14952
14970
|
return normalizeUrlPath(value) ?? UNKNOWN_LANDING_PAGE;
|
|
@@ -15016,10 +15034,10 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
|
|
|
15016
15034
|
const conditions = [];
|
|
15017
15035
|
if (parsedHostScope.data === "marketing") {
|
|
15018
15036
|
const lowerHost = sql9`lower(${hostColumn})`;
|
|
15019
|
-
const
|
|
15037
|
+
const normalizedHost = sql9`case when ${lowerHost} like 'www.%' then substr(${lowerHost}, 5) else ${lowerHost} end`;
|
|
15020
15038
|
conditions.push(or4(...marketingHosts.flatMap((host) => [
|
|
15021
|
-
sql9`${
|
|
15022
|
-
sql9`${
|
|
15039
|
+
sql9`${normalizedHost} = ${host}`,
|
|
15040
|
+
sql9`${normalizedHost} like ${`%.${host}`}`
|
|
15023
15041
|
])));
|
|
15024
15042
|
}
|
|
15025
15043
|
if (pathPrefix && pathPrefix !== "/") {
|
|
@@ -15276,11 +15294,7 @@ var normalizedPath = (value) => {
|
|
|
15276
15294
|
return normalized.split("?")[0] || "/";
|
|
15277
15295
|
};
|
|
15278
15296
|
var inRange = (date, startDate, endDate) => date >= startDate && date <= endDate;
|
|
15279
|
-
var
|
|
15280
|
-
var matchesMarketingHost = (value, marketingHosts) => {
|
|
15281
|
-
const hostname = normalizedHost(value);
|
|
15282
|
-
return marketingHosts.some((candidate) => hostname === candidate || hostname.endsWith(`.${candidate}`));
|
|
15283
|
-
};
|
|
15297
|
+
var matchesMarketingHost = (value, marketingHosts) => hostMatchesAnyDomain(value, marketingHosts);
|
|
15284
15298
|
var pageMatchesMarketingHost = (value, marketingHosts) => {
|
|
15285
15299
|
return matchesMarketingHost(hostOf(value) ?? "", marketingHosts);
|
|
15286
15300
|
};
|
|
@@ -15360,7 +15374,9 @@ function buildOrganicEvidence(db, projectName, periodDays) {
|
|
|
15360
15374
|
const nativeAcquisition = measurement.acquisition;
|
|
15361
15375
|
const nativeAcquisitionActive = nativeAcquisition.status !== "never-synced";
|
|
15362
15376
|
const marketingHosts = measurement.filters.marketingHosts;
|
|
15363
|
-
const normalizedMarketingHosts = [...new Set(
|
|
15377
|
+
const normalizedMarketingHosts = [...new Set(
|
|
15378
|
+
marketingHosts.map((host) => hostOf(host)).filter((host) => host !== null)
|
|
15379
|
+
)];
|
|
15364
15380
|
const nativeStartDate = nativeAcquisition.periods[0]?.startDate;
|
|
15365
15381
|
const nativeEndDate = nativeAcquisition.periods.at(-1)?.endDate;
|
|
15366
15382
|
const gscCoverage = aggregateCoverage(db.select({
|
|
@@ -15813,7 +15829,7 @@ async function citationRoutes(app) {
|
|
|
15813
15829
|
queryId: s.queryId,
|
|
15814
15830
|
runCreatedAt: runCreatedAt.get(s.runId) ?? s.createdAt
|
|
15815
15831
|
}));
|
|
15816
|
-
const projectCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq19(competitors.projectId, project.id)).all().map((c) =>
|
|
15832
|
+
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
15833
|
const response = computeCitationVisibility({
|
|
15818
15834
|
queries: projectQueries.map((q) => ({ id: q.id, query: q.query })),
|
|
15819
15835
|
snapshots,
|
|
@@ -15887,18 +15903,19 @@ function computeCitationVisibility(input) {
|
|
|
15887
15903
|
totalProviders: providers.length
|
|
15888
15904
|
});
|
|
15889
15905
|
}
|
|
15890
|
-
const competitorSet = new Set(competitorDomains);
|
|
15891
15906
|
const competitorGaps = [];
|
|
15892
15907
|
const queryById = new Map(qs.map((q) => [q.id, q.query]));
|
|
15893
15908
|
for (const snap of latestByPair.values()) {
|
|
15894
15909
|
if (citationStateToCited(snap.citationState)) continue;
|
|
15895
|
-
if (
|
|
15910
|
+
if (competitorDomains.length === 0) continue;
|
|
15896
15911
|
const cited = snap.citedDomains;
|
|
15897
15912
|
const overlap = snap.competitorOverlap;
|
|
15898
15913
|
const candidates = new Set(
|
|
15899
|
-
[...cited, ...overlap].map((d) =>
|
|
15914
|
+
[...cited, ...overlap].map((d) => hostOf(d) ?? "").filter((d) => d.length > 0)
|
|
15915
|
+
);
|
|
15916
|
+
const citingCompetitors = competitorDomains.filter(
|
|
15917
|
+
(competitor) => [...candidates].some((candidate) => hostMatchesDomain(candidate, competitor))
|
|
15900
15918
|
);
|
|
15901
|
-
const citingCompetitors = Array.from(candidates).filter((d) => competitorSet.has(d));
|
|
15902
15919
|
if (citingCompetitors.length === 0) continue;
|
|
15903
15920
|
competitorGaps.push({
|
|
15904
15921
|
queryId: snap.queryId,
|
|
@@ -15940,9 +15957,6 @@ function computeCitationVisibility(input) {
|
|
|
15940
15957
|
status: "ready"
|
|
15941
15958
|
};
|
|
15942
15959
|
}
|
|
15943
|
-
function normalizeDomain2(domain) {
|
|
15944
|
-
return domain.toLowerCase().trim().replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/$/, "");
|
|
15945
|
-
}
|
|
15946
15960
|
|
|
15947
15961
|
// ../api-routes/src/visibility-stats.ts
|
|
15948
15962
|
import { and as and16, desc as desc9, eq as eq20, inArray as inArray9 } from "drizzle-orm";
|
|
@@ -15952,9 +15966,6 @@ var VISIBILITY_COMPARE_MIN_RUNS = 5;
|
|
|
15952
15966
|
function basketPairKey(queryId, provider) {
|
|
15953
15967
|
return JSON.stringify([queryId, provider]);
|
|
15954
15968
|
}
|
|
15955
|
-
function citedHostMatches(citedHost, competitorHost) {
|
|
15956
|
-
return citedHost === competitorHost || citedHost.endsWith(`.${competitorHost}`);
|
|
15957
|
-
}
|
|
15958
15969
|
function restrict(snapshots, attribution, pairs) {
|
|
15959
15970
|
const out = [];
|
|
15960
15971
|
for (const snap of snapshots) {
|
|
@@ -16035,7 +16046,7 @@ function countPeriod(snaps, competitors2) {
|
|
|
16035
16046
|
if (competitorHosts.length > 0 && snap.citedDomains.length > 0) {
|
|
16036
16047
|
const citedHosts = snap.citedDomains.map((d) => hostOf(d)).filter((h) => h !== null && h.length > 0);
|
|
16037
16048
|
for (const compHost of competitorHosts) {
|
|
16038
|
-
if (citedHosts.some((ch) =>
|
|
16049
|
+
if (citedHosts.some((ch) => hostMatchesDomain(ch, compHost))) competitorCited += 1;
|
|
16039
16050
|
}
|
|
16040
16051
|
}
|
|
16041
16052
|
}
|
|
@@ -17501,6 +17512,7 @@ var SCHEMA_TABLE = {
|
|
|
17501
17512
|
LatestProjectRunDto: latestProjectRunDtoSchema,
|
|
17502
17513
|
LocationContext: locationContextSchema,
|
|
17503
17514
|
NotificationDto: notificationDtoSchema,
|
|
17515
|
+
OnboardingTelemetryEvent: onboardingTelemetryEventSchema,
|
|
17504
17516
|
OrganicEvidenceDto: organicEvidenceDtoSchema,
|
|
17505
17517
|
ProjectDto: projectDtoSchema,
|
|
17506
17518
|
ProjectUpsertRequest: projectUpsertRequestSchema,
|
|
@@ -17522,6 +17534,7 @@ var SCHEMA_TABLE = {
|
|
|
17522
17534
|
SnapshotListResponse: snapshotListResponseSchema,
|
|
17523
17535
|
SnapshotReportDto: snapshotReportSchema,
|
|
17524
17536
|
SourceBreakdownDto: sourceBreakdownDtoSchema,
|
|
17537
|
+
TelemetryEventAcceptedDto: telemetryEventAcceptedDtoSchema,
|
|
17525
17538
|
TrafficBackfillResponse: trafficBackfillResponseSchema,
|
|
17526
17539
|
TrafficEventsResponse: trafficEventsResponseSchema,
|
|
17527
17540
|
TrafficSourceDetailDto: trafficSourceDetailDtoSchema,
|
|
@@ -18547,7 +18560,9 @@ var routeCatalog = [
|
|
|
18547
18560
|
},
|
|
18548
18561
|
responses: {
|
|
18549
18562
|
201: jsonResponse("Run queued.", "RunDto"),
|
|
18550
|
-
|
|
18563
|
+
422: errorResponse("Project has no tracked queries."),
|
|
18564
|
+
409: errorResponse("Run already in progress."),
|
|
18565
|
+
503: errorResponse("No runnable answer provider is configured.")
|
|
18551
18566
|
}
|
|
18552
18567
|
},
|
|
18553
18568
|
{
|
|
@@ -19172,6 +19187,24 @@ var routeCatalog = [
|
|
|
19172
19187
|
501: errorResponse("Telemetry configuration is not available.")
|
|
19173
19188
|
}
|
|
19174
19189
|
},
|
|
19190
|
+
{
|
|
19191
|
+
method: "post",
|
|
19192
|
+
path: "/api/v1/telemetry/onboarding",
|
|
19193
|
+
summary: "Record a privacy-safe onboarding milestone",
|
|
19194
|
+
tags: ["telemetry"],
|
|
19195
|
+
requestBody: {
|
|
19196
|
+
required: true,
|
|
19197
|
+
content: {
|
|
19198
|
+
"application/json": {
|
|
19199
|
+
schema: { $ref: "#/components/schemas/OnboardingTelemetryEvent" }
|
|
19200
|
+
}
|
|
19201
|
+
}
|
|
19202
|
+
},
|
|
19203
|
+
responses: {
|
|
19204
|
+
202: jsonResponse("Onboarding milestone accepted.", "TelemetryEventAcceptedDto"),
|
|
19205
|
+
400: errorResponse("Invalid onboarding telemetry event.")
|
|
19206
|
+
}
|
|
19207
|
+
},
|
|
19175
19208
|
{
|
|
19176
19209
|
method: "get",
|
|
19177
19210
|
path: "/api/v1/screenshots/{snapshotId}",
|
|
@@ -22822,6 +22855,19 @@ async function telemetryRoutes(app, opts) {
|
|
|
22822
22855
|
anonymousId: status?.anonymousId ? status.anonymousId.slice(0, 8) + "..." : void 0
|
|
22823
22856
|
};
|
|
22824
22857
|
});
|
|
22858
|
+
app.post("/telemetry/onboarding", async (request, reply) => {
|
|
22859
|
+
const parsed = onboardingTelemetryEventSchema.safeParse(request.body);
|
|
22860
|
+
if (!parsed.success) {
|
|
22861
|
+
throw validationError("Invalid onboarding telemetry event", {
|
|
22862
|
+
issues: parsed.error.issues.map((issue) => ({
|
|
22863
|
+
code: issue.code,
|
|
22864
|
+
path: issue.path.join(".")
|
|
22865
|
+
}))
|
|
22866
|
+
});
|
|
22867
|
+
}
|
|
22868
|
+
opts.recordOnboardingEvent?.(parsed.data);
|
|
22869
|
+
return reply.status(202).send({ accepted: Boolean(opts.recordOnboardingEvent) });
|
|
22870
|
+
});
|
|
22825
22871
|
}
|
|
22826
22872
|
|
|
22827
22873
|
// ../api-routes/src/schedules.ts
|
|
@@ -24843,12 +24889,28 @@ function stableStringify3(value) {
|
|
|
24843
24889
|
}
|
|
24844
24890
|
|
|
24845
24891
|
// ../api-routes/src/google.ts
|
|
24892
|
+
var GOOGLE_OAUTH_COMPLETE_MESSAGE = "canonry:google-oauth-complete";
|
|
24893
|
+
function googleOAuthSuccessHtml(type) {
|
|
24894
|
+
const message = JSON.stringify({
|
|
24895
|
+
type: GOOGLE_OAUTH_COMPLETE_MESSAGE,
|
|
24896
|
+
connectionType: type
|
|
24897
|
+
});
|
|
24898
|
+
return `<html><body style="font-family:system-ui;text-align:center;padding:60px">
|
|
24899
|
+
<h2>Connected successfully!</h2>
|
|
24900
|
+
<p>Google ${type.toUpperCase()} has been linked to your domain.</p>
|
|
24901
|
+
<p style="color:#888">This window closes automatically.</p>
|
|
24902
|
+
<script>
|
|
24903
|
+
if (window.opener && !window.opener.closed) {
|
|
24904
|
+
window.opener.postMessage(${message}, '*')
|
|
24905
|
+
window.close()
|
|
24906
|
+
}
|
|
24907
|
+
</script>
|
|
24908
|
+
</body></html>`;
|
|
24909
|
+
}
|
|
24846
24910
|
function isOnProjectDomain(url, projectDomain) {
|
|
24847
|
-
|
|
24848
|
-
|
|
24849
|
-
|
|
24850
|
-
return false;
|
|
24851
|
-
}
|
|
24911
|
+
const urlHost = hostOf(url);
|
|
24912
|
+
const projectHost = hostOf(projectDomain);
|
|
24913
|
+
return urlHost !== null && projectHost !== null && urlHost === projectHost;
|
|
24852
24914
|
}
|
|
24853
24915
|
function scopesForConnectionType(type) {
|
|
24854
24916
|
switch (type) {
|
|
@@ -24870,12 +24932,10 @@ function isSitemapOwnedByProperty(sitemapUrl, propertyId, canonicalDomain) {
|
|
|
24870
24932
|
if (sitemap.protocol !== "http:" && sitemap.protocol !== "https:") return false;
|
|
24871
24933
|
if (/^sc-domain:/i.test(propertyId)) {
|
|
24872
24934
|
const domain = propertyId.slice("sc-domain:".length).toLowerCase();
|
|
24873
|
-
|
|
24874
|
-
return host === domain || host.endsWith(`.${domain}`);
|
|
24935
|
+
return hostMatchesDomain(sitemap.hostname, domain);
|
|
24875
24936
|
}
|
|
24876
24937
|
if (/^\d+$/.test(propertyId)) {
|
|
24877
|
-
|
|
24878
|
-
return sitemap.hostname.toLowerCase().replace(/^www\./, "") === domain;
|
|
24938
|
+
return hostOf(sitemap.hostname) === hostOf(canonicalDomain);
|
|
24879
24939
|
}
|
|
24880
24940
|
try {
|
|
24881
24941
|
const property = new URL(propertyId);
|
|
@@ -25172,13 +25232,7 @@ async function googleRoutes(app, opts) {
|
|
|
25172
25232
|
entityId: type,
|
|
25173
25233
|
diff: { domain, type, propertyId }
|
|
25174
25234
|
});
|
|
25175
|
-
return reply.type("text/html").send(
|
|
25176
|
-
`<html><body style="font-family:system-ui;text-align:center;padding:60px">
|
|
25177
|
-
<h2>Connected successfully!</h2>
|
|
25178
|
-
<p>Google ${type.toUpperCase()} has been linked to your domain.</p>
|
|
25179
|
-
<p style="color:#888">You can close this tab.</p>
|
|
25180
|
-
</body></html>`
|
|
25181
|
-
);
|
|
25235
|
+
return reply.type("text/html").send(googleOAuthSuccessHtml(type));
|
|
25182
25236
|
}
|
|
25183
25237
|
app.get("/google/callback", async (request, reply) => {
|
|
25184
25238
|
return handleOAuthCallback(request, reply);
|
|
@@ -38436,9 +38490,7 @@ function normalizeHost2(host) {
|
|
|
38436
38490
|
return host.trim().toLowerCase().replace(/^www\./, "");
|
|
38437
38491
|
}
|
|
38438
38492
|
function hostMatches(host, domain) {
|
|
38439
|
-
|
|
38440
|
-
const normalizedDomain = normalizeHost2(domain);
|
|
38441
|
-
return normalizedHost2 === normalizedDomain || normalizedHost2.endsWith(`.${normalizedDomain}`);
|
|
38493
|
+
return hostMatchesDomain(host, domain);
|
|
38442
38494
|
}
|
|
38443
38495
|
function utmTokenMatchesDomain(utmSource, domain) {
|
|
38444
38496
|
if (hostMatches(utmSource, domain)) return true;
|
|
@@ -38447,12 +38499,7 @@ function utmTokenMatchesDomain(utmSource, domain) {
|
|
|
38447
38499
|
return Boolean(firstLabel) && normalizedUtm === firstLabel;
|
|
38448
38500
|
}
|
|
38449
38501
|
function hostFromUrl(value) {
|
|
38450
|
-
|
|
38451
|
-
try {
|
|
38452
|
-
return normalizeHost2(new URL(value).hostname);
|
|
38453
|
-
} catch {
|
|
38454
|
-
return null;
|
|
38455
|
-
}
|
|
38502
|
+
return hostOf(value);
|
|
38456
38503
|
}
|
|
38457
38504
|
function utmSourceFromQuery(queryString) {
|
|
38458
38505
|
if (!queryString) return null;
|
|
@@ -38622,13 +38669,9 @@ function sessionWindowBucket(value, windowMs) {
|
|
|
38622
38669
|
if (Number.isNaN(date.getTime())) return value;
|
|
38623
38670
|
return new Date(Math.floor(date.getTime() / windowMs) * windowMs).toISOString();
|
|
38624
38671
|
}
|
|
38625
|
-
function normalizeHost3(host) {
|
|
38626
|
-
if (!host) return null;
|
|
38627
|
-
return host.trim().toLowerCase().replace(/^www\./, "") || null;
|
|
38628
|
-
}
|
|
38629
38672
|
function sameHost(a, b) {
|
|
38630
|
-
const normalizedA =
|
|
38631
|
-
const normalizedB =
|
|
38673
|
+
const normalizedA = hostOf(a);
|
|
38674
|
+
const normalizedB = hostOf(b);
|
|
38632
38675
|
return !!normalizedA && !!normalizedB && normalizedA === normalizedB;
|
|
38633
38676
|
}
|
|
38634
38677
|
function pathFromSameOriginReferer(event) {
|
|
@@ -43888,7 +43931,7 @@ function upsertDomainClassifications(db, projectId, sessionId, competitorMap) {
|
|
|
43888
43931
|
if (competitorMap.length === 0) return;
|
|
43889
43932
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
43890
43933
|
for (const entry of competitorMap) {
|
|
43891
|
-
const domain =
|
|
43934
|
+
const domain = hostOf(entry.domain) ?? "";
|
|
43892
43935
|
if (!domain) continue;
|
|
43893
43936
|
db.insert(domainClassifications).values({
|
|
43894
43937
|
id: crypto31.randomUUID(),
|
|
@@ -44219,7 +44262,8 @@ async function apiRoutes(app, opts) {
|
|
|
44219
44262
|
await api.register(competitorRoutes);
|
|
44220
44263
|
await api.register(runRoutes, {
|
|
44221
44264
|
onRunCreated: opts.onRunCreated,
|
|
44222
|
-
validProviderNames: opts.providerAdapters?.map((a) => a.name)
|
|
44265
|
+
validProviderNames: opts.providerAdapters?.map((a) => a.name),
|
|
44266
|
+
getRunnableProviderNames: opts.getRunnableProviderNames
|
|
44223
44267
|
});
|
|
44224
44268
|
await api.register(applyRoutes, {
|
|
44225
44269
|
onScheduleUpdated: opts.onScheduleUpdated,
|
|
@@ -44270,7 +44314,8 @@ async function apiRoutes(app, opts) {
|
|
|
44270
44314
|
});
|
|
44271
44315
|
await api.register(telemetryRoutes, {
|
|
44272
44316
|
getTelemetryStatus: opts.getTelemetryStatus,
|
|
44273
|
-
setTelemetryEnabled: opts.setTelemetryEnabled
|
|
44317
|
+
setTelemetryEnabled: opts.setTelemetryEnabled,
|
|
44318
|
+
recordOnboardingEvent: opts.recordOnboardingEvent
|
|
44274
44319
|
});
|
|
44275
44320
|
await api.register(adsRoutes, {
|
|
44276
44321
|
adsCredentialStore: opts.adsCredentialStore,
|
|
@@ -44555,38 +44600,20 @@ function createLogger(module) {
|
|
|
44555
44600
|
}
|
|
44556
44601
|
|
|
44557
44602
|
// 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
44603
|
function pickProjectCitedDomain(citedDomains, projectDomains) {
|
|
44564
44604
|
for (const cited of citedDomains) {
|
|
44565
|
-
if (projectDomains.some((pd) =>
|
|
44605
|
+
if (projectDomains.some((pd) => hostMatchesDomain(cited, pd))) return cited;
|
|
44566
44606
|
}
|
|
44567
44607
|
return void 0;
|
|
44568
44608
|
}
|
|
44569
44609
|
function determineCitationState(normalized, domains) {
|
|
44570
44610
|
for (const canonicalDomain of domains) {
|
|
44571
|
-
|
|
44572
|
-
if (normalized.citedDomains.some((d) => domainMatches(d, bareDomain))) {
|
|
44611
|
+
if (normalized.citedDomains.some((d) => hostMatchesDomain(d, canonicalDomain))) {
|
|
44573
44612
|
return "cited";
|
|
44574
44613
|
}
|
|
44575
|
-
const lowerDomain = bareDomain.toLowerCase();
|
|
44576
44614
|
for (const source of normalized.groundingSources) {
|
|
44577
|
-
|
|
44578
|
-
|
|
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
|
-
}
|
|
44615
|
+
if (hostMatchesDomain(source.uri, canonicalDomain)) return "cited";
|
|
44616
|
+
if (source.title && hostMatchesDomain(source.title, canonicalDomain)) return "cited";
|
|
44590
44617
|
}
|
|
44591
44618
|
}
|
|
44592
44619
|
return "not-cited";
|
|
@@ -44595,27 +44622,25 @@ function computeCompetitorOverlap(normalized, competitorDomains) {
|
|
|
44595
44622
|
const overlapSet = /* @__PURE__ */ new Set();
|
|
44596
44623
|
for (const d of normalized.citedDomains) {
|
|
44597
44624
|
for (const cd of competitorDomains) {
|
|
44598
|
-
if (
|
|
44625
|
+
if (hostMatchesDomain(d, cd)) {
|
|
44599
44626
|
overlapSet.add(cd);
|
|
44600
44627
|
}
|
|
44601
44628
|
}
|
|
44602
44629
|
}
|
|
44603
44630
|
for (const source of normalized.groundingSources) {
|
|
44604
|
-
const uri = source.uri.toLowerCase();
|
|
44605
44631
|
for (const cd of competitorDomains) {
|
|
44606
|
-
if (uri
|
|
44632
|
+
if (hostMatchesDomain(source.uri, cd)) {
|
|
44607
44633
|
overlapSet.add(cd);
|
|
44608
44634
|
}
|
|
44609
44635
|
}
|
|
44610
44636
|
}
|
|
44611
44637
|
if (normalized.answerText) {
|
|
44612
|
-
const lowerAnswer = normalized.answerText.toLowerCase();
|
|
44613
44638
|
for (const cd of competitorDomains) {
|
|
44614
|
-
if (
|
|
44639
|
+
if (textContainsDomain(normalized.answerText, cd)) {
|
|
44615
44640
|
overlapSet.add(cd);
|
|
44616
44641
|
}
|
|
44617
44642
|
const brand = brandLabelFromDomain(cd);
|
|
44618
|
-
if (brand.length >= 4 &&
|
|
44643
|
+
if (brandKeyFromText(brand).length >= 4 && textContainsBrandAlias(normalized.answerText, brand)) {
|
|
44619
44644
|
overlapSet.add(cd);
|
|
44620
44645
|
}
|
|
44621
44646
|
}
|
|
@@ -44626,27 +44651,24 @@ function computeCitedCompetitorDomains(citedDomains, competitorDomains) {
|
|
|
44626
44651
|
const citedCompetitors = /* @__PURE__ */ new Set();
|
|
44627
44652
|
for (const citedDomain of citedDomains) {
|
|
44628
44653
|
for (const competitorDomain of competitorDomains) {
|
|
44629
|
-
if (
|
|
44654
|
+
if (hostMatchesDomain(citedDomain, competitorDomain)) citedCompetitors.add(competitorDomain);
|
|
44630
44655
|
}
|
|
44631
44656
|
}
|
|
44632
44657
|
return [...citedCompetitors];
|
|
44633
44658
|
}
|
|
44634
|
-
function escapeRegExp5(value) {
|
|
44635
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
44636
|
-
}
|
|
44637
44659
|
function extractRecommendedCompetitors(answerText, ownDomains, citedDomains, competitorDomains, ownBrandNames = []) {
|
|
44638
44660
|
if (!answerText || answerText.length < 20) return [];
|
|
44639
|
-
const
|
|
44640
|
-
ownDomains.flatMap((domain) =>
|
|
44661
|
+
const ownBrandAliases = new Set(
|
|
44662
|
+
ownDomains.flatMap((domain) => collectBrandAliasesFromDomain(domain))
|
|
44641
44663
|
);
|
|
44642
44664
|
for (const name of ownBrandNames) {
|
|
44643
|
-
|
|
44644
|
-
if (key.length >= 4) ownBrandKeys.add(key);
|
|
44665
|
+
if (brandKeyFromText(name).length >= 4) ownBrandAliases.add(name);
|
|
44645
44666
|
}
|
|
44646
|
-
const
|
|
44647
|
-
|
|
44667
|
+
const ownKeys = new Set([...ownBrandAliases].map(brandKeyFromText));
|
|
44668
|
+
const knownCompetitorAliases = new Set(
|
|
44669
|
+
[...citedDomains, ...competitorDomains].flatMap((domain) => collectBrandAliasesFromDomain(domain)).filter((alias) => !ownKeys.has(brandKeyFromText(alias)))
|
|
44648
44670
|
);
|
|
44649
|
-
if (
|
|
44671
|
+
if (knownCompetitorAliases.size === 0) return [];
|
|
44650
44672
|
const candidatePatterns = [
|
|
44651
44673
|
/^\s*(?:[-*]|\d+\.)\s+(?:\*\*)?([A-Z0-9][A-Za-z0-9][\w\s.&',/()-]{1,50}?)(?:\*\*)?\s*[:\u2014\u2013-]/gm,
|
|
44652
44674
|
/\*\*([A-Z0-9][A-Za-z0-9][\w\s.&',/()-]{1,50})\*\*/g,
|
|
@@ -44696,8 +44718,8 @@ function extractRecommendedCompetitors(answerText, ownDomains, citedDomains, com
|
|
|
44696
44718
|
if (!candidateKey) continue;
|
|
44697
44719
|
if (genericKeys.has(candidateKey)) continue;
|
|
44698
44720
|
if (candidate.split(/\s+/).length > 6) continue;
|
|
44699
|
-
if (
|
|
44700
|
-
if (!
|
|
44721
|
+
if (matchesBrandAlias(candidate, ownBrandAliases)) continue;
|
|
44722
|
+
if (!matchesBrandAlias(candidate, knownCompetitorAliases)) continue;
|
|
44701
44723
|
if (!seen.has(candidateKey)) seen.set(candidateKey, candidate);
|
|
44702
44724
|
}
|
|
44703
44725
|
}
|
|
@@ -44706,27 +44728,17 @@ function extractRecommendedCompetitors(answerText, ownDomains, citedDomains, com
|
|
|
44706
44728
|
function cleanCandidateName(candidate) {
|
|
44707
44729
|
return candidate.replace(/^[\s"'`]+|[\s"'`.,:;!?]+$/g, "").replace(/\s+/g, " ").trim();
|
|
44708
44730
|
}
|
|
44709
|
-
function
|
|
44731
|
+
function collectBrandAliasesFromDomain(domain) {
|
|
44710
44732
|
const reg = registrableDomain(domain);
|
|
44711
|
-
if (!reg)
|
|
44712
|
-
|
|
44713
|
-
|
|
44714
|
-
|
|
44715
|
-
|
|
44716
|
-
|
|
44717
|
-
|
|
44718
|
-
|
|
44719
|
-
|
|
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;
|
|
44733
|
+
if (!reg) return [];
|
|
44734
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
44735
|
+
if (brandKeyFromText(reg).length >= 4) aliases.add(reg);
|
|
44736
|
+
const brand = brandLabelFromDomain(reg);
|
|
44737
|
+
if (brandKeyFromText(brand).length >= 4) aliases.add(brand);
|
|
44738
|
+
return [...aliases];
|
|
44739
|
+
}
|
|
44740
|
+
function matchesBrandAlias(candidate, aliases) {
|
|
44741
|
+
return [...aliases].some((alias) => textContainsBrandAlias(candidate, alias));
|
|
44730
44742
|
}
|
|
44731
44743
|
|
|
44732
44744
|
// src/intelligence-service.ts
|