@ainyc/canonry 4.92.0 → 4.93.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 (25) hide show
  1. package/assets/agent-workspace/skills/canonry/references/google-business-profile.md +6 -0
  2. package/assets/assets/{BacklinksPage-D3JpcWtH.js → BacklinksPage-nkgAbbjk.js} +1 -1
  3. package/assets/assets/{ChartPrimitives-DdwqEX5v.js → ChartPrimitives-kdfylr1f.js} +1 -1
  4. package/assets/assets/ProjectPage-BQYmq7FP.js +6 -0
  5. package/assets/assets/{RunRow-c_2jd8iz.js → RunRow-P-n5OkiY.js} +1 -1
  6. package/assets/assets/{RunsPage-D6eM_5tB.js → RunsPage-DfBb4WCS.js} +1 -1
  7. package/assets/assets/{SettingsPage-DaMc8nxK.js → SettingsPage-CRGuFgeK.js} +1 -1
  8. package/assets/assets/{TrafficPage-Dw16dcSc.js → TrafficPage-CMqP0AGZ.js} +1 -1
  9. package/assets/assets/{TrafficSourceDetailPage-Dy9D80dz.js → TrafficSourceDetailPage-XRGzkKWk.js} +1 -1
  10. package/assets/assets/{arrow-left-D8UaBG9a.js → arrow-left-BCR55g4n.js} +1 -1
  11. package/assets/assets/{extract-error-message-CfxCIMNJ.js → extract-error-message-BG1AlucT.js} +1 -1
  12. package/assets/assets/{index-BA-lh2pG.js → index-BPjIZIDO.js} +73 -73
  13. package/assets/assets/{index-fLUYE9Z5.css → index-DiykVUPr.css} +1 -1
  14. package/assets/assets/{trash-2-j1tX1-Kq.js → trash-2-Cme1gNjU.js} +1 -1
  15. package/assets/index.html +2 -2
  16. package/dist/{chunk-QYC2N23P.js → chunk-ADTLQHJY.js} +69 -5
  17. package/dist/{chunk-VJPVFCZW.js → chunk-AUQNKEHV.js} +18 -0
  18. package/dist/{chunk-IFUHXONX.js → chunk-KBDQ5OE4.js} +166 -56
  19. package/dist/{chunk-HW4GVIQD.js → chunk-PK46TKGM.js} +33 -1
  20. package/dist/cli.js +36 -4
  21. package/dist/index.js +4 -4
  22. package/dist/{intelligence-service-24HCX4SJ.js → intelligence-service-4ATCJITP.js} +2 -2
  23. package/dist/mcp.js +2 -2
  24. package/package.json +9 -9
  25. 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-AUQNKEHV.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) {
@@ -13778,6 +13809,7 @@ var SCHEMA_TABLE = {
13778
13809
  GbpLocationDto: gbpLocationDtoSchema,
13779
13810
  GbpLocationListResponse: gbpLocationListResponseSchema,
13780
13811
  GbpLodgingListResponse: gbpLodgingListResponseSchema,
13812
+ GbpAttributesListResponse: gbpAttributesListResponseSchema,
13781
13813
  GbpPlaceActionListResponse: gbpPlaceActionListResponseSchema,
13782
13814
  GbpPlaceDetailsListResponse: gbpPlaceDetailsListResponseSchema,
13783
13815
  GbpSummaryDto: gbpSummaryDtoSchema,
@@ -15791,6 +15823,20 @@ var routeCatalog = [
15791
15823
  404: errorResponse("Project not found.")
15792
15824
  }
15793
15825
  },
15826
+ {
15827
+ method: "get",
15828
+ path: "/api/v1/projects/{name}/gbp/attributes",
15829
+ summary: "List latest Google Business Profile owner-set attribute snapshots per location",
15830
+ tags: ["gbp"],
15831
+ parameters: [
15832
+ nameParameter,
15833
+ { in: "query", name: "locationName", required: false, description: "Filter to one location resource name", schema: stringSchema }
15834
+ ],
15835
+ responses: {
15836
+ 200: jsonResponse("Attribute snapshots returned.", "GbpAttributesListResponse"),
15837
+ 404: errorResponse("Project not found.")
15838
+ }
15839
+ },
15794
15840
  {
15795
15841
  method: "get",
15796
15842
  path: "/api/v1/projects/{name}/gbp/places",
@@ -18529,7 +18575,7 @@ function formatNotification(row) {
18529
18575
  }
18530
18576
 
18531
18577
  // ../api-routes/src/google.ts
18532
- import crypto17 from "crypto";
18578
+ import crypto18 from "crypto";
18533
18579
  import { eq as eq21, and as and14, desc as desc11, sql as sql8, inArray as inArray10 } from "drizzle-orm";
18534
18580
 
18535
18581
  // ../api-routes/src/gbp-summary.ts
@@ -20082,6 +20128,46 @@ function stableStringify2(value) {
20082
20128
  return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify2(obj[k])}`).join(",")}}`;
20083
20129
  }
20084
20130
 
20131
+ // ../integration-google-business-profile/src/attributes-client.ts
20132
+ import crypto17 from "crypto";
20133
+ async function getAttributes(accessToken, locationName, opts = {}) {
20134
+ const url = `${GBP_BUSINESS_INFO_BASE}/${locationName}/attributes`;
20135
+ let res;
20136
+ try {
20137
+ res = await gbpFetchGet(url, accessToken, opts);
20138
+ } catch (err) {
20139
+ if (err instanceof GbpApiError && err.status === 404) return [];
20140
+ throw err;
20141
+ }
20142
+ return (res.attributes ?? []).map(normalizeAttribute).filter((a) => a.name.length > 0);
20143
+ }
20144
+ function normalizeAttribute(raw) {
20145
+ const values = [];
20146
+ for (const v of raw.values ?? []) {
20147
+ if (typeof v === "boolean" || typeof v === "string") values.push(v);
20148
+ }
20149
+ for (const s of raw.repeatedEnumValue?.setValues ?? []) values.push(s);
20150
+ const uris = [];
20151
+ for (const u of raw.uriValues ?? []) {
20152
+ if (u?.uri) uris.push(u.uri);
20153
+ }
20154
+ return { name: raw.name ?? "", valueType: raw.valueType ?? "", values, uris };
20155
+ }
20156
+ function countAttributes(attrs) {
20157
+ return attrs.length;
20158
+ }
20159
+ function hashAttributes(attrs) {
20160
+ const sorted = [...attrs].sort((a, b) => a.name.localeCompare(b.name));
20161
+ return crypto17.createHash("sha256").update(stableStringify3(sorted)).digest("hex");
20162
+ }
20163
+ function stableStringify3(value) {
20164
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
20165
+ if (Array.isArray(value)) return `[${value.map(stableStringify3).join(",")}]`;
20166
+ const obj = value;
20167
+ const keys = Object.keys(obj).sort();
20168
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify3(obj[k])}`).join(",")}}`;
20169
+ }
20170
+
20085
20171
  // ../api-routes/src/google.ts
