@canonry/canonry 5.20.0 → 5.20.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/README.md CHANGED
@@ -166,14 +166,6 @@ For a configured portfolio destination, select one `--market <key>` or `--proper
166
166
 
167
167
  <a id="compare-competitors-and-measure-complex-portfolios"></a>
168
168
 
169
- ### Portfolios and competitors
170
-
171
- Use [versioned measurement plans](docs/mcp.md#tool-surface) for portfolios of locations, products, or site sections.
172
-
173
- - **Properties:** Assign queries, engines, models, and locations to each property. Keep branded and non-brand coverage separate.
174
- - **Market groups:** Organize overlapping sets of properties, each with its own competitors.
175
- - **Comparisons:** Compare properties, groups, engines, and locations. Open the exact answers and cited URLs behind each result.
176
-
177
169
  ### Search, traffic, and site health
178
170
 
179
171
  Investigate visibility changes with evidence from search and local performance, crawler visits, AI page fetches, referrals, and conversions.
@@ -183,6 +175,14 @@ Site audits show technical findings alongside that evidence.
183
175
 
184
176
  *Map crawlable pages and the internal links that connect them.*
185
177
 
178
+ ### Portfolios and competitors
179
+
180
+ Use [versioned measurement plans](docs/mcp.md#tool-surface) for portfolios of locations, products, or site sections.
181
+
182
+ - **Properties:** Assign queries, engines, models, and locations to each property. Keep branded and non-brand coverage separate.
183
+ - **Market groups:** Organize overlapping sets of properties, each with its own competitors.
184
+ - **Comparisons:** Compare properties, groups, engines, and locations. Open the exact answers and cited URLs behind each result.
185
+
186
186
  ## Act on the evidence
187
187
 
188
188
  Your agent coordinates the work through Canonry and its own tools.
@@ -136,7 +136,7 @@ Keep it tight. The operator wakes to a short, decision-ready summary, not a full
136
136
  - **Gemini not configured** → orchestrator throws early; `runs.status='failed'` with `Gemini provider is not configured.` Surface as "configure Gemini before running discovery" — link to `cnry init` or `~/.canonry/config.yaml`.
137
137
  - **Vertex-only Gemini** → embeddings step throws (Vertex embeddings deferred). Same surface, "use a Gemini API key for now."
138
138
  - **ICP missing** → route returns 400 with `VALIDATION_ERROR`. Ask the operator for the ICP description in plain language.
139
- - **Seed collapse (hyperlocal/niche businesses)** → 40 raw seeds collapse to 1-2 canonical queries after embedding+clustering, even at low dedup thresholds. This happens when Gemini generates seed queries that all live in the same semantic pocket (e.g. all variants of "boutique hotel Venice Beach"). The embedding model sees them as near-identical, so clustering produces one representative.
139
+ - **Seed collapse (hyperlocal/niche businesses)** → 40 raw seeds collapse to 1-2 canonical queries after embedding+clustering, even at low dedup thresholds. This happens when Gemini generates seed queries that all live in the same semantic pocket (e.g. all variants of "boutique hotel Harborline Beach"). The embedding model sees them as near-identical, so clustering produces one representative.
140
140
 
141
141
  **Diagnostic signal:** `seedCountRaw / seedCount > 10:1` (e.g. 40 raw → 1 selected).
142
142
 
@@ -144,9 +144,9 @@ Keep it tight. The operator wakes to a short, decision-ready summary, not a full
144
144
 
145
145
  ```bash
146
146
  cnry discover run <project> \
147
- --icp-angle "romantic anniversary stay in Venice Beach" \
148
- --icp-angle "best rooftop bars and dining hotels LA" \
149
- --icp-angle "walkable Venice Beach hotels near Abbot Kinney" \
147
+ --icp-angle "romantic anniversary stay in Harborline Beach" \
148
+ --icp-angle "best rooftop bars and dining hotels near Harborline" \
149
+ --icp-angle "walkable Harborline Beach hotels near Kettle Row" \
150
150
  --icp-angle "design-forward boutique hotels for creative professionals" \
