@ainyc/canonry 3.2.4 → 3.2.7

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-B8epngdo.js"></script>
16
- <link rel="stylesheet" crossorigin href="./assets/index-CQGbgHZY.css">
15
+ <script type="module" crossorigin src="./assets/index-BJz9S5-O.js"></script>
16
+ <link rel="stylesheet" crossorigin href="./assets/index-JG7aBJrz.css">
17
17
  </head>
18
18
  <body>
19
19
  <div id="root"></div>
@@ -19,6 +19,7 @@ import {
19
19
  authInvalid,
20
20
  authRequired,
21
21
  brandKeyFromText,
22
+ brandLabelFromDomain,
22
23
  buildRunErrorFromMessages,
23
24
  canonryMcpTools,
24
25
  categorizeSource,
@@ -49,6 +50,7 @@ import {
49
50
  projectConfigSchema,
50
51
  projectUpsertRequestSchema,
51
52
  providerError,
53
+ registrableDomain,
52
54
  runInProgress,
53
55
  runNotCancellable,
54
56
  runTriggerRequestSchema,
@@ -62,7 +64,7 @@ import {
62
64
  visibilityStateFromAnswerMentioned,
63
65
  windowCutoff,
64
66
  wordpressEnvSchema
65
- } from "./chunk-PS7JRDL3.js";
67
+ } from "./chunk-GY4MNUTI.js";
66
68
  import {
67
69
  IntelligenceService,
68
70
  agentMemory,
@@ -100,7 +102,7 @@ import {
100
102
  runs,
101
103
  schedules,
102
104
  usageCounters
103
- } from "./chunk-UQH5SKM2.js";
105
+ } from "./chunk-ZCPZOVVE.js";
104
106
 
105
107
  // src/telemetry.ts
106
108
  import crypto from "crypto";
@@ -721,6 +723,24 @@ async function keywordRoutes(app, opts) {
721
723
  // ../api-routes/src/competitors.ts
722
724
  import crypto6 from "crypto";
723
725
  import { eq as eq5 } from "drizzle-orm";
726
+ function normalizeCompetitor(domain) {
727
+ const reg = registrableDomain(domain);
728
+ if (reg) return reg;
729
+ return normalizeProjectDomain(domain);
730
+ }
731
+ function normalizeCompetitorList(domains) {
732
+ const seen = /* @__PURE__ */ new Set();
733
+ const result = [];
734
+ for (const raw of domains) {
735
+ const trimmed = raw?.trim();
736
+ if (!trimmed) continue;
737
+ const normalized = normalizeCompetitor(trimmed);
738
+ if (!normalized || seen.has(normalized)) continue;
739
+ seen.add(normalized);
740
+ result.push(normalized);
741
+ }
742
+ return result;
743
+ }
724
744
  async function competitorRoutes(app) {
725
745
  app.get("/projects/:name/competitors", async (request, reply) => {
726
746
  const project = resolveProject(app.db, request.params.name);
@@ -734,9 +754,10 @@ async function competitorRoutes(app) {
734
754
  throw validationError('Body must contain a "competitors" array');
735
755
  }
736
756
  const now = (/* @__PURE__ */ new Date()).toISOString();
757
+ const normalizedCompetitors = normalizeCompetitorList(body.competitors);
737
758
  app.db.transaction((tx) => {
738
759
  tx.delete(competitors).where(eq5(competitors.projectId, project.id)).run();
739
- for (const domain of body.competitors) {
760
+ for (const domain of normalizedCompetitors) {
740
761
  tx.insert(competitors).values({
741
762
  id: crypto6.randomUUID(),
742
763
  projectId: project.id,
@@ -749,7 +770,7 @@ async function competitorRoutes(app) {
749
770
  actor: "api",
750
771
  action: "competitors.replaced",
751
772
  entityType: "competitor",
752
- diff: { competitors: body.competitors }
773
+ diff: { competitors: normalizedCompetitors }
753
774
  });
754
775
  });
755
776
  const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
@@ -759,7 +780,7 @@ async function competitorRoutes(app) {
759
780
  const project = resolveProject(app.db, request.params.name);
760
781
  const body = parseCompetitorBatch(request.body);
761
782
  const now = (/* @__PURE__ */ new Date()).toISOString();
762
- const requested = uniqueStrings(body.competitors);
783
+ const requested = normalizeCompetitorList(body.competitors);
763
784
  app.db.transaction((tx) => {
764
785
  const existing = tx.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
765
786
  const existingSet = new Set(existing.map((c) => c.domain));
@@ -789,7 +810,7 @@ async function competitorRoutes(app) {
789
810
  app.delete("/projects/:name/competitors", async (request, reply) => {
790
811
  const project = resolveProject(app.db, request.params.name);
791
812
  const body = parseCompetitorBatch(request.body);
792
- const requested = new Set(uniqueStrings(body.competitors));
813
+ const requested = new Set(normalizeCompetitorList(body.competitors));
793
814
  app.db.transaction((tx) => {
794
815
  const existing = tx.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
795
816
  const rowsToDelete = existing.filter((c) => requested.has(c.domain));
@@ -819,16 +840,6 @@ function parseCompetitorBatch(value) {
819
840
  }))
820
841
  });
821
842
  }
822
- function uniqueStrings(values) {
823
- const seen = /* @__PURE__ */ new Set();
824
- const result = [];
825
- for (const value of values) {
826
- if (seen.has(value)) continue;
827
- seen.add(value);
828
- result.push(value);
829
- }
830
- return result;
831
- }
832
843
 
833
844
  // ../api-routes/src/runs.ts
834
845
  import crypto8 from "crypto";
@@ -1550,7 +1561,8 @@ async function applyRoutes(app, opts) {
1550
1561
  diff: { keywords: config.spec.keywords }
1551
1562
  });
1552
1563
  tx.delete(competitors).where(eq8(competitors.projectId, projectId)).run();
1553
- for (const domain of config.spec.competitors) {
1564
+ const normalizedCompetitors = normalizeCompetitorList2(config.spec.competitors);
1565
+ for (const domain of normalizedCompetitors) {
1554
1566
  tx.insert(competitors).values({
1555
1567
  id: crypto10.randomUUID(),
1556
1568
  projectId,
@@ -1563,7 +1575,7 @@ async function applyRoutes(app, opts) {
1563
1575
  actor: "api",
1564
1576
  action: "competitors.replaced",
1565
1577
  entityType: "competitor",
1566
- diff: { competitors: config.spec.competitors }
1578
+ diff: { competitors: normalizedCompetitors }
1567
1579
  });
1568
1580
  if (resolvedSchedule) {
1569
1581
  const existingSched = tx.select().from(schedules).where(eq8(schedules.projectId, projectId)).get();
@@ -1651,6 +1663,19 @@ async function applyRoutes(app, opts) {
1651
1663
  });
1652
1664
  });
1653
1665
  }
1666
+ function normalizeCompetitorList2(domains) {
1667
+ const seen = /* @__PURE__ */ new Set();
1668
+ const result = [];
1669
+ for (const raw of domains) {
1670
+ const trimmed = raw?.trim();
1671
+ if (!trimmed) continue;
1672
+ const normalized = registrableDomain(trimmed) || normalizeProjectDomain(trimmed);
1673
+ if (!normalized || seen.has(normalized)) continue;
1674
+ seen.add(normalized);
1675
+ result.push(normalized);
1676
+ }
1677
+ return result;
1678
+ }
1654
1679
 
1655
1680
  // ../api-routes/src/history.ts
1656
1681
  import { eq as eq9, desc as desc2, inArray } from "drizzle-orm";
@@ -15134,14 +15159,17 @@ function computeCompetitorOverlap(normalized, competitorDomains) {
15134
15159
  if (lowerAnswer.includes(cd.toLowerCase())) {
15135
15160
  overlapSet.add(cd);
15136
15161
  }
15137
- const brand = cd.split(".")[0];
15138
- if (brand && brand.length >= 4 && new RegExp(`\\b${brand}\\b`, "i").test(lowerAnswer)) {
15162
+ const brand = brandLabelFromDomain(cd);
15163
+ if (brand.length >= 4 && new RegExp(`\\b${escapeRegExp5(brand)}\\b`, "i").test(lowerAnswer)) {
15139
15164
  overlapSet.add(cd);
15140
15165
  }
15141
15166
  }
15142
15167
  }
15143
15168
  return [...overlapSet];
15144
15169
  }
15170
+ function escapeRegExp5(value) {
15171
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15172
+ }
15145
15173
  function extractRecommendedCompetitors(answerText, ownDomains, citedDomains, competitorDomains) {
15146
15174
  if (!answerText || answerText.length < 20) return [];
15147
15175
  const ownBrandKeys = new Set(
@@ -15211,15 +15239,17 @@ function cleanCandidateName(candidate) {
15211
15239
  return candidate.replace(/^[\s"'`]+|[\s"'`.,:;!?]+$/g, "").replace(/\s+/g, " ").trim();
15212
15240
  }
15213
15241
  function collectBrandKeysFromDomain(domain) {
15214
- const hostname = normalizeProjectDomain(domain).split("/")[0] ?? "";
15215
- const labels = hostname.split(".").filter(Boolean);
15216
- const keys = /* @__PURE__ */ new Set();
15217
- const hostnameKey = hostname.replace(/[^a-z0-9]/gi, "").toLowerCase();
15218
- if (hostnameKey.length >= 4) keys.add(hostnameKey);
15219
- for (const label of labels) {
15220
- const key = label.replace(/[^a-z0-9]/gi, "").toLowerCase();
15221
- if (key.length >= 4) keys.add(key);
15242
+ const reg = registrableDomain(domain);
15243
+ if (!reg) {
15244
+ const hostname = normalizeProjectDomain(domain).split("/")[0] ?? "";
15245
+ const fallback = hostname.replace(/[^a-z0-9]/gi, "").toLowerCase();
15246
+ return fallback.length >= 4 ? [fallback] : [];
15222
15247
  }
15248
+ const keys = /* @__PURE__ */ new Set();
15249
+ const fullKey = reg.replace(/[^a-z0-9]/gi, "").toLowerCase();
15250
+ if (fullKey.length >= 4) keys.add(fullKey);
15251
+ const brand = brandLabelFromDomain(reg).replace(/[^a-z0-9]/gi, "").toLowerCase();
15252
+ if (brand.length >= 4) keys.add(brand);
15223
15253
  return [...keys];
15224
15254
  }
15225
15255
  function matchesBrandKey(candidateKey, brandKeys) {
@@ -18398,7 +18428,7 @@ var SnapshotService = class {
18398
18428
  });
18399
18429
  const enrichedResults = applyBatchAssessment(queryResults, batchAssessment);
18400
18430
  const summary = buildSnapshotSummary(companyName, profile.phrases, providers, enrichedResults, audit, batchAssessment);
18401
- const reportCompetitors = uniqueStrings2([
18431
+ const reportCompetitors = uniqueStrings([
18402
18432
  ...manualCompetitors,
18403
18433
  ...summary.topCompetitors.map((entry) => entry.name)
18404
18434
  ]);
@@ -18451,8 +18481,8 @@ var SnapshotService = class {
18451
18481
  return {
18452
18482
  industry: parsed.industry?.trim() || "Unknown",
18453
18483
  summary: parsed.summary?.trim() || ctx.audit.summary,
18454
- services: uniqueStrings2(parsed.services ?? []).slice(0, 6),
18455
- categoryTerms: uniqueStrings2(parsed.categoryTerms ?? []).slice(0, 8),
18484
+ services: uniqueStrings(parsed.services ?? []).slice(0, 6),
18485
+ categoryTerms: uniqueStrings(parsed.categoryTerms ?? []).slice(0, 8),
18456
18486
  phrases: parsedPhrases
18457
18487
  };
18458
18488
  } catch (err) {
@@ -18520,9 +18550,9 @@ var SnapshotService = class {
18520
18550
  accuracyNotes: null,
18521
18551
  incorrectClaims: [],
18522
18552
  recommendedCompetitors: preliminaryCompetitors,
18523
- citedDomains: uniqueStrings2(normalized.citedDomains),
18553
+ citedDomains: uniqueStrings(normalized.citedDomains),
18524
18554
  groundingSources: normalized.groundingSources,
18525
- searchQueries: uniqueStrings2(normalized.searchQueries),
18555
+ searchQueries: uniqueStrings(normalized.searchQueries),
18526
18556
  answerText: normalized.answerText,
18527
18557
  error: null
18528
18558
  };
@@ -18588,14 +18618,14 @@ var SnapshotService = class {
18588
18618
  mentioned: assessment.mentioned,
18589
18619
  describedAccurately: assessment.describedAccurately,
18590
18620
  accuracyNotes: assessment.accuracyNotes ?? null,
18591
- incorrectClaims: uniqueStrings2(assessment.incorrectClaims ?? []).slice(0, 5),
18621
+ incorrectClaims: uniqueStrings(assessment.incorrectClaims ?? []).slice(0, 5),
18592
18622
  ...hasReviewedCompetitors ? {
18593
- recommendedCompetitors: uniqueStrings2(assessment.recommendedCompetitors ?? []).slice(0, 10)
18623
+ recommendedCompetitors: uniqueStrings(assessment.recommendedCompetitors ?? []).slice(0, 10)
18594
18624
  } : {}
18595
18625
  };
18596
18626
  }),
18597
- whatThisMeans: uniqueStrings2(parsed.whatThisMeans ?? []).slice(0, 4),
18598
- recommendedActions: uniqueStrings2(parsed.recommendedActions ?? []).slice(0, 4)
18627
+ whatThisMeans: uniqueStrings(parsed.whatThisMeans ?? []).slice(0, 4),
18628
+ recommendedActions: uniqueStrings(parsed.recommendedActions ?? []).slice(0, 4)
18599
18629
  };
18600
18630
  } catch (err) {
18601
18631
  log12.warn("response.analysis-failed", {
@@ -18690,13 +18720,13 @@ function applyBatchAssessment(queryResults, batchAssessment) {
18690
18720
  };
18691
18721
  }
18692
18722
  const reviewedCompetitors = assessment.recommendedCompetitors;
18693
- const recommendedCompetitors = reviewedCompetitors !== void 0 ? uniqueStrings2(reviewedCompetitors) : result.recommendedCompetitors;
18723
+ const recommendedCompetitors = reviewedCompetitors !== void 0 ? uniqueStrings(reviewedCompetitors) : result.recommendedCompetitors;
18694
18724
  return {
18695
18725
  ...result,
18696
18726
  mentioned: result.mentioned || assessment.mentioned === true,
18697
18727
  describedAccurately: assessment.describedAccurately ?? (result.mentioned ? "unknown" : "not-mentioned"),
18698
18728
  accuracyNotes: assessment.accuracyNotes ?? result.accuracyNotes ?? null,
18699
- incorrectClaims: uniqueStrings2([
18729
+ incorrectClaims: uniqueStrings([
18700
18730
  ...result.incorrectClaims,
18701
18731
  ...assessment.incorrectClaims ?? []
18702
18732
  ]),
@@ -18726,7 +18756,7 @@ function buildSnapshotSummary(companyName, phrases, providers, queryResults, aud
18726
18756
  const whatThisMeans = batchAssessment.whatThisMeans.length > 0 ? batchAssessment.whatThisMeans : [
18727
18757
  defaultMeaning
18728
18758
  ];
18729
- const combinedWhatThisMeans = uniqueStrings2([...whatThisMeans, ...failureNote]).slice(0, 5);
18759
+ const combinedWhatThisMeans = uniqueStrings([...whatThisMeans, ...failureNote]).slice(0, 5);
18730
18760
  const recommendedActions = batchAssessment.recommendedActions.length > 0 ? batchAssessment.recommendedActions : buildFallbackRecommendedActions(audit);
18731
18761
  return {
18732
18762
  totalQueries: phrases.length,
@@ -18769,7 +18799,7 @@ function buildFallbackRecommendedActions(audit) {
18769
18799
  "Add machine-readable trust signals such as schema, FAQs, and llms.txt support.",
18770
18800
  "Build comparison and proof content that makes the category fit unmistakable."
18771
18801
  ];
18772
- return uniqueStrings2([...weakestFactors, ...defaults]).slice(0, 4);
18802
+ return uniqueStrings([...weakestFactors, ...defaults]).slice(0, 4);
18773
18803
  }
18774
18804
  function citesTargetDomain(citedDomains, groundingSources, targetDomain) {
18775
18805
  const normalizedTarget = extractHostname2(targetDomain);
@@ -18843,11 +18873,11 @@ function parseJsonObject(input) {
18843
18873
  }
18844
18874
  function normalizeStringList(values) {
18845
18875
  const items = values.flatMap((value) => value.split(","));
18846
- return uniqueStrings2(
18876
+ return uniqueStrings(
18847
18877
  items.map((value) => value.trim()).filter(Boolean)
18848
18878
  );
18849
18879
  }
18850
- function uniqueStrings2(values) {
18880
+ function uniqueStrings(values) {
18851
18881
  if (!Array.isArray(values)) return [];
18852
18882
  return [...new Set(
18853
18883
  values.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean)
@@ -641,6 +641,64 @@ function normalizeProjectDomain(input) {
641
641
  }
642
642
  return domain.replace(/^www\./, "");
643
643
  }
644
+ var MULTI_LABEL_PUBLIC_SUFFIXES = /* @__PURE__ */ new Set([
645
+ "ac.uk",
646
+ "co.id",
647
+ "co.il",
648
+ "co.in",
649
+ "co.jp",
650
+ "co.kr",
651
+ "co.nz",
652
+ "co.th",
653
+ "co.uk",
654
+ "co.za",
655
+ "com.au",
656
+ "com.br",
657
+ "com.cn",
658
+ "com.mx",
659
+ "com.ph",
660
+ "com.sg",
661
+ "com.tr",
662
+ "edu.au",
663
+ "edu.sg",
664
+ "gov.au",
665
+ "gov.uk",
666
+ "me.uk",
667
+ "ne.jp",
668
+ "net.au",
669
+ "net.br",
670
+ "net.cn",
671
+ "net.in",
672
+ "net.tr",
673
+ "or.jp",
674
+ "or.kr",
675
+ "org.au",
676
+ "org.br",
677
+ "org.in",
678
+ "org.nz",
679
+ "org.tr",
680
+ "org.uk",
681
+ "org.za"
682
+ ]);
683
+ function registrableDomain(input) {
684
+ const normalized = normalizeProjectDomain(input);
685
+ if (!normalized) return "";
686
+ const hostname = normalized.split("/")[0]?.split(":")[0] ?? "";
687
+ if (!hostname) return "";
688
+ const labels = hostname.split(".").filter(Boolean);
689
+ if (labels.length < 2) return "";
690
+ if (labels.length === 2) return labels.join(".");
691
+ const lastTwo = labels.slice(-2).join(".");
692
+ if (MULTI_LABEL_PUBLIC_SUFFIXES.has(lastTwo)) {
693
+ return labels.length >= 3 ? labels.slice(-3).join(".") : "";
694
+ }
695
+ return labels.slice(-2).join(".");
696
+ }
697
+ function brandLabelFromDomain(input) {
698
+ const reg = registrableDomain(input);
699
+ if (!reg) return "";
700
+ return reg.split(".")[0] ?? "";
701
+ }
644
702
  function effectiveDomains(project) {
645
703
  const all = [project.canonicalDomain, ...project.ownedDomains ?? []];
646
704
  const seen = /* @__PURE__ */ new Set();
@@ -1482,11 +1540,10 @@ function collectDistinctiveTokens(displayName, domains) {
1482
1540
  tokens.add(token);
1483
1541
  }
1484
1542
  for (const domain of domains) {
1485
- const hostname = normalizeProjectDomain(domain).split("/")[0] ?? "";
1486
- for (const label of hostname.split(".").filter(Boolean)) {
1487
- const token = label.replace(/[^a-z0-9]/gi, "").toLowerCase();
1488
- if (isDistinctiveToken(token)) tokens.add(token);
1489
- }
1543
+ const reg = registrableDomain(domain);
1544
+ const brand = reg ? brandLabelFromDomain(reg) : normalizeProjectDomain(domain).split("/")[0]?.split(".")[0] ?? "";
1545
+ const token = brand.replace(/[^a-z0-9]/gi, "").toLowerCase();
1546
+ if (isDistinctiveToken(token)) tokens.add(token);
1490
1547
  }
1491
1548
  return [...tokens];
1492
1549
  }
@@ -3607,6 +3664,8 @@ export {
3607
3664
  keywordGenerateRequestSchema,
3608
3665
  competitorBatchRequestSchema,
3609
3666
  normalizeProjectDomain,
3667
+ registrableDomain,
3668
+ brandLabelFromDomain,
3610
3669
  effectiveDomains,
3611
3670
  projectConfigSchema,
3612
3671
  AppError,