@canonry/canonry 4.153.0 → 4.154.1

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 (42) hide show
  1. package/README.md +2 -2
  2. package/assets/agent-workspace/skills/canonry/SKILL.md +4 -19
  3. package/assets/assets/{AuditHistoryPanel-CcoXjMxm.js → AuditHistoryPanel-WKf-vvmg.js} +1 -1
  4. package/assets/assets/BacklinksPage-By06DbHN.js +1 -0
  5. package/assets/assets/HistoryPage-C_PcPiyX.js +1 -0
  6. package/assets/assets/MeasurementPropertyPage-BHnNegEa.js +1 -0
  7. package/assets/assets/ProjectPage-VTbW21dz.js +12 -0
  8. package/assets/assets/{RunRow-xfuUNQ2O.js → RunRow-9qn5Eer_.js} +1 -1
  9. package/assets/assets/RunsPage-FxMTprK1.js +1 -0
  10. package/assets/assets/SettingsPage-Cr7_PrNW.js +1 -0
  11. package/assets/assets/TrafficPage-D0qk9uMN.js +1 -0
  12. package/assets/assets/TrafficSourceDetailPage-CXa_3_Gy.js +1 -0
  13. package/assets/assets/extract-error-message-RkTEH1Vb.js +1 -0
  14. package/assets/assets/{index-CaYn5phz.css → index-DyL54s8x.css} +1 -1
  15. package/assets/assets/index-nSWJ9yka.js +83 -0
  16. package/assets/assets/v2-overview-adapter-COMP5UGt.js +1 -0
  17. package/assets/assets/vendor-lucide-ByU7vBHt.js +1 -0
  18. package/assets/assets/vendor-yaml-GWJz5H2G.js +131 -0
  19. package/assets/index.html +4 -2
  20. package/dist/{chunk-LNJF7LL5.js → chunk-ANSQ6JNO.js} +1 -1
  21. package/dist/{chunk-YV4VKT4M.js → chunk-JOP2DGXL.js} +172 -77
  22. package/dist/{chunk-VTL2WOIO.js → chunk-KRMLGFY3.js} +130 -82
  23. package/dist/{chunk-SKTXCZZD.js → chunk-NNW7TJUZ.js} +6 -1
  24. package/dist/cli.js +27 -4
  25. package/dist/index.js +4 -4
  26. package/dist/{intelligence-service-APPDGUH6.js → intelligence-service-ZY5KOSZV.js} +2 -2
  27. package/dist/mcp.js +2 -2
  28. package/package.json +9 -9
  29. package/assets/assets/BacklinksPage-Cij_A80K.js +0 -1
  30. package/assets/assets/HistoryPage-CdxhPEHF.js +0 -1
  31. package/assets/assets/MeasurementPropertyPage-CJaqCeMr.js +0 -1
  32. package/assets/assets/ProjectPage-BJPVmD_-.js +0 -12
  33. package/assets/assets/RunsPage-BirDqVe_.js +0 -1
  34. package/assets/assets/SettingsPage-BzMoD-42.js +0 -1
  35. package/assets/assets/TrafficPage-Du-7BL_j.js +0 -1
  36. package/assets/assets/TrafficSourceDetailPage-Dk6-TuNw.js +0 -1
  37. package/assets/assets/arrow-left-HyrLx9li.js +0 -1
  38. package/assets/assets/extract-error-message-Db0s9Qon.js +0 -1
  39. package/assets/assets/index-Bio0Z1kJ.js +0 -213
  40. package/assets/assets/refresh-cw-BFnkrZE9.js +0 -1
  41. package/assets/assets/trash-2-CQtsaJ-3.js +0 -1
  42. package/assets/assets/v2-overview-adapter-D81QVAhK.js +0 -1
@@ -521,7 +521,7 @@ import {
521
521
  wordpressSchemaDeployResultDtoSchema,
522
522
  wordpressSchemaStatusResultDtoSchema,
523
523
  wordpressStatusDtoSchema
524
- } from "./chunk-SKTXCZZD.js";
524
+ } from "./chunk-NNW7TJUZ.js";
525
525
 
526
526
  // src/intelligence-service.ts
527
527
  import { eq as eq57, desc as desc26, asc as asc11, and as and45, ne as ne7, or as or11, inArray as inArray19, gte as gte13, lte as lte10 } from "drizzle-orm";
@@ -22384,15 +22384,19 @@ function loadQueryLookup(db, projectId) {
22384
22384
  return { byId };
22385
22385
  }
22386
22386
  function buildGscSection(db, projectId, projectBrandNames, canonicalDomain, trackedQueries, windowDays2) {
22387
- const allRows = db.select().from(gscSearchData).where(eq28(gscSearchData.projectId, projectId)).all();
22388
- const allDailyTotals = readGscDailyTotals(db, projectId, "", "9999-12-31");
22389
- let maxDate = "";
22390
- for (const r of allRows) if (r.date > maxDate) maxDate = r.date;
22391
- for (const r of allDailyTotals) if (r.date > maxDate) maxDate = r.date;
22387
+ const maxSearch = db.select({ m: sql9`MAX(${gscSearchData.date})` }).from(gscSearchData).where(eq28(gscSearchData.projectId, projectId)).get()?.m ?? "";
22388
+ const maxDaily = db.select({ m: sql9`MAX(${gscDailyTotals.date})` }).from(gscDailyTotals).where(eq28(gscDailyTotals.projectId, projectId)).get()?.m ?? "";
22389
+ const maxDate = maxSearch > maxDaily ? maxSearch : maxDaily;
22392
22390
  if (!maxDate) return null;
22393
22391
  const startDate = windowStartDate(maxDate, windowDays2);
22394
- const rows = allRows.filter((r) => r.date >= startDate && r.date <= maxDate);
22395
- const dailyTotals = allDailyTotals.filter((r) => r.date >= startDate && r.date <= maxDate);
22392
+ const rows = db.select().from(gscSearchData).where(
22393
+ and22(
22394
+ eq28(gscSearchData.projectId, projectId),
22395
+ gte5(gscSearchData.date, startDate),
22396
+ lte3(gscSearchData.date, maxDate)
22397
+ )
22398
+ ).all();
22399
+ const dailyTotals = readGscDailyTotals(db, projectId, startDate, maxDate);
22396
22400
  if (rows.length === 0 && dailyTotals.length === 0) return null;
22397
22401
  let dimensionedClicks = 0;
22398
22402
  const queryDayAgg = /* @__PURE__ */ new Map();
@@ -22492,12 +22496,16 @@ function buildGaSection(db, projectId, windowDays2) {
22492
22496
  )
22493
22497
  ).limit(1).get() : void 0;