151
151
  --wait
152
152
  ```
@@ -262,22 +262,22 @@ Every GBP number belongs to one of three planes. **Tag each figure by plane befo
262
262
 
263
263
  ## Real-World Data Shapes & Signal Patterns
264
264
 
265
- Validated against three live businesses of different types (a computer-support shop, a roofing contractor, and a Venice Beach hotel). Bake these into any parsing or analysis code.
265
+ Validated against three live businesses of different types (a computer-support shop, a roofing contractor, and a beachfront boutique hotel). Bake these into any parsing or analysis code.
266
266
 
267
267
  ### Response-shape quirks (the parser MUST handle these)
268
268
 
269
- - **Values are string-encoded integers.** Keyword counts come as `{ "insightsValue": { "value": "10939" } }` or `{ "insightsValue": { "threshold": "15" } }` — note the nesting under `insightsValue` and that `"10939"` is a string. `Number()` it.
269
+ - **Values are string-encoded integers.** Keyword counts come as `{ "insightsValue": { "value": "10412" } }` or `{ "insightsValue": { "threshold": "15" } }` — note the nesting under `insightsValue` and that `"10412"` is a string. `Number()` it.
270
270
  - **Daily-metric zero days omit the value entirely.** A datedValue with no traffic is `{ "date": {"year":2026,"month":5,"day":1} }` — there is no `"value": "0"`. Treat a missing `value` as 0; don't skip the row.
271
271
  - **Dates are split objects** (`{year, month, day}`), not ISO strings. Reassemble.
272
272
 
273
273
  ### Signal patterns (what the data actually looks like)
274
274
 
275
- - **`BUSINESS_DIRECTION_REQUESTS` is the most reliably-populated conversion signal** across every business type — even a tiny roofing contractor logged 66/30d while its website-clicks (2) and call-clicks (1) were near-zero. For local/service businesses it's the headline AEO-conversion proxy, not website clicks.
275
+ - **`BUSINESS_DIRECTION_REQUESTS` is the most reliably-populated conversion signal** across every business type — even a tiny roofing contractor logged 58/30d while its website-clicks (2) and call-clicks (1) were near-zero. For local/service businesses it's the headline AEO-conversion proxy, not website clicks.
276
276
  - **Most of the 11 daily metrics are all-zero** for non-retail businesses (`BUSINESS_CONVERSATIONS`, `BUSINESS_BOOKINGS`, `BUSINESS_FOOD_*` were 0 for all three). Syncing all 11 is fine (zeros are cheap) but the dashboard should hide all-zero series.
277
- - **Impressions skew to Maps for physical-destination businesses.** The hotel pulled 7,402 desktop-maps impressions vs 2,257 desktop-search in 30 days — people find it on Maps.
278
- - **Keyword thresholding scales with volume.** A busy hotel was ~89% thresholded (its head terms like `hotels`→10,939 had exact values); both small businesses were **100% thresholded** (every keyword redacted). For the typical SMB location, expect zero exact keyword values — design the UI to lead with the `<N` floor, not exact counts.
277
+ - **Impressions skew to Maps for physical-destination businesses.** The hotel pulled 7,150 desktop-maps impressions vs 2,180 desktop-search in 30 days — people find it on Maps.
278
+ - **Keyword thresholding scales with volume.** A busy hotel was ~89% thresholded (its head terms like `hotels`→10,412 had exact values); both small businesses were **100% thresholded** (every keyword redacted). For the typical SMB location, expect zero exact keyword values — design the UI to lead with the `<N` floor, not exact counts.
279
279
  - **An empty Lodging resource is the norm, but it does NOT prove the owner set no amenities.** A real operating hotel returned a lodging resource with only `{ "name": ... }` and zero place-action links, yet its GBP "Hotel details" panel had amenities filled in (breakfast, wifi, parking, accessibility). So `populatedGroupCount === 0` means "canonry can't read structured attributes via this API response", not "the hotel has none". Surface the lodging signal as a **verify** (check the "Hotel details" panel), not a confirmed gap. The place-action emptiness is a separate, genuinely owner-readable signal.
280
- - **The Places cross-reference is a thin slice for hotels, not the full rendered listing.** Run live against the Venice Beach hotel at the Atmosphere tier, Place Details surfaced exactly one structured amenity, `wheelchair accessibility`, even though the rendered Google hotel module advertises far more (wifi, pool, room service, room rates). Those richer fields come from **Hotel Center**, which the Places API does not expose. So a thin or empty `gbp places` amenity list is NOT evidence the public listing is sparse; Places only carries a narrow, schema-bound subset (breakfast, dining, parking, pet-friendly, accessibility, restroom, family-friendly, outdoor seating, reservations). Read `gbp-listing-discrepancy` as a **floor** on the public-vs-owner gap (proof the listing advertises *at least* the named amenities), never a complete inventory. The owner-control point still stands at any size: even one amenity the profile fails to assert is a structured-data gap the operator can close.
280
+ - **The Places cross-reference is a thin slice for hotels, not the full rendered listing.** Run live against the beachfront hotel at the Atmosphere tier, Place Details surfaced exactly one structured amenity, `wheelchair accessibility`, even though the rendered Google hotel module advertises far more (wifi, pool, room service, room rates). Those richer fields come from **Hotel Center**, which the Places API does not expose. So a thin or empty `gbp places` amenity list is NOT evidence the public listing is sparse; Places only carries a narrow, schema-bound subset (breakfast, dining, parking, pet-friendly, accessibility, restroom, family-friendly, outdoor seating, reservations). Read `gbp-listing-discrepancy` as a **floor** on the public-vs-owner gap (proof the listing advertises *at least* the named amenities), never a complete inventory. The owner-control point still stands at any size: even one amenity the profile fails to assert is a structured-data gap the operator can close.
281
281
 
282
282
  ## Troubleshooting
283
283
 
@@ -5,7 +5,7 @@ import {
5
5
  siteCrawlGraphNodes,
6
6
  siteCrawlPages,
7
7
  siteCrawlSnapshots
8
- } from "./chunk-WL27HO5C.js";
8
+ } from "./chunk-PAZVNFQ3.js";
9
9
  import {
10
10
  SITE_CRAWL_GRAPH_MAX_EDGES,
11
11
  SITE_CRAWL_GRAPH_MAX_NODES,
@@ -4841,7 +4841,7 @@ var MIGRATION_VERSIONS = [
4841
4841
  // through the LEFT JOIN forces every value to be either a valid `q.id`
4842
4842
  // or NULL — pre-existing orphans land with NULL `query_id` / NULL
4843
4843
  // `query_text`, preserving the snapshot row instead of failing the
4844
- // migration. The May 2026 azcoatings DB had 459 such pre-existing
4844
+ // migration. A May 2026 production DB had 459 such pre-existing
4845
4845
  // orphans; without this guard, migrate() throws SQLITE_CONSTRAINT_FOREIGNKEY.
4846
4846
  `INSERT INTO query_snapshots_v58 (
4847
4847
  id, run_id, query_id, query_text, provider, model, citation_state,
@@ -4968,7 +4968,7 @@ var MIGRATION_VERSIONS = [
4968
4968
  version: 63,
4969
4969
  name: "audit-log-attribution-columns",
4970
4970
  // Adds `user_agent` and `actor_session` to `audit_log` so post-mortems
4971
- // can attribute destructive events (like the 2026-05-15 azcoatings
4971
+ // can attribute destructive events (like the 2026-05-15
4972
4972
  // queries.replaced incident — see PR #593) to a specific caller.
4973
4973
  // Without these columns, every mutation rides as `actor='api'` with no
4974
4974
  // narrower identity, so it's impossible to tell whether a destructive
@@ -18,7 +18,7 @@ import {
18
18
  deleteSiteCrawlGraphLayout,
19
19
  persistSiteCrawlGraphLayout,
20
20
  prepareSiteCrawlGraphLayout
21
- } from "./chunk-4PENJKRN.js";
21
+ } from "./chunk-EM7ZNUVO.js";
22
22
  import {
23
23
  CanonryMcpToolNames,
24
24
  CliError,
@@ -186,7 +186,7 @@ import {
186
186
  siteLivenessState,
187
187
  toAlertView,
188
188
  usageCounters
189
- } from "./chunk-WL27HO5C.js";
189
+ } from "./chunk-PAZVNFQ3.js";
190
190
  import {
191
191
  AGENT_MEMORY_VALUE_MAX_BYTES,
192
192
  AGENT_NONE,
@@ -6604,7 +6604,7 @@ function nonBlank(value) {
6604
6604
  return trimmed ? trimmed : void 0;
6605
6605
  }
6606
6606
  function resolveBuildCommit(env = process.env) {
6607
- const embedded = true ? "b5366056cfff6f0e275fe651252cfa1961bf40a9" : void 0;
6607
+ const embedded = true ? "ce28687c960cb27f6e173cc56510b33b44a0f0b3" : void 0;
6608
6608
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
6609
6609
  }
6610
6610
  function resolveInstanceIdentity(env = process.env) {
@@ -12181,7 +12181,7 @@ function readStoredGroundingSources(rawResponse) {
12181
12181
  return result2;
12182
12182
  }
12183
12183
  async function backfillInsightsCommand(project, opts) {
12184
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-MKHINDHN.js");
12184
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-73OEZNLJ.js");
12185
12185
  const config = loadConfig();
12186
12186
  const db = createClient(config.database);
12187
12187
  migrate(db);
package/dist/cli.js CHANGED
@@ -35,7 +35,7 @@ import {
35
35
  trackCliCommandFinished,
36
36
  trackEvent,
37
37
  waitForServerRuntimeStartup
38
- } from "./chunk-DRZWMFRV.js";
38
+ } from "./chunk-S2RDUO6K.js";
39
39
  import {
40
40
  autoSyncSkills,
41
41
  formatAutoSyncNotice
@@ -62,7 +62,7 @@ import {
62
62
  trustedProxiesProblem,
63
63
  trustedProxyProblem
64
64
  } from "./chunk-RB2F4D3Z.js";
65
- import "./chunk-4PENJKRN.js";
65
+ import "./chunk-EM7ZNUVO.js";
66
66
  import {
67
67
  CliError,
68
68
  EXIT_SYSTEM_ERROR,
@@ -89,7 +89,7 @@ import {
89
89
  queries,
90
90
  renderReportHtml,
91
91
  runs
92
- } from "./chunk-WL27HO5C.js";
92
+ } from "./chunk-PAZVNFQ3.js";
93
93
  import {
94
94
  AdsDeliverySnapshotStatuses,
95
95
  AdsHistoricalCampaignRollupStatuses,
@@ -442,7 +442,7 @@ function parseDemoTrustedProxies(values) {
442
442
  async function demoCommand(options) {
443
443
  const { host, port } = parseDemoListenOptions(options);
444
444
  const trustProxy = parseDemoTrustedProxies(options.trustProxy);
445
- const { createDemoServer } = await import("./demo-server-OCBV5OMS.js");
445
+ const { createDemoServer } = await import("./demo-server-UOIOPFAV.js");
446
446
  const app = await createDemoServer({ trustProxy });
447
447
  let stopping = false;
448
448
  const stop = () => {
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  layoutSiteCrawlGraphInput,
6
6
  persistSiteCrawlGraphLayout
7
- } from "./chunk-4PENJKRN.js";
7
+ } from "./chunk-EM7ZNUVO.js";
8
8
  import {
9
9
  PACKAGE_VERSION,
10
10
  canonryMcpTools
@@ -84,7 +84,7 @@ import {
84
84
  siteCrawlPages,
85
85
  siteCrawlSnapshots,
86
86
  trafficSources
87
- } from "./chunk-WL27HO5C.js";
87
+ } from "./chunk-PAZVNFQ3.js";
88
88
  import {
89
89
  RunKinds,
90
90
  SiteCrawlFetchStates,
package/dist/index.js CHANGED
@@ -3,13 +3,13 @@ import {
3
3
  createGoogleMarketingCredentialStore,
4
4
  createGoogleMarketingRuntime,
5
5
  createServer
6
- } from "./chunk-DRZWMFRV.js";
6
+ } from "./chunk-S2RDUO6K.js";
7
7
  import {
8
8
  loadConfig
9
9
  } from "./chunk-5FKR2K76.js";
10
- import "./chunk-4PENJKRN.js";
10
+ import "./chunk-EM7ZNUVO.js";
11
11
  import "./chunk-XBGPHZKA.js";
12
- import "./chunk-WL27HO5C.js";
12
+ import "./chunk-PAZVNFQ3.js";
13
13
  import "./chunk-QF7SY7AF.js";
14
14
  export {
15
15
  GoogleMarketingRuntimeError,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  HISTORY_WINDOW_RUNS,
3
3
  IntelligenceService
4
- } from "./chunk-WL27HO5C.js";
4
+ } from "./chunk-PAZVNFQ3.js";
5
5
  import "./chunk-QF7SY7AF.js";
6
6
  export {
7
7
  HISTORY_WINDOW_RUNS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonry/canonry",
3
- "version": "5.20.0",
3
+ "version": "5.20.1",
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": [
@@ -87,31 +87,31 @@
87
87
  "tsup": "^8.5.1",
88
88
  "tsx": "^4.19.0",
89
89
  "@ainyc/canonry-web": "0.0.0",
90
- "@ainyc/canonry-api-routes": "0.0.0",
91
90
  "@ainyc/canonry-api-client": "0.0.0",
92
- "@ainyc/canonry-contracts": "0.0.0",
93
- "@ainyc/canonry-config": "0.0.0",
91
+ "@ainyc/canonry-api-routes": "0.0.0",
94
92
  "@ainyc/canonry-db": "0.0.0",
95
- "@ainyc/canonry-integration-bing": "0.0.0",
93
+ "@ainyc/canonry-config": "0.0.0",
94
+ "@ainyc/canonry-contracts": "0.0.0",
96
95
  "@ainyc/canonry-integration-cloud-run": "0.0.0",
97
96
  "@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
97
+ "@ainyc/canonry-integration-bing": "0.0.0",
98
98
  "@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
99
- "@ainyc/canonry-integration-commoncrawl": "0.0.0",
100
- "@ainyc/canonry-integration-google-ads": "0.0.0",
99
+ "@ainyc/canonry-integration-google": "0.0.0",
101
100
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
101
+ "@ainyc/canonry-integration-google-ads": "0.0.0",
102
+ "@ainyc/canonry-integration-google-places": "0.0.0",
102
103
  "@ainyc/canonry-integration-google-tag-manager": "0.0.0",
103
- "@ainyc/canonry-integration-traffic": "0.0.0",
104
104
  "@ainyc/canonry-integration-openai-ads": "0.0.0",
105
- "@ainyc/canonry-integration-google": "0.0.0",
105
+ "@ainyc/canonry-integration-commoncrawl": "0.0.0",
106
+ "@ainyc/canonry-integration-traffic": "0.0.0",
106
107
  "@ainyc/canonry-integration-wordpress": "0.0.0",
107
108
  "@ainyc/canonry-intelligence": "0.0.0",
108
- "@ainyc/canonry-integration-google-places": "0.0.0",
109
109
  "@ainyc/canonry-provider-claude": "0.0.0",
110
110
  "@ainyc/canonry-provider-cdp": "0.0.0",
111
111
  "@ainyc/canonry-provider-gemini": "0.0.0",
112
112
  "@ainyc/canonry-provider-local": "0.0.0",
113
- "@ainyc/canonry-provider-perplexity": "0.0.0",
114
- "@ainyc/canonry-provider-openai": "0.0.0"
113
+ "@ainyc/canonry-provider-openai": "0.0.0",
114
+ "@ainyc/canonry-provider-perplexity": "0.0.0"
115
115
  },
116
116
  "scripts": {
117
117
  "build": "pnpm run build:cli && pnpm run build:web",