@canonry/canonry 4.164.0 → 4.165.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/README.md +120 -66
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +4 -1
- package/assets/assets/{AuditHistoryPanel-CD1GVkS9.js → AuditHistoryPanel-IYMElyK5.js} +1 -1
- package/assets/assets/{BacklinksPage-B0Ijhntp.js → BacklinksPage-BMqIPRvZ.js} +1 -1
- package/assets/assets/{HistoryPage-DIKtpT_z.js → HistoryPage-iuETLmwO.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-BfPwwIsT.js → MeasurementPropertyPage-BzNAaB8X.js} +1 -1
- package/assets/assets/{ProjectPage-3czWArKE.js → ProjectPage-DIzi4Ggo.js} +1 -1
- package/assets/assets/{RunRow-CHyd0gQH.js → RunRow-BkkYxEjl.js} +1 -1
- package/assets/assets/{RunsPage-Dujt0SQ5.js → RunsPage-DGTrqrTA.js} +1 -1
- package/assets/assets/{SettingsPage-CJE9Oeg9.js → SettingsPage-Cwb_Ft9u.js} +1 -1
- package/assets/assets/{SiteHealthSection-DvuvziPJ.js → SiteHealthSection-k4I71wfJ.js} +3 -3
- package/assets/assets/{TrafficPage-B0tru9ek.js → TrafficPage-C8nyb8l1.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-DshEUtQX.js → TrafficSourceDetailPage-ry7Da0pV.js} +1 -1
- package/assets/assets/{extract-error-message-O2dirpiH.js → extract-error-message-BYyBW07F.js} +1 -1
- package/assets/assets/{index-C_y0aGQL.js → index-rqOXhYwp.js} +13 -13
- package/assets/assets/{react-sigma_core.esm.min-sVSSzmzq.js → react-sigma_core.esm.min-BtG8TWTe.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-BFGHZ2GU.js → chunk-BU6C2DKF.js} +23 -0
- package/dist/{chunk-RULHNUBH.js → chunk-L5SMSDZB.js} +4 -4
- package/dist/{chunk-C4WKYU5C.js → chunk-QDOZEMDK.js} +29 -1
- package/dist/{chunk-GCQPFCP2.js → chunk-QKLNTSQ2.js} +161 -14
- package/dist/cli.js +46 -8
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-Z3XS2UXQ.js → intelligence-service-RDGUEEZQ.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +10 -10
package/assets/assets/{react-sigma_core.esm.min-sVSSzmzq.js → react-sigma_core.esm.min-BtG8TWTe.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{g as Bt,t as S,r as C}from"./vendor-tanstack-Dock0eH1.js";import{e as Ht,G as Wt}from"./SiteHealthSection-
|
|
1
|
+
import{g as Bt,t as S,r as C}from"./vendor-tanstack-Dock0eH1.js";import{e as Ht,G as Wt}from"./SiteHealthSection-k4I71wfJ.js";import"./index-rqOXhYwp.js";import"./vendor-radix-4UOaVWza.js";import"./vendor-recharts-D8rt5lKn.js";import"./vendor-lucide-nYKv-BB7.js";import"./vendor-markdown-Bn7fRwrx.js";import"./vendor-yaml-GWJz5H2G.js";import"./ChartPrimitives-BoYe2M-i.js";function Vt(r,n){if(typeof r!="object"||!r)return r;var i=r[Symbol.toPrimitive];if(i!==void 0){var e=i.call(r,n);if(typeof e!="object")return e;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(r)}function ae(r){var n=Vt(r,"string");return typeof n=="symbol"?n:n+""}function A(r,n){if(!(r instanceof n))throw new TypeError("Cannot call a class as a function")}function He(r,n){for(var i=0;i<n.length;i++){var e=n[i];e.enumerable=e.enumerable||!1,e.configurable=!0,"value"in e&&(e.writable=!0),Object.defineProperty(r,ae(e.key),e)}}function L(r,n,i){return n&&He(r.prototype,n),i&&He(r,i),Object.defineProperty(r,"prototype",{writable:!1}),r}function Q(r){return Q=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(n){return n.__proto__||Object.getPrototypeOf(n)},Q(r)}function yt(){try{var r=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch{}return(yt=function(){return!!r})()}function jt(r){if(r===void 0)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return r}function Zt(r,n){if(n&&(typeof n=="object"||typeof n=="function"))return n;if(n!==void 0)throw new TypeError("Derived constructors may only return object or undefined");return jt(r)}function I(r,n,i){return n=Q(n),Zt(r,yt()?Reflect.construct(n,i||[],Q(r).constructor):n.apply(r,i))}function xe(r,n){return xe=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,e){return i.__proto__=e,i},xe(r,n)}function G(r,n){if(typeof n!="function"&&n!==null)throw new TypeError("Super expression must either be null or a function");r.prototype=Object.create(n&&n.prototype,{constructor:{value:r,writable:!0,configurable:!0}}),Object.defineProperty(r,"prototype",{writable:!1}),n&&xe(r,n)}function qt(r){if(Array.isArray(r))return r}function Yt(r,n){var i=r==null?null:typeof Symbol<"u"&&r[Symbol.iterator]||r["@@iterator"];if(i!=null){var e,t,a,o,s=[],l=!0,h=!1;try{if(a=(i=i.call(r)).next,n===0){if(Object(i)!==i)return;l=!1}else for(;!(l=(e=a.call(i)).done)&&(s.push(e.value),s.length!==n);l=!0);}catch(u){h=!0,t=u}finally{try{if(!l&&i.return!=null&&(o=i.return(),Object(o)!==o))return}finally{if(h)throw t}}return s}}function Se(r,n){(n==null||n>r.length)&&(n=r.length);for(var i=0,e=Array(n);i<n;i++)e[i]=r[i];return e}function Tt(r,n){if(r){if(typeof r=="string")return Se(r,n);var i={}.toString.call(r).slice(8,-1);return i==="Object"&&r.constructor&&(i=r.constructor.name),i==="Map"||i==="Set"?Array.from(r):i==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(i)?Se(r,n):void 0}}function Xt(){throw new TypeError(`Invalid attempt to destructure non-iterable instance.
|
|
2
2
|
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function ee(r,n){return qt(r)||Yt(r,n)||Tt(r,n)||Xt()}var pe={black:"#000000",silver:"#C0C0C0",gray:"#808080",grey:"#808080",white:"#FFFFFF",maroon:"#800000",red:"#FF0000",purple:"#800080",fuchsia:"#FF00FF",green:"#008000",lime:"#00FF00",olive:"#808000",yellow:"#FFFF00",navy:"#000080",blue:"#0000FF",teal:"#008080",aqua:"#00FFFF",darkblue:"#00008B",mediumblue:"#0000CD",darkgreen:"#006400",darkcyan:"#008B8B",deepskyblue:"#00BFFF",darkturquoise:"#00CED1",mediumspringgreen:"#00FA9A",springgreen:"#00FF7F",cyan:"#00FFFF",midnightblue:"#191970",dodgerblue:"#1E90FF",lightseagreen:"#20B2AA",forestgreen:"#228B22",seagreen:"#2E8B57",darkslategray:"#2F4F4F",darkslategrey:"#2F4F4F",limegreen:"#32CD32",mediumseagreen:"#3CB371",turquoise:"#40E0D0",royalblue:"#4169E1",steelblue:"#4682B4",darkslateblue:"#483D8B",mediumturquoise:"#48D1CC",indigo:"#4B0082",darkolivegreen:"#556B2F",cadetblue:"#5F9EA0",cornflowerblue:"#6495ED",rebeccapurple:"#663399",mediumaquamarine:"#66CDAA",dimgray:"#696969",dimgrey:"#696969",slateblue:"#6A5ACD",olivedrab:"#6B8E23",slategray:"#708090",slategrey:"#708090",lightslategray:"#778899",lightslategrey:"#778899",mediumslateblue:"#7B68EE",lawngreen:"#7CFC00",chartreuse:"#7FFF00",aquamarine:"#7FFFD4",skyblue:"#87CEEB",lightskyblue:"#87CEFA",blueviolet:"#8A2BE2",darkred:"#8B0000",darkmagenta:"#8B008B",saddlebrown:"#8B4513",darkseagreen:"#8FBC8F",lightgreen:"#90EE90",mediumpurple:"#9370DB",darkviolet:"#9400D3",palegreen:"#98FB98",darkorchid:"#9932CC",yellowgreen:"#9ACD32",sienna:"#A0522D",brown:"#A52A2A",darkgray:"#A9A9A9",darkgrey:"#A9A9A9",lightblue:"#ADD8E6",greenyellow:"#ADFF2F",paleturquoise:"#AFEEEE",lightsteelblue:"#B0C4DE",powderblue:"#B0E0E6",firebrick:"#B22222",darkgoldenrod:"#B8860B",mediumorchid:"#BA55D3",rosybrown:"#BC8F8F",darkkhaki:"#BDB76B",mediumvioletred:"#C71585",indianred:"#CD5C5C",peru:"#CD853F",chocolate:"#D2691E",tan:"#D2B48C",lightgray:"#D3D3D3",lightgrey:"#D3D3D3",thistle:"#D8BFD8",orchid:"#DA70D6",goldenrod:"#DAA520",palevioletred:"#DB7093",crimson:"#DC143C",gainsboro:"#DCDCDC",plum:"#DDA0DD",burlywood:"#DEB887",lightcyan:"#E0FFFF",lavender:"#E6E6FA",darksalmon:"#E9967A",violet:"#EE82EE",palegoldenrod:"#EEE8AA",lightcoral:"#F08080",khaki:"#F0E68C",aliceblue:"#F0F8FF",honeydew:"#F0FFF0",azure:"#F0FFFF",sandybrown:"#F4A460",wheat:"#F5DEB3",beige:"#F5F5DC",whitesmoke:"#F5F5F5",mintcream:"#F5FFFA",ghostwhite:"#F8F8FF",salmon:"#FA8072",antiquewhite:"#FAEBD7",linen:"#FAF0E6",lightgoldenrodyellow:"#FAFAD2",oldlace:"#FDF5E6",magenta:"#FF00FF",deeppink:"#FF1493",orangered:"#FF4500",tomato:"#FF6347",hotpink:"#FF69B4",coral:"#FF7F50",darkorange:"#FF8C00",lightsalmon:"#FFA07A",orange:"#FFA500",lightpink:"#FFB6C1",pink:"#FFC0CB",gold:"#FFD700",peachpuff:"#FFDAB9",navajowhite:"#FFDEAD",moccasin:"#FFE4B5",bisque:"#FFE4C4",mistyrose:"#FFE4E1",blanchedalmond:"#FFEBCD",papayawhip:"#FFEFD5",lavenderblush:"#FFF0F5",seashell:"#FFF5EE",cornsilk:"#FFF8DC",lemonchiffon:"#FFFACD",floralwhite:"#FFFAF0",snow:"#FFFAFA",lightyellow:"#FFFFE0",ivory:"#FFFFF0"},Ct=new Int8Array(4),be=new Int32Array(Ct.buffer,0,1),$t=new Float32Array(Ct.buffer,0,1),Kt=/^\s*rgba?\s*\(/,Jt=/^\s*rgba?\s*\(\s*([0-9]*)\s*,\s*([0-9]*)\s*,\s*([0-9]*)(?:\s*,\s*(.*)?)?\)\s*$/;function Qt(r){var n=0,i=0,e=0,t=1;if(r[0]==="#")r.length===4?(n=parseInt(r.charAt(1)+r.charAt(1),16),i=parseInt(r.charAt(2)+r.charAt(2),16),e=parseInt(r.charAt(3)+r.charAt(3),16)):(n=parseInt(r.charAt(1)+r.charAt(2),16),i=parseInt(r.charAt(3)+r.charAt(4),16),e=parseInt(r.charAt(5)+r.charAt(6),16)),r.length===9&&(t=parseInt(r.charAt(7)+r.charAt(8),16)/255);else if(Kt.test(r)){var a=r.match(Jt);a&&(n=+a[1],i=+a[2],e=+a[3],a[4]&&(t=+a[4]))}return{r:n,g:i,b:e,a:t}}var J={};for(var le in pe)J[le]=oe(pe[le]),J[pe[le]]=J[le];function Rt(r,n,i,e,t){return be[0]=e<<24|i<<16|n<<8|r,be[0]=be[0]&4278190079,$t[0]}function oe(r){if(r=r.toLowerCase(),typeof J[r]<"u")return J[r];var n=Qt(r),i=n.r,e=n.g,t=n.b,a=n.a;a=a*255|0;var o=Rt(i,e,t,a);return J[r]=o,o}var Ee={};function _t(r){if(typeof Ee[r]<"u")return Ee[r];var n=(r&16711680)>>>16,i=(r&65280)>>>8,e=r&255,t=255,a=Rt(n,i,e,t);return Ee[r]=a,a}function We(r,n,i,e){return i+(n<<8)+(r<<16)}function Ve(r,n,i,e,t,a){var o=Math.floor(i/a*t),s=Math.floor(r.drawingBufferHeight/a-e/a*t),l=new Uint8Array(4);r.bindFramebuffer(r.FRAMEBUFFER,n),r.readPixels(o,s,1,1,r.RGBA,r.UNSIGNED_BYTE,l);var h=ee(l,4),u=h[0],d=h[1],f=h[2],g=h[3];return[u,d,f,g]}function c(r,n,i){return(n=ae(n))in r?Object.defineProperty(r,n,{value:i,enumerable:!0,configurable:!0,writable:!0}):r[n]=i,r}function je(r,n){var i=Object.keys(r);if(Object.getOwnPropertySymbols){var e=Object.getOwnPropertySymbols(r);n&&(e=e.filter(function(t){return Object.getOwnPropertyDescriptor(r,t).enumerable})),i.push.apply(i,e)}return i}function b(r){for(var n=1;n<arguments.length;n++){var i=arguments[n]!=null?arguments[n]:{};n%2?je(Object(i),!0).forEach(function(e){c(r,e,i[e])}):Object.getOwnPropertyDescriptors?Object.defineProperties(r,Object.getOwnPropertyDescriptors(i)):je(Object(i)).forEach(function(e){Object.defineProperty(r,e,Object.getOwnPropertyDescriptor(i,e))})}return r}function ei(r,n){for(;!{}.hasOwnProperty.call(r,n)&&(r=Q(r))!==null;);return r}function Ae(){return Ae=typeof Reflect<"u"&&Reflect.get?Reflect.get.bind():function(r,n,i){var e=ei(r,n);if(e){var t=Object.getOwnPropertyDescriptor(e,n);return t.get?t.get.call(arguments.length<3?r:i):t.value}},Ae.apply(null,arguments)}function wt(r,n,i,e){var t=Ae(Q(r.prototype),n,i);return typeof t=="function"?function(a){return t.apply(i,a)}:t}function ti(r){return r.normalized?1:r.size}function ye(r){var n=0;return r.forEach(function(i){return n+=ti(i)}),n}function xt(r,n,i){var e=r==="VERTEX"?n.VERTEX_SHADER:n.FRAGMENT_SHADER,t=n.createShader(e);if(t===null)throw new Error("loadShader: error while creating the shader");n.shaderSource(t,i),n.compileShader(t);var a=n.getShaderParameter(t,n.COMPILE_STATUS);if(!a){var o=n.getShaderInfoLog(t);throw n.deleteShader(t),new Error(`loadShader: error while compiling the shader:
|
|
3
3
|
`.concat(o,`
|
|
4
4
|
`).concat(i))}return t}function ii(r,n){return xt("VERTEX",r,n)}function ri(r,n){return xt("FRAGMENT",r,n)}function ni(r,n){var i=r.createProgram();if(i===null)throw new Error("loadProgram: error while creating the program.");var e,t;for(e=0,t=n.length;e<t;e++)r.attachShader(i,n[e]);r.linkProgram(i);var a=r.getProgramParameter(i,r.LINK_STATUS);if(!a){var o=r.getProgramInfoLog(i);throw r.deleteProgram(i),new Error("loadProgram: error while linking the program: ".concat(o))}return i}function Ze(r){var n=r.gl,i=r.buffer,e=r.program,t=r.vertexShader,a=r.fragmentShader;n.deleteShader(t),n.deleteShader(a),n.deleteProgram(e),n.deleteBuffer(i)}var qe=`#define PICKING_MODE
|
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-rqOXhYwp.js"></script>
|
|
16
16
|
<link rel="modulepreload" crossorigin href="./assets/vendor-tanstack-Dock0eH1.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="./assets/vendor-radix-4UOaVWza.js">
|
|
18
18
|
<link rel="modulepreload" crossorigin href="./assets/vendor-recharts-D8rt5lKn.js">
|
|
@@ -11189,12 +11189,34 @@ var ga4StatusDtoSchema = z34.object({
|
|
|
11189
11189
|
createdAt: z34.string().nullable().optional(),
|
|
11190
11190
|
updatedAt: z34.string().nullable().optional()
|
|
11191
11191
|
});
|
|
11192
|
+
var ga4PropertySummaryDtoSchema = z34.object({
|
|
11193
|
+
propertyId: z34.string(),
|
|
11194
|
+
displayName: z34.string(),
|
|
11195
|
+
/** GA4 account that owns the property. Two accounts can hold like-named properties. */
|
|
11196
|
+
accountName: z34.string()
|
|
11197
|
+
});
|
|
11198
|
+
var ga4PropertiesDtoSchema = z34.object({
|
|
11199
|
+
properties: z34.array(ga4PropertySummaryDtoSchema)
|
|
11200
|
+
});
|
|
11192
11201
|
var ga4SyncResponseDtoSchema = z34.object({
|
|
11193
11202
|
synced: z34.boolean(),
|
|
11194
11203
|
rowCount: z34.number().int().nonnegative(),
|
|
11195
11204
|
aiReferralCount: z34.number().int().nonnegative(),
|
|
11196
11205
|
socialReferralCount: z34.number().int().nonnegative(),
|
|
11206
|
+
/**
|
|
11207
|
+
* The window that was ACTUALLY fetched and written, in days — the request's
|
|
11208
|
+
* `days` bounded to GA4's supported sync range (1–90). Read this, not
|
|
11209
|
+
* `requestedDays`, when reporting or reasoning about coverage: a
|
|
11210
|
+
* `--days 500` sync writes 90 days, and this field says 90.
|
|
11211
|
+
*/
|
|
11197
11212
|
days: z34.number().int().nonnegative(),
|
|
11213
|
+
/** The window the caller asked for, before bounding. Equals `days` unless `clamped`. */
|
|
11214
|
+
requestedDays: z34.number().int().nonnegative(),
|
|
11215
|
+
/**
|
|
11216
|
+
* True when `requestedDays` fell outside GA4's supported range and `days`
|
|
11217
|
+
* differs from it — i.e. the synced history is not the range requested.
|
|
11218
|
+
*/
|
|
11219
|
+
clamped: z34.boolean(),
|
|
11198
11220
|
syncedAt: z34.string(),
|
|
11199
11221
|
measurement: z34.object({
|
|
11200
11222
|
acquisition: z34.object({ days: z34.number().int().nonnegative(), status: z34.enum(["ready", "error"]), rowCount: z34.number().int().nonnegative(), error: z34.string().optional() }),
|
|
@@ -15380,6 +15402,7 @@ export {
|
|
|
15380
15402
|
aiReferralClassCounts,
|
|
15381
15403
|
formatAiReferralClassSummary,
|
|
15382
15404
|
ga4StatusDtoSchema,
|
|
15405
|
+
ga4PropertiesDtoSchema,
|
|
15383
15406
|
ga4SyncResponseDtoSchema,
|
|
15384
15407
|
ga4AiReferralHistoryEntrySchema,
|
|
15385
15408
|
ga4AiReferralDailyDtoSchema,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
loadConfig,
|
|
11
11
|
loadConfigRaw,
|
|
12
12
|
saveConfigPatch
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-QDOZEMDK.js";
|
|
14
14
|
import {
|
|
15
15
|
CC_CACHE_DIR,
|
|
16
16
|
DUCKDB_SPEC,
|
|
@@ -139,7 +139,7 @@ import {
|
|
|
139
139
|
siteCrawlSnapshots,
|
|
140
140
|
toAlertView,
|
|
141
141
|
usageCounters
|
|
142
|
-
} from "./chunk-
|
|
142
|
+
} from "./chunk-QKLNTSQ2.js";
|
|
143
143
|
import {
|
|
144
144
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
145
145
|
AGENT_PROVIDER_IDS,
|
|
@@ -253,7 +253,7 @@ import {
|
|
|
253
253
|
validationError,
|
|
254
254
|
winnabilityClassLabel,
|
|
255
255
|
withRetry
|
|
256
|
-
} from "./chunk-
|
|
256
|
+
} from "./chunk-BU6C2DKF.js";
|
|
257
257
|
|
|
258
258
|
// src/telemetry.ts
|
|
259
259
|
import crypto from "crypto";
|
|
@@ -9084,7 +9084,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
9084
9084
|
return result;
|
|
9085
9085
|
}
|
|
9086
9086
|
async function backfillInsightsCommand(project, opts) {
|
|
9087
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
9087
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-RDGUEEZQ.js");
|
|
9088
9088
|
const config = loadConfig();
|
|
9089
9089
|
const db = createClient(config.database);
|
|
9090
9090
|
migrate(db);
|
|
@@ -81,7 +81,7 @@ import {
|
|
|
81
81
|
trafficConnectWordpressRequestSchema,
|
|
82
82
|
trafficEventKindSchema,
|
|
83
83
|
trafficSeriesGranularitySchema
|
|
84
|
-
} from "./chunk-
|
|
84
|
+
} from "./chunk-BU6C2DKF.js";
|
|
85
85
|
|
|
86
86
|
// src/config.ts
|
|
87
87
|
import fs from "fs";
|
|
@@ -4148,6 +4148,18 @@ var getApiV1ProjectsByNameGaStatus = (options) => {
|
|
|
4148
4148
|
...options
|
|
4149
4149
|
});
|
|
4150
4150
|
};
|
|
4151
|
+
var getApiV1ProjectsByNameGaProperties = (options) => {
|
|
4152
|
+
return (options.client ?? client).get({
|
|
4153
|
+
security: [
|
|
4154
|
+
{
|
|
4155
|
+
scheme: "bearer",
|
|
4156
|
+
type: "http"
|
|
4157
|
+
}
|
|
4158
|
+
],
|
|
4159
|
+
url: "/api/v1/projects/{name}/ga/properties",
|
|
4160
|
+
...options
|
|
4161
|
+
});
|
|
4162
|
+
};
|
|
4151
4163
|
var getApiV1ProjectsByNameGaMeasurementAnalysis = (options) => {
|
|
4152
4164
|
return (options.client ?? client).get({
|
|
4153
4165
|
security: [
|
|
@@ -6925,6 +6937,11 @@ var ApiClient = class {
|
|
|
6925
6937
|
() => getApiV1ProjectsByNameGaStatus({ client: this.heyClient, path: { name: project } })
|
|
6926
6938
|
);
|
|
6927
6939
|
}
|
|
6940
|
+
async gaProperties(project) {
|
|
6941
|
+
return this.invoke(
|
|
6942
|
+
() => getApiV1ProjectsByNameGaProperties({ client: this.heyClient, path: { name: project } })
|
|
6943
|
+
);
|
|
6944
|
+
}
|
|
6928
6945
|
async gaMeasurementAnalysis(project, params) {
|
|
6929
6946
|
return this.invoke(
|
|
6930
6947
|
() => getApiV1ProjectsByNameGaMeasurementAnalysis({
|
|
@@ -9299,6 +9316,17 @@ var canonryMcpTools = [
|
|
|
9299
9316
|
openApiOperations: ["GET /api/v1/projects/{name}/ga/status"],
|
|
9300
9317
|
handler: (client2, input) => client2.gaStatus(input.project)
|
|
9301
9318
|
}),
|
|
9319
|
+
defineTool({
|
|
9320
|
+
name: "canonry_ga_properties",
|
|
9321
|
+
title: "List GA4 properties",
|
|
9322
|
+
description: `List the GA4 properties the project's connected Google account can read, with their numeric property ids. Use this to find the id that "canonry ga connect --property-id" needs; it cannot be derived from the domain or the OAuth grant.`,
|
|
9323
|
+
access: "read",
|
|
9324
|
+
tier: "ga",
|
|
9325
|
+
inputSchema: projectInputSchema,
|
|
9326
|
+
annotations: readAnnotations(),
|
|
9327
|
+
openApiOperations: ["GET /api/v1/projects/{name}/ga/properties"],
|
|
9328
|
+
handler: (client2, input) => client2.gaProperties(input.project)
|
|
9329
|
+
}),
|
|
9302
9330
|
defineTool({
|
|
9303
9331
|
name: "canonry_ga_measurement_analysis",
|
|
9304
9332
|
title: "Analyze GA acquisition and search demand",
|
|
@@ -271,6 +271,7 @@ import {
|
|
|
271
271
|
formatWindowCountDelta,
|
|
272
272
|
ga4AiReferralDailyDtoSchema,
|
|
273
273
|
ga4AiReferralHistoryEntrySchema,
|
|
274
|
+
ga4PropertiesDtoSchema,
|
|
274
275
|
ga4SessionHistoryEntrySchema,
|
|
275
276
|
ga4SocialReferralHistoryEntrySchema,
|
|
276
277
|
ga4StatusDtoSchema,
|
|
@@ -581,7 +582,7 @@ import {
|
|
|
581
582
|
wordpressSchemaDeployResultDtoSchema,
|
|
582
583
|
wordpressSchemaStatusResultDtoSchema,
|
|
583
584
|
wordpressStatusDtoSchema
|
|
584
|
-
} from "./chunk-
|
|
585
|
+
} from "./chunk-BU6C2DKF.js";
|
|
585
586
|
|
|
586
587
|
// src/intelligence-service.ts
|
|
587
588
|
import { eq as eq59, desc as desc26, asc as asc11, and as and47, ne as ne7, or as or13, inArray as inArray21, gte as gte14, lte as lte12 } from "drizzle-orm";
|
|
@@ -9225,6 +9226,13 @@ function assertProjectScope(request, projectId) {
|
|
|
9225
9226
|
throw forbidden("This API key is scoped to a different project.");
|
|
9226
9227
|
}
|
|
9227
9228
|
}
|
|
9229
|
+
function assertNotProjectScoped(request, what) {
|
|
9230
|
+
if (request.apiKey?.projectId) {
|
|
9231
|
+
throw forbidden(
|
|
9232
|
+
`This API key is scoped to a single project, and ${what} covers every account the connected Google principal can see. Use a full-instance key.`
|
|
9233
|
+
);
|
|
9234
|
+
}
|
|
9235
|
+
}
|
|
9228
9236
|
function hashApiKey(key) {
|
|
9229
9237
|
return crypto4.createHash("sha256").update(key).digest("hex");
|
|
9230
9238
|
}
|
|
@@ -28064,6 +28072,7 @@ var SCHEMA_TABLE = {
|
|
|
28064
28072
|
GA4MeasurementAnalysisDto: gaMeasurementAnalysisDtoSchema,
|
|
28065
28073
|
GA4SessionHistoryEntry: ga4SessionHistoryEntrySchema,
|
|
28066
28074
|
GA4SocialReferralHistoryEntry: ga4SocialReferralHistoryEntrySchema,
|
|
28075
|
+
GA4PropertiesDto: ga4PropertiesDtoSchema,
|
|
28067
28076
|
GA4StatusDto: ga4StatusDtoSchema,
|
|
28068
28077
|
GA4SyncResponseDto: ga4SyncResponseDtoSchema,
|
|
28069
28078
|
GbpAccountListResponse: gbpAccountListResponseSchema,
|
|
@@ -32893,7 +32902,7 @@ var routeCatalog = [
|
|
|
32893
32902
|
{
|
|
32894
32903
|
method: "post",
|
|
32895
32904
|
path: "/api/v1/projects/{name}/ga/connect",
|
|
32896
|
-
summary: "Connect Google Analytics 4
|
|
32905
|
+
summary: "Connect Google Analytics 4 (service account or existing OAuth connection)",
|
|
32897
32906
|
tags: ["ga4"],
|
|
32898
32907
|
parameters: [nameParameter],
|
|
32899
32908
|
requestBody: {
|
|
@@ -32902,9 +32911,12 @@ var routeCatalog = [
|
|
|
32902
32911
|
"application/json": {
|
|
32903
32912
|
schema: {
|
|
32904
32913
|
type: "object",
|
|
32905
|
-
required: ["propertyId"
|
|
32914
|
+
required: ["propertyId"],
|
|
32906
32915
|
properties: {
|
|
32907
32916
|
propertyId: stringSchema,
|
|
32917
|
+
// Optional: omit it to select a property on an existing OAuth
|
|
32918
|
+
// connection (canonry google connect --type ga4). Required only
|
|
32919
|
+
// for service-account auth.
|
|
32908
32920
|
keyJson: stringSchema
|
|
32909
32921
|
}
|
|
32910
32922
|
}
|
|
@@ -32940,6 +32952,19 @@ var routeCatalog = [
|
|
|
32940
32952
|
404: errorResponse("Project not found.")
|
|
32941
32953
|
}
|
|
32942
32954
|
},
|
|
32955
|
+
{
|
|
32956
|
+
method: "get",
|
|
32957
|
+
path: "/api/v1/projects/{name}/ga/properties",
|
|
32958
|
+
summary: "List GA4 properties the connected account can read",
|
|
32959
|
+
description: "Lists every GA4 property visible to the project's OAuth connection, so the numeric property id needed by ga/connect can be discovered without leaving canonry. Requires an OAuth GA4 connection; service-account connections already carry their property id.",
|
|
32960
|
+
tags: ["ga4"],
|
|
32961
|
+
parameters: [nameParameter],
|
|
32962
|
+
responses: {
|
|
32963
|
+
200: jsonResponse("GA4 properties returned.", "GA4PropertiesDto"),
|
|
32964
|
+
400: errorResponse("No OAuth GA4 connection for this project."),
|
|
32965
|
+
404: errorResponse("Project not found.")
|
|
32966
|
+
}
|
|
32967
|
+
},
|
|
32943
32968
|
{
|
|
32944
32969
|
method: "get",
|
|
32945
32970
|
path: "/api/v1/projects/{name}/ga/measurement-analysis",
|
|
@@ -32985,6 +33010,7 @@ var routeCatalog = [
|
|
|
32985
33010
|
method: "post",
|
|
32986
33011
|
path: "/api/v1/projects/{name}/ga/sync",
|
|
32987
33012
|
summary: "Sync GA4 traffic and AI referral data",
|
|
33013
|
+
description: "Syncs a window of GA4 history. `days` is bounded to GA4's supported sync range (1-90); a request outside it is clamped rather than rejected. The response reports the window ACTUALLY written as `days`, the unbounded request as `requestedDays`, and sets `clamped` when the two differ.",
|
|
32988
33014
|
tags: ["ga4"],
|
|
32989
33015
|
parameters: [nameParameter],
|
|
32990
33016
|
requestBody: {
|
|
@@ -32994,7 +33020,7 @@ var routeCatalog = [
|
|
|
32994
33020
|
schema: {
|
|
32995
33021
|
type: "object",
|
|
32996
33022
|
properties: {
|
|
32997
|
-
days: integerSchema
|
|
33023
|
+
days: { ...integerSchema, description: "Days of history to sync. Clamped to 1-90; check `clamped` in the response to detect truncation. Defaults to 30." }
|
|
32998
33024
|
}
|
|
32999
33025
|
}
|
|
33000
33026
|
}
|
|
@@ -36113,10 +36139,16 @@ import crypto26 from "crypto";
|
|
|
36113
36139
|
|
|
36114
36140
|
// ../integration-google-analytics/src/constants.ts
|
|
36115
36141
|
var GA4_DATA_API_BASE = "https://analyticsdata.googleapis.com/v1beta";
|
|
36142
|
+
var GA4_ADMIN_API_BASE = "https://analyticsadmin.googleapis.com/v1beta";
|
|
36116
36143
|
var GA4_SCOPE = "https://www.googleapis.com/auth/analytics.readonly";
|
|
36117
36144
|
var GOOGLE_TOKEN_URL2 = "https://oauth2.googleapis.com/token";
|
|
36118
36145
|
var GA4_DEFAULT_SYNC_DAYS = 30;
|
|
36119
36146
|
var GA4_MAX_SYNC_DAYS = 90;
|
|
36147
|
+
function resolveGa4SyncDays(days) {
|
|
36148
|
+
const requestedDays = days != null && Number.isFinite(days) ? Math.trunc(days) : GA4_DEFAULT_SYNC_DAYS;
|
|
36149
|
+
const effectiveDays = Math.min(Math.max(1, requestedDays), GA4_MAX_SYNC_DAYS);
|
|
36150
|
+
return { requestedDays, effectiveDays, clamped: effectiveDays !== requestedDays };
|
|
36151
|
+
}
|
|
36120
36152
|
var GA4_REQUEST_TIMEOUT_MS = 3e4;
|
|
36121
36153
|
var GA4_MAX_PAGES = 50;
|
|
36122
36154
|
var GA4_MAX_CONCURRENT_REQUESTS = 4;
|
|
@@ -36299,6 +36331,9 @@ function isRetryableGa4Error(err) {
|
|
|
36299
36331
|
if (err instanceof GA4ApiError) {
|
|
36300
36332
|
return err.status === 429 || err.status >= 500;
|
|
36301
36333
|
}
|
|
36334
|
+
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
|
36335
|
+
return true;
|
|
36336
|
+
}
|
|
36302
36337
|
return false;
|
|
36303
36338
|
}
|
|
36304
36339
|
async function withGa4Retry(fn, errLabel) {
|
|
@@ -36435,7 +36470,7 @@ function formatDate2(d) {
|
|
|
36435
36470
|
return d.toISOString().slice(0, 10);
|
|
36436
36471
|
}
|
|
36437
36472
|
function measurementDateRange(days) {
|
|
36438
|
-
const syncDays =
|
|
36473
|
+
const { effectiveDays: syncDays } = resolveGa4SyncDays(days);
|
|
36439
36474
|
const endDate = /* @__PURE__ */ new Date();
|
|
36440
36475
|
const startDate = /* @__PURE__ */ new Date();
|
|
36441
36476
|
startDate.setUTCDate(startDate.getUTCDate() - syncDays);
|
|
@@ -36543,7 +36578,7 @@ var AI_REFERRAL_SOURCE_FILTERS = [
|
|
|
36543
36578
|
async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
36544
36579
|
validateAccessToken2(accessToken);
|
|
36545
36580
|
validatePropertyId(propertyId);
|
|
36546
|
-
const syncDays =
|
|
36581
|
+
const { effectiveDays: syncDays } = resolveGa4SyncDays(days);
|
|
36547
36582
|
const endDate = /* @__PURE__ */ new Date();
|
|
36548
36583
|
const startDate = /* @__PURE__ */ new Date();
|
|
36549
36584
|
startDate.setDate(startDate.getDate() - syncDays);
|
|
@@ -36672,10 +36707,73 @@ async function verifyConnectionWithToken(accessToken, propertyId) {
|
|
|
36672
36707
|
});
|
|
36673
36708
|
return true;
|
|
36674
36709
|
}
|
|
36710
|
+
async function listProperties(accessToken) {
|
|
36711
|
+
validateAccessToken2(accessToken);
|
|
36712
|
+
const properties = [];
|
|
36713
|
+
let pageToken;
|
|
36714
|
+
do {
|
|
36715
|
+
const url = new URL(`${GA4_ADMIN_API_BASE}/accountSummaries`);
|
|
36716
|
+
url.searchParams.set("pageSize", "200");
|
|
36717
|
+
if (pageToken) url.searchParams.set("pageToken", pageToken);
|
|
36718
|
+
const body = await withGa4Limit(() => withGa4Retry(async () => {
|
|
36719
|
+
const res = await fetch(url, {
|
|
36720
|
+
headers: { "Authorization": `Bearer ${accessToken}` },
|
|
36721
|
+
signal: AbortSignal.timeout(GA4_REQUEST_TIMEOUT_MS)
|
|
36722
|
+
});
|
|
36723
|
+
if (!res.ok) {
|
|
36724
|
+
const raw = await res.text().catch(() => "");
|
|
36725
|
+
let detail = "";
|
|
36726
|
+
let quotaReason = false;
|
|
36727
|
+
try {
|
|
36728
|
+
const parsed = JSON.parse(raw);
|
|
36729
|
+
if (parsed.error?.status === "SERVICE_DISABLED") {
|
|
36730
|
+
detail = " The Google Analytics Admin API is not enabled for this GCP project. Enable it at: https://console.developers.google.com/apis/api/analyticsadmin.googleapis.com/overview";
|
|
36731
|
+
} else if (parsed.error?.message) {
|
|
36732
|
+
detail = ` ${parsed.error.message}`;
|
|
36733
|
+
}
|
|
36734
|
+
quotaReason = (parsed.error?.errors ?? []).some(
|
|
36735
|
+
(e) => e.reason === "rateLimitExceeded" || e.reason === "quotaExceeded" || e.reason === "userRateLimitExceeded"
|
|
36736
|
+
) || parsed.error?.status === "RESOURCE_EXHAUSTED";
|
|
36737
|
+
} catch {
|
|
36738
|
+
if (raw.length < 200) detail = ` ${raw}`;
|
|
36739
|
+
}
|
|
36740
|
+
const sanitized = detail.replace(new RegExp(escapeRegExp3(accessToken), "g"), "***");
|
|
36741
|
+
const retryAfterSeconds = res.status === 429 || res.status >= 500 || res.status === 403 && quotaReason ? parseRetryAfter(res.headers.get("retry-after")) : void 0;
|
|
36742
|
+
ga4Log("error", "admin.account-summaries-failed", {
|
|
36743
|
+
httpStatus: res.status,
|
|
36744
|
+
retryAfterSeconds,
|
|
36745
|
+
quotaReason
|
|
36746
|
+
});
|
|
36747
|
+
throw new GA4ApiError(
|
|
36748
|
+
`Failed to list GA4 properties.${sanitized}`,
|
|
36749
|
+
// A quota 403 is surfaced as 429 so every downstream consumer — the
|
|
36750
|
+
// retry predicate and the route's error mapper — reads it as rate
|
|
36751
|
+
// limiting rather than as a permission denial.
|
|
36752
|
+
res.status === 403 && quotaReason ? 429 : res.status,
|
|
36753
|
+
retryAfterSeconds
|
|
36754
|
+
);
|
|
36755
|
+
}
|
|
36756
|
+
return await res.json();
|
|
36757
|
+
}, "GA4 accountSummaries"));
|
|
36758
|
+
for (const account of body.accountSummaries ?? []) {
|
|
36759
|
+
for (const summary of account.propertySummaries ?? []) {
|
|
36760
|
+
const propertyId = (summary.property ?? "").split("/").pop() ?? "";
|
|
36761
|
+
if (!propertyId) continue;
|
|
36762
|
+
properties.push({
|
|
36763
|
+
propertyId,
|
|
36764
|
+
displayName: summary.displayName ?? "",
|
|
36765
|
+
accountName: account.displayName ?? ""
|
|
36766
|
+
});
|
|
36767
|
+
}
|
|
36768
|
+
}
|
|
36769
|
+
pageToken = body.nextPageToken;
|
|
36770
|
+
} while (pageToken);
|
|
36771
|
+
return properties;
|
|
36772
|
+
}
|
|
36675
36773
|
async function fetchAggregateSummary(accessToken, propertyId, days) {
|
|
36676
36774
|
validateAccessToken2(accessToken);
|
|
36677
36775
|
validatePropertyId(propertyId);
|
|
36678
|
-
const syncDays =
|
|
36776
|
+
const { effectiveDays: syncDays } = resolveGa4SyncDays(days);
|
|
36679
36777
|
const endDate = /* @__PURE__ */ new Date();
|
|
36680
36778
|
const startDate = /* @__PURE__ */ new Date();
|
|
36681
36779
|
startDate.setDate(startDate.getDate() - syncDays);
|
|
@@ -36725,7 +36823,7 @@ function parseOptionalCountMetric(value) {
|
|
|
36725
36823
|
async function fetchDailyTotals(accessToken, propertyId, days) {
|
|
36726
36824
|
validateAccessToken2(accessToken);
|
|
36727
36825
|
validatePropertyId(propertyId);
|
|
36728
|
-
const syncDays =
|
|
36826
|
+
const { effectiveDays: syncDays } = resolveGa4SyncDays(days);
|
|
36729
36827
|
const endDate = /* @__PURE__ */ new Date();
|
|
36730
36828
|
const startDate = /* @__PURE__ */ new Date();
|
|
36731
36829
|
startDate.setDate(startDate.getDate() - syncDays);
|
|
@@ -36818,7 +36916,7 @@ async function fetchWindowSummary(accessToken, propertyId, windowKey) {
|
|
|
36818
36916
|
async function fetchAiReferrals(accessToken, propertyId, days) {
|
|
36819
36917
|
validateAccessToken2(accessToken);
|
|
36820
36918
|
validatePropertyId(propertyId);
|
|
36821
|
-
const syncDays =
|
|
36919
|
+
const { effectiveDays: syncDays } = resolveGa4SyncDays(days);
|
|
36822
36920
|
const endDate = /* @__PURE__ */ new Date();
|
|
36823
36921
|
const startDate = /* @__PURE__ */ new Date();
|
|
36824
36922
|
startDate.setDate(startDate.getDate() - syncDays);
|
|
@@ -36911,7 +37009,7 @@ var SOCIAL_CHANNEL_GROUPS = ["Organic Social", "Paid Social"];
|
|
|
36911
37009
|
async function fetchSocialReferrals(accessToken, propertyId, days) {
|
|
36912
37010
|
validateAccessToken2(accessToken);
|
|
36913
37011
|
validatePropertyId(propertyId);
|
|
36914
|
-
const syncDays =
|
|
37012
|
+
const { effectiveDays: syncDays } = resolveGa4SyncDays(days);
|
|
36915
37013
|
const endDate = /* @__PURE__ */ new Date();
|
|
36916
37014
|
const startDate = /* @__PURE__ */ new Date();
|
|
36917
37015
|
startDate.setDate(startDate.getDate() - syncDays);
|
|
@@ -37736,6 +37834,7 @@ async function googleRoutes(app, opts) {
|
|
|
37736
37834
|
return reply.status(204).send();
|
|
37737
37835
|
});
|
|
37738
37836
|
app.get("/projects/:name/google/properties", async (request) => {
|
|
37837
|
+
assertNotProjectScoped(request, "listing Search Console properties");
|
|
37739
37838
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getAuthConfig();
|
|
37740
37839
|
if (!googleClientId || !googleClientSecret) {
|
|
37741
37840
|
throw validationError("Google OAuth is not configured");
|
|
@@ -38652,6 +38751,7 @@ async function googleRoutes(app, opts) {
|
|
|
38652
38751
|
return listSelectionResponse(project.id);
|
|
38653
38752
|
});
|
|
38654
38753
|
app.get("/projects/:name/gbp/accounts", async (request) => {
|
|
38754
|
+
assertNotProjectScoped(request, "listing Business Profile accounts");
|
|
38655
38755
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getAuthConfig();
|
|
38656
38756
|
if (!googleClientId || !googleClientSecret) {
|
|
38657
38757
|
throw validationError("Google OAuth is not configured");
|
|
@@ -44172,6 +44272,7 @@ async function bingRoutes(app, opts) {
|
|
|
44172
44272
|
};
|
|
44173
44273
|
});
|
|
44174
44274
|
app.get("/projects/:name/bing/sites", async (request) => {
|
|
44275
|
+
assertNotProjectScoped(request, "listing Bing Webmaster sites");
|
|
44175
44276
|
const store = requireConnectionStore();
|
|
44176
44277
|
const project = resolveProject(app.db, request.params.name);
|
|
44177
44278
|
const conn = requireConnection(store, project.canonicalDomain);
|
|
@@ -45038,6 +45139,22 @@ async function refreshOAuthTokenIfNeeded(googleStore, authConfig, canonicalDomai
|
|
|
45038
45139
|
}
|
|
45039
45140
|
return oauthConn.accessToken;
|
|
45040
45141
|
}
|
|
45142
|
+
function ga4ErrorToAppError(err, context) {
|
|
45143
|
+
if (err instanceof AppError) return err;
|
|
45144
|
+
if (err instanceof GA4ApiError) {
|
|
45145
|
+
if (err.status === 429) {
|
|
45146
|
+
return quotaExceeded("Google Analytics Admin API (rate limited; retries exhausted)");
|
|
45147
|
+
}
|
|
45148
|
+
if (err.status === 401 || err.status === 403) {
|
|
45149
|
+
return forbidden(
|
|
45150
|
+
`${context}: the connected Google account was rejected by Google (${err.status}). Reconnect with "canonry google connect <project> --type ga4".`,
|
|
45151
|
+
{ reason: "ga4-upstream-auth", upstreamStatus: err.status }
|
|
45152
|
+
);
|
|
45153
|
+
}
|
|
45154
|
+
return providerError(`${context}: ${err.message}`);
|
|
45155
|
+
}
|
|
45156
|
+
return providerError(`${context}: ${err instanceof Error ? err.message : String(err)}`);
|
|
45157
|
+
}
|
|
45041
45158
|
async function resolveGa4AccessToken(opts, projectName, canonicalDomain) {
|
|
45042
45159
|
const saConn = opts.ga4CredentialStore?.getConnection(projectName);
|
|
45043
45160
|
if (saConn?.clientEmail && saConn?.privateKey && saConn?.propertyId) {
|
|
@@ -45059,7 +45176,7 @@ async function resolveGa4AccessToken(opts, projectName, canonicalDomain) {
|
|
|
45059
45176
|
}
|
|
45060
45177
|
if (!oauthConn.propertyId) {
|
|
45061
45178
|
throw validationError(
|
|
45062
|
-
'GA4 property ID not set. Run "canonry ga
|
|
45179
|
+
'GA4 property ID not set. Run "canonry ga properties <project>" to list the readable properties, then "canonry ga connect <project> --property-id <id>" to select one.'
|
|
45063
45180
|
);
|
|
45064
45181
|
}
|
|
45065
45182
|
const accessToken = await refreshOAuthTokenIfNeeded(googleStore, authConfig, canonicalDomain, {
|
|
@@ -45188,6 +45305,33 @@ async function ga4Routes(app, opts) {
|
|
|
45188
45305
|
});
|
|
45189
45306
|
return reply.status(204).send();
|
|
45190
45307
|
});
|
|
45308
|
+
app.get("/projects/:name/ga/properties", async (request) => {
|
|
45309
|
+
assertNotProjectScoped(request, "listing GA4 properties");
|
|
45310
|
+
const project = resolveProject(app.db, request.params.name);
|
|
45311
|
+
const googleStore = opts.googleConnectionStore;
|
|
45312
|
+
const authConfig = opts.getGoogleAuthConfig?.();
|
|
45313
|
+
if (!googleStore || !authConfig) {
|
|
45314
|
+
throw validationError(
|
|
45315
|
+
'Google OAuth is not configured. Run "canonry google connect <project> --type ga4" first.'
|
|
45316
|
+
);
|
|
45317
|
+
}
|
|
45318
|
+
const oauthConn = googleStore.getConnection(project.canonicalDomain, "ga4");
|
|
45319
|
+
if (!oauthConn?.accessToken || !oauthConn?.refreshToken) {
|
|
45320
|
+
throw validationError(
|
|
45321
|
+
'No GA4 OAuth connection for this project. Run "canonry google connect <project> --type ga4" first. Service-account connections already carry their property id and do not need this listing.'
|
|
45322
|
+
);
|
|
45323
|
+
}
|
|
45324
|
+
try {
|
|
45325
|
+
const accessToken = await refreshOAuthTokenIfNeeded(googleStore, authConfig, project.canonicalDomain, {
|
|
45326
|
+
accessToken: oauthConn.accessToken,
|
|
45327
|
+
refreshToken: oauthConn.refreshToken,
|
|
45328
|
+
tokenExpiresAt: oauthConn.tokenExpiresAt
|
|
45329
|
+
});
|
|
45330
|
+
return { properties: await listProperties(accessToken) };
|
|
45331
|
+
} catch (err) {
|
|
45332
|
+
throw ga4ErrorToAppError(err, "Failed to list GA4 properties");
|
|
45333
|
+
}
|
|
45334
|
+
});
|
|
45191
45335
|
app.get("/projects/:name/ga/status", async (request, _reply) => {
|
|
45192
45336
|
const project = resolveProject(app.db, request.params.name);
|
|
45193
45337
|
const saConn = opts.ga4CredentialStore?.getConnection(project.name);
|
|
@@ -45209,7 +45353,7 @@ async function ga4Routes(app, opts) {
|
|
|
45209
45353
|
});
|
|
45210
45354
|
app.post("/projects/:name/ga/sync", async (request, _reply) => {
|
|
45211
45355
|
const project = resolveProject(app.db, request.params.name);
|
|
45212
|
-
const days = request.body?.days
|
|
45356
|
+
const { requestedDays, effectiveDays: days, clamped } = resolveGa4SyncDays(request.body?.days);
|
|
45213
45357
|
const only = request.body?.only;
|
|
45214
45358
|
const validOnlyValues = ["traffic", "ai", "social"];
|
|
45215
45359
|
if (only !== void 0 && !validOnlyValues.includes(only)) {
|
|
@@ -45220,8 +45364,8 @@ async function ga4Routes(app, opts) {
|
|
|
45220
45364
|
const syncAi = !only || only === "ai";
|
|
45221
45365
|
const syncSocial = !only || only === "social";
|
|
45222
45366
|
const measurementState = app.db.select().from(gaMeasurementSyncStates).where(eq44(gaMeasurementSyncStates.projectId, project.id)).get();
|
|
45223
|
-
const acquisitionDays = measurementState?.acquisitionSyncedAt != null ?
|
|
45224
|
-
const leadDays = measurementState?.leadSyncedAt != null ?
|
|
45367
|
+
const acquisitionDays = measurementState?.acquisitionSyncedAt != null ? days : GA4_MAX_SYNC_DAYS;
|
|
45368
|
+
const leadDays = measurementState?.leadSyncedAt != null ? days : GA4_MAX_SYNC_DAYS;
|
|
45225
45369
|
const leadEventNames = project.measurement.leadEventNames;
|
|
45226
45370
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
45227
45371
|
const runId = crypto35.randomUUID();
|
|
@@ -45484,6 +45628,7 @@ async function ga4Routes(app, opts) {
|
|
|
45484
45628
|
aiReferralCount: aiReferrals.length,
|
|
45485
45629
|
socialReferralCount: socialReferrals.length,
|
|
45486
45630
|
days,
|
|
45631
|
+
...clamped ? { requestedDays, clamped } : {},
|
|
45487
45632
|
totalUsers: summary.totalUsers,
|
|
45488
45633
|
...only ? { only } : {}
|
|
45489
45634
|
});
|
|
@@ -45493,6 +45638,8 @@ async function ga4Routes(app, opts) {
|
|
|
45493
45638
|
aiReferralCount: aiReferrals.length,
|
|
45494
45639
|
socialReferralCount: socialReferrals.length,
|
|
45495
45640
|
days,
|
|
45641
|
+
requestedDays,
|
|
45642
|
+
clamped,
|
|
45496
45643
|
syncedAt: now,
|
|
45497
45644
|
measurement: {
|
|
45498
45645
|
acquisition: acquisitionResult,
|