@ainyc/canonry 4.67.0 → 4.69.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 (28) hide show
  1. package/README.md +1 -0
  2. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +8 -1
  3. package/assets/assets/{BacklinksPage-B9oyoljV.js → BacklinksPage-BXVoTc3S.js} +1 -1
  4. package/assets/assets/ChartPrimitives-pJRJPd17.js +1 -0
  5. package/assets/assets/ProjectPage-CQ1_itHh.js +6 -0
  6. package/assets/assets/{RunRow-vHM36Fdi.js → RunRow-DPL4FxPl.js} +1 -1
  7. package/assets/assets/{RunsPage-Cr58nTet.js → RunsPage-B4dCG_66.js} +1 -1
  8. package/assets/assets/{SettingsPage-BuiP8ZOv.js → SettingsPage-D8aWhLsU.js} +1 -1
  9. package/assets/assets/{TrafficPage-bwOxChyo.js → TrafficPage-COZa5_Q_.js} +1 -1
  10. package/assets/assets/{TrafficSourceDetailPage-B0uY6VIB.js → TrafficSourceDetailPage-CN8Cx6YI.js} +1 -1
  11. package/assets/assets/{extract-error-message-CWdzuNp4.js → extract-error-message-D8g8YXDH.js} +1 -1
  12. package/assets/assets/index-BUNCrWTe.css +1 -0
  13. package/assets/assets/{index-DiN_mzYU.js → index-DPO3uDWZ.js} +79 -79
  14. package/assets/assets/{server-traffic-D3aICbxr.js → server-traffic-0JT1Vbj_.js} +1 -1
  15. package/assets/assets/{trash-2-CVKno2W2.js → trash-2-_1TgguOP.js} +1 -1
  16. package/assets/index.html +2 -2
  17. package/dist/{chunk-KHN3XMOR.js → chunk-B2CH7GBW.js} +93 -28
  18. package/dist/{chunk-34PATQZM.js → chunk-D75O5A27.js} +36 -1
  19. package/dist/{chunk-4V3V4MFF.js → chunk-WQ44ZXGQ.js} +45 -8
  20. package/dist/{chunk-RQCVITY4.js → chunk-YYFBMDLC.js} +15 -1
  21. package/dist/cli.js +27 -11
  22. package/dist/index.js +4 -4
  23. package/dist/{intelligence-service-SMU5JVVD.js → intelligence-service-IUKD3PMZ.js} +2 -2
  24. package/dist/mcp.js +2 -2
  25. package/package.json +11 -10
  26. package/assets/assets/ChartPrimitives-CvfM24iC.js +0 -1
  27. package/assets/assets/ProjectPage-DELbOAlm.js +0 -6
  28. package/assets/assets/index-yMJe1bJR.css +0 -1
