@ainyc/canonry 3.1.0 → 3.2.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/assets/index-DZm9xxNs.css +1 -0
- package/assets/assets/index-Z2rYL3K8.js +302 -0
- package/assets/index.html +2 -2
- package/dist/{chunk-VUP7AQTD.js → chunk-33ATDBGM.js} +53 -15
- package/dist/{chunk-NEDRCOOL.js → chunk-UQH5SKM2.js} +18 -2
- package/dist/{chunk-5X3TJ5BC.js → chunk-YGIWXQGM.js} +10 -0
- package/dist/cli.js +134 -8
- 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 +8 -8
- package/assets/assets/index-CEF4UuGT.css +0 -1
- package/assets/assets/index-DOxzPUzl.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-Z2rYL3K8.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-YGIWXQGM.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);
|
|
@@ -8964,6 +8966,8 @@ async function ga4Routes(app, opts) {
|
|
|
8964
8966
|
source: row.source,
|
|
8965
8967
|
medium: row.medium,
|
|
8966
8968
|
sourceDimension: row.sourceDimension,
|
|
8969
|
+
landingPage: row.landingPage,
|
|
8970
|
+
landingPageNormalized: normalizeUrlPath(row.landingPage),
|
|
8967
8971
|
sessions: row.sessions,
|
|
8968
8972
|
users: row.users,
|
|
8969
8973
|
syncedAt: now,
|
|
@@ -9080,16 +9084,35 @@ async function ga4Routes(app, opts) {
|
|
|
9080
9084
|
sessions: sql5`SUM(${gaAiReferrals.sessions})`,
|
|
9081
9085
|
users: sql5`SUM(${gaAiReferrals.users})`
|
|
9082
9086
|
}).from(gaAiReferrals).where(and8(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).orderBy(sql5`SUM(${gaAiReferrals.sessions}) DESC`).all();
|
|
9087
|
+
const aiReferralLandingPages = app.db.select({
|
|
9088
|
+
source: gaAiReferrals.source,
|
|
9089
|
+
medium: gaAiReferrals.medium,
|
|
9090
|
+
sourceDimension: gaAiReferrals.sourceDimension,
|
|
9091
|
+
landingPage: sql5`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
|
|
9092
|
+
sessions: sql5`SUM(${gaAiReferrals.sessions})`,
|
|
9093
|
+
users: sql5`SUM(${gaAiReferrals.users})`
|
|
9094
|
+
}).from(gaAiReferrals).where(and8(...aiConditions)).groupBy(
|
|
9095
|
+
gaAiReferrals.source,
|
|
9096
|
+
gaAiReferrals.medium,
|
|
9097
|
+
gaAiReferrals.sourceDimension,
|
|
9098
|
+
sql5`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`
|
|
9099
|
+
).orderBy(sql5`SUM(${gaAiReferrals.sessions}) DESC`).all();
|
|
9083
9100
|
const aiDeduped = app.db.select({
|
|
9084
|
-
sessions: sql5`SUM(max_sessions)`,
|
|
9085
|
-
users: sql5`SUM(max_users)`
|
|
9101
|
+
sessions: sql5`COALESCE(SUM(max_sessions), 0)`,
|
|
9102
|
+
users: sql5`COALESCE(SUM(max_users), 0)`
|
|
9086
9103
|
}).from(
|
|
9087
9104
|
sql5`(
|
|
9088
9105
|
SELECT date, source, medium,
|
|
9089
|
-
MAX(
|
|
9090
|
-
MAX(
|
|
9091
|
-
FROM
|
|
9092
|
-
|
|
9106
|
+
MAX(dimension_sessions) AS max_sessions,
|
|
9107
|
+
MAX(dimension_users) AS max_users
|
|
9108
|
+
FROM (
|
|
9109
|
+
SELECT date, source, medium, source_dimension,
|
|
9110
|
+
SUM(sessions) AS dimension_sessions,
|
|
9111
|
+
SUM(users) AS dimension_users
|
|
9112
|
+
FROM ga_ai_referrals
|
|
9113
|
+
WHERE project_id = ${project.id}${cutoffDate ? sql5` AND date >= ${cutoffDate}` : sql5``}
|
|
9114
|
+
GROUP BY date, source, medium, source_dimension
|
|
9115
|
+
)
|
|
9093
9116
|
GROUP BY date, source, medium
|
|
9094
9117
|
)`
|
|
9095
9118
|
).get();
|
|
@@ -9130,6 +9153,14 @@ async function ga4Routes(app, opts) {
|
|
|
9130
9153
|
sessions: r.sessions ?? 0,
|
|
9131
9154
|
users: r.users ?? 0
|
|
9132
9155
|
})),
|
|
9156
|
+
aiReferralLandingPages: aiReferralLandingPages.map((r) => ({
|
|
9157
|
+
source: r.source,
|
|
9158
|
+
medium: r.medium,
|
|
9159
|
+
sourceDimension: r.sourceDimension,
|
|
9160
|
+
landingPage: r.landingPage,
|
|
9161
|
+
sessions: r.sessions ?? 0,
|
|
9162
|
+
users: r.users ?? 0
|
|
9163
|
+
})),
|
|
9133
9164
|
aiSessionsDeduped: aiDeduped?.sessions ?? 0,
|
|
9134
9165
|
aiUsersDeduped: aiDeduped?.users ?? 0,
|
|
9135
9166
|
aiSessionsBySession: aiBySession?.sessions ?? 0,
|
|
@@ -9168,10 +9199,17 @@ async function ga4Routes(app, opts) {
|
|
|
9168
9199
|
date: gaAiReferrals.date,
|
|
9169
9200
|
source: gaAiReferrals.source,
|
|
9170
9201
|
medium: gaAiReferrals.medium,
|
|
9202
|
+
landingPage: sql5`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
|
|
9171
9203
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
9172
|
-
sessions: gaAiReferrals.sessions
|
|
9173
|
-
users: gaAiReferrals.users
|
|
9174
|
-
}).from(gaAiReferrals).where(and8(...conditions)).
|
|
9204
|
+
sessions: sql5`SUM(${gaAiReferrals.sessions})`,
|
|
9205
|
+
users: sql5`SUM(${gaAiReferrals.users})`
|
|
9206
|
+
}).from(gaAiReferrals).where(and8(...conditions)).groupBy(
|
|
9207
|
+
gaAiReferrals.date,
|
|
9208
|
+
gaAiReferrals.source,
|
|
9209
|
+
gaAiReferrals.medium,
|
|
9210
|
+
gaAiReferrals.sourceDimension,
|
|
9211
|
+
sql5`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`
|
|
9212
|
+
).orderBy(gaAiReferrals.date).all();
|
|
9175
9213
|
return rows;
|
|
9176
9214
|
});
|
|
9177
9215
|
app.get("/projects/:name/ga/social-referral-history", async (request, _reply) => {
|
|
@@ -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;
|
|
@@ -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. */
|
|
@@ -1339,6 +1348,7 @@ var ga4AiReferralHistoryEntrySchema = z12.object({
|
|
|
1339
1348
|
date: z12.string(),
|
|
1340
1349
|
source: z12.string(),
|
|
1341
1350
|
medium: z12.string(),
|
|
1351
|
+
landingPage: z12.string(),
|
|
1342
1352
|
sessions: z12.number(),
|
|
1343
1353
|
users: z12.number(),
|
|
1344
1354
|
/** Which GA4 dimension this row came from: session (sessionSource), first_user (firstUserSource), or manual_utm (utm_source parameter) */
|
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-33ATDBGM.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-YGIWXQGM.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) {
|
|
@@ -2121,6 +2223,7 @@ async function gaAttribution(project, opts) {
|
|
|
2121
2223
|
organicSharePct: traffic.organicSharePct,
|
|
2122
2224
|
directSharePct: traffic.directSharePct,
|
|
2123
2225
|
aiReferrals: traffic.aiReferrals,
|
|
2226
|
+
aiReferralLandingPages: traffic.aiReferralLandingPages,
|
|
2124
2227
|
socialReferrals: traffic.socialReferrals,
|
|
2125
2228
|
trend
|
|
2126
2229
|
}, null, 2));
|
|
@@ -2183,6 +2286,7 @@ async function gaAttribution(project, opts) {
|
|
|
2183
2286
|
organicSharePct: traffic.organicSharePct,
|
|
2184
2287
|
directSharePct: traffic.directSharePct,
|
|
2185
2288
|
aiReferrals: traffic.aiReferrals,
|
|
2289
|
+
aiReferralLandingPages: traffic.aiReferralLandingPages,
|
|
2186
2290
|
socialReferrals: traffic.socialReferrals,
|
|
2187
2291
|
periodStart: traffic.periodStart,
|
|
2188
2292
|
periodEnd: traffic.periodEnd
|
|
@@ -2216,6 +2320,16 @@ async function gaAttribution(project, opts) {
|
|
|
2216
2320
|
console.log(` ${ref.source.padEnd(25)} ${String(ref.sessions).padEnd(8)} sessions (${dimLabel})`);
|
|
2217
2321
|
}
|
|
2218
2322
|
}
|
|
2323
|
+
if (traffic.aiReferralLandingPages.length > 0) {
|
|
2324
|
+
console.log();
|
|
2325
|
+
console.log(" AI LANDING PAGES");
|
|
2326
|
+
for (const row of traffic.aiReferralLandingPages.slice(0, 10)) {
|
|
2327
|
+
const dimLabel = row.sourceDimension === "first_user" ? "first-visit" : row.sourceDimension === "manual_utm" ? "utm" : "session";
|
|
2328
|
+
const page = row.landingPage.length > 30 ? row.landingPage.slice(0, 27) + "..." : row.landingPage;
|
|
2329
|
+
const source = row.source.length > 22 ? row.source.slice(0, 19) + "..." : row.source;
|
|
2330
|
+
console.log(` ${page.padEnd(30)} ${source.padEnd(22)} ${String(row.sessions).padEnd(8)} sessions (${dimLabel})`);
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2219
2333
|
if (traffic.socialReferrals.length > 0) {
|
|
2220
2334
|
console.log();
|
|
2221
2335
|
console.log(" SOCIAL SOURCES");
|
|
@@ -7287,6 +7401,18 @@ async function serveCommand(format = "text") {
|
|
|
7287
7401
|
} catch (err) {
|
|
7288
7402
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7289
7403
|
process.stderr.write(`warning: normalized-path backfill skipped: ${msg}
|
|
7404
|
+
`);
|
|
7405
|
+
}
|
|
7406
|
+
try {
|
|
7407
|
+
const result = backfillAiReferralPaths(db);
|
|
7408
|
+
if (result.updated > 0 && format === "text") {
|
|
7409
|
+
console.log(
|
|
7410
|
+
`Migrated ${result.updated} GA AI referral row${result.updated === 1 ? "" : "s"} to canonical form.`
|
|
7411
|
+
);
|
|
7412
|
+
}
|
|
7413
|
+
} catch (err) {
|
|
7414
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7415
|
+
process.stderr.write(`warning: ai-referral-paths backfill skipped: ${msg}
|
|
7290
7416
|
`);
|
|
7291
7417
|
}
|
|
7292
7418
|
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-33ATDBGM.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-YGIWXQGM.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.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",
|
|
@@ -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-contracts": "0.0.0",
|
|
63
62
|
"@ainyc/canonry-api-routes": "0.0.0",
|
|
64
63
|
"@ainyc/canonry-config": "0.0.0",
|
|
65
|
-
"@ainyc/canonry-
|
|
66
|
-
"@ainyc/canonry-intelligence": "0.0.0",
|
|
64
|
+
"@ainyc/canonry-contracts": "0.0.0",
|
|
67
65
|
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
66
|
+
"@ainyc/canonry-db": "0.0.0",
|
|
68
67
|
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
69
68
|
"@ainyc/canonry-integration-google": "0.0.0",
|
|
70
|
-
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
71
69
|
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
70
|
+
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
71
|
+
"@ainyc/canonry-intelligence": "0.0.0",
|
|
72
|
+
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
72
73
|
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
73
74
|
"@ainyc/canonry-provider-local": "0.0.0",
|
|
74
|
-
"@ainyc/canonry-provider-
|
|
75
|
-
"@ainyc/canonry-provider-
|
|
76
|
-
"@ainyc/canonry-provider-openai": "0.0.0"
|
|
75
|
+
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
76
|
+
"@ainyc/canonry-provider-perplexity": "0.0.0"
|
|
77
77
|
},
|
|
78
78
|
"scripts": {
|
|
79
79
|
"build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",
|