@ainyc/canonry 4.60.1 → 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.
- package/assets/agent-workspace/skills/aero/references/regression-playbook.md +2 -1
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +4 -0
- package/assets/agent-workspace/skills/canonry/references/google-business-profile.md +23 -2
- package/assets/assets/{BacklinksPage-Dj4AVTma.js → BacklinksPage-CsGOAPNN.js} +1 -1
- package/assets/assets/{ChartPrimitives-7SFwUlCh.js → ChartPrimitives-Bjow7aaC.js} +1 -1
- package/assets/assets/{ProjectPage-4tWuU1ZR.js → ProjectPage-BZoMD93_.js} +1 -1
- package/assets/assets/{RunRow-CgPJfmWX.js → RunRow-ve7H_XIu.js} +1 -1
- package/assets/assets/{RunsPage-Cdo4jBn4.js → RunsPage-mYmYevh0.js} +1 -1
- package/assets/assets/{SettingsPage-N5iccPoU.js → SettingsPage-DoRiIJK5.js} +1 -1
- package/assets/assets/{TrafficPage-pSlQPdDg.js → TrafficPage-1LFyX4OT.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-BQx3Evf6.js → TrafficSourceDetailPage-BYDJtQdO.js} +1 -1
- package/assets/assets/{extract-error-message-BOembgFV.js → extract-error-message-Bt6jcL_M.js} +1 -1
- package/assets/assets/{index-DjKFsFsl.js → index-BQxaYi-t.js} +70 -70
- package/assets/assets/{server-traffic-DNgNJ4Ht.js → server-traffic-C5f87b84.js} +1 -1
- package/assets/assets/{trash-2-DOznxxMW.js → trash-2-BFSmyr_7.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-CKWHFAVB.js → chunk-PITZUUFV.js} +33 -1
- package/dist/{chunk-3G3GAT3E.js → chunk-QZN3J35I.js} +489 -156
- package/dist/{chunk-DXWUBWBD.js → chunk-URPUUKLC.js} +22 -0
- package/dist/{chunk-X4ZZFZQZ.js → chunk-ZTVBTGDW.js} +217 -68
- package/dist/cli.js +32 -4
- package/dist/index.d.ts +16 -0
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-UYVVKQ2K.js → intelligence-service-RWVBWSB5.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +10 -9
|
@@ -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-
|
|
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-
|
|
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-
|
|
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 = {};
|
|
@@ -3542,6 +3560,71 @@ function buildPhases(input) {
|
|
|
3542
3560
|
// src/gsc-sync.ts
|
|
3543
3561
|
import crypto4 from "crypto";
|
|
3544
3562
|
import { eq as eq2, and as and2, sql as sql2 } from "drizzle-orm";
|
|
3563
|
+
|
|
3564
|
+
// src/gsc-inspect-paced.ts
|
|
3565
|
+
var INSPECT_BASE_DELAY_MS = 1e3;
|
|
3566
|
+
var INSPECT_PACING_JITTER_MS = 250;
|
|
3567
|
+
var INSPECT_MAX_RETRIES = 3;
|
|
3568
|
+
var INSPECT_MAX_BACKOFF_MS = 3e4;
|
|
3569
|
+
var INSPECT_FAILFAST_THRESHOLD = 5;
|
|
3570
|
+
function isRetryableGscInspectError(err) {
|
|
3571
|
+
if (err != null && typeof err === "object" && "status" in err) {
|
|
3572
|
+
if (err.status === 403) return true;
|
|
3573
|
+
}
|
|
3574
|
+
return isRetryableHttpError(err);
|
|
3575
|
+
}
|
|
3576
|
+
function defaultSleep(ms) {
|
|
3577
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3578
|
+
}
|
|
3579
|
+
async function inspectUrlsPaced(urls, cb, deps = {}) {
|
|
3580
|
+
const sleep3 = deps.sleep ?? defaultSleep;
|
|
3581
|
+
const jitter = deps.jitter ?? Math.random;
|
|
3582
|
+
let inspected = 0;
|
|
3583
|
+
let errors = 0;
|
|
3584
|
+
let consecutiveRetryableFailures = 0;
|
|
3585
|
+
for (let index = 0; index < urls.length; index++) {
|
|
3586
|
+
const url = urls[index];
|
|
3587
|
+
try {
|
|
3588
|
+
const result = await withRetry(() => cb.inspectOne(url), {
|
|
3589
|
+
maxRetries: INSPECT_MAX_RETRIES,
|
|
3590
|
+
baseDelayMs: INSPECT_BASE_DELAY_MS,
|
|
3591
|
+
maxDelayMs: INSPECT_MAX_BACKOFF_MS,
|
|
3592
|
+
isRetryable: isRetryableGscInspectError,
|
|
3593
|
+
sleep: sleep3,
|
|
3594
|
+
onRetry: ({ attempt, delayMs, err }) => deps.log?.info("inspect.retry", {
|
|
3595
|
+
url,
|
|
3596
|
+
attempt,
|
|
3597
|
+
delayMs: Math.round(delayMs),
|
|
3598
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3599
|
+
})
|
|
3600
|
+
});
|
|
3601
|
+
cb.onResult(url, result, index);
|
|
3602
|
+
inspected++;
|
|
3603
|
+
consecutiveRetryableFailures = 0;
|
|
3604
|
+
} catch (err) {
|
|
3605
|
+
errors++;
|
|
3606
|
+
cb.onError(url, err, index);
|
|
3607
|
+
if (isRetryableGscInspectError(err)) {
|
|
3608
|
+
consecutiveRetryableFailures++;
|
|
3609
|
+
if (consecutiveRetryableFailures >= INSPECT_FAILFAST_THRESHOLD) {
|
|
3610
|
+
deps.log?.error("inspect.circuit-break", {
|
|
3611
|
+
consecutiveFailures: consecutiveRetryableFailures,
|
|
3612
|
+
inspected,
|
|
3613
|
+
errors,
|
|
3614
|
+
remaining: urls.length - index - 1
|
|
3615
|
+
});
|
|
3616
|
+
return { inspected, errors, aborted: true, abortError: err };
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
if (index < urls.length - 1) {
|
|
3621
|
+
await sleep3(INSPECT_BASE_DELAY_MS + jitter() * INSPECT_PACING_JITTER_MS);
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
return { inspected, errors, aborted: false };
|
|
3625
|
+
}
|
|
3626
|
+
|
|
3627
|
+
// src/gsc-sync.ts
|
|
3545
3628
|
var log2 = createLogger("GscSync");
|
|
3546
3629
|
function formatDate(d) {
|
|
3547
3630
|
return d.toISOString().split("T")[0];
|
|
@@ -3570,6 +3653,7 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
3570
3653
|
if (!conn.propertyId) {
|
|
3571
3654
|
throw new Error('No GSC property selected. Use "canonry google properties" to list available sites, then set one with the API.');
|
|
3572
3655
|
}
|
|
3656
|
+
const propertyId = conn.propertyId;
|
|
3573
3657
|
let accessToken = conn.accessToken;
|
|
3574
3658
|
const expiresAt = conn.tokenExpiresAt ? new Date(conn.tokenExpiresAt).getTime() : 0;
|
|
3575
3659
|
if (Date.now() > expiresAt - 5 * 60 * 1e3) {
|
|
@@ -3631,35 +3715,53 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
3631
3715
|
}
|
|
3632
3716
|
const topPages = [...pageClicks.entries()].sort((a, b) => b[1] - a[1]).slice(0, 50).map(([page]) => page);
|
|
3633
3717
|
log2.info("inspect.start", { runId, projectId, urlCount: topPages.length });
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3718
|
+
const inspectOutcome = await inspectUrlsPaced(
|
|
3719
|
+
topPages,
|
|
3720
|
+
{
|
|
3721
|
+
inspectOne: (pageUrl) => inspectUrl(accessToken, pageUrl, propertyId),
|
|
3722
|
+
onResult: (pageUrl, result) => {
|
|
3723
|
+
const ir = result.inspectionResult;
|
|
3724
|
+
const idx = ir.indexStatusResult;
|
|
3725
|
+
const mob = ir.mobileUsabilityResult;
|
|
3726
|
+
const rich = ir.richResultsResult;
|
|
3727
|
+
const inspectedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3728
|
+
db.insert(gscUrlInspections).values({
|
|
3729
|
+
id: crypto4.randomUUID(),
|
|
3730
|
+
projectId,
|
|
3731
|
+
syncRunId: runId,
|
|
3732
|
+
url: pageUrl,
|
|
3733
|
+
indexingState: idx?.indexingState ?? null,
|
|
3734
|
+
verdict: idx?.verdict ?? null,
|
|
3735
|
+
coverageState: idx?.coverageState ?? null,
|
|
3736
|
+
pageFetchState: idx?.pageFetchState ?? null,
|
|
3737
|
+
robotsTxtState: idx?.robotsTxtState ?? null,
|
|
3738
|
+
crawlTime: idx?.lastCrawlTime ?? null,
|
|
3739
|
+
lastCrawlResult: idx?.crawlResult ?? null,
|
|
3740
|
+
isMobileFriendly: mob?.verdict === "PASS" ? true : mob?.verdict === "FAIL" ? false : null,
|
|
3741
|
+
richResults: rich?.detectedItems?.map((d) => d.richResultType) ?? [],
|
|
3742
|
+
referringUrls: idx?.referringUrls ?? [],
|
|
3743
|
+
inspectedAt,
|
|
3744
|
+
createdAt: inspectedAt
|
|
3745
|
+
}).run();
|
|
3746
|
+
},
|
|
3747
|
+
onError: (pageUrl, err) => {
|
|
3748
|
+
log2.error("inspect.url-failed", { runId, projectId, url: pageUrl, error: err instanceof Error ? err.message : String(err) });
|
|
3749
|
+
}
|
|
3750
|
+
},
|
|
3751
|
+
{
|
|
3752
|
+
log: {
|
|
3753
|
+
info: (action, ctx) => log2.info(action, { runId, projectId, ...ctx }),
|
|
3754
|
+
error: (action, ctx) => log2.error(action, { runId, projectId, ...ctx })
|
|
3755
|
+
}
|
|
3662
3756
|
}
|
|
3757
|
+
);
|
|
3758
|
+
if (inspectOutcome.aborted) {
|
|
3759
|
+
log2.error("inspect.stopped-early", {
|
|
3760
|
+
runId,
|
|
3761
|
+
projectId,
|
|
3762
|
+
inspected: inspectOutcome.inspected,
|
|
3763
|
+
note: "URL inspection stopped early after sustained rate/access failures; search-analytics data was still saved"
|
|
3764
|
+
});
|
|
3663
3765
|
}
|
|
3664
3766
|
const allInspections = db.select().from(gscUrlInspections).where(eq2(gscUrlInspections.projectId, projectId)).all();
|
|
3665
3767
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
@@ -3706,6 +3808,7 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
3706
3808
|
// src/gbp-sync.ts
|
|
3707
3809
|
import crypto5 from "crypto";
|
|
3708
3810
|
import { eq as eq3, and as and3, desc, inArray as inArray2, lt } from "drizzle-orm";
|
|
3811
|
+
var MS_PER_DAY = 864e5;
|
|
3709
3812
|
var log3 = createLogger("GbpSync");
|
|
3710
3813
|
var LOCATION_CONCURRENCY = 4;
|
|
3711
3814
|
var DEFAULT_DAYS_OF_METRICS = 30;
|
|
@@ -3765,6 +3868,7 @@ async function executeGbpSync(db, runId, projectId, opts) {
|
|
|
3765
3868
|
const keywordsEnd = monthMinus(0);
|
|
3766
3869
|
const trendMonths = Array.from({ length: KEYWORD_TREND_MONTHS }, (_, i) => monthMinus(i + 1));
|
|
3767
3870
|
const keywordRetentionCutoff = monthKey(monthMinus(KEYWORD_HISTORY_RETENTION_MONTHS));
|
|
3871
|
+
const { apiKey: placesApiKey, tier: placesTier, refreshIntervalDays: placesRefreshDays } = getPlacesConfig(opts.config);
|
|
3768
3872
|
log3.info("sync.start", { runId, projectId, locations: locationRows.length, daysOfMetrics, monthsOfKeywords });
|
|
3769
3873
|
const errors = /* @__PURE__ */ new Map();
|
|
3770
3874
|
let okCount = 0;
|
|
@@ -3796,6 +3900,25 @@ async function executeGbpSync(db, runId, projectId, opts) {
|
|
|
3796
3900
|
const lodgingHash = lodging ? hashLodging(lodging) : null;
|
|
3797
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;
|
|
3798
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
|
+
}
|
|
3799
3922
|
const insertNow = (/* @__PURE__ */ new Date()).toISOString();
|
|
3800
3923
|
db.transaction((tx) => {
|
|
3801
3924
|
tx.delete(gbpDailyMetrics).where(and3(eq3(gbpDailyMetrics.projectId, projectId), eq3(gbpDailyMetrics.locationName, loc.locationName))).run();
|
|
@@ -3878,6 +4001,21 @@ async function executeGbpSync(db, runId, projectId, opts) {
|
|
|
3878
4001
|
syncRunId: runId
|
|
3879
4002
|
}).run();
|
|
3880
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
|
+
}
|
|
3881
4019
|
tx.update(gbpLocations).set({ syncedAt: insertNow, updatedAt: insertNow }).where(eq3(gbpLocations.id, loc.id)).run();
|
|
3882
4020
|
});
|
|
3883
4021
|
okCount++;
|
|
@@ -4057,6 +4195,7 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
|
|
|
4057
4195
|
if (!conn.propertyId) {
|
|
4058
4196
|
throw new Error('No GSC property selected. Use "canonry google properties" to list available sites, then set one.');
|
|
4059
4197
|
}
|
|
4198
|
+
const propertyId = conn.propertyId;
|
|
4060
4199
|
let accessToken = conn.accessToken;
|
|
4061
4200
|
const expiresAt = conn.tokenExpiresAt ? new Date(conn.tokenExpiresAt).getTime() : 0;
|
|
4062
4201
|
if (Date.now() > expiresAt - 5 * 60 * 1e3) {
|
|
@@ -4076,43 +4215,52 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
|
|
|
4076
4215
|
if (urls.length === 0) {
|
|
4077
4216
|
throw new Error("No URLs found in sitemap");
|
|
4078
4217
|
}
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
}
|
|
4113
|
-
|
|
4114
|
-
|
|
4218
|
+
const { inspected, errors, aborted, abortError } = await inspectUrlsPaced(
|
|
4219
|
+
urls,
|
|
4220
|
+
{
|
|
4221
|
+
inspectOne: (pageUrl) => inspectUrl(accessToken, pageUrl, propertyId),
|
|
4222
|
+
onResult: (pageUrl, result, index) => {
|
|
4223
|
+
const ir = result.inspectionResult;
|
|
4224
|
+
const idx = ir.indexStatusResult;
|
|
4225
|
+
const mob = ir.mobileUsabilityResult;
|
|
4226
|
+
const rich = ir.richResultsResult;
|
|
4227
|
+
const inspectedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4228
|
+
db.insert(gscUrlInspections).values({
|
|
4229
|
+
id: crypto6.randomUUID(),
|
|
4230
|
+
projectId,
|
|
4231
|
+
syncRunId: runId,
|
|
4232
|
+
url: pageUrl,
|
|
4233
|
+
indexingState: idx?.indexingState ?? null,
|
|
4234
|
+
verdict: idx?.verdict ?? null,
|
|
4235
|
+
coverageState: idx?.coverageState ?? null,
|
|
4236
|
+
pageFetchState: idx?.pageFetchState ?? null,
|
|
4237
|
+
robotsTxtState: idx?.robotsTxtState ?? null,
|
|
4238
|
+
crawlTime: idx?.lastCrawlTime ?? null,
|
|
4239
|
+
lastCrawlResult: idx?.crawlResult ?? null,
|
|
4240
|
+
isMobileFriendly: mob?.verdict === "PASS" ? true : mob?.verdict === "FAIL" ? false : null,
|
|
4241
|
+
richResults: rich?.detectedItems?.map((d) => d.richResultType) ?? [],
|
|
4242
|
+
referringUrls: idx?.referringUrls ?? [],
|
|
4243
|
+
inspectedAt,
|
|
4244
|
+
createdAt: inspectedAt
|
|
4245
|
+
}).run();
|
|
4246
|
+
log5.info("inspect.url-done", { runId, projectId, url: pageUrl, progress: `${index + 1}/${urls.length}` });
|
|
4247
|
+
},
|
|
4248
|
+
onError: (pageUrl, err) => {
|
|
4249
|
+
log5.error("inspect.url-failed", { runId, projectId, url: pageUrl, error: err instanceof Error ? err.message : String(err) });
|
|
4250
|
+
}
|
|
4251
|
+
},
|
|
4252
|
+
{
|
|
4253
|
+
log: {
|
|
4254
|
+
info: (action, ctx) => log5.info(action, { runId, projectId, ...ctx }),
|
|
4255
|
+
error: (action, ctx) => log5.error(action, { runId, projectId, ...ctx })
|
|
4256
|
+
}
|
|
4115
4257
|
}
|
|
4258
|
+
);
|
|
4259
|
+
if (aborted) {
|
|
4260
|
+
const detail = abortError instanceof Error ? abortError.message : String(abortError);
|
|
4261
|
+
throw new Error(
|
|
4262
|
+
`URL inspection aborted after ${INSPECT_FAILFAST_THRESHOLD} consecutive rate/access failures (likely GSC URL Inspection quota exhaustion or property access loss). Last error: ${detail}`
|
|
4263
|
+
);
|
|
4116
4264
|
}
|
|
4117
4265
|
const allInspections = db.select().from(gscUrlInspections).where(eq4(gscUrlInspections.projectId, projectId)).all();
|
|
4118
4266
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
@@ -5468,7 +5616,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
5468
5616
|
return result;
|
|
5469
5617
|
}
|
|
5470
5618
|
async function backfillInsightsCommand(project, opts) {
|
|
5471
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
5619
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-RWVBWSB5.js");
|
|
5472
5620
|
const config = loadConfig();
|
|
5473
5621
|
const db = createClient(config.database);
|
|
5474
5622
|
migrate(db);
|
|
@@ -9682,6 +9830,7 @@ async function createServer(opts) {
|
|
|
9682
9830
|
registerAgentRoutes(scope, { db: opts.db, sessionRegistry });
|
|
9683
9831
|
},
|
|
9684
9832
|
getGoogleAuthConfig: () => getGoogleAuthConfig(opts.config),
|
|
9833
|
+
getPlacesConfig: () => getPlacesConfig(opts.config),
|
|
9685
9834
|
googleConnectionStore,
|
|
9686
9835
|
googleStateSecret,
|
|
9687
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
3
|
+
} from "./chunk-ZTVBTGDW.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-PITZUUFV.js";
|
|
7
|
+
import "./chunk-QZN3J35I.js";
|
|
8
|
+
import "./chunk-URPUUKLC.js";
|
|
9
9
|
export {
|
|
10
10
|
createServer,
|
|
11
11
|
loadConfig
|
package/dist/mcp.js
CHANGED
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
PACKAGE_VERSION,
|
|
4
4
|
canonryMcpTools,
|
|
5
5
|
createApiClient
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
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.
|
|
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-client": "0.0.0",
|
|
65
|
-
"@ainyc/canonry-contracts": "0.0.0",
|
|
66
64
|
"@ainyc/canonry-config": "0.0.0",
|
|
67
|
-
"@ainyc/canonry-
|
|
65
|
+
"@ainyc/canonry-api-client": "0.0.0",
|
|
68
66
|
"@ainyc/canonry-api-routes": "0.0.0",
|
|
67
|
+
"@ainyc/canonry-db": "0.0.0",
|
|
69
68
|
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
69
|
+
"@ainyc/canonry-contracts": "0.0.0",
|
|
70
70
|
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
71
|
-
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
72
|
-
"@ainyc/canonry-integration-google": "0.0.0",
|
|
73
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",
|
|
74
75
|
"@ainyc/canonry-integration-traffic": "0.0.0",
|
|
75
|
-
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
76
76
|
"@ainyc/canonry-intelligence": "0.0.0",
|
|
77
77
|
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
78
78
|
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
79
|
+
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
79
80
|
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
80
|
-
"@ainyc/canonry-provider-local": "0.0.0",
|
|
81
81
|
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
82
|
-
"@ainyc/canonry-provider-perplexity": "0.0.0"
|
|
82
|
+
"@ainyc/canonry-provider-perplexity": "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",
|