@ainyc/canonry 2.16.3 → 3.0.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.
package/assets/index.html CHANGED
@@ -12,8 +12,8 @@
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-Wi4Qqm0A.js"></script>
16
- <link rel="stylesheet" crossorigin href="./assets/index-Ec4au-rY.css">
15
+ <script type="module" crossorigin src="./assets/index-DOxzPUzl.js"></script>
16
+ <link rel="stylesheet" crossorigin href="./assets/index-CEF4UuGT.css">
17
17
  </head>
18
18
  <body>
19
19
  <div id="root"></div>
@@ -1155,7 +1155,7 @@ var scheduleUpsertRequestSchema = z10.object({
1155
1155
 
1156
1156
  // ../contracts/src/analytics.ts
1157
1157
  import { z as z11 } from "zod";
1158
- var visibilityMetricModeSchema = z11.enum(["answer", "citation"]);
1158
+ var visibilityMetricModeSchema = z11.enum(["mentioned", "cited"]);
1159
1159
  var VisibilityMetricModes = visibilityMetricModeSchema.enum;
1160
1160
  function parseWindow(value) {
1161
1161
  if (value === "7d" || value === "30d" || value === "90d" || value === "all") return value;
@@ -1919,6 +1919,7 @@ var citationCoverageProviderSchema = z18.object({
1919
1919
  provider: z18.string(),
1920
1920
  citationState: citationStateSchema,
1921
1921
  cited: z18.boolean(),
1922
+ mentioned: z18.boolean(),
1922
1923
  runId: z18.string(),
1923
1924
  runCreatedAt: z18.string()
1924
1925
  });
@@ -1927,6 +1928,7 @@ var citationCoverageRowSchema = z18.object({
1927
1928
  keyword: z18.string(),
1928
1929
  providers: z18.array(citationCoverageProviderSchema),
1929
1930
  citedCount: z18.number().int().nonnegative(),
1931
+ mentionedCount: z18.number().int().nonnegative(),
1930
1932
  totalProviders: z18.number().int().nonnegative()
1931
1933
  });
1932
1934
  var competitorGapRowSchema = z18.object({
@@ -1940,10 +1942,15 @@ var competitorGapRowSchema = z18.object({
1940
1942
  var citationVisibilitySummarySchema = z18.object({
1941
1943
  providersConfigured: z18.number().int().nonnegative(),
1942
1944
  providersCiting: z18.number().int().nonnegative(),
1945
+ providersMentioning: z18.number().int().nonnegative(),
1943
1946
  totalKeywords: z18.number().int().nonnegative(),
1944
- keywordsCited: z18.number().int().nonnegative(),
1945
- keywordsFullyCovered: z18.number().int().nonnegative(),
1946
- keywordsUncovered: z18.number().int().nonnegative(),
1947
+ // Cross-tab buckets — each tracked keyword with at least one snapshot lands
1948
+ // in exactly one of these. Keywords with zero snapshots are not counted in
1949
+ // any bucket; (sum of buckets) ≤ totalKeywords.
1950
+ keywordsCitedAndMentioned: z18.number().int().nonnegative(),
1951
+ keywordsCitedOnly: z18.number().int().nonnegative(),
1952
+ keywordsMentionedOnly: z18.number().int().nonnegative(),
1953
+ keywordsInvisible: z18.number().int().nonnegative(),
1947
1954
  latestRunId: z18.string().nullable(),
1948
1955
  latestRunAt: z18.string().nullable()
1949
1956
  });
@@ -1959,10 +1966,12 @@ function emptyCitationVisibility(reason) {
1959
1966
  summary: {
1960
1967
  providersConfigured: 0,
1961
1968
  providersCiting: 0,
1969
+ providersMentioning: 0,
1962
1970
  totalKeywords: 0,
1963
- keywordsCited: 0,
1964
- keywordsFullyCovered: 0,
1965
- keywordsUncovered: 0,
1971
+ keywordsCitedAndMentioned: 0,
1972
+ keywordsCitedOnly: 0,
1973
+ keywordsMentionedOnly: 0,
1974
+ keywordsInvisible: 0,
1966
1975
  latestRunId: null,
1967
1976
  latestRunAt: null
1968
1977
  },
@@ -62,7 +62,7 @@ import {
62
62
  visibilityStateFromAnswerMentioned,
63
63
  windowCutoff,
64
64
  wordpressEnvSchema
65
- } from "./chunk-7DVIJC6L.js";
65
+ } from "./chunk-5X3TJ5BC.js";
66
66
  import {
67
67
  IntelligenceService,
68
68
  agentMemory,
@@ -1940,10 +1940,10 @@ async function analyticsRoutes(app) {
1940
1940
  return reply.send({
1941
1941
  window,
1942
1942
  buckets: [],
1943
- overall: { citationRate: 0, cited: 0, total: 0, answerRate: 0, answerMentionedCount: 0 },
1943
+ overall: { citationRate: 0, cited: 0, total: 0, mentionRate: 0, mentionedCount: 0 },
1944
1944
  byProvider: {},
1945
1945
  trend: "stable",
1946
- answerTrend: "stable",
1946
+ mentionTrend: "stable",
1947
1947
  keywordChanges: []
1948
1948
  });
1949
1949
  }
@@ -1975,9 +1975,9 @@ async function analyticsRoutes(app) {
1975
1975
  const bucketSize = bucketSizeForSpan(spanDays);
1976
1976
  const buckets = computeBuckets(allSnapshots, projectRuns, bucketSize, keywordCreatedAt);
1977
1977
  const trend = computeTrend(buckets, "citationRate");
1978
- const answerTrend = computeTrend(buckets, "answerRate");
1978
+ const mentionTrend = computeTrend(buckets, "mentionRate");
1979
1979
  const keywordChanges = computeKeywordChanges(projectKeywords, cutoff);
1980
- return reply.send({ window, buckets, overall, byProvider, trend, answerTrend, keywordChanges });
1980
+ return reply.send({ window, buckets, overall, byProvider, trend, mentionTrend, keywordChanges });
1981
1981
  });
1982
1982
  app.get("/projects/:name/analytics/gaps", async (request, reply) => {
1983
1983
  const project = resolveProject(app.db, request.params.name);
@@ -2167,13 +2167,13 @@ function bucketSizeForSpan(spanDays) {
2167
2167
  function computeProviderMetric(snapshots) {
2168
2168
  const total = snapshots.length;
2169
2169
  const cited = snapshots.filter((s) => s.citationState === "cited").length;
2170
- const answerMentionedCount = snapshots.filter((s) => s.resolvedMentioned).length;
2170
+ const mentionedCount = snapshots.filter((s) => s.resolvedMentioned).length;
2171
2171
  return {
2172
2172
  citationRate: total > 0 ? Math.round(cited / total * 1e4) / 1e4 : 0,
2173
2173
  cited,
2174
2174
  total,
2175
- answerRate: total > 0 ? Math.round(answerMentionedCount / total * 1e4) / 1e4 : 0,
2176
- answerMentionedCount
2175
+ mentionRate: total > 0 ? Math.round(mentionedCount / total * 1e4) / 1e4 : 0,
2176
+ mentionedCount
2177
2177
  };
2178
2178
  }
2179
2179
  function computeBuckets(snapshots, projectRuns, bucketDays, keywordCreatedAt) {
@@ -2207,8 +2207,8 @@ function computeBuckets(snapshots, projectRuns, bucketDays, keywordCreatedAt) {
2207
2207
  cited: metric.cited,
2208
2208
  total: metric.total,
2209
2209
  keywordCount,
2210
- answerRate: metric.answerRate,
2211
- answerMentionedCount: metric.answerMentionedCount
2210
+ mentionRate: metric.mentionRate,
2211
+ mentionedCount: metric.mentionedCount
2212
2212
  });
2213
2213
  }
2214
2214
  start = end;
@@ -2383,6 +2383,7 @@ async function citationRoutes(app) {
2383
2383
  citationState: querySnapshots.citationState,
2384
2384
  citedDomains: querySnapshots.citedDomains,
2385
2385
  competitorOverlap: querySnapshots.competitorOverlap,
2386
+ answerMentioned: querySnapshots.answerMentioned,
2386
2387
  createdAt: querySnapshots.createdAt
2387
2388
  }).from(querySnapshots).where(inArray3(querySnapshots.runId, projectRuns.map((r) => r.id))).all();
2388
2389
  if (rawSnapshots.length === 0) {
@@ -2415,39 +2416,54 @@ function computeCitationVisibility(input) {
2415
2416
  const observedProviders = /* @__PURE__ */ new Set();
2416
2417
  for (const pair of latestByPair.values()) observedProviders.add(pair.provider);
2417
2418
  const providerUniverse = configuredProviders.length > 0 ? Array.from(new Set(configuredProviders)) : Array.from(observedProviders).sort();
2418
- const byKeyword = [];
2419
2419
  const providersCitingTracker = /* @__PURE__ */ new Set();
2420
- let keywordsCited = 0;
2421
- let keywordsFullyCovered = 0;
2422
- let keywordsUncovered = 0;
2420
+ const providersMentioningTracker = /* @__PURE__ */ new Set();
2421
+ let keywordsCitedAndMentioned = 0;
2422
+ let keywordsCitedOnly = 0;
2423
+ let keywordsMentionedOnly = 0;
2424
+ let keywordsInvisible = 0;
2425
+ const byKeyword = [];
2423
2426
  for (const kw of kws) {
2424
2427
  const providers = [];
2425
2428
  let citedCount = 0;
2429
+ let mentionedCount = 0;
2426
2430
  for (const provider of providerUniverse) {
2427
2431
  const snap = latestByPair.get(`${kw.id}::${provider}`);
2428
2432
  if (!snap) continue;
2429
2433
  const state = snap.citationState;
2430
2434
  const cited = citationStateToCited(state);
2435
+ const mentioned = snap.answerMentioned === true;
2431
2436
  if (cited) {
2432
2437
  citedCount++;
2433
2438
  providersCitingTracker.add(provider);
2434
2439
  }
2440
+ if (mentioned) {
2441
+ mentionedCount++;
2442
+ providersMentioningTracker.add(provider);
2443
+ }
2435
2444
  providers.push({
2436
2445
  provider,
2437
2446
  citationState: state,
2438
2447
  cited,
2448
+ mentioned,
2439
2449
  runId: snap.runId,
2440
2450
  runCreatedAt: snap.runCreatedAt
2441
2451
  });
2442
2452
  }
2443
- if (citedCount > 0) keywordsCited++;
2444
- if (providerUniverse.length > 0 && citedCount === providerUniverse.length) keywordsFullyCovered++;
2445
- if (providers.length > 0 && citedCount === 0) keywordsUncovered++;
2453
+ if (providers.length > 0) {
2454
+ const anyCited = citedCount > 0;
2455
+ const anyMentioned = mentionedCount > 0;
2456
+ if (anyCited && anyMentioned) keywordsCitedAndMentioned++;
2457
+ else if (anyCited) keywordsCitedOnly++;
2458
+ else if (anyMentioned) keywordsMentionedOnly++;
2459
+ else keywordsInvisible++;
2460
+ }
2446
2461
  byKeyword.push({
2447
2462
  keywordId: kw.id,
2448
2463
  keyword: kw.keyword,
2449
2464
  providers,
2450
2465
  citedCount,
2466
+ mentionedCount,
2451
2467
  totalProviders: providers.length
2452
2468
  });
2453
2469
  }
@@ -2488,10 +2504,12 @@ function computeCitationVisibility(input) {
2488
2504
  const summary = {
2489
2505
  providersConfigured: providerUniverse.length,
2490
2506
  providersCiting: providersCitingTracker.size,
2507
+ providersMentioning: providersMentioningTracker.size,
2491
2508
  totalKeywords: kws.length,
2492
- keywordsCited,
2493
- keywordsFullyCovered,
2494
- keywordsUncovered,
2509
+ keywordsCitedAndMentioned,
2510
+ keywordsCitedOnly,
2511
+ keywordsMentionedOnly,
2512
+ keywordsInvisible,
2495
2513
  latestRunId,
2496
2514
  latestRunAt
2497
2515
  };
@@ -5388,8 +5406,8 @@ var routeCatalog = [
5388
5406
  {
5389
5407
  method: "get",
5390
5408
  path: "/api/v1/projects/{name}/citations/visibility",
5391
- summary: "Citation visibility headline (cited by N of M engines)",
5392
- description: 'Single-call read for the AI citation surface. Returns project headline (`providersConfigured`/`providersCiting`/keyword coverage counts), per-keyword engine coverage rows from the latest snapshot per (keyword \xD7 provider), and a competitor-gap list (keywords where the project is not cited but a configured competitor is). Status `no-data` with `reason: "no-runs-yet"` or `"no-keywords"` when the project lacks the inputs.',
5409
+ summary: "Citation visibility headline (citation + answer-mention, by engine + keyword)",
5410
+ description: 'Single-call read for the AI citation surface. Returns two parallel headline metrics (`providersCiting` = engines that cite the project in their grounding/source list, `providersMentioning` = engines that name the project in answer prose), per-keyword cross-tab buckets (`keywordsCitedAndMentioned` / `keywordsCitedOnly` / `keywordsMentionedOnly` / `keywordsInvisible` \u2014 mutually exclusive over keywords that have at least one snapshot), per-keyword engine coverage rows from the latest snapshot per (keyword \xD7 provider) with both `cited` and `mentioned` flags, and a competitor-gap list (keywords where the project is not cited but a configured competitor is). Status `no-data` with `reason: "no-runs-yet"` or `"no-keywords"` when the project lacks the inputs.',
5393
5411
  tags: ["intelligence"],
5394
5412
  parameters: [nameParameter],
5395
5413
  responses: {
package/dist/cli.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  setGoogleAuthConfig,
18
18
  showFirstRunNotice,
19
19
  trackEvent
20
- } from "./chunk-75EV36V6.js";
20
+ } from "./chunk-VUP7AQTD.js";
21
21
  import {
22
22
  CcReleaseSyncStatuses,
23
23
  CheckScopes,
@@ -45,7 +45,7 @@ import {
45
45
  saveConfig,
46
46
  saveConfigPatch,
47
47
  usageError
48
- } from "./chunk-7DVIJC6L.js";
48
+ } from "./chunk-5X3TJ5BC.js";
49
49
  import {
50
50
  apiKeys,
51
51
  competitors,
@@ -172,7 +172,7 @@ async function backfillAnswerVisibilityCommand(opts) {
172
172
  const scopedProjects = projectFilter ? db.select().from(projects).where(eq(projects.name, projectFilter)).all() : db.select().from(projects).all();
173
173
  let examined = 0;
174
174
  let updated = 0;
175
- let visible = 0;
175
+ let mentioned = 0;
176
176
  let reparsed = 0;
177
177
  let providerErrors = 0;
178
178
  if (scopedProjects.length > 0) {
@@ -215,7 +215,7 @@ async function backfillAnswerVisibilityCommand(opts) {
215
215
  if (reparsedResult?.providerError) providerErrors++;
216
216
  const answerText = reparsedResult?.answerText ?? snapshot.answerText ?? "";
217
217
  const nextValue = determineAnswerMentioned(answerText, project.displayName, projectDomains);
218
- if (nextValue) visible++;
218
+ if (nextValue) mentioned++;
219
219
  const nextPatch = {};
220
220
  if (snapshot.answerMentioned !== nextValue) {
221
221
  nextPatch.answerMentioned = nextValue;
@@ -284,7 +284,7 @@ async function backfillAnswerVisibilityCommand(opts) {
284
284
  projects: scopedProjects.length,
285
285
  examined,
286
286
  updated,
287
- visible,
287
+ mentioned,
288
288
  reparsed,
289
289
  providerErrors
290
290
  };
@@ -297,11 +297,11 @@ async function backfillAnswerVisibilityCommand(opts) {
297
297
  console.log(` Project: ${projectFilter}`);
298
298
  }
299
299
  console.log(` Projects: ${scopedProjects.length}`);
300
- console.log(` Examined: ${examined}`);
301
- console.log(` Updated: ${updated}`);
302
- console.log(` Visible: ${visible}`);
303
- console.log(` Reparsed: ${reparsed}`);
304
- console.log(` Errors: ${providerErrors}`);
300
+ console.log(` Examined: ${examined}`);
301
+ console.log(` Updated: ${updated}`);
302
+ console.log(` Mentioned: ${mentioned}`);
303
+ console.log(` Reparsed: ${reparsed}`);
304
+ console.log(` Errors: ${providerErrors}`);
305
305
  }
306
306
  function backfillNormalizedPaths(db, opts) {
307
307
  const baseConditions = [];
@@ -6351,15 +6351,28 @@ async function showCitationVisibility(project, opts) {
6351
6351
  }
6352
6352
  }
6353
6353
  function printSummary(data) {
6354
- const { providersCiting, providersConfigured, totalKeywords, keywordsCited, keywordsFullyCovered, keywordsUncovered } = data.summary;
6355
- console.log(`Citation visibility \u2014 cited by ${providersCiting}/${providersConfigured} engines`);
6354
+ const {
6355
+ providersCiting,
6356
+ providersMentioning,
6357
+ providersConfigured,
6358
+ totalKeywords,
6359
+ keywordsCitedAndMentioned,
6360
+ keywordsCitedOnly,
6361
+ keywordsMentionedOnly,
6362
+ keywordsInvisible
6363
+ } = data.summary;
6364
+ console.log("Citation visibility");
6356
6365
  if (data.summary.latestRunAt) {
6357
- console.log(`Latest run: ${data.summary.latestRunAt}`);
6366
+ console.log(`Latest run: ${data.summary.latestRunAt}`);
6358
6367
  }
6359
- console.log(`Keywords: ${totalKeywords}`);
6360
- console.log(` cited (any): ${keywordsCited}`);
6361
- console.log(` fully covered: ${keywordsFullyCovered}`);
6362
- console.log(` uncovered: ${keywordsUncovered}`);
6368
+ console.log(`Cited in sources: ${providersCiting}/${providersConfigured} engines`);
6369
+ console.log(`Mentioned in answers: ${providersMentioning}/${providersConfigured} engines`);
6370
+ console.log("");
6371
+ console.log(`Keywords (${totalKeywords} total):`);
6372
+ console.log(` cited + mentioned: ${keywordsCitedAndMentioned}`);
6373
+ console.log(` cited only: ${keywordsCitedOnly}`);
6374
+ console.log(` mentioned only: ${keywordsMentionedOnly}`);
6375
+ console.log(` invisible: ${keywordsInvisible}`);
6363
6376
  }
6364
6377
  function printCoverage(data) {
6365
6378
  if (data.byKeyword.length === 0) {
@@ -6378,19 +6391,23 @@ function printCoverage(data) {
6378
6391
  }
6379
6392
  return;
6380
6393
  }
6394
+ const cellWidth = Math.max(6, ...providerColumns.map((p) => p.length));
6381
6395
  const keywordWidth = Math.max(7, ...data.byKeyword.map((r) => r.keyword.length));
6382
- const header = ["Keyword".padEnd(keywordWidth), ...providerColumns.map((p) => p.padEnd(10)), "Coverage"].join(" ");
6383
- console.log("Per-keyword coverage:");
6396
+ const header = ["Keyword".padEnd(keywordWidth), ...providerColumns.map((p) => p.padEnd(cellWidth)), "Cite", "Ment"].join(" ");
6397
+ console.log("Per-keyword coverage: (cell = [citation][mention]; C=cited c=not, M=mentioned m=not, \u2013=no data)");
6384
6398
  console.log(header);
6385
6399
  console.log("\u2500".repeat(header.length));
6386
6400
  for (const row of data.byKeyword) {
6387
6401
  const cells = providerColumns.map((p) => {
6388
6402
  const provider = row.providers.find((x) => x.provider === p);
6389
- if (!provider) return "\u2013".padEnd(10);
6390
- return (provider.cited ? "\u2713" : "\u2717").padEnd(10);
6403
+ if (!provider) return "\u2013".padEnd(cellWidth);
6404
+ const citationGlyph = provider.cited ? "C" : "c";
6405
+ const mentionGlyph = provider.mentioned ? "M" : "m";
6406
+ return `${citationGlyph}${mentionGlyph}`.padEnd(cellWidth);
6391
6407
  });
6392
- const coverage = `${row.citedCount}/${row.totalProviders}`;
6393
- console.log([row.keyword.padEnd(keywordWidth), ...cells, coverage].join(" "));
6408
+ const citeCol = `${row.citedCount}/${row.totalProviders}`;
6409
+ const mentCol = `${row.mentionedCount}/${row.totalProviders}`;
6410
+ console.log([row.keyword.padEnd(keywordWidth), ...cells, citeCol, mentCol].join(" "));
6394
6411
  }
6395
6412
  }
6396
6413
  function printGaps2(data) {
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-75EV36V6.js";
3
+ } from "./chunk-VUP7AQTD.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-7DVIJC6L.js";
6
+ } from "./chunk-5X3TJ5BC.js";
7
7
  import "./chunk-NEDRCOOL.js";
8
8
  import "./chunk-MLKGABMK.js";
9
9
  export {
package/dist/mcp.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  CliError,
3
3
  canonryMcpTools,
4
4
  createApiClient
5
- } from "./chunk-7DVIJC6L.js";
5
+ } from "./chunk-5X3TJ5BC.js";
6
6
  import "./chunk-MLKGABMK.js";
7
7
 
8
8
  // src/mcp/cli.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "2.16.3",
3
+ "version": "3.0.1",
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",
@@ -59,19 +59,19 @@
59
59
  "@types/node-cron": "^3.0.11",
60
60
  "tsup": "^8.5.1",
61
61
  "tsx": "^4.19.0",
62
- "@ainyc/canonry-api-routes": "0.0.0",
63
- "@ainyc/canonry-db": "0.0.0",
64
- "@ainyc/canonry-contracts": "0.0.0",
65
62
  "@ainyc/canonry-config": "0.0.0",
66
- "@ainyc/canonry-integration-bing": "0.0.0",
67
- "@ainyc/canonry-integration-commoncrawl": "0.0.0",
68
63
  "@ainyc/canonry-intelligence": "0.0.0",
64
+ "@ainyc/canonry-integration-commoncrawl": "0.0.0",
65
+ "@ainyc/canonry-contracts": "0.0.0",
66
+ "@ainyc/canonry-db": "0.0.0",
67
+ "@ainyc/canonry-integration-bing": "0.0.0",
68
+ "@ainyc/canonry-integration-wordpress": "0.0.0",
69
+ "@ainyc/canonry-api-routes": "0.0.0",
69
70
  "@ainyc/canonry-integration-google": "0.0.0",
70
- "@ainyc/canonry-provider-cdp": "0.0.0",
71
71
  "@ainyc/canonry-provider-claude": "0.0.0",
72
- "@ainyc/canonry-provider-gemini": "0.0.0",
72
+ "@ainyc/canonry-provider-cdp": "0.0.0",
73
73
  "@ainyc/canonry-provider-local": "0.0.0",
74
- "@ainyc/canonry-integration-wordpress": "0.0.0",
74
+ "@ainyc/canonry-provider-gemini": "0.0.0",
75
75
  "@ainyc/canonry-provider-openai": "0.0.0",
76
76
  "@ainyc/canonry-provider-perplexity": "0.0.0"
77
77
  },