@@ -1 +1 @@
1
- import{c as d,j as a,bT as S,bN as l,bU as v,au as m,l as t,bV as y,bO as i,bW as T,bX as h,bY as p,bZ as b}from"./index-DiN_mzYU.js";import{u as s,r as C,n as c,o as u}from"./vendor-tanstack-Dq7p98wZ.js";const g=[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]],M=d("refresh-cw",g);function P(e){switch(e){case i.connected:return"positive";case i.paused:return"caution";case i.error:return"negative";case i.archived:return"neutral"}}function w(e){const r={};return e.kind&&e.kind!=="all"&&(r.kind=e.kind),e.sourceId&&(r.sourceId=e.sourceId),e.sinceMinutes!==void 0&&(r.since=new Date(Date.now()-e.sinceMinutes*6e4).toISOString()),e.limit!==void 0&&(r.limit=String(e.limit)),r}function o(e){e.invalidateQueries({predicate:r=>{const n=r.queryKey[0];return typeof n?._id=="string"&&n._id.startsWith("getApiV1ProjectsByNameTraffic")}})}function V(e){return s({...S({client:t,path:{name:e??""}}),enabled:!!e,staleTime:a})}function k(e){return s({...b({client:t,path:{name:e??""}}),enabled:!!e,staleTime:a})}function F(e,r){return s({...l({client:t,path:{name:e??"",id:r??""}}),enabled:!!(e&&r),staleTime:a})}function A(e,r){const n=C.useMemo(()=>w(r),[r.kind,r.sourceId,r.sinceMinutes,r.limit]);return s({...v({client:t,path:{name:e??""},query:n}),enabled:!!e,staleTime:a})}function E(e){const r=c();return u({mutationFn:n=>{if(!e)throw new Error("Project is required to connect a Cloud Run source");return p(e,n)},onSuccess:()=>{e&&o(r)}})}function I(e){const r=c();return u({mutationFn:n=>{if(!e)throw new Error("Project is required to connect a WordPress source");return T(e,n)},onSuccess:()=>{e&&o(r)}})}function N(e){const r=c();return u({mutationFn:n=>{if(!e)throw new Error("Project is required to connect a Vercel source");return h(e,n)},onSuccess:()=>{e&&o(r)}})}function Q(e,r){const n=c();return u({mutationFn:f=>{if(!e||!r)throw new Error("Project and sourceId are required to sync");return y(e,r,f??void 0)},onSuccess:()=>{e&&(o(n),n.invalidateQueries({queryKey:m({client:t})}))}})}export{M as R,I as a,N as b,E as c,V as d,F as e,A as f,Q as g,P as t,k as u};
1
+ import{c as d,j as a,bU as S,bO as l,bV as v,av as m,l as t,bW as y,bP as i,bX as T,bY as h,bZ as p,b_ as b}from"./index-DPO3uDWZ.js";import{u as s,r as C,n as c,o as u}from"./vendor-tanstack-Dq7p98wZ.js";const g=[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]],B=d("refresh-cw",g);function M(e){switch(e){case i.connected:return"positive";case i.paused:return"caution";case i.error:return"negative";case i.archived:return"neutral"}}function w(e){const r={};return e.kind&&e.kind!=="all"&&(r.kind=e.kind),e.sourceId&&(r.sourceId=e.sourceId),e.sinceMinutes!==void 0&&(r.since=new Date(Date.now()-e.sinceMinutes*6e4).toISOString()),e.limit!==void 0&&(r.limit=String(e.limit)),r}function o(e){e.invalidateQueries({predicate:r=>{const n=r.queryKey[0];return typeof n?._id=="string"&&n._id.startsWith("getApiV1ProjectsByNameTraffic")}})}function V(e){return s({...S({client:t,path:{name:e??""}}),enabled:!!e,staleTime:a})}function k(e){return s({...b({client:t,path:{name:e??""}}),enabled:!!e,staleTime:a})}function F(e,r){return s({...l({client:t,path:{name:e??"",id:r??""}}),enabled:!!(e&&r),staleTime:a})}function A(e,r){const n=C.useMemo(()=>w(r),[r.kind,r.sourceId,r.sinceMinutes,r.limit]);return s({...v({client:t,path:{name:e??""},query:n}),enabled:!!e,staleTime:a})}function E(e){const r=c();return u({mutationFn:n=>{if(!e)throw new Error("Project is required to connect a Cloud Run source");return p(e,n)},onSuccess:()=>{e&&o(r)}})}function I(e){const r=c();return u({mutationFn:n=>{if(!e)throw new Error("Project is required to connect a WordPress source");return T(e,n)},onSuccess:()=>{e&&o(r)}})}function Q(e){const r=c();return u({mutationFn:n=>{if(!e)throw new Error("Project is required to connect a Vercel source");return h(e,n)},onSuccess:()=>{e&&o(r)}})}function R(e,r){const n=c();return u({mutationFn:f=>{if(!e||!r)throw new Error("Project and sourceId are required to sync");return y(e,r,f??void 0)},onSuccess:()=>{e&&(o(n),n.invalidateQueries({queryKey:m({client:t})}))}})}export{B as R,I as a,Q as b,E as c,V as d,F as e,A as f,R as g,M as t,k as u};
@@ -1 +1 @@
1
- import{c}from"./index-DiN_mzYU.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
1
+ import{c}from"./index-DPO3uDWZ.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
package/assets/index.html CHANGED
@@ -12,12 +12,12 @@
12
12
  <link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
13
13
  <link rel="apple-touch-icon" href="./apple-touch-icon.png" />
14
14
  <title>Canonry</title>
15
- <script type="module" crossorigin src="./assets/index-DiN_mzYU.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-DPO3uDWZ.js"></script>
16
16
  <link rel="modulepreload" crossorigin href="./assets/vendor-tanstack-Dq7p98wZ.js">
17
17
  <link rel="modulepreload" crossorigin href="./assets/vendor-radix-B57xfQbP.js">
18
18
  <link rel="modulepreload" crossorigin href="./assets/vendor-recharts-ClRVR6aX.js">
19
19
  <link rel="modulepreload" crossorigin href="./assets/vendor-markdown-DK7fbRNb.js">
20
- <link rel="stylesheet" crossorigin href="./assets/index-yMJe1bJR.css">
20
+ <link rel="stylesheet" crossorigin href="./assets/index-BUNCrWTe.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
@@ -51,6 +51,7 @@ import {
51
51
  bingUrlInspectionDtoSchema,
52
52
  brandKeyFromText,
53
53
  brandLabelFromDomain,
54
+ brandMetricsDtoSchema,
54
55
  categorizeSource,
55
56
  categorizeSourceWithCompetitors,
56
57
  categoryLabel,
@@ -207,7 +208,7 @@ import {
207
208
  wordpressSchemaDeployResultDtoSchema,
208
209
  wordpressSchemaStatusResultDtoSchema,
209
210
  wordpressStatusDtoSchema
210
- } from "./chunk-34PATQZM.js";
211
+ } from "./chunk-D75O5A27.js";
211
212
 
