@ainyc/canonry 4.116.0 → 4.117.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/README.md +0 -1
  2. package/assets/agent-workspace/skills/aero/references/reporting.md +10 -0
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +6 -0
  4. package/assets/assets/{BacklinksPage-Dy6Xc-LT.js → BacklinksPage-Bkafm10m.js} +1 -1
  5. package/assets/assets/{ChartPrimitives-CHJh5M8x.js → ChartPrimitives-NV_GgqFn.js} +1 -1
  6. package/assets/assets/{ProjectPage-Bjfa4CI5.js → ProjectPage-0-NnRxys.js} +1 -1
  7. package/assets/assets/{RunRow-DmvQnWPf.js → RunRow-df0d24S6.js} +1 -1
  8. package/assets/assets/{RunsPage-BxkxQEKE.js → RunsPage-CU6-4YTM.js} +1 -1
  9. package/assets/assets/{SettingsPage-DCyqHqDh.js → SettingsPage-BKw6B6cd.js} +1 -1
  10. package/assets/assets/{TrafficPage-BT_ae5aO.js → TrafficPage-CzpiYHKj.js} +1 -1
  11. package/assets/assets/{TrafficSourceDetailPage-Cps-tSHn.js → TrafficSourceDetailPage-CC5nuqM1.js} +1 -1
  12. package/assets/assets/{arrow-left-D8vdqhx4.js → arrow-left-CbQcW2pL.js} +1 -1
  13. package/assets/assets/{extract-error-message-BOY5txUA.js → extract-error-message-CvqIZ-le.js} +1 -1
  14. package/assets/assets/index-LjcWHaNZ.js +210 -0
  15. package/assets/assets/{trash-2-ap_Fa4zA.js → trash-2-3J2u0woR.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-WEAXJ25Y.js → chunk-FVVKNMZW.js} +353 -19
  18. package/dist/{chunk-IQPMK36Y.js → chunk-IAJ5TQ4S.js} +155 -3
  19. package/dist/{chunk-RVH6QPTA.js → chunk-PJATAPZW.js} +37 -1
  20. package/dist/{chunk-GV665WOU.js → chunk-ZZEA774R.js} +4 -4
  21. package/dist/cli.js +93 -4
  22. package/dist/index.js +4 -4
  23. package/dist/{intelligence-service-HEN5HLUM.js → intelligence-service-J7G4CMI5.js} +2 -2
  24. package/dist/mcp.js +2 -2
  25. package/package.json +9 -9
  26. package/assets/assets/index-DxjY63VC.js +0 -210
@@ -1998,7 +1998,7 @@ var DEDUP_SIMILARITY_BAND_HIGH = 0.97;
1998
1998
  function computeDedupSimilarityStats(vectors, indexClusters) {
1999
1999
  const n = vectors.length;
2000
2000
  if (n < 2) return { perClusterMinSimilarity: [], bandPairFraction: null, pairsTotal: 0 };
2001
- const round = (v) => Math.round(v * 1e4) / 1e4;
2001
+ const round2 = (v) => Math.round(v * 1e4) / 1e4;
2002
2002
  const perClusterMinSimilarity = [];
2003
2003
  for (const cluster of indexClusters) {
2004
2004
  if (cluster.length < 2) continue;
@@ -2008,7 +2008,7 @@ function computeDedupSimilarityStats(vectors, indexClusters) {
2008
2008
  min = Math.min(min, cosineSimilarity(vectors[cluster[a]], vectors[cluster[b]]));
2009
2009
  }
2010
2010
  }
2011
- perClusterMinSimilarity.push(round(min));
2011
+ perClusterMinSimilarity.push(round2(min));
2012
2012
  }
2013
2013
  let inBand = 0;
2014
2014
  let pairsTotal = 0;
@@ -2019,7 +2019,7 @@ function computeDedupSimilarityStats(vectors, indexClusters) {
2019
2019
  if (sim >= DEDUP_SIMILARITY_BAND_LOW && sim < DEDUP_SIMILARITY_BAND_HIGH) inBand++;
2020
2020
  }
