@ainyc/canonry 4.92.0 → 4.94.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 (26) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +4 -0
  2. package/assets/agent-workspace/skills/canonry/references/google-business-profile.md +6 -0
  3. package/assets/assets/{BacklinksPage-D3JpcWtH.js → BacklinksPage-FeUApIRv.js} +1 -1
  4. package/assets/assets/{ChartPrimitives-DdwqEX5v.js → ChartPrimitives-DFCM9oRt.js} +1 -1
  5. package/assets/assets/ProjectPage-Dyx8eZf_.js +6 -0
  6. package/assets/assets/{RunRow-c_2jd8iz.js → RunRow-CF4jm8w8.js} +1 -1
  7. package/assets/assets/{RunsPage-D6eM_5tB.js → RunsPage-Dlyl-dmG.js} +1 -1
  8. package/assets/assets/{SettingsPage-DaMc8nxK.js → SettingsPage-DaQ0YNHz.js} +1 -1
  9. package/assets/assets/{TrafficPage-Dw16dcSc.js → TrafficPage-38zf4QS8.js} +1 -1
  10. package/assets/assets/{TrafficSourceDetailPage-Dy9D80dz.js → TrafficSourceDetailPage-DJY18oUo.js} +1 -1
  11. package/assets/assets/{arrow-left-D8UaBG9a.js → arrow-left-CRHf1qtt.js} +1 -1
  12. package/assets/assets/{extract-error-message-CfxCIMNJ.js → extract-error-message-uWeOU6mU.js} +1 -1
  13. package/assets/assets/{index-BA-lh2pG.js → index-B7ZT1i67.js} +73 -73
  14. package/assets/assets/{index-fLUYE9Z5.css → index-DiykVUPr.css} +1 -1
  15. package/assets/assets/{trash-2-j1tX1-Kq.js → trash-2-B1qn8ZLk.js} +1 -1
  16. package/assets/index.html +2 -2
  17. package/dist/{chunk-QYC2N23P.js → chunk-3TTBKG73.js} +69 -5
  18. package/dist/{chunk-HW4GVIQD.js → chunk-K4QOSPBX.js} +37 -2
  19. package/dist/{chunk-VJPVFCZW.js → chunk-PV3RAHMK.js} +19 -0
  20. package/dist/{chunk-IFUHXONX.js → chunk-ZJADMHJ5.js} +207 -63
  21. package/dist/cli.js +57 -9
  22. package/dist/index.js +4 -4
  23. package/dist/{intelligence-service-24HCX4SJ.js → intelligence-service-M33PXIWS.js} +2 -2
  24. package/dist/mcp.js +2 -2
  25. package/package.json +9 -9
  26. package/assets/assets/ProjectPage-BPLf4U3A.js +0 -6
@@ -137,6 +137,7 @@ import {
137
137
  ga4SyncResponseDtoSchema,
138
138
  gateHarvestedSearchQueries,
139
139
  gbpAccountListResponseSchema,
140
+ gbpAttributesListResponseSchema,
140
141
  gbpDailyMetricListResponseSchema,
141
142
  gbpDiscoverRequestSchema,
142
143
  gbpKeywordImpressionListResponseSchema,
@@ -258,7 +259,7 @@ import {
258
259
  wordpressSchemaDeployResultDtoSchema,
259
260
  wordpressSchemaStatusResultDtoSchema,
260
261
  wordpressStatusDtoSchema
261
- } from "./chunk-VJPVFCZW.js";
262
+ } from "./chunk-PV3RAHMK.js";
262
263
 
263
264
  // src/intelligence-service.ts
