@canonry/canonry 4.130.0 → 4.131.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.
Files changed (24) hide show
  1. package/assets/assets/{AuditHistoryPanel-Ydzb2Y_Y.js → AuditHistoryPanel-yJXZMO5t.js} +1 -1
  2. package/assets/assets/{BacklinksPage-DYodEusK.js → BacklinksPage-6VTLbAYx.js} +1 -1
  3. package/assets/assets/{ChartPrimitives-D67hsnF7.js → ChartPrimitives-D5YGiDLi.js} +1 -1
  4. package/assets/assets/{HistoryPage-_h9ITL1w.js → HistoryPage-BxdTM6-R.js} +1 -1
  5. package/assets/assets/{ProjectPage-fCJKCcHE.js → ProjectPage-BJKxZPRO.js} +1 -1
  6. package/assets/assets/{RunRow-DzUNyTE-.js → RunRow-DTrAO7tE.js} +1 -1
  7. package/assets/assets/{RunsPage-FTvAjptk.js → RunsPage-CHU0DQGl.js} +1 -1
  8. package/assets/assets/{SettingsPage-CvgpYEAt.js → SettingsPage-bOIwju6t.js} +1 -1
  9. package/assets/assets/{TrafficPage-qaMW9k6o.js → TrafficPage-BKvuR482.js} +1 -1
  10. package/assets/assets/{TrafficSourceDetailPage-CRCso7_A.js → TrafficSourceDetailPage-3-iUTgQg.js} +1 -1
  11. package/assets/assets/{arrow-left-C--lHdP2.js → arrow-left-BXSfGhr6.js} +1 -1
  12. package/assets/assets/{extract-error-message-DVqYbKpB.js → extract-error-message-DEAY62LS.js} +1 -1
  13. package/assets/assets/{index-CdXn0JEf.js → index-CfjIYitV.js} +65 -65
  14. package/assets/assets/{trash-2-CyKRU3tR.js → trash-2-O1wo9IX1.js} +1 -1
  15. package/assets/index.html +1 -1
  16. package/dist/{chunk-NVRJZU6V.js → chunk-3WBAJKRR.js} +4 -0
  17. package/dist/{chunk-QR5U2K3O.js → chunk-5GQ6FF5R.js} +4 -4
  18. package/dist/{chunk-WIALMCXM.js → chunk-73HPY6XQ.js} +1 -1
  19. package/dist/{chunk-5XU6WEMK.js → chunk-ZXVWL5HX.js} +308 -4
  20. package/dist/cli.js +4 -4
  21. package/dist/index.js +4 -4
  22. package/dist/{intelligence-service-53GUE6EI.js → intelligence-service-YJKQQYJQ.js} +2 -2
  23. package/dist/mcp.js +2 -2
  24. package/package.json +9 -9
@@ -1 +1 @@
1
- import{c as a}from"./index-CdXn0JEf.js";const c=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],d=a("circle-check",c);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],k=a("circle-question-mark",e);const t=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=a("download",t);const o=[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]],r=a("external-link",o);const n=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],y=a("trash-2",n);export{d as C,s as D,r as E,y as T,k as a};
1
+ import{c as a}from"./index-CfjIYitV.js";const c=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],d=a("circle-check",c);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],k=a("circle-question-mark",e);const t=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=a("download",t);const o=[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]],r=a("external-link",o);const n=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],y=a("trash-2",n);export{d as C,s as D,r as E,y as T,k as a};
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-CdXn0JEf.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-CfjIYitV.js"></script>
16
16
  <link rel="modulepreload" crossorigin href="./assets/vendor-tanstack-Be8JdV5M.js">
17
17
  <link rel="modulepreload" crossorigin href="./assets/vendor-radix-CQ77EO2y.js">
18
18
  <link rel="modulepreload" crossorigin href="./assets/vendor-recharts-DSxTZhaH.js">