2021
2021
  }
2022
- return { perClusterMinSimilarity, bandPairFraction: round(inBand / pairsTotal), pairsTotal };
2022
+ return { perClusterMinSimilarity, bandPairFraction: round2(inBand / pairsTotal), pairsTotal };
2023
2023
  }
2024
2024
 
2025
2025
  // ../contracts/src/url-normalize.ts
@@ -2894,6 +2894,137 @@ var visibilityStatsDtoSchema = z20.object({
2894
2894
  /** Pooled share of voice vs tracked competitors — present only when `shareOfVoice=1` was requested. */
2895
2895
  shareOfVoice: visibilityStatsShareOfVoiceSchema.optional()
2896
2896
  });
2897
+ var visibilityCompareVerdictSchema = z20.enum([
2898
+ "within-noise",
2899
+ "moved",
2900
+ "insufficient-data",
2901
+ "model-discontinuous",
2902
+ "model-unknown"
2903
+ ]);
2904
+ var visibilityCompareDirectionSchema = z20.enum(["up", "down", "flat"]);
2905
+ var visibilityCompareMetricPeriodSchema = z20.object({
2906
+ /** The proportion in `[0,1]`, rounded to 4 dp; `null` when `denominator === 0` (undefined over no data). */
2907
+ point: z20.number().nullable(),
2908
+ /** Wilson 95% lower bound `[0,1]`; `null` when `denominator === 0`. */
2909
+ ciLow: z20.number().nullable(),
2910
+ /** Wilson 95% upper bound `[0,1]`; `null` when `denominator === 0`. */
2911
+ ciHigh: z20.number().nullable(),
2912
+ /** Successes (mentions / citations / project-brand mentions). */
2913
+ numerator: z20.number().int(),
2914
+ /** Sample size the proportion is over (checked snapshots / total / project+competitor brand mentions). */
2915
+ denominator: z20.number().int()
2916
+ });
2917
+ var visibilityCompareMetricKeySchema = z20.enum([
2918
+ "mention-share-of-voice",
2919
+ "cited-share-of-voice",
2920
+ "mention-rate",
2921
+ "cited-rate"
2922
+ ]);
2923
+ var visibilityCompareMetricSchema = z20.object({
2924
+ key: visibilityCompareMetricKeySchema,
2925
+ /** Human label ("Named share of voice", "Cited rate", …). */
2926
+ label: z20.string(),
2927
+ /**
2928
+ * `true` for share of voice, which is less exposed to broad model-wide naming
2929
+ * propensity than an absolute rate. It does not bypass model continuity: no
2930
+ * metric can make a directional call when every provider is excluded.
2931
+ */
2932
+ driftRobust: z20.boolean(),
2933
+ from: visibilityCompareMetricPeriodSchema,
2934
+ to: visibilityCompareMetricPeriodSchema,
2935
+ /** `to.point / from.point`; `null` when `from.point` is `0` or `null` (ratio undefined). */
2936
+ rateRatio: z20.number().nullable(),
2937
+ /** Sign of `to.point - from.point`; `null` when either point is `null`. */
2938
+ direction: z20.union([visibilityCompareDirectionSchema, z20.null()]),
2939
+ verdict: visibilityCompareVerdictSchema
2940
+ });
2941
+ var visibilityComparePeriodWindowSchema = z20.object({
2942
+ /** The `YYYY-MM` requested. */
2943
+ month: z20.string(),
2944
+ /** Resolved inclusive ISO bounds the month expanded to. */
2945
+ since: z20.string(),
2946
+ until: z20.string(),
2947
+ /** Completed/partial, non-probe answer-visibility runs in the month. */
2948
+ runCount: z20.number().int(),
2949
+ /**
2950
+ * `true` when `runCount` is below the reliability floor (`< 5` sweeps): the
2951
+ * intervals are wide and a `moved` verdict is unlikely to be reachable. A
2952
+ * signal to raise the sweep schedule, surfaced so the report can caveat.
2953
+ */
2954
+ lowRunCount: z20.boolean()
2955
+ });
2956
+ var visibilityCompareBasketSchema = z20.object({
2957
+ /** Distinct tracked queries with at least one common provider pair — the compared universe. */
2958
+ queryCount: z20.number().int(),
2959
+ /** Queries observed only in `from` (dropped from the comparison). */
2960
+ excludedFromOnly: z20.number().int(),
2961
+ /** Queries observed only in `to`. */
2962
+ excludedToOnly: z20.number().int(),
2963
+ /**
2964
+ * The compared engine set: providers with ≥1 common query/provider pair.
2965
+ */
2966
+ providers: z20.array(z20.string()),
2967
+ /** Providers observed in either period that did not make the basket (dropped from the comparison). */
2968
+ excludedProviders: z20.array(z20.string())
2969
+ });
2970
+ var visibilityCompareModelChangeSchema = z20.object({
2971
+ provider: z20.string(),
2972
+ fromModels: z20.array(z20.string()),
2973
+ toModels: z20.array(z20.string())
2974
+ });
2975
+ var visibilityCompareProviderContinuityStatusSchema = z20.enum([
2976
+ "included",
2977
+ "model-discontinuous",
2978
+ "model-unknown"
2979
+ ]);
2980
+ var visibilityCompareProviderContinuitySchema = z20.object({
2981
+ provider: z20.string(),
2982
+ /** `included` only when both months carry exactly one known model id, identical across the two, and NO snapshot with an unrecorded (null) model — a null mixed alongside a known id still marks the period `model-unknown`. */
2983
+ status: visibilityCompareProviderContinuityStatusSchema,
2984
+ fromModels: z20.array(z20.string()),
2985
+ toModels: z20.array(z20.string())
2986
+ });
2987
+ var visibilityCompareContinuityStatusSchema = z20.enum([
2988
+ "comparable",
2989
+ "model-discontinuous",
2990
+ "model-unknown",
2991
+ "insufficient-data"
2992
+ ]);
2993
+ var visibilityCompareContinuitySchema = z20.object({
2994
+ status: visibilityCompareContinuityStatusSchema,
2995
+ /** Providers retained in the directional comparison after the continuity gate. */
2996
+ comparedProviders: z20.array(z20.string()),
2997
+ /** Every provider with common query/provider pairs, including excluded ones and their model evidence. */
2998
+ providers: z20.array(visibilityCompareProviderContinuitySchema)
2999
+ });
3000
+ var visibilityCompareProviderRowSchema = z20.object({
3001
+ provider: z20.string(),
3002
+ from: z20.object({ checked: z20.number().int(), mentioned: z20.number().int(), cited: z20.number().int() }),
3003
+ to: z20.object({ checked: z20.number().int(), mentioned: z20.number().int(), cited: z20.number().int() })
3004
+ });
3005
+ var visibilityCompareQueriesMentionedSchema = z20.object({
3006
+ from: z20.object({ count: z20.number().int(), of: z20.number().int() }),
3007
+ to: z20.object({ count: z20.number().int(), of: z20.number().int() })
3008
+ });
3009
+ var visibilityCompareDtoSchema = z20.object({
3010
+ project: z20.string(),
3011
+ from: visibilityComparePeriodWindowSchema,
3012
+ to: visibilityComparePeriodWindowSchema,
3013
+ basket: visibilityCompareBasketSchema,
3014
+ /** Ordered: mention SoV (primary), cited SoV, mention rate (level), cited rate. */
3015
+ metrics: z20.array(visibilityCompareMetricSchema),
3016
+ queriesMentioned: visibilityCompareQueriesMentionedSchema,
3017
+ byProvider: z20.array(visibilityCompareProviderRowSchema),
3018
+ /** Providers whose configured model id set changed between the periods (empty = none detected). */
3019
+ modelChanges: z20.array(visibilityCompareModelChangeSchema),
3020
+ /** Strict model-continuity evidence and the providers that remain comparable. */
3021
+ continuity: visibilityCompareContinuitySchema,
3022
+ /** Per-competitor mention counts within each period's basket (for the SoV detail). */
3023
+ competitors: z20.object({
3024
+ from: z20.array(visibilityStatsShareCompetitorSchema),
3025
+ to: z20.array(visibilityStatsShareCompetitorSchema)
3026
+ })
3027
+ });
2897
3028
  function calendarMonthBounds(month) {
2898
3029
  const match = /^(\d{4})-(\d{2})$/.exec(month);
2899
3030
  if (!match) throw new RangeError("month must be in YYYY-MM format");
@@ -5501,6 +5632,25 @@ function sumInfraHits(segments) {
5501
5632
  return segments.sitemap + segments.robots + segments.asset;
5502
5633
  }
5503
5634
 
5635
+ // ../contracts/src/statistics.ts
5636
+ function round(value, dp = 4) {
5637
+ const f = 10 ** dp;
5638
+ return (Math.round(value * f) + 0) / f;
5639
+ }
5640
+ function wilsonInterval(successes, n, z33 = 1.96) {
5641
+ if (!Number.isFinite(n) || n <= 0) return null;
5642
+ const s = Math.max(0, Math.min(successes, n));
5643
+ const p = s / n;
5644
+ const z210 = z33 * z33;
5645
+ const denom = 1 + z210 / n;
5646
+ const center = (p + z210 / (2 * n)) / denom;
5647
+ const margin = z33 / denom * Math.sqrt(p * (1 - p) / n + z210 / (4 * n * n));
5648
+ return {
5649
+ low: round(Math.max(0, center - margin)),
5650
+ high: round(Math.min(1, center + margin))
5651
+ };
5652
+ }
5653
+
5504
5654
  // ../contracts/src/ads.ts
5505
5655
  import { z as z32 } from "zod";
5506
5656
  var adsConnectRequestSchema = z32.object({
@@ -5984,6 +6134,7 @@ export {
5984
6134
  parseWindow,
5985
6135
  windowCutoff,
5986
6136
  visibilityStatsDtoSchema,
6137
+ visibilityCompareDtoSchema,
5987
6138
  calendarMonthBounds,
5988
6139
  formatRatio,
5989
6140
  formatNumber,
@@ -6097,6 +6248,7 @@ export {
6097
6248
  classifyTrafficPath,
6098
6249
  segmentCrawlerHits,
6099
6250
  sumInfraHits,
6251
+ wilsonInterval,
6100
6252
  adsConnectRequestSchema,
6101
6253
  adsConnectionStatusDtoSchema,
6102
6254
  adsDisconnectResponseSchema,
@@ -25,7 +25,7 @@ import {
25
25
  trafficConnectVercelRequestSchema,
26
26
  trafficConnectWordpressRequestSchema,
27
27
  trafficEventKindSchema
28
- } from "./chunk-IQPMK36Y.js";
28
+ } from "./chunk-IAJ5TQ4S.js";
29
29
 
30
30
  // src/config.ts
31
31
  import fs from "fs";
@@ -1578,6 +1578,18 @@ var getApiV1ProjectsByNameVisibilityStats = (options) => {
1578
1578
  ...options
1579
1579
  });
1580
1580
  };
1581
+ var getApiV1ProjectsByNameVisibilityCompare = (options) => {
1582
+ return (options.client ?? client).get({
1583
+ security: [
1584
+ {
1585
+ scheme: "bearer",
1586
+ type: "http"
1587
+ }
1588
+ ],
1589
+ url: "/api/v1/projects/{name}/visibility-compare",
1590
+ ...options
1591
+ });
1592
+ };
1581
1593
  var getApiV1ProjectsByNameSnapshotsDiff = (options) => {
1582
1594
  return (options.client ?? client).get({
1583
1595
  security: [
@@ -5171,6 +5183,15 @@ var ApiClient = class {
5171
5183
  })
5172
5184
  );
5173
5185
  }
5186
+ async getVisibilityCompare(project, from, to) {
5187
+ return this.invoke(
5188
+ () => getApiV1ProjectsByNameVisibilityCompare({
5189
+ client: this.heyClient,
5190
+ path: { name: project },
5191
+ query: { from, to }
5192
+ })
5193
+ );
5194
+ }
5174
5195
  // ── Backlinks — workspace-level ────────────────────────────────────────
5175
5196
  async backlinksStatus() {
5176
5197
  return this.invoke(() => getApiV1BacklinksStatus({ client: this.heyClient }));
@@ -5915,6 +5936,21 @@ var canonryMcpTools = [
5915
5936
  shareOfVoice: input.shareOfVoice
5916
5937
  })
5917
5938
  }),
5939
+ defineTool({
5940
+ name: "canonry_visibility_compare",
5941
+ title: "Compare AEO visibility month over month",
5942
+ description: "Statistically honest month-over-month AEO comparison in ONE call \u2014 use this instead of hand-computing deltas from two visibility-stats calls. Share of voice (`mention-share-of-voice`, `driftRobust: true`) is less exposed to broad model-wide naming propensity than absolute rates, but it never overrides model continuity. The response restricts to common query/provider pairs, then includes only providers with exactly one known, identical configured model id in both months. `continuity` surfaces every provider, its model evidence, and whether it was excluded for a changed, mixed mid-month, or unknown model. When no provider remains, metrics return `model-discontinuous` or `model-unknown`, never a directional call. A silent upstream version bump under an unchanged configured id remains undetectable. `from` must be a month strictly before `to`.",
5943
+ access: "read",
5944
+ tier: "monitoring",
5945
+ inputSchema: z2.object({
5946
+ project: projectNameSchema,
5947
+ from: z2.string().describe('Earlier calendar month (YYYY-MM), the baseline. Must be strictly before "to".'),
5948
+ to: z2.string().describe('Later calendar month (YYYY-MM), compared against "from".')
5949
+ }),
5950
+ annotations: readAnnotations(),
5951
+ openApiOperations: ["GET /api/v1/projects/{name}/visibility-compare"],
5952
+ handler: (client2, input) => client2.getVisibilityCompare(input.project, input.from, input.to)
5953
+ }),
5918
5954
  defineTool({
5919
5955
  name: "canonry_content_targets",
5920
5956
  title: "Get content targets",
@@ -10,7 +10,7 @@ import {
10
10
  loadConfig,
11
11
  loadConfigRaw,
12
12
  saveConfigPatch
13
- } from "./chunk-RVH6QPTA.js";
13
+ } from "./chunk-PJATAPZW.js";
14
14
  import {
15
15
  CC_CACHE_DIR,
16
16
  DUCKDB_SPEC,
@@ -115,7 +115,7 @@ import {
115
115
  siteAuditPages,
116
116
  siteAuditSnapshots,
117
117
  usageCounters
118
- } from "./chunk-WEAXJ25Y.js";
118
+ } from "./chunk-FVVKNMZW.js";
119
119
  import {
120
120
  AGENT_MEMORY_VALUE_MAX_BYTES,
121
121
  AGENT_PROVIDER_IDS,
@@ -178,7 +178,7 @@ import {
178
178
  validationError,
179
179
  winnabilityClassLabel,
180
180
  withRetry
181
- } from "./chunk-IQPMK36Y.js";
181
+ } from "./chunk-IAJ5TQ4S.js";
182
182
 
183
183
  // src/telemetry.ts
184
184
  import crypto from "crypto";
@@ -6515,7 +6515,7 @@ function readStoredGroundingSources(rawResponse) {
6515
6515
  return result;
6516
6516
  }
6517
6517
  async function backfillInsightsCommand(project, opts) {
6518
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-HEN5HLUM.js");
6518
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-J7G4CMI5.js");
6519
6519
  const config = loadConfig();
6520
6520
  const db = createClient(config.database);
6521
6521
  migrate(db);
package/dist/cli.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  setTelemetrySource,
30
30
  showFirstRunNotice,
31
31
  trackEvent
32
- } from "./chunk-GV665WOU.js";
32
+ } from "./chunk-ZZEA774R.js";
33
33
  import {
34
34
  CliError,
35
35
  EXIT_SYSTEM_ERROR,
@@ -46,7 +46,7 @@ import {
46
46
  saveConfig,
47
47
  saveConfigPatch,
48
48
  usageError
49
- } from "./chunk-RVH6QPTA.js";
49
+ } from "./chunk-PJATAPZW.js";
50
50
  import {
51
51
  apiKeys,
52
52
  createClient,
@@ -54,7 +54,7 @@ import {
54
54
  projects,
55
55
  queries,
56
56
  renderReportHtml
57
- } from "./chunk-WEAXJ25Y.js";
57
+ } from "./chunk-FVVKNMZW.js";
58
58
  import {
59
59
  BacklinkSources,
60
60
  CcReleaseSyncStatuses,
@@ -80,7 +80,7 @@ import {
80
80
  providerQuotaPolicySchema,
81
81
  resolveProviderInput,
82
82
  winnabilityClassSchema
83
- } from "./chunk-IQPMK36Y.js";
83
+ } from "./chunk-IAJ5TQ4S.js";
84
84
 
85
85
  // src/cli.ts
86
86
  import { pathToFileURL } from "url";
@@ -11841,6 +11841,78 @@ async function showVisibilityStats(project, opts) {
11841
11841
  function pct3(rate) {
11842
11842
  return rate === null ? "\u2014" : formatRatio(rate);
11843
11843
  }
11844
+ async function showVisibilityCompare(project, opts) {
11845
+ if (!opts.from || !opts.to) throw new Error("visibility-compare requires --from <YYYY-MM> and --to <YYYY-MM>");
11846
+ const client = createApiClient();
11847
+ const data = await client.getVisibilityCompare(project, opts.from, opts.to);
11848
+ if (isMachineFormat(opts.format)) {
11849
+ console.log(JSON.stringify(data, null, 2));
11850
+ return;
11851
+ }
11852
+ printVisibilityCompare(data);
11853
+ }
11854
+ function periodCell(p) {
11855
+ if (p.point === null || p.ciLow === null || p.ciHigh === null) return "no data";
11856
+ const p1 = (v) => formatRatio(v);
11857
+ return `${p1(p.point)} [${p1(p.ciLow)}, ${p1(p.ciHigh)}]`;
11858
+ }
11859
+ function verdictCell(m) {
11860
+ switch (m.verdict) {
11861
+ case "within-noise":
11862
+ return "within noise";
11863
+ case "insufficient-data":
11864
+ return "no data";
11865
+ case "moved":
11866
+ return `moved ${m.direction === "down" ? "down" : m.direction === "up" ? "up" : ""}`.trim();
11867
+ case "model-discontinuous":
11868
+ return "model discontinuity";
11869
+ case "model-unknown":
11870
+ return "model unknown";
11871
+ }
11872
+ }
11873
+ function printVisibilityCompare(data) {
11874
+ console.log(`AEO month over month: ${data.project} ${data.from.month} -> ${data.to.month}`);
11875
+ const b = data.basket;
11876
+ const excl = [];
11877
+ if (b.excludedFromOnly > 0) excl.push(`${b.excludedFromOnly} only in ${data.from.month}`);
11878
+ if (b.excludedToOnly > 0) excl.push(`${b.excludedToOnly} only in ${data.to.month}`);
11879
+ if (b.excludedProviders.length > 0) excl.push(`engines dropped: ${b.excludedProviders.join(", ")}`);
11880
+ console.log(`Basket: ${b.queryCount} quer${b.queryCount === 1 ? "y" : "ies"}, ${b.providers.length} engine(s)${excl.length ? ` (excluded: ${excl.join("; ")})` : ""}`);
11881
+ const sweeps = `Sweeps: ${data.from.month} ${data.from.runCount}, ${data.to.month} ${data.to.runCount}`;
11882
+ const low = data.from.lowRunCount || data.to.lowRunCount;
11883
+ console.log(low ? `${sweeps} (below the 5-sweep floor \u2014 intervals are wide, a "moved" verdict is unlikely to be reachable)` : sweeps);
11884
+ if (data.continuity.status !== "comparable") {
11885
+ console.log(`Continuity: ${data.continuity.status.replace("-", " ")} \u2014 no directional call is made.`);
11886
+ }
11887
+ console.log("");
11888
+ const rows = data.metrics.map((m) => ({
11889
+ label: `${m.label}${m.driftRobust ? " *" : ""}`,
11890
+ to: periodCell(m.to),
11891
+ from: periodCell(m.from),
11892
+ verdict: verdictCell(m)
11893
+ }));
11894
+ rows.push({
11895
+ label: "Queries named (count)",
11896
+ to: `${data.queriesMentioned.to.count} of ${data.queriesMentioned.to.of}`,
11897
+ from: `${data.queriesMentioned.from.count} of ${data.queriesMentioned.from.of}`,
11898
+ verdict: ""
11899
+ });
11900
+ const w = (sel, head) => Math.max(head.length, ...rows.map((r) => sel(r).length));
11901
+ const lw = w((r) => r.label, "Metric");
11902
+ const tw = w((r) => r.to, data.to.month);
11903
+ const fw = w((r) => r.from, data.from.month);
11904
+ console.log(` ${"Metric".padEnd(lw)} ${data.to.month.padEnd(tw)} ${data.from.month.padEnd(fw)} Verdict`);
11905
+ for (const r of rows) {
11906
+ console.log(` ${r.label.padEnd(lw)} ${r.to.padEnd(tw)} ${r.from.padEnd(fw)} ${r.verdict}`);
11907
+ }
11908
+ console.log("");
11909
+ console.log(" * share of voice is less exposed to broad model-wide naming propensity; model continuity is still required.");
11910
+ for (const provider of data.continuity.providers) {
11911
+ if (provider.status !== "included") {
11912
+ console.log(` Excluded for model continuity: ${provider.provider} (${provider.fromModels.join("/") || "?"} -> ${provider.toModels.join("/") || "?"}; ${provider.status.replace("-", " ")})`);
11913
+ }
11914
+ }
11915
+ }
11844
11916
  function citedCell(c) {
11845
11917
  return `${c.cited}/${c.total}`;
11846
11918
  }
@@ -11932,6 +12004,7 @@ function printVisibilityStats(data) {
11932
12004
 
11933
12005
  // src/cli-commands/visibility-stats.ts
11934
12006
  var USAGE4 = "canonry visibility-stats <project> [--since <iso>] [--until <iso>] [--month <YYYY-MM>] [--last-runs <n>] [--by-provider] [--share-of-voice] [--format json|jsonl]";
12007
+ var COMPARE_USAGE = "canonry visibility-compare <project> --from <YYYY-MM> --to <YYYY-MM> [--format json]";
11935
12008
  var VISIBILITY_STATS_CLI_COMMANDS = [
11936
12009
  {
11937
12010
  path: ["visibility-stats"],
@@ -11960,6 +12033,22 @@ var VISIBILITY_STATS_CLI_COMMANDS = [
11960
12033
  format: input.format
11961
12034
  });
11962
12035
  }
12036
+ },
12037
+ {
12038
+ path: ["visibility-compare"],
12039
+ usage: COMPARE_USAGE,
12040
+ options: {
12041
+ from: stringOption(),
12042
+ to: stringOption()
12043
+ },
12044
+ run: async (input) => {
12045
+ const project = requireProject(input, "visibility-compare", COMPARE_USAGE);
12046
+ await showVisibilityCompare(project, {
12047
+ from: getString(input.values, "from"),
12048
+ to: getString(input.values, "to"),
12049
+ format: input.format
12050
+ });
12051
+ }
11963
12052
  }
11964
12053
  ];
11965
12054
 
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-GV665WOU.js";
3
+ } from "./chunk-ZZEA774R.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-RVH6QPTA.js";
7
- import "./chunk-WEAXJ25Y.js";
8
- import "./chunk-IQPMK36Y.js";
6
+ } from "./chunk-PJATAPZW.js";
7
+ import "./chunk-FVVKNMZW.js";
8
+ import "./chunk-IAJ5TQ4S.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-WEAXJ25Y.js";
4
- import "./chunk-IQPMK36Y.js";
3
+ } from "./chunk-FVVKNMZW.js";
4
+ import "./chunk-IAJ5TQ4S.js";
5
5
  export {
6
6
  IntelligenceService
7
7
  };
