@ainyc/canonry 4.92.0 → 4.94.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/canonry/references/canonry-cli.md +4 -0
  2. package/assets/agent-workspace/skills/canonry/references/google-business-profile.md +6 -0
  3. package/assets/assets/{BacklinksPage-D3JpcWtH.js → BacklinksPage-FeUApIRv.js} +1 -1
  4. package/assets/assets/{ChartPrimitives-DdwqEX5v.js → ChartPrimitives-DFCM9oRt.js} +1 -1
  5. package/assets/assets/ProjectPage-Dyx8eZf_.js +6 -0
  6. package/assets/assets/{RunRow-c_2jd8iz.js → RunRow-CF4jm8w8.js} +1 -1
  7. package/assets/assets/{RunsPage-D6eM_5tB.js → RunsPage-Dlyl-dmG.js} +1 -1
  8. package/assets/assets/{SettingsPage-DaMc8nxK.js → SettingsPage-DaQ0YNHz.js} +1 -1
  9. package/assets/assets/{TrafficPage-Dw16dcSc.js → TrafficPage-38zf4QS8.js} +1 -1
  10. package/assets/assets/{TrafficSourceDetailPage-Dy9D80dz.js → TrafficSourceDetailPage-DJY18oUo.js} +1 -1
  11. package/assets/assets/{arrow-left-D8UaBG9a.js → arrow-left-CRHf1qtt.js} +1 -1
  12. package/assets/assets/{extract-error-message-CfxCIMNJ.js → extract-error-message-uWeOU6mU.js} +1 -1
  13. package/assets/assets/{index-BA-lh2pG.js → index-B7ZT1i67.js} +73 -73
  14. package/assets/assets/{index-fLUYE9Z5.css → index-DiykVUPr.css} +1 -1
  15. package/assets/assets/{trash-2-j1tX1-Kq.js → trash-2-B1qn8ZLk.js} +1 -1
  16. package/assets/index.html +2 -2
  17. package/dist/{chunk-QYC2N23P.js → chunk-3TTBKG73.js} +69 -5
  18. package/dist/{chunk-HW4GVIQD.js → chunk-K4QOSPBX.js} +37 -2
  19. package/dist/{chunk-VJPVFCZW.js → chunk-PV3RAHMK.js} +19 -0
  20. package/dist/{chunk-IFUHXONX.js → chunk-ZJADMHJ5.js} +207 -63
  21. package/dist/cli.js +57 -9
  22. package/dist/index.js +4 -4
  23. package/dist/{intelligence-service-24HCX4SJ.js → intelligence-service-M33PXIWS.js} +2 -2
  24. package/dist/mcp.js +2 -2
  25. package/package.json +9 -9
  26. package/assets/assets/ProjectPage-BPLf4U3A.js +0 -6
@@ -1 +1 @@
1
- import{c}from"./index-BA-lh2pG.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
1
+ import{c}from"./index-B7ZT1i67.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
package/assets/index.html CHANGED
@@ -12,12 +12,12 @@
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-BA-lh2pG.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-B7ZT1i67.js"></script>
16
16
  <link rel="modulepreload" crossorigin href="./assets/vendor-tanstack-Dq7p98wZ.js">
17
17
  <link rel="modulepreload" crossorigin href="./assets/vendor-radix-B57xfQbP.js">
18
18
  <link rel="modulepreload" crossorigin href="./assets/vendor-recharts-ClRVR6aX.js">
19
19
  <link rel="modulepreload" crossorigin href="./assets/vendor-markdown-DK7fbRNb.js">