@@ -3938,6 +3938,10 @@ var ga4SyncResponseDtoSchema = z25.object({
3938
3938
  socialReferralCount: z25.number().int().nonnegative(),
3939
3939
  days: z25.number().int().nonnegative(),
3940
3940
  syncedAt: z25.string(),
3941
+ measurement: z25.object({
3942
+ acquisition: z25.object({ days: z25.number().int().nonnegative(), status: z25.enum(["ready", "error"]), rowCount: z25.number().int().nonnegative(), error: z25.string().optional() }),
3943
+ leads: z25.object({ days: z25.number().int().nonnegative(), status: z25.enum(["ready", "error", "not-configured"]), rowCount: z25.number().int().nonnegative(), attributionScope: z25.enum(["landing-page", "channel"]).optional(), error: z25.string().optional() })
3944
+ }),
3941
3945
  /**
3942
3946
  * Components that were written this run. Present when `only` is set.
3943
3947
  * Always includes `traffic` and `summary` (the share denominator) plus
@@ -10,7 +10,7 @@ import {
10
10
  loadConfig,
11
11
  loadConfigRaw,
12
12
  saveConfigPatch
13
- } from "./chunk-WIALMCXM.js";
13
+ } from "./chunk-73HPY6XQ.js";
14
14
  import {
15
15
  CC_CACHE_DIR,
16
16
  DUCKDB_SPEC,
@@ -118,7 +118,7 @@ import {
118
118
  siteAuditPages,
119
119
  siteAuditSnapshots,
120
120
  usageCounters
121
- } from "./chunk-5XU6WEMK.js";
121
+ } from "./chunk-ZXVWL5HX.js";
122
122
  import {
123
123
  AGENT_MEMORY_VALUE_MAX_BYTES,
124
124
  AGENT_PROVIDER_IDS,
@@ -191,7 +191,7 @@ import {
191
191
  validationError,
192
192
  winnabilityClassLabel,
193
193
  withRetry
194
- } from "./chunk-NVRJZU6V.js";
194
+ } from "./chunk-3WBAJKRR.js";
195
195
 
196
196
  // src/telemetry.ts
197
197
  import crypto from "crypto";
@@ -7000,7 +7000,7 @@ function readStoredGroundingSources(rawResponse) {
7000
7000
  return result;
7001
7001
  }
7002
7002
  async function backfillInsightsCommand(project, opts) {
7003
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-53GUE6EI.js");
7003
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-YJKQQYJQ.js");
7004
7004
  const config = loadConfig();
7005
7005
  const db = createClient(config.database);
7006
7006
  migrate(db);
@@ -39,7 +39,7 @@ import {
39
39
  trafficConnectWordpressRequestSchema,
40
40
  trafficEventKindSchema,
41
41
  trafficSeriesGranularitySchema
42
- } from "./chunk-NVRJZU6V.js";
42
+ } from "./chunk-3WBAJKRR.js";
43
43
 
44
44
  // src/config.ts
45
45
  import fs from "fs";
@@ -344,7 +344,7 @@ import {
344
344
  wordpressSchemaDeployResultDtoSchema,
345
345
  wordpressSchemaStatusResultDtoSchema,
346
346
  wordpressStatusDtoSchema
347
- } from "./chunk-NVRJZU6V.js";
347
+ } from "./chunk-3WBAJKRR.js";
348
348
 
349
349
  // src/intelligence-service.ts
350
350
  import { eq as eq42, desc as desc19, asc as asc9, and as and33, ne as ne6, or as or8, inArray as inArray15, gte as gte10, lte as lte6 } from "drizzle-orm";
@@ -22551,11 +22551,14 @@ var GA4_DIMENSIONS = {
22551
22551
  firstUserSource: "firstUserSource",
22552
22552
  firstUserMedium: "firstUserMedium",
22553
22553
  sessionDefaultChannelGrouping: "sessionDefaultChannelGrouping",
22554
- sessionDefaultChannelGroup: "sessionDefaultChannelGroup"
22554
+ sessionDefaultChannelGroup: "sessionDefaultChannelGroup",
22555
+ hostName: "hostName",
22556
+ eventName: "eventName"
22555
22557
  };
22556
22558
  var GA4_METRICS = {
22557
22559
  sessions: "sessions",
22558
- totalUsers: "totalUsers"
22560
+ totalUsers: "totalUsers",
22561
+ eventCount: "eventCount"
22559
22562
  };
22560
22563
 
22561
22564
  // ../integration-google-analytics/src/types.ts
@@ -22824,6 +22827,100 @@ async function batchRunReports(accessToken, propertyId, requests) {
22824
22827
  function formatDate2(d) {
22825
22828
  return d.toISOString().slice(0, 10);
22826
22829
  }
22830
+ function measurementDateRange(days) {
22831
+ const syncDays = Math.min(Math.max(1, days ?? GA4_DEFAULT_SYNC_DAYS), GA4_MAX_SYNC_DAYS);
22832
+ const endDate = /* @__PURE__ */ new Date();
22833
+ const startDate = /* @__PURE__ */ new Date();
22834
+ startDate.setUTCDate(startDate.getUTCDate() - syncDays);
22835
+ return { syncDays, dateRange: { startDate: formatDate2(startDate), endDate: formatDate2(endDate) } };
22836
+ }
22837
+ async function fetchPagedReport(accessToken, propertyId, baseRequest, pageSize = 1e4) {
22838
+ const rows = [];
22839
+ let offset = 0;
22840
+ for (let page = 0; page < GA4_MAX_PAGES; page++) {
22841
+ const response = await runReport(accessToken, propertyId, { ...baseRequest, limit: pageSize, offset });
22842
+ const pageRows = response.rows ?? [];
22843
+ rows.push(...pageRows);
22844
+ offset += pageRows.length;
22845
+ if (pageRows.length < pageSize || response.rowCount !== void 0 && offset >= response.rowCount) break;
22846
+ if (page === GA4_MAX_PAGES - 1) {
22847
+ throw new Error(`GA4 report truncated: page limit (${GA4_MAX_PAGES}) reached before all rows were fetched`);
22848
+ }
22849
+ }
22850
+ return rows;
22851
+ }
22852
+ async function fetchAcquisitionByChannel(accessToken, propertyId, days, options) {
22853
+ validateAccessToken2(accessToken);
22854
+ validatePropertyId(propertyId);
22855
+ const { syncDays, dateRange } = measurementDateRange(days);
22856
+ const rows = await fetchPagedReport(accessToken, propertyId, {
22857
+ dateRanges: [dateRange],
22858
+ dimensions: [
22859
+ { name: GA4_DIMENSIONS.date },
22860
+ { name: GA4_DIMENSIONS.sessionDefaultChannelGroup },
22861
+ { name: GA4_DIMENSIONS.sessionSource },
22862
+ { name: GA4_DIMENSIONS.sessionMedium },
22863
+ { name: GA4_DIMENSIONS.hostName },
22864
+ { name: GA4_DIMENSIONS.landingPagePlusQueryString }
22865
+ ],
22866
+ metrics: [{ name: GA4_METRICS.sessions }]
22867
+ }, options?.pageSize);
22868
+ ga4Log("info", "fetch-acquisition.done", { propertyId, days: syncDays, rowCount: rows.length });
22869
+ return { ...dateRange, rows: rows.map((row) => ({
22870
+ date: compactDateToIso(row.dimensionValues[0]?.value ?? ""),
22871
+ channelGroup: row.dimensionValues[1]?.value ?? "(not set)",
22872
+ source: row.dimensionValues[2]?.value ?? "(not set)",
22873
+ medium: row.dimensionValues[3]?.value ?? "(not set)",
22874
+ hostName: row.dimensionValues[4]?.value ?? "(not set)",
22875
+ landingPage: row.dimensionValues[5]?.value ?? "(not set)",
22876
+ sessions: parseInt(row.metricValues[0]?.value ?? "0", 10) || 0
22877
+ })) };
22878
+ }
22879
+ function isLandingPageDimensionIncompatibility(error) {
22880
+ return error instanceof GA4ApiError && error.status === 400 && /landingPagePlusQueryString/i.test(error.message) && /incompatib/i.test(error.message);
22881
+ }
22882
+ async function fetchLeadEvents(accessToken, propertyId, eventNames, days) {
22883
+ validateAccessToken2(accessToken);
22884
+ validatePropertyId(propertyId);
22885
+ const { dateRange } = measurementDateRange(days);
22886
+ const filter = { orGroup: { expressions: eventNames.map((value) => ({ filter: {
22887
+ fieldName: GA4_DIMENSIONS.eventName,
22888
+ stringFilter: { matchType: "EXACT", value }
22889
+ } })) } };
22890
+ const dimensions = [
22891
+ { name: GA4_DIMENSIONS.date },
22892
+ { name: GA4_DIMENSIONS.eventName },
22893
+ { name: GA4_DIMENSIONS.sessionDefaultChannelGroup },
22894
+ { name: GA4_DIMENSIONS.sessionSource },
22895
+ { name: GA4_DIMENSIONS.sessionMedium }
22896
+ ];
22897
+ const request = (landingPage) => ({
22898
+ dateRanges: [dateRange],
22899
+ dimensions: landingPage ? [...dimensions, { name: GA4_DIMENSIONS.hostName }, { name: GA4_DIMENSIONS.landingPagePlusQueryString }] : dimensions,
22900
+ metrics: [{ name: GA4_METRICS.eventCount }],
22901
+ dimensionFilter: filter
22902
+ });
22903
+ let attributionScope = "landing-page";
22904
+ let rawRows;
22905
+ try {
22906
+ rawRows = await fetchPagedReport(accessToken, propertyId, request(true));
22907
+ } catch (error) {
22908
+ if (!isLandingPageDimensionIncompatibility(error)) throw error;
22909
+ attributionScope = "channel";
22910
+ rawRows = await fetchPagedReport(accessToken, propertyId, request(false));
22911
+ }
22912
+ const rows = rawRows.map((row) => ({
22913
+ date: compactDateToIso(row.dimensionValues[0]?.value ?? ""),
22914
+ eventName: row.dimensionValues[1]?.value ?? "(not set)",
22915
+ channelGroup: row.dimensionValues[2]?.value ?? "(not set)",
22916
+ source: row.dimensionValues[3]?.value ?? "(not set)",
22917
+ medium: row.dimensionValues[4]?.value ?? "(not set)",
22918
+ hostName: attributionScope === "landing-page" ? row.dimensionValues[5]?.value ?? "(not available)" : "(not available)",
22919
+ landingPage: attributionScope === "landing-page" ? row.dimensionValues[6]?.value ?? "(not available)" : "(not available)",
22920
+ eventCount: parseInt(row.metricValues[0]?.value ?? "0", 10) || 0
22921
+ }));
22922
+ return { ...dateRange, attributionScope, rows };
22923
+ }
22827
22924
  var AI_REFERRAL_SOURCE_FILTERS = [
22828
22925
  { matchType: "CONTAINS", value: "perplexity" },
22829
22926
  { matchType: "CONTAINS", value: "gemini" },
@@ -30050,6 +30147,134 @@ function normalizeAiTrafficClass(value) {
30050
30147
  function emptyAiCounts() {
30051
30148
  return { sessions: 0, users: 0 };
30052
30149
  }
30150
+ function persistAcquisitionMeasurement(db, input) {
30151
+ const { startDate, endDate } = input.report;
30152
+ db.transaction((tx) => {
30153
+ tx.delete(gaAcquisitionDaily).where(and23(
30154
+ eq29(gaAcquisitionDaily.projectId, input.projectId),
30155
+ sql13`${gaAcquisitionDaily.date} >= ${startDate}`,
30156
+ sql13`${gaAcquisitionDaily.date} <= ${endDate}`
30157
+ )).run();
30158
+ for (const row of input.report.rows) {
30159
+ tx.insert(gaAcquisitionDaily).values({
30160
+ id: crypto24.randomUUID(),
30161
+ projectId: input.projectId,
30162
+ date: row.date,
30163
+ channelGroup: row.channelGroup,
30164
+ source: row.source,
30165
+ medium: row.medium,
30166
+ hostName: row.hostName,
30167
+ landingPage: row.landingPage,
30168
+ landingPageNormalized: normalizeUrlPath(row.landingPage),
30169
+ sessions: row.sessions,
30170
+ syncedAt: input.syncedAt,
30171
+ syncRunId: input.runId,
30172
+ createdAt: input.syncedAt
30173
+ }).run();
30174
+ }
30175
+ tx.insert(gaMeasurementSyncStates).values({
30176
+ projectId: input.projectId,
30177
+ acquisitionStatus: "ready",
30178
+ acquisitionError: null,
30179
+ acquisitionSyncedAt: input.syncedAt,
30180
+ updatedAt: input.syncedAt
30181
+ }).onConflictDoUpdate({
30182
+ target: gaMeasurementSyncStates.projectId,
30183
+ set: {
30184
+ acquisitionStatus: "ready",
30185
+ acquisitionError: null,
30186
+ acquisitionSyncedAt: input.syncedAt,
30187
+ updatedAt: input.syncedAt
30188
+ }
30189
+ }).run();
30190
+ });
30191
+ }
30192
+ function persistLeadMeasurement(db, input) {
30193
+ const { startDate, endDate } = input.report;
30194
+ db.transaction((tx) => {
30195
+ tx.delete(gaLeadEventsDaily).where(and23(
30196
+ eq29(gaLeadEventsDaily.projectId, input.projectId),
30197
+ sql13`${gaLeadEventsDaily.date} >= ${startDate}`,
30198
+ sql13`${gaLeadEventsDaily.date} <= ${endDate}`
30199
+ )).run();
30200
+ for (const row of input.report.rows) {
30201
+ tx.insert(gaLeadEventsDaily).values({
30202
+ id: crypto24.randomUUID(),
30203
+ projectId: input.projectId,
30204
+ date: row.date,
30205
+ eventName: row.eventName,
30206
+ channelGroup: row.channelGroup,
30207
+ source: row.source,
30208
+ medium: row.medium,
30209
+ hostName: row.hostName,
30210
+ landingPage: row.landingPage,
30211
+ landingPageNormalized: input.report.attributionScope === "landing-page" ? normalizeUrlPath(row.landingPage) : null,
30212
+ attributionScope: input.report.attributionScope,
30213
+ eventCount: row.eventCount,
30214
+ syncedAt: input.syncedAt,
30215
+ syncRunId: input.runId,
30216
+ createdAt: input.syncedAt
30217
+ }).run();
30218
+ }
30219
+ tx.insert(gaMeasurementSyncStates).values({
30220
+ projectId: input.projectId,
30221
+ leadStatus: "ready",
30222
+ leadError: null,
30223
+ leadSyncedAt: input.syncedAt,
30224
+ leadAttributionScope: input.report.attributionScope,
30225
+ updatedAt: input.syncedAt
30226
+ }).onConflictDoUpdate({
30227
+ target: gaMeasurementSyncStates.projectId,
30228
+ set: {
30229
+ leadStatus: "ready",
30230
+ leadError: null,
30231
+ leadSyncedAt: input.syncedAt,
30232
+ leadAttributionScope: input.report.attributionScope,
30233
+ updatedAt: input.syncedAt
30234
+ }
30235
+ }).run();
30236
+ });
30237
+ }
30238
+ function resetLeadMeasurement(db, projectId, updatedAt) {
30239
+ db.transaction((tx) => {
30240
+ tx.delete(gaLeadEventsDaily).where(eq29(gaLeadEventsDaily.projectId, projectId)).run();
30241
+ tx.insert(gaMeasurementSyncStates).values({ projectId, leadStatus: "never-synced", leadError: null, leadSyncedAt: null, leadAttributionScope: null, updatedAt }).onConflictDoUpdate({
30242
+ target: gaMeasurementSyncStates.projectId,
30243
+ set: { leadStatus: "never-synced", leadError: null, leadSyncedAt: null, leadAttributionScope: null, updatedAt }
30244
+ }).run();
30245
+ });
30246
+ }
30247
+ function persistMeasurementError(db, projectId, component, message, updatedAt) {
30248
+ if (component === "acquisition") {
30249
+ db.insert(gaMeasurementSyncStates).values({
30250
+ projectId,
30251
+ acquisitionStatus: "error",
30252
+ acquisitionError: message,
30253
+ updatedAt
30254
+ }).onConflictDoUpdate({
30255
+ target: gaMeasurementSyncStates.projectId,
30256
+ set: {
30257
+ acquisitionStatus: "error",
30258
+ acquisitionError: message,
30259
+ updatedAt
30260
+ }
30261
+ }).run();
30262
+ return;
30263
+ }
30264
+ db.insert(gaMeasurementSyncStates).values({
30265
+ projectId,
30266
+ leadStatus: "error",
30267
+ leadError: message,
30268
+ updatedAt
30269
+ }).onConflictDoUpdate({
30270
+ target: gaMeasurementSyncStates.projectId,
30271
+ set: {
30272
+ leadStatus: "error",
30273
+ leadError: message,
30274
+ updatedAt
30275
+ }
30276
+ }).run();
30277
+ }
30053
30278
  function summarizeAiReferralCounts(rows) {
30054
30279
  const dedupeGroups = /* @__PURE__ */ new Map();
30055
30280
  const bySessionChannelGroup = /* @__PURE__ */ new Map();
@@ -30313,6 +30538,10 @@ async function ga4Routes(app, opts) {
30313
30538
  const syncSummary = true;
30314
30539
  const syncAi = !only || only === "ai";
30315
30540
  const syncSocial = !only || only === "social";
30541
+ const measurementState = app.db.select().from(gaMeasurementSyncStates).where(eq29(gaMeasurementSyncStates.projectId, project.id)).get();
30542
+ const acquisitionDays = measurementState?.acquisitionSyncedAt != null ? Math.min(Math.max(1, days), 90) : 90;
30543
+ const leadDays = measurementState?.leadSyncedAt != null ? Math.min(Math.max(1, days), 90) : 90;
30544
+ const leadEventNames = project.measurement.leadEventNames;
30316
30545
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
30317
30546
  const runId = crypto24.randomUUID();
30318
30547
  app.db.insert(runs).values({
@@ -30338,6 +30567,10 @@ async function ga4Routes(app, opts) {
30338
30567
  if (syncTraffic) fetches.push(fetchTrafficByLandingPage(accessToken, propertyId, days));
30339
30568
  if (syncAi) fetches.push(fetchAiReferrals(accessToken, propertyId, days));
30340
30569
  if (syncSocial) fetches.push(fetchSocialReferrals(accessToken, propertyId, days));
30570
+ const measurementFetch = Promise.allSettled([
30571
+ fetchAcquisitionByChannel(accessToken, propertyId, acquisitionDays),
30572
+ leadEventNames.length > 0 ? fetchLeadEvents(accessToken, propertyId, leadEventNames, leadDays) : Promise.resolve(null)
30573
+ ]);
30341
30574
  const results = await Promise.all(fetches);
30342
30575
  const summary = results[0];
30343
30576
  const windowSummaries = results.slice(1, 1 + WINDOW_KEYS.length);
@@ -30483,7 +30716,74 @@ async function ga4Routes(app, opts) {
30483
30716
  }
30484
30717
  }
30485
30718
  });
30486
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq29(runs.id, runId)).run();
30719
+ const [acquisitionSettled, leadsSettled] = await measurementFetch;
30720
+ const measurementNow = (/* @__PURE__ */ new Date()).toISOString();
30721
+ const recordMeasurementFailure = (component, reason) => {
30722
+ const message = reason instanceof Error ? reason.message : String(reason);
30723
+ gaLog("warn", "measurement.component-failed", {
30724
+ projectId: project.id,
30725
+ runId,
30726
+ component,
30727
+ error: message
30728
+ });
30729
+ try {
30730
+ persistMeasurementError(app.db, project.id, component, message, measurementNow);
30731
+ } catch (stateError) {
30732
+ gaLog("error", "measurement.state-write-failed", {
30733
+ projectId: project.id,
30734
+ runId,
30735
+ component,
30736
+ error: stateError instanceof Error ? stateError.message : String(stateError)
30737
+ });
30738
+ }
30739
+ return { status: "error", days: component === "acquisition" ? acquisitionDays : leadDays, rowCount: 0, error: message };
30740
+ };
30741
+ const acquisitionResult = (() => {
30742
+ if (acquisitionSettled.status === "rejected") {
30743
+ return recordMeasurementFailure("acquisition", acquisitionSettled.reason);
30744
+ }
30745
+ try {
30746
+ persistAcquisitionMeasurement(app.db, {
30747
+ projectId: project.id,
30748
+ runId,
30749
+ syncedAt: measurementNow,
30750
+ report: acquisitionSettled.value
30751
+ });
30752
+ return {
30753
+ status: "ready",
30754
+ days: acquisitionDays,
30755
+ rowCount: acquisitionSettled.value.rows.length
30756
+ };
30757
+ } catch (error) {
30758
+ return recordMeasurementFailure("acquisition", error);
30759
+ }
30760
+ })();
30761
+ const leadResult = (() => {
30762
+ if (leadsSettled.status === "rejected") {
30763
+ return recordMeasurementFailure("leads", leadsSettled.reason);
30764
+ }
30765
+ if (leadsSettled.value === null) {
30766
+ resetLeadMeasurement(app.db, project.id, measurementNow);
30767
+ return { status: "not-configured", days: 0, rowCount: 0 };
30768
+ }
30769
+ try {
30770
+ persistLeadMeasurement(app.db, {
30771
+ projectId: project.id,
30772
+ runId,
30773
+ syncedAt: measurementNow,
30774
+ report: leadsSettled.value
30775
+ });
30776
+ return {
30777
+ status: "ready",
30778
+ days: leadDays,
30779
+ rowCount: leadsSettled.value.rows.length,
30780
+ attributionScope: leadsSettled.value.attributionScope
30781
+ };
30782
+ } catch (error) {
30783
+ return recordMeasurementFailure("leads", error);
30784
+ }
30785
+ })();
30786
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: measurementNow }).where(eq29(runs.id, runId)).run();
30487
30787
  const syncedComponents = only ? [
30488
30788
  ...syncTraffic ? ["traffic"] : [],
30489
30789
  ...syncSummary ? ["summary"] : [],
@@ -30507,6 +30807,10 @@ async function ga4Routes(app, opts) {
30507
30807
  socialReferralCount: socialReferrals.length,
30508
30808
  days,
30509
30809
  syncedAt: now,
30810
+ measurement: {
30811
+ acquisition: acquisitionResult,
30812
+ leads: leadResult
30813
+ },
30510
30814
  ...syncedComponents ? { syncedComponents } : {}
30511
30815
  };
30512
30816
  } catch (e) {
package/dist/cli.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  setTelemetrySource,
30
30
  showFirstRunNotice,
31
31
  trackEvent
32
- } from "./chunk-QR5U2K3O.js";
32
+ } from "./chunk-5GQ6FF5R.js";
33
33
  import {
34
34
  CliError,
35
35
  EXIT_SYSTEM_ERROR,
@@ -46,7 +46,7 @@ import {
46
46
  saveConfig,
47
47
  saveConfigPatch,
48
48
  usageError
49
- } from "./chunk-WIALMCXM.js";
49
+ } from "./chunk-73HPY6XQ.js";
50
50
  import {
51
51
  apiKeys,
52
52
  createClient,
@@ -54,7 +54,7 @@ import {
54
54
  projects,
55
55
  queries,
56
56
  renderReportHtml
57
- } from "./chunk-5XU6WEMK.js";
57
+ } from "./chunk-ZXVWL5HX.js";
58
58
  import {
59
59
  AdsOperationKinds,
60
60
  AdsOperationStates,
@@ -97,7 +97,7 @@ import {
97
97
  providerQuotaPolicySchema,
98
98
  resolveProviderInput,
99
99
  winnabilityClassSchema
100
- } from "./chunk-NVRJZU6V.js";
100
+ } from "./chunk-3WBAJKRR.js";
101
101
 
102
102
  // src/cli.ts
103
103
  import { pathToFileURL } from "url";
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-QR5U2K3O.js";
3
+ } from "./chunk-5GQ6FF5R.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-WIALMCXM.js";
7
- import "./chunk-5XU6WEMK.js";
8
- import "./chunk-NVRJZU6V.js";
6
+ } from "./chunk-73HPY6XQ.js";
7
+ import "./chunk-ZXVWL5HX.js";
8
+ import "./chunk-3WBAJKRR.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-5XU6WEMK.js";
4
- import "./chunk-NVRJZU6V.js";
3
+ } from "./chunk-ZXVWL5HX.js";
4
+ import "./chunk-3WBAJKRR.js";
5
5
  export {
6
6
  IntelligenceService
7
7
  };
