@canonry/canonry 4.125.0 → 4.127.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +5 -0
- package/assets/assets/{AuditHistoryPanel-C-KSU-dr.js → AuditHistoryPanel-DwJpVWme.js} +1 -1
- package/assets/assets/{BacklinksPage-BH4njwCh.js → BacklinksPage-K8FQUBxI.js} +1 -1
- package/assets/assets/{ChartPrimitives-BhB9Kxzc.js → ChartPrimitives-6oFKrJFk.js} +1 -1
- package/assets/assets/{HistoryPage-BQVNx0eu.js → HistoryPage-DwOAoN1Z.js} +1 -1
- package/assets/assets/{ProjectPage-DnkNE1AW.js → ProjectPage-CdgFUk7h.js} +1 -1
- package/assets/assets/{RunRow-70N3oGz8.js → RunRow-lt9IS99U.js} +1 -1
- package/assets/assets/{RunsPage-CtuKRP0o.js → RunsPage-DitYcIdx.js} +1 -1
- package/assets/assets/{SettingsPage-B1ZGnDz8.js → SettingsPage-CAOrh65Q.js} +1 -1
- package/assets/assets/{TrafficPage-BOGbXHd9.js → TrafficPage-BqzNvx75.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-5jj7n7W1.js → TrafficSourceDetailPage-DXbygZUA.js} +1 -1
- package/assets/assets/{arrow-left-gOf0lyu3.js → arrow-left-AmG4gtUR.js} +1 -1
- package/assets/assets/{extract-error-message-CZKcwJhv.js → extract-error-message-DlVtjoPz.js} +1 -1
- package/assets/assets/{index-B_CDPjOg.js → index-BiCDkOH4.js} +3 -3
- package/assets/assets/{trash-2-BnFgG3Wq.js → trash-2-gFHWd1A1.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-DN5H2ZN7.js → chunk-A4YBV4Y4.js} +35 -4
- package/dist/{chunk-RFLNTI7Y.js → chunk-BLSHHRRU.js} +21 -5
- package/dist/{chunk-YPRUBNUK.js → chunk-HQRUFTLN.js} +272 -61
- package/dist/{chunk-WA57WG6W.js → chunk-IHRW6WWV.js} +1 -1
- package/dist/cli.js +4 -4
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-WJ2ZR7GF.js → intelligence-service-2WKS3EWJ.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +8 -8
|
@@ -336,7 +336,7 @@ import {
|
|
|
336
336
|
wordpressSchemaDeployResultDtoSchema,
|
|
337
337
|
wordpressSchemaStatusResultDtoSchema,
|
|
338
338
|
wordpressStatusDtoSchema
|
|
339
|
-
} from "./chunk-
|
|
339
|
+
} from "./chunk-BLSHHRRU.js";
|
|
340
340
|
|
|
341
341
|
// src/intelligence-service.ts
|
|
342
342
|
import { eq as eq41, desc as desc18, asc as asc9, and as and32, ne as ne6, or as or8, inArray as inArray15, gte as gte10, lte as lte6 } from "drizzle-orm";
|
|
@@ -396,6 +396,7 @@ __export(schema_exports, {
|
|
|
396
396
|
googleConnections: () => googleConnections,
|
|
397
397
|
gscCoverageSnapshots: () => gscCoverageSnapshots,
|
|
398
398
|
gscDailyTotals: () => gscDailyTotals,
|
|
399
|
+
gscQueryDailyTotals: () => gscQueryDailyTotals,
|
|
399
400
|
gscSearchData: () => gscSearchData,
|
|
400
401
|
gscUrlInspections: () => gscUrlInspections,
|
|
401
402
|
healthSnapshots: () => healthSnapshots,
|
|
@@ -656,6 +657,27 @@ var gscDailyTotals = sqliteTable("gsc_daily_totals", {
|
|
|
656
657
|
uniqueIndex("idx_gsc_daily_totals_project_date").on(table.projectId, table.date),
|
|
657
658
|
index("idx_gsc_daily_totals_project").on(table.projectId)
|
|
658
659
|
]);
|
|
660
|
+
var gscQueryDailyTotals = sqliteTable("gsc_query_daily_totals", {
|
|
661
|
+
id: text("id").primaryKey(),
|
|
662
|
+
projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
663
|
+
date: text("date").notNull(),
|
|
664
|
+
query: text("query").notNull(),
|
|
665
|
+
clicks: integer("clicks").notNull().default(0),
|
|
666
|
+
impressions: integer("impressions").notNull().default(0),
|
|
667
|
+
/**
|
|
668
|
+
* Stored as a string like `gscSearchData.position` (parsed on read). CTR is
|
|
669
|
+
* derived (clicks / impressions) and intentionally not stored.
|
|
670
|
+
*/
|
|
671
|
+
position: text("position").notNull().default("0"),
|
|
672
|
+
syncedAt: text("synced_at").notNull(),
|
|
673
|
+
syncRunId: text("sync_run_id").references(() => runs.id, { onDelete: "cascade" }),
|
|
674
|
+
createdAt: text("created_at").notNull()
|
|
675
|
+
}, (table) => [
|
|
676
|
+
uniqueIndex("idx_gsc_query_daily_totals_project_date_query").on(table.projectId, table.date, table.query),
|
|
677
|
+
index("idx_gsc_query_daily_totals_project_date").on(table.projectId, table.date),
|
|
678
|
+
index("idx_gsc_query_daily_totals_query").on(table.query),
|
|
679
|
+
index("idx_gsc_query_daily_totals_run").on(table.syncRunId)
|
|
680
|
+
]);
|
|
659
681
|
var gscUrlInspections = sqliteTable("gsc_url_inspections", {
|
|
660
682
|
id: text("id").primaryKey(),
|
|
661
683
|
projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
@@ -3971,6 +3993,34 @@ var MIGRATION_VERSIONS = [
|
|
|
3971
3993
|
`CREATE INDEX IF NOT EXISTS idx_ga_daily_totals_project ON ga_daily_totals(project_id)`,
|
|
3972
3994
|
`CREATE INDEX IF NOT EXISTS idx_ga_daily_totals_run ON ga_daily_totals(sync_run_id)`
|
|
3973
3995
|
]
|
|
3996
|
+
},
|
|
3997
|
+
{
|
|
3998
|
+
// Per-query daily totals fetched WITHOUT the `page` dimension. Summing
|
|
3999
|
+
// `gsc_search_data` by query multiplies impressions by how many of the
|
|
4000
|
+
// site's pages ranked on the same SERP — ~0% for single-page queries but
|
|
4001
|
+
// ~500% for brand+category terms, which reorders a top-queries table.
|
|
4002
|
+
// Dropping `page` makes Google do the dedup. Per-query counterpart to
|
|
4003
|
+
// `gsc_daily_totals`.
|
|
4004
|
+
version: 107,
|
|
4005
|
+
name: "gsc-query-daily-totals",
|
|
4006
|
+
statements: [
|
|
4007
|
+
`CREATE TABLE IF NOT EXISTS gsc_query_daily_totals (
|
|
4008
|
+
id TEXT PRIMARY KEY,
|
|
4009
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
4010
|
+
date TEXT NOT NULL,
|
|
4011
|
+
query TEXT NOT NULL,
|
|
4012
|
+
clicks INTEGER NOT NULL DEFAULT 0,
|
|
4013
|
+
impressions INTEGER NOT NULL DEFAULT 0,
|
|
4014
|
+
position TEXT NOT NULL DEFAULT '0',
|
|
4015
|
+
synced_at TEXT NOT NULL,
|
|
4016
|
+
sync_run_id TEXT REFERENCES runs(id) ON DELETE CASCADE,
|
|
4017
|
+
created_at TEXT NOT NULL
|
|
4018
|
+
)`,
|
|
4019
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_gsc_query_daily_totals_project_date_query ON gsc_query_daily_totals(project_id, date, query)`,
|
|
4020
|
+
`CREATE INDEX IF NOT EXISTS idx_gsc_query_daily_totals_project_date ON gsc_query_daily_totals(project_id, date)`,
|
|
4021
|
+
`CREATE INDEX IF NOT EXISTS idx_gsc_query_daily_totals_query ON gsc_query_daily_totals(query)`,
|
|
4022
|
+
`CREATE INDEX IF NOT EXISTS idx_gsc_query_daily_totals_run ON gsc_query_daily_totals(sync_run_id)`
|
|
4023
|
+
]
|
|
3974
4024
|
}
|
|
3975
4025
|
];
|
|
3976
4026
|
function rebuildBacklinkTableWithSource(tx, table) {
|
|
@@ -9584,14 +9634,24 @@ function computeQueryChanges(projectQueries, cutoff) {
|
|
|
9584
9634
|
label: `+${count2} kp`
|
|
9585
9635
|
}));
|
|
9586
9636
|
}
|
|
9637
|
+
function pooledRate(buckets, rateKey) {
|
|
9638
|
+
const countKey = rateKey === "citationRate" ? "cited" : "mentionedCount";
|
|
9639
|
+
let numerator = 0;
|
|
9640
|
+
let denominator = 0;
|
|
9641
|
+
for (const bucket of buckets) {
|
|
9642
|
+
numerator += bucket[countKey];
|
|
9643
|
+
denominator += bucket.total;
|
|
9644
|
+
}
|
|
9645
|
+
return denominator > 0 ? numerator / denominator : 0;
|
|
9646
|
+
}
|
|
9587
9647
|
function computeTrend(buckets, rateKey) {
|
|
9588
9648
|
const nonEmpty = buckets.filter((b) => b.total > 0);
|
|
9589
9649
|
if (nonEmpty.length < 2) return "stable";
|
|
9590
9650
|
const mid = Math.floor(nonEmpty.length / 2);
|
|
9591
9651
|
const firstHalf = nonEmpty.slice(0, mid);
|
|
9592
9652
|
const secondHalf = nonEmpty.slice(mid);
|
|
9593
|
-
const avgFirst = firstHalf
|
|
9594
|
-
const avgSecond = secondHalf
|
|
9653
|
+
const avgFirst = pooledRate(firstHalf, rateKey);
|
|
9654
|
+
const avgSecond = pooledRate(secondHalf, rateKey);
|
|
9595
9655
|
const diff = avgSecond - avgFirst;
|
|
9596
9656
|
if (diff > 0.05) return "improving";
|
|
9597
9657
|
if (diff < -0.05) return "declining";
|
|
@@ -10048,7 +10108,12 @@ function buildGaTrafficByPage(db, projectId) {
|
|
|
10048
10108
|
return map;
|
|
10049
10109
|
}
|
|
10050
10110
|
function sumAiReferralSessions(db, projectId) {
|
|
10051
|
-
const rows = db.select({ sessions: gaAiReferrals.sessions }).from(gaAiReferrals).where(
|
|
10111
|
+
const rows = db.select({ sessions: gaAiReferrals.sessions }).from(gaAiReferrals).where(
|
|
10112
|
+
and9(
|
|
10113
|
+
eq13(gaAiReferrals.projectId, projectId),
|
|
10114
|
+
eq13(gaAiReferrals.sourceDimension, "session")
|
|
10115
|
+
)
|
|
10116
|
+
).all();
|
|
10052
10117
|
return rows.reduce((acc, r) => acc + (r.sessions ?? 0), 0);
|
|
10053
10118
|
}
|
|
10054
10119
|
function buildCandidateQueries(opts) {
|
|
@@ -10636,6 +10701,64 @@ function mergeGscDailyTotalsWithFallback(propertyTotals, dimensionedFallback) {
|
|
|
10636
10701
|
for (const row of propertyTotals) byDate.set(row.date, row);
|
|
10637
10702
|
return [...byDate.values()].sort((a, b) => a.date.localeCompare(b.date));
|
|
10638
10703
|
}
|
|
10704
|
+
function readGscQueryDailyRows(db, projectId, startDate, endDate) {
|
|
10705
|
+
const rows = db.select({
|
|
10706
|
+
date: gscQueryDailyTotals.date,
|
|
10707
|
+
query: gscQueryDailyTotals.query,
|
|
10708
|
+
clicks: gscQueryDailyTotals.clicks,
|
|
10709
|
+
impressions: gscQueryDailyTotals.impressions,
|
|
10710
|
+
position: gscQueryDailyTotals.position
|
|
10711
|
+
}).from(gscQueryDailyTotals).where(
|
|
10712
|
+
and11(
|
|
10713
|
+
eq15(gscQueryDailyTotals.projectId, projectId),
|
|
10714
|
+
sql6`${gscQueryDailyTotals.date} >= ${startDate}`,
|
|
10715
|
+
sql6`${gscQueryDailyTotals.date} <= ${endDate}`
|
|
10716
|
+
)
|
|
10717
|
+
).all();
|
|
10718
|
+
return rows.map((r) => {
|
|
10719
|
+
const position = Number(r.position);
|
|
10720
|
+
return {
|
|
10721
|
+
date: r.date,
|
|
10722
|
+
query: r.query,
|
|
10723
|
+
clicks: r.clicks,
|
|
10724
|
+
impressions: r.impressions,
|
|
10725
|
+
position: Number.isFinite(position) ? position : 0
|
|
10726
|
+
};
|
|
10727
|
+
});
|
|
10728
|
+
}
|
|
10729
|
+
function mergeGscQueryTotalsWithFallback(accurateDays, fallbackDays) {
|
|
10730
|
+
const dayKey = (r) => `${r.date}\0${r.query}`;
|
|
10731
|
+
const byDay = /* @__PURE__ */ new Map();
|
|
10732
|
+
for (const row of fallbackDays) byDay.set(dayKey(row), { row, accurate: false });
|
|
10733
|
+
for (const row of accurateDays) byDay.set(dayKey(row), { row, accurate: true });
|
|
10734
|
+
const byQuery = /* @__PURE__ */ new Map();
|
|
10735
|
+
for (const { row, accurate } of byDay.values()) {
|
|
10736
|
+
const acc = byQuery.get(row.query) ?? {
|
|
10737
|
+
clicks: 0,
|
|
10738
|
+
impressions: 0,
|
|
10739
|
+
weightedPositionSum: 0,
|
|
10740
|
+
positionSum: 0,
|
|
10741
|
+
positionDays: 0,
|
|
10742
|
+
sawAccurate: false,
|
|
10743
|
+
sawLegacy: false
|
|
10744
|
+
};
|
|
10745
|
+
acc.clicks += row.clicks;
|
|
10746
|
+
acc.impressions += row.impressions;
|
|
10747
|
+
acc.weightedPositionSum += row.position * row.impressions;
|
|
10748
|
+
acc.positionSum += row.position;
|
|
10749
|
+
acc.positionDays += 1;
|
|
10750
|
+
if (accurate) acc.sawAccurate = true;
|
|
10751
|
+
else acc.sawLegacy = true;
|
|
10752
|
+
byQuery.set(row.query, acc);
|
|
10753
|
+
}
|
|
10754
|
+
return [...byQuery.entries()].map(([query, acc]) => ({
|
|
10755
|
+
query,
|
|
10756
|
+
clicks: acc.clicks,
|
|
10757
|
+
impressions: acc.impressions,
|
|
10758
|
+
position: acc.impressions > 0 ? acc.weightedPositionSum / acc.impressions : acc.positionDays > 0 ? acc.positionSum / acc.positionDays : 0,
|
|
10759
|
+
source: acc.sawAccurate && acc.sawLegacy ? "mixed" : acc.sawAccurate ? "google" : "page-summed"
|
|
10760
|
+
}));
|
|
10761
|
+
}
|
|
10639
10762
|
|
|
10640
10763
|
// ../api-routes/src/report-renderer.ts
|
|
10641
10764
|
var COLORS = {
|
|
@@ -12235,7 +12358,6 @@ function renderAiReferrals(report) {
|
|
|
12235
12358
|
<tr>
|
|
12236
12359
|
<td class="page-cell">${formatLandingPageHtml(p.page)}</td>
|
|
12237
12360
|
<td class="numeric">${formatNumber(p.sessions)}</td>
|
|
12238
|
-
<td class="numeric">${formatNumber(p.users)}</td>
|
|
12239
12361
|
</tr>`).join("");
|
|
12240
12362
|
const trendChart = renderLineChart(
|
|
12241
12363
|
ai.trend.map((t) => ({ x: t.date, y: t.sessions, label: t.date.slice(5) })),
|
|
@@ -12246,13 +12368,12 @@ function renderAiReferrals(report) {
|
|
|
12246
12368
|
{ id: "ai-referrals", eyebrow: "Section 9", title: "AI Referral Traffic", intro: "Traffic arriving from AI answer engines." },
|
|
12247
12369
|
`<div class="metric-grid">
|
|
12248
12370
|
<div class="metric"><div class="label">Total sessions</div><div class="value">${formatNumber(ai.totalSessions)}</div></div>
|
|
12249
|
-
<div class="metric"><div class="label">Total users</div><div class="value">${formatNumber(ai.totalUsers)}</div></div>
|
|
12250
12371
|
</div>
|
|
12251
12372
|
${trendChart}
|
|
12252
12373
|
${sourceBars}
|
|
12253
12374
|
<div class="chart-card"><h3>Top AI landing pages</h3>
|
|
12254
12375
|
<table class="report-table">
|
|
12255
|
-
<thead><tr><th>Page</th><th class="numeric">Sessions</th
|
|
12376
|
+
<thead><tr><th>Page</th><th class="numeric">Sessions</th></tr></thead>
|
|
12256
12377
|
<tbody>${pageRows}</tbody>
|
|
12257
12378
|
</table>
|
|
12258
12379
|
</div>`
|
|
@@ -12969,15 +13090,16 @@ function buildGscSection(db, projectId, projectBrandNames, canonicalDomain, trac
|
|
|
12969
13090
|
const dailyTotals = allDailyTotals.filter((r) => r.date >= startDate && r.date <= maxDate);
|
|
12970
13091
|
if (rows.length === 0 && dailyTotals.length === 0) return null;
|
|
12971
13092
|
let dimensionedClicks = 0;
|
|
12972
|
-
const
|
|
13093
|
+
const queryDayAgg = /* @__PURE__ */ new Map();
|
|
12973
13094
|
const dimensionedTrendAgg = /* @__PURE__ */ new Map();
|
|
12974
13095
|
for (const r of rows) {
|
|
12975
13096
|
dimensionedClicks += r.clicks;
|
|
12976
|
-
const
|
|
13097
|
+
const dayKey = `${r.date} ${r.query}`;
|
|
13098
|
+
const q = queryDayAgg.get(dayKey) ?? { date: r.date, query: r.query, clicks: 0, impressions: 0, weightedPositionSum: 0 };
|
|
12977
13099
|
q.clicks += r.clicks;
|
|
12978
13100
|
q.impressions += r.impressions;
|
|
12979
13101
|
q.weightedPositionSum += safeNum(r.position) * r.impressions;
|
|
12980
|
-
|
|
13102
|
+
queryDayAgg.set(dayKey, q);
|
|
12981
13103
|
const t = dimensionedTrendAgg.get(r.date) ?? { clicks: 0, impressions: 0, weightedPositionSum: 0 };
|
|
12982
13104
|
t.clicks += r.clicks;
|
|
12983
13105
|
t.impressions += r.impressions;
|
|
@@ -13004,17 +13126,27 @@ function buildGscSection(db, projectId, projectBrandNames, canonicalDomain, trac
|
|
|
13004
13126
|
const trend = dailySeries.map((d) => ({ date: d.date, clicks: d.clicks, impressions: d.impressions }));
|
|
13005
13127
|
const ctr = totalImpressions > 0 ? totalClicks / totalImpressions : 0;
|
|
13006
13128
|
const avgPosition = totalImpressions > 0 ? weightedPositionSum / totalImpressions : 0;
|
|
13007
|
-
const
|
|
13008
|
-
|
|
13129
|
+
const queryTotals = mergeGscQueryTotalsWithFallback(
|
|
13130
|
+
readGscQueryDailyRows(db, projectId, startDate, maxDate),
|
|
13131
|
+
[...queryDayAgg.values()].map((agg) => ({
|
|
13132
|
+
date: agg.date,
|
|
13133
|
+
query: agg.query,
|
|
13134
|
+
clicks: agg.clicks,
|
|
13135
|
+
impressions: agg.impressions,
|
|
13136
|
+
position: agg.impressions > 0 ? agg.weightedPositionSum / agg.impressions : 0
|
|
13137
|
+
}))
|
|
13138
|
+
);
|
|
13139
|
+
const topQueries = queryTotals.map((agg) => ({
|
|
13140
|
+
query: agg.query,
|
|
13009
13141
|
clicks: agg.clicks,
|
|
13010
13142
|
impressions: agg.impressions,
|
|
13011
13143
|
ctr: agg.impressions > 0 ? agg.clicks / agg.impressions : 0,
|
|
13012
|
-
avgPosition: agg.
|
|
13013
|
-
category: categorizeQuery(query, projectBrandNames, canonicalDomain)
|
|
13144
|
+
avgPosition: agg.position,
|
|
13145
|
+
category: categorizeQuery(agg.query, projectBrandNames, canonicalDomain)
|
|
13014
13146
|
})).sort((a, b) => b.clicks - a.clicks).slice(0, TOP_QUERIES_LIMIT);
|
|
13015
13147
|
const categoryAgg = /* @__PURE__ */ new Map();
|
|
13016
|
-
for (const
|
|
13017
|
-
const cat = categorizeQuery(query, projectBrandNames, canonicalDomain);
|
|
13148
|
+
for (const agg of queryTotals) {
|
|
13149
|
+
const cat = categorizeQuery(agg.query, projectBrandNames, canonicalDomain);
|
|
13018
13150
|
const bucket = categoryAgg.get(cat) ?? { clicks: 0, impressions: 0 };
|
|
13019
13151
|
bucket.clicks += agg.clicks;
|
|
13020
13152
|
bucket.impressions += agg.impressions;
|
|
@@ -13029,9 +13161,9 @@ function buildGscSection(db, projectId, projectBrandNames, canonicalDomain, trac
|
|
|
13029
13161
|
const periodStart = trend[0]?.date ?? "";
|
|
13030
13162
|
const periodEnd = trend.at(-1)?.date ?? "";
|
|
13031
13163
|
const trackedSet = new Set(trackedQueries.map((q) => q.toLowerCase()));
|
|
13032
|
-
const gscQuerySet = new Set(
|
|
13164
|
+
const gscQuerySet = new Set(queryTotals.map((q) => q.query.toLowerCase()));
|
|
13033
13165
|
const trackedButNoGsc = trackedQueries.filter((q) => !gscQuerySet.has(q.toLowerCase())).sort();
|
|
13034
|
-
const gscButNotTracked = [...
|
|
13166
|
+
const gscButNotTracked = [...queryTotals].filter((agg) => !trackedSet.has(agg.query.toLowerCase())).filter((agg) => categorizeQuery(agg.query, projectBrandNames, canonicalDomain) !== "brand").sort((a, b) => b.impressions - a.impressions).map((agg) => agg.query).slice(0, TOP_QUERIES_LIMIT);
|
|
13035
13167
|
return {
|
|
13036
13168
|
periodStart,
|
|
13037
13169
|
periodEnd,
|
|
@@ -13171,8 +13303,24 @@ function buildSocialReferrals(db, projectId) {
|
|
|
13171
13303
|
topCampaigns
|
|
13172
13304
|
};
|
|
13173
13305
|
}
|
|
13174
|
-
function
|
|
13175
|
-
const
|
|
13306
|
+
function resolveAiReferralWindow(db, projectId, windowDays) {
|
|
13307
|
+
const windowKey = GA_WINDOW_SUMMARY_KEYS[windowDays];
|
|
13308
|
+
const windowSummary = windowKey ? db.select({ periodStart: gaTrafficWindowSummaries.periodStart, periodEnd: gaTrafficWindowSummaries.periodEnd }).from(gaTrafficWindowSummaries).where(and12(
|
|
13309
|
+
eq16(gaTrafficWindowSummaries.projectId, projectId),
|
|
13310
|
+
eq16(gaTrafficWindowSummaries.windowKey, windowKey)
|
|
13311
|
+
)).get() : void 0;
|
|
13312
|
+
if (windowSummary) return { start: windowSummary.periodStart, end: windowSummary.periodEnd };
|
|
13313
|
+
const latestSummary = db.select({ periodEnd: gaTrafficSummaries.periodEnd }).from(gaTrafficSummaries).where(eq16(gaTrafficSummaries.projectId, projectId)).orderBy(desc7(gaTrafficSummaries.syncedAt)).get();
|
|
13314
|
+
const end = latestSummary?.periodEnd ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
13315
|
+
return { start: windowStartDate(end, windowDays), end };
|
|
13316
|
+
}
|
|
13317
|
+
function buildAiReferrals(db, projectId, windowDays) {
|
|
13318
|
+
const window = resolveAiReferralWindow(db, projectId, windowDays);
|
|
13319
|
+
const rows = db.select().from(gaAiReferrals).where(and12(
|
|
13320
|
+
eq16(gaAiReferrals.projectId, projectId),
|
|
13321
|
+
sql7`${gaAiReferrals.date} >= ${window.start}`,
|
|
13322
|
+
sql7`${gaAiReferrals.date} <= ${window.end}`
|
|
13323
|
+
)).all();
|
|
13176
13324
|
if (rows.length === 0) return null;
|
|
13177
13325
|
const dimSessionsByTuple = /* @__PURE__ */ new Map();
|
|
13178
13326
|
for (const r of rows) {
|
|
@@ -13200,60 +13348,47 @@ function buildAiReferrals(db, projectId) {
|
|
|
13200
13348
|
(r) => winningDimension.get(`${r.date}::${r.source}::${r.medium}`) === r.sourceDimension
|
|
13201
13349
|
);
|
|
13202
13350
|
let total = 0;
|
|
13203
|
-
let totalUsers = 0;
|
|
13204
13351
|
let paidSessions = 0;
|
|
13205
|
-
let paidUsers = 0;
|
|
13206
13352
|
let organicSessions = 0;
|
|
13207
|
-
let organicUsers = 0;
|
|
13208
13353
|
const sourceAgg = /* @__PURE__ */ new Map();
|
|
13209
13354
|
const trendAgg = /* @__PURE__ */ new Map();
|
|
13210
13355
|
const pageAgg = /* @__PURE__ */ new Map();
|
|
13211
13356
|
for (const r of dedupedRows) {
|
|
13212
13357
|
total += r.sessions;
|
|
13213
|
-
totalUsers += r.users;
|
|
13214
13358
|
const paid = isPaidAiTrafficClass(r.trafficClass);
|
|
13215
13359
|
if (paid) {
|
|
13216
13360
|
paidSessions += r.sessions;
|
|
13217
|
-
paidUsers += r.users;
|
|
13218
13361
|
} else {
|
|
13219
13362
|
organicSessions += r.sessions;
|
|
13220
|
-
organicUsers += r.users;
|
|
13221
13363
|
}
|
|
13222
13364
|
const s = sourceAgg.get(r.source) ?? {
|
|
13223
13365
|
sessions: 0,
|
|
13224
|
-
users: 0,
|
|
13225
13366
|
paidSessions: 0,
|
|
13226
13367
|
organicSessions: 0
|
|
13227
13368
|
};
|
|
13228
13369
|
s.sessions += r.sessions;
|
|
13229
|
-
s.users += r.users;
|
|
13230
13370
|
if (paid) s.paidSessions += r.sessions;
|
|
13231
13371
|
else s.organicSessions += r.sessions;
|
|
13232
13372
|
sourceAgg.set(r.source, s);
|
|
13233
13373
|
trendAgg.set(r.date, (trendAgg.get(r.date) ?? 0) + r.sessions);
|
|
13234
13374
|
const page = r.landingPageNormalized ?? r.landingPage;
|
|
13235
|
-
const p = pageAgg.get(page) ?? { sessions: 0
|
|
13375
|
+
const p = pageAgg.get(page) ?? { sessions: 0 };
|
|
13236
13376
|
p.sessions += r.sessions;
|
|
13237
|
-
p.users += r.users;
|
|
13238
13377
|
pageAgg.set(page, p);
|
|
13239
13378
|
}
|
|
13240
13379
|
const bySource = [...sourceAgg.entries()].map(([source, data]) => ({
|
|
13241
13380
|
source,
|
|
13242
13381
|
sessions: data.sessions,
|
|
13243
|
-
users: data.users,
|
|
13244
13382
|
paidSessions: data.paidSessions,
|
|
13245
13383
|
organicSessions: data.organicSessions,
|
|
13246
13384
|
sharePct: total > 0 ? Math.round(data.sessions / total * 100) : 0
|
|
13247
13385
|
})).sort((a, b) => b.sessions - a.sessions);
|
|
13248
13386
|
const trend = [...trendAgg.entries()].map(([date, sessions]) => ({ date, sessions })).sort((a, b) => a.date.localeCompare(b.date));
|
|
13249
|
-
const topLandingPages = [...pageAgg.entries()].map(([page, data]) => ({ page, sessions: data.sessions
|
|
13387
|
+
const topLandingPages = [...pageAgg.entries()].map(([page, data]) => ({ page, sessions: data.sessions })).sort((a, b) => b.sessions - a.sessions).slice(0, TOP_AI_REFERRAL_PAGES_LIMIT);
|
|
13250
13388
|
return {
|
|
13251
13389
|
totalSessions: total,
|
|
13252
|
-
totalUsers,
|
|
13253
13390
|
paidSessions,
|
|
13254
|
-
paidUsers,
|
|
13255
13391
|
organicSessions,
|
|
13256
|
-
organicUsers,
|
|
13257
13392
|
bySource,
|
|
13258
13393
|
trend,
|
|
13259
13394
|
topLandingPages
|
|
@@ -14258,7 +14393,7 @@ function buildProjectReport(db, projectName, periodDays) {
|
|
|
14258
14393
|
);
|
|
14259
14394
|
const gaSection = buildGaSection(db, project.id, periodDays);
|
|
14260
14395
|
const socialSection = buildSocialReferrals(db, project.id);
|
|
14261
|
-
const aiReferralsSection = buildAiReferrals(db, project.id);
|
|
14396
|
+
const aiReferralsSection = buildAiReferrals(db, project.id, periodDays);
|
|
14262
14397
|
const serverActivitySection = buildServerActivity(db, project.id, periodDays);
|
|
14263
14398
|
const indexingHealthSection = buildIndexingHealth(db, project.id);
|
|
14264
14399
|
const citationsTrend = buildCitationsTrend(db, project.id, queryLookup, latestRunLocation);
|
|
@@ -15754,7 +15889,8 @@ function summarizeTransitionsFromSnapshots(latest, previous, since) {
|
|
|
15754
15889
|
}
|
|
15755
15890
|
function buildSuggestedQueriesFromGsc(app, projectId, trackedQueries) {
|
|
15756
15891
|
const cutoff = new Date(Date.now() - 28 * 24 * 60 * 60 * 1e3).toISOString().slice(0, 10);
|
|
15757
|
-
const
|
|
15892
|
+
const dimensionedRows = app.db.select({
|
|
15893
|
+
date: gscSearchData.date,
|
|
15758
15894
|
query: gscSearchData.query,
|
|
15759
15895
|
impressions: sql8`COALESCE(SUM(${gscSearchData.impressions}), 0)`,
|
|
15760
15896
|
clicks: sql8`COALESCE(SUM(${gscSearchData.clicks}), 0)`,
|
|
@@ -15766,12 +15902,23 @@ function buildSuggestedQueriesFromGsc(app, projectId, trackedQueries) {
|
|
|
15766
15902
|
eq20(gscSearchData.projectId, projectId),
|
|
15767
15903
|
sql8`${gscSearchData.date} >= ${cutoff}`,
|
|
15768
15904
|
sql8`${gscSearchData.impressions} > 0`
|
|
15769
|
-
)).groupBy(gscSearchData.
|
|
15770
|
-
const
|
|
15905
|
+
)).groupBy(gscSearchData.date, gscSearchData.query).all();
|
|
15906
|
+
const todayIso = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
15907
|
+
const merged = mergeGscQueryTotalsWithFallback(
|
|
15908
|
+
readGscQueryDailyRows(app.db, projectId, cutoff, todayIso),
|
|
15909
|
+
dimensionedRows.map((r) => ({
|
|
15910
|
+
date: r.date,
|
|
15911
|
+
query: r.query,
|
|
15912
|
+
impressions: Number(r.impressions),
|
|
15913
|
+
clicks: Number(r.clicks),
|
|
15914
|
+
position: Number(r.avgPosition)
|
|
15915
|
+
}))
|
|
15916
|
+
);
|
|
15917
|
+
const gscRows = merged.filter((r) => r.impressions > 0).sort((a, b) => b.impressions - a.impressions).slice(0, 100).map((r) => ({
|
|
15771
15918
|
query: r.query,
|
|
15772
|
-
impressions:
|
|
15773
|
-
clicks:
|
|
15774
|
-
avgPosition:
|
|
15919
|
+
impressions: r.impressions,
|
|
15920
|
+
clicks: r.clicks,
|
|
15921
|
+
avgPosition: r.position
|
|
15775
15922
|
}));
|
|
15776
15923
|
return buildSuggestedQueries(gscRows, { trackedQueries });
|
|
15777
15924
|
}
|
|
@@ -24545,6 +24692,7 @@ var AdsActivationError = class extends Error {
|
|
|
24545
24692
|
entity;
|
|
24546
24693
|
};
|
|
24547
24694
|
var DEFAULT_LEASE_MS = 5 * 6e4;
|
|
24695
|
+
var DEFAULT_ACTIVATION_VERIFICATION_RETRY_DELAYS_MS = [100, 250, 500, 1e3, 2e3, 2e3];
|
|
24548
24696
|
var PROVIDER_PAUSED = "paused";
|
|
24549
24697
|
var PROVIDER_ACTIVE = "active";
|
|
24550
24698
|
function assertNever(value) {
|
|
@@ -25479,6 +25627,66 @@ async function validateActiveDescendants(ctx, parent) {
|
|
|
25479
25627
|
);
|
|
25480
25628
|
}
|
|
25481
25629
|
}
|
|
25630
|
+
async function verifyActiveAfterMutation(ctx, target) {
|
|
25631
|
+
let lastUpdatedAt = null;
|
|
25632
|
+
for (let attempt = 0; ; attempt++) {
|
|
25633
|
+
let entity = null;
|
|
25634
|
+
try {
|
|
25635
|
+
entity = await readTarget(ctx.deps.provider, target);
|
|
25636
|
+
lastUpdatedAt = entity.updatedAt;
|
|
25637
|
+
} catch {
|
|
25638
|
+
}
|
|
25639
|
+
if (entity) {
|
|
25640
|
+
try {
|
|
25641
|
+
validateIdentity(target, entity);
|
|
25642
|
+
validateAdApproval(target, entity);
|
|
25643
|
+
} catch (cause) {
|
|
25644
|
+
return {
|
|
25645
|
+
ok: false,
|
|
25646
|
+
error: cause instanceof AdsActivationError ? cause : targetError(
|
|
25647
|
+
AdsActivationErrorCodes.providerMutationFailed,
|
|
25648
|
+
"Provider activation validation failed",
|
|
25649
|
+
502,
|
|
25650
|
+
target
|
|
25651
|
+
),
|
|
25652
|
+
providerUpdatedAt: entity.updatedAt
|
|
25653
|
+
};
|
|
25654
|
+
}
|
|
25655
|
+
if (entity.status === PROVIDER_ACTIVE && entity.updatedAt !== null) {
|
|
25656
|
+
return { ok: true, entity };
|
|
25657
|
+
}
|
|
25658
|
+
const exactPreMutationSnapshot = entity.status === PROVIDER_PAUSED && entity.updatedAt === target.expectedUpdatedAt;
|
|
25659
|
+
if (!exactPreMutationSnapshot) {
|
|
25660
|
+
return {
|
|
25661
|
+
ok: false,
|
|
25662
|
+
error: targetError(
|
|
25663
|
+
AdsActivationErrorCodes.providerMutationFailed,
|
|
25664
|
+
"Provider did not confirm the required active state",
|
|
25665
|
+
502,
|
|
25666
|
+
target
|
|
25667
|
+
),
|
|
25668
|
+
providerUpdatedAt: entity.updatedAt
|
|
25669
|
+
};
|
|
25670
|
+
}
|
|
25671
|
+
}
|
|
25672
|
+
if (attempt >= ctx.deps.activationVerificationRetryDelaysMs.length) {
|
|
25673
|
+
return {
|
|
25674
|
+
ok: false,
|
|
25675
|
+
error: targetError(
|
|
25676
|
+
AdsActivationErrorCodes.providerMutationFailed,
|
|
25677
|
+
"Provider activation outcome could not be verified",
|
|
25678
|
+
502,
|
|
25679
|
+
target
|
|
25680
|
+
),
|
|
25681
|
+
providerUpdatedAt: lastUpdatedAt
|
|
25682
|
+
};
|
|
25683
|
+
}
|
|
25684
|
+
const delayMs = ctx.deps.activationVerificationRetryDelaysMs[attempt];
|
|
25685
|
+
await renewLease(ctx);
|
|
25686
|
+
await ctx.deps.sleep(delayMs);
|
|
25687
|
+
await renewLease(ctx);
|
|
25688
|
+
}
|
|
25689
|
+
}
|
|
25482
25690
|
async function activateOne(ctx, original) {
|
|
25483
25691
|
let step = currentStep(ctx, original.id);
|
|
25484
25692
|
if (step.state === AdsOperationStepStates.active) {
|
|
@@ -25530,25 +25738,16 @@ async function activateOne(ctx, original) {
|
|
|
25530
25738
|
} catch {
|
|
25531
25739
|
}
|
|
25532
25740
|
await renewLease(ctx);
|
|
25533
|
-
|
|
25534
|
-
|
|
25535
|
-
|
|
25536
|
-
|
|
25537
|
-
|
|
25538
|
-
|
|
25539
|
-
|
|
25540
|
-
|
|
25541
|
-
try {
|
|
25542
|
-
validateIdentity(target, entity);
|
|
25543
|
-
validateAdApproval(target, entity);
|
|
25544
|
-
if (entity.status !== PROVIDER_ACTIVE || entity.updatedAt === null) {
|
|
25545
|
-
throw targetError(AdsActivationErrorCodes.providerMutationFailed, "Provider did not confirm the required active state", 502, target);
|
|
25546
|
-
}
|
|
25547
|
-
} catch (cause) {
|
|
25548
|
-
const error = cause instanceof AdsActivationError ? cause : targetError(AdsActivationErrorCodes.providerMutationFailed, "Provider activation validation failed", 502, target);
|
|
25549
|
-
await transitionStep(ctx, step, unknownStep(step, error, entity.updatedAt, ctx.deps.now().toISOString()));
|
|
25550
|
-
throw error;
|
|
25741
|
+
const verification = await verifyActiveAfterMutation(ctx, target);
|
|
25742
|
+
if (!verification.ok) {
|
|
25743
|
+
await transitionStep(
|
|
25744
|
+
ctx,
|
|
25745
|
+
step,
|
|
25746
|
+
unknownStep(step, verification.error, verification.providerUpdatedAt, ctx.deps.now().toISOString())
|
|
25747
|
+
);
|
|
25748
|
+
throw verification.error;
|
|
25551
25749
|
}
|
|
25750
|
+
const { entity } = verification;
|
|
25552
25751
|
await validateExactDescendants(
|
|
25553
25752
|
ctx.deps.provider,
|
|
25554
25753
|
ctx.manifest,
|
|
@@ -25732,11 +25931,22 @@ async function executeApprovedAdsActivation(dependencies, request) {
|
|
|
25732
25931
|
provider: dependencies.provider,
|
|
25733
25932
|
now: dependencies.now ?? (() => /* @__PURE__ */ new Date()),
|
|
25734
25933
|
randomId: dependencies.randomId ?? (() => crypto20.randomUUID()),
|
|
25735
|
-
leaseMs: dependencies.leaseMs ?? DEFAULT_LEASE_MS
|
|
25934
|
+
leaseMs: dependencies.leaseMs ?? DEFAULT_LEASE_MS,
|
|
25935
|
+
sleep: dependencies.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
|
|
25936
|
+
activationVerificationRetryDelaysMs: dependencies.activationVerificationRetryDelaysMs ?? DEFAULT_ACTIVATION_VERIFICATION_RETRY_DELAYS_MS
|
|
25736
25937
|
};
|
|
25737
25938
|
if (!Number.isSafeInteger(deps.leaseMs) || deps.leaseMs <= 0) {
|
|
25738
25939
|
throw new AdsActivationError(AdsActivationErrorCodes.invalidManifest, "Activation lease duration is invalid", 400);
|
|
25739
25940
|
}
|
|
25941
|
+
if (deps.activationVerificationRetryDelaysMs.some(
|
|
25942
|
+
(delay) => !Number.isSafeInteger(delay) || delay < 0
|
|
25943
|
+
)) {
|
|
25944
|
+
throw new AdsActivationError(
|
|
25945
|
+
AdsActivationErrorCodes.invalidManifest,
|
|
25946
|
+
"Activation verification retry delays are invalid",
|
|
25947
|
+
400
|
|
25948
|
+
);
|
|
25949
|
+
}
|
|
25740
25950
|
const now = deps.now();
|
|
25741
25951
|
const operationId = deps.randomId();
|
|
25742
25952
|
const leaseOwner = deps.randomId();
|
|
@@ -42265,6 +42475,7 @@ export {
|
|
|
42265
42475
|
notifications,
|
|
42266
42476
|
gscSearchData,
|
|
42267
42477
|
gscDailyTotals,
|
|
42478
|
+
gscQueryDailyTotals,
|
|
42268
42479
|
gscUrlInspections,
|
|
42269
42480
|
gscCoverageSnapshots,
|
|
42270
42481
|
siteAuditSnapshots,
|
package/dist/cli.js
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
setTelemetrySource,
|
|
30
30
|
showFirstRunNotice,
|
|
31
31
|
trackEvent
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-A4YBV4Y4.js";
|
|
33
33
|
import {
|
|
34
34
|
CliError,
|
|
35
35
|
EXIT_SYSTEM_ERROR,
|
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
saveConfig,
|
|
47
47
|
saveConfigPatch,
|
|
48
48
|
usageError
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-IHRW6WWV.js";
|
|
50
50
|
import {
|
|
51
51
|
apiKeys,
|
|
52
52
|
createClient,
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
projects,
|
|
55
55
|
queries,
|
|
56
56
|
renderReportHtml
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-HQRUFTLN.js";
|
|
58
58
|
import {
|
|
59
59
|
AdsOperationKinds,
|
|
60
60
|
AdsOperationStates,
|
|
@@ -95,7 +95,7 @@ import {
|
|
|
95
95
|
providerQuotaPolicySchema,
|
|
96
96
|
resolveProviderInput,
|
|
97
97
|
winnabilityClassSchema
|
|
98
|
-
} from "./chunk-
|
|
98
|
+
} from "./chunk-BLSHHRRU.js";
|
|
99
99
|
|
|
100
100
|
// src/cli.ts
|
|
101
101
|
import { pathToFileURL } from "url";
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-A4YBV4Y4.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-IHRW6WWV.js";
|
|
7
|
+
import "./chunk-HQRUFTLN.js";
|
|
8
|
+
import "./chunk-BLSHHRRU.js";
|
|
9
9
|
export {
|
|
10
10
|
createServer,
|
|
11
11
|
loadConfig
|
package/dist/mcp.js
CHANGED
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
PACKAGE_VERSION,
|
|
4
4
|
canonryMcpTools,
|
|
5
5
|
createApiClient
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-IHRW6WWV.js";
|
|
7
7
|
import {
|
|
8
8
|
isReadOnlyKey
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-BLSHHRRU.js";
|
|
10
10
|
|
|
11
11
|
// src/mcp/cli.ts
|
|
12
12
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonry/canonry",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.127.0",
|
|
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",
|
|
@@ -71,21 +71,21 @@
|
|
|
71
71
|
"@ainyc/canonry-contracts": "0.0.0",
|
|
72
72
|
"@ainyc/canonry-db": "0.0.0",
|
|
73
73
|
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
74
|
-
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
75
|
-
"@ainyc/canonry-integration-google": "0.0.0",
|
|
76
74
|
"@ainyc/canonry-integration-openai-ads": "0.0.0",
|
|
75
|
+
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
77
76
|
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
78
|
-
"@ainyc/canonry-integration-google
|
|
77
|
+
"@ainyc/canonry-integration-google": "0.0.0",
|
|
79
78
|
"@ainyc/canonry-integration-google-places": "0.0.0",
|
|
80
79
|
"@ainyc/canonry-intelligence": "0.0.0",
|
|
80
|
+
"@ainyc/canonry-integration-traffic": "0.0.0",
|
|
81
|
+
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
81
82
|
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
82
83
|
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
83
|
-
"@ainyc/canonry-
|
|
84
|
-
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
85
|
-
"@ainyc/canonry-integration-traffic": "0.0.0",
|
|
84
|
+
"@ainyc/canonry-integration-google-business-profile": "0.0.0",
|
|
86
85
|
"@ainyc/canonry-provider-local": "0.0.0",
|
|
87
86
|
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
88
|
-
"@ainyc/canonry-provider-perplexity": "0.0.0"
|
|
87
|
+
"@ainyc/canonry-provider-perplexity": "0.0.0",
|
|
88
|
+
"@ainyc/canonry-provider-gemini": "0.0.0"
|
|
89
89
|
},
|
|
90
90
|
"scripts": {
|
|
91
91
|
"build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",
|