20086
20172
  function scopesForConnectionType(type) {
20087
20173
  switch (type) {
@@ -20094,7 +20180,7 @@ function scopesForConnectionType(type) {
20094
20180
  }
20095
20181
  }
20096
20182
  function signState(payload, secret) {
20097
- return crypto17.createHmac("sha256", secret).update(payload).digest("hex");
20183
+ return crypto18.createHmac("sha256", secret).update(payload).digest("hex");
20098
20184
  }
20099
20185
  function buildSignedState(data, secret) {
20100
20186
  const payload = JSON.stringify(data);
@@ -20105,7 +20191,7 @@ function verifySignedState(encoded, secret) {
20105
20191
  try {
20106
20192
  const { payload, sig } = JSON.parse(Buffer.from(encoded, "base64url").toString());
20107
20193
  const expected = signState(payload, secret);
20108
- if (!crypto17.timingSafeEqual(Buffer.from(sig, "hex"), Buffer.from(expected, "hex"))) return null;
20194
+ if (!crypto18.timingSafeEqual(Buffer.from(sig, "hex"), Buffer.from(expected, "hex"))) return null;
20109
20195
  return JSON.parse(payload);
20110
20196
  } catch {
20111
20197
  return null;
@@ -20441,7 +20527,7 @@ async function googleRoutes(app, opts) {
20441
20527
  throw validationError('No GSC connection found for this domain. Run "canonry google connect" first.');
20442
20528
  }
20443
20529
  const now = (/* @__PURE__ */ new Date()).toISOString();
20444
- const runId = crypto17.randomUUID();
20530
+ const runId = crypto18.randomUUID();
20445
20531
  app.db.insert(runs).values({
20446
20532
  id: runId,
20447
20533
  projectId: project.id,
@@ -20539,7 +20625,7 @@ async function googleRoutes(app, opts) {
20539
20625
  const mob = ir.mobileUsabilityResult;
20540
20626
  const rich = ir.richResultsResult;
20541
20627
  const now = (/* @__PURE__ */ new Date()).toISOString();
20542
- const id = crypto17.randomUUID();
20628
+ const id = crypto18.randomUUID();
20543
20629
  app.db.insert(gscUrlInspections).values({
20544
20630
  id,
20545
20631
  projectId: project.id,
@@ -20783,7 +20869,7 @@ async function googleRoutes(app, opts) {
20783
20869
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
20784
20870
  });
20785
20871
  const now = (/* @__PURE__ */ new Date()).toISOString();
20786
- const runId = crypto17.randomUUID();
20872
+ const runId = crypto18.randomUUID();
20787
20873
  app.db.insert(runs).values({
20788
20874
  id: runId,
20789
20875
  projectId: project.id,
@@ -20809,7 +20895,7 @@ async function googleRoutes(app, opts) {
20809
20895
  throw validationError("No GSC property configured for this connection");
20810
20896
  }
20811
20897
  const now = (/* @__PURE__ */ new Date()).toISOString();
20812
- const runId = crypto17.randomUUID();
20898
+ const runId = crypto18.randomUUID();
20813
20899
  app.db.insert(runs).values({
20814
20900
  id: runId,
20815
20901
  projectId: project.id,
@@ -21090,7 +21176,7 @@ async function googleRoutes(app, opts) {
21090
21176
  }).where(eq21(gbpLocations.id, existing.id)).run();
21091
21177
  } else {
21092
21178
  tx.insert(gbpLocations).values({
21093
- id: crypto17.randomUUID(),
21179
+ id: crypto18.randomUUID(),
21094
21180
  projectId: project.id,
21095
21181
  accountName,
21096
21182
  locationName: remote.name,
@@ -21214,7 +21300,7 @@ async function googleRoutes(app, opts) {
21214
21300
  throw validationError(parsed.error.issues[0]?.message ?? "Invalid sync request");
21215
21301
  }
21216
21302
  const now = (/* @__PURE__ */ new Date()).toISOString();
21217
- const runId = crypto17.randomUUID();
21303
+ const runId = crypto18.randomUUID();
21218
21304
  app.db.insert(runs).values({
21219
21305
  id: runId,
21220
21306
  projectId: project.id,
@@ -21291,6 +21377,23 @@ async function googleRoutes(app, opts) {
21291
21377
  }));
21292
21378
  return { lodging, total: lodging.length };
21293
21379
  });
21380
+ app.get("/projects/:name/gbp/attributes", async (request) => {
21381
+ const project = resolveProject(app.db, request.params.name);
21382
+ const conditions = [eq21(gbpAttributesSnapshots.projectId, project.id)];
21383
+ if (request.query.locationName) conditions.push(eq21(gbpAttributesSnapshots.locationName, request.query.locationName));
21384
+ const rows = app.db.select().from(gbpAttributesSnapshots).where(and14(...conditions)).orderBy(desc11(gbpAttributesSnapshots.syncedAt)).all();
21385
+ const latestByLocation = /* @__PURE__ */ new Map();
21386
+ for (const row of rows) {
21387
+ if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
21388
+ }
21389
+ const attributes = [...latestByLocation.values()].map((r) => ({
21390
+ locationName: r.locationName,
21391
+ attributeCount: r.attributeCount,
21392
+ syncedAt: r.syncedAt,
21393
+ attributes: r.attributes
21394
+ }));
21395
+ return { attributes, total: attributes.length };
21396
+ });
21294
21397
  app.get("/projects/:name/gbp/places", async (request) => {
21295
21398
  const project = resolveProject(app.db, request.params.name);
21296
21399
  const conditions = [eq21(gbpPlaceDetails.projectId, project.id)];
@@ -21360,7 +21463,7 @@ async function googleRoutes(app, opts) {
21360
21463
  }
21361
21464
 
21362
21465
  // ../api-routes/src/ads.ts
21363
- import crypto18 from "crypto";
21466
+ import crypto19 from "crypto";
21364
21467
  import { eq as eq22, and as and15, asc as asc2, gte as gte3, lte as lte2, inArray as inArray11 } from "drizzle-orm";
21365
21468
  function statusDto(row) {
21366
21469
  if (!row) return { connected: false };
@@ -21423,7 +21526,7 @@ async function adsRoutes(app, opts) {
21423
21526
  }).where(eq22(adsConnections.id, existingRow.id)).run();
21424
21527
  } else {
21425
21528
  tx.insert(adsConnections).values({
21426
- id: crypto18.randomUUID(),
21529
+ id: crypto19.randomUUID(),
21427
21530
  projectId: project.id,
21428
21531
  adAccountId: account.id,
21429
21532
  displayName: account.name,
@@ -21485,7 +21588,7 @@ async function adsRoutes(app, opts) {
21485
21588
  const existing = { runId: inFlight.id, status: inFlight.status };
21486
21589
  return existing;
21487
21590
  }
21488
- const runId = crypto18.randomUUID();
21591
+ const runId = crypto19.randomUUID();
21489
21592
  app.db.insert(runs).values({
21490
21593
  id: runId,
21491
21594
  projectId: project.id,
@@ -21620,7 +21723,7 @@ async function adsRoutes(app, opts) {
21620
21723
  }
21621
21724
 
21622
21725
  // ../api-routes/src/bing.ts
21623
- import crypto19 from "crypto";
21726
+ import crypto20 from "crypto";
21624
21727
  import { eq as eq23, and as and16, desc as desc12 } from "drizzle-orm";
21625
21728
 
21626
21729
  // ../integration-bing/src/constants.ts
@@ -22052,7 +22155,7 @@ async function bingRoutes(app, opts) {
22052
22155
  const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
22053
22156
  const now = (/* @__PURE__ */ new Date()).toISOString();
22054
22157
  app.db.insert(bingCoverageSnapshots).values({
22055
- id: crypto19.randomUUID(),
22158
+ id: crypto20.randomUUID(),
22056
22159
  projectId: project.id,
22057
22160
  syncRunId: snapshotRunId,
22058
22161
  date: snapshotDate,
@@ -22123,7 +22226,7 @@ async function bingRoutes(app, opts) {
22123
22226
  throw validationError("url is required");
22124
22227
  }
22125
22228
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
22126
- const runId = crypto19.randomUUID();
22229
+ const runId = crypto20.randomUUID();
22127
22230
  app.db.insert(runs).values({
22128
22231
  id: runId,
22129
22232
  projectId: project.id,
@@ -22144,7 +22247,7 @@ async function bingRoutes(app, opts) {
22144
22247
  discoveryDate: result.DiscoveryDate ?? null
22145
22248
  });
22146
22249
  const now = (/* @__PURE__ */ new Date()).toISOString();
22147
- const id = crypto19.randomUUID();
22250
+ const id = crypto20.randomUUID();
22148
22251
  const httpCode = result.HttpStatus ?? result.HttpCode ?? null;
22149
22252
  const lastCrawledDate = parseBingDate(result.LastCrawledDate);
22150
22253
  const inIndexDate = parseBingDate(result.InIndexDate);
@@ -22214,7 +22317,7 @@ async function bingRoutes(app, opts) {
22214
22317
  throw validationError('No Bing site configured. Run "canonry bing set-site <project> <url>" first.');
22215
22318
  }
22216
22319
  const now = (/* @__PURE__ */ new Date()).toISOString();
22217
- const runId = crypto19.randomUUID();
22320
+ const runId = crypto20.randomUUID();
22218
22321
  app.db.insert(runs).values({
22219
22322
  id: runId,
22220
22323
  projectId: project.id,
@@ -22498,7 +22601,7 @@ async function cdpRoutes(app, opts) {
22498
22601
  }
22499
22602
 
22500
22603
  // ../api-routes/src/ga.ts
22501
- import crypto20 from "crypto";
22604
+ import crypto21 from "crypto";
22502
22605
  import { eq as eq25, desc as desc13, and as and18, sql as sql9 } from "drizzle-orm";
22503
22606
  function gaLog(level, action, ctx) {
22504
22607
  const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
@@ -22754,7 +22857,7 @@ async function ga4Routes(app, opts) {
22754
22857
  const syncAi = !only || only === "ai";
22755
22858
  const syncSocial = !only || only === "social";
22756
22859
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
22757
- const runId = crypto20.randomUUID();
22860
+ const runId = crypto21.randomUUID();
22758
22861
  app.db.insert(runs).values({
22759
22862
  id: runId,
22760
22863
  projectId: project.id,
@@ -22802,7 +22905,7 @@ async function ga4Routes(app, opts) {
22802
22905
  ).run();
22803
22906
  for (const row of rows) {
22804
22907
  tx.insert(gaTrafficSnapshots).values({
22805
- id: crypto20.randomUUID(),
22908
+ id: crypto21.randomUUID(),
22806
22909
  projectId: project.id,
22807
22910
  date: row.date,
22808
22911
  landingPage: row.landingPage,
@@ -22826,7 +22929,7 @@ async function ga4Routes(app, opts) {
22826
22929
  ).run();
22827
22930
  for (const row of aiReferrals) {
22828
22931
  tx.insert(gaAiReferrals).values({
22829
- id: crypto20.randomUUID(),
22932
+ id: crypto21.randomUUID(),
22830
22933
  projectId: project.id,
22831
22934
  date: row.date,
22832
22935
  source: row.source,
@@ -22852,7 +22955,7 @@ async function ga4Routes(app, opts) {
22852
22955
  ).run();
22853
22956
  for (const row of socialReferrals) {
22854
22957
  tx.insert(gaSocialReferrals).values({
22855
- id: crypto20.randomUUID(),
22958
+ id: crypto21.randomUUID(),
22856
22959
  projectId: project.id,
22857
22960
  date: row.date,
22858
22961
  source: row.source,
@@ -22868,7 +22971,7 @@ async function ga4Routes(app, opts) {
22868
22971
  if (syncSummary) {
22869
22972
  tx.delete(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).run();
22870
22973
  tx.insert(gaTrafficSummaries).values({
22871
- id: crypto20.randomUUID(),
22974
+ id: crypto21.randomUUID(),
22872
22975
  projectId: project.id,
22873
22976
  periodStart: summary.periodStart,
22874
22977
  periodEnd: summary.periodEnd,
@@ -22881,7 +22984,7 @@ async function ga4Routes(app, opts) {
22881
22984
  tx.delete(gaTrafficWindowSummaries).where(eq25(gaTrafficWindowSummaries.projectId, project.id)).run();
22882
22985
  for (const ws of windowSummaries) {
22883
22986
  tx.insert(gaTrafficWindowSummaries).values({
22884
- id: crypto20.randomUUID(),
22987
+ id: crypto21.randomUUID(),
22885
22988
  projectId: project.id,
22886
22989
  windowKey: ws.windowKey,
22887
22990
  periodStart: ws.periodStart,
@@ -23452,7 +23555,7 @@ function parseSchemaPageEntry(entry) {
23452
23555
  }
23453
23556
 
23454
23557
  // ../integration-wordpress/src/wordpress-client.ts
23455
- import crypto21 from "crypto";
23558
+ import crypto22 from "crypto";
23456
23559
  function validateUsername(username) {
23457
23560
  if (!username || typeof username !== "string" || username.trim().length === 0) {
23458
23561
  throw new WordpressApiError("AUTH_INVALID", "Username is required and must be a non-empty string", 400);
@@ -23665,7 +23768,7 @@ function buildSnippet(content) {
23665
23768
  return `${text2.slice(0, 157)}...`;
23666
23769
  }
23667
23770
  function contentHash(content) {
23668
- return crypto21.createHash("sha256").update(content).digest("hex");
23771
+ return crypto22.createHash("sha256").update(content).digest("hex");
23669
23772
  }
23670
23773
  function buildAmbiguousSlugMessage(slug, pages) {
23671
23774
  const candidates = pages.map((page) => {
@@ -24965,7 +25068,7 @@ async function wordpressRoutes(app, opts) {
24965
25068
  }
24966
25069
 
24967
25070
  // ../api-routes/src/backlinks.ts
24968
- import crypto22 from "crypto";
25071
+ import crypto23 from "crypto";
24969
25072
  import { and as and20, asc as asc3, desc as desc14, eq as eq26, sql as sql10 } from "drizzle-orm";
24970
25073
 
24971
25074
  // ../integration-commoncrawl/src/constants.ts
@@ -25603,7 +25706,7 @@ async function backlinksRoutes(app, opts) {
25603
25706
  const refreshed = app.db.select().from(ccReleaseSyncs).where(eq26(ccReleaseSyncs.id, existing.id)).get();
25604
25707
  return reply.status(200).send(mapSyncRow(refreshed));
25605
25708
  }
25606
- const id = crypto22.randomUUID();
25709
+ const id = crypto23.randomUUID();
25607
25710
  app.db.insert(ccReleaseSyncs).values({
25608
25711
  id,
25609
25712
  release,
@@ -25660,7 +25763,7 @@ async function backlinksRoutes(app, opts) {
25660
25763
  throw validationError("Invalid release id");
25661
25764
  }
25662
25765
  const now = (/* @__PURE__ */ new Date()).toISOString();
25663
- const runId = crypto22.randomUUID();
25766
+ const runId = crypto23.randomUUID();
25664
25767
  app.db.insert(runs).values({
25665
25768
  id: runId,
25666
25769
  projectId: project.id,
@@ -25762,7 +25865,7 @@ async function backlinksRoutes(app, opts) {
25762
25865
  );
25763
25866
  }
25764
25867
  const now = (/* @__PURE__ */ new Date()).toISOString();
25765
- const runId = crypto22.randomUUID();
25868
+ const runId = crypto23.randomUUID();
25766
25869
  app.db.insert(runs).values({
25767
25870
  id: runId,
25768
25871
  projectId: project.id,
@@ -25779,12 +25882,12 @@ async function backlinksRoutes(app, opts) {
25779
25882
  }
25780
25883
 
25781
25884
  // ../api-routes/src/traffic.ts
25782
- import crypto24 from "crypto";
25885
+ import crypto25 from "crypto";
25783
25886
  import { Agent as UndiciAgent } from "undici";
25784
25887
  import { and as and21, desc as desc15, eq as eq27, gte as gte4, lte as lte3, sql as sql11 } from "drizzle-orm";
25785
25888
 
25786
25889
  // ../integration-cloud-run/src/auth.ts
25787
- import crypto23 from "crypto";
25890
+ import crypto24 from "crypto";
25788
25891
  var GOOGLE_TOKEN_URL3 = "https://oauth2.googleapis.com/token";
25789
25892
  var CLOUD_LOGGING_READ_SCOPE = "https://www.googleapis.com/auth/logging.read";
25790
25893
  var TOKEN_REQUEST_TIMEOUT_MS = 3e4;
@@ -25815,7 +25918,7 @@ function createServiceAccountJwt2(clientEmail, privateKey, scope) {
25815
25918
  const headerB64 = encode(header);
25816
25919
  const payloadB64 = encode(payload);
25817
25920
  const signingInput = `${headerB64}.${payloadB64}`;
25818
- const sign = crypto23.createSign("RSA-SHA256");
25921
+ const sign = crypto24.createSign("RSA-SHA256");
25819
25922
  sign.update(signingInput);
25820
25923
  const signature = sign.sign(privateKey, "base64url");
25821
25924
  return `${signingInput}.${signature}`;
@@ -29723,7 +29826,7 @@ async function runBackfillTask(options) {
29723
29826
  }
29724
29827
  })();
29725
29828
  tx.insert(rawEventSamples).values({
29726
- id: crypto24.randomUUID(),
29829
+ id: crypto25.randomUUID(),
29727
29830
  projectId: project.id,
29728
29831
  sourceId: sourceRow.id,
29729
29832
  ts: sample.observedAt,
@@ -29849,7 +29952,7 @@ async function trafficRoutes(app, opts) {
29849
29952
  }).where(eq27(trafficSources.id, activeSource.id)).run();
29850
29953
  sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, activeSource.id)).get();
29851
29954
  } else {
29852
- const newId = crypto24.randomUUID();
29955
+ const newId = crypto25.randomUUID();
29853
29956
  app.db.insert(trafficSources).values({
29854
29957
  id: newId,
29855
29958
  projectId: project.id,
@@ -29930,7 +30033,7 @@ async function trafficRoutes(app, opts) {
29930
30033
  }).where(eq27(trafficSources.id, activeSource.id)).run();
29931
30034
  sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, activeSource.id)).get();
29932
30035
  } else {
29933
- const newId = crypto24.randomUUID();
30036
+ const newId = crypto25.randomUUID();
29934
30037
  app.db.insert(trafficSources).values({
29935
30038
  id: newId,
29936
30039
  projectId: project.id,
@@ -30014,7 +30117,7 @@ async function trafficRoutes(app, opts) {
30014
30117
  }).where(eq27(trafficSources.id, activeSource.id)).run();
30015
30118
  row = tx.select().from(trafficSources).where(eq27(trafficSources.id, activeSource.id)).get();
30016
30119
  } else {
30017
- const newId = crypto24.randomUUID();
30120
+ const newId = crypto25.randomUUID();
30018
30121
  tx.insert(trafficSources).values({
30019
30122
  id: newId,
30020
30123
  projectId: project.id,
@@ -30048,7 +30151,7 @@ async function trafficRoutes(app, opts) {
30048
30151
  let created = false;
30049
30152
  if (!existingSchedule) {
30050
30153
  tx.insert(schedules).values({
30051
- id: crypto24.randomUUID(),
30154
+ id: crypto25.randomUUID(),
30052
30155
  projectId: project.id,
30053
30156
  kind: SchedulableRunKinds["traffic-sync"],
30054
30157
  cronExpr: DEFAULT_TRAFFIC_SYNC_CRON,
@@ -30103,7 +30206,7 @@ async function trafficRoutes(app, opts) {
30103
30206
  const windowEnd = /* @__PURE__ */ new Date();
30104
30207
  const startedAt = windowEnd.toISOString();
30105
30208
  const syncStartedAtMs = windowEnd.getTime();
30106
- const runId = crypto24.randomUUID();
30209
+ const runId = crypto25.randomUUID();
30107
30210
  app.db.insert(runs).values({
30108
30211
  id: runId,
30109
30212
  projectId: project.id,
@@ -30486,7 +30589,7 @@ async function trafficRoutes(app, opts) {
30486
30589
  }
30487
30590
  })();
30488
30591
  tx.insert(rawEventSamples).values({
30489
- id: crypto24.randomUUID(),
30592
+ id: crypto25.randomUUID(),
30490
30593
  projectId: project.id,
30491
30594
  sourceId: sourceRow.id,
30492
30595
  ts: sample.observedAt,
@@ -30724,7 +30827,7 @@ async function trafficRoutes(app, opts) {
30724
30827
  };
30725
30828
  }
30726
30829
  const startedAt = windowEnd.toISOString();
30727
- const runId = crypto24.randomUUID();
30830
+ const runId = crypto25.randomUUID();
30728
30831
  app.db.insert(runs).values({
30729
30832
  id: runId,
30730
30833
  projectId: project.id,
@@ -31025,7 +31128,7 @@ async function trafficRoutes(app, opts) {
31025
31128
  }
31026
31129
 
31027
31130
  // ../api-routes/src/doctor/checks/agent.ts
31028
- import crypto25 from "crypto";
31131
+ import crypto26 from "crypto";
31029
31132
  import fs6 from "fs";
31030
31133
  import path7 from "path";
31031
31134
  var REQUIRED_SKILLS = ["canonry", "aero"];
@@ -31178,7 +31281,7 @@ function isInstalled(dir) {
31178
31281
  }
31179
31282
  function hashInstalledFile(filePath) {
31180
31283
  try {
31181
- return crypto25.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
31284
+ return crypto26.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
31182
31285
  } catch {
31183
31286
  return void 0;
31184
31287
  }
@@ -33080,7 +33183,7 @@ async function doctorRoutes(app, opts) {
33080
33183
  }
33081
33184
 
33082
33185
  // ../api-routes/src/discovery/routes.ts
33083
- import crypto26 from "crypto";
33186
+ import crypto27 from "crypto";
33084
33187
  import { and as and25, desc as desc16, eq as eq34, gte as gte6, inArray as inArray12 } from "drizzle-orm";
33085
33188
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
33086
33189
  async function discoveryRoutes(app, opts) {
@@ -33126,8 +33229,8 @@ async function discoveryRoutes(app, opts) {
33126
33229
  if (existing && existing.runId) {
33127
33230
  return { reused: true, sessionId: existing.id, runId: existing.runId };
33128
33231
  }
33129
- const sessionId = crypto26.randomUUID();
33130
- const runId = crypto26.randomUUID();
33232
+ const sessionId = crypto27.randomUUID();
33233
+ const runId = crypto27.randomUUID();
33131
33234
  tx.insert(discoverySessions).values({
33132
33235
  id: sessionId,
33133
33236
  projectId: project.id,
@@ -33375,7 +33478,7 @@ async function discoveryRoutes(app, opts) {
33375
33478
  app.db.transaction((tx) => {
33376
33479
  for (const query of promotedQueries) {
33377
33480
  tx.insert(queries).values({
33378
- id: crypto26.randomUUID(),
33481
+ id: crypto27.randomUUID(),
33379
33482
  projectId: project.id,
33380
33483
  query,
33381
33484
  provenance,
@@ -33384,7 +33487,7 @@ async function discoveryRoutes(app, opts) {
33384
33487
  }
33385
33488
  for (const domain of promotedCompetitors) {
33386
33489
  tx.insert(competitors).values({
33387
- id: crypto26.randomUUID(),
33490
+ id: crypto27.randomUUID(),
33388
33491
  projectId: project.id,
33389
33492
  domain,
33390
33493
  provenance,
@@ -33462,7 +33565,7 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
33462
33565
  }
33463
33566
 
33464
33567
  // ../api-routes/src/discovery/orchestrate.ts
33465
- import crypto27 from "crypto";
33568
+ import crypto28 from "crypto";
33466
33569
  import { eq as eq35 } from "drizzle-orm";
33467
33570
  var DEFAULT_MAX_PROBES = 100;
33468
33571
  var ABSOLUTE_MAX_PROBES = 500;
@@ -33556,7 +33659,7 @@ async function executeDiscovery(opts) {
33556
33659
  probeRows.push({ citedDomains: probe.citedDomains, bucket });
33557
33660
  buckets[bucket]++;
33558
33661
  opts.db.insert(discoveryProbes).values({
33559
- id: crypto27.randomUUID(),
33662
+ id: crypto28.randomUUID(),
33560
33663
  sessionId: opts.sessionId,
33561
33664
  projectId: opts.project.id,
33562
33665
  query,
@@ -33601,7 +33704,7 @@ function upsertDomainClassifications(db, projectId, sessionId, competitorMap) {
33601
33704
  const domain = normalizeDomain(entry.domain);
33602
33705
  if (!domain) continue;
33603
33706
  db.insert(domainClassifications).values({
33604
- id: crypto27.randomUUID(),
33707
+ id: crypto28.randomUUID(),
33605
33708
  projectId,
33606
33709
  domain,
33607
33710
  competitorType: entry.competitorType,
@@ -33641,7 +33744,7 @@ function dedupeStrings(input) {
33641
33744
  }
33642
33745
 
33643
33746
  // ../api-routes/src/technical-aeo.ts
33644
- import crypto28 from "crypto";
33747
+ import crypto29 from "crypto";
33645
33748
  import { and as and26, asc as asc4, count, desc as desc17, eq as eq36, inArray as inArray13 } from "drizzle-orm";
33646
33749
  var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
33647
33750
  function emptyScore(projectName) {
@@ -33768,7 +33871,7 @@ async function technicalAeoRoutes(app, opts) {
33768
33871
  return { runId: existing.id, status: existing.status };
33769
33872
  }
33770
33873
  const now = (/* @__PURE__ */ new Date()).toISOString();
33771
- const runId = crypto28.randomUUID();
33874
+ const runId = crypto29.randomUUID();
33772
33875
  app.db.insert(runs).values({
33773
33876
  id: runId,
33774
33877
  projectId: project.id,
@@ -34119,7 +34222,7 @@ function buildTrafficSourceValidators(opts) {
34119
34222
  }
34120
34223
 
34121
34224
  // src/intelligence-service.ts
34122
- import crypto29 from "crypto";
34225
+ import crypto30 from "crypto";
34123
34226
 
34124
34227
  // src/logger.ts
34125
34228
  var IS_TTY = process.stdout.isTTY === true;
@@ -34784,7 +34887,7 @@ var IntelligenceService = class {
34784
34887
  }).run();
34785
34888
  }
34786
34889
  tx.insert(healthSnapshots).values({
34787
- id: crypto29.randomUUID(),
34890
+ id: crypto30.randomUUID(),
34788
34891
  projectId,
34789
34892
  runId,
34790
34893
  overallCitedRate: String(result.health.overallCitedRate),
@@ -34979,6 +35082,7 @@ export {
34979
35082
  gbpPlaceActions,
34980
35083
  gbpLodgingSnapshots,
34981
35084
  gbpPlaceDetails,
35085
+ gbpAttributesSnapshots,
34982
35086
  adsConnections,
34983
35087
  adsCampaigns,
34984
35088
  adsAdGroups,
@@ -35008,12 +35112,18 @@ export {
35008
35112
  fetchSearchAnalytics,
35009
35113
  inspectUrl,
35010
35114
  GBP_DAILY_METRICS,
35115
+ listLocations,
35116
+ formatStorefrontAddress,
35117
+ buildLocationProfileFields,
35011
35118
  fetchDailyMetrics,
35012
35119
  listMonthlyKeywords,
35013
35120
  listPlaceActionLinks,
35014
35121
  getLodging,
35015
35122
  countPopulatedGroups,
35016
35123
  hashLodging,
35124
+ getAttributes,
35125
+ countAttributes,
35126
+ hashAttributes,
35017
35127
  getUrlInfo,
35018
35128
  getCrawlIssues,
35019
35129
  getLinkCounts,