@ainyc/canonry 4.60.2 → 4.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/assets/agent-workspace/skills/aero/references/regression-playbook.md +2 -1
  2. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +4 -0
  3. package/assets/agent-workspace/skills/canonry/references/google-business-profile.md +23 -2
  4. package/assets/assets/{BacklinksPage-Dj4AVTma.js → BacklinksPage-CsGOAPNN.js} +1 -1
  5. package/assets/assets/{ChartPrimitives-7SFwUlCh.js → ChartPrimitives-Bjow7aaC.js} +1 -1
  6. package/assets/assets/{ProjectPage-4tWuU1ZR.js → ProjectPage-BZoMD93_.js} +1 -1
  7. package/assets/assets/{RunRow-CgPJfmWX.js → RunRow-ve7H_XIu.js} +1 -1
  8. package/assets/assets/{RunsPage-Cdo4jBn4.js → RunsPage-mYmYevh0.js} +1 -1
  9. package/assets/assets/{SettingsPage-N5iccPoU.js → SettingsPage-DoRiIJK5.js} +1 -1
  10. package/assets/assets/{TrafficPage-pSlQPdDg.js → TrafficPage-1LFyX4OT.js} +1 -1
  11. package/assets/assets/{TrafficSourceDetailPage-BQx3Evf6.js → TrafficSourceDetailPage-BYDJtQdO.js} +1 -1
  12. package/assets/assets/{extract-error-message-BOembgFV.js → extract-error-message-Bt6jcL_M.js} +1 -1
  13. package/assets/assets/{index-DjKFsFsl.js → index-BQxaYi-t.js} +70 -70
  14. package/assets/assets/{server-traffic-DNgNJ4Ht.js → server-traffic-C5f87b84.js} +1 -1
  15. package/assets/assets/{trash-2-DOznxxMW.js → trash-2-BFSmyr_7.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-CKWHFAVB.js → chunk-PITZUUFV.js} +33 -1
  18. package/dist/{chunk-3G3GAT3E.js → chunk-QZN3J35I.js} +489 -156
  19. package/dist/{chunk-DXWUBWBD.js → chunk-URPUUKLC.js} +22 -0
  20. package/dist/{chunk-AUR7VMQF.js → chunk-ZTVBTGDW.js} +59 -4
  21. package/dist/cli.js +32 -4
  22. package/dist/index.d.ts +16 -0
  23. package/dist/index.js +4 -4
  24. package/dist/{intelligence-service-UYVVKQ2K.js → intelligence-service-RWVBWSB5.js} +2 -2
  25. package/dist/mcp.js +2 -2
  26. package/package.json +11 -10