package/dist/mcp.js CHANGED
@@ -3,10 +3,10 @@ import {
3
3
  PACKAGE_VERSION,
4
4
  canonryMcpTools,
5
5
  createApiClient
6
- } from "./chunk-WIALMCXM.js";
6
+ } from "./chunk-73HPY6XQ.js";
7
7
  import {
8
8
  isReadOnlyKey
9
- } from "./chunk-NVRJZU6V.js";
9
+ } from "./chunk-3WBAJKRR.js";
10
10
 
11
11
  // src/mcp/cli.ts
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonry/canonry",
3
- "version": "4.130.0",
3
+ "version": "4.131.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",
@@ -67,25 +67,25 @@
67
67
  "tsx": "^4.19.0",
68
68
  "@ainyc/canonry-api-client": "0.0.0",
69
69
  "@ainyc/canonry-contracts": "0.0.0",
70
+ "@ainyc/canonry-api-routes": "0.0.0",
70
71
  "@ainyc/canonry-config": "0.0.0",
71
72
  "@ainyc/canonry-db": "0.0.0",
72
- "@ainyc/canonry-api-routes": "0.0.0",
73
73
  "@ainyc/canonry-integration-bing": "0.0.0",
74
- "@ainyc/canonry-integration-cloud-run": "0.0.0",
75
- "@ainyc/canonry-integration-commoncrawl": "0.0.0",
76
74
  "@ainyc/canonry-integration-openai-ads": "0.0.0",