22494
22498
  const fallbackSummary = windowSummary ? null : db.select().from(gaTrafficSummaries).where(eq28(gaTrafficSummaries.projectId, projectId)).orderBy(desc12(gaTrafficSummaries.syncedAt)).limit(1).get();
22495
- const allSnapshotRows = db.select().from(gaTrafficSnapshots).where(eq28(gaTrafficSnapshots.projectId, projectId)).all();
22496
- if (!windowSummary && !fallbackSummary && allSnapshotRows.length === 0) return null;
22497
- let snapshotMaxDate = "";
22498
- for (const r of allSnapshotRows) if (r.date > snapshotMaxDate) snapshotMaxDate = r.date;
22499
+ const snapshotMaxDate = db.select({ m: sql9`MAX(${gaTrafficSnapshots.date})` }).from(gaTrafficSnapshots).where(eq28(gaTrafficSnapshots.projectId, projectId)).get()?.m ?? "";
22500
+ if (!windowSummary && !fallbackSummary && !snapshotMaxDate) return null;
22499
22501
  const snapshotStartDate = snapshotMaxDate ? windowStartDate(snapshotMaxDate, windowDays2) : "";
22500
- const snapshotRows = snapshotStartDate ? allSnapshotRows.filter((r) => r.date >= snapshotStartDate && r.date <= snapshotMaxDate) : allSnapshotRows;
22502
+ const snapshotRows = snapshotStartDate ? db.select().from(gaTrafficSnapshots).where(
22503
+ and22(
22504
+ eq28(gaTrafficSnapshots.projectId, projectId),
22505
+ gte5(gaTrafficSnapshots.date, snapshotStartDate),
22506
+ lte3(gaTrafficSnapshots.date, snapshotMaxDate)
22507
+ )
22508
+ ).all() : db.select().from(gaTrafficSnapshots).where(eq28(gaTrafficSnapshots.projectId, projectId)).all();
22501
22509
  const totalSessions = windowSummary?.totalSessions ?? fallbackSummary?.totalSessions ?? snapshotRows.reduce((s, r) => s + r.sessions, 0);
22502
22510
  const totalUsers = windowSummary?.totalUsers ?? fallbackSummary?.totalUsers ?? snapshotRows.reduce((s, r) => s + r.users, 0);
22503
22511
  const totalOrganicSessions = windowSummary?.totalOrganicSessions ?? fallbackSummary?.totalOrganicSessions ?? snapshotRows.reduce((s, r) => s + r.organicSessions, 0);
@@ -35501,8 +35509,9 @@ function isSitemapOwnedByProperty(sitemapUrl, propertyId, canonicalDomain) {
35501
35509
  function signState(payload, secret) {
35502
35510
  return crypto29.createHmac("sha256", secret).update(payload).digest("hex");
35503
35511
  }
35512
+ var OAUTH_STATE_MAX_AGE_MS = 15 * 60 * 1e3;
35504
35513
  function buildSignedState(data, secret) {
35505
- const payload = JSON.stringify(data);
35514
+ const payload = JSON.stringify({ ...data, issuedAt: Date.now() });
35506
35515
  const sig = signState(payload, secret);
35507
35516
  return Buffer.from(JSON.stringify({ payload, sig })).toString("base64url");
35508
35517
  }
@@ -35511,7 +35520,10 @@ function verifySignedState(encoded, secret) {
35511
35520
  const { payload, sig } = JSON.parse(Buffer.from(encoded, "base64url").toString());
35512
35521
  const expected = signState(payload, secret);
35513
35522
  if (!crypto29.timingSafeEqual(Buffer.from(sig, "hex"), Buffer.from(expected, "hex"))) return null;
35514
- return JSON.parse(payload);
35523
+ const parsed = JSON.parse(payload);
35524
+ const issuedAt = typeof parsed.issuedAt === "number" ? parsed.issuedAt : null;
35525
+ if (issuedAt === null || Date.now() - issuedAt > OAUTH_STATE_MAX_AGE_MS) return null;
35526
+ return parsed;
35515
35527
  } catch {
35516
35528
  return null;
35517
35529
  }
@@ -41797,9 +41809,12 @@ async function adsRoutes(app, opts) {
41797
41809
  }
41798
41810
 
41799
41811
  // ../api-routes/src/bing.ts
41800
- import crypto33 from "crypto";
41812
+ import crypto34 from "crypto";
41801
41813
  import { eq as eq42, and as and33, desc as desc19 } from "drizzle-orm";
41802
41814
 
41815
+ // ../integration-bing/src/bing-client.ts
41816
+ import crypto33 from "crypto";
41817
+
41803
41818
  // ../integration-bing/src/constants.ts
41804
41819
  var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
41805
41820
  var BING_SUBMIT_URL_BATCH_LIMIT = 500;
@@ -41808,6 +41823,7 @@ var BING_REQUEST_TIMEOUT_MS = 3e4;
41808
41823
  var BING_MAX_RETRIES = 4;
41809
41824
  var BING_RETRY_BASE_DELAY_MS = 2e3;
41810
41825
  var BING_RETRY_MAX_DELAY_MS = 3e4;
41826
+ var BING_THROTTLE_COOLDOWN_MS = 10 * 60 * 1e3;
41811
41827
 
41812
41828
  // ../integration-bing/src/types.ts
41813
41829
  var BING_THROTTLE_ERROR_CODES = /* @__PURE__ */ new Set([4, 5]);
@@ -41947,26 +41963,58 @@ async function bingFetchOnce(apiKey, endpoint, opts) {
41947
41963
  throw new BingApiError("Bing API returned invalid JSON", 502);
41948
41964
  }
41949
41965
  }