@@ -851,6 +851,10 @@ var gbpLocationDtoSchema = z7.object({
851
851
  primaryCategoryDisplayName: z7.string().nullable(),
852
852
  storefrontAddress: z7.string().nullable(),
853
853
  websiteUri: z7.string().nullable(),
854
+ // Google Maps Place ID + public Maps link (from location metadata; null when
855
+ // the location is not on Maps). `placeId` is the join key to the Places API.
856
+ placeId: z7.string().nullable(),
857
+ mapsUri: z7.string().nullable(),
854
858
  selected: z7.boolean(),
855
859
  syncedAt: z7.string().nullable(),
856
860
  createdAt: z7.string(),
@@ -944,6 +948,22 @@ var gbpLodgingListResponseSchema = z7.object({
944
948
  lodging: z7.array(gbpLodgingDtoSchema),
945
949
  total: z7.number().int().nonnegative()
946
950
  });
951
+ var gbpPlaceDetailsDtoSchema = z7.object({
952
+ locationName: z7.string(),
953
+ placeId: z7.string(),
954
+ /** Field-mask SKU tier the snapshot was fetched at ('atmosphere' | 'pro'). */
955
+ tier: z7.string(),
956
+ /** Amenities the public listing advertises, derived from `place`. */
957
+ amenities: z7.array(z7.string()),
958
+ /** When this listing was last fetched from Places — advances on every fetch, even when the content is unchanged (this is what the refresh-cadence gate reads). */
959
+ syncedAt: z7.string(),
960
+ /** Raw Place Details resource as Google returned it. */
961
+ place: z7.record(z7.string(), z7.unknown())
962
+ });
963
+ var gbpPlaceDetailsListResponseSchema = z7.object({
964
+ places: z7.array(gbpPlaceDetailsDtoSchema),
965
+ total: z7.number().int().nonnegative()
966
+ });
947
967
  var gbpSummaryDtoSchema = z7.object({
948
968
  scope: z7.object({
949
969
  locationName: z7.string().nullable(),
@@ -2099,6 +2119,7 @@ var projectSearchInsightHitSchema = z18.object({
2099
2119
  "competitor-lost",
2100
2120
  // Google Business Profile (local-AEO) insight types — see InsightType.
2101
2121
  "gbp-lodging-gap",
2122
+ "gbp-listing-discrepancy",
2102
2123
  "gbp-cta-gap",
2103
2124
  "gbp-metric-drop",
2104
2125
  "gbp-keyword-drop"
@@ -3887,6 +3908,7 @@ export {
3887
3908
  gbpKeywordImpressionListResponseSchema,
3888
3909
  gbpPlaceActionListResponseSchema,
3889
3910
  gbpLodgingListResponseSchema,
3911
+ gbpPlaceDetailsListResponseSchema,
3890
3912
  gbpSummaryDtoSchema,
3891
3913
  withRetry,
3892
3914
  isRetryableHttpError,
@@ -8,7 +8,7 @@ import {
8
8
  loadConfig,
9
9
  loadConfigRaw,
10
10
  saveConfigPatch
11
- } from "./chunk-CKWHFAVB.js";
11
+ } from "./chunk-PITZUUFV.js";
12
12
  import {
13
13
  CC_CACHE_DIR,
14
14
  DUCKDB_SPEC,
@@ -54,14 +54,17 @@ import {
54
54
  gbpLocations,
55
55
  gbpLodgingSnapshots,
56
56
  gbpPlaceActions,
57
+ gbpPlaceDetails,
57
58
  getCrawlIssues,
58
59
  getLodging,
60
+ getPlaceDetails,
59
61
  getUrlInfo,
60
62
  groupRunsByCreatedAt,
61
63
  gscCoverageSnapshots,
62
64
  gscSearchData,
63
65
  gscUrlInspections,
64
66
  hashLodging,
67
+ hashPlaceDetails,
65
68
  insights,
66
69
  inspectUrl,
67
70
  installDuckdb,
@@ -90,7 +93,7 @@ import {
90
93
  runs,
91
94
  schedules,
92
95
  usageCounters
93
- } from "./chunk-3G3GAT3E.js";
96
+ } from "./chunk-QZN3J35I.js";
94
97
  import {
95
98
  AGENT_MEMORY_VALUE_MAX_BYTES,
96
99
  AGENT_PROVIDER_IDS,
@@ -138,7 +141,7 @@ import {
138
141
  skillsClientSchema,
139
142
  validationError,
140
143
  withRetry
141
- } from "./chunk-DXWUBWBD.js";
144
+ } from "./chunk-URPUUKLC.js";
142
145
 
143
146
  // src/telemetry.ts
144
147
  import crypto from "crypto";
@@ -2692,6 +2695,21 @@ var perplexityAdapter = {
2692
2695
  }
2693
2696
  };
2694
2697
 
2698
+ // src/places-config.ts
2699
+ var DEFAULT_TIER = "atmosphere";
2700
+ var DEFAULT_REFRESH_INTERVAL_DAYS = 7;
2701
+ function parseTier(raw) {
2702
+ return raw === "atmosphere" || raw === "pro" || raw === "off" ? raw : void 0;
2703
+ }
2704
+ function getPlacesConfig(config) {
2705
+ const envKey = process.env.GOOGLE_PLACES_API_KEY?.trim();
2706
+ const apiKey = envKey || config.places?.apiKey || void 0;
2707
+ const tier = parseTier(process.env.GOOGLE_PLACES_TIER?.trim()) ?? config.places?.tier ?? DEFAULT_TIER;
2708
+ const envInterval = Number(process.env.GOOGLE_PLACES_REFRESH_INTERVAL_DAYS);
2709
+ const refreshIntervalDays = Number.isFinite(envInterval) && envInterval > 0 ? envInterval : config.places?.refreshIntervalDays ?? DEFAULT_REFRESH_INTERVAL_DAYS;
2710
+ return { apiKey, tier, refreshIntervalDays };
2711
+ }
2712
+
2695
2713
  // src/google-config.ts
2696
2714
  function ensureConnections(config) {
2697
2715
  if (!config.google) config.google = {};
@@ -3790,6 +3808,7 @@ async function executeGscSync(db, runId, projectId, opts) {
3790
3808
  // src/gbp-sync.ts
3791
3809
  import crypto5 from "crypto";
3792
3810
  import { eq as eq3, and as and3, desc, inArray as inArray2, lt } from "drizzle-orm";
3811
+ var MS_PER_DAY = 864e5;
3793
3812
  var log3 = createLogger("GbpSync");
3794
3813
  var LOCATION_CONCURRENCY = 4;
3795
3814
  var DEFAULT_DAYS_OF_METRICS = 30;
@@ -3849,6 +3868,7 @@ async function executeGbpSync(db, runId, projectId, opts) {
3849
3868
  const keywordsEnd = monthMinus(0);
3850
3869
  const trendMonths = Array.from({ length: KEYWORD_TREND_MONTHS }, (_, i) => monthMinus(i + 1));
3851
3870
  const keywordRetentionCutoff = monthKey(monthMinus(KEYWORD_HISTORY_RETENTION_MONTHS));
3871
+ const { apiKey: placesApiKey, tier: placesTier, refreshIntervalDays: placesRefreshDays } = getPlacesConfig(opts.config);
3852
3872
  log3.info("sync.start", { runId, projectId, locations: locationRows.length, daysOfMetrics, monthsOfKeywords });
3853
3873
  const errors = /* @__PURE__ */ new Map();
3854
3874
  let okCount = 0;
@@ -3880,6 +3900,25 @@ async function executeGbpSync(db, runId, projectId, opts) {
3880
3900
  const lodgingHash = lodging ? hashLodging(lodging) : null;
3881
3901
  const latestLodging = lodging ? db.select().from(gbpLodgingSnapshots).where(and3(eq3(gbpLodgingSnapshots.projectId, projectId), eq3(gbpLodgingSnapshots.locationName, loc.locationName))).orderBy(desc(gbpLodgingSnapshots.syncedAt)).limit(1).get() : void 0;
3882
3902
  const lodgingChanged = lodging !== null && latestLodging?.contentHash !== lodgingHash;
3903
+ let placeToWrite = null;
3904
+ let placeToTouch = null;
3905
+ if (placesTier !== "off" && placesApiKey && lodging !== null && loc.placeId) {
3906
+ const latestPlace = db.select().from(gbpPlaceDetails).where(and3(eq3(gbpPlaceDetails.projectId, projectId), eq3(gbpPlaceDetails.locationName, loc.locationName))).orderBy(desc(gbpPlaceDetails.syncedAt)).limit(1).get();
3907
+ const ageDays = latestPlace ? (Date.now() - new Date(latestPlace.syncedAt).getTime()) / MS_PER_DAY : Infinity;
3908
+ if (ageDays >= placesRefreshDays) {
3909
+ try {
3910
+ const place = await getPlaceDetails(loc.placeId, placesApiKey, { tier: placesTier });
3911
+ const hash = hashPlaceDetails(place);
3912
+ if (!latestPlace || latestPlace.contentHash !== hash) {
3913
+ placeToWrite = { contentHash: hash, attributes: place, tier: placesTier, placeId: loc.placeId };
3914
+ } else {
3915
+ placeToTouch = latestPlace.id;
3916
+ }
3917
+ } catch (placesErr) {
3918
+ log3.warn("places.failed", { runId, location: loc.locationName, error: placesErr instanceof Error ? placesErr.message : String(placesErr) });
3919
+ }
3920
+ }
3921
+ }
3883
3922
  const insertNow = (/* @__PURE__ */ new Date()).toISOString();
3884
3923
  db.transaction((tx) => {
3885
3924
  tx.delete(gbpDailyMetrics).where(and3(eq3(gbpDailyMetrics.projectId, projectId), eq3(gbpDailyMetrics.locationName, loc.locationName))).run();
@@ -3962,6 +4001,21 @@ async function executeGbpSync(db, runId, projectId, opts) {
3962
4001
  syncRunId: runId
3963
4002
  }).run();
3964
4003
  }
4004
+ if (placeToWrite) {
4005
+ tx.insert(gbpPlaceDetails).values({
4006
+ id: crypto5.randomUUID(),
4007
+ projectId,
4008
+ locationName: loc.locationName,
4009
+ placeId: placeToWrite.placeId,
4010
+ contentHash: placeToWrite.contentHash,
4011
+ tier: placeToWrite.tier,
4012
+ attributes: placeToWrite.attributes,
4013
+ syncedAt: insertNow,
4014
+ syncRunId: runId
4015
+ }).run();
4016
+ } else if (placeToTouch) {
4017
+ tx.update(gbpPlaceDetails).set({ syncedAt: insertNow, syncRunId: runId }).where(eq3(gbpPlaceDetails.id, placeToTouch)).run();
4018
+ }
3965
4019
  tx.update(gbpLocations).set({ syncedAt: insertNow, updatedAt: insertNow }).where(eq3(gbpLocations.id, loc.id)).run();
3966
4020
  });
3967
4021
  okCount++;
@@ -5562,7 +5616,7 @@ function readStoredGroundingSources(rawResponse) {
5562
5616
  return result;
5563
5617
  }
5564
5618
  async function backfillInsightsCommand(project, opts) {
5565
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-UYVVKQ2K.js");
5619
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-RWVBWSB5.js");
5566
5620
  const config = loadConfig();
5567
5621
  const db = createClient(config.database);
5568
5622
  migrate(db);
@@ -9776,6 +9830,7 @@ async function createServer(opts) {
9776
9830
  registerAgentRoutes(scope, { db: opts.db, sessionRegistry });
9777
9831
  },
9778
9832
  getGoogleAuthConfig: () => getGoogleAuthConfig(opts.config),
9833
+ getPlacesConfig: () => getPlacesConfig(opts.config),
9779
9834
  googleConnectionStore,
9780
9835
  googleStateSecret,
9781
9836
  publicUrl: opts.config.publicUrl,
package/dist/cli.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  setTelemetrySource,
28
28
  showFirstRunNotice,
29
29
  trackEvent
30
- } from "./chunk-AUR7VMQF.js";
30
+ } from "./chunk-ZTVBTGDW.js";
31
31
  import {
32
32
  CliError,
33
33
  EXIT_SYSTEM_ERROR,
@@ -43,7 +43,7 @@ import {
43
43
  saveConfig,
44
44
  saveConfigPatch,
45
45
  usageError
46
- } from "./chunk-CKWHFAVB.js";
46
+ } from "./chunk-PITZUUFV.js";
47
47
  import {
48
48
  apiKeys,
49
49
  createClient,
@@ -51,7 +51,7 @@ import {
51
51
  projects,
52
52
  queries,
53
53
  renderReportHtml
54
- } from "./chunk-3G3GAT3E.js";
54
+ } from "./chunk-QZN3J35I.js";
55
55
  import {
56
56
  CcReleaseSyncStatuses,
57
57
  CheckScopes,
@@ -67,7 +67,7 @@ import {
67
67
  notificationEventSchema,
68
68
  providerQuotaPolicySchema,
69
69
  resolveProviderInput
70
- } from "./chunk-DXWUBWBD.js";
70
+ } from "./chunk-URPUUKLC.js";
71
71
 
72
72
  // src/cli.ts
73
73
  import { pathToFileURL } from "url";
@@ -3069,6 +3069,23 @@ async function gbpLodging(project, opts) {
3069
3069
  console.log(` ${l.locationName} ${l.populatedGroupCount} attribute group(s)${note}`);
3070
3070
  }
3071
3071
  }
3072
+ async function gbpPlaces(project, opts) {
3073
+ const client = getClient6();
3074
+ const response = await client.listGbpPlaces(project, { locationName: opts.location });
3075
+ if (opts.format === "json") {
3076
+ console.log(JSON.stringify(response, null, 2));
3077
+ return;
3078
+ }
3079
+ if (response.places.length === 0) {
3080
+ console.log('No Places data \u2014 set a Places API key (places.apiKey / GOOGLE_PLACES_API_KEY) and run "canonry gbp sync" for lodging locations.');
3081
+ return;
3082
+ }
3083
+ console.log(`${response.total} Places listing snapshot(s) \u2014 the amenities Google's public listing advertises:`);
3084
+ for (const p of response.places) {
3085
+ const amenities = p.amenities.length > 0 ? p.amenities.join(", ") : "(none detected)";
3086
+ console.log(` ${p.locationName} [${p.tier}] ${amenities}`);
3087
+ }
3088
+ }
3072
3089
  function fmtDelta(pct2) {
3073
3090
  if (pct2 === null) return "n/a";
3074
3091
  return `${pct2 >= 0 ? "+" : ""}${pct2}%`;
@@ -3261,6 +3278,17 @@ var GBP_CLI_COMMANDS = [
3261
3278
  await gbpLodging(project, { location: getString(input.values, "location"), format: input.format });
3262
3279
  }
3263
3280
  },
3281
+ {
3282
+ path: ["gbp", "places"],
3283
+ usage: "canonry gbp places <project> [--location <name>] [--format json]",
3284
+ options: {
3285
+ location: stringOption()
3286
+ },
3287
+ run: async (input) => {
3288
+ const project = requireProject(input, "gbp.places", "canonry gbp places <project> [--location <name>] [--format json]");
3289
+ await gbpPlaces(project, { location: getString(input.values, "location"), format: input.format });
3290
+ }
3291
+ },
3264
3292
  {
3265
3293
  path: ["gbp", "summary"],
3266
3294
  usage: "canonry gbp summary <project> [--location <name>] [--format json]",
package/dist/index.d.ts CHANGED
@@ -144,6 +144,21 @@ interface AgentConfigEntry {
144
144
  /** Agent mode. Only 'disabled' is valid until the native loop ships. */
145
145
  mode?: 'disabled';
146
146
  }
147
+ /**
148
+ * Google Places API config — supplemental rendered-listing data for GBP
149
+ * lodging locations (#648). The API key authenticates Place Details calls
150
+ * (`X-Goog-Api-Key`); it is NOT OAuth and is unrelated to `google.clientId`.
151
+ * - `tier`: 'atmosphere' (default; amenity booleans for the cross-reference,
152
+ * 1k free calls/month) | 'pro' (cheaper, accessibility-only) | 'off'.
153
+ * - `refreshIntervalDays`: minimum age before a location's Place Details is
154
+ * re-fetched during gbp-sync (default 7) — the cost lever, since amenities
155
+ * change rarely.
156
+ */
157
+ interface PlacesConfigEntry {
158
+ apiKey?: string;
159
+ tier?: 'atmosphere' | 'pro' | 'off';
160
+ refreshIntervalDays?: number;
161
+ }
147
162
  interface CanonryConfig {
148
163
  apiUrl: string;
149
164
  publicUrl?: string;
@@ -172,6 +187,7 @@ interface CanonryConfig {
172
187
  lastUpdateCheckAt?: string;
173
188
  lastKnownLatestVersion?: string;
174
189
  agent?: AgentConfigEntry;
190
+ places?: PlacesConfigEntry;
175
191
  }
176
192
  declare function loadConfig(): CanonryConfig;
177
193
 
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-AUR7VMQF.js";
3
+ } from "./chunk-ZTVBTGDW.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-CKWHFAVB.js";
7
- import "./chunk-3G3GAT3E.js";
8
- import "./chunk-DXWUBWBD.js";
6
+ } from "./chunk-PITZUUFV.js";
7
+ import "./chunk-QZN3J35I.js";
8
+ import "./chunk-URPUUKLC.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-3G3GAT3E.js";
4
- import "./chunk-DXWUBWBD.js";
3
+ } from "./chunk-QZN3J35I.js";
4
+ import "./chunk-URPUUKLC.js";
5
5
  export {
6
6
  IntelligenceService
7
7
  };
package/dist/mcp.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  PACKAGE_VERSION,
4
4
  canonryMcpTools,
5
5
  createApiClient
6
- } from "./chunk-CKWHFAVB.js";
7
- import "./chunk-DXWUBWBD.js";
6
+ } from "./chunk-PITZUUFV.js";
7
+ import "./chunk-URPUUKLC.js";
8
8
 
9
9
  // src/mcp/cli.ts
10
10
  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.60.2",
3
+ "version": "4.61.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",
@@ -61,25 +61,26 @@
61
61
  "@types/node-cron": "^3.0.11",
62
62
  "tsup": "^8.5.1",
63
63
  "tsx": "^4.19.0",
64
- "@ainyc/canonry-api-routes": "0.0.0",
65
- "@ainyc/canonry-api-client": "0.0.0",
66
64
  "@ainyc/canonry-config": "0.0.0",
67
- "@ainyc/canonry-contracts": "0.0.0",
65
+ "@ainyc/canonry-api-client": "0.0.0",
66
+ "@ainyc/canonry-api-routes": "0.0.0",
68
67
  "@ainyc/canonry-db": "0.0.0",
69
68
  "@ainyc/canonry-integration-bing": "0.0.0",
70
- "@ainyc/canonry-integration-commoncrawl": "0.0.0",
71
- "@ainyc/canonry-integration-google": "0.0.0",
69
+ "@ainyc/canonry-contracts": "0.0.0",
72
70
  "@ainyc/canonry-integration-cloud-run": "0.0.0",
71
+ "@ainyc/canonry-integration-google-business-profile": "0.0.0",
72
+ "@ainyc/canonry-integration-google": "0.0.0",
73
+ "@ainyc/canonry-integration-google-places": "0.0.0",
74
+ "@ainyc/canonry-integration-commoncrawl": "0.0.0",
73
75
  "@ainyc/canonry-integration-traffic": "0.0.0",
74
- "@ainyc/canonry-integration-wordpress": "0.0.0",
75
76
  "@ainyc/canonry-intelligence": "0.0.0",
76
- "@ainyc/canonry-integration-google-business-profile": "0.0.0",
77
77
  "@ainyc/canonry-provider-cdp": "0.0.0",
78
78
  "@ainyc/canonry-provider-claude": "0.0.0",
79
- "@ainyc/canonry-provider-local": "0.0.0",
79
+ "@ainyc/canonry-integration-wordpress": "0.0.0",
80
80
  "@ainyc/canonry-provider-gemini": "0.0.0",
81
+ "@ainyc/canonry-provider-openai": "0.0.0",
81
82
  "@ainyc/canonry-provider-perplexity": "0.0.0",
82
- "@ainyc/canonry-provider-openai": "0.0.0"
83
+ "@ainyc/canonry-provider-local": "0.0.0"
83
84
  },
84
85
  "scripts": {
85
86
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",