212
213
  // src/intelligence-service.ts
213
214
  import { eq as eq30, desc as desc15, asc as asc3, and as and23, ne as ne5, or as or5, inArray as inArray11, gte as gte6, lte as lte3 } from "drizzle-orm";
@@ -6131,6 +6132,7 @@ import crypto10 from "crypto";
6131
6132
  import { and as and3, eq as eq8 } from "drizzle-orm";
6132
6133
 
6133
6134
  // ../api-routes/src/schedule-utils.ts
6135
+ import { CronExpressionParser } from "cron-parser";
6134
6136
  var DAY_MAP = {
6135
6137
  sun: "0",
6136
6138
  mon: "1",
@@ -6217,6 +6219,17 @@ function isValidTimezone(tz) {
6217
6219
  return false;
6218
6220
  }
6219
6221
  }
6222
+ function nextRunFromCron(cronExpr, timezone, from = /* @__PURE__ */ new Date()) {
6223
+ try {
6224
+ const interval = CronExpressionParser.parse(cronExpr, {
6225
+ currentDate: from,
6226
+ tz: timezone
6227
+ });
6228
+ return interval.next().toDate().toISOString();
6229
+ } catch {
6230
+ return null;
6231
+ }
6232
+ }
6220
6233
 
6221
6234
  // ../api-routes/src/webhooks.ts
6222
6235
  import crypto9 from "crypto";
@@ -7243,6 +7256,10 @@ function computeBuckets(snapshots, projectRuns, bucketDays, queryCreatedAt) {
7243
7256
  }
7244
7257
  const metric = computeProviderMetric(usable);
7245
7258
  const queryCount = new Set(usable.map((s) => s.queryId)).size;
7259
+ const byProvider = {};
7260
+ for (const provider of new Set(usable.map((s) => s.provider))) {
7261
+ byProvider[provider] = computeProviderMetric(usable.filter((s) => s.provider === provider));
7262
+ }
7246
7263
  buckets.push({
7247
7264
  startDate: startISO,
7248
7265
  endDate: endISO,
@@ -7251,7 +7268,8 @@ function computeBuckets(snapshots, projectRuns, bucketDays, queryCreatedAt) {
7251
7268
  total: metric.total,
7252
7269
  queryCount,
7253
7270
  mentionRate: metric.mentionRate,
7254
- mentionedCount: metric.mentionedCount
7271
+ mentionedCount: metric.mentionedCount,
7272
+ byProvider
7255
7273
  });
7256
7274
  }
7257
7275
  start = end;