41966
+ var throttleCooldownUntil = /* @__PURE__ */ new Map();
41967
+ var cooldownNow = () => Date.now();
41968
+ function cooldownKey(apiKey) {
41969
+ return crypto33.createHash("sha256").update(apiKey).digest("hex").slice(0, 12);
41970
+ }
41971
+ function throttleCooldownRemainingMs(apiKey) {
41972
+ const until = throttleCooldownUntil.get(cooldownKey(apiKey));
41973
+ if (until == null) return 0;
41974
+ const remaining = until - cooldownNow();
41975
+ if (remaining <= 0) {
41976
+ throttleCooldownUntil.delete(cooldownKey(apiKey));
41977
+ return 0;
41978
+ }
41979
+ return remaining;
41980
+ }
41981
+ function openThrottleCooldown(apiKey) {
41982
+ throttleCooldownUntil.set(cooldownKey(apiKey), cooldownNow() + BING_THROTTLE_COOLDOWN_MS);
41983
+ bingClientLog("error", "http.cooldown-open", { cooldownMs: BING_THROTTLE_COOLDOWN_MS });
41984
+ }
41950
41985
  async function bingFetch(apiKey, endpoint, opts) {
41951
41986
  const isIdempotent = (opts?.method ?? "GET") === "GET";
41952
- return withRetry(() => bingFetchOnce(apiKey, endpoint, opts), {
41953
- maxRetries: BING_MAX_RETRIES,
41954
- baseDelayMs: BING_RETRY_BASE_DELAY_MS,
41955
- maxDelayMs: BING_RETRY_MAX_DELAY_MS,
41956
- isRetryable: (err) => {
41957
- if (err instanceof BingApiError && err.isThrottle) return true;
41958
- return isIdempotent && isRetryableHttpError(err);
41959
- },
41960
- computeDelayMs: (_attempt, err, defaultMs) => retryAfterDelayMs(err) ?? defaultMs,
41961
- onRetry: ({ attempt, err, delayMs }) => {
41962
- bingClientLog("warn", "http.retry", {
41963
- endpoint,
41964
- attempt,
41965
- delayMs: Math.round(delayMs),
41966
- throttled: err instanceof BingApiError ? err.isThrottle : false
41967
- });
41968
- }
41969
- });
41987
+ const cooling = throttleCooldownRemainingMs(apiKey);
41988
+ if (cooling > 0) {
41989
+ bingClientLog("warn", "http.cooldown-skip", { endpoint, remainingMs: cooling });
41990
+ throw new BingApiError(
41991
+ `Bing API key is in a throttle cooldown for another ${Math.ceil(cooling / 1e3)}s; the account was still throttled after a full retry budget, so this call was not attempted.`,
41992
+ 429
41993
+ );
41994
+ }
41995
+ try {
41996
+ return await withRetry(() => bingFetchOnce(apiKey, endpoint, opts), {
41997
+ maxRetries: BING_MAX_RETRIES,
41998
+ baseDelayMs: BING_RETRY_BASE_DELAY_MS,
41999
+ maxDelayMs: BING_RETRY_MAX_DELAY_MS,
42000
+ isRetryable: (err) => {
42001
+ if (err instanceof BingApiError && err.isThrottle) return true;
42002
+ return isIdempotent && isRetryableHttpError(err);
42003
+ },
42004
+ computeDelayMs: (_attempt, err, defaultMs) => retryAfterDelayMs(err) ?? defaultMs,
42005
+ onRetry: ({ attempt, err, delayMs }) => {
42006
+ bingClientLog("warn", "http.retry", {
42007
+ endpoint,
42008
+ attempt,
42009
+ delayMs: Math.round(delayMs),
42010
+ throttled: err instanceof BingApiError ? err.isThrottle : false
42011
+ });
42012
+ }
42013
+ });
42014
+ } catch (err) {
42015
+ if (err instanceof BingApiError && err.isThrottle) openThrottleCooldown(apiKey);
42016
+ throw err;
42017
+ }
41970
42018
  }
