@ainyc/canonry 4.13.1 → 4.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/assets/{index-BofSsfDl.js → index-C3thP3DI.js} +122 -122
- package/assets/index.html +1 -1
- package/dist/{chunk-LNRDWAG3.js → chunk-5NYG5EC7.js} +1 -1
- package/dist/{chunk-YDGT5CAY.js → chunk-6QTH5NS5.js} +71 -4
- package/dist/{chunk-DCE3B6KD.js → chunk-7HBZCGRL.js} +18 -2
- package/dist/{chunk-RIGQFQJJ.js → chunk-FRDVC2XF.js} +104 -47
- package/dist/cli.js +23 -23
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-NT24OLLA.js → intelligence-service-BCKXIKIL.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +6 -6
package/assets/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
|
|
13
13
|
<link rel="apple-touch-icon" href="./apple-touch-icon.png" />
|
|
14
14
|
<title>Canonry</title>
|
|
15
|
-
<script type="module" crossorigin src="./assets/index-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-C3thP3DI.js"></script>
|
|
16
16
|
<link rel="stylesheet" crossorigin href="./assets/index-D0EPNRDs.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
@@ -1239,6 +1239,18 @@ var ga4SocialReferralDtoSchema = z12.object({
|
|
|
1239
1239
|
/** GA4 default channel group (e.g. 'Organic Social', 'Paid Social') */
|
|
1240
1240
|
channelGroup: z12.string()
|
|
1241
1241
|
});
|
|
1242
|
+
var ga4ChannelBucketDtoSchema = z12.object({
|
|
1243
|
+
sessions: z12.number(),
|
|
1244
|
+
sharePct: z12.number(),
|
|
1245
|
+
sharePctDisplay: z12.string()
|
|
1246
|
+
});
|
|
1247
|
+
var ga4ChannelBreakdownDtoSchema = z12.object({
|
|
1248
|
+
organic: ga4ChannelBucketDtoSchema,
|
|
1249
|
+
social: ga4ChannelBucketDtoSchema,
|
|
1250
|
+
direct: ga4ChannelBucketDtoSchema,
|
|
1251
|
+
ai: ga4ChannelBucketDtoSchema,
|
|
1252
|
+
other: ga4ChannelBucketDtoSchema
|
|
1253
|
+
});
|
|
1242
1254
|
var ga4TrafficSummaryDtoSchema = z12.object({
|
|
1243
1255
|
totalSessions: z12.number(),
|
|
1244
1256
|
totalOrganicSessions: z12.number(),
|
|
@@ -1259,20 +1271,22 @@ var ga4TrafficSummaryDtoSchema = z12.object({
|
|
|
1259
1271
|
aiSessionsDeduped: z12.number(),
|
|
1260
1272
|
/** Deduped AI user total: MAX(users) per date+source+medium across attribution dimensions, then summed. */
|
|
1261
1273
|
aiUsersDeduped: z12.number(),
|
|
1262
|
-
/** AI sessions whose CURRENT sessionSource matched an AI engine.
|
|
1274
|
+
/** AI sessions whose CURRENT sessionSource matched an AI engine. Can overlap with raw Organic/Social/Direct totals; `channelBreakdown` removes those overlaps for display. */
|
|
1263
1275
|
aiSessionsBySession: z12.number(),
|
|
1264
|
-
/** AI users whose CURRENT sessionSource matched an AI engine.
|
|
1276
|
+
/** AI users whose CURRENT sessionSource matched an AI engine. Can overlap with raw Organic/Social/Direct totals. */
|
|
1265
1277
|
aiUsersBySession: z12.number(),
|
|
1266
1278
|
socialReferrals: z12.array(ga4SocialReferralDtoSchema),
|
|
1267
1279
|
/** Total social sessions (session-scoped, no cross-dimension dedup needed). */
|
|
1268
1280
|
socialSessions: z12.number(),
|
|
1269
1281
|
/** Total social users (session-scoped, no cross-dimension dedup needed). */
|
|
1270
1282
|
socialUsers: z12.number(),
|
|
1283
|
+
/** Five disjoint buckets used for the channel breakdown. Known AI session-source matches are removed from their native GA4 bucket before shares are computed. */
|
|
1284
|
+
channelBreakdown: ga4ChannelBreakdownDtoSchema,
|
|
1271
1285
|
/** Organic sessions as a percentage of total sessions (0–100, rounded). */
|
|
1272
1286
|
organicSharePct: z12.number(),
|
|
1273
1287
|
/** Deduped AI sessions as a percentage of total sessions (0–100, rounded). Cross-cutting: can overlap with Direct/Organic/Social. */
|
|
1274
1288
|
aiSharePct: z12.number(),
|
|
1275
|
-
/** Session-source-only AI sessions as a percentage of total sessions (0–100, rounded).
|
|
1289
|
+
/** Session-source-only AI sessions as a percentage of total sessions (0–100, rounded). Can overlap with raw Organic/Social/Direct totals. */
|
|
1276
1290
|
aiSharePctBySession: z12.number(),
|
|
1277
1291
|
/** Direct-channel sessions as a percentage of total sessions (0–100, rounded). */
|
|
1278
1292
|
directSharePct: z12.number(),
|
|
@@ -1288,6 +1302,12 @@ var ga4TrafficSummaryDtoSchema = z12.object({
|
|
|
1288
1302
|
directSharePctDisplay: z12.string(),
|
|
1289
1303
|
/** Display string for socialSharePct: 'X%', '<1%' for non-zero shares that round below 1, or '—' when sessions exist but total is unknown (partial sync). */
|
|
1290
1304
|
socialSharePctDisplay: z12.string(),
|
|
1305
|
+
/** Sessions not covered by Organic, Social, Direct, or AI (session) channels — e.g. Referral, Email, Paid Search, Display. Always non-negative; clamped to 0 when the four disjoint channels sum above total (rounding edge). */
|
|
1306
|
+
otherSessions: z12.number(),
|
|
1307
|
+
/** Other sessions as a percentage of total sessions (0–100, rounded). */
|
|
1308
|
+
otherSharePct: z12.number(),
|
|
1309
|
+
/** Display string for otherSharePct: 'X%', '<1%' for non-zero shares that round below 1, or '—' when sessions exist but total is unknown (partial sync). */
|
|
1310
|
+
otherSharePctDisplay: z12.string(),
|
|
1291
1311
|
lastSyncedAt: z12.string().nullable()
|
|
1292
1312
|
});
|
|
1293
1313
|
var ga4AiReferralHistoryEntrySchema = z12.object({
|
|
@@ -2232,6 +2252,48 @@ var trafficSyncResponseSchema = z20.object({
|
|
|
2232
2252
|
windowEnd: z20.string()
|
|
2233
2253
|
});
|
|
2234
2254
|
|
|
2255
|
+
// ../contracts/src/formatting.ts
|
|
2256
|
+
function formatRatio(value) {
|
|
2257
|
+
if (!Number.isFinite(value) || value === 0) return "0%";
|
|
2258
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
2259
|
+
}
|
|
2260
|
+
function formatNumber(value) {
|
|
2261
|
+
if (!Number.isFinite(value)) return "\u2014";
|
|
2262
|
+
if (Math.abs(value) >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
|
|
2263
|
+
if (Math.abs(value) >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
|
|
2264
|
+
return value.toLocaleString("en-US");
|
|
2265
|
+
}
|
|
2266
|
+
function formatDate(iso) {
|
|
2267
|
+
if (!iso) return "\u2014";
|
|
2268
|
+
try {
|
|
2269
|
+
const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
|
|
2270
|
+
const options = { month: "short", day: "numeric", year: "numeric" };
|
|
2271
|
+
const d = dateOnly && dateOnly[1] && dateOnly[2] && dateOnly[3] ? new Date(Date.UTC(Number(dateOnly[1]), Number(dateOnly[2]) - 1, Number(dateOnly[3]))) : new Date(iso);
|
|
2272
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
2273
|
+
return d.toLocaleDateString("en-US", dateOnly ? { ...options, timeZone: "UTC" } : options);
|
|
2274
|
+
} catch {
|
|
2275
|
+
return iso;
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
function formatIsoDate(iso) {
|
|
2279
|
+
if (!iso) return "\u2014";
|
|
2280
|
+
try {
|
|
2281
|
+
const d = new Date(iso);
|
|
2282
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
2283
|
+
const yyyy = d.getUTCFullYear();
|
|
2284
|
+
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
2285
|
+
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
2286
|
+
return `${yyyy}-${mm}-${dd}`;
|
|
2287
|
+
} catch {
|
|
2288
|
+
return iso;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
function formatDateRange(start, end) {
|
|
2292
|
+
if (!start && !end) return "";
|
|
2293
|
+
if (start && end) return `${formatDate(start)} \u2192 ${formatDate(end)}`;
|
|
2294
|
+
return formatDate(start || end);
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2235
2297
|
export {
|
|
2236
2298
|
__export,
|
|
2237
2299
|
providerQuotaPolicySchema,
|
|
@@ -2326,5 +2388,10 @@ export {
|
|
|
2326
2388
|
TrafficEvidenceKinds,
|
|
2327
2389
|
TrafficEventConfidences,
|
|
2328
2390
|
TrafficSourceStatuses,
|
|
2329
|
-
TrafficSourceAuthModes
|
|
2391
|
+
TrafficSourceAuthModes,
|
|
2392
|
+
formatRatio,
|
|
2393
|
+
formatNumber,
|
|
2394
|
+
formatDate,
|
|
2395
|
+
formatIsoDate,
|
|
2396
|
+
formatDateRange
|
|
2330
2397
|
};
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
categoryLabel,
|
|
9
9
|
determineAnswerMentioned,
|
|
10
10
|
normalizeProjectDomain
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-6QTH5NS5.js";
|
|
12
12
|
|
|
13
13
|
// src/intelligence-service.ts
|
|
14
14
|
import { eq, desc, asc, and, or, inArray } from "drizzle-orm";
|
|
@@ -358,6 +358,8 @@ var gaAiReferrals = sqliteTable("ga_ai_referrals", {
|
|
|
358
358
|
medium: text("medium").notNull(),
|
|
359
359
|
/** Which GA4 dimension produced this row: 'session' | 'first_user' | 'manual_utm' */
|
|
360
360
|
sourceDimension: text("source_dimension").notNull().default("session"),
|
|
361
|
+
/** GA4 default channel group for the session (e.g. 'Referral', 'Organic Social'). */
|
|
362
|
+
channelGroup: text("channel_group").notNull().default("(not set)"),
|
|
361
363
|
landingPage: text("landing_page").notNull().default("(not set)"),
|
|
362
364
|
landingPageNormalized: text("landing_page_normalized"),
|
|
363
365
|
sessions: integer("sessions").notNull().default(0),
|
|
@@ -368,7 +370,7 @@ var gaAiReferrals = sqliteTable("ga_ai_referrals", {
|
|
|
368
370
|
index("idx_ga_ai_ref_project_date").on(table.projectId, table.date),
|
|
369
371
|
index("idx_ga_ai_ref_source").on(table.source),
|
|
370
372
|
index("idx_ga_ai_ref_landing_page").on(table.projectId, table.date, table.landingPageNormalized),
|
|
371
|
-
uniqueIndex("
|
|
373
|
+
uniqueIndex("idx_ga_ai_ref_unique_v4").on(table.projectId, table.date, table.source, table.medium, table.sourceDimension, table.channelGroup, table.landingPage),
|
|
372
374
|
index("idx_ga_ai_ref_run").on(table.syncRunId)
|
|
373
375
|
]);
|
|
374
376
|
var gaSocialReferrals = sqliteTable("ga_social_referrals", {
|
|
@@ -1570,6 +1572,20 @@ var MIGRATION_VERSIONS = [
|
|
|
1570
1572
|
`CREATE INDEX IF NOT EXISTS idx_raw_event_samples_source_ts ON raw_event_samples(source_id, ts)`,
|
|
1571
1573
|
`CREATE INDEX IF NOT EXISTS idx_raw_event_samples_event_type ON raw_event_samples(event_type)`
|
|
1572
1574
|
]
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
version: 50,
|
|
1578
|
+
name: "ga-ai-referral-channel-group",
|
|
1579
|
+
statements: [],
|
|
1580
|
+
run: (tx) => {
|
|
1581
|
+
if (!tableExists(tx, "ga_ai_referrals")) return;
|
|
1582
|
+
if (!columnExists(tx, "ga_ai_referrals", "channel_group")) {
|
|
1583
|
+
tx.run(sql.raw(`ALTER TABLE ga_ai_referrals ADD COLUMN channel_group TEXT NOT NULL DEFAULT '(not set)'`));
|
|
1584
|
+
}
|
|
1585
|
+
tx.run(sql.raw(`DROP INDEX IF EXISTS idx_ga_ai_ref_unique_v3`));
|
|
1586
|
+
tx.run(sql.raw(`CREATE UNIQUE INDEX IF NOT EXISTS idx_ga_ai_ref_unique_v4
|
|
1587
|
+
ON ga_ai_referrals(project_id, date, source, medium, source_dimension, channel_group, landing_page)`));
|
|
1588
|
+
}
|
|
1573
1589
|
}
|
|
1574
1590
|
];
|
|
1575
1591
|
function isDuplicateColumnError(err) {
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
configExists,
|
|
5
5
|
loadConfig,
|
|
6
6
|
saveConfigPatch
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-5NYG5EC7.js";
|
|
8
8
|
import {
|
|
9
9
|
DEFAULT_RUN_HISTORY_LIMIT,
|
|
10
10
|
IntelligenceService,
|
|
@@ -65,7 +65,7 @@ import {
|
|
|
65
65
|
schedules,
|
|
66
66
|
trafficSources,
|
|
67
67
|
usageCounters
|
|
68
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-7HBZCGRL.js";
|
|
69
69
|
import {
|
|
70
70
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
71
71
|
AGENT_PROVIDER_IDS,
|
|
@@ -108,6 +108,11 @@ import {
|
|
|
108
108
|
emptyCitationVisibility,
|
|
109
109
|
extractAnswerMentions,
|
|
110
110
|
findDuplicateLocationLabels,
|
|
111
|
+
formatDate,
|
|
112
|
+
formatDateRange,
|
|
113
|
+
formatIsoDate,
|
|
114
|
+
formatNumber,
|
|
115
|
+
formatRatio,
|
|
111
116
|
getProviderLocationHandling,
|
|
112
117
|
hasLocationLabel,
|
|
113
118
|
internalError,
|
|
@@ -146,7 +151,7 @@ import {
|
|
|
146
151
|
visibilityStateFromAnswerMentioned,
|
|
147
152
|
windowCutoff,
|
|
148
153
|
wordpressEnvSchema
|
|
149
|
-
} from "./chunk-
|
|
154
|
+
} from "./chunk-6QTH5NS5.js";
|
|
150
155
|
|
|
151
156
|
// src/telemetry.ts
|
|
152
157
|
import crypto from "crypto";
|
|
@@ -2584,16 +2589,6 @@ var COLORS = {
|
|
|
2584
2589
|
function escapeHtml(value) {
|
|
2585
2590
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2586
2591
|
}
|
|
2587
|
-
function formatRatio(value) {
|
|
2588
|
-
if (!Number.isFinite(value) || value === 0) return "0%";
|
|
2589
|
-
return `${(value * 100).toFixed(1)}%`;
|
|
2590
|
-
}
|
|
2591
|
-
function formatNumber(value) {
|
|
2592
|
-
if (!Number.isFinite(value)) return "\u2014";
|
|
2593
|
-
if (Math.abs(value) >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
|
|
2594
|
-
if (Math.abs(value) >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
|
|
2595
|
-
return value.toLocaleString("en-US");
|
|
2596
|
-
}
|
|
2597
2592
|
function summarizeQueryParams(params) {
|
|
2598
2593
|
const keys = Array.from(params.keys());
|
|
2599
2594
|
const total = keys.length;
|
|
@@ -2636,23 +2631,6 @@ function formatLandingPageHtml(raw) {
|
|
|
2636
2631
|
if (!summary) return pathHtml;
|
|
2637
2632
|
return `${pathHtml}<span class="page-query" title="${escapeHtml(value)}">${escapeHtml(summary)}</span>`;
|
|
2638
2633
|
}
|
|
2639
|
-
function formatDate(iso) {
|
|
2640
|
-
if (!iso) return "\u2014";
|
|
2641
|
-
try {
|
|
2642
|
-
const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
|
|
2643
|
-
const options = { month: "short", day: "numeric", year: "numeric" };
|
|
2644
|
-
const d = dateOnly && dateOnly[1] && dateOnly[2] && dateOnly[3] ? new Date(Date.UTC(Number(dateOnly[1]), Number(dateOnly[2]) - 1, Number(dateOnly[3]))) : new Date(iso);
|
|
2645
|
-
if (Number.isNaN(d.getTime())) return iso;
|
|
2646
|
-
return d.toLocaleDateString("en-US", dateOnly ? { ...options, timeZone: "UTC" } : options);
|
|
2647
|
-
} catch {
|
|
2648
|
-
return iso;
|
|
2649
|
-
}
|
|
2650
|
-
}
|
|
2651
|
-
function formatDateRange(start, end) {
|
|
2652
|
-
if (!start && !end) return "";
|
|
2653
|
-
if (start && end) return `${formatDate(start)} \u2192 ${formatDate(end)}`;
|
|
2654
|
-
return formatDate(start || end);
|
|
2655
|
-
}
|
|
2656
2634
|
function gscDateRange(report) {
|
|
2657
2635
|
const summary = report.executiveSummary.gsc;
|
|
2658
2636
|
const gsc = report.gsc;
|
|
@@ -3461,8 +3439,32 @@ table.report-table td .badge {
|
|
|
3461
3439
|
.client-bar-row { grid-template-columns: 100px 1fr 100px; gap: 10px; }
|
|
3462
3440
|
}
|
|
3463
3441
|
@media print {
|
|
3464
|
-
|
|
3465
|
-
|
|
3442
|
+
@page { margin: 0.5in; }
|
|
3443
|
+
html, body {
|
|
3444
|
+
background: ${COLORS.bg};
|
|
3445
|
+
color: ${COLORS.text};
|
|
3446
|
+
-webkit-print-color-adjust: exact;
|
|
3447
|
+
print-color-adjust: exact;
|
|
3448
|
+
}
|
|
3449
|
+
.container { max-width: none; padding: 0; }
|
|
3450
|
+
section.report-section,
|
|
3451
|
+
.executive-hero,
|
|
3452
|
+
.headline-card,
|
|
3453
|
+
.hero-proof,
|
|
3454
|
+
.client-hero,
|
|
3455
|
+
.client-metric-tile,
|
|
3456
|
+
.client-card,
|
|
3457
|
+
.client-note,
|
|
3458
|
+
.chart-card,
|
|
3459
|
+
.action-card,
|
|
3460
|
+
.insight-card,
|
|
3461
|
+
.source-bar-row,
|
|
3462
|
+
.client-bar-row,
|
|
3463
|
+
tr,
|
|
3464
|
+
table { break-inside: avoid; }
|
|
3465
|
+
h1, h2, h3, .eyebrow { break-after: avoid; }
|
|
3466
|
+
.footer { margin-top: 32px; }
|
|
3467
|
+
.footer a { color: ${COLORS.text}; }
|
|
3466
3468
|
}
|
|
3467
3469
|
`;
|
|
3468
3470
|
function section(opts, body) {
|
|
@@ -4634,7 +4636,7 @@ function renderReportHtml(report, opts = {}) {
|
|
|
4634
4636
|
<div class="subtitle">${escapeHtml(report.meta.project.canonicalDomain)} \xB7 ${escapeHtml(report.meta.project.country)} / ${escapeHtml(report.meta.project.language.toUpperCase())}${renderHeaderLocationFragment(report.meta.location)} \xB7 Generated ${formatDate(report.meta.generatedAt)}</div>
|
|
4635
4637
|
</header>
|
|
4636
4638
|
${sections}
|
|
4637
|
-
<footer class="footer">Generated by canonry \xB7 ${escapeHtml(report.meta.generatedAt)}</footer>
|
|
4639
|
+
<footer class="footer">Generated by <a href="https://canonry.ai">canonry</a> \xB7 ${escapeHtml(formatIsoDate(report.meta.generatedAt))}</footer>
|
|
4638
4640
|
</div>
|
|
4639
4641
|
<script type="application/json" id="canonry-report-data">${json}</script>
|
|
4640
4642
|
</body>
|
|
@@ -11025,6 +11027,7 @@ async function fetchAiReferrals(accessToken, propertyId, days) {
|
|
|
11025
11027
|
{ name: "date" },
|
|
11026
11028
|
{ name: sourceDim },
|
|
11027
11029
|
{ name: mediumDim },
|
|
11030
|
+
{ name: "sessionDefaultChannelGroup" },
|
|
11028
11031
|
{ name: "landingPagePlusQueryString" }
|
|
11029
11032
|
],
|
|
11030
11033
|
metrics: [
|
|
@@ -11050,7 +11053,8 @@ async function fetchAiReferrals(accessToken, propertyId, days) {
|
|
|
11050
11053
|
date: row.dimensionValues[0].value,
|
|
11051
11054
|
source: row.dimensionValues[1].value,
|
|
11052
11055
|
medium: row.dimensionValues[2].value,
|
|
11053
|
-
|
|
11056
|
+
channelGroup: row.dimensionValues[3]?.value ?? "(not set)",
|
|
11057
|
+
landingPage: row.dimensionValues[4]?.value ?? "(not set)",
|
|
11054
11058
|
sessions: parseInt(row.metricValues[0].value, 10) || 0,
|
|
11055
11059
|
users: parseInt(row.metricValues[1].value, 10) || 0,
|
|
11056
11060
|
sourceDimension: dimLabel
|
|
@@ -11063,7 +11067,7 @@ async function fetchAiReferrals(accessToken, propertyId, days) {
|
|
|
11063
11067
|
}
|
|
11064
11068
|
const deduped = /* @__PURE__ */ new Map();
|
|
11065
11069
|
for (const row of rows) {
|
|
11066
|
-
const key = `${row.date}::${row.source}::${row.medium}::${row.sourceDimension}::${row.landingPage}`;
|
|
11070
|
+
const key = `${row.date}::${row.source}::${row.medium}::${row.sourceDimension}::${row.channelGroup}::${row.landingPage}`;
|
|
11067
11071
|
const existing = deduped.get(key);
|
|
11068
11072
|
if (!existing) {
|
|
11069
11073
|
deduped.set(key, row);
|
|
@@ -12657,6 +12661,37 @@ function formatSharePct(numerator, total) {
|
|
|
12657
12661
|
if (rounded === 0) return "<1%";
|
|
12658
12662
|
return `${rounded}%`;
|
|
12659
12663
|
}
|
|
12664
|
+
var SOCIAL_CHANNEL_GROUPS2 = /* @__PURE__ */ new Set(["Organic Social", "Paid Social"]);
|
|
12665
|
+
function buildChannelBreakdown(input) {
|
|
12666
|
+
const aiSessions = [...input.aiSessionsByChannelGroup.values()].reduce((sum, sessions) => sum + sessions, 0);
|
|
12667
|
+
const aiOrganicOverlap = Math.min(input.organicSessions, input.aiSessionsByChannelGroup.get("Organic Search") ?? 0);
|
|
12668
|
+
const aiSocialOverlap = Math.min(
|
|
12669
|
+
input.socialSessions,
|
|
12670
|
+
[...input.aiSessionsByChannelGroup.entries()].filter(([channelGroup]) => SOCIAL_CHANNEL_GROUPS2.has(channelGroup)).reduce((sum, [, sessions]) => sum + sessions, 0)
|
|
12671
|
+
);
|
|
12672
|
+
const aiDirectOverlap = Math.min(input.directSessions, input.aiSessionsByChannelGroup.get("Direct") ?? 0);
|
|
12673
|
+
const organicSessions = Math.max(0, input.organicSessions - aiOrganicOverlap);
|
|
12674
|
+
const socialSessions = Math.max(0, input.socialSessions - aiSocialOverlap);
|
|
12675
|
+
const directSessions = Math.max(0, input.directSessions - aiDirectOverlap);
|
|
12676
|
+
const coveredSessions = organicSessions + socialSessions + directSessions + aiSessions;
|
|
12677
|
+
const otherSessions = Math.max(0, input.totalSessions - coveredSessions);
|
|
12678
|
+
const bucket = (sessions) => ({
|
|
12679
|
+
sessions,
|
|
12680
|
+
sharePct: input.totalSessions > 0 ? Math.round(sessions / input.totalSessions * 100) : 0,
|
|
12681
|
+
sharePctDisplay: formatSharePct(sessions, input.totalSessions)
|
|
12682
|
+
});
|
|
12683
|
+
return {
|
|
12684
|
+
organic: bucket(organicSessions),
|
|
12685
|
+
social: bucket(socialSessions),
|
|
12686
|
+
direct: bucket(directSessions),
|
|
12687
|
+
ai: bucket(aiSessions),
|
|
12688
|
+
other: {
|
|
12689
|
+
sessions: otherSessions,
|
|
12690
|
+
sharePct: input.totalSessions > 0 ? Math.round(otherSessions / input.totalSessions * 100) : 0,
|
|
12691
|
+
sharePctDisplay: input.totalSessions <= 0 && coveredSessions > 0 ? "\u2014" : formatSharePct(otherSessions, input.totalSessions)
|
|
12692
|
+
}
|
|
12693
|
+
};
|
|
12694
|
+
}
|
|
12660
12695
|
function pickWinningDimension(rows, tupleKey) {
|
|
12661
12696
|
const winners = /* @__PURE__ */ new Map();
|
|
12662
12697
|
for (const row of rows) {
|
|
@@ -12945,6 +12980,7 @@ async function ga4Routes(app, opts) {
|
|
|
12945
12980
|
source: row.source,
|
|
12946
12981
|
medium: row.medium,
|
|
12947
12982
|
sourceDimension: row.sourceDimension,
|
|
12983
|
+
channelGroup: row.channelGroup,
|
|
12948
12984
|
landingPage: row.landingPage,
|
|
12949
12985
|
landingPageNormalized: normalizeUrlPath(row.landingPage),
|
|
12950
12986
|
sessions: row.sessions,
|
|
@@ -13136,10 +13172,18 @@ async function ga4Routes(app, opts) {
|
|
|
13136
13172
|
GROUP BY date, source, medium
|
|
13137
13173
|
)`
|
|
13138
13174
|
).get();
|
|
13139
|
-
const
|
|
13175
|
+
const aiBySessionRows = app.db.select({
|
|
13176
|
+
channelGroup: gaAiReferrals.channelGroup,
|
|
13140
13177
|
sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)`,
|
|
13141
13178
|
users: sql5`COALESCE(SUM(${gaAiReferrals.users}), 0)`
|
|
13142
|
-
}).from(gaAiReferrals).where(and9(...aiConditions, eq21(gaAiReferrals.sourceDimension, "session"))).
|
|
13179
|
+
}).from(gaAiReferrals).where(and9(...aiConditions, eq21(gaAiReferrals.sourceDimension, "session"))).groupBy(gaAiReferrals.channelGroup).all();
|
|
13180
|
+
const aiSessionsByChannelGroup = /* @__PURE__ */ new Map();
|
|
13181
|
+
let aiBySessionUsers = 0;
|
|
13182
|
+
for (const row of aiBySessionRows) {
|
|
13183
|
+
aiSessionsByChannelGroup.set(row.channelGroup, row.sessions ?? 0);
|
|
13184
|
+
aiBySessionUsers += row.users ?? 0;
|
|
13185
|
+
}
|
|
13186
|
+
const aiBySessionSessions = [...aiSessionsByChannelGroup.values()].reduce((sum, sessions) => sum + sessions, 0);
|
|
13143
13187
|
const socialReferrals = app.db.select({
|
|
13144
13188
|
source: gaSocialReferrals.source,
|
|
13145
13189
|
medium: gaSocialReferrals.medium,
|
|
@@ -13154,9 +13198,18 @@ async function ga4Routes(app, opts) {
|
|
|
13154
13198
|
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq21(gaTrafficSummaries.projectId, project.id)).orderBy(desc10(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
13155
13199
|
const total = summaryRow?.totalSessions ?? 0;
|
|
13156
13200
|
const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
|
|
13201
|
+
const totalOrganicSessions = summaryRow?.totalOrganicSessions ?? 0;
|
|
13202
|
+
const socialSessions = socialTotals?.sessions ?? 0;
|
|
13203
|
+
const channelBreakdown = buildChannelBreakdown({
|
|
13204
|
+
totalSessions: total,
|
|
13205
|
+
organicSessions: totalOrganicSessions,
|
|
13206
|
+
socialSessions,
|
|
13207
|
+
directSessions: totalDirectSessions,
|
|
13208
|
+
aiSessionsByChannelGroup
|
|
13209
|
+
});
|
|
13157
13210
|
return {
|
|
13158
13211
|
totalSessions: total,
|
|
13159
|
-
totalOrganicSessions
|
|
13212
|
+
totalOrganicSessions,
|
|
13160
13213
|
totalDirectSessions,
|
|
13161
13214
|
totalUsers: summaryRow?.totalUsers ?? 0,
|
|
13162
13215
|
topPages: rows.map((r) => ({
|
|
@@ -13183,8 +13236,8 @@ async function ga4Routes(app, opts) {
|
|
|
13183
13236
|
})),
|
|
13184
13237
|
aiSessionsDeduped: aiDeduped?.sessions ?? 0,
|
|
13185
13238
|
aiUsersDeduped: aiDeduped?.users ?? 0,
|
|
13186
|
-
aiSessionsBySession:
|
|
13187
|
-
aiUsersBySession:
|
|
13239
|
+
aiSessionsBySession: aiBySessionSessions,
|
|
13240
|
+
aiUsersBySession: aiBySessionUsers,
|
|
13188
13241
|
socialReferrals: socialReferrals.map((r) => ({
|
|
13189
13242
|
source: r.source,
|
|
13190
13243
|
medium: r.medium,
|
|
@@ -13192,18 +13245,22 @@ async function ga4Routes(app, opts) {
|
|
|
13192
13245
|
sessions: r.sessions ?? 0,
|
|
13193
13246
|
users: r.users ?? 0
|
|
13194
13247
|
})),
|
|
13195
|
-
socialSessions
|
|
13248
|
+
socialSessions,
|
|
13196
13249
|
socialUsers: socialTotals?.users ?? 0,
|
|
13197
|
-
|
|
13250
|
+
channelBreakdown,
|
|
13251
|
+
organicSharePct: total > 0 ? Math.round(totalOrganicSessions / total * 100) : 0,
|
|
13198
13252
|
aiSharePct: total > 0 ? Math.round((aiDeduped?.sessions ?? 0) / total * 100) : 0,
|
|
13199
|
-
aiSharePctBySession: total > 0 ? Math.round(
|
|
13253
|
+
aiSharePctBySession: total > 0 ? Math.round(aiBySessionSessions / total * 100) : 0,
|
|
13200
13254
|
directSharePct: total > 0 ? Math.round(totalDirectSessions / total * 100) : 0,
|
|
13201
|
-
socialSharePct: total > 0 ? Math.round(
|
|
13202
|
-
|
|
13255
|
+
socialSharePct: total > 0 ? Math.round(socialSessions / total * 100) : 0,
|
|
13256
|
+
otherSessions: channelBreakdown.other.sessions,
|
|
13257
|
+
otherSharePct: channelBreakdown.other.sharePct,
|
|
13258
|
+
otherSharePctDisplay: channelBreakdown.other.sharePctDisplay,
|
|
13259
|
+
organicSharePctDisplay: formatSharePct(totalOrganicSessions, total),
|
|
13203
13260
|
aiSharePctDisplay: formatSharePct(aiDeduped?.sessions ?? 0, total),
|
|
13204
|
-
aiSharePctBySessionDisplay: formatSharePct(
|
|
13261
|
+
aiSharePctBySessionDisplay: formatSharePct(aiBySessionSessions, total),
|
|
13205
13262
|
directSharePctDisplay: formatSharePct(totalDirectSessions, total),
|
|
13206
|
-
socialSharePctDisplay: formatSharePct(
|
|
13263
|
+
socialSharePctDisplay: formatSharePct(socialSessions, total),
|
|
13207
13264
|
lastSyncedAt: latestSync?.syncedAt ?? null,
|
|
13208
13265
|
periodStart: (() => {
|
|
13209
13266
|
const start = cutoffDate ?? summaryMeta?.periodStart ?? null;
|
package/dist/cli.js
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
setGoogleAuthConfig,
|
|
19
19
|
showFirstRunNotice,
|
|
20
20
|
trackEvent
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-FRDVC2XF.js";
|
|
22
22
|
import {
|
|
23
23
|
CliError,
|
|
24
24
|
EXIT_SYSTEM_ERROR,
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
saveConfig,
|
|
34
34
|
saveConfigPatch,
|
|
35
35
|
usageError
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-5NYG5EC7.js";
|
|
37
37
|
import {
|
|
38
38
|
apiKeys,
|
|
39
39
|
competitors,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
projects,
|
|
46
46
|
querySnapshots,
|
|
47
47
|
runs
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-7HBZCGRL.js";
|
|
49
49
|
import {
|
|
50
50
|
CcReleaseSyncStatuses,
|
|
51
51
|
CheckScopes,
|
|
@@ -64,7 +64,7 @@ import {
|
|
|
64
64
|
providerQuotaPolicySchema,
|
|
65
65
|
resolveProviderInput,
|
|
66
66
|
skillsClientSchema
|
|
67
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-6QTH5NS5.js";
|
|
68
68
|
|
|
69
69
|
// src/cli.ts
|
|
70
70
|
import { pathToFileURL } from "url";
|
|
@@ -580,7 +580,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
580
580
|
return result;
|
|
581
581
|
}
|
|
582
582
|
async function backfillInsightsCommand(project, opts) {
|
|
583
|
-
const { IntelligenceService } = await import("./intelligence-service-
|
|
583
|
+
const { IntelligenceService } = await import("./intelligence-service-BCKXIKIL.js");
|
|
584
584
|
const config = loadConfig();
|
|
585
585
|
const db = createClient(config.database);
|
|
586
586
|
migrate(db);
|
|
@@ -2378,6 +2378,10 @@ async function gaAttribution(project, opts) {
|
|
|
2378
2378
|
aiSharePctBySessionDisplay: traffic.aiSharePctBySessionDisplay,
|
|
2379
2379
|
socialSharePctDisplay: traffic.socialSharePctDisplay,
|
|
2380
2380
|
directSharePctDisplay: traffic.directSharePctDisplay,
|
|
2381
|
+
otherSessions: traffic.otherSessions,
|
|
2382
|
+
otherSharePct: traffic.otherSharePct,
|
|
2383
|
+
otherSharePctDisplay: traffic.otherSharePctDisplay,
|
|
2384
|
+
channelBreakdown: traffic.channelBreakdown,
|
|
2381
2385
|
aiReferrals: traffic.aiReferrals,
|
|
2382
2386
|
aiReferralLandingPages: traffic.aiReferralLandingPages,
|
|
2383
2387
|
socialReferrals: traffic.socialReferrals,
|
|
@@ -2395,15 +2399,11 @@ async function gaAttribution(project, opts) {
|
|
|
2395
2399
|
console.log(` Total Users: ${traffic.totalUsers}`);
|
|
2396
2400
|
console.log();
|
|
2397
2401
|
console.log(" CHANNEL BREAKDOWN 7d trend 30d trend");
|
|
2398
|
-
console.log(` Organic Search: ${String(traffic.
|
|
2399
|
-
console.log(` Social: ${String(traffic.
|
|
2400
|
-
console.log(` Direct: ${String(traffic.
|
|
2401
|
-
console.log(` AI Referrals: ${String(traffic.
|
|
2402
|
-
|
|
2403
|
-
if (otherSessions2 > 0) {
|
|
2404
|
-
const otherPct = traffic.totalSessions > 0 ? Math.round(otherSessions2 / traffic.totalSessions * 100) : 0;
|
|
2405
|
-
console.log(` Other: ${String(otherSessions2).padEnd(6)} (${String(otherPct).padStart(2)}%)`);
|
|
2406
|
-
}
|
|
2402
|
+
console.log(` Organic Search: ${String(traffic.channelBreakdown.organic.sessions).padEnd(6)} (${traffic.channelBreakdown.organic.sharePctDisplay.padStart(4)}) ${fmtTrend(trend.organic.trend7dPct).padEnd(12)} ${fmtTrend(trend.organic.trend30dPct)}`);
|
|
2403
|
+
console.log(` Social: ${String(traffic.channelBreakdown.social.sessions).padEnd(6)} (${traffic.channelBreakdown.social.sharePctDisplay.padStart(4)}) ${fmtTrend(trend.social.trend7dPct).padEnd(12)} ${fmtTrend(trend.social.trend30dPct)}`);
|
|
2404
|
+
console.log(` Direct: ${String(traffic.channelBreakdown.direct.sessions).padEnd(6)} (${traffic.channelBreakdown.direct.sharePctDisplay.padStart(4)}) ${fmtTrend(trend.direct.trend7dPct).padEnd(12)} ${fmtTrend(trend.direct.trend30dPct)}`);
|
|
2405
|
+
console.log(` AI Referrals: ${String(traffic.channelBreakdown.ai.sessions).padEnd(6)} (${traffic.channelBreakdown.ai.sharePctDisplay.padStart(4)}) ${fmtTrend(trend.ai.trend7dPct).padEnd(12)} ${fmtTrend(trend.ai.trend30dPct)} (lower bound \u2014 sessionSource only; referrer-stripped traffic falls under Direct)`);
|
|
2406
|
+
console.log(` Other: ${String(traffic.channelBreakdown.other.sessions).padEnd(6)} (${traffic.channelBreakdown.other.sharePctDisplay.padStart(4)})`);
|
|
2407
2407
|
console.log(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
2408
2408
|
console.log(` Total: ${String(traffic.totalSessions).padEnd(6)} ${fmtTrend(trend.total.trend7dPct).padEnd(12)} ${fmtTrend(trend.total.trend30dPct)}`);
|
|
2409
2409
|
if (trend.aiBiggestMover) {
|
|
@@ -2446,6 +2446,10 @@ async function gaAttribution(project, opts) {
|
|
|
2446
2446
|
aiSharePctBySessionDisplay: traffic.aiSharePctBySessionDisplay,
|
|
2447
2447
|
socialSharePctDisplay: traffic.socialSharePctDisplay,
|
|
2448
2448
|
directSharePctDisplay: traffic.directSharePctDisplay,
|
|
2449
|
+
otherSessions: traffic.otherSessions,
|
|
2450
|
+
otherSharePct: traffic.otherSharePct,
|
|
2451
|
+
otherSharePctDisplay: traffic.otherSharePctDisplay,
|
|
2452
|
+
channelBreakdown: traffic.channelBreakdown,
|
|
2449
2453
|
aiReferrals: traffic.aiReferrals,
|
|
2450
2454
|
aiReferralLandingPages: traffic.aiReferralLandingPages,
|
|
2451
2455
|
socialReferrals: traffic.socialReferrals,
|
|
@@ -2464,15 +2468,11 @@ async function gaAttribution(project, opts) {
|
|
|
2464
2468
|
console.log(` Total Users: ${traffic.totalUsers}`);
|
|
2465
2469
|
console.log();
|
|
2466
2470
|
console.log(" CHANNEL BREAKDOWN");
|
|
2467
|
-
console.log(` Organic Search: ${traffic.
|
|
2468
|
-
console.log(` Social: ${traffic.
|
|
2469
|
-
console.log(` Direct: ${traffic.
|
|
2470
|
-
console.log(` AI Referrals: ${traffic.
|
|
2471
|
-
|
|
2472
|
-
if (otherSessions > 0) {
|
|
2473
|
-
const otherPct = traffic.totalSessions > 0 ? Math.round(otherSessions / traffic.totalSessions * 100) : 0;
|
|
2474
|
-
console.log(` Other: ${otherSessions} sessions (${otherPct}%)`);
|
|
2475
|
-
}
|
|
2471
|
+
console.log(` Organic Search: ${traffic.channelBreakdown.organic.sessions} sessions (${traffic.channelBreakdown.organic.sharePctDisplay})`);
|
|
2472
|
+
console.log(` Social: ${traffic.channelBreakdown.social.sessions} sessions (${traffic.channelBreakdown.social.sharePctDisplay})`);
|
|
2473
|
+
console.log(` Direct: ${traffic.channelBreakdown.direct.sessions} sessions (${traffic.channelBreakdown.direct.sharePctDisplay})`);
|
|
2474
|
+
console.log(` AI Referrals: ${traffic.channelBreakdown.ai.sessions} sessions (${traffic.channelBreakdown.ai.sharePctDisplay}) (lower bound \u2014 sessionSource only; referrer-stripped traffic falls under Direct)`);
|
|
2475
|
+
console.log(` Other: ${traffic.channelBreakdown.other.sessions} sessions (${traffic.channelBreakdown.other.sharePctDisplay})`);
|
|
2476
2476
|
if (traffic.aiReferrals.length > 0) {
|
|
2477
2477
|
console.log();
|
|
2478
2478
|
console.log(" AI SOURCES");
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-FRDVC2XF.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-5NYG5EC7.js";
|
|
7
|
+
import "./chunk-7HBZCGRL.js";
|
|
8
|
+
import "./chunk-6QTH5NS5.js";
|
|
9
9
|
export {
|
|
10
10
|
createServer,
|
|
11
11
|
loadConfig
|
package/dist/mcp.js
CHANGED
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
CliError,
|
|
3
3
|
canonryMcpTools,
|
|
4
4
|
createApiClient
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-5NYG5EC7.js";
|
|
6
|
+
import "./chunk-6QTH5NS5.js";
|
|
7
7
|
|
|
8
8
|
// src/mcp/cli.ts
|
|
9
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainyc/canonry",
|
|
3
|
-
"version": "4.13.
|
|
3
|
+
"version": "4.13.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
|
|
6
6
|
"license": "FSL-1.1-ALv2",
|
|
@@ -59,22 +59,22 @@
|
|
|
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-contracts": "0.0.0",
|
|
63
|
+
"@ainyc/canonry-api-routes": "0.0.0",
|
|
64
64
|
"@ainyc/canonry-db": "0.0.0",
|
|
65
|
+
"@ainyc/canonry-intelligence": "0.0.0",
|
|
65
66
|
"@ainyc/canonry-config": "0.0.0",
|
|
66
67
|
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
67
|
-
"@ainyc/canonry-intelligence": "0.0.0",
|
|
68
68
|
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
69
69
|
"@ainyc/canonry-integration-google": "0.0.0",
|
|
70
|
+
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
70
71
|
"@ainyc/canonry-integration-traffic": "0.0.0",
|
|
71
|
-
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
72
72
|
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
73
|
-
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
74
73
|
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
74
|
+
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
75
75
|
"@ainyc/canonry-provider-local": "0.0.0",
|
|
76
|
-
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
77
76
|
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
77
|
+
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
78
78
|
"@ainyc/canonry-provider-perplexity": "0.0.0"
|
|
79
79
|
},
|
|
80
80
|
"scripts": {
|