@ainyc/canonry 4.88.0 → 4.90.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 -2
- package/assets/agent-workspace/skills/canonry/SKILL.md +1 -1
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +20 -2
- package/assets/agent-workspace/skills/canonry/references/google-business-profile.md +11 -11
- package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +4 -1
- package/assets/assets/{BacklinksPage-Dpx6ylmU.js → BacklinksPage-CzZbUMMc.js} +1 -1
- package/assets/assets/{ChartPrimitives-CG5EGu62.js → ChartPrimitives-BBcVCGyK.js} +1 -1
- package/assets/assets/{ProjectPage-B_xnYE7W.js → ProjectPage-BZwzpRBL.js} +1 -1
- package/assets/assets/{RunRow-C0V4vf-u.js → RunRow-B-Xzc9el.js} +1 -1
- package/assets/assets/{RunsPage-9gWTjteG.js → RunsPage-idZt03Qs.js} +1 -1
- package/assets/assets/{SettingsPage-kBwz4Glg.js → SettingsPage-BGMJxf9N.js} +1 -1
- package/assets/assets/{TrafficPage-D4adjs8S.js → TrafficPage-UpFu1vki.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-CmURRHaU.js → TrafficSourceDetailPage-m0OEVrkk.js} +1 -1
- package/assets/assets/{arrow-left-DJ0-oFbd.js → arrow-left-Cgp2_UfR.js} +1 -1
- package/assets/assets/{extract-error-message-DfBzETgi.js → extract-error-message-BiiNM-57.js} +1 -1
- package/assets/assets/{index-BeuGfMy8.js → index-DPPOgIIL.js} +92 -92
- package/assets/assets/index-fLUYE9Z5.css +1 -0
- package/assets/assets/{trash-2-Bjf6mCqw.js → trash-2-Bm_IgAE2.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-2VEFNAQ3.js → chunk-I4FR66PU.js} +52 -7
- package/dist/{chunk-WMZJO56S.js → chunk-KO3T36E5.js} +80 -9
- package/dist/{chunk-JZ67YCHT.js → chunk-PL2ODHUC.js} +77 -1
- package/dist/{chunk-RH3QYFX3.js → chunk-XG7JKKTC.js} +2 -2
- package/dist/cli.js +36 -12
- package/dist/index.d.ts +9 -1
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-WTEEV46F.js → intelligence-service-DZK4FOAJ.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +7 -7
- package/assets/assets/index-ClkRAeHL.css +0 -1
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
loadConfig,
|
|
10
10
|
loadConfigRaw,
|
|
11
11
|
saveConfigPatch
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-XG7JKKTC.js";
|
|
13
13
|
import {
|
|
14
14
|
CC_CACHE_DIR,
|
|
15
15
|
DUCKDB_SPEC,
|
|
@@ -104,7 +104,7 @@ import {
|
|
|
104
104
|
siteAuditPages,
|
|
105
105
|
siteAuditSnapshots,
|
|
106
106
|
usageCounters
|
|
107
|
-
} from "./chunk-
|
|
107
|
+
} from "./chunk-KO3T36E5.js";
|
|
108
108
|
import {
|
|
109
109
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
110
110
|
AGENT_PROVIDER_IDS,
|
|
@@ -136,6 +136,7 @@ import {
|
|
|
136
136
|
agentMemoryUpsertRequestSchema,
|
|
137
137
|
authInvalid,
|
|
138
138
|
authRequired,
|
|
139
|
+
buildEmbedClientConfig,
|
|
139
140
|
buildRunErrorFromMessages,
|
|
140
141
|
classifySkillFile,
|
|
141
142
|
coerceSkillManifest,
|
|
@@ -147,20 +148,23 @@ import {
|
|
|
147
148
|
effectiveBrandNames,
|
|
148
149
|
effectiveDomains,
|
|
149
150
|
factorStatusFromScore,
|
|
151
|
+
frameAncestorsHeaderValue,
|
|
150
152
|
hostOf,
|
|
151
153
|
isAgentProviderId,
|
|
152
154
|
isBrowserProvider,
|
|
153
155
|
isRetryableHttpError,
|
|
154
156
|
normalizeUrlPath,
|
|
155
157
|
notFound,
|
|
158
|
+
parseOriginList,
|
|
156
159
|
providerError,
|
|
157
160
|
resolveSnapshotRequestQueries,
|
|
158
161
|
serializeRunError,
|
|
159
162
|
skillsClientSchema,
|
|
163
|
+
splitList,
|
|
160
164
|
validationError,
|
|
161
165
|
winnabilityClassLabel,
|
|
162
166
|
withRetry
|
|
163
|
-
} from "./chunk-
|
|
167
|
+
} from "./chunk-PL2ODHUC.js";
|
|
164
168
|
|
|
165
169
|
// src/telemetry.ts
|
|
166
170
|
import crypto from "crypto";
|
|
@@ -2728,6 +2732,34 @@ var perplexityAdapter = {
|
|
|
2728
2732
|
}
|
|
2729
2733
|
};
|
|
2730
2734
|
|
|
2735
|
+
// src/embed.ts
|
|
2736
|
+
function resolveEmbedConfig(env, config) {
|
|
2737
|
+
const embed = config.embed;
|
|
2738
|
+
const rawEnabled = env.CANONRY_EMBED?.trim();
|
|
2739
|
+
const enabled = rawEnabled ? rawEnabled === "1" || rawEnabled.toLowerCase() === "true" : embed?.enabled === true;
|
|
2740
|
+
const allowedOrigins = env.CANONRY_EMBED_ORIGINS !== void 0 ? parseOriginList(env.CANONRY_EMBED_ORIGINS) : parseOriginList(embed?.allowOrigins);
|
|
2741
|
+
const rawViews = env.CANONRY_EMBED_VIEWS !== void 0 ? splitList(env.CANONRY_EMBED_VIEWS) : splitList(embed?.views);
|
|
2742
|
+
const views = normalizeViews(rawViews);
|
|
2743
|
+
return {
|
|
2744
|
+
enabled,
|
|
2745
|
+
allowedOrigins,
|
|
2746
|
+
...views ? { views } : {},
|
|
2747
|
+
...embed?.theme ? { theme: embed.theme } : {}
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
function normalizeViews(raw) {
|
|
2751
|
+
if (raw.length === 0) return void 0;
|
|
2752
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2753
|
+
const out = [];
|
|
2754
|
+
for (const view of raw) {
|
|
2755
|
+
const id = view.toLowerCase();
|
|
2756
|
+
if (seen.has(id)) continue;
|
|
2757
|
+
seen.add(id);
|
|
2758
|
+
out.push(id);
|
|
2759
|
+
}
|
|
2760
|
+
return out.length > 0 ? out : void 0;
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2731
2763
|
// src/places-config.ts
|
|
2732
2764
|
var DEFAULT_TIER = "atmosphere";
|
|
2733
2765
|
var DEFAULT_REFRESH_INTERVAL_DAYS = 7;
|
|
@@ -4035,6 +4067,8 @@ async function executeGbpSync(db, runId, projectId, opts) {
|
|
|
4035
4067
|
syncedAt: insertNow,
|
|
4036
4068
|
syncRunId: runId
|
|
4037
4069
|
}).run();
|
|
4070
|
+
} else if (lodging !== null && latestLodging) {
|
|
4071
|
+
tx.update(gbpLodgingSnapshots).set({ syncedAt: insertNow, syncRunId: runId }).where(eq3(gbpLodgingSnapshots.id, latestLodging.id)).run();
|
|
4038
4072
|
}
|
|
4039
4073
|
if (placeToWrite) {
|
|
4040
4074
|
tx.insert(gbpPlaceDetails).values({
|
|
@@ -6289,7 +6323,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
6289
6323
|
return result;
|
|
6290
6324
|
}
|
|
6291
6325
|
async function backfillInsightsCommand(project, opts) {
|
|
6292
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
6326
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-DZK4FOAJ.js");
|
|
6293
6327
|
const config = loadConfig();
|
|
6294
6328
|
const db = createClient(config.database);
|
|
6295
6329
|
migrate(db);
|
|
@@ -10538,6 +10572,8 @@ async function createServer(opts) {
|
|
|
10538
10572
|
const rawBasePath = process.env.CANONRY_BASE_PATH ?? opts.config.basePath;
|
|
10539
10573
|
const normalizedBasePath = rawBasePath ? "/" + rawBasePath.replace(/^\//, "").replace(/\/?$/, "/") : void 0;
|
|
10540
10574
|
const basePath = normalizedBasePath === "/" ? void 0 : normalizedBasePath;
|
|
10575
|
+
const embed = resolveEmbedConfig(process.env, opts.config);
|
|
10576
|
+
const embedCsp = embed.enabled ? frameAncestorsHeaderValue(embed.allowedOrigins) : void 0;
|
|
10541
10577
|
const apiPrefix = basePath ? `${basePath}api/v1` : "/api/v1";
|
|
10542
10578
|
if (opts.config.apiKey) {
|
|
10543
10579
|
const keyHash = hashApiKey(opts.config.apiKey);
|
|
@@ -11173,16 +11209,25 @@ async function createServer(opts) {
|
|
|
11173
11209
|
}
|
|
11174
11210
|
});
|
|
11175
11211
|
const dirname = path9.dirname(fileURLToPath3(import.meta.url));
|
|
11176
|
-
const assetsDir = path9.join(dirname, "..", "assets");
|
|
11212
|
+
const assetsDir = opts.assetsDir ?? path9.join(dirname, "..", "assets");
|
|
11177
11213
|
if (fs8.existsSync(assetsDir)) {
|
|
11178
11214
|
const indexPath = path9.join(assetsDir, "index.html");
|
|
11179
11215
|
const injectConfig = (html) => {
|
|
11180
11216
|
const clientConfig = {};
|
|
11181
11217
|
if (basePath) clientConfig.basePath = basePath;
|
|
11218
|
+
if (embed.enabled) {
|
|
11219
|
+
const embedClient = buildEmbedClientConfig(embed);
|
|
11220
|
+
if (embedClient) clientConfig.embed = embedClient;
|
|
11221
|
+
}
|
|
11182
11222
|
const configScript = `<script>window.__CANONRY_CONFIG__=${JSON.stringify(clientConfig)}</script>`;
|
|
11183
11223
|
const baseTag = `<base href="${basePath ?? "/"}">`;
|
|
11184
11224
|
return html.replace("<head>", `<head>${baseTag}`).replace("</head>", `${configScript}</head>`);
|
|
11185
11225
|
};
|
|
11226
|
+
const sendSpaDocument = (reply, html) => {
|
|
11227
|
+
reply.header("Cache-Control", "no-cache, must-revalidate");
|
|
11228
|
+
if (embedCsp) reply.header("Content-Security-Policy", embedCsp);
|
|
11229
|
+
return reply.type("text/html").send(injectConfig(html));
|
|
11230
|
+
};
|
|
11186
11231
|
const fastifyStatic = await import("@fastify/static");
|
|
11187
11232
|
await app.register(fastifyStatic.default, {
|
|
11188
11233
|
root: assetsDir,
|
|
@@ -11209,7 +11254,7 @@ async function createServer(opts) {
|
|
|
11209
11254
|
const serveIndex = (_request, reply) => {
|
|
11210
11255
|
if (fs8.existsSync(indexPath)) {
|
|
11211
11256
|
const html = fs8.readFileSync(indexPath, "utf-8");
|
|
11212
|
-
return reply
|
|
11257
|
+
return sendSpaDocument(reply, html);
|
|
11213
11258
|
}
|
|
11214
11259
|
return reply.status(404).send({ error: "Dashboard not built" });
|
|
11215
11260
|
};
|
|
@@ -11230,7 +11275,7 @@ async function createServer(opts) {
|
|
|
11230
11275
|
}
|
|
11231
11276
|
if (fs8.existsSync(indexPath)) {
|
|
11232
11277
|
const html = fs8.readFileSync(indexPath, "utf-8");
|
|
11233
|
-
return reply
|
|
11278
|
+
return sendSpaDocument(reply, html);
|
|
11234
11279
|
}
|
|
11235
11280
|
return reply.status(404).send({ error: "Not found" });
|
|
11236
11281
|
});
|
|
@@ -258,7 +258,7 @@ import {
|
|
|
258
258
|
wordpressSchemaDeployResultDtoSchema,
|
|
259
259
|
wordpressSchemaStatusResultDtoSchema,
|
|
260
260
|
wordpressStatusDtoSchema
|
|
261
|
-
} from "./chunk-
|
|
261
|
+
} from "./chunk-PL2ODHUC.js";
|
|
262
262
|
|
|
263
263
|
// src/intelligence-service.ts
|
|
264
264
|
import { eq as eq37, desc as desc18, asc as asc5, and as and27, ne as ne5, or as or5, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
|
|
@@ -1170,6 +1170,16 @@ var gbpLocations = sqliteTable("gbp_locations", {
|
|
|
1170
1170
|
// rendered-listing data. Null when Google has not assigned a Place ID.
|
|
1171
1171
|
placeId: text("place_id"),
|
|
1172
1172
|
mapsUri: text("maps_uri"),
|
|
1173
|
+
// Owner-authored profile content from the Business Information v1 Location
|
|
1174
|
+
// resource — the entity-anchor + qualifier signals AI answer engines weight.
|
|
1175
|
+
// `serviceArea` / `regularHours` are stored verbatim (presence + raw shape).
|
|
1176
|
+
additionalCategories: text("additional_categories", { mode: "json" }).$type(),
|
|
1177
|
+
description: text("description"),
|
|
1178
|
+
serviceArea: text("service_area", { mode: "json" }).$type(),
|
|
1179
|
+
regularHours: text("regular_hours", { mode: "json" }).$type(),
|
|
1180
|
+
primaryPhone: text("primary_phone"),
|
|
1181
|
+
openStatus: text("open_status"),
|
|
1182
|
+
openingDate: text("opening_date"),
|
|
1173
1183
|
selected: integer("selected", { mode: "boolean" }).notNull().default(true),
|
|
1174
1184
|
syncedAt: text("synced_at"),
|
|
1175
1185
|
createdAt: text("created_at").notNull(),
|
|
@@ -3134,6 +3144,28 @@ var MIGRATION_VERSIONS = [
|
|
|
3134
3144
|
db.run(sql.raw(`ALTER TABLE health_snapshots ADD COLUMN mentioned_pairs INTEGER`));
|
|
3135
3145
|
}
|
|
3136
3146
|
}
|
|
3147
|
+
},
|
|
3148
|
+
{
|
|
3149
|
+
version: 81,
|
|
3150
|
+
name: "gbp-locations-owner-content",
|
|
3151
|
+
statements: [],
|
|
3152
|
+
run: (db) => {
|
|
3153
|
+
if (!tableExists(db, "gbp_locations")) return;
|
|
3154
|
+
const cols = [
|
|
3155
|
+
"additional_categories",
|
|
3156
|
+
"description",
|
|
3157
|
+
"service_area",
|
|
3158
|
+
"regular_hours",
|
|
3159
|
+
"primary_phone",
|
|
3160
|
+
"open_status",
|
|
3161
|
+
"opening_date"
|
|
3162
|
+
];
|
|
3163
|
+
for (const col of cols) {
|
|
3164
|
+
if (!columnExists(db, "gbp_locations", col)) {
|
|
3165
|
+
db.run(sql.raw(`ALTER TABLE gbp_locations ADD COLUMN ${col} TEXT`));
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3137
3169
|
}
|
|
3138
3170
|
];
|
|
3139
3171
|
function rebuildBacklinkTableWithSource(tx, table) {
|
|
@@ -5217,22 +5249,22 @@ function analyzeGbp(signals) {
|
|
|
5217
5249
|
drafts.push({
|
|
5218
5250
|
...base,
|
|
5219
5251
|
type: "gbp-listing-discrepancy",
|
|
5220
|
-
severity: "
|
|
5221
|
-
title: `${loc.displayName}: public listing
|
|
5252
|
+
severity: "medium",
|
|
5253
|
+
title: `${loc.displayName}: public listing advertises ${loc.placesAmenities.length} amenit${loc.placesAmenities.length === 1 ? "y" : "ies"} canonry can\u2019t confirm via the GBP API`,
|
|
5222
5254
|
recommendation: {
|
|
5223
|
-
action:
|
|
5224
|
-
reason: `Google\u2019s rendered listing advertises ${amenityList}
|
|
5255
|
+
action: 'Verify these amenities are set in the Google Business Profile "Hotel details" panel so the structured profile matches what the public listing advertises.',
|
|
5256
|
+
reason: `Google\u2019s rendered listing advertises ${amenityList}, but the GBP Lodging API returns no structured attributes for this location. The Lodging API does not expose the owner-set "Hotel details" amenity panel, so the amenities may already be set there and simply not be readable via the API. Verify in Hotel details: if any are missing, add them, since the structured attributes are the amenity data you directly control and that AI answer engines cite.`
|
|
5225
5257
|
}
|
|
5226
5258
|
});
|
|
5227
5259
|
} else {
|
|
5228
5260
|
drafts.push({
|
|
5229
5261
|
...base,
|
|
5230
5262
|
type: "gbp-lodging-gap",
|
|
5231
|
-
severity: "
|
|
5232
|
-
title: `${loc.displayName}: lodging
|
|
5263
|
+
severity: "low",
|
|
5264
|
+
title: `${loc.displayName}: structured lodging attributes not readable via the GBP API`,
|
|
5233
5265
|
recommendation: {
|
|
5234
|
-
action:
|
|
5235
|
-
reason:
|
|
5266
|
+
action: 'Verify the hotel amenities in the Google Business Profile "Hotel details" panel. If they are already set there, no change is needed.',
|
|
5267
|
+
reason: 'The GBP Lodging API returns no structured attributes for this location. That resource is commonly empty even for complete hotels, because the owner-set "Hotel details" amenity panel (breakfast, wifi, parking, accessibility, and the like) writes to a separate surface the Lodging API does not expose. Treat this as a verify, not a confirmed gap: confirm the amenities are set in Hotel details, the amenity source you directly control and that AI answer engines cite.'
|
|
5236
5268
|
}
|
|
5237
5269
|
});
|
|
5238
5270
|
}
|
|
@@ -19710,6 +19742,15 @@ var GBP_LOCATIONS_DEFAULT_READ_MASK = [
|
|
|
19710
19742
|
"storefrontAddress",
|
|
19711
19743
|
"websiteUri",
|
|
19712
19744
|
"categories.primaryCategory.displayName",
|
|
19745
|
+
// Owner-authored profile content — the entity-anchor and qualifier signals AI
|
|
19746
|
+
// answer engines weight most. All ride the same Business Information v1
|
|
19747
|
+
// Location resource canonry already reads (no new auth / API enablement).
|
|
19748
|
+
"categories.additionalCategories.displayName",
|
|
19749
|
+
"profile.description",
|
|
19750
|
+
"serviceArea",
|
|
19751
|
+
"regularHours",
|
|
19752
|
+
"phoneNumbers",
|
|
19753
|
+
"openInfo",
|
|
19713
19754
|
"metadata.placeId",
|
|
19714
19755
|
"metadata.mapsUri"
|
|
19715
19756
|
].join(",");
|
|
@@ -19847,6 +19888,26 @@ function formatStorefrontAddress(loc) {
|
|
|
19847
19888
|
].filter((p) => Boolean(p));
|
|
19848
19889
|
return parts.length ? parts.join(", ") : null;
|
|
19849
19890
|
}
|
|
19891
|
+
function buildLocationProfileFields(loc) {
|
|
19892
|
+
return {
|
|
19893
|
+
additionalCategories: (loc.categories?.additionalCategories ?? []).map((c) => c.displayName).filter((n) => Boolean(n)),
|
|
19894
|
+
description: loc.profile?.description ?? null,
|
|
19895
|
+
serviceArea: loc.serviceArea ?? null,
|
|
19896
|
+
regularHours: loc.regularHours ?? null,
|
|
19897
|
+
primaryPhone: loc.phoneNumbers?.primaryPhone ?? null,
|
|
19898
|
+
openStatus: loc.openInfo?.status ?? null,
|
|
19899
|
+
openingDate: formatOpeningDate(loc.openInfo?.openingDate)
|
|
19900
|
+
};
|
|
19901
|
+
}
|
|
19902
|
+
function formatOpeningDate(date) {
|
|
19903
|
+
if (!date?.year) return null;
|
|
19904
|
+
let out = String(date.year);
|
|
19905
|
+
if (date.month) {
|
|
19906
|
+
out += `-${String(date.month).padStart(2, "0")}`;
|
|
19907
|
+
if (date.day) out += `-${String(date.day).padStart(2, "0")}`;
|
|
19908
|
+
}
|
|
19909
|
+
return out;
|
|
19910
|
+
}
|
|
19850
19911
|
|
|
19851
19912
|
// ../integration-google-business-profile/src/performance-client.ts
|
|
19852
19913
|
function pad2(n) {
|
|
@@ -20863,6 +20924,13 @@ async function googleRoutes(app, opts) {
|
|
|
20863
20924
|
websiteUri: row.websiteUri ?? null,
|
|
20864
20925
|
placeId: row.placeId ?? null,
|
|
20865
20926
|
mapsUri: row.mapsUri ?? null,
|
|
20927
|
+
additionalCategories: row.additionalCategories ?? [],
|
|
20928
|
+
description: row.description ?? null,
|
|
20929
|
+
serviceArea: row.serviceArea ?? null,
|
|
20930
|
+
regularHours: row.regularHours ?? null,
|
|
20931
|
+
primaryPhone: row.primaryPhone ?? null,
|
|
20932
|
+
openStatus: row.openStatus ?? null,
|
|
20933
|
+
openingDate: row.openingDate ?? null,
|
|
20866
20934
|
selected: Boolean(row.selected),
|
|
20867
20935
|
syncedAt: row.syncedAt ?? null,
|
|
20868
20936
|
createdAt: row.createdAt,
|
|
@@ -20958,6 +21026,7 @@ async function googleRoutes(app, opts) {
|
|
|
20958
21026
|
if (switching) clearGbpProjectData(tx, project.id);
|
|
20959
21027
|
for (const remote of remoteLocations) {
|
|
20960
21028
|
const existing = tx.select().from(gbpLocations).where(and14(eq21(gbpLocations.projectId, project.id), eq21(gbpLocations.locationName, remote.name))).get();
|
|
21029
|
+
const profile = buildLocationProfileFields(remote);
|
|
20961
21030
|
if (existing) {
|
|
20962
21031
|
tx.update(gbpLocations).set({
|
|
20963
21032
|
accountName,
|
|
@@ -20967,6 +21036,7 @@ async function googleRoutes(app, opts) {
|
|
|
20967
21036
|
websiteUri: remote.websiteUri ?? null,
|
|
20968
21037
|
placeId: remote.metadata?.placeId ?? null,
|
|
20969
21038
|
mapsUri: remote.metadata?.mapsUri ?? null,
|
|
21039
|
+
...profile,
|
|
20970
21040
|
updatedAt: now
|
|
20971
21041
|
}).where(eq21(gbpLocations.id, existing.id)).run();
|
|
20972
21042
|
} else {
|
|
@@ -20981,6 +21051,7 @@ async function googleRoutes(app, opts) {
|
|
|
20981
21051
|
websiteUri: remote.websiteUri ?? null,
|
|
20982
21052
|
placeId: remote.metadata?.placeId ?? null,
|
|
20983
21053
|
mapsUri: remote.metadata?.mapsUri ?? null,
|
|
21054
|
+
...profile,
|
|
20984
21055
|
selected: selectAllNew,
|
|
20985
21056
|
createdAt: now,
|
|
20986
21057
|
updatedAt: now
|
|
@@ -901,6 +901,17 @@ var gbpLocationDtoSchema = z8.object({
|
|
|
901
901
|
// the location is not on Maps). `placeId` is the join key to the Places API.
|
|
902
902
|
placeId: z8.string().nullable(),
|
|
903
903
|
mapsUri: z8.string().nullable(),
|
|
904
|
+
// Owner-authored profile content (Business Information v1 Location resource).
|
|
905
|
+
// These are the entity-anchor + qualifier signals AI answer engines weight:
|
|
906
|
+
// secondary categories, the business description, the service area + hours
|
|
907
|
+
// (stored verbatim as JSON), the primary phone, and the open state.
|
|
908
|
+
additionalCategories: z8.array(z8.string()),
|
|
909
|
+
description: z8.string().nullable(),
|
|
910
|
+
serviceArea: z8.record(z8.string(), z8.unknown()).nullable(),
|
|
911
|
+
regularHours: z8.record(z8.string(), z8.unknown()).nullable(),
|
|
912
|
+
primaryPhone: z8.string().nullable(),
|
|
913
|
+
openStatus: z8.string().nullable(),
|
|
914
|
+
openingDate: z8.string().nullable(),
|
|
904
915
|
selected: z8.boolean(),
|
|
905
916
|
syncedAt: z8.string().nullable(),
|
|
906
917
|
createdAt: z8.string(),
|
|
@@ -4827,6 +4838,7 @@ var TrafficPathClasses = {
|
|
|
4827
4838
|
other: "other"
|
|
4828
4839
|
};
|
|
4829
4840
|
var ROBOTS_BASENAMES = /* @__PURE__ */ new Set(["robots.txt", "llms.txt", "llms-full.txt"]);
|
|
4841
|
+
var INFRA_BASENAMES = /* @__PURE__ */ new Set(["xmlrpc.php", "wp-cron.php"]);
|
|
4830
4842
|
var ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4831
4843
|
"css",
|
|
4832
4844
|
"js",
|
|
@@ -4925,6 +4937,9 @@ function classifyTrafficPath(pathNormalized) {
|
|
|
4925
4937
|
if (ROBOTS_BASENAMES.has(basename)) return TrafficPathClasses.robots;
|
|
4926
4938
|
if (lower.endsWith(".xml") || lower.endsWith(".xml.gz")) return TrafficPathClasses.sitemap;
|
|
4927
4939
|
if (SITEMAP_BASENAME.test(basename)) return TrafficPathClasses.sitemap;
|
|
4940
|
+
if (INFRA_BASENAMES.has(basename)) return TrafficPathClasses.other;
|
|
4941
|
+
if (lower === "/wp-json" || lower.startsWith("/wp-json/")) return TrafficPathClasses.other;
|
|
4942
|
+
if (lower.endsWith("/feed") || lower.includes("/feed/")) return TrafficPathClasses.other;
|
|
4928
4943
|
const dot = basename.lastIndexOf(".");
|
|
4929
4944
|
const ext = dot > 0 ? basename.slice(dot + 1) : "";
|
|
4930
4945
|
if (ext) {
|
|
@@ -5183,6 +5198,63 @@ function escapeLikePattern(value) {
|
|
|
5183
5198
|
return value.replace(/[\\%_]/g, (match) => `\\${match}`);
|
|
5184
5199
|
}
|
|
5185
5200
|
|
|
5201
|
+
// ../contracts/src/embed.ts
|
|
5202
|
+
var SELF_TOKEN = "'self'";
|
|
5203
|
+
function normalizeFrameOrigin(raw) {
|
|
5204
|
+
const trimmed = raw.trim();
|
|
5205
|
+
if (!trimmed) return null;
|
|
5206
|
+
if (trimmed === SELF_TOKEN) return SELF_TOKEN;
|
|
5207
|
+
if (trimmed.includes("*")) return null;
|
|
5208
|
+
let url;
|
|
5209
|
+
try {
|
|
5210
|
+
url = new URL(trimmed);
|
|
5211
|
+
} catch {
|
|
5212
|
+
return null;
|
|
5213
|
+
}
|
|
5214
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
5215
|
+
if (url.username || url.password) return null;
|
|
5216
|
+
if (url.pathname && url.pathname !== "/" || url.search || url.hash) return null;
|
|
5217
|
+
if (!url.hostname) return null;
|
|
5218
|
+
return url.origin;
|
|
5219
|
+
}
|
|
5220
|
+
function splitList(raw) {
|
|
5221
|
+
if (raw === void 0) return [];
|
|
5222
|
+
const tokens = (Array.isArray(raw) ? raw : [raw]).flatMap(
|
|
5223
|
+
(entry) => String(entry).split(/[\s,]+/)
|
|
5224
|
+
);
|
|
5225
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5226
|
+
const out = [];
|
|
5227
|
+
for (const token of tokens) {
|
|
5228
|
+
const trimmed = token.trim();
|
|
5229
|
+
if (!trimmed || seen.has(trimmed)) continue;
|
|
5230
|
+
seen.add(trimmed);
|
|
5231
|
+
out.push(trimmed);
|
|
5232
|
+
}
|
|
5233
|
+
return out;
|
|
5234
|
+
}
|
|
5235
|
+
function parseOriginList(raw) {
|
|
5236
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5237
|
+
const out = [];
|
|
5238
|
+
for (const token of splitList(raw)) {
|
|
5239
|
+
const origin = normalizeFrameOrigin(token);
|
|
5240
|
+
if (!origin || seen.has(origin)) continue;
|
|
5241
|
+
seen.add(origin);
|
|
5242
|
+
out.push(origin);
|
|
5243
|
+
}
|
|
5244
|
+
return out;
|
|
5245
|
+
}
|
|
5246
|
+
function frameAncestorsHeaderValue(origins) {
|
|
5247
|
+
if (origins.length === 0) return "frame-ancestors 'none'";
|
|
5248
|
+
return `frame-ancestors ${origins.join(" ")}`;
|
|
5249
|
+
}
|
|
5250
|
+
function buildEmbedClientConfig(resolved) {
|
|
5251
|
+
if (!resolved.enabled) return void 0;
|
|
5252
|
+
const client = { enabled: true };
|
|
5253
|
+
if (resolved.views && resolved.views.length > 0) client.views = resolved.views;
|
|
5254
|
+
if (resolved.theme && Object.keys(resolved.theme).length > 0) client.theme = resolved.theme;
|
|
5255
|
+
return client;
|
|
5256
|
+
}
|
|
5257
|
+
|
|
5186
5258
|
export {
|
|
5187
5259
|
__export,
|
|
5188
5260
|
apiKeyDtoSchema,
|
|
@@ -5480,5 +5552,9 @@ export {
|
|
|
5480
5552
|
AI_PROVIDER_INFRA_DOMAINS,
|
|
5481
5553
|
escapeLikePattern,
|
|
5482
5554
|
READ_ONLY_SCOPE,
|
|
5483
|
-
isReadOnlyKey
|
|
5555
|
+
isReadOnlyKey,
|
|
5556
|
+
splitList,
|
|
5557
|
+
parseOriginList,
|
|
5558
|
+
frameAncestorsHeaderValue,
|
|
5559
|
+
buildEmbedClientConfig
|
|
5484
5560
|
};
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
trafficConnectVercelRequestSchema,
|
|
24
24
|
trafficConnectWordpressRequestSchema,
|
|
25
25
|
trafficEventKindSchema
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-PL2ODHUC.js";
|
|
27
27
|
|
|
28
28
|
// src/config.ts
|
|
29
29
|
import fs from "fs";
|
|
@@ -6291,7 +6291,7 @@ var canonryMcpTools = [
|
|
|
6291
6291
|
defineTool({
|
|
6292
6292
|
name: "canonry_gbp_lodging",
|
|
6293
6293
|
title: "Get GBP lodging attributes",
|
|
6294
|
-
description:
|
|
6294
|
+
description: 'List the latest Google Business Profile lodging snapshot per location (hotel structured attributes). populatedGroupCount=0 means the Lodging API returned no structured attributes, which is common even for complete hotels because the owner-set "Hotel details" amenity panel is a separate surface the API does not expose. Treat it as a "verify the Hotel details panel", not a confirmed gap.',
|
|
6295
6295
|
access: "read",
|
|
6296
6296
|
tier: "gbp",
|
|
6297
6297
|
inputSchema: gbpLocationScopedInputSchema,
|
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-I4FR66PU.js";
|
|
31
31
|
import {
|
|
32
32
|
CliError,
|
|
33
33
|
EXIT_SYSTEM_ERROR,
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
saveConfig,
|
|
45
45
|
saveConfigPatch,
|
|
46
46
|
usageError
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-XG7JKKTC.js";
|
|
48
48
|
import {
|
|
49
49
|
apiKeys,
|
|
50
50
|
createClient,
|
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
projects,
|
|
53
53
|
queries,
|
|
54
54
|
renderReportHtml
|
|
55
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-KO3T36E5.js";
|
|
56
56
|
import {
|
|
57
57
|
BacklinkSources,
|
|
58
58
|
CcReleaseSyncStatuses,
|
|
@@ -77,7 +77,7 @@ import {
|
|
|
77
77
|
providerQuotaPolicySchema,
|
|
78
78
|
resolveProviderInput,
|
|
79
79
|
winnabilityClassSchema
|
|
80
|
-
} from "./chunk-
|
|
80
|
+
} from "./chunk-PL2ODHUC.js";
|
|
81
81
|
|
|
82
82
|
// src/cli.ts
|
|
83
83
|
import { pathToFileURL } from "url";
|
|
@@ -3514,7 +3514,7 @@ async function gbpLodging(project, opts) {
|
|
|
3514
3514
|
}
|
|
3515
3515
|
console.log(`${response.total} lodging profile(s):`);
|
|
3516
3516
|
for (const l of response.lodging) {
|
|
3517
|
-
const note = l.populatedGroupCount === 0 ? "
|
|
3517
|
+
const note = l.populatedGroupCount === 0 ? ' (none readable via the Lodging API; verify the "Hotel details" panel, often set there but not exposed by the API)' : "";
|
|
3518
3518
|
console.log(` ${l.locationName} ${l.populatedGroupCount} attribute group(s)${note}`);
|
|
3519
3519
|
}
|
|
3520
3520
|
}
|
|
@@ -10299,6 +10299,18 @@ async function waitForReady(host, port, maxMs = 1e4) {
|
|
|
10299
10299
|
}
|
|
10300
10300
|
return false;
|
|
10301
10301
|
}
|
|
10302
|
+
function buildServeForwardArgs(opts) {
|
|
10303
|
+
const args = [];
|
|
10304
|
+
if (opts.port) args.push("--port", opts.port);
|
|
10305
|
+
if (opts.host) args.push("--host", opts.host);
|
|
10306
|
+
if (opts.basePath) args.push("--base-path", opts.basePath);
|
|
10307
|
+
if (opts.embed) {
|
|
10308
|
+
args.push("--embed");
|
|
10309
|
+
for (const origin of opts.embedAllowOrigins ?? []) args.push("--embed-allow-origin", origin);
|
|
10310
|
+
for (const view of opts.embedViews ?? []) args.push("--embed-view", view);
|
|
10311
|
+
}
|
|
10312
|
+
return args;
|
|
10313
|
+
}
|
|
10302
10314
|
async function startDaemon(opts) {
|
|
10303
10315
|
const pidPath = getPidPath();
|
|
10304
10316
|
const format = opts.format ?? "text";
|
|
@@ -10319,9 +10331,7 @@ async function startDaemon(opts) {
|
|
|
10319
10331
|
const cliPath = path7.resolve(new URL(import.meta.url).pathname);
|
|
10320
10332
|
const inSourceMode = new URL(import.meta.url).pathname.endsWith(".ts");
|
|
10321
10333
|
const args = inSourceMode ? ["--import", "tsx", cliPath, "serve"] : [cliPath, "serve"];
|
|
10322
|
-
|
|
10323
|
-
if (opts.host) args.push("--host", opts.host);
|
|
10324
|
-
if (opts.basePath) args.push("--base-path", opts.basePath);
|
|
10334
|
+
args.push(...buildServeForwardArgs(opts));
|
|
10325
10335
|
const child = spawn(process.execPath, args, {
|
|
10326
10336
|
detached: true,
|
|
10327
10337
|
stdio: "ignore"
|
|
@@ -11002,6 +11012,11 @@ function applyServerEnv(values) {
|
|
|
11002
11012
|
if (port) process.env.CANONRY_PORT = port;
|
|
11003
11013
|
if (host) process.env.CANONRY_HOST = host;
|
|
11004
11014
|
if (basePath) process.env.CANONRY_BASE_PATH = basePath;
|
|
11015
|
+
const embedOrigins = getStringArray(values, "embed-allow-origin");
|
|
11016
|
+
const embedViews = getStringArray(values, "embed-view");
|
|
11017
|
+
if (getBoolean(values, "embed")) process.env.CANONRY_EMBED = "1";
|
|
11018
|
+
if (embedOrigins && embedOrigins.length > 0) process.env.CANONRY_EMBED_ORIGINS = embedOrigins.join(",");
|
|
11019
|
+
if (embedViews && embedViews.length > 0) process.env.CANONRY_EMBED_VIEWS = embedViews.join(",");
|
|
11005
11020
|
}
|
|
11006
11021
|
var SYSTEM_CLI_COMMANDS = [
|
|
11007
11022
|
{
|
|
@@ -11058,11 +11073,14 @@ var SYSTEM_CLI_COMMANDS = [
|
|
|
11058
11073
|
},
|
|
11059
11074
|
{
|
|
11060
11075
|
path: ["serve"],
|
|
11061
|
-
usage: "canonry serve [--port <port>] [--host <host>] [--base-path <path>] [--format json]",
|
|
11076
|
+
usage: "canonry serve [--port <port>] [--host <host>] [--base-path <path>] [--embed] [--embed-allow-origin <origin>...] [--embed-view <view>...] [--format json]",
|
|
11062
11077
|
options: {
|
|
11063
11078
|
port: stringOption(),
|
|
11064
11079
|
host: stringOption(),
|
|
11065
|
-
"base-path": stringOption()
|
|
11080
|
+
"base-path": stringOption(),
|
|
11081
|
+
embed: { type: "boolean", default: false },
|
|
11082
|
+
"embed-allow-origin": multiStringOption(),
|
|
11083
|
+
"embed-view": multiStringOption()
|
|
11066
11084
|
},
|
|
11067
11085
|
allowPositionals: false,
|
|
11068
11086
|
run: async (input) => {
|
|
@@ -11072,11 +11090,14 @@ var SYSTEM_CLI_COMMANDS = [
|
|
|
11072
11090
|
},
|
|
11073
11091
|
{
|
|
11074
11092
|
path: ["start"],
|
|
11075
|
-
usage: "canonry start [--port <port>] [--host <host>] [--base-path <path>] [--format json]",
|
|
11093
|
+
usage: "canonry start [--port <port>] [--host <host>] [--base-path <path>] [--embed] [--embed-allow-origin <origin>...] [--embed-view <view>...] [--format json]",
|
|
11076
11094
|
options: {
|
|
11077
11095
|
port: stringOption(),
|
|
11078
11096
|
host: stringOption(),
|
|
11079
|
-
"base-path": stringOption()
|
|
11097
|
+
"base-path": stringOption(),
|
|
11098
|
+
embed: { type: "boolean", default: false },
|
|
11099
|
+
"embed-allow-origin": multiStringOption(),
|
|
11100
|
+
"embed-view": multiStringOption()
|
|
11080
11101
|
},
|
|
11081
11102
|
allowPositionals: false,
|
|
11082
11103
|
run: async (input) => {
|
|
@@ -11084,6 +11105,9 @@ var SYSTEM_CLI_COMMANDS = [
|
|
|
11084
11105
|
port: getString(input.values, "port"),
|
|
11085
11106
|
host: getString(input.values, "host"),
|
|
11086
11107
|
basePath: getString(input.values, "base-path"),
|
|
11108
|
+
embed: getBoolean(input.values, "embed"),
|
|
11109
|
+
embedAllowOrigins: getStringArray(input.values, "embed-allow-origin"),
|
|
11110
|
+
embedViews: getStringArray(input.values, "embed-view"),
|
|
11087
11111
|
format: input.format
|
|
11088
11112
|
});
|
|
11089
11113
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FastifyInstance } from 'fastify';
|
|
2
2
|
import { DatabaseClient } from '@ainyc/canonry-db';
|
|
3
|
-
import { ProviderQuotaPolicy } from '@ainyc/canonry-contracts';
|
|
3
|
+
import { ProviderQuotaPolicy, EmbedConfigEntry } from '@ainyc/canonry-contracts';
|
|
4
4
|
|
|
5
5
|
type GoogleConnectionType = 'gsc' | 'ga4' | 'gbp';
|
|
6
6
|
interface ProviderConfigEntry {
|
|
@@ -205,6 +205,7 @@ interface CanonryConfig {
|
|
|
205
205
|
lastKnownLatestVersion?: string;
|
|
206
206
|
agent?: AgentConfigEntry;
|
|
207
207
|
places?: PlacesConfigEntry;
|
|
208
|
+
embed?: EmbedConfigEntry;
|
|
208
209
|
}
|
|
209
210
|
declare function loadConfig(): CanonryConfig;
|
|
210
211
|
|
|
@@ -223,6 +224,13 @@ declare function createServer(opts: {
|
|
|
223
224
|
* Defaults to loopback when unset (programmatic/test callers).
|
|
224
225
|
*/
|
|
225
226
|
host?: string;
|
|
227
|
+
/**
|
|
228
|
+
* Override for the directory the pre-built SPA is served from. Defaults to
|
|
229
|
+
* the package's bundled `assets/` (resolved from `import.meta.url`). Exposed
|
|
230
|
+
* so tests can point at a temp dir containing a fixture `index.html` and
|
|
231
|
+
* assert the injected config + framing header on the served document.
|
|
232
|
+
*/
|
|
233
|
+
assetsDir?: string;
|
|
226
234
|
}): Promise<FastifyInstance>;
|
|
227
235
|
|
|
228
236
|
export { type CanonryConfig, createServer, loadConfig };
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-I4FR66PU.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-XG7JKKTC.js";
|
|
7
|
+
import "./chunk-KO3T36E5.js";
|
|
8
|
+
import "./chunk-PL2ODHUC.js";
|
|
9
9
|
export {
|
|
10
10
|
createServer,
|
|
11
11
|
loadConfig
|
package/dist/mcp.js
CHANGED
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
PACKAGE_VERSION,
|
|
4
4
|
canonryMcpTools,
|
|
5
5
|
createApiClient
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-XG7JKKTC.js";
|
|
7
7
|
import {
|
|
8
8
|
isReadOnlyKey
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-PL2ODHUC.js";
|
|
10
10
|
|
|
11
11
|
// src/mcp/cli.ts
|
|
12
12
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|