package/dist/mcp.js CHANGED
@@ -3,10 +3,10 @@ import {
3
3
  PACKAGE_VERSION,
4
4
  canonryMcpTools,
5
5
  createApiClient
6
- } from "./chunk-RVH6QPTA.js";
6
+ } from "./chunk-PJATAPZW.js";
7
7
  import {
8
8
  isReadOnlyKey
9
- } from "./chunk-IQPMK36Y.js";
9
+ } from "./chunk-IAJ5TQ4S.js";
10
10
 
11
11
  // src/mcp/cli.ts
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "4.116.0",
3
+ "version": "4.117.0",
4
4
  "type": "module",
5
5
  "description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
6
6
  "license": "FSL-1.1-ALv2",
@@ -65,27 +65,27 @@
65
65
  "@types/node-cron": "^3.0.11",
66
66
  "tsup": "^8.5.1",
67
67
  "tsx": "^4.19.0",
68
+ "@ainyc/canonry-api-client": "0.0.0",
68
69
  "@ainyc/canonry-api-routes": "0.0.0",
69
70
  "@ainyc/canonry-config": "0.0.0",
70
- "@ainyc/canonry-api-client": "0.0.0",
71
+ "@ainyc/canonry-contracts": "0.0.0",
71
72
  "@ainyc/canonry-db": "0.0.0",
