@ainyc/canonry 2.6.0 → 2.9.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 +23 -2
- package/assets/assets/{index-BsNi8T7D.js → index-U1lA1GKP.js} +29 -29
- package/assets/index.html +1 -1
- package/dist/{chunk-3DUTT6H2.js → chunk-FPZUQADO.js} +11 -3
- package/dist/{chunk-LF4O276A.js → chunk-MGBXRWLX.js} +159 -27
- package/dist/cli.js +484 -73
- package/dist/index.js +2 -2
- package/dist/mcp.js +282 -14
- package/package.json +4 -4
package/assets/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
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-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-U1lA1GKP.js"></script>
|
|
16
16
|
<link rel="stylesheet" crossorigin href="./assets/index-CAewPdsZ.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
@@ -546,6 +546,10 @@ var keywordDtoSchema = z4.object({
|
|
|
546
546
|
var keywordBatchRequestSchema = z4.object({
|
|
547
547
|
keywords: z4.array(z4.string().trim().min(1)).min(1)
|
|
548
548
|
});
|
|
549
|
+
var keywordGenerateRequestSchema = z4.object({
|
|
550
|
+
provider: providerNameSchema,
|
|
551
|
+
count: z4.number().int().min(1).max(20).optional()
|
|
552
|
+
});
|
|
549
553
|
var competitorDtoSchema = z4.object({
|
|
550
554
|
id: z4.string(),
|
|
551
555
|
domain: z4.string(),
|
|
@@ -1677,12 +1681,15 @@ var ApiClient = class {
|
|
|
1677
1681
|
async appendKeywords(project, keywords) {
|
|
1678
1682
|
await this.request("POST", `/projects/${encodeURIComponent(project)}/keywords`, { keywords });
|
|
1679
1683
|
}
|
|
1680
|
-
async putCompetitors(project, competitors) {
|
|
1681
|
-
await this.request("PUT", `/projects/${encodeURIComponent(project)}/competitors`, { competitors });
|
|
1682
|
-
}
|
|
1683
1684
|
async listCompetitors(project) {
|
|
1684
1685
|
return this.request("GET", `/projects/${encodeURIComponent(project)}/competitors`);
|
|
1685
1686
|
}
|
|
1687
|
+
async appendCompetitors(project, competitors) {
|
|
1688
|
+
return this.request("POST", `/projects/${encodeURIComponent(project)}/competitors`, { competitors });
|
|
1689
|
+
}
|
|
1690
|
+
async deleteCompetitors(project, competitors) {
|
|
1691
|
+
return this.request("DELETE", `/projects/${encodeURIComponent(project)}/competitors`, { competitors });
|
|
1692
|
+
}
|
|
1686
1693
|
async triggerRun(project, body) {
|
|
1687
1694
|
return this.request("POST", `/projects/${encodeURIComponent(project)}/runs`, body ?? {});
|
|
1688
1695
|
}
|
|
@@ -2097,6 +2104,7 @@ export {
|
|
|
2097
2104
|
hasLocationLabel,
|
|
2098
2105
|
projectUpsertRequestSchema,
|
|
2099
2106
|
keywordBatchRequestSchema,
|
|
2107
|
+
keywordGenerateRequestSchema,
|
|
2100
2108
|
competitorBatchRequestSchema,
|
|
2101
2109
|
normalizeProjectDomain,
|
|
2102
2110
|
effectiveDomains,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
brandKeyFromText,
|
|
19
19
|
categorizeSource,
|
|
20
20
|
categoryLabel,
|
|
21
|
+
competitorBatchRequestSchema,
|
|
21
22
|
configExists,
|
|
22
23
|
deliveryFailed,
|
|
23
24
|
determineAnswerMentioned,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
internalError,
|
|
29
30
|
isAgentProviderId,
|
|
30
31
|
isBrowserProvider,
|
|
32
|
+
keywordGenerateRequestSchema,
|
|
31
33
|
loadConfig,
|
|
32
34
|
locationContextSchema,
|
|
33
35
|
missingDependency,
|
|
@@ -49,7 +51,7 @@ import {
|
|
|
49
51
|
visibilityStateFromAnswerMentioned,
|
|
50
52
|
windowCutoff,
|
|
51
53
|
wordpressEnvSchema
|
|
52
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-FPZUQADO.js";
|
|
53
55
|
import {
|
|
54
56
|
IntelligenceService,
|
|
55
57
|
agentMemory,
|
|
@@ -660,10 +662,16 @@ async function keywordRoutes(app, opts) {
|
|
|
660
662
|
});
|
|
661
663
|
app.post("/projects/:name/keywords/generate", async (request, reply) => {
|
|
662
664
|
const project = resolveProject(app.db, request.params.name);
|
|
663
|
-
const
|
|
664
|
-
if (!
|
|
665
|
-
throw validationError(
|
|
665
|
+
const parsed = keywordGenerateRequestSchema.safeParse(request.body);
|
|
666
|
+
if (!parsed.success) {
|
|
667
|
+
throw validationError("Invalid keyword generation request", {
|
|
668
|
+
issues: parsed.error.issues.map((issue) => ({
|
|
669
|
+
path: issue.path.join("."),
|
|
670
|
+
message: issue.message
|
|
671
|
+
}))
|
|
672
|
+
});
|
|
666
673
|
}
|
|
674
|
+
const body = parsed.data;
|
|
667
675
|
const provider = body.provider.trim().toLowerCase();
|
|
668
676
|
const validNames = opts.validProviderNames ?? [];
|
|
669
677
|
if (validNames.length && !validNames.includes(provider)) {
|
|
@@ -672,10 +680,7 @@ async function keywordRoutes(app, opts) {
|
|
|
672
680
|
validProviders: validNames
|
|
673
681
|
});
|
|
674
682
|
}
|
|
675
|
-
|
|
676
|
-
throw validationError('"count" must be an integer');
|
|
677
|
-
}
|
|
678
|
-
const count = Math.min(Math.max(body.count ?? 5, 1), 20);
|
|
683
|
+
const count = body.count ?? 5;
|
|
679
684
|
if (!opts.onGenerateKeywords) {
|
|
680
685
|
throw notImplemented("Key phrase generation is not supported in this deployment");
|
|
681
686
|
}
|
|
@@ -734,6 +739,79 @@ async function competitorRoutes(app) {
|
|
|
734
739
|
const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
|
|
735
740
|
return reply.send(rows.map((r) => ({ id: r.id, domain: r.domain, createdAt: r.createdAt })));
|
|
736
741
|
});
|
|
742
|
+
app.post("/projects/:name/competitors", async (request, reply) => {
|
|
743
|
+
const project = resolveProject(app.db, request.params.name);
|
|
744
|
+
const body = parseCompetitorBatch(request.body);
|
|
745
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
746
|
+
const requested = uniqueStrings(body.competitors);
|
|
747
|
+
app.db.transaction((tx) => {
|
|
748
|
+
const existing = tx.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
|
|
749
|
+
const existingSet = new Set(existing.map((c) => c.domain));
|
|
750
|
+
const added = requested.filter((domain) => !existingSet.has(domain));
|
|
751
|
+
if (added.length === 0) return;
|
|
752
|
+
for (const domain of added) {
|
|
753
|
+
tx.insert(competitors).values({
|
|
754
|
+
id: crypto6.randomUUID(),
|
|
755
|
+
projectId: project.id,
|
|
756
|
+
domain,
|
|
757
|
+
createdAt: now
|
|
758
|
+
}).onConflictDoNothing({
|
|
759
|
+
target: [competitors.projectId, competitors.domain]
|
|
760
|
+
}).run();
|
|
761
|
+
}
|
|
762
|
+
writeAuditLog(tx, {
|
|
763
|
+
projectId: project.id,
|
|
764
|
+
actor: "api",
|
|
765
|
+
action: "competitors.appended",
|
|
766
|
+
entityType: "competitor",
|
|
767
|
+
diff: { added }
|
|
768
|
+
});
|
|
769
|
+
});
|
|
770
|
+
const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
|
|
771
|
+
return reply.send(rows.map((r) => ({ id: r.id, domain: r.domain, createdAt: r.createdAt })));
|
|
772
|
+
});
|
|
773
|
+
app.delete("/projects/:name/competitors", async (request, reply) => {
|
|
774
|
+
const project = resolveProject(app.db, request.params.name);
|
|
775
|
+
const body = parseCompetitorBatch(request.body);
|
|
776
|
+
const requested = new Set(uniqueStrings(body.competitors));
|
|
777
|
+
app.db.transaction((tx) => {
|
|
778
|
+
const existing = tx.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
|
|
779
|
+
const rowsToDelete = existing.filter((c) => requested.has(c.domain));
|
|
780
|
+
if (rowsToDelete.length === 0) return;
|
|
781
|
+
for (const row of rowsToDelete) {
|
|
782
|
+
tx.delete(competitors).where(eq5(competitors.id, row.id)).run();
|
|
783
|
+
}
|
|
784
|
+
writeAuditLog(tx, {
|
|
785
|
+
projectId: project.id,
|
|
786
|
+
actor: "api",
|
|
787
|
+
action: "competitors.deleted",
|
|
788
|
+
entityType: "competitor",
|
|
789
|
+
diff: { deleted: rowsToDelete.map((row) => row.domain) }
|
|
790
|
+
});
|
|
791
|
+
});
|
|
792
|
+
const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
|
|
793
|
+
return reply.send(rows.map((r) => ({ id: r.id, domain: r.domain, createdAt: r.createdAt })));
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
function parseCompetitorBatch(value) {
|
|
797
|
+
const result = competitorBatchRequestSchema.safeParse(value);
|
|
798
|
+
if (result.success) return result.data;
|
|
799
|
+
throw validationError("Invalid competitor batch request", {
|
|
800
|
+
issues: result.error.issues.map((issue) => ({
|
|
801
|
+
path: issue.path.join("."),
|
|
802
|
+
message: issue.message
|
|
803
|
+
}))
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
function uniqueStrings(values) {
|
|
807
|
+
const seen = /* @__PURE__ */ new Set();
|
|
808
|
+
const result = [];
|
|
809
|
+
for (const value of values) {
|
|
810
|
+
if (seen.has(value)) continue;
|
|
811
|
+
seen.add(value);
|
|
812
|
+
result.push(value);
|
|
813
|
+
}
|
|
814
|
+
return result;
|
|
737
815
|
}
|
|
738
816
|
|
|
739
817
|
// ../api-routes/src/runs.ts
|
|
@@ -985,7 +1063,12 @@ async function runRoutes(app, opts) {
|
|
|
985
1063
|
function parseRunTriggerRequest(value) {
|
|
986
1064
|
const result = runTriggerRequestSchema.safeParse(value);
|
|
987
1065
|
if (result.success) return result.data;
|
|
988
|
-
throw validationError("Invalid run trigger request", {
|
|
1066
|
+
throw validationError("Invalid run trigger request", {
|
|
1067
|
+
issues: result.error.issues.map((issue) => ({
|
|
1068
|
+
path: issue.path.join("."),
|
|
1069
|
+
message: issue.message
|
|
1070
|
+
}))
|
|
1071
|
+
});
|
|
989
1072
|
}
|
|
990
1073
|
function formatRun(row) {
|
|
991
1074
|
return {
|
|
@@ -2658,6 +2741,56 @@ var routeCatalog = [
|
|
|
2658
2741
|
200: { description: "Competitors replaced." }
|
|
2659
2742
|
}
|
|
2660
2743
|
},
|
|
2744
|
+
{
|
|
2745
|
+
method: "post",
|
|
2746
|
+
path: "/api/v1/projects/{name}/competitors",
|
|
2747
|
+
summary: "Append competitors",
|
|
2748
|
+
tags: ["competitors"],
|
|
2749
|
+
parameters: [nameParameter],
|
|
2750
|
+
requestBody: {
|
|
2751
|
+
required: true,
|
|
2752
|
+
content: {
|
|
2753
|
+
"application/json": {
|
|
2754
|
+
schema: {
|
|
2755
|
+
type: "object",
|
|
2756
|
+
required: ["competitors"],
|
|
2757
|
+
properties: {
|
|
2758
|
+
competitors: stringArraySchema
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
},
|
|
2764
|
+
responses: {
|
|
2765
|
+
200: { description: "Competitors appended." },
|
|
2766
|
+
400: { description: "Invalid competitor append request." }
|
|
2767
|
+
}
|
|
2768
|
+
},
|
|
2769
|
+
{
|
|
2770
|
+
method: "delete",
|
|
2771
|
+
path: "/api/v1/projects/{name}/competitors",
|
|
2772
|
+
summary: "Delete specific competitors",
|
|
2773
|
+
tags: ["competitors"],
|
|
2774
|
+
parameters: [nameParameter],
|
|
2775
|
+
requestBody: {
|
|
2776
|
+
required: true,
|
|
2777
|
+
content: {
|
|
2778
|
+
"application/json": {
|
|
2779
|
+
schema: {
|
|
2780
|
+
type: "object",
|
|
2781
|
+
required: ["competitors"],
|
|
2782
|
+
properties: {
|
|
2783
|
+
competitors: stringArraySchema
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
},
|
|
2789
|
+
responses: {
|
|
2790
|
+
200: { description: "Remaining competitors returned." },
|
|
2791
|
+
400: { description: "Invalid competitor delete request." }
|
|
2792
|
+
}
|
|
2793
|
+
},
|
|
2661
2794
|
{
|
|
2662
2795
|
method: "post",
|
|
2663
2796
|
path: "/api/v1/projects/{name}/runs",
|
|
@@ -15395,7 +15528,7 @@ function buildAddCompetitorsTool(ctx) {
|
|
|
15395
15528
|
return {
|
|
15396
15529
|
name: "add_competitors",
|
|
15397
15530
|
label: "Add competitors",
|
|
15398
|
-
description: "Append competitor domains to the project.
|
|
15531
|
+
description: "Append competitor domains to the project. Existing competitors are skipped by the API.",
|
|
15399
15532
|
parameters: AddCompetitorsSchema,
|
|
15400
15533
|
execute: async (_toolCallId, params) => {
|
|
15401
15534
|
const existing = await ctx.client.listCompetitors(ctx.projectName);
|
|
@@ -15404,8 +15537,7 @@ function buildAddCompetitorsTool(ctx) {
|
|
|
15404
15537
|
if (newDomains.length === 0) {
|
|
15405
15538
|
return textResult2({ added: [], alreadyTracked: params.domains });
|
|
15406
15539
|
}
|
|
15407
|
-
|
|
15408
|
-
await ctx.client.putCompetitors(ctx.projectName, merged);
|
|
15540
|
+
await ctx.client.appendCompetitors(ctx.projectName, newDomains);
|
|
15409
15541
|
return textResult2({ added: newDomains, alreadyTracked: params.domains.filter((d) => existingDomains.has(d)) });
|
|
15410
15542
|
}
|
|
15411
15543
|
};
|
|
@@ -16581,7 +16713,7 @@ var SnapshotService = class {
|
|
|
16581
16713
|
});
|
|
16582
16714
|
const enrichedResults = applyBatchAssessment(queryResults, batchAssessment);
|
|
16583
16715
|
const summary = buildSnapshotSummary(companyName, profile.phrases, providers, enrichedResults, audit, batchAssessment);
|
|
16584
|
-
const reportCompetitors =
|
|
16716
|
+
const reportCompetitors = uniqueStrings2([
|
|
16585
16717
|
...manualCompetitors,
|
|
16586
16718
|
...summary.topCompetitors.map((entry) => entry.name)
|
|
16587
16719
|
]);
|
|
@@ -16634,8 +16766,8 @@ var SnapshotService = class {
|
|
|
16634
16766
|
return {
|
|
16635
16767
|
industry: parsed.industry?.trim() || "Unknown",
|
|
16636
16768
|
summary: parsed.summary?.trim() || ctx.audit.summary,
|
|
16637
|
-
services:
|
|
16638
|
-
categoryTerms:
|
|
16769
|
+
services: uniqueStrings2(parsed.services ?? []).slice(0, 6),
|
|
16770
|
+
categoryTerms: uniqueStrings2(parsed.categoryTerms ?? []).slice(0, 8),
|
|
16639
16771
|
phrases: parsedPhrases
|
|
16640
16772
|
};
|
|
16641
16773
|
} catch (err) {
|
|
@@ -16703,9 +16835,9 @@ var SnapshotService = class {
|
|
|
16703
16835
|
accuracyNotes: null,
|
|
16704
16836
|
incorrectClaims: [],
|
|
16705
16837
|
recommendedCompetitors: preliminaryCompetitors,
|
|
16706
|
-
citedDomains:
|
|
16838
|
+
citedDomains: uniqueStrings2(normalized.citedDomains),
|
|
16707
16839
|
groundingSources: normalized.groundingSources,
|
|
16708
|
-
searchQueries:
|
|
16840
|
+
searchQueries: uniqueStrings2(normalized.searchQueries),
|
|
16709
16841
|
answerText: normalized.answerText,
|
|
16710
16842
|
error: null
|
|
16711
16843
|
};
|
|
@@ -16771,14 +16903,14 @@ var SnapshotService = class {
|
|
|
16771
16903
|
mentioned: assessment.mentioned,
|
|
16772
16904
|
describedAccurately: assessment.describedAccurately,
|
|
16773
16905
|
accuracyNotes: assessment.accuracyNotes ?? null,
|
|
16774
|
-
incorrectClaims:
|
|
16906
|
+
incorrectClaims: uniqueStrings2(assessment.incorrectClaims ?? []).slice(0, 5),
|
|
16775
16907
|
...hasReviewedCompetitors ? {
|
|
16776
|
-
recommendedCompetitors:
|
|
16908
|
+
recommendedCompetitors: uniqueStrings2(assessment.recommendedCompetitors ?? []).slice(0, 10)
|
|
16777
16909
|
} : {}
|
|
16778
16910
|
};
|
|
16779
16911
|
}),
|
|
16780
|
-
whatThisMeans:
|
|
16781
|
-
recommendedActions:
|
|
16912
|
+
whatThisMeans: uniqueStrings2(parsed.whatThisMeans ?? []).slice(0, 4),
|
|
16913
|
+
recommendedActions: uniqueStrings2(parsed.recommendedActions ?? []).slice(0, 4)
|
|
16782
16914
|
};
|
|
16783
16915
|
} catch (err) {
|
|
16784
16916
|
log12.warn("response.analysis-failed", {
|
|
@@ -16873,13 +17005,13 @@ function applyBatchAssessment(queryResults, batchAssessment) {
|
|
|
16873
17005
|
};
|
|
16874
17006
|
}
|
|
16875
17007
|
const reviewedCompetitors = assessment.recommendedCompetitors;
|
|
16876
|
-
const recommendedCompetitors = reviewedCompetitors !== void 0 ?
|
|
17008
|
+
const recommendedCompetitors = reviewedCompetitors !== void 0 ? uniqueStrings2(reviewedCompetitors) : result.recommendedCompetitors;
|
|
16877
17009
|
return {
|
|
16878
17010
|
...result,
|
|
16879
17011
|
mentioned: result.mentioned || assessment.mentioned === true,
|
|
16880
17012
|
describedAccurately: assessment.describedAccurately ?? (result.mentioned ? "unknown" : "not-mentioned"),
|
|
16881
17013
|
accuracyNotes: assessment.accuracyNotes ?? result.accuracyNotes ?? null,
|
|
16882
|
-
incorrectClaims:
|
|
17014
|
+
incorrectClaims: uniqueStrings2([
|
|
16883
17015
|
...result.incorrectClaims,
|
|
16884
17016
|
...assessment.incorrectClaims ?? []
|
|
16885
17017
|
]),
|
|
@@ -16909,7 +17041,7 @@ function buildSnapshotSummary(companyName, phrases, providers, queryResults, aud
|
|
|
16909
17041
|
const whatThisMeans = batchAssessment.whatThisMeans.length > 0 ? batchAssessment.whatThisMeans : [
|
|
16910
17042
|
defaultMeaning
|
|
16911
17043
|
];
|
|
16912
|
-
const combinedWhatThisMeans =
|
|
17044
|
+
const combinedWhatThisMeans = uniqueStrings2([...whatThisMeans, ...failureNote]).slice(0, 5);
|
|
16913
17045
|
const recommendedActions = batchAssessment.recommendedActions.length > 0 ? batchAssessment.recommendedActions : buildFallbackRecommendedActions(audit);
|
|
16914
17046
|
return {
|
|
16915
17047
|
totalQueries: phrases.length,
|
|
@@ -16952,7 +17084,7 @@ function buildFallbackRecommendedActions(audit) {
|
|
|
16952
17084
|
"Add machine-readable trust signals such as schema, FAQs, and llms.txt support.",
|
|
16953
17085
|
"Build comparison and proof content that makes the category fit unmistakable."
|
|
16954
17086
|
];
|
|
16955
|
-
return
|
|
17087
|
+
return uniqueStrings2([...weakestFactors, ...defaults]).slice(0, 4);
|
|
16956
17088
|
}
|
|
16957
17089
|
function citesTargetDomain(citedDomains, groundingSources, targetDomain) {
|
|
16958
17090
|
const normalizedTarget = extractHostname2(targetDomain);
|
|
@@ -17026,11 +17158,11 @@ function parseJsonObject(input) {
|
|
|
17026
17158
|
}
|
|
17027
17159
|
function normalizeStringList(values) {
|
|
17028
17160
|
const items = values.flatMap((value) => value.split(","));
|
|
17029
|
-
return
|
|
17161
|
+
return uniqueStrings2(
|
|
17030
17162
|
items.map((value) => value.trim()).filter(Boolean)
|
|
17031
17163
|
);
|
|
17032
17164
|
}
|
|
17033
|
-
function
|
|
17165
|
+
function uniqueStrings2(values) {
|
|
17034
17166
|
if (!Array.isArray(values)) return [];
|
|
17035
17167
|
return [...new Set(
|
|
17036
17168
|
values.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean)
|