75
+ "@ainyc/canonry-integration-cloud-run": "0.0.0",
77
76
  "@ainyc/canonry-integration-google": "0.0.0",
78
77
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
79
78
  "@ainyc/canonry-integration-google-places": "0.0.0",
80
- "@ainyc/canonry-integration-wordpress": "0.0.0",
79
+ "@ainyc/canonry-integration-traffic": "0.0.0",
80
+ "@ainyc/canonry-integration-commoncrawl": "0.0.0",
81
81
  "@ainyc/canonry-intelligence": "0.0.0",
82
- "@ainyc/canonry-provider-cdp": "0.0.0",
82
+ "@ainyc/canonry-integration-wordpress": "0.0.0",
83
83
  "@ainyc/canonry-provider-claude": "0.0.0",
84
84
  "@ainyc/canonry-provider-gemini": "0.0.0",
85
- "@ainyc/canonry-integration-traffic": "0.0.0",
85
+ "@ainyc/canonry-provider-cdp": "0.0.0",
86
86
  "@ainyc/canonry-provider-local": "0.0.0",
87
- "@ainyc/canonry-provider-openai": "0.0.0",
88
- "@ainyc/canonry-provider-perplexity": "0.0.0"
87
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
88
+ "@ainyc/canonry-provider-openai": "0.0.0"
89
89
  },
90
90
  "scripts": {
91
91
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",