@ainyc/canonry 4.84.0 → 4.86.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 (24) hide show
  1. package/assets/agent-workspace/skills/aero/references/regression-playbook.md +2 -0
  2. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +22 -1
  3. package/assets/assets/{BacklinksPage-OrSg_iPA.js → BacklinksPage-BNrvc-gV.js} +1 -1
  4. package/assets/assets/{ChartPrimitives-DPBhAT_r.js → ChartPrimitives-BlIkdUdy.js} +1 -1
  5. package/assets/assets/{ProjectPage-CpMcEmtw.js → ProjectPage-CAyx_xNr.js} +2 -2
  6. package/assets/assets/{RunRow-2Rty0BAH.js → RunRow-CAPnKzi7.js} +1 -1
  7. package/assets/assets/{RunsPage-B3ahqf8s.js → RunsPage-idnuzKBn.js} +1 -1
  8. package/assets/assets/{SettingsPage-BIjeI85q.js → SettingsPage-Bka67uJq.js} +1 -1
  9. package/assets/assets/{TrafficPage-DjGoj691.js → TrafficPage-C_o-rA5o.js} +1 -1
  10. package/assets/assets/{TrafficSourceDetailPage-BgKG-2q3.js → TrafficSourceDetailPage-D_jvoSTV.js} +1 -1
  11. package/assets/assets/{arrow-left-Cf7wmru1.js → arrow-left-B-JfzARi.js} +1 -1
  12. package/assets/assets/{extract-error-message-CANxezte.js → extract-error-message-BhPbjIX6.js} +1 -1
  13. package/assets/assets/{index-CGlPx_cu.js → index-uPSrDA8e.js} +77 -77
  14. package/assets/assets/{trash-2-6nHJZrvy.js → trash-2-BbRvn40h.js} +1 -1
  15. package/assets/index.html +1 -1
  16. package/dist/{chunk-BNF3HXBW.js → chunk-23HGQV22.js} +1460 -1149
  17. package/dist/{chunk-VJBO4VIK.js → chunk-DLBQU3VG.js} +712 -423
  18. package/dist/{chunk-Y3O3HBMN.js → chunk-LLJPZKHG.js} +94 -1
  19. package/dist/{chunk-M3IYKTSF.js → chunk-SELXBOAP.js} +19 -4
  20. package/dist/cli.js +210 -20
  21. package/dist/index.js +4 -4
  22. package/dist/{intelligence-service-PDIAMP5I.js → intelligence-service-ZHUJKZRO.js} +2 -2
  23. package/dist/mcp.js +2 -2
  24. package/package.json +10 -10
@@ -23,7 +23,7 @@ import {
23
23
  trafficConnectVercelRequestSchema,
24
24
  trafficConnectWordpressRequestSchema,
25
25
  trafficEventKindSchema
26
- } from "./chunk-BNF3HXBW.js";
26
+ } from "./chunk-23HGQV22.js";
27
27
 
28
28
  // src/config.ts
29
29
  import fs from "fs";
@@ -1538,6 +1538,18 @@ var getApiV1ProjectsByNameAnalyticsSources = (options) => {
1538
1538
  ...options
1539
1539
  });
1540
1540
  };
