@ainyc/canonry 3.2.5 → 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/assets/{index-B8epngdo.js → index-BJz9S5-O.js} +106 -106
- package/assets/assets/{index-CQGbgHZY.css → index-JG7aBJrz.css} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-AQ3AS25Y.js → chunk-7R5NSWF7.js} +72 -42
- package/dist/{chunk-PS7JRDL3.js → chunk-GY4MNUTI.js} +64 -5
- package/dist/cli.js +144 -5
- package/dist/index.js +2 -2
- package/dist/mcp.js +1 -1
- package/package.json +8 -8
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-
|
|
16
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
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-
|
|
67
|
+
} from "./chunk-GY4MNUTI.js";
|
|
66
68
|
import {
|
|
67
69
|
IntelligenceService,
|
|
68
70
|
agentMemory,
|
|
@@ -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
|
|
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:
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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
|
|
15138
|
-
if (brand
|
|
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
|
|
15215
|
-
|
|
15216
|
-
|
|
15217
|
-
|
|
15218
|
-
|
|
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 =
|
|
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:
|
|
18455
|
-
categoryTerms:
|
|
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:
|
|
18553
|
+
citedDomains: uniqueStrings(normalized.citedDomains),
|
|
18524
18554
|
groundingSources: normalized.groundingSources,
|
|
18525
|
-
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:
|
|
18621
|
+
incorrectClaims: uniqueStrings(assessment.incorrectClaims ?? []).slice(0, 5),
|
|
18592
18622
|
...hasReviewedCompetitors ? {
|
|
18593
|
-
recommendedCompetitors:
|
|
18623
|
+
recommendedCompetitors: uniqueStrings(assessment.recommendedCompetitors ?? []).slice(0, 10)
|
|
18594
18624
|
} : {}
|
|
18595
18625
|
};
|
|
18596
18626
|
}),
|
|
18597
|
-
whatThisMeans:
|
|
18598
|
-
recommendedActions:
|
|
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 ?
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
|
18876
|
+
return uniqueStrings(
|
|
18847
18877
|
items.map((value) => value.trim()).filter(Boolean)
|
|
18848
18878
|
);
|
|
18849
18879
|
}
|
|
18850
|
-
function
|
|
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
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
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,
|
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
setGoogleAuthConfig,
|
|
18
18
|
showFirstRunNotice,
|
|
19
19
|
trackEvent
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-7R5NSWF7.js";
|
|
21
21
|
import {
|
|
22
22
|
CcReleaseSyncStatuses,
|
|
23
23
|
CheckScopes,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
saveConfig,
|
|
46
46
|
saveConfigPatch,
|
|
47
47
|
usageError
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-GY4MNUTI.js";
|
|
49
49
|
import {
|
|
50
50
|
apiKeys,
|
|
51
51
|
competitors,
|
|
@@ -448,6 +448,131 @@ async function backfillAiReferralPathsCommand(opts) {
|
|
|
448
448
|
console.log(` Updated: ${updated}`);
|
|
449
449
|
console.log(` Unchanged: ${unchanged}`);
|
|
450
450
|
}
|
|
451
|
+
async function backfillAnswerMentionsCommand(opts) {
|
|
452
|
+
const config = loadConfig();
|
|
453
|
+
const db = createClient(config.database);
|
|
454
|
+
migrate(db);
|
|
455
|
+
const projectFilter = opts?.project?.trim();
|
|
456
|
+
const scopedProjects = projectFilter ? db.select().from(projects).where(eq(projects.name, projectFilter)).all() : db.select().from(projects).all();
|
|
457
|
+
let examined = 0;
|
|
458
|
+
let updated = 0;
|
|
459
|
+
let mentioned = 0;
|
|
460
|
+
if (scopedProjects.length > 0) {
|
|
461
|
+
const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and(
|
|
462
|
+
eq(runs.kind, RunKinds["answer-visibility"]),
|
|
463
|
+
inArray(runs.projectId, scopedProjects.map((project) => project.id))
|
|
464
|
+
)).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(eq(runs.kind, RunKinds["answer-visibility"])).all();
|
|
465
|
+
const runIdsByProject = /* @__PURE__ */ new Map();
|
|
466
|
+
for (const run of runRows) {
|
|
467
|
+
const existing = runIdsByProject.get(run.projectId);
|
|
468
|
+
if (existing) existing.push(run.id);
|
|
469
|
+
else runIdsByProject.set(run.projectId, [run.id]);
|
|
470
|
+
}
|
|
471
|
+
for (const project of scopedProjects) {
|
|
472
|
+
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq(competitors.projectId, project.id)).all().map((row) => row.domain);
|
|
473
|
+
const runIds = runIdsByProject.get(project.id) ?? [];
|
|
474
|
+
if (runIds.length === 0) continue;
|
|
475
|
+
const projectDomains = effectiveDomains({
|
|
476
|
+
canonicalDomain: project.canonicalDomain,
|
|
477
|
+
ownedDomains: parseJsonColumn(project.ownedDomains, [])
|
|
478
|
+
});
|
|
479
|
+
for (let offset = 0; offset < runIds.length; offset += SNAPSHOT_BATCH_SIZE) {
|
|
480
|
+
const batchRunIds = runIds.slice(offset, offset + SNAPSHOT_BATCH_SIZE);
|
|
481
|
+
const snapshotRows = db.select({
|
|
482
|
+
id: querySnapshots.id,
|
|
483
|
+
provider: querySnapshots.provider,
|
|
484
|
+
answerMentioned: querySnapshots.answerMentioned,
|
|
485
|
+
answerText: querySnapshots.answerText,
|
|
486
|
+
citedDomains: querySnapshots.citedDomains,
|
|
487
|
+
competitorOverlap: querySnapshots.competitorOverlap,
|
|
488
|
+
recommendedCompetitors: querySnapshots.recommendedCompetitors,
|
|
489
|
+
rawResponse: querySnapshots.rawResponse
|
|
490
|
+
}).from(querySnapshots).where(inArray(querySnapshots.runId, batchRunIds)).all();
|
|
491
|
+
const pendingUpdates = [];
|
|
492
|
+
for (const snapshot of snapshotRows) {
|
|
493
|
+
examined++;
|
|
494
|
+
const answerText = snapshot.answerText ?? "";
|
|
495
|
+
const nextAnswerMentioned = determineAnswerMentioned(answerText, project.displayName, projectDomains);
|
|
496
|
+
if (nextAnswerMentioned) mentioned++;
|
|
497
|
+
const citedDomains = parseJsonColumn(snapshot.citedDomains, []);
|
|
498
|
+
const groundingSources = readStoredGroundingSources(snapshot.rawResponse);
|
|
499
|
+
const normalized = {
|
|
500
|
+
provider: snapshot.provider,
|
|
501
|
+
answerText,
|
|
502
|
+
citedDomains,
|
|
503
|
+
groundingSources,
|
|
504
|
+
searchQueries: []
|
|
505
|
+
};
|
|
506
|
+
const nextCompetitorOverlap = JSON.stringify(
|
|
507
|
+
computeCompetitorOverlap(normalized, competitorDomains)
|
|
508
|
+
);
|
|
509
|
+
const nextRecommendedCompetitors = JSON.stringify(
|
|
510
|
+
extractRecommendedCompetitors(
|
|
511
|
+
answerText,
|
|
512
|
+
projectDomains,
|
|
513
|
+
citedDomains,
|
|
514
|
+
competitorDomains
|
|
515
|
+
)
|
|
516
|
+
);
|
|
517
|
+
const nextPatch = {};
|
|
518
|
+
if (snapshot.answerMentioned !== nextAnswerMentioned) {
|
|
519
|
+
nextPatch.answerMentioned = nextAnswerMentioned;
|
|
520
|
+
}
|
|
521
|
+
if (snapshot.competitorOverlap !== nextCompetitorOverlap) {
|
|
522
|
+
nextPatch.competitorOverlap = nextCompetitorOverlap;
|
|
523
|
+
}
|
|
524
|
+
if (snapshot.recommendedCompetitors !== nextRecommendedCompetitors) {
|
|
525
|
+
nextPatch.recommendedCompetitors = nextRecommendedCompetitors;
|
|
526
|
+
}
|
|
527
|
+
if (Object.keys(nextPatch).length > 0) {
|
|
528
|
+
pendingUpdates.push({ id: snapshot.id, patch: nextPatch });
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (pendingUpdates.length > 0) {
|
|
532
|
+
db.transaction((tx) => {
|
|
533
|
+
for (const update of pendingUpdates) {
|
|
534
|
+
tx.update(querySnapshots).set(update.patch).where(eq(querySnapshots.id, update.id)).run();
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
updated += pendingUpdates.length;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
const result = {
|
|
543
|
+
project: projectFilter ?? null,
|
|
544
|
+
projects: scopedProjects.length,
|
|
545
|
+
examined,
|
|
546
|
+
updated,
|
|
547
|
+
mentioned
|
|
548
|
+
};
|
|
549
|
+
if (opts?.format === "json") {
|
|
550
|
+
console.log(JSON.stringify(result, null, 2));
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
console.log("Answer mentions backfill complete.\n");
|
|
554
|
+
if (projectFilter) console.log(` Project: ${projectFilter}`);
|
|
555
|
+
console.log(` Projects: ${scopedProjects.length}`);
|
|
556
|
+
console.log(` Examined: ${examined}`);
|
|
557
|
+
console.log(` Updated: ${updated}`);
|
|
558
|
+
console.log(` Mentioned: ${mentioned}`);
|
|
559
|
+
}
|
|
560
|
+
function readStoredGroundingSources(rawResponse) {
|
|
561
|
+
const envelope = parseJsonColumn(rawResponse, {});
|
|
562
|
+
const sources = envelope.groundingSources;
|
|
563
|
+
if (!Array.isArray(sources)) return [];
|
|
564
|
+
const result = [];
|
|
565
|
+
for (const source of sources) {
|
|
566
|
+
if (source && typeof source === "object") {
|
|
567
|
+
const uri = source.uri;
|
|
568
|
+
const title = source.title;
|
|
569
|
+
if (typeof uri === "string") {
|
|
570
|
+
result.push({ uri, title: typeof title === "string" ? title : "" });
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return result;
|
|
575
|
+
}
|
|
451
576
|
async function backfillInsightsCommand(project, opts) {
|
|
452
577
|
const { IntelligenceService } = await import("./intelligence-service-FNJTFSI3.js");
|
|
453
578
|
const config = loadConfig();
|
|
@@ -628,6 +753,20 @@ var BACKFILL_CLI_COMMANDS = [
|
|
|
628
753
|
});
|
|
629
754
|
}
|
|
630
755
|
},
|
|
756
|
+
{
|
|
757
|
+
path: ["backfill", "answer-mentions"],
|
|
758
|
+
usage: "canonry backfill answer-mentions [--project <name>] [--format json]",
|
|
759
|
+
options: {
|
|
760
|
+
project: stringOption()
|
|
761
|
+
},
|
|
762
|
+
allowPositionals: false,
|
|
763
|
+
run: async (input) => {
|
|
764
|
+
await backfillAnswerMentionsCommand({
|
|
765
|
+
project: getString(input.values, "project"),
|
|
766
|
+
format: input.format
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
},
|
|
631
770
|
{
|
|
632
771
|
path: ["backfill", "insights"],
|
|
633
772
|
usage: "canonry backfill insights <project> [--from-run <id>] [--to-run <id>] [--format json]",
|
|
@@ -675,12 +814,12 @@ var BACKFILL_CLI_COMMANDS = [
|
|
|
675
814
|
},
|
|
676
815
|
{
|
|
677
816
|
path: ["backfill"],
|
|
678
|
-
usage: "canonry backfill <answer-visibility|insights|normalized-paths|ai-referral-paths> [options]",
|
|
817
|
+
usage: "canonry backfill <answer-visibility|answer-mentions|insights|normalized-paths|ai-referral-paths> [options]",
|
|
679
818
|
run: async (input) => {
|
|
680
819
|
unknownSubcommand(input.positionals[0], {
|
|
681
820
|
command: "backfill",
|
|
682
|
-
usage: "canonry backfill <answer-visibility|insights|normalized-paths|ai-referral-paths> [options]",
|
|
683
|
-
available: ["answer-visibility", "insights", "normalized-paths", "ai-referral-paths"]
|
|
821
|
+
usage: "canonry backfill <answer-visibility|answer-mentions|insights|normalized-paths|ai-referral-paths> [options]",
|
|
822
|
+
available: ["answer-visibility", "answer-mentions", "insights", "normalized-paths", "ai-referral-paths"]
|
|
684
823
|
});
|
|
685
824
|
}
|
|
686
825
|
}
|
package/dist/index.js
CHANGED
package/dist/mcp.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainyc/canonry",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
|
|
6
6
|
"license": "FSL-1.1-ALv2",
|
|
@@ -60,20 +60,20 @@
|
|
|
60
60
|
"tsup": "^8.5.1",
|
|
61
61
|
"tsx": "^4.19.0",
|
|
62
62
|
"@ainyc/canonry-api-routes": "0.0.0",
|
|
63
|
-
"@ainyc/canonry-config": "0.0.0",
|
|
64
63
|
"@ainyc/canonry-contracts": "0.0.0",
|
|
65
|
-
"@ainyc/canonry-
|
|
66
|
-
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
64
|
+
"@ainyc/canonry-config": "0.0.0",
|
|
67
65
|
"@ainyc/canonry-db": "0.0.0",
|
|
66
|
+
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
68
67
|
"@ainyc/canonry-intelligence": "0.0.0",
|
|
69
68
|
"@ainyc/canonry-integration-google": "0.0.0",
|
|
70
|
-
"@ainyc/canonry-
|
|
71
|
-
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
69
|
+
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
72
70
|
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
73
71
|
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
72
|
+
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
73
|
+
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
74
74
|
"@ainyc/canonry-provider-local": "0.0.0",
|
|
75
|
-
"@ainyc/canonry-provider-
|
|
76
|
-
"@ainyc/canonry-provider-
|
|
75
|
+
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
76
|
+
"@ainyc/canonry-provider-perplexity": "0.0.0"
|
|
77
77
|
},
|
|
78
78
|
"scripts": {
|
|
79
79
|
"build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",
|