72
73
  "@ainyc/canonry-integration-bing": "0.0.0",
73
- "@ainyc/canonry-contracts": "0.0.0",
74
74
  "@ainyc/canonry-integration-openai-ads": "0.0.0",
75
+ "@ainyc/canonry-integration-commoncrawl": "0.0.0",
75
76
  "@ainyc/canonry-integration-cloud-run": "0.0.0",
76
77
  "@ainyc/canonry-integration-google": "0.0.0",
77
78
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
78
- "@ainyc/canonry-integration-commoncrawl": "0.0.0",
79
+ "@ainyc/canonry-integration-google-places": "0.0.0",
79
80
  "@ainyc/canonry-integration-traffic": "0.0.0",
80
81
  "@ainyc/canonry-integration-wordpress": "0.0.0",
81
- "@ainyc/canonry-intelligence": "0.0.0",
82
82
  "@ainyc/canonry-provider-cdp": "0.0.0",
83
+ "@ainyc/canonry-intelligence": "0.0.0",
83
84
  "@ainyc/canonry-provider-claude": "0.0.0",
84
- "@ainyc/canonry-integration-google-places": "0.0.0",
85
- "@ainyc/canonry-provider-local": "0.0.0",
86
- "@ainyc/canonry-provider-openai": "0.0.0",
87
85
  "@ainyc/canonry-provider-gemini": "0.0.0",
88
- "@ainyc/canonry-provider-perplexity": "0.0.0"
86
+ "@ainyc/canonry-provider-openai": "0.0.0",
87
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
88
+ "@ainyc/canonry-provider-local": "0.0.0"
89
89
  },
90
90
  "scripts": {
91
91
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",