@canonry/canonry 5.34.0 → 6.0.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 (39) hide show
  1. package/assets/agent-workspace/skills/aero/references/portfolio-analysis.md +15 -3
  2. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +4 -8
  3. package/assets/assets/{AuditHistoryPanel-Ddg6JIi3.js → AuditHistoryPanel-Bz2NVB6-.js} +1 -1
  4. package/assets/assets/{BacklinksPage-DI9BmaYt.js → BacklinksPage-Dte5-usf.js} +1 -1
  5. package/assets/assets/{HistoryPage-CACIL005.js → HistoryPage-C1S4nw3I.js} +1 -1
  6. package/assets/assets/{MeasurementPropertyPage-D_i3t0DT.js → MeasurementPropertyPage-DWjsHZdt.js} +1 -1
  7. package/assets/assets/ProjectPage-CAJ9XvNt.js +8 -0
  8. package/assets/assets/RunsPage-D9wEJFfV.js +1 -0
  9. package/assets/assets/SettingsPage-CN3Gew7Y.js +1 -0
  10. package/assets/assets/SiteHealthSection-Bft5TfoU.js +4 -0
  11. package/assets/assets/TrafficPage-tkNvySQH.js +40 -0
  12. package/assets/assets/{TrafficSourceDetailPage-BPqNmuws.js → TrafficSourceDetailPage-RhRfPizo.js} +1 -1
  13. package/assets/assets/{extract-error-message-B7OeWj0x.js → extract-error-message-tfl_SU7q.js} +1 -1
  14. package/assets/assets/index-B11OXdEC.css +1 -0
  15. package/assets/assets/index-Cu_xgdb1.js +86 -0
  16. package/assets/assets/{react-sigma_core.esm.min-jiMNkILi.js → react-sigma_core.esm.min-BUu3mcQJ.js} +1 -1
  17. package/assets/assets/{v2-overview-adapter-Caj3bHQE.js → v2-overview-adapter-CzeGhaXn.js} +1 -1
  18. package/assets/index.html +2 -2
  19. package/dist/{chunk-VHPGBX4P.js → chunk-4NC5ZAC2.js} +3 -3
  20. package/dist/{chunk-NZCKJMLC.js → chunk-6M7HSNXJ.js} +2 -2
  21. package/dist/{chunk-AIAVZY6L.js → chunk-F4G7I7T7.js} +18 -132
  22. package/dist/{chunk-NOIK3FUO.js → chunk-HAALQW4R.js} +48 -15
  23. package/dist/{chunk-25H4K7KL.js → chunk-K4356AGP.js} +7 -7
  24. package/dist/{chunk-QTETJEHI.js → chunk-XGERCNUR.js} +6 -3
  25. package/dist/{chunk-G2K5KCUY.js → chunk-ZO62Z2WN.js} +27 -15
  26. package/dist/cli.js +13 -11
  27. package/dist/{demo-server-UI24OAHX.js → demo-server-54OUTZMY.js} +4 -4
  28. package/dist/index.js +6 -6
  29. package/dist/{intelligence-service-ZKFLGJ23.js → intelligence-service-MEIAMMLT.js} +2 -2
  30. package/dist/mcp.js +4 -4
  31. package/package.json +12 -12
  32. package/assets/assets/ProjectPage-d1djqfh3.js +0 -8
  33. package/assets/assets/RunRow-CjhvvA6g.js +0 -1
  34. package/assets/assets/RunsPage-DuBhw2uc.js +0 -1
  35. package/assets/assets/SettingsPage-MUpfB0HH.js +0 -1
  36. package/assets/assets/SiteHealthSection-6qdXlFcO.js +0 -4
  37. package/assets/assets/TrafficPage--nGSOgqn.js +0 -40
  38. package/assets/assets/index-C4lreGwR.js +0 -86
  39. package/assets/assets/index-DX5M4MHr.css +0 -1
@@ -128,7 +128,7 @@ import {
128
128
  trafficSeriesGranularitySchema,
129
129
  visibilityCompareSelectionSchema,
130
130
  visibilityReportRequestSchema
131
- } from "./chunk-NOIK3FUO.js";
131
+ } from "./chunk-HAALQW4R.js";
132
132
 
133
133
  // src/cli-error.ts