264
265
  import { eq as eq37, desc as desc18, asc as asc5, and as and27, ne as ne5, or as or5, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
@@ -302,6 +303,7 @@ __export(schema_exports, {
302
303
  gaTrafficSnapshots: () => gaTrafficSnapshots,
303
304
  gaTrafficSummaries: () => gaTrafficSummaries,
304
305
  gaTrafficWindowSummaries: () => gaTrafficWindowSummaries,
306
+ gbpAttributesSnapshots: () => gbpAttributesSnapshots,
305
307
  gbpDailyMetrics: () => gbpDailyMetrics,
306
308
  gbpKeywordImpressions: () => gbpKeywordImpressions,
307
309
  gbpKeywordMonthly: () => gbpKeywordMonthly,
@@ -1276,6 +1278,18 @@ var gbpPlaceDetails = sqliteTable("gbp_place_details", {
1276
1278
  }, (table) => [
1277
1279
  index("idx_gbp_place_details_loc").on(table.projectId, table.locationName, table.syncedAt)
1278
1280
  ]);
1281
+ var gbpAttributesSnapshots = sqliteTable("gbp_attributes_snapshots", {
1282
+ id: text("id").primaryKey(),
1283
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
1284
+ locationName: text("location_name").notNull(),
1285
+ contentHash: text("content_hash").notNull(),
1286
+ attributes: text("attributes", { mode: "json" }).$type().notNull().default([]),
1287
+ attributeCount: integer("attribute_count").notNull().default(0),
1288
+ syncedAt: text("synced_at").notNull(),
1289
+ syncRunId: text("sync_run_id").references(() => runs.id, { onDelete: "set null" })
1290
+ }, (table) => [
1291
+ index("idx_gbp_attributes_loc").on(table.projectId, table.locationName, table.syncedAt)
1292
+ ]);
1279
1293
  var adsConnections = sqliteTable("ads_connections", {
1280
1294
  id: text("id").primaryKey(),
1281
1295
  projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
@@ -3166,6 +3180,23 @@ var MIGRATION_VERSIONS = [
3166
3180
  }
3167
3181
  }
3168
3182
  }
3183
+ },
3184
+ {
3185
+ version: 82,
3186
+ name: "gbp-attributes-snapshots",
3187
+ statements: [
3188
+ `CREATE TABLE IF NOT EXISTS gbp_attributes_snapshots (
3189
+ id TEXT PRIMARY KEY,
3190
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
3191
+ location_name TEXT NOT NULL,
3192
+ content_hash TEXT NOT NULL,
3193
+ attributes TEXT NOT NULL DEFAULT '[]',
3194
+ attribute_count INTEGER NOT NULL DEFAULT 0,
3195
+ synced_at TEXT NOT NULL,
3196
+ sync_run_id TEXT REFERENCES runs(id) ON DELETE SET NULL
3197
+ )`,
3198
+ `CREATE INDEX IF NOT EXISTS idx_gbp_attributes_loc ON gbp_attributes_snapshots(project_id, location_name, synced_at)`
3199
+ ]
3169
3200
  }
3170
3201
  ];
3171
3202
  function rebuildBacklinkTableWithSource(tx, table) {
@@ -8086,7 +8117,15 @@ function buildCategoryCounts(counts2) {
8086
8117
  }
8087
8118
 
8088
8119
  // ../api-routes/src/intelligence.ts
8089
- import { eq as eq11, desc as desc4, and as and6, inArray as inArray4 } from "drizzle-orm";
8120
+ import { eq as eq11, desc as desc4, and as and6, inArray as inArray4, like } from "drizzle-orm";
8121
+ var SEVERITY_RANK = { low: 0, medium: 1, high: 2, critical: 3 };
8122
+ function severitiesAtOrAbove(min) {
8123
+ const floor = SEVERITY_RANK[min];
8124
+ if (floor === void 0) {
8125
+ throw validationError(`Invalid severity "${min}". Use one of: low, medium, high, critical.`);
8126
+ }
8127
+ return Object.keys(SEVERITY_RANK).filter((s) => SEVERITY_RANK[s] >= floor);
8128
+ }
8090
8129
  function emptyHealthSnapshot(projectId) {
8091
8130
  return {
8092
8131
  id: `no-data:${projectId}`,
@@ -8200,9 +8239,28 @@ async function intelligenceRoutes(app) {
8200
8239
  if (request.query.runId) {
8201
8240
  conditions.push(eq11(insights.runId, request.query.runId));
8202
8241
  }
8242
+ const typeFilter = request.query.type?.trim();
8243
+ if (typeFilter) {
8244
+ conditions.push(
8245
+ typeFilter.endsWith("*") ? like(insights.type, `${typeFilter.slice(0, -1)}%`) : eq11(insights.type, typeFilter)
8246
+ );
8247
+ }
8248
+ const severityFilter = request.query.severity?.trim();
8249
+ if (severityFilter) {
8250
+ conditions.push(inArray4(insights.severity, severitiesAtOrAbove(severityFilter)));
8251
+ }
8252
+ let limit;
8253
+ if (request.query.limit !== void 0) {
8254
+ const parsed = Number(request.query.limit);
8255
+ if (!Number.isInteger(parsed) || parsed < 1) {
8256
+ throw validationError(`Invalid limit "${request.query.limit}". Use a positive integer.`);
8257
+ }
8258
+ limit = parsed;
8259
+ }
8203
8260
  const rows = app.db.select().from(insights).where(conditions.length === 1 ? conditions[0] : and6(...conditions)).orderBy(desc4(insights.createdAt)).all();
8204
8261
  const showDismissed = request.query.dismissed === "true";
8205
- const result = rows.filter((r) => showDismissed || !r.dismissed).map(mapInsightRow);
8262
+ let result = rows.filter((r) => showDismissed || !r.dismissed).map(mapInsightRow);
8263
+ if (limit !== void 0) result = result.slice(0, limit);
8206
8264
  return reply.send(result);
8207
8265
  });
8208
8266
  app.get("/projects/:name/insights/:id", async (request, reply) => {
@@ -13019,7 +13077,7 @@ async function visibilityStatsRoutes(app) {
13019
13077
  }
13020
13078
 
13021
13079
  // ../api-routes/src/composites.ts
13022
- import { eq as eq17, and as and12, desc as desc9, sql as sql7, like, or as or4, inArray as inArray9 } from "drizzle-orm";
13080
+ import { eq as eq17, and as and12, desc as desc9, sql as sql7, like as like2, or as or4, inArray as inArray9 } from "drizzle-orm";
13023
13081
  var TOP_INSIGHT_LIMIT = 5;
13024
13082
  var SEARCH_HIT_HARD_LIMIT = 50;
13025
13083
  var SEARCH_SNIPPET_RADIUS = 80;
@@ -13199,7 +13257,7 @@ async function compositeRoutes(app) {
13199
13257
  sql7`${querySnapshots.answerText} LIKE ${pattern} ESCAPE '\\'`,
13200
13258
  sql7`${querySnapshots.citedDomains} LIKE ${pattern} ESCAPE '\\'`,
13201
13259
  sql7`${querySnapshots.rawResponse} LIKE ${pattern} ESCAPE '\\'`,
13202
- like(queries.query, pattern)
13260
+ like2(queries.query, pattern)
13203
13261
  )
13204
13262
  )
13205
13263
  ).orderBy(desc9(querySnapshots.createdAt)).limit(limit + 1).all());
@@ -13207,8 +13265,8 @@ async function compositeRoutes(app) {
13207
13265
  and12(
13208
13266
  eq17(insights.projectId, project.id),
13209
13267
  or4(
13210
- like(insights.title, pattern),
13211
- like(insights.query, pattern),
13268
+ like2(insights.title, pattern),
13269
+ like2(insights.query, pattern),
13212
13270
  sql7`${insights.recommendation} LIKE ${pattern} ESCAPE '\\'`,
13213
13271
  sql7`${insights.cause} LIKE ${pattern} ESCAPE '\\'`
13214
13272
  )
@@ -13778,6 +13836,7 @@ var SCHEMA_TABLE = {
13778
13836
  GbpLocationDto: gbpLocationDtoSchema,
13779
13837
  GbpLocationListResponse: gbpLocationListResponseSchema,
13780
13838
  GbpLodgingListResponse: gbpLodgingListResponseSchema,
13839
+ GbpAttributesListResponse: gbpAttributesListResponseSchema,
13781
13840
  GbpPlaceActionListResponse: gbpPlaceActionListResponseSchema,
13782
13841
  GbpPlaceDetailsListResponse: gbpPlaceDetailsListResponseSchema,
13783
13842
  GbpSummaryDto: gbpSummaryDtoSchema,
@@ -15791,6 +15850,20 @@ var routeCatalog = [
15791
15850
  404: errorResponse("Project not found.")
15792
15851
  }
15793
15852
  },
15853
+ {
15854
+ method: "get",
15855
+ path: "/api/v1/projects/{name}/gbp/attributes",
15856
+ summary: "List latest Google Business Profile owner-set attribute snapshots per location",
15857
+ tags: ["gbp"],
15858
+ parameters: [
15859
+ nameParameter,
15860
+ { in: "query", name: "locationName", required: false, description: "Filter to one location resource name", schema: stringSchema }
15861
+ ],
15862
+ responses: {
15863
+ 200: jsonResponse("Attribute snapshots returned.", "GbpAttributesListResponse"),
15864
+ 404: errorResponse("Project not found.")
15865
+ }
15866
+ },
15794
15867
  {
15795
15868
  method: "get",
15796
15869
  path: "/api/v1/projects/{name}/gbp/places",
@@ -16719,7 +16792,10 @@ var routeCatalog = [
16719
16792
  parameters: [
16720
16793
  nameParameter,
16721
16794
  { name: "dismissed", in: "query", description: "Include dismissed insights (true/false).", schema: stringSchema },
16722
- { name: "runId", in: "query", description: "Filter by run ID.", schema: stringSchema }
16795
+ { name: "runId", in: "query", description: "Filter by run ID.", schema: stringSchema },
16796
+ { name: "type", in: "query", description: "Filter by insight type. Exact match, or a trailing `*` for a prefix (e.g. `gbp-*`).", schema: stringSchema },
16797
+ { name: "severity", in: "query", description: "Minimum severity (low|medium|high|critical); e.g. `high` returns high + critical.", schema: stringSchema },
16798
+ { name: "limit", in: "query", description: "Cap the number of (newest-first) insights returned.", schema: stringSchema }
16723
16799
  ],
16724
16800
  responses: {
16725
16801
  // TODO: Add `InsightDto` Zod schema in contracts.
@@ -18529,7 +18605,7 @@ function formatNotification(row) {
18529
18605
  }
18530
18606
 
18531
18607
  // ../api-routes/src/google.ts
18532
- import crypto17 from "crypto";
18608
+ import crypto18 from "crypto";
18533
18609
  import { eq as eq21, and as and14, desc as desc11, sql as sql8, inArray as inArray10 } from "drizzle-orm";
18534
18610
 
18535
18611
  // ../api-routes/src/gbp-summary.ts
@@ -20082,6 +20158,47 @@ function stableStringify2(value) {
20082
20158
  return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify2(obj[k])}`).join(",")}}`;
20083
20159
  }
20084
20160
 
20161
+ // ../integration-google-business-profile/src/attributes-client.ts
20162
+ import crypto17 from "crypto";
20163
+ async function getAttributes(accessToken, locationName, opts = {}) {
20164
+ const url = `${GBP_BUSINESS_INFO_BASE}/${locationName}/attributes`;
20165
+ let res;
20166
+ try {
20167
+ res = await gbpFetchGet(url, accessToken, opts);
20168
+ } catch (err) {
20169
+ if (err instanceof GbpApiError && err.status === 404) return [];
20170
+ throw err;
20171
+ }
20172
+ return (res.attributes ?? []).map(normalizeAttribute).filter((a) => a.name.length > 0);
20173
+ }
20174
+ function normalizeAttribute(raw) {
20175
+ const values = [];
20176
+ for (const v of raw.values ?? []) {
20177
+ if (typeof v === "boolean" || typeof v === "string") values.push(v);
20178
+ }
20179
+ for (const s of raw.repeatedEnumValue?.setValues ?? []) values.push(s);
20180
+ const unsetValues = [...raw.repeatedEnumValue?.unsetValues ?? []];
20181
+ const uris = [];
20182
+ for (const u of raw.uriValues ?? []) {
20183
+ if (u.uri) uris.push(u.uri);
20184
+ }
20185
+ return { name: raw.name ?? "", valueType: raw.valueType ?? "", values, unsetValues, uris };
20186
+ }
20187
+ function countAttributes(attrs) {
20188
+ return attrs.length;
20189
+ }
20190
+ function hashAttributes(attrs) {
20191
+ const sorted = [...attrs].sort((a, b) => a.name.localeCompare(b.name));
20192
+ return crypto17.createHash("sha256").update(stableStringify3(sorted)).digest("hex");
20193
+ }
20194
+ function stableStringify3(value) {
20195
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
20196
+ if (Array.isArray(value)) return `[${value.map(stableStringify3).join(",")}]`;
20197
+ const obj = value;
20198
+ const keys = Object.keys(obj).sort();
20199
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify3(obj[k])}`).join(",")}}`;
20200
+ }
20201
+
20085
20202
  // ../api-routes/src/google.ts
20086
20203
  function scopesForConnectionType(type) {
20087
20204
  switch (type) {
@@ -20094,7 +20211,7 @@ function scopesForConnectionType(type) {
20094
20211
  }
20095
20212
  }
20096
20213
  function signState(payload, secret) {
20097
- return crypto17.createHmac("sha256", secret).update(payload).digest("hex");
20214
+ return crypto18.createHmac("sha256", secret).update(payload).digest("hex");
20098
20215
  }
20099
20216
  function buildSignedState(data, secret) {
20100
20217
  const payload = JSON.stringify(data);
@@ -20105,7 +20222,7 @@ function verifySignedState(encoded, secret) {
20105
20222
  try {
20106
20223
  const { payload, sig } = JSON.parse(Buffer.from(encoded, "base64url").toString());
20107
20224
  const expected = signState(payload, secret);
20108
- if (!crypto17.timingSafeEqual(Buffer.from(sig, "hex"), Buffer.from(expected, "hex"))) return null;
20225
+ if (!crypto18.timingSafeEqual(Buffer.from(sig, "hex"), Buffer.from(expected, "hex"))) return null;
20109
20226
  return JSON.parse(payload);
20110
20227
  } catch {
20111
20228
  return null;
@@ -20441,7 +20558,7 @@ async function googleRoutes(app, opts) {
20441
20558
  throw validationError('No GSC connection found for this domain. Run "canonry google connect" first.');
20442
20559
  }
20443
20560
  const now = (/* @__PURE__ */ new Date()).toISOString();
20444
- const runId = crypto17.randomUUID();
20561
+ const runId = crypto18.randomUUID();
20445
20562
  app.db.insert(runs).values({
20446
20563
  id: runId,
20447
20564
  projectId: project.id,
@@ -20539,7 +20656,7 @@ async function googleRoutes(app, opts) {
20539
20656
  const mob = ir.mobileUsabilityResult;
20540
20657
  const rich = ir.richResultsResult;
20541
20658
  const now = (/* @__PURE__ */ new Date()).toISOString();
20542
- const id = crypto17.randomUUID();
20659
+ const id = crypto18.randomUUID();
20543
20660
  app.db.insert(gscUrlInspections).values({
20544
20661
  id,
20545
20662
  projectId: project.id,
@@ -20783,7 +20900,7 @@ async function googleRoutes(app, opts) {
20783
20900
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
20784
20901
  });
20785
20902
  const now = (/* @__PURE__ */ new Date()).toISOString();
20786
- const runId = crypto17.randomUUID();
20903
+ const runId = crypto18.randomUUID();
20787
20904
  app.db.insert(runs).values({
20788
20905
  id: runId,
20789
20906
  projectId: project.id,
@@ -20809,7 +20926,7 @@ async function googleRoutes(app, opts) {
20809
20926
  throw validationError("No GSC property configured for this connection");
20810
20927
  }
20811
20928
  const now = (/* @__PURE__ */ new Date()).toISOString();
20812
- const runId = crypto17.randomUUID();
20929
+ const runId = crypto18.randomUUID();
20813
20930
  app.db.insert(runs).values({
20814
20931
  id: runId,
20815
20932
  projectId: project.id,
@@ -21090,7 +21207,7 @@ async function googleRoutes(app, opts) {
21090
21207
  }).where(eq21(gbpLocations.id, existing.id)).run();
21091
21208
  } else {
21092
21209
  tx.insert(gbpLocations).values({
21093
- id: crypto17.randomUUID(),
21210
+ id: crypto18.randomUUID(),
21094
21211
  projectId: project.id,
21095
21212
  accountName,
21096
21213
  locationName: remote.name,
@@ -21214,7 +21331,7 @@ async function googleRoutes(app, opts) {
21214
21331
  throw validationError(parsed.error.issues[0]?.message ?? "Invalid sync request");
21215
21332
  }
21216
21333
  const now = (/* @__PURE__ */ new Date()).toISOString();
21217
- const runId = crypto17.randomUUID();
21334
+ const runId = crypto18.randomUUID();
21218
21335
  app.db.insert(runs).values({
21219
21336
  id: runId,
21220
21337
  projectId: project.id,
@@ -21291,6 +21408,26 @@ async function googleRoutes(app, opts) {
21291
21408
  }));
21292
21409
  return { lodging, total: lodging.length };
21293
21410
  });
21411
+ app.get("/projects/:name/gbp/attributes", async (request) => {
21412
+ const project = resolveProject(app.db, request.params.name);
21413
+ const conditions = [eq21(gbpAttributesSnapshots.projectId, project.id)];
21414
+ if (request.query.locationName) conditions.push(eq21(gbpAttributesSnapshots.locationName, request.query.locationName));
21415
+ const rows = app.db.select().from(gbpAttributesSnapshots).where(and14(...conditions)).orderBy(desc11(gbpAttributesSnapshots.syncedAt)).all();
21416
+ const latestByLocation = /* @__PURE__ */ new Map();
21417
+ for (const row of rows) {
21418
+ if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
21419
+ }
21420
+ const attributes = [...latestByLocation.values()].map((r) => ({
21421
+ locationName: r.locationName,
21422
+ attributeCount: r.attributeCount,
21423
+ syncedAt: r.syncedAt,
21424
+ attributes: r.attributes.map((attr) => ({
21425
+ ...attr,
21426
+ unsetValues: attr.unsetValues ?? []
21427
+ }))
21428
+ }));
21429
+ return { attributes, total: attributes.length };
21430
+ });
21294
21431
  app.get("/projects/:name/gbp/places", async (request) => {
21295
21432
  const project = resolveProject(app.db, request.params.name);
21296
21433
  const conditions = [eq21(gbpPlaceDetails.projectId, project.id)];
@@ -21360,7 +21497,7 @@ async function googleRoutes(app, opts) {
21360
21497
  }
21361
21498
 
21362
21499
  // ../api-routes/src/ads.ts
21363
- import crypto18 from "crypto";
21500
+ import crypto19 from "crypto";
21364
21501
  import { eq as eq22, and as and15, asc as asc2, gte as gte3, lte as lte2, inArray as inArray11 } from "drizzle-orm";
21365
21502
  function statusDto(row) {
21366
21503
  if (!row) return { connected: false };
@@ -21423,7 +21560,7 @@ async function adsRoutes(app, opts) {
21423
21560
  }).where(eq22(adsConnections.id, existingRow.id)).run();
21424
21561
  } else {
21425
21562
  tx.insert(adsConnections).values({
21426
- id: crypto18.randomUUID(),
21563
+ id: crypto19.randomUUID(),
21427
21564
  projectId: project.id,
21428
21565
  adAccountId: account.id,
21429
21566
  displayName: account.name,
@@ -21485,7 +21622,7 @@ async function adsRoutes(app, opts) {
21485
21622
  const existing = { runId: inFlight.id, status: inFlight.status };
21486
21623
  return existing;
21487
21624
  }
21488
- const runId = crypto18.randomUUID();
21625
+ const runId = crypto19.randomUUID();
21489
21626
  app.db.insert(runs).values({
21490
21627
  id: runId,
21491
21628
  projectId: project.id,
@@ -21620,7 +21757,7 @@ async function adsRoutes(app, opts) {
21620
21757
  }
21621
21758
 
21622
21759
  // ../api-routes/src/bing.ts
21623
- import crypto19 from "crypto";
21760
+ import crypto20 from "crypto";
21624
21761
  import { eq as eq23, and as and16, desc as desc12 } from "drizzle-orm";
21625
21762
 
21626
21763
  // ../integration-bing/src/constants.ts
@@ -22052,7 +22189,7 @@ async function bingRoutes(app, opts) {
22052
22189
  const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
22053
22190
  const now = (/* @__PURE__ */ new Date()).toISOString();
22054
22191
  app.db.insert(bingCoverageSnapshots).values({
22055
- id: crypto19.randomUUID(),
22192
+ id: crypto20.randomUUID(),
22056
22193
  projectId: project.id,
22057
22194
  syncRunId: snapshotRunId,
22058
22195
  date: snapshotDate,
@@ -22123,7 +22260,7 @@ async function bingRoutes(app, opts) {
22123
22260
  throw validationError("url is required");
22124
22261
  }
22125
22262
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
22126
- const runId = crypto19.randomUUID();
22263
+ const runId = crypto20.randomUUID();
22127
22264
  app.db.insert(runs).values({
22128
22265
  id: runId,
22129
22266
  projectId: project.id,
@@ -22144,7 +22281,7 @@ async function bingRoutes(app, opts) {
22144
22281
  discoveryDate: result.DiscoveryDate ?? null
22145
22282
  });
22146
22283
  const now = (/* @__PURE__ */ new Date()).toISOString();
22147
- const id = crypto19.randomUUID();
22284
+ const id = crypto20.randomUUID();
22148
22285
  const httpCode = result.HttpStatus ?? result.HttpCode ?? null;
22149
22286
  const lastCrawledDate = parseBingDate(result.LastCrawledDate);
22150
22287
  const inIndexDate = parseBingDate(result.InIndexDate);
@@ -22214,7 +22351,7 @@ async function bingRoutes(app, opts) {
22214
22351
  throw validationError('No Bing site configured. Run "canonry bing set-site <project> <url>" first.');
22215
22352
  }
22216
22353
  const now = (/* @__PURE__ */ new Date()).toISOString();
22217
- const runId = crypto19.randomUUID();
22354
+ const runId = crypto20.randomUUID();
22218
22355
  app.db.insert(runs).values({
22219
22356
  id: runId,
22220
22357
  projectId: project.id,
@@ -22498,7 +22635,7 @@ async function cdpRoutes(app, opts) {
22498
22635
  }
22499
22636
 
22500
22637
  // ../api-routes/src/ga.ts
22501
- import crypto20 from "crypto";
22638
+ import crypto21 from "crypto";
22502
22639
  import { eq as eq25, desc as desc13, and as and18, sql as sql9 } from "drizzle-orm";
22503
22640
  function gaLog(level, action, ctx) {
22504
22641
  const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
@@ -22754,7 +22891,7 @@ async function ga4Routes(app, opts) {
22754
22891
  const syncAi = !only || only === "ai";
22755
22892
  const syncSocial = !only || only === "social";
22756
22893
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
22757
- const runId = crypto20.randomUUID();
22894
+ const runId = crypto21.randomUUID();
22758
22895
  app.db.insert(runs).values({
22759
22896
  id: runId,
22760
22897
  projectId: project.id,
@@ -22802,7 +22939,7 @@ async function ga4Routes(app, opts) {
22802
22939
  ).run();
22803
22940
  for (const row of rows) {
22804
22941
  tx.insert(gaTrafficSnapshots).values({
22805
- id: crypto20.randomUUID(),
22942
+ id: crypto21.randomUUID(),
22806
22943
  projectId: project.id,
22807
22944
  date: row.date,
22808
22945
  landingPage: row.landingPage,
@@ -22826,7 +22963,7 @@ async function ga4Routes(app, opts) {
22826
22963
  ).run();
22827
22964
  for (const row of aiReferrals) {
22828
22965
  tx.insert(gaAiReferrals).values({
22829
- id: crypto20.randomUUID(),
22966
+ id: crypto21.randomUUID(),
22830
22967
  projectId: project.id,
22831
22968
  date: row.date,
22832
22969
  source: row.source,
@@ -22852,7 +22989,7 @@ async function ga4Routes(app, opts) {
22852
22989
  ).run();
22853
22990
  for (const row of socialReferrals) {
22854
22991
  tx.insert(gaSocialReferrals).values({
22855
- id: crypto20.randomUUID(),
22992
+ id: crypto21.randomUUID(),
22856
22993
  projectId: project.id,
22857
22994
  date: row.date,
22858
22995
  source: row.source,
@@ -22868,7 +23005,7 @@ async function ga4Routes(app, opts) {
22868
23005
  if (syncSummary) {
22869
23006
  tx.delete(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).run();
22870
23007
  tx.insert(gaTrafficSummaries).values({
22871
- id: crypto20.randomUUID(),
23008
+ id: crypto21.randomUUID(),
22872
23009
  projectId: project.id,
22873
23010
  periodStart: summary.periodStart,
22874
23011
  periodEnd: summary.periodEnd,
@@ -22881,7 +23018,7 @@ async function ga4Routes(app, opts) {
22881
23018
  tx.delete(gaTrafficWindowSummaries).where(eq25(gaTrafficWindowSummaries.projectId, project.id)).run();
22882
23019
  for (const ws of windowSummaries) {
22883
23020
  tx.insert(gaTrafficWindowSummaries).values({
22884
- id: crypto20.randomUUID(),
23021
+ id: crypto21.randomUUID(),
22885
23022
  projectId: project.id,
22886
23023
  windowKey: ws.windowKey,
22887
23024
  periodStart: ws.periodStart,
@@ -23452,7 +23589,7 @@ function parseSchemaPageEntry(entry) {
23452
23589
  }
23453
23590
 
23454
23591
  // ../integration-wordpress/src/wordpress-client.ts
23455
- import crypto21 from "crypto";
23592
+ import crypto22 from "crypto";
23456
23593
  function validateUsername(username) {
23457
23594
  if (!username || typeof username !== "string" || username.trim().length === 0) {
23458
23595
  throw new WordpressApiError("AUTH_INVALID", "Username is required and must be a non-empty string", 400);
@@ -23665,7 +23802,7 @@ function buildSnippet(content) {
23665
23802
  return `${text2.slice(0, 157)}...`;
23666
23803
  }
23667
23804
  function contentHash(content) {
23668
- return crypto21.createHash("sha256").update(content).digest("hex");
23805
+ return crypto22.createHash("sha256").update(content).digest("hex");
23669
23806
  }
23670
23807
  function buildAmbiguousSlugMessage(slug, pages) {
23671
23808
  const candidates = pages.map((page) => {
@@ -24965,7 +25102,7 @@ async function wordpressRoutes(app, opts) {
24965
25102
  }
24966
25103
 
24967
25104
  // ../api-routes/src/backlinks.ts
24968
- import crypto22 from "crypto";
25105
+ import crypto23 from "crypto";
24969
25106
  import { and as and20, asc as asc3, desc as desc14, eq as eq26, sql as sql10 } from "drizzle-orm";
24970
25107
 
24971
25108
  // ../integration-commoncrawl/src/constants.ts
@@ -25603,7 +25740,7 @@ async function backlinksRoutes(app, opts) {
25603
25740
  const refreshed = app.db.select().from(ccReleaseSyncs).where(eq26(ccReleaseSyncs.id, existing.id)).get();
25604
25741
  return reply.status(200).send(mapSyncRow(refreshed));
25605
25742
  }
25606
- const id = crypto22.randomUUID();
25743
+ const id = crypto23.randomUUID();
25607
25744
  app.db.insert(ccReleaseSyncs).values({
25608
25745
  id,
25609
25746
  release,
@@ -25660,7 +25797,7 @@ async function backlinksRoutes(app, opts) {
25660
25797
  throw validationError("Invalid release id");
25661
25798
  }
25662
25799
  const now = (/* @__PURE__ */ new Date()).toISOString();
25663
- const runId = crypto22.randomUUID();
25800
+ const runId = crypto23.randomUUID();
25664
25801
  app.db.insert(runs).values({
25665
25802
  id: runId,
25666
25803
  projectId: project.id,
@@ -25762,7 +25899,7 @@ async function backlinksRoutes(app, opts) {
25762
25899
  );
25763
25900
  }
25764
25901
  const now = (/* @__PURE__ */ new Date()).toISOString();
25765
- const runId = crypto22.randomUUID();
25902
+ const runId = crypto23.randomUUID();
25766
25903
  app.db.insert(runs).values({
25767
25904
  id: runId,
25768
25905
  projectId: project.id,
@@ -25779,12 +25916,12 @@ async function backlinksRoutes(app, opts) {
25779
25916
  }
25780
25917
 
25781
25918
  // ../api-routes/src/traffic.ts
25782
- import crypto24 from "crypto";
25919
+ import crypto25 from "crypto";
25783
25920
  import { Agent as UndiciAgent } from "undici";
25784
25921
  import { and as and21, desc as desc15, eq as eq27, gte as gte4, lte as lte3, sql as sql11 } from "drizzle-orm";
25785
25922
 
25786
25923
  // ../integration-cloud-run/src/auth.ts
25787
- import crypto23 from "crypto";
25924
+ import crypto24 from "crypto";
25788
25925
  var GOOGLE_TOKEN_URL3 = "https://oauth2.googleapis.com/token";
25789
25926
  var CLOUD_LOGGING_READ_SCOPE = "https://www.googleapis.com/auth/logging.read";
25790
25927
  var TOKEN_REQUEST_TIMEOUT_MS = 3e4;
@@ -25815,7 +25952,7 @@ function createServiceAccountJwt2(clientEmail, privateKey, scope) {
25815
25952
  const headerB64 = encode(header);
25816
25953
  const payloadB64 = encode(payload);
25817
25954
  const signingInput = `${headerB64}.${payloadB64}`;
25818
- const sign = crypto23.createSign("RSA-SHA256");
25955
+ const sign = crypto24.createSign("RSA-SHA256");
25819
25956
  sign.update(signingInput);
25820
25957
  const signature = sign.sign(privateKey, "base64url");
25821
25958
  return `${signingInput}.${signature}`;
@@ -29723,7 +29860,7 @@ async function runBackfillTask(options) {
29723
29860
  }
29724
29861
  })();
29725
29862
  tx.insert(rawEventSamples).values({
29726
- id: crypto24.randomUUID(),
29863
+ id: crypto25.randomUUID(),
29727
29864
  projectId: project.id,
29728
29865
  sourceId: sourceRow.id,
29729
29866
  ts: sample.observedAt,
@@ -29849,7 +29986,7 @@ async function trafficRoutes(app, opts) {
29849
29986
  }).where(eq27(trafficSources.id, activeSource.id)).run();
29850
29987
  sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, activeSource.id)).get();
29851
29988
  } else {
29852
- const newId = crypto24.randomUUID();
29989
+ const newId = crypto25.randomUUID();
29853
29990
  app.db.insert(trafficSources).values({
29854
29991
  id: newId,
29855
29992
  projectId: project.id,
@@ -29930,7 +30067,7 @@ async function trafficRoutes(app, opts) {
29930
30067
  }).where(eq27(trafficSources.id, activeSource.id)).run();
29931
30068
  sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, activeSource.id)).get();
29932
30069
  } else {
29933
- const newId = crypto24.randomUUID();
30070
+ const newId = crypto25.randomUUID();
29934
30071
  app.db.insert(trafficSources).values({
29935
30072
  id: newId,
29936
30073
  projectId: project.id,
@@ -30014,7 +30151,7 @@ async function trafficRoutes(app, opts) {
30014
30151
  }).where(eq27(trafficSources.id, activeSource.id)).run();
30015
30152
  row = tx.select().from(trafficSources).where(eq27(trafficSources.id, activeSource.id)).get();
30016
30153
  } else {
30017
- const newId = crypto24.randomUUID();
30154
+ const newId = crypto25.randomUUID();
30018
30155
  tx.insert(trafficSources).values({
30019
30156
  id: newId,
30020
30157
  projectId: project.id,
@@ -30048,7 +30185,7 @@ async function trafficRoutes(app, opts) {
30048
30185
  let created = false;
30049
30186
  if (!existingSchedule) {
30050
30187
  tx.insert(schedules).values({
30051
- id: crypto24.randomUUID(),
30188
+ id: crypto25.randomUUID(),
30052
30189
  projectId: project.id,
30053
30190
  kind: SchedulableRunKinds["traffic-sync"],
30054
30191
  cronExpr: DEFAULT_TRAFFIC_SYNC_CRON,
@@ -30103,7 +30240,7 @@ async function trafficRoutes(app, opts) {
30103
30240
  const windowEnd = /* @__PURE__ */ new Date();
30104
30241
  const startedAt = windowEnd.toISOString();
30105
30242
  const syncStartedAtMs = windowEnd.getTime();
30106
- const runId = crypto24.randomUUID();
30243
+ const runId = crypto25.randomUUID();
30107
30244
  app.db.insert(runs).values({
30108
30245
  id: runId,
30109
30246
  projectId: project.id,
@@ -30486,7 +30623,7 @@ async function trafficRoutes(app, opts) {
30486
30623
  }
30487
30624
  })();
30488
30625
  tx.insert(rawEventSamples).values({
30489
- id: crypto24.randomUUID(),
30626
+ id: crypto25.randomUUID(),
30490
30627
  projectId: project.id,
30491
30628
  sourceId: sourceRow.id,
30492
30629
  ts: sample.observedAt,
@@ -30724,7 +30861,7 @@ async function trafficRoutes(app, opts) {
30724
30861
  };
30725
30862
  }
30726
30863
  const startedAt = windowEnd.toISOString();
30727
- const runId = crypto24.randomUUID();
30864
+ const runId = crypto25.randomUUID();
30728
30865
  app.db.insert(runs).values({
30729
30866
  id: runId,
30730
30867
  projectId: project.id,
@@ -31025,7 +31162,7 @@ async function trafficRoutes(app, opts) {
31025
31162
  }
31026
31163
 
31027
31164
  // ../api-routes/src/doctor/checks/agent.ts
31028
- import crypto25 from "crypto";
31165
+ import crypto26 from "crypto";
31029
31166
  import fs6 from "fs";
31030
31167
  import path7 from "path";
31031
31168
  var REQUIRED_SKILLS = ["canonry", "aero"];
@@ -31178,7 +31315,7 @@ function isInstalled(dir) {
31178
31315
  }
31179
31316
  function hashInstalledFile(filePath) {
31180
31317
  try {
31181
- return crypto25.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
31318
+ return crypto26.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
31182
31319
  } catch {
31183
31320
  return void 0;
31184
31321
  }
@@ -33080,7 +33217,7 @@ async function doctorRoutes(app, opts) {
33080
33217
  }
33081
33218
 
33082
33219
  // ../api-routes/src/discovery/routes.ts
33083
- import crypto26 from "crypto";
33220
+ import crypto27 from "crypto";
33084
33221
  import { and as and25, desc as desc16, eq as eq34, gte as gte6, inArray as inArray12 } from "drizzle-orm";
33085
33222
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
33086
33223
  async function discoveryRoutes(app, opts) {
@@ -33126,8 +33263,8 @@ async function discoveryRoutes(app, opts) {
33126
33263
  if (existing && existing.runId) {
33127
33264
  return { reused: true, sessionId: existing.id, runId: existing.runId };
33128
33265
  }
33129
- const sessionId = crypto26.randomUUID();
33130
- const runId = crypto26.randomUUID();
33266
+ const sessionId = crypto27.randomUUID();
33267
+ const runId = crypto27.randomUUID();
33131
33268
  tx.insert(discoverySessions).values({
33132
33269
  id: sessionId,
33133
33270
  projectId: project.id,
@@ -33375,7 +33512,7 @@ async function discoveryRoutes(app, opts) {
33375
33512
  app.db.transaction((tx) => {
33376
33513
  for (const query of promotedQueries) {
33377
33514
  tx.insert(queries).values({
33378
- id: crypto26.randomUUID(),
33515
+ id: crypto27.randomUUID(),
33379
33516
  projectId: project.id,
33380
33517
  query,
33381
33518
  provenance,
@@ -33384,7 +33521,7 @@ async function discoveryRoutes(app, opts) {
33384
33521
  }
33385
33522
  for (const domain of promotedCompetitors) {
33386
33523
  tx.insert(competitors).values({
33387
- id: crypto26.randomUUID(),
33524
+ id: crypto27.randomUUID(),
33388
33525
  projectId: project.id,
33389
33526
  domain,
33390
33527
  provenance,
@@ -33462,7 +33599,7 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
33462
33599
  }
33463
33600
 
33464
33601
  // ../api-routes/src/discovery/orchestrate.ts
33465
- import crypto27 from "crypto";
33602
+ import crypto28 from "crypto";
33466
33603
  import { eq as eq35 } from "drizzle-orm";
33467
33604
  var DEFAULT_MAX_PROBES = 100;
33468
33605
  var ABSOLUTE_MAX_PROBES = 500;
@@ -33556,7 +33693,7 @@ async function executeDiscovery(opts) {
33556
33693
  probeRows.push({ citedDomains: probe.citedDomains, bucket });
33557
33694
  buckets[bucket]++;
33558
33695
  opts.db.insert(discoveryProbes).values({
33559
- id: crypto27.randomUUID(),
33696
+ id: crypto28.randomUUID(),
33560
33697
  sessionId: opts.sessionId,
33561
33698
  projectId: opts.project.id,
33562
33699
  query,
@@ -33601,7 +33738,7 @@ function upsertDomainClassifications(db, projectId, sessionId, competitorMap) {
33601
33738
  const domain = normalizeDomain(entry.domain);
33602
33739
  if (!domain) continue;
33603
33740
  db.insert(domainClassifications).values({
33604
- id: crypto27.randomUUID(),
33741
+ id: crypto28.randomUUID(),
33605
33742
  projectId,
33606
33743
  domain,
33607
33744
  competitorType: entry.competitorType,
@@ -33641,7 +33778,7 @@ function dedupeStrings(input) {
33641
33778
  }
33642
33779
 
33643
33780
  // ../api-routes/src/technical-aeo.ts
33644
- import crypto28 from "crypto";
33781
+ import crypto29 from "crypto";
33645
33782
  import { and as and26, asc as asc4, count, desc as desc17, eq as eq36, inArray as inArray13 } from "drizzle-orm";
33646
33783
  var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
33647
33784
  function emptyScore(projectName) {
@@ -33768,7 +33905,7 @@ async function technicalAeoRoutes(app, opts) {
33768
33905
  return { runId: existing.id, status: existing.status };
33769
33906
  }
33770
33907
  const now = (/* @__PURE__ */ new Date()).toISOString();
33771
- const runId = crypto28.randomUUID();
33908
+ const runId = crypto29.randomUUID();
33772
33909
  app.db.insert(runs).values({
33773
33910
  id: runId,
33774
33911
  projectId: project.id,
@@ -34119,7 +34256,7 @@ function buildTrafficSourceValidators(opts) {
34119
34256
  }
34120
34257
 
34121
34258
  // src/intelligence-service.ts
34122
- import crypto29 from "crypto";
34259
+ import crypto30 from "crypto";
34123
34260
 
34124
34261
  // src/logger.ts
34125
34262
  var IS_TTY = process.stdout.isTTY === true;
@@ -34784,7 +34921,7 @@ var IntelligenceService = class {
34784
34921
  }).run();
34785
34922
  }
34786
34923
  tx.insert(healthSnapshots).values({
34787
- id: crypto29.randomUUID(),
34924
+ id: crypto30.randomUUID(),
34788
34925
  projectId,
34789
34926
  runId,
34790
34927
  overallCitedRate: String(result.health.overallCitedRate),
@@ -34979,6 +35116,7 @@ export {
34979
35116
  gbpPlaceActions,
34980
35117
  gbpLodgingSnapshots,
34981
35118
  gbpPlaceDetails,
35119
+ gbpAttributesSnapshots,
34982
35120
  adsConnections,
34983
35121
  adsCampaigns,
34984
35122
  adsAdGroups,
@@ -35008,12 +35146,18 @@ export {
35008
35146
  fetchSearchAnalytics,
35009
35147
  inspectUrl,
35010
35148
  GBP_DAILY_METRICS,
35149
+ listLocations,
35150
+ formatStorefrontAddress,
35151
+ buildLocationProfileFields,
35011
35152
  fetchDailyMetrics,
35012
35153
  listMonthlyKeywords,
35013
35154
  listPlaceActionLinks,
35014
35155
  getLodging,
35015
35156
  countPopulatedGroups,
35016
35157
  hashLodging,
35158
+ getAttributes,
35159
+ countAttributes,
35160
+ hashAttributes,
35017
35161
  getUrlInfo,
35018
35162
  getCrawlIssues,
35019
35163
  getLinkCounts,