@@ -12012,6 +12030,7 @@ async function compositeRoutes(app) {
12012
12030
  const attentionItems = buildAttentionItems(insightRows, allRuns);
12013
12031
  const sparklineRuns = visibilityRuns.slice(0, DEFAULT_RUN_HISTORY_LIMIT).map((r) => ({ id: r.id, createdAt: r.createdAt, status: r.status }));
12014
12032
  const runHistory = buildRunHistory(sparklineRuns, snapshotsByRun);
12033
+ scores.visibility.trend = runHistory.map((p) => p.citationRate);
12015
12034
  const suggestedQueries = buildSuggestedQueriesFromGsc(
12016
12035
  app,
12017
12036
  project.id,
@@ -12171,17 +12190,30 @@ function loadSnapshotsByRunIds(app, runIds) {
12171
12190
  }
12172
12191
  function summarizeFromSnapshots(snapshots) {
12173
12192
  const empty = {
12174
- queryCounts: { totalQueries: 0, citedQueries: 0, notCitedQueries: 0, citedRate: 0 },
12193
+ queryCounts: {
12194
+ totalQueries: 0,
12195
+ citedQueries: 0,
12196
+ notCitedQueries: 0,
12197
+ citedRate: 0,
12198
+ mentionedQueries: 0,
12199
+ notMentionedQueries: 0,
12200
+ mentionRate: 0
12201
+ },
12175
12202
  providers: []
12176
12203
  };
12177
12204
  if (snapshots.length === 0) return empty;
12178
12205
  const perQuery = /* @__PURE__ */ new Map();
12206
+ const perQueryMentioned = /* @__PURE__ */ new Map();
12179
12207
  const perProvider = /* @__PURE__ */ new Map();
12180
12208
  for (const snap of snapshots) {
12181
12209
  const cited = snap.citationState === CitationStates.cited;
12182
12210
  if (!perQuery.has(snap.queryId) || cited) {
12183
12211
  perQuery.set(snap.queryId, cited);
12184
12212
  }
12213
+ const mentioned = snap.answerMentioned === true;
12214
+ if (!perQueryMentioned.has(snap.queryId) || mentioned) {
12215
+ perQueryMentioned.set(snap.queryId, mentioned);
12216
+ }
12185
12217
  const bucket = perProvider.get(snap.provider) ?? { cited: 0, total: 0 };
12186
12218
  bucket.total += 1;
12187
12219
  if (cited) bucket.cited += 1;
@@ -12194,6 +12226,12 @@ function summarizeFromSnapshots(snapshots) {
12194
12226
  }
12195
12227
  const notCitedQueries = totalQueries - citedQueries;
12196
12228
  const citedRate = totalQueries === 0 ? 0 : Number((citedQueries / totalQueries).toFixed(4));
12229
+ let mentionedQueries = 0;
12230
+ for (const wasMentioned of perQueryMentioned.values()) {
12231
+ if (wasMentioned) mentionedQueries += 1;
12232
+ }
12233
+ const notMentionedQueries = totalQueries - mentionedQueries;
12234
+ const mentionRate = totalQueries === 0 ? 0 : Number((mentionedQueries / totalQueries).toFixed(4));
12197
12235
  const providers = [...perProvider.entries()].map(([provider, { cited, total }]) => ({
12198
12236
  provider,
12199
12237
  cited,
@@ -12201,7 +12239,15 @@ function summarizeFromSnapshots(snapshots) {
12201
12239
  citedRate: total === 0 ? 0 : Number((cited / total).toFixed(4))
12202
12240
  })).sort((a, b) => a.provider.localeCompare(b.provider));
12203
12241
  return {
12204
- queryCounts: { totalQueries, citedQueries, notCitedQueries, citedRate },
12242
+ queryCounts: {
12243
+ totalQueries,
12244
+ citedQueries,
12245
+ notCitedQueries,
12246
+ citedRate,
12247
+ mentionedQueries,
12248
+ notMentionedQueries,
12249
+ mentionRate
12250
+ },
12205
12251
  providers
12206
12252
  };
12207
12253
  }
@@ -12544,6 +12590,7 @@ var SCHEMA_TABLE = {
12544
12590
  BingSitesResponseDto: bingSitesResponseDtoSchema,
12545
12591
  BingStatusDto: bingStatusDtoSchema,
12546
12592
  BingUrlInspectionDto: bingUrlInspectionDtoSchema,
12593
+ BrandMetricsDto: brandMetricsDtoSchema,
12547
12594
  CcAvailableRelease: ccAvailableReleaseSchema,
12548
12595
  CcCachedRelease: ccCachedReleaseSchema,
12549
12596
  CcReleaseSyncDto: ccReleaseSyncDtoSchema,
@@ -12595,6 +12642,7 @@ var SCHEMA_TABLE = {
12595
12642
  QueryDto: queryDtoSchema,
12596
12643
  RunDetailDto: runDetailDtoSchema,
12597
12644
  RunDto: runDtoSchema,
12645
+ SchedulableRunKind: schedulableRunKindSchema,
12598
12646
  ScheduleDto: scheduleDtoSchema,
12599
12647
  SettingsDto: settingsDtoSchema,
12600
12648
  SnapshotDiffResponse: snapshotDiffResponseSchema,
@@ -12630,11 +12678,20 @@ var SCHEMA_TABLE = {
12630
12678
  })
12631
12679
  })
12632
12680
  };
12681
+ var SHARED_ENUM_REFS = [
12682
+ { schema: "ScheduleDto", property: "kind", component: "SchedulableRunKind" }
12683
+ ];
12633
12684
  function buildComponentSchemas() {
12634
12685
  const out = {};
12635
12686
  for (const [name, schema] of Object.entries(SCHEMA_TABLE)) {
12636
12687
  out[name] = z.toJSONSchema(schema, { target: "openapi-3.0" });
12637
12688
  }
12689
+ for (const { schema, property, component } of SHARED_ENUM_REFS) {
12690
+ const properties = out[schema]?.properties;
12691
+ if (properties && properties[property]) {
12692
+ properties[property] = { $ref: `#/components/schemas/${component}` };
12693
+ }
12694
+ }
12638
12695
  return out;
12639
12696
  }
12640
12697
  function jsonResponse(description, schemaName) {
@@ -12680,7 +12737,6 @@ var integerSchema = { type: "integer" };
12680
12737
  var objectSchema = { type: "object", additionalProperties: true };
12681
12738
  var stringArraySchema = { type: "array", items: stringSchema };
12682
12739
  var googleConnectionTypeSchema = { type: "string", enum: ["gsc", "ga4", "gbp"] };
12683
- var scheduleKindEnum = Object.values(SchedulableRunKinds);
12684
12740
  var locationSchema = {
12685
12741
  type: "object",
12686
12742
  required: ["label", "city", "region", "country"],
@@ -12770,7 +12826,7 @@ var scheduleKindQueryParameter = {
12770
12826
  name: "kind",
12771
12827
  in: "query",
12772
12828
  description: 'Schedulable run kind. Defaults to "answer-visibility" for backward compatibility.',
12773
- schema: { type: "string", enum: scheduleKindEnum }
12829
+ schema: { $ref: "#/components/schemas/SchedulableRunKind" }
12774
12830
  };
12775
12831
  var runsListKindQueryParameter = {
12776
12832
  name: "kind",
@@ -13516,8 +13572,7 @@ var routeCatalog = [
13516
13572
  tags: ["analytics"],
13517
13573
  parameters: [nameParameter, analyticsWindowParameter],
13518
13574
  responses: {
13519
- // TODO: Add `BrandMetricsDto` Zod schema in contracts.
13520
- 200: rawJsonResponse("Citation metrics returned.", looseObjectSchema),
13575
+ 200: jsonResponse("Citation metrics returned.", "BrandMetricsDto"),
13521
13576
  404: errorResponse("Project not found.")
13522
13577
  }
13523
13578
  },
@@ -13731,7 +13786,7 @@ var routeCatalog = [
13731
13786
  schema: {
13732
13787
  type: "object",
13733
13788
  properties: {
13734
- kind: { type: "string", enum: scheduleKindEnum },
13789
+ kind: { $ref: "#/components/schemas/SchedulableRunKind" },
13735
13790
  preset: stringSchema,
13736
13791
  cron: stringSchema,
13737
13792
  timezone: stringSchema,
@@ -16583,6 +16638,9 @@ async function scheduleRoutes(app, opts) {
16583
16638
  } else if (sourceId) {
16584
16639
  throw validationError(`"sourceId" is only valid when kind is "traffic-sync"`);
16585
16640
  }
16641
+ if (kind === SchedulableRunKinds["backlinks-sync"] && providers && providers.length > 0) {
16642
+ throw validationError('"providers" is not valid for kind "backlinks-sync"');
16643
+ }
16586
16644
  const validNames = opts.validProviderNames ?? [];
16587
16645
  if (validNames.length && providers?.length) {
16588
16646
  const invalid = providers.filter((p) => !validNames.includes(p));
@@ -22781,7 +22839,8 @@ var PLUGIN_DIR = path2.join(os2.homedir(), ".canonry", "plugins");
22781
22839
  var PLUGIN_PKG_JSON = path2.join(PLUGIN_DIR, "package.json");
22782
22840
  var DUCKDB_SPEC = process.env.CANONRY_DUCKDB_SPEC ?? "@duckdb/node-api@1.4.4-r.3";
22783
22841
  var CC_CACHE_DIR = process.env.CANONRY_CC_CACHE_DIR ?? path2.join(os2.homedir(), ".canonry", "cache", "commoncrawl");
22784
- var RELEASE_ID_REGEX = /^cc-main-(\d{4})-(jan-feb-mar|apr-may-jun|jul-aug-sep|oct-nov-dec)$/;
22842
+ var CC_MONTH = "(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)";
22843
+ var RELEASE_ID_REGEX = new RegExp(`^cc-main-(\\d{4})-(${CC_MONTH}-${CC_MONTH}-${CC_MONTH})$`);
22785
22844
  function ccReleasePaths(release) {
22786
22845
  const base = `${CC_BASE_URL}/${release}/domain`;
22787
22846
  const vertexFilename = `${release}-domain-vertices.txt.gz`;
@@ -22806,23 +22865,28 @@ function forwardDomain(revDomain) {
22806
22865
  function isValidReleaseId(id) {
22807
22866
  return RELEASE_ID_REGEX.test(id);
22808
22867
  }
22809
- function formatReleaseId(year, quarter) {
22810
- return `cc-main-${year}-${quarter}`;
22868
+ function formatReleaseId(year, window) {
22869
+ return `cc-main-${year}-${window}`;
22811
22870
  }
22812
22871
 
22813
22872
  // ../integration-commoncrawl/src/release-discovery.ts
22814
- var QUARTERS = [
22815
- "oct-nov-dec",
22816
- "jul-aug-sep",
22817
- "apr-may-jun",
22818
- "jan-feb-mar"
22819
- ];
22820
- function probeCandidates(now, maxBack) {
22821
- const year = now.getUTCFullYear();
22873
+ var MONTHS = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
22874
+ function windowSlug(firstMonthIndex) {
22875
+ const a = MONTHS[firstMonthIndex % 12];
22876
+ const b = MONTHS[(firstMonthIndex + 1) % 12];
22877
+ const c = MONTHS[(firstMonthIndex + 2) % 12];
22878
+ return `${a}-${b}-${c}`;
22879
+ }
22880
+ function probeCandidates(now, maxMonthsBack) {
22881
+ let year = now.getUTCFullYear();
22882
+ let month = now.getUTCMonth();
22822
22883
  const out = [];
22823
- for (let y = year; y >= year - maxBack; y--) {
22824
- for (const q of QUARTERS) {
22825
- out.push({ year: y, quarter: q });
22884
+ for (let step = 0; step <= maxMonthsBack; step++) {
22885
+ out.push({ year, window: windowSlug(month) });
22886
+ month -= 1;
22887
+ if (month < 0) {
22888
+ month = 11;
22889
+ year -= 1;
22826
22890
  }
22827
22891
  }
22828
22892
  return out;
@@ -22845,11 +22909,11 @@ async function probeRelease(release, fetchImpl = fetch) {
22845
22909
  }
22846
22910
  async function probeLatestRelease(opts = {}) {
22847
22911
  const now = opts.now ?? /* @__PURE__ */ new Date();
22848
- const maxBack = opts.maxQuartersBack ?? 3;
22912
+ const maxBack = opts.maxMonthsBack ?? 14;
22849
22913
  const fetchImpl = opts.fetchImpl ?? fetch;
22850
22914
  const candidates = probeCandidates(now, maxBack);
22851
- for (const { year, quarter } of candidates) {
22852
- const release = formatReleaseId(year, quarter);
22915
+ for (const { year, window } of candidates) {
22916
+ const release = formatReleaseId(year, window);
22853
22917
  const result = await probeRelease(release, fetchImpl);
22854
22918
  if (result) return result;
22855
22919
  }
@@ -23312,7 +23376,7 @@ async function backlinksRoutes(app, opts) {
23312
23376
  if (!release) {
23313
23377
  if (!opts.discoverLatestRelease) {
23314
23378
  throw validationError(
23315
- "No `release` provided and auto-discovery is unavailable on this deployment. Pass an explicit release id (e.g., cc-main-2026-jan-feb-mar)."
23379
+ "No `release` provided and auto-discovery is unavailable on this deployment. Pass an explicit release id (e.g., cc-main-2026-mar-apr-may)."
23316
23380
  );
23317
23381
  }
23318
23382
  const discovered = await opts.discoverLatestRelease();
@@ -23324,7 +23388,7 @@ async function backlinksRoutes(app, opts) {
23324
23388
  release = discovered.release;
23325
23389
  }
23326
23390
  if (!isValidReleaseId(release)) {
23327
- throw validationError("Invalid release id. Expected form: cc-main-YYYY-{jan-feb-mar,apr-may-jun,jul-aug-sep,oct-nov-dec}");
23391
+ throw validationError("Invalid release id. Expected form: cc-main-YYYY-<mon>-<mon>-<mon> (a rolling 3-month window, e.g. cc-main-2026-mar-apr-may).");
23328
23392
  }
23329
23393
  if (!opts.getBacklinksStatus || !opts.onReleaseSyncRequested) {
23330
23394
  throw missingDependency(BACKLINKS_UNSUPPORTED_MESSAGE);
@@ -31959,6 +32023,7 @@ export {
31959
32023
  getPlaceDetails,
31960
32024
  hashPlaceDetails,
31961
32025
  queueRunIfProjectIdle,
32026
+ nextRunFromCron,
31962
32027
  resolveWebhookTarget,
31963
32028
  deliverWebhook,
31964
32029
  redactNotificationUrl,
@@ -1498,7 +1498,7 @@ var snapshotReportSchema = z12.object({
1498
1498
 
1499
1499
  // ../contracts/src/schedule.ts
1500
1500
  import { z as z13 } from "zod";
1501
- var schedulableRunKindSchema = z13.enum(["answer-visibility", "traffic-sync", "gbp-sync", "data-refresh"]);
1501
+ var schedulableRunKindSchema = z13.enum(["answer-visibility", "traffic-sync", "gbp-sync", "data-refresh", "backlinks-sync"]);
1502
1502
  var SchedulableRunKinds = schedulableRunKindSchema.enum;
1503
1503
  var scheduleDtoSchema = z13.object({
1504
1504
  id: z13.string(),
@@ -1534,8 +1534,42 @@ var scheduleUpsertRequestSchema = z13.object({
1534
1534
 
1535
1535
  // ../contracts/src/analytics.ts
1536
1536
  import { z as z14 } from "zod";
1537
+ var metricsWindowSchema = z14.enum(["7d", "30d", "90d", "all"]);
1538
+ var trendDirectionSchema = z14.enum(["improving", "declining", "stable"]);
1537
1539
  var visibilityMetricModeSchema = z14.enum(["mentioned", "cited"]);
1538
1540
  var VisibilityMetricModes = visibilityMetricModeSchema.enum;
1541
+ var providerMetricSchema = z14.object({
1542
+ citationRate: z14.number(),
1543
+ cited: z14.number().int(),
1544
+ total: z14.number().int(),
1545
+ mentionRate: z14.number(),
1546
+ mentionedCount: z14.number().int()
1547
+ });
1548
+ var timeBucketSchema = z14.object({
1549
+ startDate: z14.string(),
1550
+ endDate: z14.string(),
1551
+ citationRate: z14.number(),
1552
+ cited: z14.number().int(),
1553
+ total: z14.number().int(),
1554
+ queryCount: z14.number().int(),
1555
+ mentionRate: z14.number(),
1556
+ mentionedCount: z14.number().int(),
1557
+ byProvider: z14.record(z14.string(), providerMetricSchema)
1558
+ });
1559
+ var queryChangeEventSchema = z14.object({
1560
+ date: z14.string(),
1561
+ delta: z14.number().int(),
1562
+ label: z14.string()
1563
+ });
1564
+ var brandMetricsDtoSchema = z14.object({
1565
+ window: metricsWindowSchema,
1566
+ buckets: z14.array(timeBucketSchema),
1567
+ overall: providerMetricSchema,
1568
+ byProvider: z14.record(z14.string(), providerMetricSchema),
1569
+ trend: trendDirectionSchema,
1570
+ mentionTrend: trendDirectionSchema,
1571
+ queryChanges: z14.array(queryChangeEventSchema)
1572
+ });
1539
1573
  function parseWindow(value) {
1540
1574
  if (value === "7d" || value === "30d" || value === "90d" || value === "all") return value;
1541
1575
  return "all";
@@ -4010,6 +4044,7 @@ export {
4010
4044
  SchedulableRunKinds,
4011
4045
  scheduleDtoSchema,
4012
4046
  scheduleUpsertRequestSchema,
4047
+ brandMetricsDtoSchema,
4013
4048
  parseWindow,
4014
4049
  windowCutoff,
4015
4050
  categorizeSource,
@@ -9,7 +9,7 @@ import {
9
9
  loadConfig,
10
10
  loadConfigRaw,
11
11
  saveConfigPatch
12
- } from "./chunk-RQCVITY4.js";
12
+ } from "./chunk-YYFBMDLC.js";
13
13
  import {
14
14
  CC_CACHE_DIR,
15
15
  DUCKDB_SPEC,
@@ -77,6 +77,7 @@ import {
77
77
  loadDuckdb,
78
78
  markSessionFailed,
79
79
  migrate,
80
+ nextRunFromCron,
80
81
  notifications,
81
82
  parseJsonColumn,
82
83
  probeLatestRelease,
@@ -94,7 +95,7 @@ import {
94
95
  runs,
95
96
  schedules,
96
97
  usageCounters
97
- } from "./chunk-KHN3XMOR.js";
98
+ } from "./chunk-B2CH7GBW.js";
98
99
  import {
99
100
  AGENT_MEMORY_VALUE_MAX_BYTES,
100
101
  AGENT_PROVIDER_IDS,
@@ -142,7 +143,7 @@ import {
142
143
  skillsClientSchema,
143
144
  validationError,
144
145
  withRetry
145
- } from "./chunk-34PATQZM.js";
146
+ } from "./chunk-D75O5A27.js";
146
147
 
147
148
  // src/telemetry.ts
148
149
  import crypto from "crypto";
@@ -434,7 +435,7 @@ import crypto17 from "crypto";
434
435
  import fs8 from "fs";
435
436
  import path9 from "path";
436
437
  import { fileURLToPath as fileURLToPath3 } from "url";
437
- import { eq as eq17 } from "drizzle-orm";
438
+ import { and as and13, eq as eq17 } from "drizzle-orm";
438
439
  import Fastify from "fastify";
439
440
  import os5 from "os";
440
441
 
@@ -5617,7 +5618,7 @@ function readStoredGroundingSources(rawResponse) {
5617
5618
  return result;
5618
5619
  }
5619
5620
  async function backfillInsightsCommand(project, opts) {
5620
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-SMU5JVVD.js");
5621
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-IUKD3PMZ.js");
5621
5622
  const config = loadConfig();
5622
5623
  const db = createClient(config.database);
5623
5624
  migrate(db);
@@ -6602,7 +6603,7 @@ var Scheduler = class {
6602
6603
  });
6603
6604
  this.tasks.set(taskKey(projectId, kind), task);
6604
6605
  this.db.update(schedules).set({
6605
- nextRunAt: task.getNextRun()?.toISOString() ?? null,
6606
+ nextRunAt: nextRunFromCron(cronExpr, timezone),
6606
6607
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
6607
6608
  }).where(eq11(schedules.id, scheduleId)).run();
6608
6609
  const label = schedule.preset ?? cronExpr;
@@ -6617,8 +6618,7 @@ var Scheduler = class {
6617
6618
  this.remove(projectId, kind);
6618
6619
  return;
6619
6620
  }
6620
- const task = this.tasks.get(taskKey(projectId, kind));
6621
- const nextRunAt = task?.getNextRun()?.toISOString() ?? null;
6621
+ const nextRunAt = nextRunFromCron(currentSchedule.cronExpr, currentSchedule.timezone);
6622
6622
  const project = this.db.select().from(projects).where(eq11(projects.id, projectId)).get();
6623
6623
  if (!project) {
6624
6624
  log11.error("project.not-found", { projectId, kind, msg: "skipping scheduled run" });
@@ -6681,6 +6681,20 @@ var Scheduler = class {
6681
6681
  this.callbacks.onDataRefreshRequested(project.name);
6682
6682
  return;
6683
6683
  }
6684
+ if (kind === SchedulableRunKinds["backlinks-sync"]) {
6685
+ if (!this.callbacks.onBacklinksSyncRequested) {
6686
+ log11.warn("backlinks-sync.no-callback", { scheduleId, projectId, msg: "host did not register onBacklinksSyncRequested" });
6687
+ return;
6688
+ }
6689
+ this.db.update(schedules).set({
6690
+ lastRunAt: now,
6691
+ nextRunAt,
6692
+ updatedAt: now
6693
+ }).where(eq11(schedules.id, currentSchedule.id)).run();
6694
+ log11.info("backlinks-sync.triggered", { projectName: project.name });
6695
+ this.callbacks.onBacklinksSyncRequested(project.name);
6696
+ return;
6697
+ }
6684
6698
  const projectLocations = project.locations;
6685
6699
  let resolvedLocation;
6686
6700
  if (project.defaultLocation) {
@@ -9452,6 +9466,29 @@ async function createServer(opts) {
9452
9466
  },
9453
9467
  onDataRefreshRequested: (projectName) => {
9454
9468
  void refreshAllIntegrations(aeroClient, projectName);
9469
+ },
9470
+ onBacklinksSyncRequested: (projectName) => {
9471
+ void (async () => {
9472
+ const probed = await probeLatestRelease().catch((err) => {
9473
+ app.log.warn({ projectName, err }, "Scheduled backlinks sync: latest-release probe failed");
9474
+ return null;
9475
+ });
9476
+ if (!probed) return;
9477
+ const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(and13(
9478
+ eq17(ccReleaseSyncs.release, probed.release),
9479
+ eq17(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)
9480
+ )).limit(1).get();
9481
+ if (alreadySynced) {
9482
+ app.log.info({ projectName, release: probed.release }, "Scheduled backlinks sync: already up to date, skipping");
9483
+ return;
9484
+ }
9485
+ aeroClient.backlinksTriggerSync(probed.release).catch((err) => {
9486
+ app.log.error(
9487
+ { projectName, release: probed.release, err: err instanceof Error ? err.message : String(err) },
9488
+ "Scheduled backlinks sync failed"
9489
+ );
9490
+ });
9491
+ })();
9455
9492
  }
9456
9493
  });
9457
9494
  const providerSummary = API_ADAPTERS.map((adapter) => ({
@@ -22,7 +22,7 @@ import {
22
22
  trafficConnectVercelRequestSchema,
23
23
  trafficConnectWordpressRequestSchema,
24
24
  trafficEventKindSchema
25
- } from "./chunk-34PATQZM.js";
25
+ } from "./chunk-D75O5A27.js";
26
26
 
27
27
  // src/config.ts
28
28
  import fs from "fs";
@@ -5131,6 +5131,20 @@ var canonryMcpTools = [
5131
5131
  openApiOperations: ["GET /api/v1/projects/{name}/report"],
5132
5132
  handler: (client2, input) => client2.getReport(input.project)
5133
5133
  }),
5134
+ defineTool({
5135
+ name: "canonry_analytics_metrics",
5136
+ title: "Get citation & mention trend",
5137
+ description: "Citation and mention rates over time for a project, bucketed adaptively (daily \u2192 monthly by span) and probe-excluded. Returns overall + per-provider window aggregates AND a per-bucket `byProvider` breakdown so you can read how each engine's cited/mentioned rate moved run-over-run \u2014 the same data the dashboard's \"Citations & mentions over time\" chart plots. Includes trend direction (improving/declining/stable) for both signals and query-set-change annotations. Filter the range with `window` (7d/30d/90d/all).",
5138
+ access: "read",
5139
+ tier: "monitoring",
5140
+ inputSchema: z2.object({
5141
+ project: projectNameSchema,
5142
+ window: analyticsWindowSchema.optional().describe("Time range: 7d, 30d, 90d, or all (default all).")
5143
+ }),
5144
+ annotations: readAnnotations(),
5145
+ openApiOperations: ["GET /api/v1/projects/{name}/analytics/metrics"],
5146
+ handler: (client2, input) => client2.getAnalyticsMetrics(input.project, input.window)
5147
+ }),
5134
5148
  defineTool({
5135
5149
  name: "canonry_search",
5136
5150
  title: "Search project (composite)",