@ainyc/canonry 2.14.1 → 2.14.2

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/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-BwFUCV6e.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-D1v6Q-fS.js"></script>
16
16
  <link rel="stylesheet" crossorigin href="./assets/index-U2SLimrz.css">
17
17
  </head>
18
18
  <body>
@@ -1298,11 +1298,15 @@ var ga4SocialReferralDtoSchema = z12.object({
1298
1298
  var ga4TrafficSummaryDtoSchema = z12.object({
1299
1299
  totalSessions: z12.number(),
1300
1300
  totalOrganicSessions: z12.number(),
1301
+ /** Direct-channel sessions (sessions with no source — bookmarks, typed URLs, AI-driven traffic with stripped referrer). 0 for legacy rows from before the column was added. */
1302
+ totalDirectSessions: z12.number(),
1301
1303
  totalUsers: z12.number(),
1302
1304
  topPages: z12.array(z12.object({
1303
1305
  landingPage: z12.string(),
1304
1306
  sessions: z12.number(),
1305
1307
  organicSessions: z12.number(),
1308
+ /** Per-page Direct-channel sessions. 0 for legacy rows. */
1309
+ directSessions: z12.number(),
1306
1310
  users: z12.number()
1307
1311
  })),
1308
1312
  aiReferrals: z12.array(ga4AiReferralDtoSchema),
@@ -1319,6 +1323,8 @@ var ga4TrafficSummaryDtoSchema = z12.object({
1319
1323
  organicSharePct: z12.number(),
1320
1324
  /** Deduped AI sessions as a percentage of total sessions (0–100, rounded). */
1321
1325
  aiSharePct: z12.number(),
1326
+ /** Direct-channel sessions as a percentage of total sessions (0–100, rounded). */
1327
+ directSharePct: z12.number(),
1322
1328
  /** Social sessions as a percentage of total sessions (0–100, rounded). */
1323
1329
  socialSharePct: z12.number(),
1324
1330
  lastSyncedAt: z12.string().nullable()
@@ -60,7 +60,7 @@ import {
60
60
  visibilityStateFromAnswerMentioned,
61
61
  windowCutoff,
62
62
  wordpressEnvSchema
63
- } from "./chunk-QTS7VZXN.js";
63
+ } from "./chunk-7VWSR5F6.js";
64
64
  import {
65
65
  IntelligenceService,
66
66
  agentMemory,
@@ -98,7 +98,7 @@ import {
98
98
  runs,
99
99
  schedules,
100
100
  usageCounters
101
- } from "./chunk-FV6PY5UE.js";
101
+ } from "./chunk-NEDRCOOL.js";
102
102
 
103
103
  // src/telemetry.ts
104
104
  import crypto from "crypto";
@@ -6662,6 +6662,8 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
6662
6662
  sessions: parseInt(row.metricValues[0].value, 10) || 0,
6663
6663
  organicSessions: 0,
6664
6664
  // populated by organic-only pass below
6665
+ directSessions: 0,
6666
+ // populated by direct-only pass below
6665
6667
  users: parseInt(row.metricValues[1].value, 10) || 0
6666
6668
  }));
6667
6669
  rows.push(...pageRows);
@@ -6697,9 +6699,38 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
6697
6699
  organicOffset += (organicResponse.rows ?? []).length;
6698
6700
  if ((organicResponse.rows ?? []).length < 1e4 || organicOffset >= total) break;
6699
6701
  }
6702
+ const directMap = /* @__PURE__ */ new Map();
6703
+ let directOffset = 0;
6704
+ let directPageCount = 0;
6705
+ while (directPageCount < GA4_MAX_PAGES) {
6706
+ directPageCount++;
6707
+ const directRequest = {
6708
+ dateRanges: [{ startDate: formatDate(startDate), endDate: formatDate(endDate) }],
6709
+ dimensions: [{ name: "date" }, { name: "landingPagePlusQueryString" }],
6710
+ metrics: [{ name: "sessions" }],
6711
+ dimensionFilter: {
6712
+ filter: {
6713
+ fieldName: "sessionDefaultChannelGrouping",
6714
+ stringFilter: { matchType: "EXACT", value: "Direct" }
6715
+ }
6716
+ },
6717
+ limit: 1e4,
6718
+ offset: directOffset
6719
+ };
6720
+ const directResponse = await runReport(accessToken, propertyId, directRequest);
6721
+ if (!directResponse) break;
6722
+ for (const row of directResponse.rows ?? []) {
6723
+ const key = `${row.dimensionValues[0].value}::${row.dimensionValues[1].value}`;
6724
+ directMap.set(key, parseInt(row.metricValues[0].value, 10) || 0);
6725
+ }
6726
+ const total = directResponse.rowCount ?? 0;
6727
+ directOffset += (directResponse.rows ?? []).length;
6728
+ if ((directResponse.rows ?? []).length < 1e4 || directOffset >= total) break;
6729
+ }
6700
6730
  for (const row of rows) {
6701
6731
  const key = `${row.date}::${row.landingPage}`;
6702
6732
  row.organicSessions = organicMap.get(key) ?? 0;
6733
+ row.directSessions = directMap.get(key) ?? 0;
6703
6734
  }
6704
6735
  for (const row of rows) {
6705
6736
  if (row.date.length === 8 && !row.date.includes("-")) {
@@ -8665,6 +8696,7 @@ async function ga4Routes(app, opts) {
8665
8696
  landingPageNormalized: normalizeUrlPath(row.landingPage),
8666
8697
  sessions: row.sessions,
8667
8698
  organicSessions: row.organicSessions,
8699
+ directSessions: row.directSessions,
8668
8700
  users: row.users,
8669
8701
  syncedAt: now,
8670
8702
  syncRunId: runId
@@ -8782,6 +8814,9 @@ async function ga4Routes(app, opts) {
8782
8814
  totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
8783
8815
  totalUsers: gaTrafficSummaries.totalUsers
8784
8816
  }).from(gaTrafficSummaries).where(eq19(gaTrafficSummaries.projectId, project.id)).get();
8817
+ const directTotalRow = app.db.select({
8818
+ totalDirectSessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
8819
+ }).from(gaTrafficSnapshots).where(and8(...snapshotConditions)).get();
8785
8820
  const summaryMeta = app.db.select({
8786
8821
  periodStart: gaTrafficSummaries.periodStart,
8787
8822
  periodEnd: gaTrafficSummaries.periodEnd
@@ -8790,6 +8825,7 @@ async function ga4Routes(app, opts) {
8790
8825
  landingPage: sql5`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
8791
8826
  sessions: sql5`SUM(${gaTrafficSnapshots.sessions})`,
8792
8827
  organicSessions: sql5`SUM(${gaTrafficSnapshots.organicSessions})`,
8828
+ directSessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`,
8793
8829
  users: sql5`SUM(${gaTrafficSnapshots.users})`
8794
8830
  }).from(gaTrafficSnapshots).where(and8(...snapshotConditions)).groupBy(sql5`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql5`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
8795
8831
  const aiReferrals = app.db.select({
@@ -8825,14 +8861,17 @@ async function ga4Routes(app, opts) {
8825
8861
  }).from(gaSocialReferrals).where(and8(...socialConditions)).get();
8826
8862
  const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq19(gaTrafficSummaries.projectId, project.id)).orderBy(desc9(gaTrafficSummaries.syncedAt)).limit(1).get();
8827
8863
  const total = summaryRow?.totalSessions ?? 0;
8864
+ const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
8828
8865
  return {
8829
8866
  totalSessions: total,
8830
8867
  totalOrganicSessions: summaryRow?.totalOrganicSessions ?? 0,
8868
+ totalDirectSessions,
8831
8869
  totalUsers: summaryRow?.totalUsers ?? 0,
8832
8870
  topPages: rows.map((r) => ({
8833
8871
  landingPage: r.landingPage,
8834
8872
  sessions: r.sessions ?? 0,
8835
8873
  organicSessions: r.organicSessions ?? 0,
8874
+ directSessions: r.directSessions ?? 0,
8836
8875
  users: r.users ?? 0
8837
8876
  })),
8838
8877
  aiReferrals: aiReferrals.map((r) => ({
@@ -8855,6 +8894,7 @@ async function ga4Routes(app, opts) {
8855
8894
  socialUsers: socialTotals?.users ?? 0,
8856
8895
  organicSharePct: total > 0 ? Math.round((summaryRow?.totalOrganicSessions ?? 0) / total * 100) : 0,
8857
8896
  aiSharePct: total > 0 ? Math.round((aiDeduped?.sessions ?? 0) / total * 100) : 0,
8897
+ directSharePct: total > 0 ? Math.round(totalDirectSessions / total * 100) : 0,
8858
8898
  socialSharePct: total > 0 ? Math.round((socialTotals?.sessions ?? 0) / total * 100) : 0,
8859
8899
  lastSyncedAt: latestSync?.syncedAt ?? null,
8860
8900
  periodStart: (() => {
@@ -320,6 +320,13 @@ var gaTrafficSnapshots = sqliteTable("ga_traffic_snapshots", {
320
320
  landingPageNormalized: text("landing_page_normalized"),
321
321
  sessions: integer("sessions").notNull().default(0),
322
322
  organicSessions: integer("organic_sessions").notNull().default(0),
323
+ /**
324
+ * Per-page Direct channel sessions. Nullable so existing rows survive
325
+ * the migration; new GA4 sync writes populate it. Distinct from
326
+ * `sessions - organicSessions` because that residual lumps Direct
327
+ * together with social, referral, paid, and email.
328
+ */
329
+ directSessions: integer("direct_sessions"),
323
330
  users: integer("users").notNull().default(0),
324
331
  syncedAt: text("synced_at").notNull(),
325
332
  syncRunId: text("sync_run_id").references(() => runs.id, { onDelete: "cascade" })
@@ -1068,7 +1075,13 @@ var MIGRATIONS = [
1068
1075
  // See plans/ai-attribution-research.md "Step 1 — data hygiene".
1069
1076
  `ALTER TABLE ga_traffic_snapshots ADD COLUMN landing_page_normalized TEXT`,
1070
1077
  `CREATE INDEX IF NOT EXISTS idx_ga_traffic_page_normalized
1071
- ON ga_traffic_snapshots(project_id, date, landing_page_normalized)`
1078
+ ON ga_traffic_snapshots(project_id, date, landing_page_normalized)`,
1079
+ // v45: Per-page Direct channel sessions on ga_traffic_snapshots. Nullable
1080
+ // so existing rows survive; populated by the GA4 sync writer in a
1081
+ // separate commit. Unblocks an honest channel breakdown for the project
1082
+ // dashboard (organic / social / direct / known-AI) — see
1083
+ // plans/ai-attribution-research.md scope A.
1084
+ `ALTER TABLE ga_traffic_snapshots ADD COLUMN direct_sessions INTEGER`
1072
1085
  ];
1073
1086
  function isDuplicateColumnError(err) {
1074
1087
  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-3T64Y7GR.js";
20
+ } from "./chunk-CILBPOHB.js";
21
21
  import {
22
22
  CcReleaseSyncStatuses,
23
23
  CheckScopes,
@@ -45,7 +45,7 @@ import {
45
45
  saveConfig,
46
46
  saveConfigPatch,
47
47
  usageError
48
- } from "./chunk-QTS7VZXN.js";
48
+ } from "./chunk-7VWSR5F6.js";
49
49
  import {
50
50
  apiKeys,
51
51
  competitors,
@@ -56,7 +56,7 @@ import {
56
56
  projects,
57
57
  querySnapshots,
58
58
  runs
59
- } from "./chunk-FV6PY5UE.js";
59
+ } from "./chunk-NEDRCOOL.js";
60
60
  import "./chunk-MLKGABMK.js";
61
61
 
62
62
  // src/cli.ts
@@ -371,7 +371,7 @@ async function backfillNormalizedPathsCommand(opts) {
371
371
  console.log(` Unchanged: ${unchanged}`);
372
372
  }
373
373
  async function backfillInsightsCommand(project, opts) {
374
- const { IntelligenceService } = await import("./intelligence-service-AEI46KC5.js");
374
+ const { IntelligenceService } = await import("./intelligence-service-6S5YKANX.js");
375
375
  const config = loadConfig();
376
376
  const db = createClient(config.database);
377
377
  migrate(db);
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-3T64Y7GR.js";
3
+ } from "./chunk-CILBPOHB.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-QTS7VZXN.js";
7
- import "./chunk-FV6PY5UE.js";
6
+ } from "./chunk-7VWSR5F6.js";
7
+ import "./chunk-NEDRCOOL.js";
8
8
  import "./chunk-MLKGABMK.js";
9
9
  export {
10
10
  createServer,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-FV6PY5UE.js";
3
+ } from "./chunk-NEDRCOOL.js";
4
4
  import "./chunk-MLKGABMK.js";
5
5
  export {
6
6
  IntelligenceService
package/dist/mcp.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  projectUpsertRequestSchema,
11
11
  runTriggerRequestSchema,
12
12
  scheduleUpsertRequestSchema
13
- } from "./chunk-QTS7VZXN.js";
13
+ } from "./chunk-7VWSR5F6.js";
14
14
  import "./chunk-MLKGABMK.js";
15
15
 
16
16
  // src/mcp/cli.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "2.14.1",
3
+ "version": "2.14.2",
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",
@@ -60,20 +60,20 @@
60
60
  "tsup": "^8.5.1",
61
61
  "tsx": "^4.19.0",
62
62
  "@ainyc/canonry-config": "0.0.0",
63
- "@ainyc/canonry-api-routes": "0.0.0",
64
63
  "@ainyc/canonry-contracts": "0.0.0",
65
64
  "@ainyc/canonry-db": "0.0.0",
66
- "@ainyc/canonry-integration-bing": "0.0.0",
67
- "@ainyc/canonry-integration-commoncrawl": "0.0.0",
65
+ "@ainyc/canonry-api-routes": "0.0.0",
68
66
  "@ainyc/canonry-intelligence": "0.0.0",
67
+ "@ainyc/canonry-integration-commoncrawl": "0.0.0",
69
68
  "@ainyc/canonry-integration-google": "0.0.0",
69
+ "@ainyc/canonry-integration-bing": "0.0.0",
70
70
  "@ainyc/canonry-provider-cdp": "0.0.0",
71
- "@ainyc/canonry-provider-claude": "0.0.0",
72
- "@ainyc/canonry-provider-gemini": "0.0.0",
73
71
  "@ainyc/canonry-integration-wordpress": "0.0.0",
74
72
  "@ainyc/canonry-provider-local": "0.0.0",
73
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
74
+ "@ainyc/canonry-provider-gemini": "0.0.0",
75
75
  "@ainyc/canonry-provider-openai": "0.0.0",
76
- "@ainyc/canonry-provider-perplexity": "0.0.0"
76
+ "@ainyc/canonry-provider-claude": "0.0.0"
77
77
  },
78
78
  "scripts": {
79
79
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",