41971
42019
  async function getSites(apiKey) {
41972
42020
  validateApiKey(apiKey);
@@ -42237,7 +42285,7 @@ async function bingRoutes(app, opts) {
42237
42285
  const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
42238
42286
  const now = (/* @__PURE__ */ new Date()).toISOString();
42239
42287
  app.db.insert(bingCoverageSnapshots).values({
42240
- id: crypto33.randomUUID(),
42288
+ id: crypto34.randomUUID(),
42241
42289
  projectId: project.id,
42242
42290
  syncRunId: snapshotRunId,
42243
42291
  date: snapshotDate,
@@ -42308,7 +42356,7 @@ async function bingRoutes(app, opts) {
42308
42356
  throw validationError("url is required");
42309
42357
  }
42310
42358
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
42311
- const runId = crypto33.randomUUID();
42359
+ const runId = crypto34.randomUUID();
42312
42360
  app.db.insert(runs).values({
42313
42361
  id: runId,
42314
42362
  projectId: project.id,
@@ -42329,7 +42377,7 @@ async function bingRoutes(app, opts) {
42329
42377
  discoveryDate: result.DiscoveryDate ?? null
42330
42378
  });
42331
42379
  const now = (/* @__PURE__ */ new Date()).toISOString();
42332
- const id = crypto33.randomUUID();
42380
+ const id = crypto34.randomUUID();
42333
42381
  const httpCode = result.HttpStatus ?? result.HttpCode ?? null;
42334
42382
  const lastCrawledDate = parseBingDate(result.LastCrawledDate);
42335
42383
  const inIndexDate = parseBingDate(result.InIndexDate);
@@ -42399,7 +42447,7 @@ async function bingRoutes(app, opts) {
42399
42447
  throw validationError('No Bing site configured. Run "canonry bing set-site <project> <url>" first.');
42400
42448
  }
42401
42449
  const now = (/* @__PURE__ */ new Date()).toISOString();
42402
- const runId = crypto33.randomUUID();
42450
+ const runId = crypto34.randomUUID();
42403
42451
  app.db.insert(runs).values({
42404
42452
  id: runId,
42405
42453
  projectId: project.id,
@@ -42684,7 +42732,7 @@ async function cdpRoutes(app, opts) {
42684
42732
  }
42685
42733
 
42686
42734
  // ../api-routes/src/ga.ts
42687
- import crypto34 from "crypto";
42735
+ import crypto35 from "crypto";
42688
42736
  import { eq as eq44, desc as desc20, and as and35, sql as sql16 } from "drizzle-orm";
42689
42737
 
42690
42738
  // ../api-routes/src/ga-session-history.ts
@@ -42886,7 +42934,7 @@ function persistAcquisitionMeasurement(db, input) {
42886
42934
  )).run();
42887
42935
  for (const row of input.report.rows) {
42888
42936
  tx.insert(gaAcquisitionDaily).values({
42889
- id: crypto34.randomUUID(),
42937
+ id: crypto35.randomUUID(),
42890
42938
  projectId: input.projectId,
42891
42939
  date: row.date,
42892
42940
  channelGroup: row.channelGroup,
@@ -42928,7 +42976,7 @@ function persistLeadMeasurement(db, input) {
42928
42976
  )).run();
42929
42977
  for (const row of input.report.rows) {
42930
42978
  tx.insert(gaLeadEventsDaily).values({
42931
- id: crypto34.randomUUID(),
42979
+ id: crypto35.randomUUID(),
42932
42980
  projectId: input.projectId,
42933
42981
  date: row.date,
42934
42982
  eventName: row.eventName,
@@ -43208,7 +43256,7 @@ async function ga4Routes(app, opts) {
43208
43256
  const leadDays = measurementState?.leadSyncedAt != null ? Math.min(Math.max(1, days), 90) : 90;
43209
43257
  const leadEventNames = project.measurement.leadEventNames;
43210
43258
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
43211
- const runId = crypto34.randomUUID();
43259
+ const runId = crypto35.randomUUID();
43212
43260
  app.db.insert(runs).values({
43213
43261
  id: runId,
43214
43262
  projectId: project.id,
@@ -43262,7 +43310,7 @@ async function ga4Routes(app, opts) {
43262
43310
  ).run();
43263
43311
  for (const row of rows) {
43264
43312
  tx.insert(gaTrafficSnapshots).values({
43265
- id: crypto34.randomUUID(),
43313
+ id: crypto35.randomUUID(),
43266
43314
  projectId: project.id,
43267
43315
  date: row.date,
43268
43316
  landingPage: row.landingPage,
@@ -43285,7 +43333,7 @@ async function ga4Routes(app, opts) {
43285
43333
  ).run();
43286
43334
  for (const row of dailyTotals) {
43287
43335
  tx.insert(gaDailyTotals).values({
43288
- id: crypto34.randomUUID(),
43336
+ id: crypto35.randomUUID(),
43289
43337
  projectId: project.id,
43290
43338
  date: row.date,
43291
43339
  sessions: row.sessions,
@@ -43311,7 +43359,7 @@ async function ga4Routes(app, opts) {
43311
43359
  ).run();
43312
43360
  for (const row of aiReferrals) {
43313
43361
  tx.insert(gaAiReferrals).values({
43314
- id: crypto34.randomUUID(),
43362
+ id: crypto35.randomUUID(),
43315
43363
  projectId: project.id,
43316
43364
  date: row.date,
43317
43365
  source: row.source,
@@ -43343,7 +43391,7 @@ async function ga4Routes(app, opts) {
43343
43391
  ).run();
43344
43392
  for (const row of socialReferrals) {
43345
43393
  tx.insert(gaSocialReferrals).values({
43346
- id: crypto34.randomUUID(),
43394
+ id: crypto35.randomUUID(),
43347
43395
  projectId: project.id,
43348
43396
  date: row.date,
43349
43397
  source: row.source,
@@ -43359,7 +43407,7 @@ async function ga4Routes(app, opts) {
43359
43407
  if (syncSummary) {
43360
43408
  tx.delete(gaTrafficSummaries).where(eq44(gaTrafficSummaries.projectId, project.id)).run();
43361
43409
  tx.insert(gaTrafficSummaries).values({
43362
- id: crypto34.randomUUID(),
43410
+ id: crypto35.randomUUID(),
43363
43411
  projectId: project.id,
43364
43412
  periodStart: summary.periodStart,
43365
43413
  periodEnd: summary.periodEnd,
@@ -43372,7 +43420,7 @@ async function ga4Routes(app, opts) {
43372
43420
  tx.delete(gaTrafficWindowSummaries).where(eq44(gaTrafficWindowSummaries.projectId, project.id)).run();
43373
43421
  for (const ws of windowSummaries) {
43374
43422
  tx.insert(gaTrafficWindowSummaries).values({
43375
- id: crypto34.randomUUID(),
43423
+ id: crypto35.randomUUID(),
43376
43424
  projectId: project.id,
43377
43425
  windowKey: ws.windowKey,
43378
43426
  periodStart: ws.periodStart,
@@ -44053,7 +44101,7 @@ function parseSchemaPageEntry(entry) {
44053
44101
  }
44054
44102
 
44055
44103
  // ../integration-wordpress/src/wordpress-client.ts
44056
- import crypto35 from "crypto";
44104
+ import crypto36 from "crypto";
44057
44105
  function validateUsername(username) {
44058
44106
  if (!username || typeof username !== "string" || username.trim().length === 0) {
44059
44107
  throw new WordpressApiError("AUTH_INVALID", "Username is required and must be a non-empty string", 400);
@@ -44273,7 +44321,7 @@ function buildSnippet(content) {
44273
44321
  return `${text2.slice(0, 157)}...`;
44274
44322
  }
44275
44323
  function contentHash(content) {
44276
- return crypto35.createHash("sha256").update(content).digest("hex");
44324
+ return crypto36.createHash("sha256").update(content).digest("hex");
44277
44325
  }
44278
44326
  function buildAmbiguousSlugMessage(slug, pages) {
44279
44327
  const candidates = pages.map((page) => {
@@ -45573,7 +45621,7 @@ async function wordpressRoutes(app, opts) {
45573
45621
  }
45574
45622
 
45575
45623
  // ../api-routes/src/backlinks.ts
45576
- import crypto36 from "crypto";
45624
+ import crypto37 from "crypto";
45577
45625
  import { and as and37, asc as asc9, desc as desc21, eq as eq45, sql as sql17 } from "drizzle-orm";
45578
45626
 
45579
45627
  // ../integration-commoncrawl/src/constants.ts
@@ -46210,7 +46258,7 @@ async function backlinksRoutes(app, opts) {
46210
46258
  const refreshed = app.db.select().from(ccReleaseSyncs).where(eq45(ccReleaseSyncs.id, existing.id)).get();
46211
46259
  return reply.status(200).send(mapSyncRow(refreshed));
46212
46260
  }
46213
- const id = crypto36.randomUUID();
46261
+ const id = crypto37.randomUUID();
46214
46262
  app.db.insert(ccReleaseSyncs).values({
46215
46263
  id,
46216
46264
  release,
@@ -46267,7 +46315,7 @@ async function backlinksRoutes(app, opts) {
46267
46315
  throw validationError("Invalid release id");
46268
46316
  }
46269
46317
  const now = (/* @__PURE__ */ new Date()).toISOString();
46270
- const runId = crypto36.randomUUID();
46318
+ const runId = crypto37.randomUUID();
46271
46319
  app.db.insert(runs).values({
46272
46320
  id: runId,
46273
46321
  projectId: project.id,
@@ -46356,7 +46404,7 @@ async function backlinksRoutes(app, opts) {
46356
46404
  }
46357
46405
 
46358
46406
  // ../api-routes/src/traffic.ts
46359
- import crypto38 from "crypto";
46407
+ import crypto39 from "crypto";
46360
46408
  import { Agent as UndiciAgent } from "undici";
46361
46409
  import { and as and38, desc as desc22, eq as eq46, gte as gte10, lte as lte9, sql as sql18 } from "drizzle-orm";
46362
46410
 
@@ -46377,7 +46425,7 @@ function resolveVercelSyncDeadlineMs(env = process.env) {
46377
46425
  }
46378
46426
 
46379
46427
  // ../integration-cloud-run/src/auth.ts
46380
- import crypto37 from "crypto";
46428
+ import crypto38 from "crypto";
46381
46429
  var GOOGLE_TOKEN_URL3 = "https://oauth2.googleapis.com/token";
46382
46430
  var CLOUD_LOGGING_READ_SCOPE = "https://www.googleapis.com/auth/logging.read";
46383
46431
  var TOKEN_REQUEST_TIMEOUT_MS = 3e4;
@@ -46408,7 +46456,7 @@ function createServiceAccountJwt2(clientEmail, privateKey, scope) {
46408
46456
  const headerB64 = encode(header);
46409
46457
  const payloadB64 = encode(payload);
46410
46458
  const signingInput = `${headerB64}.${payloadB64}`;
46411
- const sign = crypto37.createSign("RSA-SHA256");
46459
+ const sign = crypto38.createSign("RSA-SHA256");
46412
46460
  sign.update(signingInput);
46413
46461
  const signature = sign.sign(privateKey, "base64url");
46414
46462
  return `${signingInput}.${signature}`;
@@ -50385,7 +50433,7 @@ async function runBackfillTask(options) {
50385
50433
  }
50386
50434
  })();
50387
50435
  tx.insert(rawEventSamples).values({
50388
- id: crypto38.randomUUID(),
50436
+ id: crypto39.randomUUID(),
50389
50437
  projectId: project.id,
50390
50438
  sourceId: sourceRow2.id,
50391
50439
  ts: sample.observedAt,
@@ -50514,7 +50562,7 @@ async function trafficRoutes(app, opts) {
50514
50562
  }).where(eq46(trafficSources.id, activeSource.id)).run();
50515
50563
  sourceRow2 = app.db.select().from(trafficSources).where(eq46(trafficSources.id, activeSource.id)).get();
50516
50564
  } else {
50517
- const newId = crypto38.randomUUID();
50565
+ const newId = crypto39.randomUUID();
50518
50566
  app.db.insert(trafficSources).values({
50519
50567
  id: newId,
50520
50568
  projectId: project.id,
@@ -50595,7 +50643,7 @@ async function trafficRoutes(app, opts) {
50595
50643
  }).where(eq46(trafficSources.id, activeSource.id)).run();
50596
50644
  sourceRow2 = app.db.select().from(trafficSources).where(eq46(trafficSources.id, activeSource.id)).get();
50597
50645
  } else {
50598
- const newId = crypto38.randomUUID();
50646
+ const newId = crypto39.randomUUID();
50599
50647
  app.db.insert(trafficSources).values({
50600
50648
  id: newId,
50601
50649
  projectId: project.id,
@@ -50679,7 +50727,7 @@ async function trafficRoutes(app, opts) {
50679
50727
  }).where(eq46(trafficSources.id, activeSource.id)).run();
50680
50728
  row = tx.select().from(trafficSources).where(eq46(trafficSources.id, activeSource.id)).get();
50681
50729
  } else {
50682
- const newId = crypto38.randomUUID();
50730
+ const newId = crypto39.randomUUID();
50683
50731
  tx.insert(trafficSources).values({
50684
50732
  id: newId,
50685
50733
  projectId: project.id,
@@ -50713,7 +50761,7 @@ async function trafficRoutes(app, opts) {
50713
50761
  let created = false;
50714
50762
  if (!existingSchedule) {
50715
50763
  tx.insert(schedules).values({
50716
- id: crypto38.randomUUID(),
50764
+ id: crypto39.randomUUID(),
50717
50765
  projectId: project.id,
50718
50766
  kind: SchedulableRunKinds["traffic-sync"],
50719
50767
  cronExpr: DEFAULT_TRAFFIC_SYNC_CRON,
@@ -50768,7 +50816,7 @@ async function trafficRoutes(app, opts) {
50768
50816
  const windowEnd = /* @__PURE__ */ new Date();
50769
50817
  const startedAt = windowEnd.toISOString();
50770
50818
  const syncStartedAtMs = windowEnd.getTime();
50771
- const runId = crypto38.randomUUID();
50819
+ const runId = crypto39.randomUUID();
50772
50820
  app.db.insert(runs).values({
50773
50821
  id: runId,
50774
50822
  projectId: project.id,
@@ -51161,7 +51209,7 @@ async function trafficRoutes(app, opts) {
51161
51209
  }
51162
51210
  })();
51163
51211
  tx.insert(rawEventSamples).values({
51164
- id: crypto38.randomUUID(),
51212
+ id: crypto39.randomUUID(),
51165
51213
  projectId: project.id,
51166
51214
  sourceId: sourceRow2.id,
51167
51215
  ts: sample.observedAt,
@@ -51412,7 +51460,7 @@ async function trafficRoutes(app, opts) {
51412
51460
  };
51413
51461
  }
51414
51462
  const startedAt = windowEnd.toISOString();
51415
- const runId = crypto38.randomUUID();
51463
+ const runId = crypto39.randomUUID();
51416
51464
  app.db.insert(runs).values({
51417
51465
  id: runId,
51418
51466
  projectId: project.id,
@@ -51782,7 +51830,7 @@ async function trafficRoutes(app, opts) {
51782
51830
  }
51783
51831
 
51784
51832
  // ../api-routes/src/doctor/checks/agent.ts
51785
- import crypto39 from "crypto";
51833
+ import crypto40 from "crypto";
51786
51834
  import fs6 from "fs";
51787
51835
  import path7 from "path";
51788
51836
  var REQUIRED_SKILLS = ["canonry", "aero"];
@@ -52013,7 +52061,7 @@ function isInstalled(dir) {
52013
52061
  }
52014
52062
  function hashInstalledFile(filePath) {
52015
52063
  try {
52016
- return crypto39.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
52064
+ return crypto40.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
52017
52065
  } catch {
52018
52066
  return void 0;
52019
52067
  }
@@ -54086,7 +54134,7 @@ async function doctorRoutes(app, opts) {
54086
54134
  }
54087
54135
 
54088
54136
  // ../api-routes/src/discovery/routes.ts
54089
- import crypto40 from "crypto";
54137
+ import crypto41 from "crypto";
54090
54138
  import { and as and42, desc as desc23, eq as eq53, gte as gte12, inArray as inArray17, isNull as isNull4, or as or10 } from "drizzle-orm";
54091
54139
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
54092
54140
  async function discoveryRoutes(app, opts) {
@@ -54152,8 +54200,8 @@ async function discoveryRoutes(app, opts) {
54152
54200
  if (existing && existing.runId) {
54153
54201
  return { reused: true, sessionId: existing.id, runId: existing.runId };
54154
54202
  }
54155
- const sessionId = crypto40.randomUUID();
54156
- const runId = crypto40.randomUUID();
54203
+ const sessionId = crypto41.randomUUID();
54204
+ const runId = crypto41.randomUUID();
54157
54205
  tx.insert(discoverySessions).values({
54158
54206
  id: sessionId,
54159
54207
  projectId: project.id,
@@ -54407,7 +54455,7 @@ async function discoveryRoutes(app, opts) {
54407
54455
  app.db.transaction((tx) => {
54408
54456
  for (const query of promotedQueries) {
54409
54457
  tx.insert(queries).values({
54410
- id: crypto40.randomUUID(),
54458
+ id: crypto41.randomUUID(),
54411
54459
  projectId: project.id,
54412
54460
  query,
54413
54461
  provenance,
@@ -54416,7 +54464,7 @@ async function discoveryRoutes(app, opts) {
54416
54464
  }
54417
54465
  for (const domain of promotedCompetitors) {
54418
54466
  tx.insert(competitors).values({
54419
- id: crypto40.randomUUID(),
54467
+ id: crypto41.randomUUID(),
54420
54468
  projectId: project.id,
54421
54469
  domain,
54422
54470
  provenance,
@@ -54506,7 +54554,7 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
54506
54554
  }
54507
54555
 
54508
54556
  // ../api-routes/src/discovery/orchestrate.ts
54509
- import crypto41 from "crypto";
54557
+ import crypto42 from "crypto";
54510
54558
  import { eq as eq54 } from "drizzle-orm";
54511
54559
  var DEFAULT_MAX_PROBES = 100;
54512
54560
  var ABSOLUTE_MAX_PROBES = 500;
@@ -54674,7 +54722,7 @@ async function executeDiscovery(opts) {
54674
54722
  probeRows.push({ citedDomains: probe.citedDomains, bucket });
54675
54723
  buckets[bucket]++;
54676
54724
  return {
54677
- id: crypto41.randomUUID(),
54725
+ id: crypto42.randomUUID(),
54678
54726
  sessionId: opts.sessionId,
54679
54727
  projectId: opts.project.id,
54680
54728
  query: probedCanonicals[index2],
@@ -54726,7 +54774,7 @@ function upsertDomainClassifications(db, projectId, sessionId, competitorMap) {
54726
54774
  const domain = hostOf(entry.domain) ?? "";
54727
54775
  if (!domain) continue;
54728
54776
  db.insert(domainClassifications).values({
54729
- id: crypto41.randomUUID(),
54777
+ id: crypto42.randomUUID(),
54730
54778
  projectId,
54731
54779
  domain,
54732
54780
  competitorType: entry.competitorType,
@@ -54766,7 +54814,7 @@ function dedupeStrings(input) {
54766
54814
  }
54767
54815
 
54768
54816
  // ../api-routes/src/technical-aeo.ts
54769
- import crypto42 from "crypto";
54817
+ import crypto43 from "crypto";
54770
54818
  import { and as and43, asc as asc10, count, desc as desc24, eq as eq55, inArray as inArray18, lt as lt7 } from "drizzle-orm";
54771
54819
  var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
54772
54820
  function emptyScore(projectName) {
@@ -54902,7 +54950,7 @@ async function technicalAeoRoutes(app, opts) {
54902
54950
  return { runId: existing.id, status: existing.status };
54903
54951
  }
54904
54952
  const now = (/* @__PURE__ */ new Date()).toISOString();
54905
- const runId = crypto42.randomUUID();
54953
+ const runId = crypto43.randomUUID();
54906
54954
  app.db.insert(runs).values({
54907
54955
  id: runId,
54908
54956
  projectId: project.id,
@@ -54920,7 +54968,7 @@ async function technicalAeoRoutes(app, opts) {
54920
54968
  }
54921
54969
 
54922
54970
  // ../api-routes/src/research.ts
54923
- import crypto43 from "crypto";
54971
+ import crypto44 from "crypto";
54924
54972
  import { and as and44, desc as desc25, eq as eq56 } from "drizzle-orm";
54925
54973
  var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
54926
54974
  async function researchRoutes(app, opts) {
@@ -54947,7 +54995,7 @@ async function researchRoutes(app, opts) {
54947
54995
  if (!adapter.modelValidationPattern.test(resolvedModel)) throw validationError(`Invalid resolved model "${resolvedModel}" for provider "${providerName}".`, { provider: providerName, model: resolvedModel, hint: adapter.modelValidationHint });
54948
54996
  if (new Set(input.queries.map((query) => query.toLocaleLowerCase())).size !== input.queries.length) throw validationError("Research queries must be unique within a batch.");
54949
54997
  const normalized = { queries: input.queries, provider: providerName, model: requestedModel, location: location ?? null };
54950
- const requestHash2 = crypto43.createHash("sha256").update(JSON.stringify(normalized)).digest("hex");
54998
+ const requestHash2 = crypto44.createHash("sha256").update(JSON.stringify(normalized)).digest("hex");
54951
54999
  const now = (/* @__PURE__ */ new Date()).toISOString();
54952
55000
  const decision = app.db.transaction((tx) => {
54953
55001
  if (input.idempotencyKey) {
@@ -54957,9 +55005,9 @@ async function researchRoutes(app, opts) {
54957
55005
  return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
54958
55006
  }
54959
55007
  }
54960
- const id = crypto43.randomUUID();
55008
+ const id = crypto44.randomUUID();
54961
55009
  tx.insert(researchRuns).values({ id, projectId: project.id, status: ResearchRunStatuses.queued, provider: providerName, requestedModel, resolvedModel, location: location ?? null, totalQueries: input.queries.length, idempotencyKey: input.idempotencyKey ?? null, requestHash: input.idempotencyKey ? requestHash2 : null, createdAt: now }).run();
54962
- for (const [position, query] of input.queries.entries()) tx.insert(researchRunQueries).values({ id: crypto43.randomUUID(), researchRunId: id, position, queryText: query, status: ResearchQueryStatuses.queued, requestedModel, resolvedModel, groundingSources: [], citedDomains: [], searchQueries: [], createdAt: now }).run();
55010
+ for (const [position, query] of input.queries.entries()) tx.insert(researchRunQueries).values({ id: crypto44.randomUUID(), researchRunId: id, position, queryText: query, status: ResearchQueryStatuses.queued, requestedModel, resolvedModel, groundingSources: [], citedDomains: [], searchQueries: [], createdAt: now }).run();
54963
55011
  writeAuditLog(tx, { projectId: project.id, actor: "api", action: "research.created", entityType: "research_run", entityId: id });
54964
55012
  return { reused: false, id, shouldDispatch: true };
54965
55013
  });
@@ -55376,7 +55424,7 @@ function buildTrafficSourceValidators(opts) {
55376
55424
  }
55377
55425
 
55378
55426
  // src/intelligence-service.ts
55379
- import crypto44 from "crypto";
55427
+ import crypto45 from "crypto";
55380
55428
 
55381
55429
  // src/logger.ts
55382
55430
  var IS_TTY = process.stdout.isTTY === true;
@@ -56017,7 +56065,7 @@ var IntelligenceService = class {
56017
56065
  }).run();
56018
56066
  }
56019
56067
  tx.insert(healthSnapshots).values({
56020
- id: crypto44.randomUUID(),
56068
+ id: crypto45.randomUUID(),
56021
56069
  projectId,
56022
56070
  runId,
56023
56071
  overallCitedRate: String(result.health.overallCitedRate),
@@ -4084,7 +4084,12 @@ var measurementOverviewResponseSchema = z5.object({
4084
4084
  )
4085
4085
  }).strict(),
4086
4086
  properties: measurementCursorPageSchema(measurementPropertyRowSchema),
4087
- /** Scope-wide outcome split. Counted over EVERY Property in scope, not the page. */
4087
+ /**
4088
+ * Outcome split over the whole RESULT SET, not the page — so paging through
4089
+ * does not move it. It narrows with `search` exactly as `properties.totalEstimate`
4090
+ * does, because both are computed from the same filtered rows; with no search
4091
+ * that result set is the entire scope.
4092
+ */
4088
4093
  outcomes: measurementOutcomeCountsSchema,
4089
4094
  flags: z5.object({ total: z5.number().int().nonnegative() }).strict(),
4090
4095
  namedShareOfVoice: measurementNamedShareOfVoiceSchema.optional()
package/dist/cli.js CHANGED
@@ -30,7 +30,7 @@ import {
30
30
  showFirstRunNotice,
31
31
  trackCliCommandFinished,
32
32
  trackEvent
33
- } from "./chunk-YV4VKT4M.js";
33
+ } from "./chunk-JOP2DGXL.js";
34
34
  import {
35
35
  CliError,
36
36
  EXIT_SYSTEM_ERROR,
@@ -50,7 +50,7 @@ import {
50
50
  saveConfigPatch,
51
51
  systemError,
52
52
  usageError
53
- } from "./chunk-LNJF7LL5.js";
53
+ } from "./chunk-ANSQ6JNO.js";
54
54
  import {
55
55
  apiKeys,
56
56
  createClient,
@@ -58,7 +58,7 @@ import {
58
58
  projects,
59
59
  queries,
60
60
  renderReportHtml
61
- } from "./chunk-VTL2WOIO.js";
61
+ } from "./chunk-KRMLGFY3.js";
62
62
  import {
63
63
  AdsDeliverySnapshotStatuses,
64
64
  AdsHistoricalCampaignRollupStatuses,
@@ -122,7 +122,7 @@ import {
122
122
  providerQuotaPolicySchema,
123
123
  resolveProviderInput,
124
124
  winnabilityClassSchema
125
- } from "./chunk-SKTXCZZD.js";
125
+ } from "./chunk-NNW7TJUZ.js";
126
126
 
127
127
  // src/cli.ts
128
128
  import { pathToFileURL } from "url";
@@ -13163,6 +13163,8 @@ import fs14 from "fs";
13163
13163
  import path11 from "path";
13164
13164
  var CANONRY_PLUGIN_ID = "canonry@canonry";
13165
13165
  var REQUIRED_SKILLS = ["canonry", "aero"];
13166
+ var AGENT_PLUGINS_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json";
13167
+ var AGENT_PLUGINS_MCP_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json";
13166
13168
  function parseSemver(version) {
13167
13169
  const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9a-z.-]+))?(?:\+[0-9a-z.-]+)?$/i.exec(version);
13168
13170
  if (!match) return null;
@@ -13254,6 +13256,27 @@ function readCodexPluginConfigured(filePath) {
13254
13256
  }
13255
13257
  }
13256
13258
  function verifiedPluginVersion(root, client) {
13259
+ if (client === "codex" && fs14.existsSync(path11.join(root, "plugin.json"))) {
13260
+ const manifest2 = readJson(path11.join(root, "plugin.json"));
13261
+ if (manifest2 && typeof manifest2 === "object") {
13262
+ const plugin2 = manifest2;
13263
+ if (plugin2.$schema === AGENT_PLUGINS_SCHEMA) {
13264
+ if (plugin2.name !== "canonry" || typeof plugin2.version !== "string" || !parseSemver(plugin2.version)) {
13265
+ return null;
13266
+ }
13267
+ const mcp2 = readJson(path11.join(root, "mcp.json"));
13268
+ if (!mcp2 || typeof mcp2 !== "object") return null;
13269
+ const portableMcp = mcp2;
13270
+ if (portableMcp.$schema !== AGENT_PLUGINS_MCP_SCHEMA || portableMcp.mcpServers?.canonry?.type !== "stdio" || portableMcp.mcpServers.canonry.command !== "canonry-mcp") {
13271
+ return null;
13272
+ }
13273
+ return REQUIRED_SKILLS.every((name) => fs14.existsSync(path11.join(root, "skills", name, "SKILL.md"))) ? plugin2.version : null;
13274
+ }
13275
+ if (typeof plugin2.$schema === "string" && plugin2.$schema.startsWith("https://agent-plugins.org/schemas/")) {
13276
+ return null;
13277
+ }
13278
+ }
13279
+ }
13257
13280
  const manifestDir = client === "claude-code" ? ".claude-plugin" : ".codex-plugin";
13258
13281
  const manifest = readJson(path11.join(root, manifestDir, "plugin.json"));
13259
13282
  if (!manifest || typeof manifest !== "object") return null;