@ainyc/canonry 3.1.1 → 3.2.1
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-D6R1HCPk.js +302 -0
- package/assets/assets/{index-qR7US7tB.css → index-DZm9xxNs.css} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-VUP7AQTD.js → chunk-QJPPK4WW.js} +65 -15
- package/dist/{chunk-5X3TJ5BC.js → chunk-TUXS2Y6C.js} +20 -0
- package/dist/{chunk-NEDRCOOL.js → chunk-UQH5SKM2.js} +18 -2
- package/dist/cli.js +152 -16
- package/dist/index.js +3 -3
- package/dist/{intelligence-service-6S5YKANX.js → intelligence-service-GYY23WHR.js} +1 -1
- package/dist/mcp.js +1 -1
- package/package.json +10 -10
- package/assets/assets/index-Bk6pxbs2.js +0 -302
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-D6R1HCPk.js"></script>
|
|
16
|
+
<link rel="stylesheet" crossorigin href="./assets/index-DZm9xxNs.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
19
19
|
<div id="root"></div>
|
|
@@ -62,7 +62,7 @@ import {
|
|
|
62
62
|
visibilityStateFromAnswerMentioned,
|
|
63
63
|
windowCutoff,
|
|
64
64
|
wordpressEnvSchema
|
|
65
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-TUXS2Y6C.js";
|
|
66
66
|
import {
|
|
67
67
|
IntelligenceService,
|
|
68
68
|
agentMemory,
|
|
@@ -100,7 +100,7 @@ import {
|
|
|
100
100
|
runs,
|
|
101
101
|
schedules,
|
|
102
102
|
usageCounters
|
|
103
|
-
} from "./chunk-
|
|
103
|
+
} from "./chunk-UQH5SKM2.js";
|
|
104
104
|
|
|
105
105
|
// src/telemetry.ts
|
|
106
106
|
import crypto from "crypto";
|
|
@@ -5252,7 +5252,7 @@ var routeCatalog = [
|
|
|
5252
5252
|
{
|
|
5253
5253
|
method: "get",
|
|
5254
5254
|
path: "/api/v1/projects/{name}/ga/traffic",
|
|
5255
|
-
summary: "Get GA4 landing page traffic and AI referral
|
|
5255
|
+
summary: "Get GA4 landing page traffic, channel breakdown, and AI referral landing pages",
|
|
5256
5256
|
tags: ["ga4"],
|
|
5257
5257
|
parameters: [nameParameter, limitQueryParameter, analyticsWindowParameter],
|
|
5258
5258
|
responses: {
|
|
@@ -5264,7 +5264,7 @@ var routeCatalog = [
|
|
|
5264
5264
|
{
|
|
5265
5265
|
method: "get",
|
|
5266
5266
|
path: "/api/v1/projects/{name}/ga/ai-referral-history",
|
|
5267
|
-
summary: "Get AI referral sessions per day grouped by source",
|
|
5267
|
+
summary: "Get AI referral sessions per day grouped by source and landing page",
|
|
5268
5268
|
tags: ["ga4"],
|
|
5269
5269
|
parameters: [nameParameter, analyticsWindowParameter],
|
|
5270
5270
|
responses: {
|
|
@@ -7072,7 +7072,8 @@ async function fetchAiReferrals(accessToken, propertyId, days) {
|
|
|
7072
7072
|
dimensions: [
|
|
7073
7073
|
{ name: "date" },
|
|
7074
7074
|
{ name: sourceDim },
|
|
7075
|
-
{ name: mediumDim }
|
|
7075
|
+
{ name: mediumDim },
|
|
7076
|
+
{ name: "landingPagePlusQueryString" }
|
|
7076
7077
|
],
|
|
7077
7078
|
metrics: [
|
|
7078
7079
|
{ name: "sessions" },
|
|
@@ -7097,6 +7098,7 @@ async function fetchAiReferrals(accessToken, propertyId, days) {
|
|
|
7097
7098
|
date: row.dimensionValues[0].value,
|
|
7098
7099
|
source: row.dimensionValues[1].value,
|
|
7099
7100
|
medium: row.dimensionValues[2].value,
|
|
7101
|
+
landingPage: row.dimensionValues[3]?.value ?? "(not set)",
|
|
7100
7102
|
sessions: parseInt(row.metricValues[0].value, 10) || 0,
|
|
7101
7103
|
users: parseInt(row.metricValues[1].value, 10) || 0,
|
|
7102
7104
|
sourceDimension: dimLabel
|
|
@@ -7109,7 +7111,7 @@ async function fetchAiReferrals(accessToken, propertyId, days) {
|
|
|
7109
7111
|
}
|
|
7110
7112
|
const deduped = /* @__PURE__ */ new Map();
|
|
7111
7113
|
for (const row of rows) {
|
|
7112
|
-
const key = `${row.date}::${row.source}::${row.medium}::${row.sourceDimension}`;
|
|
7114
|
+
const key = `${row.date}::${row.source}::${row.medium}::${row.sourceDimension}::${row.landingPage}`;
|
|
7113
7115
|
const existing = deduped.get(key);
|
|
7114
7116
|
if (!existing) {
|
|
7115
7117
|
deduped.set(key, row);
|
|
@@ -8692,6 +8694,13 @@ function gaLog(level, action, ctx) {
|
|
|
8692
8694
|
const stream = level === "error" ? process.stderr : process.stdout;
|
|
8693
8695
|
stream.write(JSON.stringify(entry) + "\n");
|
|
8694
8696
|
}
|
|
8697
|
+
function formatSharePct(numerator, total) {
|
|
8698
|
+
if (total <= 0 || numerator <= 0) return "0%";
|
|
8699
|
+
const pct = numerator / total * 100;
|
|
8700
|
+
const rounded = Math.round(pct);
|
|
8701
|
+
if (rounded === 0) return "<1%";
|
|
8702
|
+
return `${rounded}%`;
|
|
8703
|
+
}
|
|
8695
8704
|
async function refreshOAuthTokenIfNeeded(googleStore, authConfig, canonicalDomain, oauthConn) {
|
|
8696
8705
|
const expiresAt = oauthConn.tokenExpiresAt ? new Date(oauthConn.tokenExpiresAt).getTime() : 0;
|
|
8697
8706
|
const fiveMinutes = 5 * 60 * 1e3;
|
|
@@ -8964,6 +8973,8 @@ async function ga4Routes(app, opts) {
|
|
|
8964
8973
|
source: row.source,
|
|
8965
8974
|
medium: row.medium,
|
|
8966
8975
|
sourceDimension: row.sourceDimension,
|
|
8976
|
+
landingPage: row.landingPage,
|
|
8977
|
+
landingPageNormalized: normalizeUrlPath(row.landingPage),
|
|
8967
8978
|
sessions: row.sessions,
|
|
8968
8979
|
users: row.users,
|
|
8969
8980
|
syncedAt: now,
|
|
@@ -9080,16 +9091,35 @@ async function ga4Routes(app, opts) {
|
|
|
9080
9091
|
sessions: sql5`SUM(${gaAiReferrals.sessions})`,
|
|
9081
9092
|
users: sql5`SUM(${gaAiReferrals.users})`
|
|
9082
9093
|
}).from(gaAiReferrals).where(and8(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).orderBy(sql5`SUM(${gaAiReferrals.sessions}) DESC`).all();
|
|
9094
|
+
const aiReferralLandingPages = app.db.select({
|
|
9095
|
+
source: gaAiReferrals.source,
|
|
9096
|
+
medium: gaAiReferrals.medium,
|
|
9097
|
+
sourceDimension: gaAiReferrals.sourceDimension,
|
|
9098
|
+
landingPage: sql5`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
|
|
9099
|
+
sessions: sql5`SUM(${gaAiReferrals.sessions})`,
|
|
9100
|
+
users: sql5`SUM(${gaAiReferrals.users})`
|
|
9101
|
+
}).from(gaAiReferrals).where(and8(...aiConditions)).groupBy(
|
|
9102
|
+
gaAiReferrals.source,
|
|
9103
|
+
gaAiReferrals.medium,
|
|
9104
|
+
gaAiReferrals.sourceDimension,
|
|
9105
|
+
sql5`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`
|
|
9106
|
+
).orderBy(sql5`SUM(${gaAiReferrals.sessions}) DESC`).all();
|
|
9083
9107
|
const aiDeduped = app.db.select({
|
|
9084
|
-
sessions: sql5`SUM(max_sessions)`,
|
|
9085
|
-
users: sql5`SUM(max_users)`
|
|
9108
|
+
sessions: sql5`COALESCE(SUM(max_sessions), 0)`,
|
|
9109
|
+
users: sql5`COALESCE(SUM(max_users), 0)`
|
|
9086
9110
|
}).from(
|
|
9087
9111
|
sql5`(
|
|
9088
9112
|
SELECT date, source, medium,
|
|
9089
|
-
MAX(
|
|
9090
|
-
MAX(
|
|
9091
|
-
FROM
|
|
9092
|
-
|
|
9113
|
+
MAX(dimension_sessions) AS max_sessions,
|
|
9114
|
+
MAX(dimension_users) AS max_users
|
|
9115
|
+
FROM (
|
|
9116
|
+
SELECT date, source, medium, source_dimension,
|
|
9117
|
+
SUM(sessions) AS dimension_sessions,
|
|
9118
|
+
SUM(users) AS dimension_users
|
|
9119
|
+
FROM ga_ai_referrals
|
|
9120
|
+
WHERE project_id = ${project.id}${cutoffDate ? sql5` AND date >= ${cutoffDate}` : sql5``}
|
|
9121
|
+
GROUP BY date, source, medium, source_dimension
|
|
9122
|
+
)
|
|
9093
9123
|
GROUP BY date, source, medium
|
|
9094
9124
|
)`
|
|
9095
9125
|
).get();
|
|
@@ -9130,6 +9160,14 @@ async function ga4Routes(app, opts) {
|
|
|
9130
9160
|
sessions: r.sessions ?? 0,
|
|
9131
9161
|
users: r.users ?? 0
|
|
9132
9162
|
})),
|
|
9163
|
+
aiReferralLandingPages: aiReferralLandingPages.map((r) => ({
|
|
9164
|
+
source: r.source,
|
|
9165
|
+
medium: r.medium,
|
|
9166
|
+
sourceDimension: r.sourceDimension,
|
|
9167
|
+
landingPage: r.landingPage,
|
|
9168
|
+
sessions: r.sessions ?? 0,
|
|
9169
|
+
users: r.users ?? 0
|
|
9170
|
+
})),
|
|
9133
9171
|
aiSessionsDeduped: aiDeduped?.sessions ?? 0,
|
|
9134
9172
|
aiUsersDeduped: aiDeduped?.users ?? 0,
|
|
9135
9173
|
aiSessionsBySession: aiBySession?.sessions ?? 0,
|
|
@@ -9148,6 +9186,11 @@ async function ga4Routes(app, opts) {
|
|
|
9148
9186
|
aiSharePctBySession: total > 0 ? Math.round((aiBySession?.sessions ?? 0) / total * 100) : 0,
|
|
9149
9187
|
directSharePct: total > 0 ? Math.round(totalDirectSessions / total * 100) : 0,
|
|
9150
9188
|
socialSharePct: total > 0 ? Math.round((socialTotals?.sessions ?? 0) / total * 100) : 0,
|
|
9189
|
+
organicSharePctDisplay: formatSharePct(summaryRow?.totalOrganicSessions ?? 0, total),
|
|
9190
|
+
aiSharePctDisplay: formatSharePct(aiDeduped?.sessions ?? 0, total),
|
|
9191
|
+
aiSharePctBySessionDisplay: formatSharePct(aiBySession?.sessions ?? 0, total),
|
|
9192
|
+
directSharePctDisplay: formatSharePct(totalDirectSessions, total),
|
|
9193
|
+
socialSharePctDisplay: formatSharePct(socialTotals?.sessions ?? 0, total),
|
|
9151
9194
|
lastSyncedAt: latestSync?.syncedAt ?? null,
|
|
9152
9195
|
periodStart: (() => {
|
|
9153
9196
|
const start = cutoffDate ?? summaryMeta?.periodStart ?? null;
|
|
@@ -9168,10 +9211,17 @@ async function ga4Routes(app, opts) {
|
|
|
9168
9211
|
date: gaAiReferrals.date,
|
|
9169
9212
|
source: gaAiReferrals.source,
|
|
9170
9213
|
medium: gaAiReferrals.medium,
|
|
9214
|
+
landingPage: sql5`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
|
|
9171
9215
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
9172
|
-
sessions: gaAiReferrals.sessions
|
|
9173
|
-
users: gaAiReferrals.users
|
|
9174
|
-
}).from(gaAiReferrals).where(and8(...conditions)).
|
|
9216
|
+
sessions: sql5`SUM(${gaAiReferrals.sessions})`,
|
|
9217
|
+
users: sql5`SUM(${gaAiReferrals.users})`
|
|
9218
|
+
}).from(gaAiReferrals).where(and8(...conditions)).groupBy(
|
|
9219
|
+
gaAiReferrals.date,
|
|
9220
|
+
gaAiReferrals.source,
|
|
9221
|
+
gaAiReferrals.medium,
|
|
9222
|
+
gaAiReferrals.sourceDimension,
|
|
9223
|
+
sql5`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`
|
|
9224
|
+
).orderBy(gaAiReferrals.date).all();
|
|
9175
9225
|
return rows;
|
|
9176
9226
|
});
|
|
9177
9227
|
app.get("/projects/:name/ga/social-referral-history", async (request, _reply) => {
|
|
@@ -1287,6 +1287,14 @@ var ga4AiReferralDtoSchema = z12.object({
|
|
|
1287
1287
|
users: z12.number(),
|
|
1288
1288
|
sourceDimension: ga4SourceDimensionSchema
|
|
1289
1289
|
});
|
|
1290
|
+
var ga4AiReferralLandingPageDtoSchema = z12.object({
|
|
1291
|
+
source: z12.string(),
|
|
1292
|
+
medium: z12.string(),
|
|
1293
|
+
sourceDimension: ga4SourceDimensionSchema,
|
|
1294
|
+
landingPage: z12.string(),
|
|
1295
|
+
sessions: z12.number(),
|
|
1296
|
+
users: z12.number()
|
|
1297
|
+
});
|
|
1290
1298
|
var ga4SocialReferralDtoSchema = z12.object({
|
|
1291
1299
|
source: z12.string(),
|
|
1292
1300
|
medium: z12.string(),
|
|
@@ -1310,6 +1318,7 @@ var ga4TrafficSummaryDtoSchema = z12.object({
|
|
|
1310
1318
|
users: z12.number()
|
|
1311
1319
|
})),
|
|
1312
1320
|
aiReferrals: z12.array(ga4AiReferralDtoSchema),
|
|
1321
|
+
aiReferralLandingPages: z12.array(ga4AiReferralLandingPageDtoSchema),
|
|
1313
1322
|
/** Deduped AI session total: MAX(sessions) per date+source+medium across attribution dimensions, then summed. Cross-cutting: can overlap with Direct/Organic/Social via firstUserSource. */
|
|
1314
1323
|
aiSessionsDeduped: z12.number(),
|
|
1315
1324
|
/** Deduped AI user total: MAX(users) per date+source+medium across attribution dimensions, then summed. */
|
|
@@ -1333,12 +1342,23 @@ var ga4TrafficSummaryDtoSchema = z12.object({
|
|
|
1333
1342
|
directSharePct: z12.number(),
|
|
1334
1343
|
/** Social sessions as a percentage of total sessions (0–100, rounded). */
|
|
1335
1344
|
socialSharePct: z12.number(),
|
|
1345
|
+
/** Display string for organicSharePct ('10%' or '<1%' when there are sessions but the rounded pct is 0). */
|
|
1346
|
+
organicSharePctDisplay: z12.string(),
|
|
1347
|
+
/** Display string for aiSharePct ('5%' or '<1%' when there are sessions but the rounded pct is 0). */
|
|
1348
|
+
aiSharePctDisplay: z12.string(),
|
|
1349
|
+
/** Display string for aiSharePctBySession ('5%' or '<1%' when there are sessions but the rounded pct is 0). */
|
|
1350
|
+
aiSharePctBySessionDisplay: z12.string(),
|
|
1351
|
+
/** Display string for directSharePct ('20%' or '<1%' when there are sessions but the rounded pct is 0). */
|
|
1352
|
+
directSharePctDisplay: z12.string(),
|
|
1353
|
+
/** Display string for socialSharePct ('15%' or '<1%' when there are sessions but the rounded pct is 0). */
|
|
1354
|
+
socialSharePctDisplay: z12.string(),
|
|
1336
1355
|
lastSyncedAt: z12.string().nullable()
|
|
1337
1356
|
});
|
|
1338
1357
|
var ga4AiReferralHistoryEntrySchema = z12.object({
|
|
1339
1358
|
date: z12.string(),
|
|
1340
1359
|
source: z12.string(),
|
|
1341
1360
|
medium: z12.string(),
|
|
1361
|
+
landingPage: z12.string(),
|
|
1342
1362
|
sessions: z12.number(),
|
|
1343
1363
|
users: z12.number(),
|
|
1344
1364
|
/** Which GA4 dimension this row came from: session (sessionSource), first_user (firstUserSource), or manual_utm (utm_source parameter) */
|
|
@@ -344,6 +344,8 @@ var gaAiReferrals = sqliteTable("ga_ai_referrals", {
|
|
|
344
344
|
medium: text("medium").notNull(),
|
|
345
345
|
/** Which GA4 dimension produced this row: 'session' | 'first_user' | 'manual_utm' */
|
|
346
346
|
sourceDimension: text("source_dimension").notNull().default("session"),
|
|
347
|
+
landingPage: text("landing_page").notNull().default("(not set)"),
|
|
348
|
+
landingPageNormalized: text("landing_page_normalized"),
|
|
347
349
|
sessions: integer("sessions").notNull().default(0),
|
|
348
350
|
users: integer("users").notNull().default(0),
|
|
349
351
|
syncedAt: text("synced_at").notNull(),
|
|
@@ -351,7 +353,8 @@ var gaAiReferrals = sqliteTable("ga_ai_referrals", {
|
|
|
351
353
|
}, (table) => [
|
|
352
354
|
index("idx_ga_ai_ref_project_date").on(table.projectId, table.date),
|
|
353
355
|
index("idx_ga_ai_ref_source").on(table.source),
|
|
354
|
-
|
|
356
|
+
index("idx_ga_ai_ref_landing_page").on(table.projectId, table.date, table.landingPageNormalized),
|
|
357
|
+
uniqueIndex("idx_ga_ai_ref_unique_v3").on(table.projectId, table.date, table.source, table.medium, table.sourceDimension, table.landingPage),
|
|
355
358
|
index("idx_ga_ai_ref_run").on(table.syncRunId)
|
|
356
359
|
]);
|
|
357
360
|
var gaSocialReferrals = sqliteTable("ga_social_referrals", {
|
|
@@ -1081,7 +1084,20 @@ var MIGRATIONS = [
|
|
|
1081
1084
|
// separate commit. Unblocks an honest channel breakdown for the project
|
|
1082
1085
|
// dashboard (organic / social / direct / known-AI) — see
|
|
1083
1086
|
// plans/ai-attribution-research.md scope A.
|
|
1084
|
-
`ALTER TABLE ga_traffic_snapshots ADD COLUMN direct_sessions INTEGER
|
|
1087
|
+
`ALTER TABLE ga_traffic_snapshots ADD COLUMN direct_sessions INTEGER`,
|
|
1088
|
+
// v46: Landing-page breakdown for GA4 known-AI referral rows. The raw
|
|
1089
|
+
// landing_page participates in the unique key so distinct query strings can
|
|
1090
|
+
// be ingested without collision; API reads group by landing_page_normalized.
|
|
1091
|
+
// Default '(not set)' matches GA4's own sentinel for missing dimension
|
|
1092
|
+
// values, so legacy rows surface as the same bucket new ingestion uses
|
|
1093
|
+
// when GA4 returns nothing.
|
|
1094
|
+
`ALTER TABLE ga_ai_referrals ADD COLUMN landing_page TEXT NOT NULL DEFAULT '(not set)'`,
|
|
1095
|
+
`ALTER TABLE ga_ai_referrals ADD COLUMN landing_page_normalized TEXT`,
|
|
1096
|
+
`DROP INDEX IF EXISTS idx_ga_ai_ref_unique_v2`,
|
|
1097
|
+
`CREATE INDEX IF NOT EXISTS idx_ga_ai_ref_landing_page
|
|
1098
|
+
ON ga_ai_referrals(project_id, date, landing_page_normalized)`,
|
|
1099
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_ga_ai_ref_unique_v3
|
|
1100
|
+
ON ga_ai_referrals(project_id, date, source, medium, source_dimension, landing_page)`
|
|
1085
1101
|
];
|
|
1086
1102
|
function isDuplicateColumnError(err) {
|
|
1087
1103
|
if (!(err instanceof Error)) return false;
|
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-QJPPK4WW.js";
|
|
21
21
|
import {
|
|
22
22
|
CcReleaseSyncStatuses,
|
|
23
23
|
CheckScopes,
|
|
@@ -45,18 +45,19 @@ import {
|
|
|
45
45
|
saveConfig,
|
|
46
46
|
saveConfigPatch,
|
|
47
47
|
usageError
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-TUXS2Y6C.js";
|
|
49
49
|
import {
|
|
50
50
|
apiKeys,
|
|
51
51
|
competitors,
|
|
52
52
|
createClient,
|
|
53
|
+
gaAiReferrals,
|
|
53
54
|
gaTrafficSnapshots,
|
|
54
55
|
migrate,
|
|
55
56
|
parseJsonColumn,
|
|
56
57
|
projects,
|
|
57
58
|
querySnapshots,
|
|
58
59
|
runs
|
|
59
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-UQH5SKM2.js";
|
|
60
61
|
import "./chunk-MLKGABMK.js";
|
|
61
62
|
|
|
62
63
|
// src/cli.ts
|
|
@@ -375,8 +376,80 @@ async function backfillNormalizedPathsCommand(opts) {
|
|
|
375
376
|
console.log(` Updated: ${updated}`);
|
|
376
377
|
console.log(` Unchanged: ${unchanged}`);
|
|
377
378
|
}
|
|
379
|
+
function backfillAiReferralPaths(db, opts) {
|
|
380
|
+
const baseConditions = [];
|
|
381
|
+
if (opts?.projectId) {
|
|
382
|
+
baseConditions.push(eq(gaAiReferrals.projectId, opts.projectId));
|
|
383
|
+
}
|
|
384
|
+
const rows = db.select({
|
|
385
|
+
id: gaAiReferrals.id,
|
|
386
|
+
landingPage: gaAiReferrals.landingPage,
|
|
387
|
+
landingPageNormalized: gaAiReferrals.landingPageNormalized
|
|
388
|
+
}).from(gaAiReferrals).where(baseConditions.length > 0 ? and(...baseConditions) : void 0).all();
|
|
389
|
+
let updated = 0;
|
|
390
|
+
let unchanged = 0;
|
|
391
|
+
if (rows.length > 0) {
|
|
392
|
+
db.transaction((tx) => {
|
|
393
|
+
for (const row of rows) {
|
|
394
|
+
const next = normalizeUrlPath(row.landingPage);
|
|
395
|
+
if (next === null) {
|
|
396
|
+
unchanged++;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (row.landingPageNormalized === next) {
|
|
400
|
+
unchanged++;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
tx.update(gaAiReferrals).set({ landingPageNormalized: next }).where(eq(gaAiReferrals.id, row.id)).run();
|
|
404
|
+
updated++;
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
return { examined: rows.length, updated, unchanged };
|
|
409
|
+
}
|
|
410
|
+
async function backfillAiReferralPathsCommand(opts) {
|
|
411
|
+
const config = loadConfig();
|
|
412
|
+
const db = createClient(config.database);
|
|
413
|
+
migrate(db);
|
|
414
|
+
const projectFilter = opts?.project?.trim();
|
|
415
|
+
let projectId;
|
|
416
|
+
if (projectFilter) {
|
|
417
|
+
const project = db.select({ id: projects.id }).from(projects).where(eq(projects.name, projectFilter)).get();
|
|
418
|
+
if (!project) {
|
|
419
|
+
const result2 = {
|
|
420
|
+
project: projectFilter,
|
|
421
|
+
examined: 0,
|
|
422
|
+
updated: 0,
|
|
423
|
+
unchanged: 0
|
|
424
|
+
};
|
|
425
|
+
if (opts?.format === "json") {
|
|
426
|
+
console.log(JSON.stringify(result2, null, 2));
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
console.log(`Backfill ai-referral-paths: project "${projectFilter}" not found.`);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
projectId = project.id;
|
|
433
|
+
}
|
|
434
|
+
const { examined, updated, unchanged } = backfillAiReferralPaths(db, { projectId });
|
|
435
|
+
const result = {
|
|
436
|
+
project: projectFilter ?? null,
|
|
437
|
+
examined,
|
|
438
|
+
updated,
|
|
439
|
+
unchanged
|
|
440
|
+
};
|
|
441
|
+
if (opts?.format === "json") {
|
|
442
|
+
console.log(JSON.stringify(result, null, 2));
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
console.log("AI referral landing-page backfill complete.\n");
|
|
446
|
+
if (projectFilter) console.log(` Project: ${projectFilter}`);
|
|
447
|
+
console.log(` Examined: ${examined}`);
|
|
448
|
+
console.log(` Updated: ${updated}`);
|
|
449
|
+
console.log(` Unchanged: ${unchanged}`);
|
|
450
|
+
}
|
|
378
451
|
async function backfillInsightsCommand(project, opts) {
|
|
379
|
-
const { IntelligenceService } = await import("./intelligence-service-
|
|
452
|
+
const { IntelligenceService } = await import("./intelligence-service-GYY23WHR.js");
|
|
380
453
|
const config = loadConfig();
|
|
381
454
|
const db = createClient(config.database);
|
|
382
455
|
migrate(db);
|
|
@@ -586,14 +659,28 @@ var BACKFILL_CLI_COMMANDS = [
|
|
|
586
659
|
});
|
|
587
660
|
}
|
|
588
661
|
},
|
|
662
|
+
{
|
|
663
|
+
path: ["backfill", "ai-referral-paths"],
|
|
664
|
+
usage: "canonry backfill ai-referral-paths [--project <name>] [--format json]",
|
|
665
|
+
options: {
|
|
666
|
+
project: stringOption()
|
|
667
|
+
},
|
|
668
|
+
allowPositionals: false,
|
|
669
|
+
run: async (input) => {
|
|
670
|
+
await backfillAiReferralPathsCommand({
|
|
671
|
+
project: getString(input.values, "project"),
|
|
672
|
+
format: input.format
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
},
|
|
589
676
|
{
|
|
590
677
|
path: ["backfill"],
|
|
591
|
-
usage: "canonry backfill <answer-visibility|insights|normalized-paths> [options]",
|
|
678
|
+
usage: "canonry backfill <answer-visibility|insights|normalized-paths|ai-referral-paths> [options]",
|
|
592
679
|
run: async (input) => {
|
|
593
680
|
unknownSubcommand(input.positionals[0], {
|
|
594
681
|
command: "backfill",
|
|
595
|
-
usage: "canonry backfill <answer-visibility|insights|normalized-paths> [options]",
|
|
596
|
-
available: ["answer-visibility", "insights", "normalized-paths"]
|
|
682
|
+
usage: "canonry backfill <answer-visibility|insights|normalized-paths|ai-referral-paths> [options]",
|
|
683
|
+
available: ["answer-visibility", "insights", "normalized-paths", "ai-referral-paths"]
|
|
597
684
|
});
|
|
598
685
|
}
|
|
599
686
|
}
|
|
@@ -1876,7 +1963,7 @@ async function gaTraffic(project, opts) {
|
|
|
1876
1963
|
console.log(JSON.stringify(result, null, 2));
|
|
1877
1964
|
return;
|
|
1878
1965
|
}
|
|
1879
|
-
if (result.topPages.length === 0 && result.aiReferrals.length === 0 && result.socialReferrals.length === 0) {
|
|
1966
|
+
if (result.topPages.length === 0 && result.aiReferrals.length === 0 && result.aiReferralLandingPages.length === 0 && result.socialReferrals.length === 0) {
|
|
1880
1967
|
if (!result.lastSyncedAt) {
|
|
1881
1968
|
console.log('No GA4 traffic data. Run "canonry ga sync <project>" first.');
|
|
1882
1969
|
} else {
|
|
@@ -1907,6 +1994,21 @@ async function gaTraffic(project, opts) {
|
|
|
1907
1994
|
}
|
|
1908
1995
|
console.log();
|
|
1909
1996
|
}
|
|
1997
|
+
if (result.aiReferralLandingPages.length > 0) {
|
|
1998
|
+
const attrWidth = 12;
|
|
1999
|
+
console.log(" AI REFERRAL LANDING PAGES");
|
|
2000
|
+
console.log(` ${"LANDING PAGE".padEnd(30)} ${"SOURCE".padEnd(25)} ${"ATTRIBUTION".padEnd(attrWidth)} ${"SESSIONS".padEnd(10)}${"USERS".padEnd(8)}`);
|
|
2001
|
+
console.log(` ${"\u2500".repeat(30)} ${"\u2500".repeat(25)} ${"\u2500".repeat(attrWidth)} ${"\u2500".repeat(10)}${"\u2500".repeat(8)}`);
|
|
2002
|
+
for (const row of result.aiReferralLandingPages) {
|
|
2003
|
+
const dimLabel = row.sourceDimension === "first_user" ? "first-visit" : row.sourceDimension === "manual_utm" ? "utm" : "session";
|
|
2004
|
+
const page = row.landingPage.length > 30 ? row.landingPage.slice(0, 27) + "..." : row.landingPage;
|
|
2005
|
+
const source = row.source.length > 25 ? row.source.slice(0, 22) + "..." : row.source;
|
|
2006
|
+
console.log(
|
|
2007
|
+
` ${page.padEnd(30)} ${source.padEnd(25)} ${dimLabel.padEnd(attrWidth)} ${String(row.sessions).padEnd(10)}${String(row.users).padEnd(8)}`
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
console.log();
|
|
2011
|
+
}
|
|
1910
2012
|
if (result.socialReferrals.length > 0) {
|
|
1911
2013
|
const chanWidth = 12;
|
|
1912
2014
|
if (result.socialSessions > 0) {
|
|
@@ -2120,7 +2222,13 @@ async function gaAttribution(project, opts) {
|
|
|
2120
2222
|
socialSharePct: traffic.socialSharePct,
|
|
2121
2223
|
organicSharePct: traffic.organicSharePct,
|
|
2122
2224
|
directSharePct: traffic.directSharePct,
|
|
2225
|
+
organicSharePctDisplay: traffic.organicSharePctDisplay,
|
|
2226
|
+
aiSharePctDisplay: traffic.aiSharePctDisplay,
|
|
2227
|
+
aiSharePctBySessionDisplay: traffic.aiSharePctBySessionDisplay,
|
|
2228
|
+
socialSharePctDisplay: traffic.socialSharePctDisplay,
|
|
2229
|
+
directSharePctDisplay: traffic.directSharePctDisplay,
|
|
2123
2230
|
aiReferrals: traffic.aiReferrals,
|
|
2231
|
+
aiReferralLandingPages: traffic.aiReferralLandingPages,
|
|
2124
2232
|
socialReferrals: traffic.socialReferrals,
|
|
2125
2233
|
trend
|
|
2126
2234
|
}, null, 2));
|
|
@@ -2136,10 +2244,10 @@ async function gaAttribution(project, opts) {
|
|
|
2136
2244
|
console.log(` Total Users: ${traffic.totalUsers}`);
|
|
2137
2245
|
console.log();
|
|
2138
2246
|
console.log(" CHANNEL BREAKDOWN 7d trend 30d trend");
|
|
2139
|
-
console.log(` Organic Search: ${String(traffic.totalOrganicSessions).padEnd(6)} (${
|
|
2140
|
-
console.log(` Social: ${String(traffic.socialSessions).padEnd(6)} (${
|
|
2141
|
-
console.log(` Direct: ${String(traffic.totalDirectSessions).padEnd(6)} (${
|
|
2142
|
-
console.log(` AI Referrals: ${String(traffic.aiSessionsBySession).padEnd(6)} (${
|
|
2247
|
+
console.log(` Organic Search: ${String(traffic.totalOrganicSessions).padEnd(6)} (${traffic.organicSharePctDisplay.padStart(4)}) ${fmtTrend(trend.organic.trend7dPct).padEnd(12)} ${fmtTrend(trend.organic.trend30dPct)}`);
|
|
2248
|
+
console.log(` Social: ${String(traffic.socialSessions).padEnd(6)} (${traffic.socialSharePctDisplay.padStart(4)}) ${fmtTrend(trend.social.trend7dPct).padEnd(12)} ${fmtTrend(trend.social.trend30dPct)}`);
|
|
2249
|
+
console.log(` Direct: ${String(traffic.totalDirectSessions).padEnd(6)} (${traffic.directSharePctDisplay.padStart(4)}) ${fmtTrend(trend.direct.trend7dPct).padEnd(12)} ${fmtTrend(trend.direct.trend30dPct)}`);
|
|
2250
|
+
console.log(` AI Referrals: ${String(traffic.aiSessionsBySession).padEnd(6)} (${traffic.aiSharePctBySessionDisplay.padStart(4)}) ${fmtTrend(trend.ai.trend7dPct).padEnd(12)} ${fmtTrend(trend.ai.trend30dPct)} (lower bound \u2014 sessionSource only; referrer-stripped traffic falls under Direct)`);
|
|
2143
2251
|
const otherSessions2 = traffic.totalSessions - traffic.totalOrganicSessions - traffic.aiSessionsBySession - traffic.socialSessions - traffic.totalDirectSessions;
|
|
2144
2252
|
if (otherSessions2 > 0) {
|
|
2145
2253
|
const otherPct = traffic.totalSessions > 0 ? Math.round(otherSessions2 / traffic.totalSessions * 100) : 0;
|
|
@@ -2182,7 +2290,13 @@ async function gaAttribution(project, opts) {
|
|
|
2182
2290
|
socialSharePct: traffic.socialSharePct,
|
|
2183
2291
|
organicSharePct: traffic.organicSharePct,
|
|
2184
2292
|
directSharePct: traffic.directSharePct,
|
|
2293
|
+
organicSharePctDisplay: traffic.organicSharePctDisplay,
|
|
2294
|
+
aiSharePctDisplay: traffic.aiSharePctDisplay,
|
|
2295
|
+
aiSharePctBySessionDisplay: traffic.aiSharePctBySessionDisplay,
|
|
2296
|
+
socialSharePctDisplay: traffic.socialSharePctDisplay,
|
|
2297
|
+
directSharePctDisplay: traffic.directSharePctDisplay,
|
|
2185
2298
|
aiReferrals: traffic.aiReferrals,
|
|
2299
|
+
aiReferralLandingPages: traffic.aiReferralLandingPages,
|
|
2186
2300
|
socialReferrals: traffic.socialReferrals,
|
|
2187
2301
|
periodStart: traffic.periodStart,
|
|
2188
2302
|
periodEnd: traffic.periodEnd
|
|
@@ -2199,10 +2313,10 @@ async function gaAttribution(project, opts) {
|
|
|
2199
2313
|
console.log(` Total Users: ${traffic.totalUsers}`);
|
|
2200
2314
|
console.log();
|
|
2201
2315
|
console.log(" CHANNEL BREAKDOWN");
|
|
2202
|
-
console.log(` Organic Search: ${traffic.totalOrganicSessions} sessions (${traffic.
|
|
2203
|
-
console.log(` Social: ${traffic.socialSessions} sessions (${traffic.
|
|
2204
|
-
console.log(` Direct: ${traffic.totalDirectSessions} sessions (${traffic.
|
|
2205
|
-
console.log(` AI Referrals: ${traffic.aiSessionsBySession} sessions (${traffic.
|
|
2316
|
+
console.log(` Organic Search: ${traffic.totalOrganicSessions} sessions (${traffic.organicSharePctDisplay})`);
|
|
2317
|
+
console.log(` Social: ${traffic.socialSessions} sessions (${traffic.socialSharePctDisplay})`);
|
|
2318
|
+
console.log(` Direct: ${traffic.totalDirectSessions} sessions (${traffic.directSharePctDisplay})`);
|
|
2319
|
+
console.log(` AI Referrals: ${traffic.aiSessionsBySession} sessions (${traffic.aiSharePctBySessionDisplay}) (lower bound \u2014 sessionSource only; referrer-stripped traffic falls under Direct)`);
|
|
2206
2320
|
const otherSessions = traffic.totalSessions - traffic.totalOrganicSessions - traffic.aiSessionsBySession - traffic.socialSessions - traffic.totalDirectSessions;
|
|
2207
2321
|
if (otherSessions > 0) {
|
|
2208
2322
|
const otherPct = traffic.totalSessions > 0 ? Math.round(otherSessions / traffic.totalSessions * 100) : 0;
|
|
@@ -2216,6 +2330,16 @@ async function gaAttribution(project, opts) {
|
|
|
2216
2330
|
console.log(` ${ref.source.padEnd(25)} ${String(ref.sessions).padEnd(8)} sessions (${dimLabel})`);
|
|
2217
2331
|
}
|
|
2218
2332
|
}
|
|
2333
|
+
if (traffic.aiReferralLandingPages.length > 0) {
|
|
2334
|
+
console.log();
|
|
2335
|
+
console.log(" AI LANDING PAGES");
|
|
2336
|
+
for (const row of traffic.aiReferralLandingPages.slice(0, 10)) {
|
|
2337
|
+
const dimLabel = row.sourceDimension === "first_user" ? "first-visit" : row.sourceDimension === "manual_utm" ? "utm" : "session";
|
|
2338
|
+
const page = row.landingPage.length > 30 ? row.landingPage.slice(0, 27) + "..." : row.landingPage;
|
|
2339
|
+
const source = row.source.length > 22 ? row.source.slice(0, 19) + "..." : row.source;
|
|
2340
|
+
console.log(` ${page.padEnd(30)} ${source.padEnd(22)} ${String(row.sessions).padEnd(8)} sessions (${dimLabel})`);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2219
2343
|
if (traffic.socialReferrals.length > 0) {
|
|
2220
2344
|
console.log();
|
|
2221
2345
|
console.log(" SOCIAL SOURCES");
|
|
@@ -7287,6 +7411,18 @@ async function serveCommand(format = "text") {
|
|
|
7287
7411
|
} catch (err) {
|
|
7288
7412
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7289
7413
|
process.stderr.write(`warning: normalized-path backfill skipped: ${msg}
|
|
7414
|
+
`);
|
|
7415
|
+
}
|
|
7416
|
+
try {
|
|
7417
|
+
const result = backfillAiReferralPaths(db);
|
|
7418
|
+
if (result.updated > 0 && format === "text") {
|
|
7419
|
+
console.log(
|
|
7420
|
+
`Migrated ${result.updated} GA AI referral row${result.updated === 1 ? "" : "s"} to canonical form.`
|
|
7421
|
+
);
|
|
7422
|
+
}
|
|
7423
|
+
} catch (err) {
|
|
7424
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7425
|
+
process.stderr.write(`warning: ai-referral-paths backfill skipped: ${msg}
|
|
7290
7426
|
`);
|
|
7291
7427
|
}
|
|
7292
7428
|
const app = await createServer({ config, db });
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-QJPPK4WW.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-TUXS2Y6C.js";
|
|
7
|
+
import "./chunk-UQH5SKM2.js";
|
|
8
8
|
import "./chunk-MLKGABMK.js";
|
|
9
9
|
export {
|
|
10
10
|
createServer,
|
package/dist/mcp.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainyc/canonry",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.1",
|
|
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-api-routes": "0.0.0",
|
|
63
62
|
"@ainyc/canonry-config": "0.0.0",
|
|
64
|
-
"@ainyc/canonry-
|
|
65
|
-
"@ainyc/canonry-intelligence": "0.0.0",
|
|
66
|
-
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
63
|
+
"@ainyc/canonry-api-routes": "0.0.0",
|
|
67
64
|
"@ainyc/canonry-db": "0.0.0",
|
|
68
|
-
"@ainyc/canonry-
|
|
65
|
+
"@ainyc/canonry-intelligence": "0.0.0",
|
|
69
66
|
"@ainyc/canonry-integration-google": "0.0.0",
|
|
70
|
-
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
71
67
|
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
68
|
+
"@ainyc/canonry-contracts": "0.0.0",
|
|
69
|
+
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
70
|
+
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
71
|
+
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
72
72
|
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
73
|
-
"@ainyc/canonry-provider-local": "0.0.0",
|
|
74
73
|
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
75
|
-
"@ainyc/canonry-provider-
|
|
76
|
-
"@ainyc/canonry-provider-perplexity": "0.0.0"
|
|
74
|
+
"@ainyc/canonry-provider-local": "0.0.0",
|
|
75
|
+
"@ainyc/canonry-provider-perplexity": "0.0.0",
|
|
76
|
+
"@ainyc/canonry-provider-openai": "0.0.0"
|
|
77
77
|
},
|
|
78
78
|
"scripts": {
|
|
79
79
|
"build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",
|