20
- <link rel="stylesheet" crossorigin href="./assets/index-fLUYE9Z5.css">
20
+ <link rel="stylesheet" crossorigin href="./assets/index-DiykVUPr.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
@@ -9,7 +9,7 @@ import {
9
9
  loadConfig,
10
10
  loadConfigRaw,
11
11
  saveConfigPatch
12
- } from "./chunk-HW4GVIQD.js";
12
+ } from "./chunk-K4QOSPBX.js";
13
13
  import {
14
14
  CC_CACHE_DIR,
15
15
  DUCKDB_SPEC,
@@ -32,12 +32,14 @@ import {
32
32
  backlinkSummaries,
33
33
  bingCoverageSnapshots,
34
34
  bingUrlInspections,
35
+ buildLocationProfileFields,
35
36
  ccReleasePaths,
36
37
  ccReleaseSyncs,
37
38
  classifyAiUserFetch,
38
39
  classifyCrawler,
39
40
  competitors,
40
41
  computeCompetitorOverlap,
42
+ countAttributes,
41
43
  countPopulatedGroups,
42
44
  crawlerEventsHourly,
43
45
  createClient,
@@ -52,8 +54,10 @@ import {
52
54
  extractRecommendedCompetitors,
53
55
  fetchDailyMetrics,
54
56
  fetchSearchAnalytics,
57
+ formatStorefrontAddress,
55
58
  gaAiReferrals,
56
59
  gaTrafficSnapshots,
60
+ gbpAttributesSnapshots,
57
61
  gbpDailyMetrics,
58
62
  gbpKeywordImpressions,
59
63
  gbpKeywordMonthly,
@@ -61,6 +65,7 @@ import {
61
65
  gbpLodgingSnapshots,
62
66
  gbpPlaceActions,
63
67
  gbpPlaceDetails,
68
+ getAttributes,
64
69
  getCrawlIssues,
65
70
  getLinkCounts,
66
71
  getLodging,
@@ -71,6 +76,7 @@ import {
71
76
  gscCoverageSnapshots,
72
77
  gscSearchData,
73
78
  gscUrlInspections,
79
+ hashAttributes,
74
80
  hashLodging,
75
81
  hashPlaceDetails,
76
82
  insights,
@@ -79,6 +85,7 @@ import {
79
85
  isDuckdbInstalled,
80
86
  isValidReleaseId,
81
87
  listCachedReleases,
88
+ listLocations,
82
89
  listMonthlyKeywords,
83
90
  listPlaceActionLinks,
84
91
  loadDuckdb,
@@ -104,7 +111,7 @@ import {
104
111
  siteAuditPages,
105
112
  siteAuditSnapshots,
106
113
  usageCounters
107
- } from "./chunk-IFUHXONX.js";
114
+ } from "./chunk-ZJADMHJ5.js";
108
115
  import {
109
116
  AGENT_MEMORY_VALUE_MAX_BYTES,
110
117
  AGENT_PROVIDER_IDS,
@@ -164,7 +171,7 @@ import {
164
171
  validationError,
165
172
  winnabilityClassLabel,
166
173
  withRetry
167
- } from "./chunk-VJPVFCZW.js";
174
+ } from "./chunk-PV3RAHMK.js";
168
175
 
169
176
  // src/telemetry.ts
170
177
  import crypto from "crypto";
@@ -3927,6 +3934,7 @@ async function executeGbpSync(db, runId, projectId, opts) {
3927
3934
  if (locationRows.length === 0) {
3928
3935
  throw new Error("No selected GBP locations to sync. Discover and select locations first.");
3929
3936
  }
3937
+ locationRows = await refreshSelectedLocationProfiles(db, projectId, accessToken, locationRows);
3930
3938
  const daysOfMetrics = opts.daysOfMetrics ?? DEFAULT_DAYS_OF_METRICS;
3931
3939
  const monthsOfKeywords = opts.monthsOfKeywords ?? DEFAULT_MONTHS_OF_KEYWORDS;
3932
3940
  const metricsStart = daysAgo2(daysOfMetrics);
@@ -3943,7 +3951,7 @@ async function executeGbpSync(db, runId, projectId, opts) {
3943
3951
  const batch = locationRows.slice(i, i + LOCATION_CONCURRENCY);
3944
3952
  await Promise.all(batch.map(async (loc) => {
3945
3953
  try {
3946
- const [metricRows, keywordRows, placeActionRows, lodging, monthlyKeywordResults] = await Promise.all([
3954
+ const [metricRows, keywordRows, placeActionRows, lodging, attributes, monthlyKeywordResults] = await Promise.all([
3947
3955
  fetchDailyMetrics(accessToken, loc.locationName, {
3948
3956
  metrics: GBP_DAILY_METRICS,
3949
3957
  startDate: metricsStart,
@@ -3956,6 +3964,9 @@ async function executeGbpSync(db, runId, projectId, opts) {
3956
3964
  listPlaceActionLinks(accessToken, loc.locationName),
3957
3965
  // null when the location is not a lodging-category property.
3958
3966
  getLodging(accessToken, loc.locationName),
3967
+ // Owner-set attributes (any business category) — [] when the
3968
+ // location has none or there is no attributes resource (404).
3969
+ getAttributes(accessToken, loc.locationName),
3959
3970
  // Per-month keyword impressions for the accumulating monthly series.
3960
3971
  // One call per complete month (the endpoint aggregates a range into
3961
3972
  // a single figure, so month resolution requires month-sized calls).
@@ -3967,6 +3978,9 @@ async function executeGbpSync(db, runId, projectId, opts) {
3967
3978
  const lodgingHash = lodging ? hashLodging(lodging) : null;
3968
3979
  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;
3969
3980
  const lodgingChanged = lodging !== null && latestLodging?.contentHash !== lodgingHash;
3981
+ const attributesHash = hashAttributes(attributes);
3982
+ const latestAttributes = db.select().from(gbpAttributesSnapshots).where(and3(eq3(gbpAttributesSnapshots.projectId, projectId), eq3(gbpAttributesSnapshots.locationName, loc.locationName))).orderBy(desc(gbpAttributesSnapshots.syncedAt)).limit(1).get();
3983
+ const attributesChanged = latestAttributes?.contentHash !== attributesHash;
3970
3984
  let placeToWrite = null;
3971
3985
  let placeToTouch = null;
3972
3986
  if (placesTier !== "off" && placesApiKey && lodging !== null && loc.placeId) {
@@ -4070,6 +4084,20 @@ async function executeGbpSync(db, runId, projectId, opts) {
4070
4084
  } else if (lodging !== null && latestLodging) {
4071
4085
  tx.update(gbpLodgingSnapshots).set({ syncedAt: insertNow, syncRunId: runId }).where(eq3(gbpLodgingSnapshots.id, latestLodging.id)).run();
4072
4086
  }
4087
+ if (attributesChanged) {
4088
+ tx.insert(gbpAttributesSnapshots).values({
4089
+ id: crypto5.randomUUID(),
4090
+ projectId,
4091
+ locationName: loc.locationName,
4092
+ contentHash: attributesHash,
4093
+ attributes,
4094
+ attributeCount: countAttributes(attributes),
4095
+ syncedAt: insertNow,
4096
+ syncRunId: runId
4097
+ }).run();
4098
+ } else if (latestAttributes) {
4099
+ tx.update(gbpAttributesSnapshots).set({ syncedAt: insertNow, syncRunId: runId }).where(eq3(gbpAttributesSnapshots.id, latestAttributes.id)).run();
4100
+ }
4073
4101
  if (placeToWrite) {
4074
4102
  tx.insert(gbpPlaceDetails).values({
4075
4103
  id: crypto5.randomUUID(),
@@ -4121,6 +4149,42 @@ async function executeGbpSync(db, runId, projectId, opts) {
4121
4149
  function monthKey(m) {
4122
4150
  return `${m.year}-${String(m.month).padStart(2, "0")}`;
4123
4151
  }
4152
+ async function refreshSelectedLocationProfiles(db, projectId, accessToken, locationRows) {
4153
+ const byAccount = /* @__PURE__ */ new Map();
4154
+ for (const row of locationRows) {
4155
+ const accountRows = byAccount.get(row.accountName) ?? [];
4156
+ accountRows.push(row);
4157
+ byAccount.set(row.accountName, accountRows);
4158
+ }
4159
+ const refreshedRows = /* @__PURE__ */ new Map();
4160
+ for (const [accountName, accountRows] of byAccount.entries()) {
4161
+ const remoteLocations = await listLocations(accessToken, accountName);
4162
+ const remoteByName = new Map(remoteLocations.map((loc) => [loc.name, loc]));
4163
+ const missing = accountRows.filter((row) => !remoteByName.has(row.locationName));
4164
+ if (missing.length > 0) {
4165
+ throw new Error(`Selected GBP location(s) no longer appear under ${accountName}: ${missing.map((row) => row.locationName).join(", ")}. Run "canonry gbp locations discover" to refresh the selection.`);
4166
+ }
4167
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
4168
+ for (const row of accountRows) {
4169
+ const remote = remoteByName.get(row.locationName);
4170
+ const profile = buildLocationProfileFields(remote);
4171
+ const update = {
4172
+ accountName,
4173
+ displayName: remote.title ?? row.displayName,
4174
+ primaryCategoryDisplayName: remote.categories?.primaryCategory?.displayName ?? null,
4175
+ storefrontAddress: formatStorefrontAddress(remote),
4176
+ websiteUri: remote.websiteUri ?? null,
4177
+ placeId: remote.metadata?.placeId ?? null,
4178
+ mapsUri: remote.metadata?.mapsUri ?? null,
4179
+ ...profile,
4180
+ updatedAt
4181
+ };
4182
+ db.update(gbpLocations).set(update).where(and3(eq3(gbpLocations.projectId, projectId), eq3(gbpLocations.id, row.id))).run();
4183
+ refreshedRows.set(row.id, { ...row, ...update });
4184
+ }
4185
+ }
4186
+ return locationRows.map((row) => refreshedRows.get(row.id) ?? row);
4187
+ }
4124
4188
 
4125
4189
  // src/ads-sync.ts
4126
4190
  import crypto6 from "crypto";
@@ -6323,7 +6387,7 @@ function readStoredGroundingSources(rawResponse) {
6323
6387
  return result;
6324
6388
  }
6325
6389
  async function backfillInsightsCommand(project, opts) {
6326
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-24HCX4SJ.js");
6390
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-M33PXIWS.js");
6327
6391
  const config = loadConfig();
6328
6392
  const db = createClient(config.database);
6329
6393
  migrate(db);
@@ -23,7 +23,7 @@ import {
23
23
  trafficConnectVercelRequestSchema,
24
24
  trafficConnectWordpressRequestSchema,
25
25
  trafficEventKindSchema
26
- } from "./chunk-VJPVFCZW.js";
26
+ } from "./chunk-PV3RAHMK.js";
27
27
 
28
28
  // src/config.ts
29
29
  import fs from "fs";
@@ -2194,6 +2194,18 @@ var getApiV1ProjectsByNameGbpLodging = (options) => {
2194
2194
  ...options
2195
2195
  });
2196
2196
  };
2197
+ var getApiV1ProjectsByNameGbpAttributes = (options) => {
2198
+ return (options.client ?? client).get({
2199
+ security: [
2200
+ {
2201
+ scheme: "bearer",
2202
+ type: "http"
2203
+ }
2204
+ ],
2205
+ url: "/api/v1/projects/{name}/gbp/attributes",
2206
+ ...options
2207
+ });
2208
+ };
2197
2209
  var getApiV1ProjectsByNameGbpPlaces = (options) => {
2198
2210
  return (options.client ?? client).get({
2199
2211
  security: [
@@ -4353,6 +4365,15 @@ var ApiClient = class {
4353
4365
  })
4354
4366
  );
4355
4367
  }
4368
+ async listGbpAttributes(project, opts) {
4369
+ return this.invoke(
4370
+ () => getApiV1ProjectsByNameGbpAttributes({
4371
+ client: this.heyClient,
4372
+ path: { name: project },
4373
+ query: opts?.locationName ? { locationName: opts.locationName } : void 0
4374
+ })
4375
+ );
4376
+ }
4356
4377
  async listGbpPlaces(project, opts) {
4357
4378
  return this.invoke(
4358
4379
  () => getApiV1ProjectsByNameGbpPlaces({
@@ -5003,7 +5024,10 @@ var ApiClient = class {
5003
5024
  path: { name: project },
5004
5025
  query: {
5005
5026
  dismissed: opts?.dismissed ? "true" : void 0,
5006
- runId: opts?.runId
5027
+ runId: opts?.runId,
5028
+ type: opts?.type,
5029
+ severity: opts?.severity,
5030
+ limit: opts?.limit !== void 0 ? String(opts.limit) : void 0
5007
5031
  }
5008
5032
  })
5009
5033
  );
@@ -6299,6 +6323,17 @@ var canonryMcpTools = [
6299
6323
  openApiOperations: ["GET /api/v1/projects/{name}/gbp/lodging"],
6300
6324
  handler: (client2, input) => client2.listGbpLodging(input.project, compactStringParams(input, ["locationName"]))
6301
6325
  }),
6326
+ defineTool({
6327
+ name: "canonry_gbp_attributes",
6328
+ title: "Get GBP owner-set attributes",
6329
+ description: "List the latest Google Business Profile owner-set attribute snapshot per location: the generic amenity / service / accessibility / identity / social-URL tags the owner has set (e.g. has_onsite_services, offers_online_estimates, is_owned_by_women, wheelchair accessibility, url_instagram). Works for every business category, unlike canonry_gbp_lodging (hotels only). Returns only attributes the owner has set, so attributeCount is a real, owner-readable completeness signal (not a verify-nudge).",
6330
+ access: "read",
6331
+ tier: "gbp",
6332
+ inputSchema: gbpLocationScopedInputSchema,
6333
+ annotations: readAnnotations(),
6334
+ openApiOperations: ["GET /api/v1/projects/{name}/gbp/attributes"],
6335
+ handler: (client2, input) => client2.listGbpAttributes(input.project, compactStringParams(input, ["locationName"]))
6336
+ }),
6302
6337
  defineTool({
6303
6338
  name: "canonry_gbp_places",
6304
6339
  title: "Get GBP Places rendered-listing data",
@@ -1005,6 +1005,24 @@ var gbpLodgingListResponseSchema = z8.object({
1005
1005
  lodging: z8.array(gbpLodgingDtoSchema),
1006
1006
  total: z8.number().int().nonnegative()
1007
1007
  });
1008
+ var gbpAttributeDtoSchema = z8.object({
1009
+ name: z8.string(),
1010
+ valueType: z8.string(),
1011
+ values: z8.array(z8.union([z8.boolean(), z8.string()])),
1012
+ unsetValues: z8.array(z8.string()),
1013
+ uris: z8.array(z8.string())
1014
+ });
1015
+ var gbpAttributesDtoSchema = z8.object({
1016
+ locationName: z8.string(),
1017
+ /** Count of owner-set attributes (getAttributes returns only set ones). */
1018
+ attributeCount: z8.number().int().nonnegative(),
1019
+ syncedAt: z8.string(),
1020
+ attributes: z8.array(gbpAttributeDtoSchema)
1021
+ });
1022
+ var gbpAttributesListResponseSchema = z8.object({
1023
+ attributes: z8.array(gbpAttributesDtoSchema),
1024
+ total: z8.number().int().nonnegative()
1025
+ });
1008
1026
  var gbpPlaceDetailsDtoSchema = z8.object({
1009
1027
  locationName: z8.string(),
1010
1028
  placeId: z8.string(),
@@ -5344,6 +5362,7 @@ export {
5344
5362
  gbpKeywordImpressionListResponseSchema,
5345
5363
  gbpPlaceActionListResponseSchema,
5346
5364
  gbpLodgingListResponseSchema,
5365
+ gbpAttributesListResponseSchema,
5347
5366
  gbpPlaceDetailsListResponseSchema,
5348
5367
  gbpSummaryDtoSchema,
5349
5368
  formatGbpMetricLabel,