@canonry/canonry 4.156.0 → 4.158.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 (30) hide show
  1. package/README.md +2 -2
  2. package/assets/agent-workspace/skills/canonry/SKILL.md +8 -3
  3. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +454 -21
  4. package/assets/assets/{AuditHistoryPanel-gmJMVlXX.js → AuditHistoryPanel-McQfjoU9.js} +1 -1
  5. package/assets/assets/{BacklinksPage-Bv2sTCyN.js → BacklinksPage-Da8y1Wv8.js} +1 -1
  6. package/assets/assets/{HistoryPage-13q7GkiI.js → HistoryPage-9MJfhRyT.js} +1 -1
  7. package/assets/assets/{MeasurementPropertyPage-BgBqXT1p.js → MeasurementPropertyPage-BiNAHEJW.js} +1 -1
  8. package/assets/assets/ProjectPage-mWSMjXrR.js +15 -0
  9. package/assets/assets/{RunRow-bPzapIGJ.js → RunRow-DCG2LBlS.js} +1 -1
  10. package/assets/assets/{RunsPage-Dqfz18lH.js → RunsPage-ByWA_FSk.js} +1 -1
  11. package/assets/assets/{SettingsPage-_wMecn-V.js → SettingsPage-DdSeKAKF.js} +1 -1
  12. package/assets/assets/{TrafficPage-RYBdsOLB.js → TrafficPage-5u8rPcBt.js} +1 -1
  13. package/assets/assets/{TrafficSourceDetailPage-DpeMMyKU.js → TrafficSourceDetailPage-Dm-fK87u.js} +1 -1
  14. package/assets/assets/{extract-error-message-DUarAlyD.js → extract-error-message-CtxkAd6E.js} +1 -1
  15. package/assets/assets/{index-DP-fT2uI.js → index-DUfg50Pl.js} +28 -28
  16. package/assets/assets/index-cXb3mfeQ.css +1 -0
  17. package/assets/assets/{react-sigma_core.esm.min-DnypJl5w.js → react-sigma_core.esm.min-ComIyDKi.js} +1 -1
  18. package/assets/index.html +2 -2
  19. package/dist/{chunk-ZWESOFA5.js → chunk-4VZP64GZ.js} +273 -15
  20. package/dist/{chunk-AMPT2EP7.js → chunk-DEVAEC2S.js} +121 -1
  21. package/dist/{chunk-CPASKGUE.js → chunk-LK6YFHG4.js} +35 -1
  22. package/dist/{chunk-ABNN2ZVR.js → chunk-YP4K2KXH.js} +1933 -451
  23. package/dist/cli.js +667 -142
  24. package/dist/index.d.ts +32 -0
  25. package/dist/index.js +4 -4
  26. package/dist/{intelligence-service-WHC7B32V.js → intelligence-service-LQ6ZRWMF.js} +2 -2
  27. package/dist/mcp.js +2 -2
  28. package/package.json +12 -10
  29. package/assets/assets/ProjectPage-EsGwKhot.js +0 -15
  30. package/assets/assets/index-BxegEwic.css +0 -1
@@ -10,7 +10,7 @@ import {
10
10
  loadConfig,
11
11
  loadConfigRaw,
12
12
  saveConfigPatch
13
- } from "./chunk-CPASKGUE.js";
13
+ } from "./chunk-LK6YFHG4.js";
14
14
  import {
15
15
  CC_CACHE_DIR,
16
16
  DUCKDB_SPEC,
@@ -136,7 +136,7 @@ import {
136
136
  siteCrawlSnapshots,
137
137
  toAlertView,
138
138
  usageCounters
139
- } from "./chunk-ABNN2ZVR.js";
139
+ } from "./chunk-YP4K2KXH.js";
140
140
  import {
141
141
  AGENT_MEMORY_VALUE_MAX_BYTES,
142
142
  AGENT_PROVIDER_IDS,
@@ -236,7 +236,7 @@ import {
236
236
  validationError,
237
237
  winnabilityClassLabel,
238
238
  withRetry
239
- } from "./chunk-AMPT2EP7.js";
239
+ } from "./chunk-DEVAEC2S.js";
240
240
 
241
241
  // src/telemetry.ts
242
242
  import crypto from "crypto";
@@ -3167,8 +3167,67 @@ function removeVercelTrafficConnection(config, projectName) {
3167
3167
  return true;
3168
3168
  }
3169
3169
 
