@ainyc/canonry 1.31.0 → 1.33.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/assets/index.html CHANGED
@@ -12,8 +12,8 @@
12
12
  <link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
13
13
  <link rel="apple-touch-icon" href="./apple-touch-icon.png" />
14
14
  <title>Canonry</title>
15
- <script type="module" crossorigin src="./assets/index-DmFB_uXa.js"></script>
16
- <link rel="stylesheet" crossorigin href="./assets/index-r6biprHB.css">
15
+ <script type="module" crossorigin src="./assets/index-BiIPqZJb.js"></script>
16
+ <link rel="stylesheet" crossorigin href="./assets/index-B5Cg2H9M.css">
17
17
  </head>
18
18
  <body>
19
19
  <div id="root"></div>
@@ -784,6 +784,7 @@ var runStatusSchema = z8.enum(["queued", "running", "completed", "partial", "fai
784
784
  var runKindSchema = z8.enum(["answer-visibility", "site-audit", "gsc-sync", "inspect-sitemap"]);
785
785
  var runTriggerSchema = z8.enum(["manual", "scheduled", "config-apply"]);
786
786
  var citationStateSchema = z8.enum(["cited", "not-cited"]);
787
+ var visibilityStateSchema = z8.enum(["visible", "not-visible"]);
787
788
  var computedTransitionSchema = z8.enum(["new", "cited", "lost", "emerging", "not-cited"]);
788
789
  var runDtoSchema = z8.object({
789
790
  id: z8.string(),
@@ -808,11 +809,14 @@ var querySnapshotDtoSchema = z8.object({
808
809
  keyword: z8.string().optional(),
809
810
  provider: providerNameSchema,
810
811
  citationState: citationStateSchema,
812
+ answerMentioned: z8.boolean().optional(),
813
+ visibilityState: visibilityStateSchema.optional(),
811
814
  transition: computedTransitionSchema.optional(),
812
815
  answerText: z8.string().nullable().optional(),
813
816
  citedDomains: z8.array(z8.string()).default([]),
814
817
  competitorOverlap: z8.array(z8.string()).default([]),
815
818
  recommendedCompetitors: z8.array(z8.string()).default([]),
819
+ matchedTerms: z8.array(z8.string()).default([]),
816
820
  groundingSources: z8.array(groundingSourceSchema).default([]),
817
821
  searchQueries: z8.array(z8.string()).default([]),
818
822
  model: z8.string().nullable().optional(),
@@ -1071,6 +1075,99 @@ var ga4TrafficSummaryDtoSchema = z11.object({
1071
1075
  lastSyncedAt: z11.string().nullable()
1072
1076
  });
1073
1077
 
1078
+ // ../contracts/src/answer-visibility.ts
1079
+ var GENERIC_TOKENS = /* @__PURE__ */ new Set([
1080
+ "agency",
1081
+ "app",
1082
+ "company",
1083
+ "corp",
1084
+ "group",
1085
+ "health",
1086
+ "inc",
1087
+ "llc",
1088
+ "online",
1089
+ "platform",
1090
+ "services",
1091
+ "site",
1092
+ "solutions",
1093
+ "software",
1094
+ "systems",
1095
+ "tech"
1096
+ ]);
1097
+ function extractAnswerMentions(answerText, displayName, domains) {
1098
+ if (!answerText) return { mentioned: false, matchedTerms: [] };
1099
+ const matchedTerms = [];
1100
+ const lowerAnswer = answerText.toLowerCase();
1101
+ for (const domain of domains) {
1102
+ const normalizedDomain = normalizeProjectDomain(domain);
1103
+ if (!normalizedDomain || !normalizedDomain.includes(".")) continue;
1104
+ if (domainMentioned(lowerAnswer, normalizedDomain)) {
1105
+ matchedTerms.push(normalizedDomain);
1106
+ }
1107
+ }
1108
+ const normalizedDisplayName = normalizeText(displayName);
1109
+ if (normalizedDisplayName && normalizeText(answerText).includes(normalizedDisplayName)) {
1110
+ matchedTerms.push(displayName);
1111
+ }
1112
+ const tokens = collectDistinctiveTokens(displayName, domains);
1113
+ let tokenMatches = 0;
1114
+ const matchedTokens = [];
1115
+ for (const token of tokens) {
1116
+ if (new RegExp(`\\b${escapeRegExp(token)}\\b`).test(lowerAnswer)) {
1117
+ tokenMatches++;
1118
+ matchedTokens.push(token);
1119
+ }
1120
+ }
1121
+ const tokenThresholdMet = tokens.length > 0 && (tokens.length === 1 && tokenMatches >= 1 || tokenMatches >= Math.min(2, tokens.length));
1122
+ if (tokenThresholdMet) {
1123
+ matchedTerms.push(...matchedTokens);
1124
+ }
1125
+ const unique = [...new Set(matchedTerms)];
1126
+ return { mentioned: unique.length > 0, matchedTerms: unique };
1127
+ }
1128
+ function determineAnswerMentioned(answerText, displayName, domains) {
1129
+ return extractAnswerMentions(answerText, displayName, domains).mentioned;
1130
+ }
1131
+ function visibilityStateFromAnswerMentioned(answerMentioned) {
1132
+ return answerMentioned ? "visible" : "not-visible";
1133
+ }
1134
+ function domainMentioned(lowerAnswer, normalizedDomain) {
1135
+ const escapedDomain = escapeRegExp(normalizedDomain.toLowerCase());
1136
+ const patterns = [
1137
+ new RegExp(`(^|[^a-z0-9-])${escapedDomain}($|[^a-z0-9-])`),
1138
+ new RegExp(`https?://(?:www\\.)?${escapedDomain}(?:[/:?#]|$)`),
1139
+ new RegExp(`www\\.${escapedDomain}(?:[/:?#]|$)`)
1140
+ ];
1141
+ return patterns.some((pattern) => pattern.test(lowerAnswer));
1142
+ }
1143
+ function collectDistinctiveTokens(displayName, domains) {
1144
+ const tokens = /* @__PURE__ */ new Set();
1145
+ for (const token of extractDistinctiveTokens(displayName)) {
1146
+ tokens.add(token);
1147
+ }
1148
+ for (const domain of domains) {
1149
+ const hostname = normalizeProjectDomain(domain).split("/")[0] ?? "";
1150
+ for (const label of hostname.split(".").filter(Boolean)) {
1151
+ const token = label.replace(/[^a-z0-9]/gi, "").toLowerCase();
1152
+ if (isDistinctiveToken(token)) tokens.add(token);
1153
+ }
1154
+ }
1155
+ return [...tokens];
1156
+ }
1157
+ function extractDistinctiveTokens(value) {
1158
+ return normalizeText(value).split(" ").filter(isDistinctiveToken);
1159
+ }
1160
+ function isDistinctiveToken(token) {
1161
+ if (token.length < 4) return false;
1162
+ return !GENERIC_TOKENS.has(token);
1163
+ }
1164
+ function normalizeText(value) {
1165
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
1166
+ }
1167
+ function escapeRegExp(value) {
1168
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1169
+ }
1170
+
1074
1171
  // ../api-routes/src/auth.ts
1075
1172
  import crypto2 from "crypto";
1076
1173
  import { eq } from "drizzle-orm";
@@ -1165,6 +1262,7 @@ var querySnapshots = sqliteTable("query_snapshots", {
1165
1262
  provider: text("provider").notNull().default("gemini"),
1166
1263
  model: text("model"),
1167
1264
  citationState: text("citation_state").notNull(),
1265
+ answerMentioned: integer("answer_mentioned", { mode: "boolean" }),
1168
1266
  answerText: text("answer_text"),
1169
1267
  citedDomains: text("cited_domains").notNull().default("[]"),
1170
1268
  competitorOverlap: text("competitor_overlap").notNull().default("[]"),
@@ -1731,7 +1829,9 @@ var MIGRATIONS = [
1731
1829
  )`,
1732
1830
  `CREATE INDEX IF NOT EXISTS idx_ga_ai_ref_project_date ON ga_ai_referrals(project_id, date)`,
1733
1831
  `CREATE INDEX IF NOT EXISTS idx_ga_ai_ref_source ON ga_ai_referrals(source)`,
1734
- `CREATE UNIQUE INDEX IF NOT EXISTS idx_ga_ai_ref_unique ON ga_ai_referrals(project_id, date, source, medium)`
1832
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_ga_ai_ref_unique ON ga_ai_referrals(project_id, date, source, medium)`,
1833
+ // v18: Answer-level visibility derived from answer text
1834
+ `ALTER TABLE query_snapshots ADD COLUMN answer_mentioned INTEGER`
1735
1835
  ];
1736
1836
  function migrate(db) {
1737
1837
  const statements = MIGRATION_SQL.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
@@ -1840,6 +1940,33 @@ function writeAuditLog(db, entry) {
1840
1940
  createdAt: now
1841
1941
  }).run();
1842
1942
  }
1943
+ function resolveSnapshotMentionResult(snapshot, project) {
1944
+ if (snapshot.answerText) {
1945
+ const domains = effectiveDomains({
1946
+ canonicalDomain: project.canonicalDomain,
1947
+ ownedDomains: normalizeOwnedDomains(project.ownedDomains)
1948
+ });
1949
+ return extractAnswerMentions(snapshot.answerText, project.displayName, domains);
1950
+ }
1951
+ if (typeof snapshot.answerMentioned === "boolean") {
1952
+ return { mentioned: snapshot.answerMentioned, matchedTerms: [] };
1953
+ }
1954
+ return { mentioned: false, matchedTerms: [] };
1955
+ }
1956
+ function resolveSnapshotAnswerMentioned(snapshot, project) {
1957
+ return resolveSnapshotMentionResult(snapshot, project).mentioned;
1958
+ }
1959
+ function resolveSnapshotVisibilityState(snapshot, project) {
1960
+ return visibilityStateFromAnswerMentioned(resolveSnapshotMentionResult(snapshot, project).mentioned);
1961
+ }
1962
+ function resolveSnapshotMatchedTerms(snapshot, project) {
1963
+ return resolveSnapshotMentionResult(snapshot, project).matchedTerms;
1964
+ }
1965
+ function normalizeOwnedDomains(value) {
1966
+ if (Array.isArray(value)) return value.filter((item) => typeof item === "string");
1967
+ const parsed = parseJsonColumn(typeof value === "string" ? value : null, []);
1968
+ return parsed.filter((item) => typeof item === "string");
1969
+ }
1843
1970
 
1844
1971
  // ../api-routes/src/projects.ts
1845
1972
  async function projectRoutes(app, opts) {
@@ -2498,6 +2625,11 @@ async function runRoutes(app, opts) {
2498
2625
  app.get("/runs/:id", async (request, reply) => {
2499
2626
  const run = app.db.select().from(runs).where(eq7(runs.id, request.params.id)).get();
2500
2627
  if (!run) throw notFound("Run", request.params.id);
2628
+ const project = app.db.select({
2629
+ displayName: projects.displayName,
2630
+ canonicalDomain: projects.canonicalDomain,
2631
+ ownedDomains: projects.ownedDomains
2632
+ }).from(projects).where(eq7(projects.id, run.projectId)).get();
2501
2633
  const snapshots = app.db.select({
2502
2634
  id: querySnapshots.id,
2503
2635
  runId: querySnapshots.runId,
@@ -2506,6 +2638,7 @@ async function runRoutes(app, opts) {
2506
2638
  provider: querySnapshots.provider,
2507
2639
  model: querySnapshots.model,
2508
2640
  citationState: querySnapshots.citationState,
2641
+ answerMentioned: querySnapshots.answerMentioned,
2509
2642
  answerText: querySnapshots.answerText,
2510
2643
  citedDomains: querySnapshots.citedDomains,
2511
2644
  competitorOverlap: querySnapshots.competitorOverlap,
@@ -2518,6 +2651,7 @@ async function runRoutes(app, opts) {
2518
2651
  ...formatRun(run),
2519
2652
  snapshots: snapshots.map((s) => {
2520
2653
  const rawParsed = parseSnapshotRawResponse(s.rawResponse);
2654
+ const answerMentioned = project ? resolveSnapshotAnswerMentioned(s, project) : s.answerMentioned ?? false;
2521
2655
  return {
2522
2656
  id: s.id,
2523
2657
  runId: s.runId,
@@ -2525,10 +2659,13 @@ async function runRoutes(app, opts) {
2525
2659
  keyword: s.keyword,
2526
2660
  provider: s.provider,
2527
2661
  citationState: s.citationState,
2662
+ answerMentioned,
2663
+ visibilityState: project ? resolveSnapshotVisibilityState(s, project) : answerMentioned ? "visible" : "not-visible",
2528
2664
  answerText: s.answerText,
2529
2665
  citedDomains: parseJsonColumn(s.citedDomains, []),
2530
2666
  competitorOverlap: parseJsonColumn(s.competitorOverlap, []),
2531
2667
  recommendedCompetitors: parseJsonColumn(s.recommendedCompetitors, []),
2668
+ matchedTerms: project ? resolveSnapshotMatchedTerms(s, project) : [],
2532
2669
  model: s.model ?? rawParsed.model,
2533
2670
  location: s.location,
2534
2671
  groundingSources: rawParsed.groundingSources,
@@ -3115,6 +3252,7 @@ async function historyRoutes(app) {
3115
3252
  provider: querySnapshots.provider,
3116
3253
  model: querySnapshots.model,
3117
3254
  citationState: querySnapshots.citationState,
3255
+ answerMentioned: querySnapshots.answerMentioned,
3118
3256
  answerText: querySnapshots.answerText,
3119
3257
  citedDomains: querySnapshots.citedDomains,
3120
3258
  competitorOverlap: querySnapshots.competitorOverlap,
@@ -3135,6 +3273,8 @@ async function historyRoutes(app) {
3135
3273
  provider: s.provider,
3136
3274
  model: s.model,
3137
3275
  citationState: s.citationState,
3276
+ answerMentioned: resolveSnapshotAnswerMentioned(s, project),
3277
+ visibilityState: resolveSnapshotVisibilityState(s, project),
3138
3278
  answerText: s.answerText,
3139
3279
  citedDomains: parseJsonColumn(s.citedDomains, []),
3140
3280
  competitorOverlap: parseJsonColumn(s.competitorOverlap, []),
@@ -3155,12 +3295,16 @@ async function historyRoutes(app) {
3155
3295
  const runIds = new Set(projectRuns.map((r) => r.id));
3156
3296
  const rawSnapshots = app.db.select().from(querySnapshots).where(inArray(querySnapshots.runId, [...runIds])).all();
3157
3297
  const timelineLocationFilter = request.query.location;
3158
- const allSnapshots = timelineLocationFilter !== void 0 ? rawSnapshots.filter((s) => s.location === (timelineLocationFilter || null)) : rawSnapshots;
3298
+ const filteredSnapshots = timelineLocationFilter !== void 0 ? rawSnapshots.filter((s) => s.location === (timelineLocationFilter || null)) : rawSnapshots;
3299
+ const allSnapshots = filteredSnapshots.map((snapshot) => ({
3300
+ ...snapshot,
3301
+ answerMentioned: resolveSnapshotAnswerMentioned(snapshot, project)
3302
+ }));
3159
3303
  const deduped = /* @__PURE__ */ new Map();
3160
3304
  for (const snap of allSnapshots) {
3161
3305
  const key = `${snap.runId}:${snap.keywordId}`;
3162
3306
  const existing = deduped.get(key);
3163
- if (!existing || snap.citationState === "cited") {
3307
+ if (!existing || !existing.answerMentioned && snap.answerMentioned || existing.answerMentioned === snap.answerMentioned && snap.citationState === "cited") {
3164
3308
  deduped.set(key, snap);
3165
3309
  }
3166
3310
  }
@@ -3183,8 +3327,10 @@ async function historyRoutes(app) {
3183
3327
  return snaps.map((snap, idx) => {
3184
3328
  const run = projectRuns.find((r) => r.id === snap.runId);
3185
3329
  let transition = snap.citationState === "cited" ? "cited" : "not-cited";
3330
+ let visibilityTransition = snap.answerMentioned ? "visible" : "not-visible";
3186
3331
  if (idx === 0) {
3187
3332
  transition = "new";
3333
+ visibilityTransition = "new";
3188
3334
  } else {
3189
3335
  const prev = snaps[idx - 1];
3190
3336
  if (prev.citationState === "not-cited" && snap.citationState === "cited") {
@@ -3192,12 +3338,20 @@ async function historyRoutes(app) {
3192
3338
  } else if (prev.citationState === "cited" && snap.citationState === "not-cited") {
3193
3339
  transition = "lost";
3194
3340
  }
3341
+ if (!prev.answerMentioned && snap.answerMentioned) {
3342
+ visibilityTransition = "emerging";
3343
+ } else if (prev.answerMentioned && !snap.answerMentioned) {
3344
+ visibilityTransition = "lost";
3345
+ }
3195
3346
  }
3196
3347
  return {
3197
3348
  runId: snap.runId,
3198
3349
  createdAt: run?.createdAt ?? snap.createdAt,
3199
3350
  citationState: snap.citationState,
3200
- transition
3351
+ transition,
3352
+ answerMentioned: snap.answerMentioned,
3353
+ visibilityState: snap.answerMentioned ? "visible" : "not-visible",
3354
+ visibilityTransition
3201
3355
  };
3202
3356
  });
3203
3357
  }
@@ -3228,7 +3382,7 @@ async function historyRoutes(app) {
3228
3382
  return reply.send(timeline);
3229
3383
  });
3230
3384
  app.get("/projects/:name/snapshots/diff", async (request, reply) => {
3231
- resolveProject(app.db, request.params.name);
3385
+ const project = resolveProject(app.db, request.params.name);
3232
3386
  const { run1, run2 } = request.query;
3233
3387
  if (!run1 || !run2) {
3234
3388
  throw validationError("Both run1 and run2 query params are required");
@@ -3236,22 +3390,40 @@ async function historyRoutes(app) {
3236
3390
  const snaps1 = app.db.select({
3237
3391
  keywordId: querySnapshots.keywordId,
3238
3392
  keyword: keywords.keyword,
3239
- citationState: querySnapshots.citationState
3393
+ citationState: querySnapshots.citationState,
3394
+ answerMentioned: querySnapshots.answerMentioned,
3395
+ answerText: querySnapshots.answerText
3240
3396
  }).from(querySnapshots).leftJoin(keywords, eq9(querySnapshots.keywordId, keywords.id)).where(eq9(querySnapshots.runId, run1)).all();
3241
3397
  const snaps2 = app.db.select({
3242
3398
  keywordId: querySnapshots.keywordId,
3243
3399
  keyword: keywords.keyword,
3244
- citationState: querySnapshots.citationState
3400
+ citationState: querySnapshots.citationState,
3401
+ answerMentioned: querySnapshots.answerMentioned,
3402
+ answerText: querySnapshots.answerText
3245
3403
  }).from(querySnapshots).leftJoin(keywords, eq9(querySnapshots.keywordId, keywords.id)).where(eq9(querySnapshots.runId, run2)).all();
3246
3404
  const map1 = /* @__PURE__ */ new Map();
3247
3405
  for (const s of snaps1) {
3406
+ const resolved = {
3407
+ ...s,
3408
+ resolvedAnswerMentioned: resolveSnapshotAnswerMentioned(s, project),
3409
+ resolvedVisibilityState: resolveSnapshotVisibilityState(s, project)
3410
+ };
3248
3411
  const existing = map1.get(s.keywordId);
3249
- if (!existing || s.citationState === "cited") map1.set(s.keywordId, s);
3412
+ if (!existing || !existing.resolvedAnswerMentioned && resolved.resolvedAnswerMentioned || existing.resolvedAnswerMentioned === resolved.resolvedAnswerMentioned && resolved.citationState === "cited") {
3413
+ map1.set(s.keywordId, resolved);
3414
+ }
3250
3415
  }
3251
3416
  const map2 = /* @__PURE__ */ new Map();
3252
3417
  for (const s of snaps2) {
3418
+ const resolved = {
3419
+ ...s,
3420
+ resolvedAnswerMentioned: resolveSnapshotAnswerMentioned(s, project),
3421
+ resolvedVisibilityState: resolveSnapshotVisibilityState(s, project)
3422
+ };
3253
3423
  const existing = map2.get(s.keywordId);
3254
- if (!existing || s.citationState === "cited") map2.set(s.keywordId, s);
3424
+ if (!existing || !existing.resolvedAnswerMentioned && resolved.resolvedAnswerMentioned || existing.resolvedAnswerMentioned === resolved.resolvedAnswerMentioned && resolved.citationState === "cited") {
3425
+ map2.set(s.keywordId, resolved);
3426
+ }
3255
3427
  }
3256
3428
  const allKeywordIds = /* @__PURE__ */ new Set([...map1.keys(), ...map2.keys()]);
3257
3429
  const diff = [...allKeywordIds].map((kwId) => {
@@ -3262,7 +3434,12 @@ async function historyRoutes(app) {
3262
3434
  keyword: s2?.keyword ?? s1?.keyword ?? null,
3263
3435
  run1State: s1?.citationState ?? null,
3264
3436
  run2State: s2?.citationState ?? null,
3265
- changed: (s1?.citationState ?? null) !== (s2?.citationState ?? null)
3437
+ run1AnswerMentioned: s1?.resolvedAnswerMentioned ?? null,
3438
+ run2AnswerMentioned: s2?.resolvedAnswerMentioned ?? null,
3439
+ run1VisibilityState: s1?.resolvedVisibilityState ?? null,
3440
+ run2VisibilityState: s2?.resolvedVisibilityState ?? null,
3441
+ changed: (s1?.citationState ?? null) !== (s2?.citationState ?? null),
3442
+ visibilityChanged: (s1?.resolvedAnswerMentioned ?? null) !== (s2?.resolvedAnswerMentioned ?? null)
3266
3443
  };
3267
3444
  });
3268
3445
  return reply.send({ run1, run2, diff });
@@ -8919,12 +9096,12 @@ var CANONRY_SCHEMA_START = "<!-- canonry:schema:start -->";
8919
9096
  var CANONRY_SCHEMA_END = "<!-- canonry:schema:end -->";
8920
9097
  function stripCanonrySchema(content) {
8921
9098
  const regex = new RegExp(
8922
- `${escapeRegExp(CANONRY_SCHEMA_START)}[\\s\\S]*?${escapeRegExp(CANONRY_SCHEMA_END)}`,
9099
+ `${escapeRegExp2(CANONRY_SCHEMA_START)}[\\s\\S]*?${escapeRegExp2(CANONRY_SCHEMA_END)}`,
8923
9100
  "g"
8924
9101
  );
8925
9102
  return content.replace(regex, "").replace(/\n{3,}/g, "\n\n").trim();
8926
9103
  }
8927
- function escapeRegExp(str) {
9104
+ function escapeRegExp2(str) {
8928
9105
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8929
9106
  }
8930
9107
  function injectCanonrySchema(content, schemas) {
@@ -9031,7 +9208,7 @@ async function getSchemaStatus(connection, env) {
9031
9208
  const thirdPartySchemas = [];
9032
9209
  if (hasCanonryMarker) {
9033
9210
  const markerRegex = new RegExp(
9034
- `${escapeRegExp(CANONRY_SCHEMA_START)}([\\s\\S]*?)${escapeRegExp(CANONRY_SCHEMA_END)}`
9211
+ `${escapeRegExp2(CANONRY_SCHEMA_START)}([\\s\\S]*?)${escapeRegExp2(CANONRY_SCHEMA_END)}`
9035
9212
  );
9036
9213
  const match = markerRegex.exec(rawContent);
9037
9214
  if (match?.[1]) {
@@ -11124,7 +11301,7 @@ var CDPConnectionManager = class {
11124
11301
  await sleep(1500);
11125
11302
  } catch (err) {
11126
11303
  throw new CDPProviderError(
11127
- "CDP_CONNECTION_REFUSED",
11304
+ "CDP_TARGET_SELECTOR_FAILED",
11128
11305
  `Failed to navigate to ${target.newConversationUrl}: ${err instanceof Error ? err.message : String(err)}`
11129
11306
  );
11130
11307
  }
@@ -11613,10 +11790,11 @@ async function healthcheck5(config) {
11613
11790
  async function executeTrackedQuery5(input) {
11614
11791
  const model = input.config.model ?? DEFAULT_MODEL5;
11615
11792
  const client = new OpenAI3({ apiKey: input.config.apiKey, baseURL: BASE_URL });
11793
+ const prompt = buildPrompt4(input.keyword, input.location);
11616
11794
  const response = await client.chat.completions.create({
11617
11795
  model,
11618
11796
  messages: [
11619
- { role: "user", content: input.keyword }
11797
+ { role: "user", content: prompt }
11620
11798
  ]
11621
11799
  });
11622
11800
  const rawResponse = responseToRecord4(response);
@@ -11644,6 +11822,12 @@ function normalizeResult6(raw) {
11644
11822
  searchQueries: raw.searchQueries
11645
11823
  };
11646
11824
  }
11825
+ function buildPrompt4(keyword, location) {
11826
+ if (location) {
11827
+ return `${keyword} (searching from ${location.city}, ${location.region}, ${location.country})`;
11828
+ }
11829
+ return keyword;
11830
+ }
11647
11831
  function extractCitations(rawResponse) {
11648
11832
  if (Array.isArray(rawResponse.citations)) {
11649
11833
  return rawResponse.citations.filter((c) => typeof c === "string");
@@ -12176,6 +12360,11 @@ var JobRunner = class {
12176
12360
  const normalized = adapter.normalizeResult(raw);
12177
12361
  log.info("query.result", { runId, provider: providerName, keyword: kw.keyword, citedDomains: normalized.citedDomains, groundingSources: normalized.groundingSources.map((s) => s.uri), matchDomains: allDomains });
12178
12362
  const citationState = determineCitationState(normalized, allDomains);
12363
+ const answerMentioned = determineAnswerMentioned(
12364
+ normalized.answerText,
12365
+ project.displayName,
12366
+ allDomains
12367
+ );
12179
12368
  const overlap = computeCompetitorOverlap(normalized, competitorDomains);
12180
12369
  const extractedCompetitors = extractRecommendedCompetitors(
12181
12370
  normalized.answerText,
@@ -12198,6 +12387,7 @@ var JobRunner = class {
12198
12387
  provider: providerName,
12199
12388
  model: raw.model,
12200
12389
  citationState,
12390
+ answerMentioned,
12201
12391
  answerText: normalized.answerText,
12202
12392
  citedDomains: JSON.stringify(normalized.citedDomains),
12203
12393
  competitorOverlap: JSON.stringify(overlap),
@@ -12220,6 +12410,7 @@ var JobRunner = class {
12220
12410
  provider: providerName,
12221
12411
  model: raw.model,
12222
12412
  citationState,
12413
+ answerMentioned,
12223
12414
  answerText: normalized.answerText,
12224
12415
  citedDomains: JSON.stringify(normalized.citedDomains),
12225
12416
  competitorOverlap: JSON.stringify(overlap),
@@ -12235,7 +12426,7 @@ var JobRunner = class {
12235
12426
  }).run();
12236
12427
  }
12237
12428
  totalSnapshotsInserted++;
12238
- log.info("query.citation", { runId, provider: providerName, keyword: kw.keyword, citationState });
12429
+ log.info("query.citation", { runId, provider: providerName, keyword: kw.keyword, citationState, answerMentioned });
12239
12430
  });
12240
12431
  } catch (err) {
12241
12432
  if (err instanceof RunCancelledError) {
@@ -13561,11 +13752,12 @@ var SnapshotService = class {
13561
13752
  manualCompetitors: ctx.manualCompetitors,
13562
13753
  targetDomain: ctx.domain
13563
13754
  });
13755
+ const answerVisibilityDomains = [ctx.domain];
13564
13756
  return {
13565
13757
  provider: provider.adapter.name,
13566
13758
  displayName: provider.adapter.displayName,
13567
13759
  model: raw.model,
13568
- mentioned: mentionsTargetCompany(normalized.answerText, ctx.companyName, ctx.domain),
13760
+ mentioned: determineAnswerMentioned(normalized.answerText, ctx.companyName, answerVisibilityDomains),
13569
13761
  cited: citesTargetDomain(normalized.citedDomains, normalized.groundingSources, ctx.domain),
13570
13762
  describedAccurately: "unknown",
13571
13763
  accuracyNotes: null,
@@ -13822,26 +14014,6 @@ function buildFallbackRecommendedActions(audit) {
13822
14014
  ];
13823
14015
  return uniqueStrings([...weakestFactors, ...defaults]).slice(0, 4);
13824
14016
  }
13825
- function mentionsTargetCompany(answerText, companyName, domain) {
13826
- const haystack = normalizeText(answerText);
13827
- if (!haystack) return false;
13828
- const fullName = normalizeText(companyName);
13829
- if (fullName && haystack.includes(fullName)) {
13830
- return true;
13831
- }
13832
- const targetTokens = uniqueStrings([
13833
- ...extractDistinctiveTokens(companyName),
13834
- ...extractDistinctiveTokens(extractHostname2(domain).split(".")[0] ?? "")
13835
- ]);
13836
- if (targetTokens.length === 0) return false;
13837
- let matches = 0;
13838
- for (const token of targetTokens) {
13839
- if (new RegExp(`\\b${escapeRegExp2(token)}\\b`, "i").test(answerText)) {
13840
- matches++;
13841
- }
13842
- }
13843
- return matches >= Math.min(2, targetTokens.length) || matches >= 1 && targetTokens.length === 1;
13844
- }
13845
14017
  function citesTargetDomain(citedDomains, groundingSources, targetDomain) {
13846
14018
  const normalizedTarget = extractHostname2(targetDomain);
13847
14019
  for (const domain of citedDomains) {
@@ -13912,9 +14084,6 @@ function parseJsonObject(input) {
13912
14084
  const json = start >= 0 && end >= start ? candidate.slice(start, end + 1) : candidate;
13913
14085
  return JSON.parse(json);
13914
14086
  }
13915
- function normalizeText(value) {
13916
- return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
13917
- }
13918
14087
  function normalizeStringList(values) {
13919
14088
  const items = values.flatMap((value) => value.split(","));
13920
14089
  return uniqueStrings(
@@ -13945,9 +14114,6 @@ function domainMatches2(candidate, target) {
13945
14114
  const normalizedTarget = normalizeDomain(target);
13946
14115
  return normalizedCandidate === normalizedTarget || normalizedCandidate.endsWith(`.${normalizedTarget}`);
13947
14116
  }
13948
- function extractDistinctiveTokens(value) {
13949
- return normalizeText(value).split(" ").filter((token) => token.length >= 4).filter((token) => !["llc", "inc", "corp", "company", "group", "services", "solutions", "agency"].includes(token));
13950
- }
13951
14117
  function isDomainLike(value) {
13952
14118
  const normalized = normalizeDomain(value);
13953
14119
  return normalized.includes(".") && !normalized.includes(" ");
@@ -13956,9 +14122,6 @@ function clipText(value, length) {
13956
14122
  if (value.length <= length) return value;
13957
14123
  return `${value.slice(0, length - 3)}...`;
13958
14124
  }
13959
- function escapeRegExp2(value) {
13960
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13961
- }
13962
14125
 
13963
14126
  // src/server.ts
13964
14127
  var _require2 = createRequire2(import.meta.url);
@@ -14725,14 +14888,19 @@ export {
14725
14888
  isFirstRun,
14726
14889
  showFirstRunNotice,
14727
14890
  trackEvent,
14891
+ projects,
14892
+ runs,
14893
+ querySnapshots,
14894
+ apiKeys,
14895
+ createClient,
14896
+ parseJsonColumn,
14897
+ migrate,
14728
14898
  providerQuotaPolicySchema,
14729
14899
  resolveProviderInput,
14730
14900
  notificationEventSchema,
14731
14901
  effectiveDomains,
14902
+ determineAnswerMentioned,
14732
14903
  setGoogleAuthConfig,
14733
14904
  formatAuditFactorScore,
14734
- apiKeys,
14735
- createClient,
14736
- migrate,
14737
14905
  createServer
14738
14906
  };