1541
+ var getApiV1ProjectsByNameVisibilityStats = (options) => {
1542
+ return (options.client ?? client).get({
1543
+ security: [
1544
+ {
1545
+ scheme: "bearer",
1546
+ type: "http"
1547
+ }
1548
+ ],
1549
+ url: "/api/v1/projects/{name}/visibility-stats",
1550
+ ...options
1551
+ });
1552
+ };
1541
1553
  var getApiV1ProjectsByNameSnapshotsDiff = (options) => {
1542
1554
  return (options.client ?? client).get({
1543
1555
  security: [
@@ -3442,6 +3454,18 @@ var getApiV1ProjectsByNameDiscoverSessionsById = (options) => {
3442
3454
  ...options
3443
3455
  });
3444
3456
  };
3457
+ var getApiV1ProjectsByNameDiscoverSessionsByIdHarvest = (options) => {
3458
+ return (options.client ?? client).get({
3459
+ security: [
3460
+ {
3461
+ scheme: "bearer",
3462
+ type: "http"
3463
+ }
3464
+ ],
3465
+ url: "/api/v1/projects/{name}/discover/sessions/{id}/harvest",
3466
+ ...options
3467
+ });
3468
+ };
3445
3469
  var getApiV1ProjectsByNameDiscoverSessionsByIdPromote = (options) => {
3446
3470
  return (options.client ?? client).get({
3447
3471
  security: [
@@ -4738,6 +4762,19 @@ var ApiClient = class {
4738
4762
  })
4739
4763
  );
4740
4764
  }
4765
+ async getDiscoveryHarvest(project, sessionId, opts) {
4766
+ return this.invoke(
4767
+ () => getApiV1ProjectsByNameDiscoverSessionsByIdHarvest({
4768
+ client: this.heyClient,
4769
+ path: { name: project, id: sessionId },
4770
+ query: {
4771
+ minProbeHits: opts?.minProbeHits !== void 0 ? String(opts.minProbeHits) : void 0,
4772
+ // The server treats anchor=false as "disable"; omit otherwise.
4773
+ anchor: opts?.anchor === false ? "false" : void 0
4774
+ }
4775
+ })
4776
+ );
4777
+ }
4741
4778
  async previewDiscoveryPromote(project, sessionId) {
4742
4779
  return this.invoke(
4743
4780
  () => getApiV1ProjectsByNameDiscoverSessionsByIdPromote({
@@ -5062,6 +5099,20 @@ var ApiClient = class {
5062
5099
  () => getApiV1ProjectsByNameCitationsVisibility({ client: this.heyClient, path: { name: project } })
5063
5100
  );
5064
5101
  }
5102
+ async getVisibilityStats(project, opts = {}) {
5103
+ return this.invoke(
5104
+ () => getApiV1ProjectsByNameVisibilityStats({
5105
+ client: this.heyClient,
5106
+ path: { name: project },
5107
+ query: {
5108
+ since: opts.since,
5109
+ until: opts.until,
5110
+ lastRuns: opts.lastRuns,
5111
+ groupBy: opts.groupBy
5112
+ }
5113
+ })
5114
+ );
5115
+ }
5065
5116
  // ── Backlinks — workspace-level ────────────────────────────────────────
5066
5117
  async backlinksStatus() {
5067
5118
  return this.invoke(() => getApiV1BacklinksStatus({ client: this.heyClient }));
@@ -5468,6 +5519,12 @@ var discoverySessionIdInputSchema = z2.object({
5468
5519
  project: projectNameSchema,
5469
5520
  sessionId: z2.string().min(1).describe("Discovery session ID returned by canonry_discover_run_start.")
5470
5521
  });
5522
+ var discoveryHarvestInputSchema = z2.object({
5523
+ project: projectNameSchema,
5524
+ sessionId: z2.string().min(1).describe("Discovery session ID returned by canonry_discover_run_start."),
5525
+ minProbeHits: z2.number().int().positive().optional().describe("Recurrence floor \u2014 a candidate must have appeared in at least this many distinct probes to be admitted. Default 1."),
5526
+ anchor: z2.boolean().optional().describe("Apply the subject-anchor filter that drops off-topic acronym collisions. Default true; pass false for new-subject discovery on a well-scoped project.")
5527
+ });
5471
5528
  var discoveryPromoteInputSchema = z2.object({
5472
5529
  project: projectNameSchema,
5473
5530
  sessionId: z2.string().min(1).describe("Discovery session ID returned by canonry_discover_run_start."),
@@ -5768,6 +5825,28 @@ var canonryMcpTools = [
5768
5825
  openApiOperations: ["GET /api/v1/projects/{name}/citations/visibility"],
5769
5826
  handler: (client2, input) => client2.getCitationVisibility(input.project)
5770
5827
  }),
5828
+ defineTool({
5829
+ name: "canonry_visibility_stats",
5830
+ title: "Get aggregated mention/citation stats",
5831
+ description: 'Per-query mention (answer-text) and citation (source-list) counts WITH a sample size, pooled across many answer-visibility runs (probe-excluded) \u2014 the data to compute a confidence-aware (Wilson) proportion or detect drift without fetching every run. Tri-state aware: `checked` (the n for the mention proportion) counts only snapshots where answerMentioned was recorded; `null` ("not checked") is excluded, never counted as not-mentioned. Returns per-query `total`/`checked`/`mentioned`/`cited` + derived `mentionRate` (mentioned/checked) and `citedRate` (cited/total), `firstObserved`/`lastObserved`, and pooled `totals`. Window with `since`/`until` (ISO) OR `lastRuns` (mutually exclusive); with none set, EVERY completed/partial run is pooled (`window.runCount` says how many) \u2014 pass `lastRuns` for a recent sample. Set `groupBy=provider` for a per-provider breakdown whose counts sum to the pooled counts (`groupBy` is omitted from the response otherwise).',
5832
+ access: "read",
5833
+ tier: "monitoring",
5834
+ inputSchema: z2.object({
5835
+ project: projectNameSchema,
5836
+ since: z2.string().optional().describe("Inclusive lower bound on run createdAt (ISO 8601). A date-only value (YYYY-MM-DD) is the start of that UTC day. Mutually exclusive with lastRuns."),
5837
+ until: z2.string().optional().describe("Inclusive upper bound on run createdAt (ISO 8601). A date-only value (YYYY-MM-DD) covers the whole UTC day (through 23:59:59.999). Mutually exclusive with lastRuns."),
5838
+ lastRuns: z2.number().int().positive().optional().describe("Aggregate only the most recent N answer-visibility runs. Mutually exclusive with since/until."),
5839
+ groupBy: z2.enum(["provider"]).optional().describe('Set to "provider" for a per-provider breakdown.')
5840
+ }),
5841
+ annotations: readAnnotations(),
5842
+ openApiOperations: ["GET /api/v1/projects/{name}/visibility-stats"],
5843
+ handler: (client2, input) => client2.getVisibilityStats(input.project, {
5844
+ since: input.since,
5845
+ until: input.until,
5846
+ lastRuns: input.lastRuns,
5847
+ groupBy: input.groupBy
5848
+ })
5849
+ }),
5771
5850
  defineTool({
5772
5851
  name: "canonry_content_targets",
5773
5852
  title: "Get content targets",
@@ -6701,6 +6780,20 @@ var canonryMcpTools = [
6701
6780
  openApiOperations: ["GET /api/v1/projects/{name}/discover/sessions/{id}"],
6702
6781
  handler: (client2, input) => client2.getDiscoverySession(input.project, input.sessionId)
6703
6782
  }),
6783
+ defineTool({
6784
+ name: "canonry_discover_harvest",
6785
+ title: "Harvest discovery search queries",
6786
+ description: `Read the search queries the answer engine actually issued (Gemini's grounding fan-out) back out of a session's stored probes, gate them for buyer-intent + novelty, and return the survivors as candidate seeds ranked by how many distinct probes issued each one. These are a THIRD signal \u2014 issued retrieval queries \u2014 distinct from mention (answer text) and cited (source list); they carry no demand of their own. Read-only and derived: nothing is probed, tracked, or promoted. Use it to surface "queries the model searched for that you aren't tracking yet"; the operator/agent then decides what to add via canonry_query_add. minProbeHits raises the recurrence floor; anchor=false disables the subject filter. stats carries the raw count and per-reason rejection tally.`,
6787
+ access: "read",
6788
+ tier: "discovery",
6789
+ inputSchema: discoveryHarvestInputSchema,
6790
+ annotations: readAnnotations(),
6791
+ openApiOperations: ["GET /api/v1/projects/{name}/discover/sessions/{id}/harvest"],
6792
+ handler: (client2, input) => client2.getDiscoveryHarvest(input.project, input.sessionId, {
6793
+ minProbeHits: input.minProbeHits,
6794
+ anchor: input.anchor
6795
+ })
6796
+ }),
6704
6797
  defineTool({
6705
6798
  name: "canonry_discover_promote_preview",
6706
6799
  title: "Preview discovery promotion",
@@ -9,7 +9,7 @@ import {
9
9
  loadConfig,
10
10
  loadConfigRaw,
11
11
  saveConfigPatch
12
- } from "./chunk-Y3O3HBMN.js";
12
+ } from "./chunk-LLJPZKHG.js";
13
13
  import {
14
14
  CC_CACHE_DIR,
15
15
  DUCKDB_SPEC,
@@ -104,7 +104,7 @@ import {
104
104
  siteAuditPages,
105
105
  siteAuditSnapshots,
106
106
  usageCounters
107
- } from "./chunk-VJBO4VIK.js";
107
+ } from "./chunk-DLBQU3VG.js";
108
108
  import {
109
109
  AGENT_MEMORY_VALUE_MAX_BYTES,
110
110
  AGENT_PROVIDER_IDS,
@@ -160,7 +160,7 @@ import {
160
160
  validationError,
161
161
  winnabilityClassLabel,
162
162
  withRetry
163
- } from "./chunk-BNF3HXBW.js";
163
+ } from "./chunk-23HGQV22.js";
164
164
 
165
165
  // src/telemetry.ts
166
166
  import crypto from "crypto";
@@ -6289,7 +6289,7 @@ function readStoredGroundingSources(rawResponse) {
6289
6289
  return result;
6290
6290
  }
6291
6291
  async function backfillInsightsCommand(project, opts) {
6292
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-PDIAMP5I.js");
6292
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-ZHUJKZRO.js");
6293
6293
  const config = loadConfig();
6294
6294
  const db = createClient(config.database);
6295
6295
  migrate(db);
@@ -10877,6 +10877,21 @@ async function createServer(opts) {
10877
10877
  app.log.error({ runId: input.runId, err }, "Discovery run failed");
10878
10878
  });
10879
10879
  },
10880
+ // Read issued search queries (fan-out) back out of a stored probe payload.
10881
+ // Discovery is Gemini-only today, so the Gemini extractor handles every
10882
+ // probe; the provider arg lets a future multi-provider discovery dispatch.
10883
+ harvestSearchQueries: ({ rawResponse }) => extractSearchQueriesFromRaw(rawResponse),
10884
+ // Embed seam for the harvest's semantic novelty pass — the same Gemini
10885
+ // embedder the discovery seed pipeline uses. Resolved at call time so a
10886
+ // provider key set after boot is picked up; rejects (→ route degrades to
10887
+ // exact-match novelty) when no Gemini key is configured.
10888
+ embedQueries: (queriesToEmbed) => {
10889
+ const cfg = registry.get("gemini")?.config;
10890
+ if (!cfg?.apiKey) {
10891
+ return Promise.reject(new Error("Gemini API key not configured; harvest semantic novelty unavailable"));
10892
+ }
10893
+ return embedQueries(queriesToEmbed, { apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
10894
+ },
10880
10895
  onSiteAuditRequested: (runId, projectId, auditOpts) => {
10881
10896
  runSiteAudit(runId, projectId, auditOpts);
10882
10897
  },
package/dist/cli.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  setTelemetrySource,
28
28
  showFirstRunNotice,
29
29
  trackEvent
30
- } from "./chunk-M3IYKTSF.js";
30
+ } from "./chunk-SELXBOAP.js";
31
31
  import {
32
32
  CliError,
33
33
  EXIT_SYSTEM_ERROR,
@@ -44,7 +44,7 @@ import {
44
44
  saveConfig,
45
45
  saveConfigPatch,
46
46
  usageError
47
- } from "./chunk-Y3O3HBMN.js";
47
+ } from "./chunk-LLJPZKHG.js";
48
48
  import {
49
49
  apiKeys,
50
50
  createClient,
@@ -52,7 +52,7 @@ import {
52
52
  projects,
53
53
  queries,
54
54
  renderReportHtml
55
- } from "./chunk-VJBO4VIK.js";
55
+ } from "./chunk-DLBQU3VG.js";
56
56
  import {
57
57
  BacklinkSources,
58
58
  CcReleaseSyncStatuses,
@@ -70,13 +70,14 @@ import {
70
70
  formatGbpMetricLabel,
71
71
  formatIsoDate,
72
72
  formatMicros,
73
+ formatRatio,
73
74
  formatRunErrorOneLine,
74
75
  normalizeProjectAliases,
75
76
  notificationEventSchema,
76
77
  providerQuotaPolicySchema,
77
78
  resolveProviderInput,
78
79
  winnabilityClassSchema
79
- } from "./chunk-BNF3HXBW.js";
80
+ } from "./chunk-23HGQV22.js";
80
81
 
81
82
  // src/cli.ts
82
83
  import { pathToFileURL } from "url";
@@ -1831,6 +1832,39 @@ async function discoverShow(project, sessionId, opts) {
1831
1832
  }
1832
1833
  printSessionDetail(session);
1833
1834
  }
1835
+ async function discoverHarvest(project, sessionId, opts) {
1836
+ const client = getClient4();
1837
+ const harvest = await client.getDiscoveryHarvest(project, sessionId, {
1838
+ minProbeHits: opts.minProbeHits,
1839
+ anchor: opts.anchor
1840
+ });
1841
+ if (opts.format === "json") {
1842
+ console.log(JSON.stringify(harvest, null, 2));
1843
+ return;
1844
+ }
1845
+ if (opts.format === "jsonl") {
1846
+ const context = { project, sessionId, provider: harvest.provider };
1847
+ emitJsonl(harvest.candidates.map((c) => ({ ...context, ...c })));
1848
+ return;
1849
+ }
1850
+ const r = harvest.stats.rejected;
1851
+ console.log(`Harvested issued search queries \u2014 session ${harvest.sessionId} (${harvest.provider})`);
1852
+ console.log(
1853
+ ` ${harvest.stats.admitted} admitted of ${harvest.stats.rawCandidates} raw \xB7 floor=${harvest.minProbeHits} \xB7 anchor=${harvest.anchorApplied ? "on" : "off"} \xB7 novelty=${harvest.semanticNoveltyApplied ? "semantic" : "exact-only"}`
1854
+ );
1855
+ console.log(
1856
+ ` rejected: ${r.belowFloor} below-floor \xB7 ${r.length} length \xB7 ${r.navigational} navigational \xB7 ${r.duplicate} already-tracked \xB7 ${r.semanticDuplicate} synonym \xB7 ${r.offAnchor} off-subject`
1857
+ );
1858
+ if (harvest.candidates.length === 0) {
1859
+ console.log(" (no candidates \u2014 nothing to track from this session)");
1860
+ return;
1861
+ }
1862
+ console.log("");
1863
+ console.log(" HITS QUERY");
1864
+ for (const candidate of harvest.candidates) {
1865
+ console.log(` ${String(candidate.probeHits).padStart(4)} ${candidate.query}`);
1866
+ }
1867
+ }
1834
1868
  async function discoverPromotePreview(project, sessionId, opts) {
1835
1869
  const client = getClient4();
1836
1870
  const preview = await client.previewDiscoveryPromote(project, sessionId);
@@ -2155,6 +2189,32 @@ var DISCOVER_CLI_COMMANDS = [
2155
2189
  });
2156
2190
  }
2157
2191
  },
2192
+ {
2193
+ path: ["discover", "harvest"],
2194
+ usage: "canonry discover harvest <project> <session-id> [--min-probe-hits <n>] [--no-anchor] [--format json|jsonl]",
2195
+ options: {
2196
+ "min-probe-hits": stringOption(),
2197
+ "no-anchor": { type: "boolean", default: false }
2198
+ },
2199
+ run: async (input) => {
2200
+ const usage = "canonry discover harvest <project> <session-id> [--min-probe-hits <n>] [--no-anchor] [--format json|jsonl]";
2201
+ const project = requireProject(input, "discover.harvest", usage);
2202
+ const sessionId = requirePositional(input, 1, {
2203
+ command: "discover.harvest",
2204
+ usage,
2205
+ message: "session ID is required"
2206
+ });
2207
+ await discoverHarvest(project, sessionId, {
2208
+ minProbeHits: parseIntegerOption(input, "min-probe-hits", {
2209
+ command: "discover.harvest",
2210
+ usage,
2211
+ message: "--min-probe-hits must be an integer"
2212
+ }),
2213
+ anchor: !getBoolean(input.values, "no-anchor"),
2214
+ format: input.format
2215
+ });
2216
+ }
2217
+ },
2158
2218
  {
2159
2219
  path: ["discover", "promote", "preview"],
2160
2220
  usage: "canonry discover promote preview <project> <session-id> [--format json]",
@@ -2868,7 +2928,7 @@ async function gaSocialReferralSummary(project, opts) {
2868
2928
  console.log(` Sessions: ${traffic.socialSessions} (${traffic.socialSharePct}% of ${traffic.totalSessions} total)`);
2869
2929
  console.log(` Users: ${traffic.socialUsers}`);
2870
2930
  console.log();
2871
- const fmtTrend = (pct3) => pct3 === null ? "n/a" : `${pct3 >= 0 ? "+" : ""}${pct3}%`;
2931
+ const fmtTrend = (pct4) => pct4 === null ? "n/a" : `${pct4 >= 0 ? "+" : ""}${pct4}%`;
2872
2932
  console.log(` 7d trend: ${fmtTrend(trend.trend7dPct)} (${trend.socialSessions7d} vs ${trend.socialSessionsPrev7d})`);
2873
2933
  console.log(` 30d trend: ${fmtTrend(trend.trend30dPct)} (${trend.socialSessions30d} vs ${trend.socialSessionsPrev30d})`);
2874
2934
  if (trend.biggestMover) {
@@ -2911,7 +2971,7 @@ async function gaSocialReferralSummary(project, opts) {
2911
2971
  async function gaAttribution(project, opts) {
2912
2972
  const client = getClient6();
2913
2973
  const traffic = await client.gaTraffic(project);
2914
- const fmtTrend = (pct3) => pct3 === null ? "n/a" : `${pct3 >= 0 ? "+" : ""}${pct3}%`;
2974
+ const fmtTrend = (pct4) => pct4 === null ? "n/a" : `${pct4 >= 0 ? "+" : ""}${pct4}%`;
2915
2975
  if (opts?.trend) {
2916
2976
  const trend = await client.gaAttributionTrend(project);
2917
2977
  if (isMachineFormat(opts.format)) {
@@ -3475,9 +3535,9 @@ async function gbpPlaces(project, opts) {
3475
3535
  console.log(` ${p.locationName} [${p.tier}] ${amenities}`);
3476
3536
  }
3477
3537
  }
3478
- function fmtDelta(pct3) {
3479
- if (pct3 === null) return "n/a";
3480
- return `${pct3 >= 0 ? "+" : ""}${pct3}%`;
3538
+ function fmtDelta(pct4) {
3539
+ if (pct4 === null) return "n/a";
3540
+ return `${pct4 >= 0 ? "+" : ""}${pct4}%`;
3481
3541
  }
3482
3542
  async function gbpSummary(project, opts) {
3483
3543
  const client = getClient7();
@@ -6951,14 +7011,14 @@ function printMetrics(data) {
6951
7011
  console.log(`
6952
7012
  Citation Rate Trends (${data.window})`);
6953
7013
  console.log("\u2500".repeat(50));
6954
- const pct3 = (n) => `${(n * 100).toFixed(1)}%`;
6955
- console.log(` Overall: ${pct3(data.overall.citationRate)} (${data.overall.cited}/${data.overall.total})`);
7014
+ const pct4 = (n) => `${(n * 100).toFixed(1)}%`;
7015
+ console.log(` Overall: ${pct4(data.overall.citationRate)} (${data.overall.cited}/${data.overall.total})`);
6956
7016
  console.log(` Trend: ${data.trend}`);
6957
7017
  if (Object.keys(data.byProvider).length > 0) {
6958
7018
  console.log(`
6959
7019
  By Provider:`);
6960
7020
  for (const [provider, metric] of Object.entries(data.byProvider)) {
6961
- console.log(` ${provider.padEnd(10)} ${pct3(metric.citationRate).padStart(6)} (${metric.cited}/${metric.total})`);
7021
+ console.log(` ${provider.padEnd(10)} ${pct4(metric.citationRate).padStart(6)} (${metric.cited}/${metric.total})`);
6962
7022
  }
6963
7023
  }
6964
7024
  if (data.buckets.length > 0) {
@@ -6967,7 +7027,7 @@ Citation Rate Trends (${data.window})`);
6967
7027
  for (const bucket of data.buckets) {
6968
7028
  const start = bucket.startDate.slice(0, 10);
6969
7029
  const bar = bucket.total > 0 ? "\u2588".repeat(Math.round(bucket.citationRate * 20)) : "";
6970
- console.log(` ${start} ${pct3(bucket.citationRate).padStart(6)} ${bar}`);
7030
+ console.log(` ${start} ${pct4(bucket.citationRate).padStart(6)} ${bar}`);
6971
7031
  }
6972
7032
  }
6973
7033
  const providersInBuckets = [...new Set(data.buckets.flatMap((b) => Object.keys(b.byProvider ?? {})))].sort();
@@ -6981,7 +7041,7 @@ Citation Rate Trends (${data.window})`);
6981
7041
  if (!metric) continue;
6982
7042
  const start = bucket.startDate.slice(0, 10);
6983
7043
  const bar = metric.total > 0 ? "\u2588".repeat(Math.round(metric.citationRate * 20)) : "";
6984
- console.log(` ${start} ${pct3(metric.citationRate).padStart(6)} ${bar}`);
7044
+ console.log(` ${start} ${pct4(metric.citationRate).padStart(6)} ${bar}`);
6985
7045
  }
6986
7046
  }
6987
7047
  }
@@ -7019,9 +7079,9 @@ Source Origin Breakdown`);
7019
7079
  return;
7020
7080
  }
7021
7081
  for (const cat of data.overall) {
7022
- const pct3 = `${(cat.percentage * 100).toFixed(1)}%`;
7082
+ const pct4 = `${(cat.percentage * 100).toFixed(1)}%`;
7023
7083
  const domains = cat.topDomains.slice(0, 3).map((d) => d.domain).join(", ");
7024
- console.log(` ${cat.label.padEnd(20)} ${pct3.padStart(6)} (${cat.count}) ${domains}`);
7084
+ console.log(` ${cat.label.padEnd(20)} ${pct4.padStart(6)} (${cat.count}) ${domains}`);
7025
7085
  }
7026
7086
  }
7027
7087
 
@@ -9455,8 +9515,8 @@ function printMentionShareBreakdown(mentionShare) {
9455
9515
  const youPct = (breakdown.projectMentionSnapshots / total * 100).toFixed(1);
9456
9516
  console.log(` you${" ".repeat(28)} ${breakdown.projectMentionSnapshots} mentions (${youPct}% of combined)`);
9457
9517
  for (const row of breakdown.perCompetitor.slice(0, 3)) {
9458
- const pct3 = (row.mentionSnapshots / total * 100).toFixed(1);
9459
- console.log(` ${row.domain.padEnd(30)} ${row.mentionSnapshots} mentions (${pct3}% of combined)`);
9518
+ const pct4 = (row.mentionSnapshots / total * 100).toFixed(1);
9519
+ console.log(` ${row.domain.padEnd(30)} ${row.mentionSnapshots} mentions (${pct4}% of combined)`);
9460
9520
  }
9461
9521
  if (breakdown.perCompetitor.length > 3) {
9462
9522
  console.log(` + ${breakdown.perCompetitor.length - 3} more competitor${breakdown.perCompetitor.length - 3 === 1 ? "" : "s"} (--format json for full breakdown)`);
@@ -11266,6 +11326,135 @@ var TECHNICAL_AEO_CLI_COMMANDS = [
11266
11326
  }
11267
11327
  ];
11268
11328
 
11329
+ // src/commands/visibility-stats.ts
11330
+ async function showVisibilityStats(project, opts) {
11331
+ const client = createApiClient();
11332
+ const data = await client.getVisibilityStats(project, {
11333
+ since: opts.since,
11334
+ until: opts.until,
11335
+ lastRuns: opts.lastRuns,
11336
+ groupBy: opts.byProvider ? "provider" : void 0
11337
+ });
11338
+ if (opts.format === "jsonl") {
11339
+ emitJsonl(data.queries.map((q) => ({ project: data.project, runCount: data.window.runCount, ...q })));
11340
+ return;
11341
+ }
11342
+ if (opts.format === "json") {
11343
+ console.log(JSON.stringify(data, null, 2));
11344
+ return;
11345
+ }
11346
+ printVisibilityStats(data);
11347
+ }
11348
+ function pct3(rate) {
11349
+ return rate === null ? "\u2014" : formatRatio(rate);
11350
+ }
11351
+ function citedCell(c) {
11352
+ return `${c.cited}/${c.total}`;
11353
+ }
11354
+ function mentionedCell(c) {
11355
+ return `${c.mentioned}/${c.checked}`;
11356
+ }
11357
+ function printVisibilityStats(data) {
11358
+ const w = data.window;
11359
+ const windowParts = [`${w.runCount} run(s)`];
11360
+ if (w.lastRuns !== null) windowParts.push(`last ${w.lastRuns}`);
11361
+ if (w.since !== null) windowParts.push(`since ${w.since}`);
11362
+ if (w.until !== null) windowParts.push(`until ${w.until}`);
11363
+ console.log("Visibility stats (cited = source list, mentioned = answer text)");
11364
+ console.log(`Window: ${windowParts.join(" \xB7 ")}`);
11365
+ console.log('Cited = cited/total snapshots \xB7 Mentioned = mentioned/checked (checked excludes "not checked")');
11366
+ console.log("");
11367
+ if (data.queries.length === 0) {
11368
+ console.log("No answer-visibility snapshots in this window \u2014 run a sweep first (canonry run <project>).");
11369
+ return;
11370
+ }
11371
+ const rows = data.queries.map((q) => ({
11372
+ label: q.query,
11373
+ cited: citedCell(q),
11374
+ citedPct: pct3(q.citedRate),
11375
+ ment: mentionedCell(q),
11376
+ mentPct: pct3(q.mentionRate)
11377
+ }));
11378
+ const queryWidth = Math.max(7, ...rows.map((r) => r.label.length));
11379
+ const citedWidth = Math.max(7, ...rows.map((r) => r.cited.length));
11380
+ const mentWidth = Math.max(9, ...rows.map((r) => r.ment.length));
11381
+ const header = [
11382
+ "Query".padEnd(queryWidth),
11383
+ "Cited".padEnd(citedWidth),
11384
+ "Cited%".padStart(7),
11385
+ "Mentioned".padEnd(mentWidth),
11386
+ "Ment%".padStart(7)
11387
+ ].join(" ");
11388
+ console.log(header);
11389
+ console.log("\u2500".repeat(header.length));
11390
+ for (const r of rows) {
11391
+ console.log(
11392
+ [
11393
+ r.label.padEnd(queryWidth),
11394
+ r.cited.padEnd(citedWidth),
11395
+ r.citedPct.padStart(7),
11396
+ r.ment.padEnd(mentWidth),
11397
+ r.mentPct.padStart(7)
11398
+ ].join(" ")
11399
+ );
11400
+ }
11401
+ console.log("\u2500".repeat(header.length));
11402
+ console.log(
11403
+ [
11404
+ "TOTAL".padEnd(queryWidth),
11405
+ citedCell(data.totals).padEnd(citedWidth),
11406
+ pct3(data.totals.citedRate).padStart(7),
11407
+ mentionedCell(data.totals).padEnd(mentWidth),
11408
+ pct3(data.totals.mentionRate).padStart(7)
11409
+ ].join(" ")
11410
+ );
11411
+ if (data.groupBy === "provider" && data.byProvider && data.byProvider.length > 0) {
11412
+ console.log("");
11413
+ console.log("By provider (pooled across queries):");
11414
+ const provWidth = Math.max(8, ...data.byProvider.map((p) => p.provider.length));
11415
+ for (const p of data.byProvider) {
11416
+ console.log(
11417
+ [
11418
+ ` ${p.provider}`.padEnd(provWidth + 2),
11419
+ citedCell(p).padEnd(citedWidth),
11420
+ pct3(p.citedRate).padStart(7),
11421
+ mentionedCell(p).padEnd(mentWidth),
11422
+ pct3(p.mentionRate).padStart(7)
11423
+ ].join(" ")
11424
+ );
11425
+ }
11426
+ }
11427
+ }
11428
+
11429
+ // src/cli-commands/visibility-stats.ts
11430
+ var USAGE4 = "canonry visibility-stats <project> [--since <iso>] [--until <iso>] [--last-runs <n>] [--by-provider] [--format json|jsonl]";
11431
+ var VISIBILITY_STATS_CLI_COMMANDS = [
11432
+ {
11433
+ path: ["visibility-stats"],
11434
+ usage: USAGE4,
11435
+ options: {
11436
+ since: stringOption(),
11437
+ until: stringOption(),
11438
+ "last-runs": stringOption(),
11439
+ "by-provider": { type: "boolean", default: false }
11440
+ },
11441
+ run: async (input) => {
11442
+ const project = requireProject(input, "visibility-stats", USAGE4);
11443
+ await showVisibilityStats(project, {
11444
+ since: getString(input.values, "since"),
11445
+ until: getString(input.values, "until"),
11446
+ lastRuns: parseIntegerOption(input, "last-runs", {
11447
+ command: "visibility-stats",
11448
+ usage: USAGE4,
11449
+ message: "--last-runs must be an integer"
11450
+ }),
11451
+ byProvider: getBoolean(input.values, "by-provider"),
11452
+ format: input.format
11453
+ });
11454
+ }
11455
+ }
11456
+ ];
11457
+
11269
11458
  // src/cli-commands/wordpress.ts
11270
11459
  import fs11 from "fs";
11271
11460
 
@@ -12850,6 +13039,7 @@ var REGISTERED_CLI_COMMANDS = [
12850
13039
  ...GBP_CLI_COMMANDS,
12851
13040
  ...TRAFFIC_CLI_COMMANDS,
12852
13041
  ...INTELLIGENCE_CLI_COMMANDS,
13042
+ ...VISIBILITY_STATS_CLI_COMMANDS,
12853
13043
  ...CONTENT_CLI_COMMANDS,
12854
13044
  ...AGENT_CLI_COMMANDS,
12855
13045
  ...DISCOVER_CLI_COMMANDS,
@@ -12861,7 +13051,7 @@ var REGISTERED_CLI_COMMANDS = [
12861
13051
 
12862
13052
  // src/cli.ts
12863
13053
  import { createRequire as createRequire2 } from "module";
12864
- var USAGE4 = `
13054
+ var USAGE5 = `
12865
13055
  cnry \u2014 AEO monitoring CLI ('canonry' also works)
12866
13056
 
12867
13057
  Usage: cnry <command> [options]
@@ -12926,7 +13116,7 @@ function extractFormat(cmdArgs) {
12926
13116
  }
12927
13117
  async function runCli(args = process.argv.slice(2)) {
12928
13118
  if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
12929
- console.log(USAGE4);
13119
+ console.log(USAGE5);
12930
13120
  return 0;
12931
13121
  }
12932
13122
  if (args.includes("--version") || args.includes("-v")) {
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-M3IYKTSF.js";
3
+ } from "./chunk-SELXBOAP.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-Y3O3HBMN.js";
7
- import "./chunk-VJBO4VIK.js";
8
- import "./chunk-BNF3HXBW.js";
6
+ } from "./chunk-LLJPZKHG.js";
7
+ import "./chunk-DLBQU3VG.js";
8
+ import "./chunk-23HGQV22.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-VJBO4VIK.js";
4
- import "./chunk-BNF3HXBW.js";
3
+ } from "./chunk-DLBQU3VG.js";
4
+ import "./chunk-23HGQV22.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-Y3O3HBMN.js";
6
+ } from "./chunk-LLJPZKHG.js";
7
7
  import {
8
8
  isReadOnlyKey
9
- } from "./chunk-BNF3HXBW.js";
9
+ } from "./chunk-23HGQV22.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.84.0",
3
+ "version": "4.86.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",
@@ -63,26 +63,26 @@
63
63
  "tsup": "^8.5.1",
64
64
  "tsx": "^4.19.0",
65
65
  "@ainyc/canonry-api-client": "0.0.0",
66
- "@ainyc/canonry-db": "0.0.0",
67
66
  "@ainyc/canonry-api-routes": "0.0.0",
68
- "@ainyc/canonry-contracts": "0.0.0",
69
67
  "@ainyc/canonry-config": "0.0.0",
68
+ "@ainyc/canonry-contracts": "0.0.0",
69
+ "@ainyc/canonry-db": "0.0.0",
70
70
  "@ainyc/canonry-integration-bing": "0.0.0",
71
- "@ainyc/canonry-integration-cloud-run": "0.0.0",
72
71
  "@ainyc/canonry-integration-openai-ads": "0.0.0",
72
+ "@ainyc/canonry-integration-cloud-run": "0.0.0",
73
73
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
74
74
  "@ainyc/canonry-integration-google": "0.0.0",
75
75
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
76
76
  "@ainyc/canonry-integration-google-places": "0.0.0",
77
- "@ainyc/canonry-intelligence": "0.0.0",
78
77
  "@ainyc/canonry-integration-traffic": "0.0.0",
79
- "@ainyc/canonry-provider-claude": "0.0.0",
80
- "@ainyc/canonry-provider-cdp": "0.0.0",
78
+ "@ainyc/canonry-intelligence": "0.0.0",
81
79
  "@ainyc/canonry-integration-wordpress": "0.0.0",
82
- "@ainyc/canonry-provider-gemini": "0.0.0",
83
- "@ainyc/canonry-provider-openai": "0.0.0",
80
+ "@ainyc/canonry-provider-cdp": "0.0.0",
81
+ "@ainyc/canonry-provider-claude": "0.0.0",
84
82
  "@ainyc/canonry-provider-local": "0.0.0",
85
- "@ainyc/canonry-provider-perplexity": "0.0.0"
83
+ "@ainyc/canonry-provider-gemini": "0.0.0",
84
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
85
+ "@ainyc/canonry-provider-openai": "0.0.0"
86
86
  },
87
87
  "scripts": {
88
88
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",