3170
- // src/wordpress-config.ts
3170
+ // src/cloudflare-traffic-config.ts
3171
+ function unsupportedDeliveryMode(deliveryMode) {
3172
+ const label = typeof deliveryMode === "string" ? ` "${deliveryMode}"` : "";
3173
+ return new Error(`Unsupported Cloudflare traffic delivery mode${label}`);
3174
+ }
3171
3175
  function ensureConnections6(config) {
3176
+ if (!config.cloudflareTraffic) config.cloudflareTraffic = {};
3177
+ if (!config.cloudflareTraffic.connections) config.cloudflareTraffic.connections = [];
3178
+ return config.cloudflareTraffic.connections;
3179
+ }
3180
+ function normalizeDirectPushConnection(connection) {
3181
+ const deliveryMode = connection.deliveryMode;
3182
+ if (deliveryMode !== void 0 && deliveryMode !== "direct-push") {
3183
+ throw unsupportedDeliveryMode(deliveryMode);
3184
+ }
3185
+ if (deliveryMode === "direct-push") return connection;
3186
+ return { ...connection, deliveryMode: "direct-push" };
3187
+ }
3188
+ function getCloudflareTrafficConnection(config, projectName) {
3189
+ const connection = (config.cloudflareTraffic?.connections ?? []).find((c) => c.projectName === projectName);
3190
+ return connection ? normalizeDirectPushConnection(connection) : void 0;
3191
+ }
3192
+ function getCloudflareTrafficConnectionBySourceId(config, sourceId) {
3193
+ const connection = (config.cloudflareTraffic?.connections ?? []).find((c) => c.sourceId === sourceId);
3194
+ return connection ? normalizeDirectPushConnection(connection) : void 0;
3195
+ }
3196
+ function upsertCloudflareTrafficConnection(config, connection) {
3197
+ const deliveryMode = connection.deliveryMode;
3198
+ if (deliveryMode !== "direct-push") {
3199
+ throw unsupportedDeliveryMode(deliveryMode);
3200
+ }
3201
+ const connections = ensureConnections6(config);
3202
+ const index = connections.findIndex((c) => c.projectName === connection.projectName);
3203
+ if (index === -1) {
3204
+ connections.push(connection);
3205
+ return connection;
3206
+ }
3207
+ connections[index] = connection;
3208
+ return connection;
3209
+ }
3210
+ function removeCloudflareTrafficConnection(config, projectName) {
3211
+ const connections = config.cloudflareTraffic?.connections;
3212
+ if (!connections?.length) return false;
3213
+ const next = connections.filter((c) => c.projectName !== projectName);
3214
+ if (next.length === connections.length) return false;
3215
+ if (!config.cloudflareTraffic) return false;
3216
+ config.cloudflareTraffic.connections = next;
3217
+ if (next.length === 0) {
3218
+ delete config.cloudflareTraffic;
3219
+ }
3220
+ return true;
3221
+ }
3222
+
3223
+ // src/cloudflare-ingest-url.ts
3224
+ function buildCloudflareIngestUrlTemplate(config) {
3225
+ const base = (config.publicUrl ?? config.apiUrl).replace(/\/+$/, "");
3226
+ return `${base}/api/v1/projects/{name}/traffic/cloudflare/ingest`;
3227
+ }
3228
+
3229
+ // src/wordpress-config.ts
3230
+ function ensureConnections7(config) {
3172
3231
  if (!config.wordpress) config.wordpress = {};
3173
3232
  if (!config.wordpress.connections) config.wordpress.connections = [];
3174
3233
  return config.wordpress.connections;
@@ -3185,7 +3244,7 @@ function getWordpressConnection(config, projectName) {
3185
3244
  return (config.wordpress?.connections ?? []).find((connection) => connection.projectName === projectName);
3186
3245
  }
3187
3246
  function upsertWordpressConnection(config, connection) {
3188
- const connections = ensureConnections6(config);
3247
+ const connections = ensureConnections7(config);
3189
3248
  const normalized = normalizeConnection(connection);
3190
3249
  const index = connections.findIndex((entry) => entry.projectName === connection.projectName);
3191
3250
  if (index === -1) {
@@ -5290,7 +5349,7 @@ async function getAdGroupInsights(apiKey, adGroupId, opts) {
5290
5349
  }
5291
5350
 
5292
5351
  // src/ads-config.ts
5293
- function ensureConnections7(config) {
5352
+ function ensureConnections8(config) {
5294
5353
  if (!config.openaiAds) config.openaiAds = {};
5295
5354
  if (!config.openaiAds.connections) config.openaiAds.connections = [];
5296
5355
  return config.openaiAds.connections;
@@ -5299,7 +5358,7 @@ function getOpenAiAdsConnection(config, projectName) {
5299
5358
  return (config.openaiAds?.connections ?? []).find((c) => c.projectName === projectName);
5300
5359
  }
5301
5360
  function upsertOpenAiAdsConnection(config, connection) {
5302
- const connections = ensureConnections7(config);
5361
+ const connections = ensureConnections8(config);
5303
5362
  const index = connections.findIndex((c) => c.projectName === connection.projectName);
5304
5363
  if (index === -1) {
5305
5364
  connections.push(connection);
@@ -6945,7 +7004,6 @@ import { createRequire as createRequire3 } from "module";
6945
7004
  import { Worker } from "worker_threads";
6946
7005
  import { and as and10, asc, count, desc as desc6, eq as eq13, isNull, ne as ne2, or, sql as sql5 } from "drizzle-orm";
6947
7006
  import { alias } from "drizzle-orm/sqlite-core";
6948
- var SITE_CRAWL_GRAPH_LAYOUT_VERSION = "site-health-fa2-v1";
6949
7007
  var SITE_CRAWL_GRAPH_LAYOUT_TIMEOUT_MS = 15e3;
6950
7008
  var SITE_CRAWL_GRAPH_SEED_SCALE = 100;
6951
7009
  var graphSourcePage = alias(siteCrawlPages, "layout_graph_source_page");
@@ -6955,20 +7013,81 @@ var FORCE_ATLAS_SETTINGS = Object.freeze({
6955
7013
  barnesHutOptimize: true,
6956
7014
  barnesHutTheta: 0.5,
6957
7015
  edgeWeightInfluence: 0.5,
6958
- gravity: 1,
6959
- linLogMode: false,
6960
- outboundAttractionDistribution: false,
6961
- scalingRatio: 10,
7016
+ gravity: 0.2,
7017
+ linLogMode: true,
7018
+ outboundAttractionDistribution: true,
7019
+ scalingRatio: 2,
6962
7020
  slowDown: 10,
6963
7021
  strongGravityMode: false
6964
7022
  });
7023
+ var RENDER_MAX_NODE_SIZE = 14;
7024
+ var RENDER_HALF_EXTENT_PX = 420;
7025
+ var NODE_SIZE_EXTENT_FRACTION = RENDER_MAX_NODE_SIZE / RENDER_HALF_EXTENT_PX;
7026
+ var NOVERLAP_SETTINGS = Object.freeze({
7027
+ /** Half a node of clear space between two touching nodes. */
7028
+ marginFactor: 0.5,
7029
+ expansion: 1.1,
7030
+ ratio: 1,
7031
+ speed: 3,
7032
+ /**
7033
+ * Node discs may claim at most this share of the graph's bounding box. At
7034
+ * the 20k cap the extent-derived size asks for spacing the graph physically
7035
+ * does not have, and noverlap either runs for minutes or overflows its
7036
+ * collision set. This bound keeps the pass solvable at every graph size,
7037
+ * and it only binds on graphs far too dense to read node-by-node anyway.
7038
+ */
7039
+ maxPackingFraction: 0.15,
7040
+ /** Cells hold a handful of nodes each, which is what keeps the pass linear. */
7041
+ gridSizeMin: 10,
7042
+ gridSizeMax: 200,
7043
+ iterationLadder: [
7044
+ { maxNodes: 1e3, iterations: 120 },
7045
+ { maxNodes: 5e3, iterations: 60 }
7046
+ ],
7047
+ iterationsFloor: 20
7048
+ });
7049
+ function noverlapGridSize(nodeCount) {
7050
+ return Math.max(
7051
+ NOVERLAP_SETTINGS.gridSizeMin,
7052
+ Math.min(NOVERLAP_SETTINGS.gridSizeMax, Math.ceil(Math.sqrt(Math.max(1, nodeCount))))
7053
+ );
7054
+ }
7055
+ function noverlapMaxIterations(nodeCount) {
7056
+ for (const step of NOVERLAP_SETTINGS.iterationLadder) {
7057
+ if (nodeCount <= step.maxNodes) return step.iterations;
7058
+ }
7059
+ return NOVERLAP_SETTINGS.iterationsFloor;
7060
+ }
7061
+ var OUTLIER_RING_INNER_FACTOR = 1.25;
7062
+ var OUTLIER_RING_OUTER_FACTOR = 1.75;
7063
+ var OUTLIER_RING_MAX_RINGS = 4;
7064
+ var OUTLIER_RING_TARGET_PER_RING = 16;
7065
+ var OUTLIER_RING_MIN_RADIUS = 1;
7066
+ function siteCrawlGraphLayoutSettingsFingerprint(settings) {
7067
+ return crypto16.createHash("sha256").update(JSON.stringify(settings)).digest("hex").slice(0, 8);
7068
+ }
7069
+ var SITE_CRAWL_GRAPH_LAYOUT_ALGORITHM = "site-health-fa2-v2";
7070
+ var SITE_CRAWL_GRAPH_LAYOUT_VERSION = `${SITE_CRAWL_GRAPH_LAYOUT_ALGORITHM}-${siteCrawlGraphLayoutSettingsFingerprint({
7071
+ forceAtlas: FORCE_ATLAS_SETTINGS,
7072
+ noverlap: NOVERLAP_SETTINGS,
7073
+ nodeSizeExtentFraction: NODE_SIZE_EXTENT_FRACTION,
7074
+ outlierRing: {
7075
+ inner: OUTLIER_RING_INNER_FACTOR,
7076
+ outer: OUTLIER_RING_OUTER_FACTOR,
7077
+ maxRings: OUTLIER_RING_MAX_RINGS,
7078
+ targetPerRing: OUTLIER_RING_TARGET_PER_RING,
7079
+ minRadius: OUTLIER_RING_MIN_RADIUS
7080
+ }
7081
+ })}`;
6965
7082
  var WORKER_SOURCE = String.raw`
6966
7083
  const {parentPort, workerData} = require('node:worker_threads');
6967
7084
  try {
6968
7085
  const graphologyModule = require(workerData.graphologyPath);
6969
7086
  const forceAtlasModule = require(workerData.forceAtlasPath);
7087
+ const noverlapModule = require(workerData.noverlapPath);
6970
7088
  const MultiDirectedGraph = graphologyModule.MultiDirectedGraph;
6971
7089
  const forceAtlas2 = forceAtlasModule.default || forceAtlasModule;
7090
+ const noverlap = noverlapModule.default || noverlapModule;
6972
7091
  const graph = new MultiDirectedGraph({allowSelfLoops: true});
6973
7092
  for (const node of workerData.input.nodes) {
6974
7093
  graph.addNode(node.nodeKey, {x: node.x, y: node.y});
@@ -6984,6 +7103,45 @@ try {
6984
7103
  settings: workerData.settings,
6985
7104
  });
6986
7105
  }
7106
+ // Anti-overlap runs on the spatialization ForceAtlas2 just produced. Node
7107
+ // size is uniform and derived from the graph's own extent, so the pass is
7108
+ // scale invariant and stays deterministic for a fixed input.
7109
+ if (graph.order > 1) {
7110
+ let sumX = 0;
7111
+ let sumY = 0;
7112
+ graph.forEachNode(function (nodeKey, attributes) {
7113
+ sumX += attributes.x;
7114
+ sumY += attributes.y;
7115
+ });
7116
+ const centerX = sumX / graph.order;
7117
+ const centerY = sumY / graph.order;
7118
+ let extent = 0;
7119
+ graph.forEachNode(function (nodeKey, attributes) {
7120
+ extent = Math.max(extent, Math.abs(attributes.x - centerX), Math.abs(attributes.y - centerY));
7121
+ });
7122
+ if (Number.isFinite(extent) && extent > 0) {
7123
+ // Whichever bound is tighter: one render-sized node, or the largest disc
7124
+ // that still leaves the graph mostly empty space at this node count.
7125
+ const byExtent = extent * workerData.nodeSizeExtentFraction;
7126
+ const byArea = Math.sqrt(
7127
+ (workerData.noverlap.maxPackingFraction * Math.pow(2 * extent, 2)) / graph.order,
7128
+ ) / 2;
7129
+ const nodeSize = Math.min(byExtent, byArea);
7130
+ graph.forEachNode(function (nodeKey) {
7131
+ graph.setNodeAttribute(nodeKey, 'size', nodeSize);
7132
+ });
7133
+ noverlap.assign(graph, {
7134
+ maxIterations: workerData.noverlap.maxIterations,
7135
+ settings: {
7136
+ gridSize: workerData.noverlap.gridSize,
7137
+ margin: nodeSize * workerData.noverlap.marginFactor,
7138
+ expansion: workerData.noverlap.expansion,
7139
+ ratio: workerData.noverlap.ratio,
7140
+ speed: workerData.noverlap.speed,
7141
+ },
7142
+ });
7143
+ }
7144
+ }
6987
7145
  const positions = [];
6988
7146
  graph.forEachNode((nodeKey, attributes) => {
6989
7147
  positions.push({nodeKey, x: attributes.x, y: attributes.y});
@@ -7054,8 +7212,15 @@ async function computeForceAtlasPositions(input, options = {}) {
7054
7212
  workerData: {
7055
7213
  graphologyPath: requireFromPackage.resolve("graphology"),
7056
7214
  forceAtlasPath: requireFromPackage.resolve("graphology-layout-forceatlas2"),
7215
+ noverlapPath: requireFromPackage.resolve("graphology-layout-noverlap"),
7057
7216
  input,
7058
- settings: FORCE_ATLAS_SETTINGS
7217
+ settings: FORCE_ATLAS_SETTINGS,
7218
+ noverlap: {
7219
+ ...NOVERLAP_SETTINGS,
7220
+ gridSize: noverlapGridSize(input.nodes.length),
7221
+ maxIterations: noverlapMaxIterations(input.nodes.length)
7222
+ },
7223
+ nodeSizeExtentFraction: NODE_SIZE_EXTENT_FRACTION
7059
7224
  }
7060
7225
  });
7061
7226
  const timeoutMs = options.timeoutMs ?? SITE_CRAWL_GRAPH_LAYOUT_TIMEOUT_MS;
@@ -7084,6 +7249,77 @@ async function computeForceAtlasPositions(input, options = {}) {
7084
7249
  });
7085
7250
  });
7086
7251
  }
7252
+ function siteCrawlGraphComponents(nodeKeys, edges) {
7253
+ const parent = new Map(nodeKeys.map((key) => [key, key]));
7254
+ const find = (key) => {
7255
+ let root = key;
7256
+ while (parent.get(root) !== root) root = parent.get(root);
7257
+ let cursor = key;
7258
+ while (parent.get(cursor) !== root) {
7259
+ const next = parent.get(cursor);
7260
+ parent.set(cursor, root);
7261
+ cursor = next;
7262
+ }
7263
+ return root;
7264
+ };
7265
+ for (const edge of edges) {
7266
+ if (!parent.has(edge.sourceNodeKey) || !parent.has(edge.targetNodeKey)) continue;
7267
+ const sourceRoot = find(edge.sourceNodeKey);
7268
+ const targetRoot = find(edge.targetNodeKey);
7269
+ if (sourceRoot === targetRoot) continue;
7270
+ if (sourceRoot < targetRoot) parent.set(targetRoot, sourceRoot);
7271
+ else parent.set(sourceRoot, targetRoot);
7272
+ }
7273
+ const members = /* @__PURE__ */ new Map();
7274
+ for (const key of [...nodeKeys].sort()) {
7275
+ const root = find(key);
7276
+ const group = members.get(root);
7277
+ if (group) group.push(key);
7278
+ else members.set(root, [key]);
7279
+ }
7280
+ let mainRoot = null;
7281
+ for (const [root, group] of members) {
7282
+ if (mainRoot === null) {
7283
+ mainRoot = root;
7284
+ continue;
7285
+ }
7286
+ const best = members.get(mainRoot);
7287
+ if (group.length > best.length || group.length === best.length && root < mainRoot) mainRoot = root;
7288
+ }
7289
+ const main = new Set(mainRoot === null ? [] : members.get(mainRoot));
7290
+ const outliers = [...members.entries()].filter(([root]) => root !== mainRoot).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).flatMap(([, group]) => group);
7291
+ return { main, outliers };
7292
+ }
7293
+ function frameDisconnectedNodes(positions, edges) {
7294
+ const { main, outliers } = siteCrawlGraphComponents(positions.map((p) => p.nodeKey), edges);
7295
+ if (outliers.length === 0 || main.size === 0) return [...positions];
7296
+ const mainPositions = positions.filter((position) => main.has(position.nodeKey));
7297
+ const centerX = mainPositions.reduce((sum, p) => sum + p.x, 0) / mainPositions.length;
7298
+ const centerY = mainPositions.reduce((sum, p) => sum + p.y, 0) / mainPositions.length;
7299
+ if (!Number.isFinite(centerX) || !Number.isFinite(centerY)) return [...positions];
7300
+ const mainRadius = Math.max(
7301
+ OUTLIER_RING_MIN_RADIUS,
7302
+ ...mainPositions.map((p) => Math.hypot(p.x - centerX, p.y - centerY)).filter(Number.isFinite)
7303
+ );
7304
+ const ringCount = Math.max(1, Math.min(
7305
+ OUTLIER_RING_MAX_RINGS,
7306
+ Math.ceil(outliers.length / OUTLIER_RING_TARGET_PER_RING)
7307
+ ));
7308
+ const placed = /* @__PURE__ */ new Map();
7309
+ for (const [index, nodeKey] of outliers.entries()) {
7310
+ const ring = index % ringCount;
7311
+ const ringRadius = mainRadius * (ringCount === 1 ? OUTLIER_RING_INNER_FACTOR : OUTLIER_RING_INNER_FACTOR + (OUTLIER_RING_OUTER_FACTOR - OUTLIER_RING_INNER_FACTOR) * (ring / (ringCount - 1)));
7312
+ const slot = Math.floor(index / ringCount);
7313
+ const slotsOnRing = Math.ceil((outliers.length - ring) / ringCount);
7314
+ const angle = (slot + ring % 2 * 0.5) / Math.max(1, slotsOnRing) * Math.PI * 2;
7315
+ placed.set(nodeKey, {
7316
+ nodeKey,
7317
+ x: centerX + Math.cos(angle) * ringRadius,
7318
+ y: centerY + Math.sin(angle) * ringRadius
7319
+ });
7320
+ }
7321
+ return positions.map((position) => placed.get(position.nodeKey) ?? position);
7322
+ }
7087
7323
  function normalizePositions(seededNodes, computed, rootNodeKey) {
7088
7324
  const byKey = new Map(computed.map((position) => [position.nodeKey, position]));
7089
7325
  if (byKey.size !== seededNodes.length) throw new Error("Graph layout returned an incomplete node set");
@@ -7131,6 +7367,7 @@ async function layoutSiteCrawlGraphInput(input, options = {}) {
7131
7367
  iterations: adaptiveForceAtlasIterations(input.nodes.length, input.edges.length)
7132
7368
  }, { signal: options.signal, timeoutMs: options.timeoutMs });
7133
7369
  options.signal?.throwIfAborted();
7370
+ const framed = frameDisconnectedNodes(positions, input.edges);
7134
7371
  return {
7135
7372
  state: "ready",
7136
7373
  layoutVersion: SITE_CRAWL_GRAPH_LAYOUT_VERSION,
@@ -7138,7 +7375,7 @@ async function layoutSiteCrawlGraphInput(input, options = {}) {
7138
7375
  totalEdges: input.totalEdges,
7139
7376
  nodeCount: input.nodes.length,
7140
7377
  edgeCount: input.edges.length,
7141
- nodes: normalizePositions(seeded, positions, input.rootNodeKey),
7378
+ nodes: normalizePositions(seeded, framed, input.rootNodeKey),
7142
7379
  edges: input.edges.map((edge, sampleRank) => ({ ...edge, sampleRank }))
7143
7380
  };
7144
7381
  } catch (error) {
@@ -8413,7 +8650,7 @@ function readStoredGroundingSources(rawResponse) {
8413
8650
  return result;
8414
8651
  }
8415
8652
  async function backfillInsightsCommand(project, opts) {
8416
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-WHC7B32V.js");
8653
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-LQ6ZRWMF.js");
8417
8654
  const config = loadConfig();
8418
8655
  const db = createClient(config.database);
8419
8656
  migrate(db);
@@ -14043,6 +14280,24 @@ async function createServer(opts) {
14043
14280
  return removed;
14044
14281
  }
14045
14282
  };
14283
+ const cloudflareTrafficCredentialStore = {
14284
+ getConnection: (projectName) => {
14285
+ return getCloudflareTrafficConnection(opts.config, projectName);
14286
+ },
14287
+ getConnectionBySourceId: (sourceId) => {
14288
+ return getCloudflareTrafficConnectionBySourceId(opts.config, sourceId);
14289
+ },
14290
+ upsertConnection: (record) => {
14291
+ const updated = upsertCloudflareTrafficConnection(opts.config, record);
14292
+ saveConfigPatch(opts.config);
14293
+ return updated;
14294
+ },
14295
+ deleteConnection: (projectName) => {
14296
+ const removed = removeCloudflareTrafficConnection(opts.config, projectName);
14297
+ if (removed) saveConfigPatch(opts.config);
14298
+ return removed;
14299
+ }
14300
+ };
14046
14301
  const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto25.randomBytes(32).toString("hex");
14047
14302
  const googleConnectionStore = {
14048
14303
  listConnections: (domain) => listGoogleConnections(opts.config, domain),
@@ -14652,6 +14907,8 @@ async function createServer(opts) {
14652
14907
  cloudRunCredentialStore,
14653
14908
  wordpressTrafficCredentialStore,
14654
14909
  vercelTrafficCredentialStore,
14910
+ cloudflareTrafficCredentialStore,
14911
+ cloudflareTrafficIngestUrl: buildCloudflareIngestUrlTemplate(opts.config),
14655
14912
  onTrafficSynced: (event) => {
14656
14913
  trackEvent(
14657
14914
  "traffic.synced",
@@ -15090,6 +15347,7 @@ export {
15090
15347
  backfillInsightsCommand,
15091
15348
  backfillSnapshotAttributionCommand,
15092
15349
  backfillTrafficClassificationCommand,
15350
+ getCloudflareTrafficConnection,
15093
15351
  setGoogleAuthConfig,
15094
15352
  installSkills,
15095
15353
  listSkills,
@@ -8561,7 +8561,24 @@ var siteCrawlSummarySchema = z24.object({
8561
8561
  counts: siteCrawlCountsSchema,
8562
8562
  deadLinks: siteCrawlDeadLinksSummarySchema
8563
8563
  });
8564
+ var siteCrawlTerminationSchema = z24.enum([
8565
+ "complete",
8566
+ "unknown",
8567
+ "max-pages",
8568
+ "max-edges",
8569
+ "max-fetches",
8570
+ "max-duration",
8571
+ "max-bytes",
8572
+ "max-page-bytes",
8573
+ "max-depth",
8574
+ "max-links-per-page",
8575
+ "max-query-variants",
8576
+ "max-sitemap-fanout",
8577
+ "max-sitemap-urls",
8578
+ "root-host-redirect"
8579
+ ]);
8564
8580
  var siteHealthStateSchema = z24.enum(["eligible", "hidden", "failed", "unchecked"]);
8581
+ var SiteHealthStates = siteHealthStateSchema.enum;
8565
8582
  var SiteCrawlFetchStates = {
8566
8583
  discovered: "discovered",
8567
8584
  robotsBlocked: "robots-blocked",
@@ -8762,6 +8779,13 @@ var siteCrawlGraphEdgeSchema = z24.object({
8762
8779
  followable: z24.boolean(),
8763
8780
  occurrences: z24.number().int().positive()
8764
8781
  });
8782
+ var siteCrawlGraphLayoutUnavailableReasonSchema = z24.enum([
8783
+ "no-crawl",
8784
+ "legacy-snapshot",
8785
+ "details-unavailable",
8786
+ "layout-failed",
8787
+ "empty-crawl"
8788
+ ]);
8765
8789
  var siteCrawlGraphLayoutSchema = z24.discriminatedUnion("state", [
8766
8790
  z24.object({
8767
8791
  state: z24.literal("ready"),
@@ -8771,13 +8795,20 @@ var siteCrawlGraphLayoutSchema = z24.discriminatedUnion("state", [
8771
8795
  z24.object({
8772
8796
  state: z24.literal("unavailable"),
8773
8797
  version: z24.null(),
8774
- reason: z24.enum(["no-crawl", "legacy-snapshot", "details-unavailable", "layout-failed", "empty-crawl"])
8798
+ reason: siteCrawlGraphLayoutUnavailableReasonSchema
8775
8799
  })
8776
8800
  ]);
8777
8801
  var siteCrawlGraphResponseSchema = z24.object({
8778
8802
  project: z24.string(),
8779
8803
  hasCrawlData: z24.boolean(),
8780
8804
  runId: z24.string().nullable(),
8805
+ /**
8806
+ * Server-owned identity of the crawl root, resolved from the snapshot root
8807
+ * URL (or the depth-0 page). Consumers must not infer the home page from a
8808
+ * path or a link score. It is null when the crawl persisted no page detail,
8809
+ * and it can name a page that graph sampling left out of `nodes`.
8810
+ */
8811
+ rootNodeKey: z24.string().nullable(),
8781
8812
  layout: siteCrawlGraphLayoutSchema,
8782
8813
  /** Total canonical pages in the persisted crawl before graph sampling. */
8783
8814
  totalNodes: z24.number().int().nonnegative(),
@@ -9020,6 +9051,20 @@ var siteAuditRunResponseSchema = z24.object({
9020
9051
  runId: z24.string(),
9021
9052
  status: runStatusSchema
9022
9053
  });
9054
+ var siteHealthScanSchema = z24.object({
9055
+ runId: z24.string(),
9056
+ status: runStatusSchema,
9057
+ createdAt: z24.string(),
9058
+ startedAt: z24.string().nullable(),
9059
+ finishedAt: z24.string().nullable(),
9060
+ hasCrawlData: z24.boolean()
9061
+ });
9062
+ var siteHealthScansResponseSchema = z24.object({
9063
+ project: z24.string(),
9064
+ scans: z24.array(siteHealthScanSchema).default([])
9065
+ });
9066
+ var SITE_HEALTH_SCANS_DEFAULT_LIMIT = 20;
9067
+ var SITE_HEALTH_SCANS_MAX_LIMIT = 100;
9023
9068
 
9024
9069
  // ../contracts/src/schedule.ts
9025
9070
  import { z as z25 } from "zod";
@@ -12745,6 +12790,71 @@ var trafficConnectVercelRequestSchema = z44.object({
12745
12790
  environment: vercelTrafficEnvironmentSchema.optional(),
12746
12791
  displayName: z44.string().min(1).optional()
12747
12792
  });
12793
+ var cloudflareTrafficDeliveryModeSchema = z44.enum(["direct-push", "queue-pull"]);
12794
+ var CloudflareTrafficDeliveryModes = cloudflareTrafficDeliveryModeSchema.enum;
12795
+ var cloudflareTrafficSourceConfigSchema = z44.object({
12796
+ schemaVersion: z44.literal(1),
12797
+ deliveryMode: z44.literal(CloudflareTrafficDeliveryModes["direct-push"]).default("direct-push"),
12798
+ /** Semver of the Worker script bundle that was generated at connect/rotate time. */
12799
+ workerVersion: z44.string().min(1),
12800
+ /** Identifier of the bot/referer keyword set baked into the deployed Worker. */
12801
+ expectedBotListVersion: z44.string().min(1),
12802
+ /** Target Cloudflare zone for the operator-managed Worker route. */
12803
+ zoneId: z44.string().nullable(),
12804
+ /** Cloudflare account id used to target the correct account during Wrangler deploy. */
12805
+ accountId: z44.string().nullable()
12806
+ });
12807
+ var trafficConnectCloudflareRequestSchema = z44.object({
12808
+ deliveryMode: z44.literal(CloudflareTrafficDeliveryModes["direct-push"]).default("direct-push"),
12809
+ displayName: z44.string().min(1).optional(),
12810
+ /** Target Cloudflare zone. Canonry does not validate it against Cloudflare. */
12811
+ zoneId: z44.string().min(1).optional(),
12812
+ /** Cloudflare account id. Set it when Wrangler can access more than one account. */
12813
+ accountId: z44.string().min(1).optional()
12814
+ });
12815
+ var trafficConnectCloudflareResponseSchema = z44.object({
12816
+ sourceId: z44.string().min(1),
12817
+ deliveryMode: z44.literal(CloudflareTrafficDeliveryModes["direct-push"]),
12818
+ workerScript: z44.string().min(1),
12819
+ wranglerToml: z44.string().min(1),
12820
+ workerVersion: z44.string().min(1),
12821
+ instructions: z44.string().min(1)
12822
+ });
12823
+ var cloudflareEdgeEventSchema = z44.object({
12824
+ /** Cloudflare `cf-ray` request id — globally unique per request. */
12825
+ eventId: z44.string().min(1),
12826
+ observedAt: z44.string().datetime(),
12827
+ method: z44.string().nullable(),
12828
+ host: z44.string().nullable(),
12829
+ path: z44.string().min(1),
12830
+ queryString: z44.string().nullable(),
12831
+ status: z44.number().int().nullable(),
12832
+ userAgent: z44.string().nullable(),
12833
+ remoteIp: z44.string().nullable(),
12834
+ referer: z44.string().nullable(),
12835
+ cf: z44.object({
12836
+ verifiedBot: z44.boolean().nullable(),
12837
+ botScore: z44.number().int().nullable(),
12838
+ country: z44.string().nullable(),
12839
+ asn: z44.number().int().nullable(),
12840
+ asOrganization: z44.string().nullable()
12841
+ }).nullable()
12842
+ });
12843
+ var cloudflareEdgeEventBatchSchema = z44.object({
12844
+ schemaVersion: z44.literal(1),
12845
+ workerVersion: z44.string().min(1),
12846
+ events: z44.array(cloudflareEdgeEventSchema).min(1).max(100)
12847
+ });
12848
+ var cloudflareWorkerIngestRequestSchema = cloudflareEdgeEventBatchSchema;
12849
+ var cloudflareWorkerIngestResponseSchema = z44.object({
12850
+ acceptedEvents: z44.number().int(),
12851
+ droppedEvents: z44.number().int(),
12852
+ workerVersionAck: z44.string(),
12853
+ crawlerBucketRows: z44.number().int(),
12854
+ aiUserFetchBucketRows: z44.number().int(),
12855
+ aiReferralBucketRows: z44.number().int(),
12856
+ sampleRows: z44.number().int()
12857
+ });
12748
12858
  var trafficSyncResponseSchema = z44.object({
12749
12859
  sourceId: z44.string(),
12750
12860
  runId: z44.string(),
@@ -14634,6 +14744,7 @@ export {
14634
14744
  siteAuditPagesResponseSchema,
14635
14745
  siteAuditTrendResponseSchema,
14636
14746
  siteCrawlSummarySchema,
14747
+ siteHealthStateSchema,
14637
14748
  SiteCrawlFetchedStates,
14638
14749
  deriveSiteHealthState,
14639
14750
  siteCrawlAuditFactorSchema,
@@ -14669,6 +14780,9 @@ export {
14669
14780
  normalizeSiteAuditRunRequest,
14670
14781
  siteAuditRequestIdentity,
14671
14782
  siteAuditRunResponseSchema,
14783
+ siteHealthScansResponseSchema,
14784
+ SITE_HEALTH_SCANS_DEFAULT_LIMIT,
14785
+ SITE_HEALTH_SCANS_MAX_LIMIT,
14672
14786
  schedulableRunKindSchema,
14673
14787
  SchedulableRunKinds,
14674
14788
  scheduleDtoSchema,
@@ -14812,6 +14926,12 @@ export {
14812
14926
  trafficConnectCloudRunRequestSchema,
14813
14927
  trafficConnectWordpressRequestSchema,
14814
14928
  trafficConnectVercelRequestSchema,
14929
+ CloudflareTrafficDeliveryModes,
14930
+ cloudflareTrafficSourceConfigSchema,
14931
+ trafficConnectCloudflareRequestSchema,
14932
+ trafficConnectCloudflareResponseSchema,
14933
+ cloudflareWorkerIngestRequestSchema,
14934
+ cloudflareWorkerIngestResponseSchema,
14815
14935
  trafficSyncResponseSchema,
14816
14936
  trafficResetRequestSchema,
14817
14937
  trafficBackfillResponseSchema,
@@ -81,7 +81,7 @@ import {
81
81
  trafficConnectWordpressRequestSchema,
82
82
  trafficEventKindSchema,
83
83
  trafficSeriesGranularitySchema
84
- } from "./chunk-AMPT2EP7.js";
84
+ } from "./chunk-DEVAEC2S.js";
85
85
 
86
86
  // src/config.ts
87
87
  import fs from "fs";
@@ -107,6 +107,14 @@ function normalizeWordpressConfig(config) {
107
107
  defaultEnv: connection.defaultEnv ?? "live"
108
108
  }));
109
109
  }
110
+ function normalizeCloudflareTrafficConfig(config) {
111
+ for (const connection of config.cloudflareTraffic?.connections ?? []) {
112
+ const legacy = connection;
113
+ if (legacy.deliveryMode === void 0) {
114
+ legacy.deliveryMode = "direct-push";
115
+ }
116
+ }
117
+ }
110
118
  function getConfigDir() {
111
119
  const override = process.env.CANONRY_CONFIG_DIR?.trim();
112
120
  if (override) {
@@ -152,6 +160,7 @@ Do not write config.yaml by hand; use "canonry init", "canonry settings", or "ca
152
160
  }
153
161
  normalizeGoogleConfig(parsed);
154
162
  normalizeWordpressConfig(parsed);
163
+ normalizeCloudflareTrafficConfig(parsed);
155
164
  const portOverride = process.env.CANONRY_PORT?.trim();
156
165
  if (portOverride) {
157
166
  try {
@@ -4695,6 +4704,22 @@ var postApiV1ProjectsByNameTrafficConnectVercel = (options) => {
4695
4704
  }
4696
4705
  });
4697
4706
  };
4707
+ var postApiV1ProjectsByNameTrafficConnectCloudflare = (options) => {
4708
+ return (options.client ?? client).post({
4709
+ security: [
4710
+ {
4711
+ scheme: "bearer",
4712
+ type: "http"
4713
+ }
4714
+ ],
4715
+ url: "/api/v1/projects/{name}/traffic/connect/cloudflare",
4716
+ ...options,
4717
+ headers: {
4718
+ "Content-Type": "application/json",
4719
+ ...options.headers
4720
+ }
4721
+ });
4722
+ };
4698
4723
  var postApiV1ProjectsByNameTrafficSourcesByIdSync = (options) => {
4699
4724
  return (options.client ?? client).post({
4700
4725
  security: [
@@ -6982,6 +7007,15 @@ var ApiClient = class {
6982
7007
  })
6983
7008
  );
6984
7009
  }
7010
+ async trafficConnectCloudflare(project, body) {
7011
+ return this.invoke(
7012
+ () => postApiV1ProjectsByNameTrafficConnectCloudflare({
7013
+ client: this.heyClient,
7014
+ path: { name: project },
7015
+ body
7016
+ })
7017
+ );
7018
+ }
6985
7019
  async trafficSync(project, sourceId, body) {
6986
7020
  return this.invoke(
6987
7021
  () => postApiV1ProjectsByNameTrafficSourcesByIdSync({