134
134
  function isMachineFormat(format) {
@@ -456,7 +456,7 @@ var MEASUREMENT_CHANGES_AGENT_ROWS = 20;
456
456
  var measurementChangesInputSchema = measurementChangesQuerySchema.safeExtend({
457
457
  project: projectNameSchema,
458
458
  queryClass: measurementQueryClassFilterSchema.default(AGENT_DEFAULT_QUERY_CLASS).describe("Defaults to non-brand. Call once per class; all pools both classes in rows and distribution."),
459
- limit: z3.number().int().positive().max(MEASUREMENT_CHANGES_AGENT_ROWS).optional().describe(`Changed rows, largest move first. Default 10, at most ${MEASUREMENT_CHANGES_AGENT_ROWS}; distribution counts every Property whatever the limit.`)
459
+ limit: z3.number().int().positive().max(MEASUREMENT_CHANGES_AGENT_ROWS).optional().describe(`Changed rows, largest move first, every move beyond noise before any within it. Default and maximum ${MEASUREMENT_CHANGES_AGENT_ROWS}; distribution counts every Property whatever the limit.`)
460
460
  }).strict();
461
461
  var measurementDataQualityInputSchema = measurementDataQualityQuerySchema.extend({
462
462
  project: projectNameSchema
@@ -948,7 +948,7 @@ var contentTargetsInputSchema = z3.object({
948
948
  var contentBriefInputSchema = z3.object({
949
949
  project: projectNameSchema,
950
950
  targetRef: z3.string().min(1).describe("Stable target ref from canonry_content_targets. The target must be ownable; ceded targets are rejected."),
951
- provider: z3.string().optional().describe("Optional provider override (claude|openai|gemini|zai|deepinfra)."),
951
+ provider: z3.string().optional().describe("Optional provider override (claude|openai|gemini|zai)."),
952
952
  model: z3.string().optional().describe("Optional model override within the chosen provider."),
953
953
  forceRefresh: z3.boolean().optional().describe("Force a fresh synthesis even if a cached brief exists.")
954
954
  });
@@ -1515,7 +1515,7 @@ var canonryMcpTools = [
1515
1515
  defineTool({
1516
1516
  name: "canonry_analytics_sources",
1517
1517
  title: "Get cited-source rankings",
1518
- description: "Where AI engines cite from for a project: cited domains ranked by how many answers cite them, each tagged with a category and a surface class (own / direct-competitor / ota-aggregator / editorial-media / other), plus a surface-class roll-up and a per-provider breakdown. Project-wide: without `queryClass` it pools branded and non-brand answers, where branded queries inflate your own domain. With `queryClass` set and neither `runId` nor `window`, this tool reads the latest sweep (`runId` latest); pass `window` to pool every sweep in it. `pooledAcrossRuns` true (`runCount` above 1) means the counts sum several runs and `runIds` is their scope; the legacy `runId` field is then only the newest run in the window. On a v2 measurement plan the class comes from the plan, the same answers as canonry_competitor_landscape with scope=all-markets. `countUnits` says what each count counts: `domainTotal` is distinct domains, never answers or citations. Compare engines by answer count or `answerShare`, never by rank position. Counts come from each answer's stored source list, so every engine is included, Gemini too. A domain counts at most once per answer; `answerShare` is the share of all answers in scope that cite it. `providersWithoutSources` names engines that answered but cited nothing. `limit` caps each ranked list (default 10 for this tool, which keeps each engine list whole); a long-tail rollup keeps the totals. The per-query breakdown is omitted unless `includeByQuery` is true. No LLM calls; probe runs excluded.",
1518
+ description: "Where AI engines cite from for a project: cited domains ranked by how many answers cite them, each tagged with a category and a surface class (own / direct-competitor / ota-aggregator / editorial-media / other), plus a surface-class roll-up and a per-provider breakdown. Every entry carries rank, its position by count in that list over the whole scope (equal counts share a rank); state where a domain places only from its rank, never by counting rows. Project-wide: without `queryClass` it pools branded and non-brand answers, where branded queries inflate your own domain. With `queryClass` set and neither `runId` nor `window`, this tool reads the latest sweep (`runId` latest); pass `window` to pool every sweep in it. `pooledAcrossRuns` true (`runCount` above 1) means the counts sum several runs and `runIds` is their scope; the legacy `runId` field is then only the newest run in the window. On a v2 measurement plan the class comes from the plan, the same answers as canonry_competitor_landscape with scope=all-markets. `countUnits` says what each count counts: `domainTotal` is distinct domains, never answers or citations. Compare engines by answer count or `answerShare`, never by rank position. Counts come from each answer's stored source list, so every engine is included, Gemini too. A domain counts at most once per answer; `answerShare` is the share of all answers in scope that cite it. `providersWithoutSources` names engines that answered but cited nothing. `limit` caps each ranked list (default 10 for this tool, which keeps each engine list whole); a long-tail rollup keeps the totals. The per-query breakdown is omitted unless `includeByQuery` is true. No LLM calls; probe runs excluded.",
1519
1519
  access: "read",
1520
1520
  tier: "monitoring",
1521
1521
  inputSchema: z3.object({
@@ -2842,7 +2842,7 @@ var canonryMcpTools = [
2842
2842
  defineTool({
2843
2843
  name: "canonry_measurement_portfolio_summary",
2844
2844
  title: "Summarize measured Properties",
2845
- description: "Start here for best and worst Properties. One call returns the weakest Properties, the names answers wrote instead of them, and the domains engines cited for them. Defaults to non-brand queries; state the returned queryClass and never pool branded with non-brand. Returns at most 4 Property rows per list, the most that fit one result; a larger limit is lowered and limitNote says so. For other rows pass groupKey (one metro's weakest), or page canonry_measurement_overview. Mention (answer text names the Property) and citation (a source URL on its page) are separate: report each as numerator/denominator, with any unattributed count beside mention. Denominators count answers, one per query per engine (queries x engines = answers). Group Properties only by each row's metro and submarkets, never by label. namedInsteadInAnswerText lists names written in the answer text of answers that neither named nor cited the Property, counted by answer: they are not citations, and namedInsteadInAnswerTextTotal counts distinct names, not answers. citedDomains and weakestAnswerSources count answers citing each domain, every engine included; weakestAnswerSources pools the weakest rows plus every tied Property, each answer once, and its domainTotal counts distinct domains, not citations. When tiedAtWeakest is set, tiedAtWeakest.count Properties share the weakest rates and the rows are the first of them by name, not a rank: report the count, tiedAtWeakest.byMetro (tied Properties per metro over the whole tie; a Property in two metros counts in both) and tiedAtWeakest.namedInstead (names written instead across the whole tie, counted by distinct answer; namedInsteadTotal is distinct names), and call the rows examples. mentionRanking.strongest/.weakest rank every Property with an available mention rate; .excluded lists the rest with reasons. markets lists every market at one level, worst-first (every metro, or a groupKey's submarkets), whatever the limit. Markets may share Properties and never sum to portfolio totals. Reads stored data only; never starts provider work.",
2845
+ description: "Start here for best and worst Properties. One call returns the weakest Properties, the names answers wrote instead of them, and the domains engines cited for them. Defaults to non-brand queries; state the returned queryClass and never pool branded with non-brand. Returns at most 4 Property rows per list, the most that fit one result; a larger limit is lowered and limitNote says so. For other rows pass groupKey (one metro's weakest), or page canonry_measurement_overview. Mention (answer text names the Property) and citation (a source URL on its page) are separate: report each as numerator/denominator, with any unattributed count beside mention. Denominators count answers, one per query per engine (queries x engines = answers). Group Properties only by each row's metro and submarkets, never by label. namedInsteadInAnswerText lists names written in the answer text of answers that neither named nor cited the Property, counted by answer: they are not citations, and namedInsteadInAnswerTextTotal counts distinct names, not answers. citedDomains and weakestAnswerSources count answers citing each domain, every engine included; weakestAnswerSources pools the weakest rows plus every tied Property, each answer once, and its domainTotal counts distinct domains, not citations. weakestAnswerSources.ownDomainAnswers counts those answers that cite any of your own domains. A Property's citationCoverage counts only answers citing that Property's own page, so 0% citation does not mean your domain went uncited; use ownDomainAnswers for that. When tiedAtWeakest is set, tiedAtWeakest.count Properties share the weakest rates and the rows are the first of them by name, not a rank: report the count, tiedAtWeakest.byMetro (tied Properties per metro over the whole tie; a Property in two metros counts in both) and tiedAtWeakest.namedInstead (names written instead across the whole tie, counted by distinct answer; namedInsteadTotal is distinct names), and call the rows examples. mentionRanking.strongest/.weakest rank every Property with an available mention rate; .excluded lists the rest with reasons. markets lists every market at one level, worst-first (every metro, or a groupKey's submarkets), whatever the limit. Markets may share Properties and never sum to portfolio totals. Reads stored data only; never starts provider work.",
2846
2846
  access: "read",
2847
2847
  tier: "monitoring",
2848
2848
  inputSchema: measurementPortfolioSummaryInputSchema,
@@ -2902,7 +2902,7 @@ var canonryMcpTools = [
2902
2902
  defineTool({
2903
2903
  name: "canonry_measurement_changes",
2904
2904
  title: "Compare stored measurement changes",
2905
- description: "What changed since the previous sweep: compares the displayed run with the previous run of the same plan revision and execution identity; an incompatible pair returns unavailable with a reason, never a trend. Defaults to non-brand queries: call once per class (queryClass branded for the branded move) and never report one class as the whole. queryClass all pools both classes in rows and distribution and adds metricsByClass, the headline metrics per class; report those per class, never the pooled metrics alone. changedProperties come largest move first (answers that named the Property, then answers that cited it; sort label for alphabetical), default 10 and at most 20 rows; totalProperties above the rows returned means rows you did not see. distribution counts every Property in scope: improved, declined, mixed, withinNoise, unchanged, notComparable. mentionAnswersDelta and citationAnswersDelta are signed answer counts; denominatorChanged true means a rate was taken over a different number of answers, so those counts are not like for like and the coverage rates give the direction. A move is the rate change times the larger answer count (the count change when the denominator held). withinNoise true means every move was 2 answers or fewer: within noise, never a gain, loss, trend or regression. Reads stored data only; it never starts provider work.",
2905
+ description: "What changed since the previous sweep: compares the displayed run with the previous run of the same plan revision and execution identity; an incompatible pair returns unavailable with a reason, never a trend. Defaults to non-brand queries: call once per class (queryClass branded for the branded move) and never report one class as the whole. queryClass all pools both classes in rows and distribution and adds metricsByClass, the headline metrics per class; report those per class, never the pooled metrics alone. changedProperties come largest move first (answers that named the Property, then answers that cited it; sort label for alphabetical), every move beyond noise before any within it, 20 rows by default and at most; totalProperties above the rows returned means rows you did not see. distribution counts every Property in scope: improved, declined, mixed, withinNoise, unchanged, notComparable. mentionAnswersDelta and citationAnswersDelta are signed answer counts; denominatorChanged true means a rate was taken over a different number of answers, so those counts are not like for like and the coverage rates give the direction. A move is the rate change times the larger answer count (the count change when the denominator held). withinNoise true means every move was 2 answers or fewer: within noise, never a gain, loss, trend or regression. mentionWithinNoise and citationWithinNoise apply the same rule to each signal alone: a Property beyond noise can still have one signal within noise, and that signal is not a gain or loss. Report every headline metric, branded included, as previous numerator/denominator to current numerator/denominator. Reads stored data only; it never starts provider work.",
2906
2906
  access: "read",
2907
2907
  tier: "monitoring",
2908
2908
  inputSchema: measurementChangesInputSchema,
@@ -2910,7 +2910,7 @@ var canonryMcpTools = [
2910
2910
  openApiOperations: ["GET /api/v1/projects/{name}/measurement-changes"],
2911
2911
  handler: (client, input) => {
2912
2912
  const { project, ...query } = input;
2913
- return client.getMeasurementChanges(project, { ...query, queryClass: agentQueryClass(query.queryClass) });
2913
+ return client.getMeasurementChanges(project, { ...query, limit: query.limit ?? MEASUREMENT_CHANGES_AGENT_ROWS, queryClass: agentQueryClass(query.queryClass) });
2914
2914
  }
2915
2915
  }),
2916
2916
  defineTool({
@@ -5,7 +5,7 @@ import {
5
5
  PACKAGE_VERSION,
6
6
  canonryMcpTools,
7
7
  isMachineFormat
8
- } from "./chunk-25H4K7KL.js";
8
+ } from "./chunk-K4356AGP.js";
9
9
  import {
10
10
  AGENT_PROVIDER_IDS,
11
11
  CANONRY_NPM_PACKAGE_URL,
@@ -28,6 +28,7 @@ import {
28
28
  formatCanonryPluginVersionMismatch,
29
29
  isInstallMethod,
30
30
  isReadOnlyKey,
31
+ isRetiredAgentProviderId,
31
32
  isSensitiveDiagnosticQueryKey,
32
33
  isStrictSemver,
33
34
  normalizeAgentSlug,
@@ -49,7 +50,7 @@ import {
49
50
  skillsClientSchema,
50
51
  upgradeCaveatFor,
51
52
  upgradeCommandFor
52
- } from "./chunk-NOIK3FUO.js";
53
+ } from "./chunk-HAALQW4R.js";
53
54
 
54
55
  // src/config.ts
55
56
  import fs from "fs";
@@ -429,9 +430,11 @@ Do not use "canonry init --force" for recovery. It replaces credentials without
429
430
  });
430
431
  }
431
432
  if (!agentProviderSchema.safeParse(parsed.agent?.provider).success) {
433
+ const pinned = parsed.agent?.provider;
434
+ const retired = typeof pinned === "string" && isRetiredAgentProviderId(pinned);
432
435
  throw new CliError({
433
436
  code: "CONFIG_INVALID",
434
- message: `Invalid config at ${configPath}: agent.provider must be one of ${AGENT_PROVIDER_IDS.join(", ")}, or left blank.`
437
+ message: retired ? `Invalid config at ${configPath}: agent.provider ${pinned} was removed. Pin one of ${AGENT_PROVIDER_IDS.join(", ")} (with a key under providers.<name>.apiKey), or leave it blank.` : `Invalid config at ${configPath}: agent.provider must be one of ${AGENT_PROVIDER_IDS.join(", ")}, or left blank.`
435
438
  });
436
439
  }
437
440
  if (!agentModelSchema.safeParse(parsed.agent?.model).success) {
@@ -869,7 +869,7 @@ import {
869
869
  wordpressSchemaDeployResultDtoSchema,
870
870
  wordpressSchemaStatusResultDtoSchema,
871
871
  wordpressStatusDtoSchema
872
- } from "./chunk-NOIK3FUO.js";
872
+ } from "./chunk-HAALQW4R.js";
873
873
 
874
874
  // src/intelligence-service.ts
875
875
  import { eq as eq82, desc as desc36, asc as asc15, and as and69, ne as ne14, or as or20, inArray as inArray32, gte as gte22, lte as lte20, isNull as isNull16, sql as sql31, exists } from "drizzle-orm";
@@ -11294,7 +11294,7 @@ function gapTone(gapCount, totalCount) {
11294
11294
 
11295
11295
  // ../intelligence/src/visibility-score.ts
11296
11296
  function buildVisibilityScore(snapshots, options) {
11297
- const tooltip = "An LLM used a page on your domain as a source for its answer.";
11297
+ const tooltip = "An AI engine used a page on your domain as a source for its answer.";
11298
11298
  if (snapshots.length === 0) {
11299
11299
  return {
11300
11300
  label: "Citation Coverage",
@@ -11326,13 +11326,13 @@ function buildVisibilityScore(snapshots, options) {
11326
11326
  tooltip,
11327
11327
  trend: [],
11328
11328
  progress: score,
11329
- providerCoverage: isPartialProviderRun ? `${runApiProviderCount} of ${options.configuredApiProviders.length} providers` : void 0
11329
+ providerCoverage: isPartialProviderRun ? `${runApiProviderCount} of ${options.configuredApiProviders.length} engines` : void 0
11330
11330
  };
11331
11331
  }
11332
11332
 
11333
11333
  // ../intelligence/src/mention-coverage.ts
11334
11334
  function buildMentionCoverage(snapshots, options) {
11335
- const tooltip = "Your domain or company name was in the answer returned by the LLM.";
11335
+ const tooltip = "Your domain or company name was in the answer returned by the AI engine.";
11336
11336
  if (snapshots.length === 0) {
11337
11337
  return {
11338
11338
  label: "Mention Coverage",
@@ -11364,7 +11364,7 @@ function buildMentionCoverage(snapshots, options) {
11364
11364
  tooltip,
11365
11365
  trend: [],
11366
11366
  progress: score,
11367
- providerCoverage: isPartialProviderRun ? `${runApiProviderCount} of ${options.configuredApiProviders.length} providers` : void 0
11367
+ providerCoverage: isPartialProviderRun ? `${runApiProviderCount} of ${options.configuredApiProviders.length} engines` : void 0
11368
11368
  };
11369
11369
  }
11370
11370
 
@@ -11398,7 +11398,7 @@ function buildGapQueryScore(snapshots) {
11398
11398
  return {
11399
11399
  label: "Citation Gaps",
11400
11400
  value: `${gapCount}`,
11401
- delta: `${gapCount} of ${totalCount} queries at risk`,
11401
+ delta: `${gapCount} of ${totalCount} ${totalCount === 1 ? "query" : "queries"}`,
11402
11402
  tone: gapTone(gapCount, totalCount),
11403
11403
  description: gapCount > 0 ? `${gapCount} tracked ${gapQueryLabel} currently cite competitors without citing your domain.` : "No competitive citation gaps detected in the latest visibility run.",
11404
11404
  tooltip,
@@ -11435,7 +11435,7 @@ function buildMentionGapScore(snapshots) {
11435
11435
  return {
11436
11436
  label: "Mention Gaps",
11437
11437
  value: `${gapCount}`,
11438
- delta: `${gapCount} of ${totalCount} queries at risk`,
11438
+ delta: `${gapCount} of ${totalCount} ${totalCount === 1 ? "query" : "queries"}`,
11439
11439
  tone: gapTone(gapCount, totalCount),
11440
11440
  description: gapCount > 0 ? `${gapCount} tracked ${gapQueryLabel} mention competitors but never your brand.` : "No competitive mention gaps detected in the latest visibility run.",
11441
11441
  tooltip,
@@ -25735,7 +25735,7 @@ function mentionRanking(rows, limit) {
25735
25735
  truncated: eligible.length > limit
25736
25736
  };
25737
25737
  }
25738
- function portfolioResponse(db, active, plan, query) {
25738
+ function portfolioResponse(db, active, plan, query, ownDomains) {
25739
25739
  const group = query.groupKey === void 0 ? void 0 : requireGroup2(plan, query.groupKey);
25740
25740
  const run = selectMeasurementQuestionRun(db, active.version.projectId, active, query.runId);
25741
25741
  const requestedTargetKeys = group?.targetKeys ?? plan.targets.map((target) => target.stableKey);
@@ -25831,7 +25831,7 @@ function portfolioResponse(db, active, plan, query) {
25831
25831
  },
25832
25832
  weakestProperties: rows,
25833
25833
  tiedAtWeakest: tie.summary,
25834
- weakestAnswerSources: weakestAnswerSources([...displayed, ...tie.rows]),
25834
+ weakestAnswerSources: weakestAnswerSources([...displayed, ...tie.rows], ownDomains),
25835
25835
  mentionRanking: mentionRanking(ranked, limit),
25836
25836
  ...marketRollup(plan, { run, evaluator }, group, includeNestedMarkets),
25837
25837
  totalProperties: ranked.length,
@@ -25892,7 +25892,7 @@ function tieNamedInstead(rows) {
25892
25892
  const counted = [...grouped.values()].map((row) => ({ name: row.name, answers: row.answers.size })).sort((left, right) => right.answers - left.answers || compareText13(left.name, right.name));
25893
25893
  return { rows: counted.slice(0, MEASUREMENT_PORTFOLIO_TIE_NAMED_INSTEAD_LIMIT), total: counted.length };
25894
25894
  }
25895
- function weakestAnswerSources(rows) {
25895
+ function weakestAnswerSources(rows, ownDomains) {
25896
25896
  const properties = /* @__PURE__ */ new Set();
25897
25897
  const answers = /* @__PURE__ */ new Map();
25898
25898
  for (const row of rows) {
@@ -25900,7 +25900,11 @@ function weakestAnswerSources(rows) {
25900
25900
  for (const answer of row.population.sourceAnswers) answers.set(answer.snapshot.id, answer);
25901
25901
  }
25902
25902
  const domains = domainRows([...answers.values()], MEASUREMENT_PORTFOLIO_ANSWER_SOURCES_LIMIT);
25903
- return { properties: properties.size, answers: answers.size, domains: domains.rows, domainTotal: domains.total };
25903
+ let ownDomainAnswers = 0;
25904
+ for (const answer of answers.values()) {
25905
+ if ([...answerDomains(answer)].some((host) => hostMatchesAnyDomain(host, ownDomains))) ownDomainAnswers++;
25906
+ }
25907
+ return { properties: properties.size, answers: answers.size, domains: domains.rows, domainTotal: domains.total, ownDomainAnswers };
25904
25908
  }
25905
25909
  function propertyCompetitorsResponse(db, active, plan, query) {
25906
25910
  const target = requireTarget3(plan, query.targetKey);
@@ -26112,11 +26116,14 @@ function classifyPropertyMove(mention, citation) {
26112
26116
  const down = directions.some((move) => move < 0);
26113
26117
  bucket = up && down ? "mixed" : up ? "improved" : "declined";
26114
26118
  }
26119
+ const signalWithinNoise = (move) => move === null ? null : Math.abs(move) <= MEASUREMENT_CHANGES_NOISE_ANSWERS;
26115
26120
  return {
26116
26121
  mentionAnswersDelta: answersDelta(mention),
26117
26122
  citationAnswersDelta: answersDelta(citation),
26118
26123
  denominatorChanged: baseChanged(mention) || baseChanged(citation),
26119
26124
  withinNoise: changed && withinNoise,
26125
+ mentionWithinNoise: signalWithinNoise(mentionMove),
26126
+ citationWithinNoise: signalWithinNoise(citationMove),
26120
26127
  bucket,
26121
26128
  mentionSize: mentionMove === null ? -1 : Math.abs(mentionMove),
26122
26129
  citationSize: citationMove === null ? -1 : Math.abs(citationMove)
@@ -26205,7 +26212,9 @@ function changesResponse(db, active, plan, query) {
26205
26212
  mentionAnswersDelta: move.mentionAnswersDelta,
26206
26213
  citationAnswersDelta: move.citationAnswersDelta,
26207
26214
  denominatorChanged: move.denominatorChanged,
26208
- withinNoise: move.withinNoise
26215
+ withinNoise: move.withinNoise,
26216
+ mentionWithinNoise: move.mentionWithinNoise,
26217
+ citationWithinNoise: move.citationWithinNoise
26209
26218
  },
26210
26219
  move
26211
26220
  }];
@@ -26386,7 +26395,7 @@ async function measurementPortfolioReadRoutes(app) {
26386
26395
  const raw = nested === "true" || nested === "false" ? { ...request.query, includeNestedMarkets: nested === "true" } : request.query;
26387
26396
  const query = parseLimitQuery(raw, measurementPortfolioSummaryQuerySchema, "Invalid measurement portfolio summary query");
26388
26397
  const { active, plan } = activeV2Plan2(app.db, project.id, "Portfolio summary");
26389
- return portfolioResponse(app.db, active, plan, query);
26398
+ return portfolioResponse(app.db, active, plan, query, effectiveDomains(project));
26390
26399
  }
26391
26400
  );
26392
26401
  app.get(
@@ -31366,8 +31375,11 @@ function buildRankedList(domains, limit, answerTotal, answersWithSources) {
31366
31375
  const domainTotal = all.length;
31367
31376
  all.sort((a, b) => b.count - a.count || a.domain.localeCompare(b.domain));
31368
31377
  const shownEntries = limit != null && limit < all.length ? all.slice(0, limit) : all;
31369
- const entries = shownEntries.map((d) => ({
31378
+ let rank = 0;
31379
+ const ranks = all.map((d, i) => rank = i > 0 && d.count === all[i - 1].count ? rank : i + 1);
31380
+ const entries = shownEntries.map((d, i) => ({
31370
31381
  domain: d.domain,
31382
+ rank: ranks[i],
31371
31383
  count: d.count,
31372
31384
  percentage: totalCitedSlots > 0 ? roundRatio(d.count / totalCitedSlots, RatioUnits.fraction) : 0,
31373
31385
  answerShare: answerTotal > 0 ? roundRatio(d.count / answerTotal, RatioUnits.fraction) : 0,
@@ -86104,7 +86116,7 @@ var agentProvidersConfiguredCheck = {
86104
86116
  status: CheckStatuses.warn,
86105
86117
  code: "agent-providers.none-configured",
86106
86118
  summary: "No agent LLM provider has credentials configured \u2014 the built-in Aero agent cannot run.",
86107
- remediation: "Add a key for one of the agent providers (claude, openai, gemini, zai, deepinfra) under `providers.<name>.apiKey` in ~/.canonry/config.yaml, or export its env var (e.g. ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPINFRA_TOKEN).",
86119
+ remediation: "Add a key for one of the agent providers (claude, openai, gemini, zai) under `providers.<name>.apiKey` in ~/.canonry/config.yaml, or export its env var (e.g. ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY).",
86108
86120
  details
86109
86121
  };
86110
86122
  }
package/dist/cli.js CHANGED
@@ -35,11 +35,11 @@ import {
35
35
  trackCliCommandFinished,
36
36
  trackEvent,
37
37
  waitForServerRuntimeStartup
38
- } from "./chunk-AIAVZY6L.js";
38
+ } from "./chunk-F4G7I7T7.js";
39
39
  import {
40
40
  autoSyncSkills,
41
41
  formatAutoSyncNotice
42
- } from "./chunk-VHPGBX4P.js";
42
+ } from "./chunk-4NC5ZAC2.js";
43
43
  import {
44
44
  configExists,
45
45
  createApiClient,
@@ -56,13 +56,13 @@ import {
56
56
  saveConfig,
57
57
  saveConfigPatch,
58
58
  shouldPrintServeSkillsNudge
59
- } from "./chunk-QTETJEHI.js";
59
+ } from "./chunk-XGERCNUR.js";
60
60
  import {
61
61
  DEFAULT_DEMO_TRUSTED_PROXIES,
62
62
  trustedProxiesProblem,
63
63
  trustedProxyProblem
64
64
  } from "./chunk-RB2F4D3Z.js";
65
- import "./chunk-NZCKJMLC.js";
65
+ import "./chunk-6M7HSNXJ.js";
66
66
  import {
67
67
  CliError,
68
68
  EXIT_SYSTEM_ERROR,
@@ -74,7 +74,7 @@ import {
74
74
  runMeasurementDraftAction,
75
75
  systemError,
76
76
  usageError
77
- } from "./chunk-25H4K7KL.js";
77
+ } from "./chunk-K4356AGP.js";
78
78
  import {
79
79
  CLOUDFLARE_WORKER_BINDINGS,
80
80
  CLOUDFLARE_WORKER_GENERATED_MARKER,
@@ -89,7 +89,7 @@ import {
89
89
  queries,
90
90
  renderReportHtml,
91
91
  runs
92
- } from "./chunk-G2K5KCUY.js";
92
+ } from "./chunk-ZO62Z2WN.js";
93
93
  import {
94
94
  AdsDeliverySnapshotStatuses,
95
95
  AdsHistoricalCampaignRollupStatuses,
@@ -151,6 +151,7 @@ import {
151
151
  formatSignedPointDelta,
152
152
  gbpNegativeReviewMaxStarsSchema,
153
153
  googleAdsMetricsWindowSchema,
154
+ isRetiredAgentProviderId,
154
155
  isVisibilityCompareClassMetric,
155
156
  logQuerySchema,
156
157
  measurementChangesQuerySchema,
@@ -195,7 +196,7 @@ import {
195
196
  visibilityCompareSelectionSchema,
196
197
  visibilityReportRequestSchema,
197
198
  winnabilityClassSchema
198
- } from "./chunk-NOIK3FUO.js";
199
+ } from "./chunk-HAALQW4R.js";
199
200
 
200
201
  // src/cli.ts
201
202
  import { pathToFileURL } from "url";
@@ -721,7 +722,7 @@ function parseDemoTrustedProxies(values) {
721
722
  async function demoCommand(options) {
722
723
  const { host, port } = parseDemoListenOptions(options);
723
724
  const trustProxy = parseDemoTrustedProxies(options.trustProxy);
724
- const { createDemoServer } = await import("./demo-server-UI24OAHX.js");
725
+ const { createDemoServer } = await import("./demo-server-54OUTZMY.js");
725
726
  const app = await createDemoServer({ trustProxy });
726
727
  let stopping = false;
727
728
  const stop = () => {
@@ -11825,7 +11826,7 @@ Citation Rate Trends (${data.window})`);
11825
11826
  for (const [provider, served] of servedEntries) {
11826
11827
  const rawIds = served.latestServedModelIds.length > 0 ? served.latestServedModelIds.join(", ") : formatModelEvidence(served.latestObservation.state);
11827
11828
  const substituted = mismatch[provider];
11828
- const note = substituted ? ` \u2014 not the ${formatModelEvidence(substituted.configured)} you selected` : "";
11829
+ const note = substituted ? ` (not the ${formatModelEvidence(substituted.configured)} you selected)` : "";
11829
11830
  console.log(` ${provider}: ${rawIds} at ${served.latestObservation.observedAt}${note}`);
11830
11831
  for (const event of served.events) {
11831
11832
  const dating = event.fromPreWindowAnchor ? " (on or before)" : "";
@@ -19874,9 +19875,10 @@ Usage: ${usage}`, {
19874
19875
  }
19875
19876
  const providerInput = getString(input.values, "provider");
19876
19877
  if (providerInput && !coerceAgentProvider(providerInput)) {
19877
- throw usageError(`Error: --provider must be one of: ${listAgentProviders().join(", ")}
19878
+ const message = isRetiredAgentProviderId(providerInput) ? `--provider ${providerInput} was removed; use one of: ${listAgentProviders().join(", ")}` : `--provider must be one of: ${listAgentProviders().join(", ")}`;
19879
+ throw usageError(`Error: ${message}
19878
19880
  Usage: ${usage}`, {
19879
- message: `--provider must be one of: ${listAgentProviders().join(", ")}`,
19881
+ message,
19880
19882
  details: {
19881
19883
  command: "agent.ask",
19882
19884
  usage,
@@ -5,11 +5,11 @@ import {
5
5
  computeFactorAverages,
6
6
  layoutSiteCrawlGraphInput,
7
7
  persistSiteCrawlGraphLayout
8
- } from "./chunk-NZCKJMLC.js";
8
+ } from "./chunk-6M7HSNXJ.js";
9
9
  import {
10
10
  PACKAGE_VERSION,
11
11
  canonryMcpTools
12
- } from "./chunk-25H4K7KL.js";
12
+ } from "./chunk-K4356AGP.js";
13
13
  import {
14
14
  HISTORY_WINDOW_RUNS,
15
15
  IntelligenceService,
@@ -85,7 +85,7 @@ import {
85
85
  siteCrawlPages,
86
86
  siteCrawlSnapshots,
87
87
  trafficSources
88
- } from "./chunk-G2K5KCUY.js";
88
+ } from "./chunk-ZO62Z2WN.js";
89
89
  import {
90
90
  RunKinds,
91
91
  SiteCrawlFetchStates,
@@ -112,7 +112,7 @@ import {
112
112
  roundPreservingTotal,
113
113
  siteAuditPageFactorSchema,
114
114
  wilsonInterval
115
- } from "./chunk-NOIK3FUO.js";
115
+ } from "./chunk-HAALQW4R.js";
116
116
 
117
117
  // src/demo-server.ts
118
118
  import { dirname, join as join2 } from "path";
package/dist/index.js CHANGED
@@ -3,14 +3,14 @@ import {
3
3
  createGoogleMarketingCredentialStore,
4
4
  createGoogleMarketingRuntime,
5
5
  createServer
6
- } from "./chunk-AIAVZY6L.js";
6
+ } from "./chunk-F4G7I7T7.js";
7
7
  import {
8
8
  loadConfig
9
- } from "./chunk-QTETJEHI.js";
10
- import "./chunk-NZCKJMLC.js";
11
- import "./chunk-25H4K7KL.js";
12
- import "./chunk-G2K5KCUY.js";
13
- import "./chunk-NOIK3FUO.js";
9
+ } from "./chunk-XGERCNUR.js";
10
+ import "./chunk-6M7HSNXJ.js";
11
+ import "./chunk-K4356AGP.js";
12
+ import "./chunk-ZO62Z2WN.js";
13
+ import "./chunk-HAALQW4R.js";
14
14
  export {
15
15
  GoogleMarketingRuntimeError,
16
16
  createGoogleMarketingCredentialStore,
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  HISTORY_WINDOW_RUNS,
3
3
  IntelligenceService
4
- } from "./chunk-G2K5KCUY.js";
5
- import "./chunk-NOIK3FUO.js";
4
+ } from "./chunk-ZO62Z2WN.js";
5
+ import "./chunk-HAALQW4R.js";
6
6
  export {
7
7
  HISTORY_WINDOW_RUNS,
8
8
  IntelligenceService
package/dist/mcp.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  autoSyncSkills
3
- } from "./chunk-VHPGBX4P.js";
3
+ } from "./chunk-4NC5ZAC2.js";
4
4
  import {
5
5
  createApiClient,
6
6
  createCanonryMcpServer
7
- } from "./chunk-QTETJEHI.js";
8
- import "./chunk-25H4K7KL.js";
7
+ } from "./chunk-XGERCNUR.js";
8
+ import "./chunk-K4356AGP.js";
9
9
  import {
10
10
  isReadOnlyKey,
11
11
  updateCheckEnvOptOut
12
- } from "./chunk-NOIK3FUO.js";
12
+ } from "./chunk-HAALQW4R.js";
13
13
 
14
14
  // src/mcp/cli.ts
15
15
  import { randomUUID } from "crypto";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonry/canonry",
3
- "version": "5.34.0",
3
+ "version": "6.0.0",
4
4
  "type": "module",
5
5
  "description": "Self-hosted AI visibility (AEO) platform: track how ChatGPT, Claude, Gemini, and Perplexity cite your domain, join it with Search Console, GA4, server-side traffic, and paid media, and fix what you find through agent tools (CLI, REST, MCP). Local SQLite.",
6
6
  "keywords": [
@@ -86,33 +86,33 @@
86
86
  "tsup": "^8.5.1",
87
87
  "tsx": "^4.19.0",
88
88
  "@ainyc/canonry-web": "0.0.0",
89
- "@ainyc/canonry-api-client": "0.0.0",
90
89
  "@ainyc/canonry-config": "0.0.0",
91
- "@ainyc/canonry-db": "0.0.0",
90
+ "@ainyc/canonry-api-client": "0.0.0",
92
91
  "@ainyc/canonry-contracts": "0.0.0",
93
92
  "@ainyc/canonry-api-routes": "0.0.0",
94
- "@ainyc/canonry-integration-bing": "0.0.0",
93
+ "@ainyc/canonry-db": "0.0.0",
95
94
  "@ainyc/canonry-integration-cloud-run": "0.0.0",
96
- "@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
97
95
  "@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
98
96
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
99
97
  "@ainyc/canonry-integration-google": "0.0.0",
100
98
  "@ainyc/canonry-integration-google-ads": "0.0.0",
101
- "@ainyc/canonry-integration-google-places": "0.0.0",
99
+ "@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
100
+ "@ainyc/canonry-integration-bing": "0.0.0",
102
101
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
102
+ "@ainyc/canonry-integration-google-places": "0.0.0",
103
103
  "@ainyc/canonry-integration-google-tag-manager": "0.0.0",
104
+ "@ainyc/canonry-intelligence": "0.0.0",
104
105
  "@ainyc/canonry-integration-openai-ads": "0.0.0",
105
- "@ainyc/canonry-integration-traffic": "0.0.0",
106
106
  "@ainyc/canonry-integration-wordpress": "0.0.0",
107
- "@ainyc/canonry-intelligence": "0.0.0",
108
- "@ainyc/canonry-provider-claude": "0.0.0",
107
+ "@ainyc/canonry-integration-traffic": "0.0.0",
109
108
  "@ainyc/canonry-provider-cdp": "0.0.0",
109
+ "@ainyc/canonry-provider-claude": "0.0.0",
110
110
  "@ainyc/canonry-provider-gemini": "0.0.0",
111
- "@ainyc/canonry-provider-local": "0.0.0",
112
111
  "@ainyc/canonry-provider-muse": "0.0.0",
113
- "@ainyc/canonry-provider-perplexity": "0.0.0",
112
+ "@ainyc/canonry-provider-local": "0.0.0",
113
+ "@ainyc/canonry-provider-openai": "0.0.0",
114
114
  "@ainyc/canonry-integration-typesafe": "0.0.0",
115
- "@ainyc/canonry-provider-openai": "0.0.0"
115
+ "@ainyc/canonry-provider-perplexity": "0.0.0"
116
116
  },
117
117
  "scripts": {
118
118
  "build": "pnpm run build:cli && pnpm run build:web",