@ainyc/canonry 3.5.1 → 3.6.3
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/agent-workspace/skills/aero/references/reporting.md +1 -1
- package/assets/agent-workspace/skills/canonry-setup/references/canonry-cli.md +1 -1
- package/assets/assets/{index-CFtdvSnQ.js → index-1wUFsyjk.js} +129 -129
- package/assets/assets/{index-BfwQqd05.css → index-f8lqs-ju.css} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-R6YCYS2C.js → chunk-CG4HEQAK.js} +126 -30
- package/dist/{chunk-ZYESHCMF.js → chunk-GLPZ5NVP.js} +2 -2
- package/dist/{chunk-VIUWGDDU.js → chunk-RDX6GBWM.js} +12 -0
- package/dist/{chunk-M4KG7RJT.js → chunk-W463NVVC.js} +1 -1
- package/dist/cli.js +5 -5
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-MY2EO4WD.js → intelligence-service-WZUM3AX6.js} +2 -2
- package/dist/mcp.js +2 -2
- 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-1wUFsyjk.js"></script>
|
|
16
|
+
<link rel="stylesheet" crossorigin href="./assets/index-f8lqs-ju.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
19
19
|
<div id="root"></div>
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
configExists,
|
|
5
5
|
loadConfig,
|
|
6
6
|
saveConfigPatch
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-GLPZ5NVP.js";
|
|
8
8
|
import {
|
|
9
9
|
IntelligenceService,
|
|
10
10
|
MIN_TREND_POINTS,
|
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
runs,
|
|
50
50
|
schedules,
|
|
51
51
|
usageCounters
|
|
52
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-W463NVVC.js";
|
|
53
53
|
import {
|
|
54
54
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
55
55
|
AGENT_PROVIDER_IDS,
|
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
RunKinds,
|
|
65
65
|
RunStatuses,
|
|
66
66
|
RunTriggers,
|
|
67
|
+
absolutizeProjectUrl,
|
|
67
68
|
agentBusy,
|
|
68
69
|
agentMemoryDeleteRequestSchema,
|
|
69
70
|
agentMemoryUpsertRequestSchema,
|
|
@@ -111,7 +112,7 @@ import {
|
|
|
111
112
|
visibilityStateFromAnswerMentioned,
|
|
112
113
|
windowCutoff,
|
|
113
114
|
wordpressEnvSchema
|
|
114
|
-
} from "./chunk-
|
|
115
|
+
} from "./chunk-RDX6GBWM.js";
|
|
115
116
|
|
|
116
117
|
// src/telemetry.ts
|
|
117
118
|
import crypto from "crypto";
|
|
@@ -2783,7 +2784,12 @@ function renderExecutiveSummary(report) {
|
|
|
2783
2784
|
<span>${escapeHtml(f.detail)}</span>
|
|
2784
2785
|
</div>`).join("")}</div>` : "";
|
|
2785
2786
|
return section(
|
|
2786
|
-
{
|
|
2787
|
+
{
|
|
2788
|
+
id: "executive-summary",
|
|
2789
|
+
eyebrow: "Section 1",
|
|
2790
|
+
title: "Executive Summary",
|
|
2791
|
+
intro: "Top-line citation rate with trend versus the prior run, plus the most actionable findings from the latest visibility sweep."
|
|
2792
|
+
},
|
|
2787
2793
|
metricsHtml + findingsHtml
|
|
2788
2794
|
);
|
|
2789
2795
|
}
|
|
@@ -2842,15 +2848,11 @@ function renderCitationScorecard(report) {
|
|
|
2842
2848
|
${renderCitationMatrix(report.citationScorecard)}
|
|
2843
2849
|
`;
|
|
2844
2850
|
return section(
|
|
2845
|
-
{ id: "citation-scorecard", eyebrow: "Section 2", title: "Citation Scorecard", intro: "
|
|
2851
|
+
{ id: "citation-scorecard", eyebrow: "Section 2", title: "Citation Scorecard", intro: "Whether your domain appeared in each AI engine\u2019s source list for every tracked keyword in the latest sweep \u2014 a cell turns green when your domain was cited, red when it was not, and gray when no snapshot exists for that pair." },
|
|
2846
2852
|
body
|
|
2847
2853
|
);
|
|
2848
2854
|
}
|
|
2849
|
-
function
|
|
2850
|
-
const data = [
|
|
2851
|
-
{ label: canonical, count: landscape.projectCitationCount, isProject: true },
|
|
2852
|
-
...landscape.competitors.map((c) => ({ label: c.domain, count: c.citationCount, isProject: false }))
|
|
2853
|
-
];
|
|
2855
|
+
function renderLandscapeBars(data, heading, ariaLabel) {
|
|
2854
2856
|
if (data.length <= 1) return "";
|
|
2855
2857
|
const max = Math.max(...data.map((d) => d.count), 1);
|
|
2856
2858
|
const width = 600;
|
|
@@ -2867,22 +2869,43 @@ function renderCompetitorBars(landscape, canonical) {
|
|
|
2867
2869
|
<text x="${labelWidth + w + 6}" y="${y + 13}" fill="${COLORS.text}" font-size="11">${d.count}</text>`;
|
|
2868
2870
|
}).join("");
|
|
2869
2871
|
return `<div class="chart-card">
|
|
2870
|
-
<h3
|
|
2871
|
-
<svg viewBox="0 0 ${width} ${height}" width="100%" preserveAspectRatio="xMinYMin meet" role="img" aria-label="
|
|
2872
|
+
<h3>${escapeHtml(heading)}</h3>
|
|
2873
|
+
<svg viewBox="0 0 ${width} ${height}" width="100%" preserveAspectRatio="xMinYMin meet" role="img" aria-label="${escapeHtml(ariaLabel)}">
|
|
2872
2874
|
${bars}
|
|
2873
2875
|
</svg>
|
|
2874
2876
|
</div>`;
|
|
2875
2877
|
}
|
|
2878
|
+
function renderCompetitorBars(landscape, canonical) {
|
|
2879
|
+
const data = [
|
|
2880
|
+
{ label: canonical, count: landscape.projectCitationCount, isProject: true },
|
|
2881
|
+
...landscape.competitors.map((c) => ({ label: c.domain, count: c.citationCount, isProject: false }))
|
|
2882
|
+
];
|
|
2883
|
+
return renderLandscapeBars(data, "Citations per domain", "Citations per domain bar chart");
|
|
2884
|
+
}
|
|
2885
|
+
function renderMentionBars(landscape, canonical) {
|
|
2886
|
+
const data = [
|
|
2887
|
+
{ label: canonical, count: landscape.projectMentionCount, isProject: true },
|
|
2888
|
+
...landscape.competitors.map((c) => ({ label: c.domain, count: c.mentionCount, isProject: false }))
|
|
2889
|
+
];
|
|
2890
|
+
return renderLandscapeBars(data, "Mentions per domain", "Mentions per domain bar chart");
|
|
2891
|
+
}
|
|
2876
2892
|
function renderCompetitorLandscape(report) {
|
|
2877
2893
|
const competitors2 = report.competitorLandscape.competitors;
|
|
2878
|
-
|
|
2894
|
+
const mentionLandscape = report.mentionLandscape;
|
|
2895
|
+
const noCitationData = competitors2.length === 0 && report.competitorLandscape.projectCitationCount === 0;
|
|
2896
|
+
const noMentionData = mentionLandscape.competitors.length === 0 && mentionLandscape.projectMentionCount === 0;
|
|
2897
|
+
if (noCitationData && noMentionData) {
|
|
2879
2898
|
return section(
|
|
2880
2899
|
{ id: "competitor-landscape", eyebrow: "Section 3", title: "Competitor Landscape" },
|
|
2881
2900
|
renderEmpty("No competitor data yet. Add competitors and run a visibility sweep.")
|
|
2882
2901
|
);
|
|
2883
2902
|
}
|
|
2903
|
+
const mentionByDomain = new Map(mentionLandscape.competitors.map((m) => [m.domain, m]));
|
|
2884
2904
|
const rows = competitors2.map((c) => {
|
|
2885
2905
|
const tone = pressureTone(c.pressureLabel);
|
|
2906
|
+
const mention = mentionByDomain.get(c.domain);
|
|
2907
|
+
const mentionCount = mention?.mentionCount ?? 0;
|
|
2908
|
+
const mentionTotal = mention?.totalCount ?? mentionLandscape.totalAnswerSnapshots;
|
|
2886
2909
|
const pagesDisclosure = c.theirCitedPages.length > 0 ? `<details class="cited-pages"><summary>${c.theirCitedPages.length} cited URL${c.theirCitedPages.length > 1 ? "s" : ""}</summary>
|
|
2887
2910
|
<ul>${c.theirCitedPages.map((p) => `<li><a href="${escapeHtml(p.url)}">${escapeHtml(p.url)}</a> <span class="cited-for">${escapeHtml(p.citedFor.join(", "))}</span></li>`).join("")}</ul>
|
|
2888
2911
|
</details>` : "";
|
|
@@ -2890,17 +2913,26 @@ function renderCompetitorLandscape(report) {
|
|
|
2890
2913
|
<td>${escapeHtml(c.domain)}</td>
|
|
2891
2914
|
<td><span class="badge tone-${tone}">${escapeHtml(c.pressureLabel)}</span></td>
|
|
2892
2915
|
<td class="numeric">${c.citationCount} / ${c.totalCount}</td>
|
|
2916
|
+
<td class="numeric">${mentionCount} / ${mentionTotal}</td>
|
|
2893
2917
|
<td class="numeric">${c.sharePct}%</td>
|
|
2894
2918
|
<td>${escapeHtml(c.citedKeywords.slice(0, 5).join(", "))}${c.citedKeywords.length > 5 ? "\u2026" : ""}${pagesDisclosure}</td>
|
|
2895
2919
|
</tr>`;
|
|
2896
2920
|
}).join("");
|
|
2897
2921
|
const table = competitors2.length > 0 ? `<table class="report-table">
|
|
2898
|
-
<thead><tr><th>Domain</th><th>Pressure</th><th>Citations</th><th class="numeric">SOV</th><th>Cited keywords</th></tr></thead>
|
|
2922
|
+
<thead><tr><th>Domain</th><th>Pressure</th><th>Citations</th><th class="numeric">Mentions</th><th class="numeric">SOV</th><th>Cited keywords</th></tr></thead>
|
|
2899
2923
|
<tbody>${rows}</tbody>
|
|
2900
2924
|
</table>` : renderEmpty("No competitors configured.");
|
|
2925
|
+
const citationBars = renderCompetitorBars(report.competitorLandscape, report.meta.project.canonicalDomain);
|
|
2926
|
+
const mentionBars = renderMentionBars(mentionLandscape, report.meta.project.canonicalDomain);
|
|
2927
|
+
const charts = citationBars && mentionBars ? `<div class="chart-grid">${citationBars}${mentionBars}</div>` : `${citationBars}${mentionBars}`;
|
|
2901
2928
|
return section(
|
|
2902
|
-
{
|
|
2903
|
-
|
|
2929
|
+
{
|
|
2930
|
+
id: "competitor-landscape",
|
|
2931
|
+
eyebrow: "Section 3",
|
|
2932
|
+
title: "Competitor Landscape",
|
|
2933
|
+
intro: "Where tracked competitors appear in AI answers compared to your domain \u2014 both in source citations and in the answer text itself."
|
|
2934
|
+
},
|
|
2935
|
+
`${charts}${table}`
|
|
2904
2936
|
);
|
|
2905
2937
|
}
|
|
2906
2938
|
function renderDonut(buckets) {
|
|
@@ -2947,7 +2979,7 @@ function renderAiSourceOrigin(report) {
|
|
|
2947
2979
|
const origin = report.aiSourceOrigin;
|
|
2948
2980
|
if (origin.categories.length === 0 && origin.topDomains.length === 0) {
|
|
2949
2981
|
return section(
|
|
2950
|
-
{ id: "ai-source-origin", eyebrow: "Section 4", title: "AI
|
|
2982
|
+
{ id: "ai-source-origin", eyebrow: "Section 4", title: "AI Citation Sources" },
|
|
2951
2983
|
renderEmpty("No source data yet. Run a visibility sweep first.")
|
|
2952
2984
|
);
|
|
2953
2985
|
}
|
|
@@ -2962,7 +2994,12 @@ function renderAiSourceOrigin(report) {
|
|
|
2962
2994
|
<tbody>${rows}</tbody>
|
|
2963
2995
|
</table>` : "";
|
|
2964
2996
|
return section(
|
|
2965
|
-
{
|
|
2997
|
+
{
|
|
2998
|
+
id: "ai-source-origin",
|
|
2999
|
+
eyebrow: "Section 4",
|
|
3000
|
+
title: "AI Citation Sources",
|
|
3001
|
+
intro: "Every external website AI engines cited as a source for your tracked keywords in the latest sweep \u2014 categorized by site type (Reddit, YouTube, news, etc.) on the left and ranked by citation count on the right. Your own domains are excluded; tracked competitors are flagged."
|
|
3002
|
+
},
|
|
2966
3003
|
`${renderDonut(origin.categories)}${table}`
|
|
2967
3004
|
);
|
|
2968
3005
|
}
|
|
@@ -3041,7 +3078,7 @@ function renderGsc(report) {
|
|
|
3041
3078
|
</div>`);
|
|
3042
3079
|
}
|
|
3043
3080
|
return section(
|
|
3044
|
-
{ id: "gsc", eyebrow: "Section 5", title: "GSC Performance", intro: "
|
|
3081
|
+
{ id: "gsc", eyebrow: "Section 5", title: "GSC Performance", intro: "Your site\u2019s performance in Google\u2019s regular (non-AI) search results \u2014 top queries that drove impressions, intent breakdown, and the click trend, sourced from Google Search Console for the most recent sync window." },
|
|
3045
3082
|
`<div class="metric-grid">
|
|
3046
3083
|
<div class="metric"><div class="label">Total clicks</div><div class="value">${formatNumber(gsc.totalClicks)}</div></div>
|
|
3047
3084
|
<div class="metric"><div class="label">Total impressions</div><div class="value">${formatNumber(gsc.totalImpressions)}</div></div>
|
|
@@ -3086,7 +3123,7 @@ function renderGa(report) {
|
|
|
3086
3123
|
<td class="numeric">${c.sharePct}%</td>
|
|
3087
3124
|
</tr>`).join("");
|
|
3088
3125
|
return section(
|
|
3089
|
-
{ id: "ga", eyebrow: "Section 6", title: "GA4 Traffic", intro: `
|
|
3126
|
+
{ id: "ga", eyebrow: "Section 6", title: "GA4 Traffic", intro: `Total sessions and users on your site between ${formatDate(ga.periodStart)} and ${formatDate(ga.periodEnd)}, with the top landing pages and channel breakdown \u2014 sourced from Google Analytics 4.` },
|
|
3090
3127
|
`<div class="metric-grid">
|
|
3091
3128
|
<div class="metric"><div class="label">Total sessions</div><div class="value">${formatNumber(ga.totalSessions)}</div></div>
|
|
3092
3129
|
<div class="metric"><div class="label">Total users</div><div class="value">${formatNumber(ga.totalUsers)}</div></div>
|
|
@@ -3127,7 +3164,7 @@ function renderSocial(report) {
|
|
|
3127
3164
|
<td class="numeric">${formatNumber(c.sessions)}</td>
|
|
3128
3165
|
</tr>`).join("");
|
|
3129
3166
|
return section(
|
|
3130
|
-
{ id: "social-referrals", eyebrow: "Section 7", title: "Social Referrals", intro: "
|
|
3167
|
+
{ id: "social-referrals", eyebrow: "Section 7", title: "Social Referrals", intro: "Sessions on your site sent by social platforms (LinkedIn, Facebook, X, etc.) \u2014 paid versus organic split and the top campaigns that drove them. Sourced from Google Analytics 4." },
|
|
3131
3168
|
`<div class="metric-grid">
|
|
3132
3169
|
<div class="metric"><div class="label">Total sessions</div><div class="value">${formatNumber(social.totalSessions)}</div></div>
|
|
3133
3170
|
<div class="metric"><div class="label">Organic social</div><div class="value">${formatNumber(social.organicSessions)}</div></div>
|
|
@@ -3174,7 +3211,7 @@ function renderAiReferrals(report) {
|
|
|
3174
3211
|
"AI referral sessions over time"
|
|
3175
3212
|
);
|
|
3176
3213
|
return section(
|
|
3177
|
-
{ id: "ai-referrals", eyebrow: "Section 8", title: "AI Referral Traffic", intro: "Sessions
|
|
3214
|
+
{ id: "ai-referrals", eyebrow: "Section 8", title: "AI Referral Traffic", intro: "Sessions on your site referred by AI answer engines (ChatGPT, Perplexity, Claude, Copilot, Gemini, etc.) \u2014 broken down by referrer with a daily trend and the top landing pages. Sourced from Google Analytics 4." },
|
|
3178
3215
|
`<div class="metric-grid">
|
|
3179
3216
|
<div class="metric"><div class="label">Total sessions</div><div class="value">${formatNumber(ai.totalSessions)}</div></div>
|
|
3180
3217
|
<div class="metric"><div class="label">Total users</div><div class="value">${formatNumber(ai.totalUsers)}</div></div>
|
|
@@ -3220,7 +3257,7 @@ function renderIndexingHealth(report) {
|
|
|
3220
3257
|
}).join("");
|
|
3221
3258
|
const legend = segments.map((s) => `<span><span class="legend-swatch" style="background:${s.color}"></span>${escapeHtml(s.label)}: ${s.count}</span>`).join("");
|
|
3222
3259
|
return section(
|
|
3223
|
-
{ id: "indexing-health", eyebrow: "Section 9", title: "Indexing Health", intro: `
|
|
3260
|
+
{ id: "indexing-health", eyebrow: "Section 9", title: "Indexing Health", intro: `What share of your tracked URLs are currently indexed in ${ih.provider === "google" ? "Google" : "Bing"} \u2014 sourced from ${ih.provider === "google" ? "Google Search Console URL Inspection" : "Bing Webmaster Tools URL Inspection"}. Pages absent from the index can\u2019t be retrieved by AI engines either.` },
|
|
3224
3261
|
`<div class="metric-grid">
|
|
3225
3262
|
<div class="metric"><div class="label">Indexed</div><div class="value tone-positive">${formatNumber(ih.indexed)}</div></div>
|
|
3226
3263
|
<div class="metric"><div class="label">Total inspected</div><div class="value">${formatNumber(ih.total)}</div></div>
|
|
@@ -3260,7 +3297,7 @@ function renderCitationsTrend(report) {
|
|
|
3260
3297
|
<td>${t.providerRates.map((r) => `${escapeHtml(r.provider)}: ${r.citationRate}%`).join(" \xB7 ")}</td>
|
|
3261
3298
|
</tr>`).join("");
|
|
3262
3299
|
return section(
|
|
3263
|
-
{ id: "citations-trend", eyebrow: "Section 10", title: "Citations Over Time", intro: "
|
|
3300
|
+
{ id: "citations-trend", eyebrow: "Section 10", title: "Citations Over Time", intro: "Citation rate across every visibility sweep \u2014 the share of (keyword \xD7 provider) pairs in each run where your domain appeared in the source list, with a per-provider breakdown beneath." },
|
|
3264
3301
|
`${chart}
|
|
3265
3302
|
<div class="chart-card"><h3>Run-by-run breakdown</h3>
|
|
3266
3303
|
<table class="report-table">
|
|
@@ -3291,7 +3328,7 @@ function renderInsights(report) {
|
|
|
3291
3328
|
</tr>`;
|
|
3292
3329
|
}).join("");
|
|
3293
3330
|
return section(
|
|
3294
|
-
{ id: "insights", eyebrow: "Section 11", title: "Insights & Alerts", intro: "
|
|
3331
|
+
{ id: "insights", eyebrow: "Section 11", title: "Insights & Alerts", intro: "Regressions (citations lost), gains (citations won), and opportunities surfaced by the intelligence engine across the most recent sweeps \u2014 ordered by severity and recurrence." },
|
|
3295
3332
|
`<table class="report-table">
|
|
3296
3333
|
<thead><tr><th>Severity</th><th>Title</th><th>Keyword</th><th>Provider</th><th>Recommendation</th></tr></thead>
|
|
3297
3334
|
<tbody>${rows}</tbody>
|
|
@@ -3301,8 +3338,9 @@ function renderInsights(report) {
|
|
|
3301
3338
|
function renderOpportunities(report) {
|
|
3302
3339
|
const opps = report.contentOpportunities;
|
|
3303
3340
|
if (opps.length === 0) return "";
|
|
3341
|
+
const canonical = report.meta.project.canonicalDomain;
|
|
3304
3342
|
const rows = opps.slice(0, 10).map((o) => {
|
|
3305
|
-
const ourPage = o.ourBestPage ? `<a href="${escapeHtml(o.ourBestPage.url)}">${escapeHtml(o.ourBestPage.url)}</a>` : '<span class="cell-not-cited">\u2014</span>';
|
|
3343
|
+
const ourPage = o.ourBestPage ? `<a href="${escapeHtml(absolutizeProjectUrl(o.ourBestPage.url, canonical))}">${escapeHtml(o.ourBestPage.url)}</a>` : '<span class="cell-not-cited">\u2014</span>';
|
|
3306
3344
|
const winning = o.winningCompetitor ? `<a href="${escapeHtml(o.winningCompetitor.url)}">${escapeHtml(o.winningCompetitor.domain)}</a>` : '<span class="cell-not-cited">\u2014</span>';
|
|
3307
3345
|
return `<tr>
|
|
3308
3346
|
<td>${escapeHtml(o.query)}</td>
|
|
@@ -3319,7 +3357,7 @@ function renderOpportunities(report) {
|
|
|
3319
3357
|
id: "content-opportunities",
|
|
3320
3358
|
eyebrow: "Section 12",
|
|
3321
3359
|
title: "Content Opportunities",
|
|
3322
|
-
intro: "
|
|
3360
|
+
intro: "Queries where you have search demand or competitor citation pressure but aren\u2019t winning AI citations. Each row carries a suggested action (create / refresh / expand / add-schema). Top 10 shown."
|
|
3323
3361
|
},
|
|
3324
3362
|
`<table class="report-table">
|
|
3325
3363
|
<thead><tr><th>Query</th><th>Action</th><th class="numeric">Score</th><th>Our page</th><th>Winning competitor</th><th>Demand</th><th>Confidence</th></tr></thead>
|
|
@@ -3331,7 +3369,7 @@ function renderRecommendedNextSteps(report) {
|
|
|
3331
3369
|
const steps = report.recommendedNextSteps;
|
|
3332
3370
|
if (steps.length === 0) {
|
|
3333
3371
|
return section(
|
|
3334
|
-
{ id: "recommended-next-steps", eyebrow: "Section 13", title: "Recommended Next Steps" },
|
|
3372
|
+
{ id: "recommended-next-steps", eyebrow: "Section 13", title: "Recommended Next Steps", intro: "Action items bucketed by horizon (immediate, short-term, medium-term), drawn from open insights and the highest-ranked content opportunities." },
|
|
3335
3373
|
renderEmpty("No outstanding actions.")
|
|
3336
3374
|
);
|
|
3337
3375
|
}
|
|
@@ -3342,7 +3380,7 @@ function renderRecommendedNextSteps(report) {
|
|
|
3342
3380
|
<span class="rationale">${escapeHtml(s.rationale)}</span>
|
|
3343
3381
|
</div>`).join("");
|
|
3344
3382
|
return section(
|
|
3345
|
-
{ id: "recommended-next-steps", eyebrow: "Section 13", title: "Recommended Next Steps" },
|
|
3383
|
+
{ id: "recommended-next-steps", eyebrow: "Section 13", title: "Recommended Next Steps", intro: "Action items bucketed by horizon (immediate, short-term, medium-term), drawn from open insights and the highest-ranked content opportunities." },
|
|
3346
3384
|
`<div class="steps">${items}</div>`
|
|
3347
3385
|
);
|
|
3348
3386
|
}
|
|
@@ -3818,6 +3856,56 @@ function buildCompetitorLandscape(snapshots, competitorDomains, projectDomains,
|
|
|
3818
3856
|
competitorRows.sort((a, b) => b.citationCount - a.citationCount);
|
|
3819
3857
|
return { projectCitationCount, competitors: competitorRows };
|
|
3820
3858
|
}
|
|
3859
|
+
function buildMentionLandscape(snapshots, competitorDomains, projectDisplayName, projectDomains, keywordLookup) {
|
|
3860
|
+
let projectMentionCount = 0;
|
|
3861
|
+
let totalAnswerSnapshots = 0;
|
|
3862
|
+
const competitorMap = /* @__PURE__ */ new Map();
|
|
3863
|
+
for (const c of competitorDomains) {
|
|
3864
|
+
competitorMap.set(c, { count: 0, keywords: /* @__PURE__ */ new Set() });
|
|
3865
|
+
}
|
|
3866
|
+
for (const snap of snapshots) {
|
|
3867
|
+
const text = snap.answerText;
|
|
3868
|
+
if (!text) continue;
|
|
3869
|
+
totalAnswerSnapshots++;
|
|
3870
|
+
const kw = keywordLookup.byId.get(snap.keywordId);
|
|
3871
|
+
const projectMentioned = snap.answerMentioned ?? determineAnswerMentioned(
|
|
3872
|
+
text,
|
|
3873
|
+
projectDisplayName,
|
|
3874
|
+
projectDomains
|
|
3875
|
+
);
|
|
3876
|
+
if (projectMentioned) projectMentionCount++;
|
|
3877
|
+
for (const competitor of competitorDomains) {
|
|
3878
|
+
const brand = brandLabelFromDomain(competitor);
|
|
3879
|
+
const mentioned = determineAnswerMentioned(text, brand, [competitor]);
|
|
3880
|
+
if (mentioned) {
|
|
3881
|
+
const entry = competitorMap.get(competitor);
|
|
3882
|
+
entry.count++;
|
|
3883
|
+
if (kw) entry.keywords.add(kw);
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
const totalMentionedSlots = projectMentionCount + [...competitorMap.values()].reduce((sum, v) => sum + v.count, 0);
|
|
3888
|
+
const competitorRows = [...competitorMap.entries()].map(([domain, data]) => {
|
|
3889
|
+
const ratio = totalAnswerSnapshots > 0 ? data.count / totalAnswerSnapshots : 0;
|
|
3890
|
+
let pressureLabel = "None";
|
|
3891
|
+
if (data.count > 0) {
|
|
3892
|
+
if (ratio >= 0.5) pressureLabel = "High";
|
|
3893
|
+
else if (ratio >= 0.2) pressureLabel = "Moderate";
|
|
3894
|
+
else pressureLabel = "Low";
|
|
3895
|
+
}
|
|
3896
|
+
const sharePct = totalMentionedSlots > 0 ? Math.round(data.count / totalMentionedSlots * 100) : 0;
|
|
3897
|
+
return {
|
|
3898
|
+
domain,
|
|
3899
|
+
mentionCount: data.count,
|
|
3900
|
+
totalCount: totalAnswerSnapshots,
|
|
3901
|
+
pressureLabel,
|
|
3902
|
+
mentionedKeywords: [...data.keywords].sort(),
|
|
3903
|
+
sharePct
|
|
3904
|
+
};
|
|
3905
|
+
});
|
|
3906
|
+
competitorRows.sort((a, b) => b.mentionCount - a.mentionCount);
|
|
3907
|
+
return { projectMentionCount, totalAnswerSnapshots, competitors: competitorRows };
|
|
3908
|
+
}
|
|
3821
3909
|
function buildAiSourceOrigin(snapshots, projectDomains, competitorDomains) {
|
|
3822
3910
|
const categoryCounts = /* @__PURE__ */ new Map();
|
|
3823
3911
|
const domainCounts = /* @__PURE__ */ new Map();
|
|
@@ -4262,6 +4350,13 @@ function buildProjectReport(db, projectName) {
|
|
|
4262
4350
|
projectDomains,
|
|
4263
4351
|
keywordLookup
|
|
4264
4352
|
);
|
|
4353
|
+
const mentionLandscape = buildMentionLandscape(
|
|
4354
|
+
latestSnapshots,
|
|
4355
|
+
competitorDomains,
|
|
4356
|
+
project.displayName,
|
|
4357
|
+
projectDomains,
|
|
4358
|
+
keywordLookup
|
|
4359
|
+
);
|
|
4265
4360
|
const aiSourceOrigin = buildAiSourceOrigin(latestSnapshots, projectDomains, competitorDomains);
|
|
4266
4361
|
const trackedKeywords = [...keywordLookup.byId.values()];
|
|
4267
4362
|
const gscSection = buildGscSection(
|
|
@@ -4354,6 +4449,7 @@ function buildProjectReport(db, projectName) {
|
|
|
4354
4449
|
},
|
|
4355
4450
|
citationScorecard,
|
|
4356
4451
|
competitorLandscape,
|
|
4452
|
+
mentionLandscape,
|
|
4357
4453
|
aiSourceOrigin,
|
|
4358
4454
|
gsc: gscSection,
|
|
4359
4455
|
ga: gaSection,
|
|
@@ -7132,7 +7228,7 @@ var routeCatalog = [
|
|
|
7132
7228
|
path: "/api/v1/projects/{name}/report",
|
|
7133
7229
|
summary: "Aggregated client-facing AEO report",
|
|
7134
7230
|
tags: ["report"],
|
|
7135
|
-
description: "Bundles every section the canonry-report HTML output needs (executive summary, citation scorecard, competitor landscape, AI
|
|
7231
|
+
description: "Bundles every section the canonry-report HTML output needs (executive summary, citation scorecard, competitor landscape \u2014 citation + mention landscapes, AI citation sources, GSC, GA4, social/AI referrals, indexing health, citations trend, insights, and recommended next steps) into a single JSON payload. Backs `canonry report <project>`.",
|
|
7136
7232
|
parameters: [nameParameter],
|
|
7137
7233
|
responses: {
|
|
7138
7234
|
200: { description: "Report returned." },
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
projectUpsertRequestSchema,
|
|
11
11
|
runTriggerRequestSchema,
|
|
12
12
|
scheduleUpsertRequestSchema
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-RDX6GBWM.js";
|
|
14
14
|
|
|
15
15
|
// src/config.ts
|
|
16
16
|
import fs from "fs";
|
|
@@ -1135,7 +1135,7 @@ var canonryMcpTools = [
|
|
|
1135
1135
|
defineTool({
|
|
1136
1136
|
name: "canonry_report",
|
|
1137
1137
|
title: "Get aggregated AEO report",
|
|
1138
|
-
description: "Returns the full client-facing AEO report bundle for a project \u2014 executive summary, per-keyword \xD7 per-provider citation matrix, competitor landscape, AI
|
|
1138
|
+
description: "Returns the full client-facing AEO report bundle for a project \u2014 executive summary, per-keyword \xD7 per-provider citation matrix, competitor landscape, AI citation sources, GSC/GA4 performance, social and AI referrals, indexing health, citations trend, prioritized insights, and recommended next steps. Same payload `canonry report <project>` consumes to render the self-contained HTML.",
|
|
1139
1139
|
access: "read",
|
|
1140
1140
|
tier: "monitoring",
|
|
1141
1141
|
inputSchema: projectInputSchema,
|
|
@@ -1711,6 +1711,17 @@ function dropTrailingSlash(path) {
|
|
|
1711
1711
|
}
|
|
1712
1712
|
return path;
|
|
1713
1713
|
}
|
|
1714
|
+
function absolutizeProjectUrl(url, canonicalDomain) {
|
|
1715
|
+
if (!url) return "";
|
|
1716
|
+
const trimmed = url.trim();
|
|
1717
|
+
if (!trimmed) return "";
|
|
1718
|
+
if (/^https?:\/\//i.test(trimmed)) return trimmed;
|
|
1719
|
+
if (trimmed.startsWith("//")) return `https:${trimmed}`;
|
|
1720
|
+
const host = canonicalDomain.trim().replace(/^https?:\/\//i, "").replace(/\/+$/, "");
|
|
1721
|
+
if (!host) return trimmed;
|
|
1722
|
+
if (trimmed.startsWith("/")) return `https://${host}${trimmed}`;
|
|
1723
|
+
return `https://${host}/${trimmed}`;
|
|
1724
|
+
}
|
|
1714
1725
|
function normalizeUrlPath(input) {
|
|
1715
1726
|
if (input == null) return null;
|
|
1716
1727
|
let trimmed = input.trim();
|
|
@@ -1910,6 +1921,7 @@ export {
|
|
|
1910
1921
|
CheckScopes,
|
|
1911
1922
|
CheckCategories,
|
|
1912
1923
|
summarizeCheckResults,
|
|
1924
|
+
absolutizeProjectUrl,
|
|
1913
1925
|
normalizeUrlPath,
|
|
1914
1926
|
emptyCitationVisibility,
|
|
1915
1927
|
citationStateToCited,
|
package/dist/cli.js
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
setGoogleAuthConfig,
|
|
19
19
|
showFirstRunNotice,
|
|
20
20
|
trackEvent
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-CG4HEQAK.js";
|
|
22
22
|
import {
|
|
23
23
|
CliError,
|
|
24
24
|
EXIT_SYSTEM_ERROR,
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
saveConfig,
|
|
34
34
|
saveConfigPatch,
|
|
35
35
|
usageError
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-GLPZ5NVP.js";
|
|
37
37
|
import {
|
|
38
38
|
apiKeys,
|
|
39
39
|
competitors,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
projects,
|
|
46
46
|
querySnapshots,
|
|
47
47
|
runs
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-W463NVVC.js";
|
|
49
49
|
import {
|
|
50
50
|
CcReleaseSyncStatuses,
|
|
51
51
|
CheckScopes,
|
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
providerQuotaPolicySchema,
|
|
64
64
|
resolveProviderInput,
|
|
65
65
|
skillsClientSchema
|
|
66
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-RDX6GBWM.js";
|
|
67
67
|
|
|
68
68
|
// src/cli.ts
|
|
69
69
|
import { pathToFileURL } from "url";
|
|
@@ -579,7 +579,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
579
579
|
return result;
|
|
580
580
|
}
|
|
581
581
|
async function backfillInsightsCommand(project, opts) {
|
|
582
|
-
const { IntelligenceService } = await import("./intelligence-service-
|
|
582
|
+
const { IntelligenceService } = await import("./intelligence-service-WZUM3AX6.js");
|
|
583
583
|
const config = loadConfig();
|
|
584
584
|
const db = createClient(config.database);
|
|
585
585
|
migrate(db);
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-CG4HEQAK.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-GLPZ5NVP.js";
|
|
7
|
+
import "./chunk-W463NVVC.js";
|
|
8
|
+
import "./chunk-RDX6GBWM.js";
|
|
9
9
|
export {
|
|
10
10
|
createServer,
|
|
11
11
|
loadConfig
|
package/dist/mcp.js
CHANGED
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
CliError,
|
|
3
3
|
canonryMcpTools,
|
|
4
4
|
createApiClient
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-GLPZ5NVP.js";
|
|
6
|
+
import "./chunk-RDX6GBWM.js";
|
|
7
7
|
|
|
8
8
|
// src/mcp/cli.ts
|
|
9
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainyc/canonry",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.3",
|
|
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",
|
|
@@ -59,21 +59,21 @@
|
|
|
59
59
|
"@types/node-cron": "^3.0.11",
|
|
60
60
|
"tsup": "^8.5.1",
|
|
61
61
|
"tsx": "^4.19.0",
|
|
62
|
-
"@ainyc/canonry-config": "0.0.0",
|
|
63
|
-
"@ainyc/canonry-db": "0.0.0",
|
|
64
|
-
"@ainyc/canonry-intelligence": "0.0.0",
|
|
65
62
|
"@ainyc/canonry-api-routes": "0.0.0",
|
|
66
63
|
"@ainyc/canonry-contracts": "0.0.0",
|
|
64
|
+
"@ainyc/canonry-config": "0.0.0",
|
|
65
|
+
"@ainyc/canonry-intelligence": "0.0.0",
|
|
66
|
+
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
67
67
|
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
68
|
+
"@ainyc/canonry-db": "0.0.0",
|
|
68
69
|
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
70
|
+
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
69
71
|
"@ainyc/canonry-integration-google": "0.0.0",
|
|
70
|
-
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
71
72
|
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
72
73
|
"@ainyc/canonry-provider-local": "0.0.0",
|
|
73
|
-
"@ainyc/canonry-provider-perplexity": "0.0.0",
|
|
74
74
|
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
75
|
-
"@ainyc/canonry-provider-
|
|
76
|
-
"@ainyc/canonry-provider-
|
|
75
|
+
"@ainyc/canonry-provider-gemini": "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",
|