@ainyc/canonry 4.13.1 → 4.14.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-BofSsfDl.js → index-B6Mi9Fd1.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-UQHWSCTE.js} +240 -73
- package/dist/cli.js +58 -26
- 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-B6Mi9Fd1.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) {
|