@canonry/canonry 7.7.0 → 7.9.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/SKILL.md +43 -17
- package/assets/agent-workspace/skills/aero/references/agent-operations.md +4 -1
- package/assets/agent-workspace/skills/aero/references/portfolio-analysis.md +15 -7
- package/assets/agent-workspace/skills/canonry/SKILL.md +4 -1
- package/assets/assets/{AuditHistoryPanel-qumC-I56.js → AuditHistoryPanel-D0nukTwD.js} +1 -1
- package/assets/assets/{BacklinksPage-BNyYcY5w.js → BacklinksPage-B525ij0g.js} +1 -1
- package/assets/assets/{HistoryPage-CQfOM6NA.js → HistoryPage-DvupqQDM.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-D0LTQHL7.js → MeasurementPropertyPage-CwDbi1XN.js} +1 -1
- package/assets/assets/{ProjectPage-QQErJAT8.js → ProjectPage-DmZZ2C7q.js} +1 -1
- package/assets/assets/{RunsPage-CniAGDVF.js → RunsPage-DHTvHcGm.js} +1 -1
- package/assets/assets/{SettingsPage-BPsrE4ao.js → SettingsPage-BwAFzB7A.js} +1 -1
- package/assets/assets/SiteHealthSection-hi54eSIt.js +4 -0
- package/assets/assets/{TrafficPage-DXKk92zy.js → TrafficPage-BOcXASAM.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-4POLIA_c.js → TrafficSourceDetailPage-DTq0cN6g.js} +1 -1
- package/assets/assets/{extract-error-message-C6t0hB2w.js → extract-error-message-BOTDI6g5.js} +1 -1
- package/assets/assets/index-BLJfGYUw.js +86 -0
- package/assets/assets/{index-HWSQgVum.css → index-Rcbm8bK1.css} +1 -1
- package/assets/assets/{react-sigma_core.esm.min-D7GjzWYV.js → react-sigma_core.esm.min-BEBdJNtD.js} +1 -1
- package/assets/assets/{v2-overview-adapter-ChE5k_dV.js → v2-overview-adapter-BPZ-H3VY.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-ODEAE5T5.js → chunk-AAMKTL6B.js} +3 -3
- package/dist/{chunk-WJJTKZKK.js → chunk-DO7XPDG5.js} +5977 -5743
- package/dist/{chunk-75RSUOHZ.js → chunk-KRFTA4I7.js} +1126 -475
- package/dist/{chunk-WBTYXLXL.js → chunk-LO56QXPO.js} +5 -4
- package/dist/{chunk-LFUSODDL.js → chunk-NU62UT2S.js} +444 -158
- package/dist/{chunk-JJXTHJ4E.js → chunk-T2V2PESH.js} +4 -3
- package/dist/{chunk-3DB43KT2.js → chunk-ZLB7WX5D.js} +26 -33
- package/dist/cli.js +67 -28
- package/dist/{demo-server-UWBPY743.js → demo-server-KDMG3APY.js} +4 -4
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-LOH37GPJ.js → intelligence-service-LAXJFE3U.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +9 -9
- package/assets/assets/SiteHealthSection-zW3ykBCu.js +0 -4
- package/assets/assets/index-DBQ-8jB1.js +0 -86
|
@@ -5,15 +5,16 @@ import {
|
|
|
5
5
|
siteCrawlGraphNodes,
|
|
6
6
|
siteCrawlPages,
|
|
7
7
|
siteCrawlSnapshots
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-KRFTA4I7.js";
|
|
9
9
|
import {
|
|
10
10
|
SITE_CRAWL_GRAPH_MAX_EDGES,
|
|
11
11
|
SITE_CRAWL_GRAPH_MAX_NODES,
|
|
12
12
|
SiteAuditFactorStatuses,
|
|
13
13
|
TEMPLATE_LINK_RATIO_THRESHOLD,
|
|
14
|
+
factorStatusFromDistribution,
|
|
14
15
|
factorStatusFromScore,
|
|
15
16
|
roundPreservingTotal
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-DO7XPDG5.js";
|
|
17
18
|
|
|
18
19
|
// src/site-audit-factors.ts
|
|
19
20
|
function recordedSharePct(factor) {
|
|
@@ -56,7 +57,7 @@ function computeFactorAverages(pages) {
|
|
|
56
57
|
weight: rollup.weight,
|
|
57
58
|
sharePct: shares?.[index] ?? null,
|
|
58
59
|
avgScore,
|
|
59
|
-
status:
|
|
60
|
+
status: factorStatusFromDistribution({ pagesFailing: rollup.fail, pagesPartial: rollup.partial }),
|
|
60
61
|
pagesPassing: rollup.pass,
|
|
61
62
|
pagesPartial: rollup.partial,
|
|
62
63
|
pagesFailing: rollup.fail
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
agentConversationCreateSchema,
|
|
27
27
|
apiKeyDtoSchema,
|
|
28
28
|
backlinkSourceSchema,
|
|
29
|
+
calendarDateSchema,
|
|
29
30
|
canonicalizeGtmAccountId,
|
|
30
31
|
canonicalizeGtmResourceSelection,
|
|
31
32
|
competitorAliasesRequestSchema,
|
|
@@ -137,7 +138,7 @@ import {
|
|
|
137
138
|
trafficSeriesGranularitySchema,
|
|
138
139
|
visibilityCompareSelectionSchema,
|
|
139
140
|
visibilityReportRequestSchema
|
|
140
|
-
} from "./chunk-
|
|
141
|
+
} from "./chunk-DO7XPDG5.js";
|
|
141
142
|
|
|
142
143
|
// src/cli-error.ts
|
|
143
144
|
function isMachineFormat(format) {
|
|
@@ -407,7 +408,8 @@ var measurementOverviewInputSchema = z3.object({
|
|
|
407
408
|
search: measurementOverviewQuerySchema.shape.search,
|
|
408
409
|
sort: measurementOverviewQuerySchema.shape.sort,
|
|
409
410
|
cursor: measurementOverviewQuerySchema.shape.cursor,
|
|
410
|
-
limit: measurementOverviewQuerySchema.shape.limit
|
|
411
|
+
limit: measurementOverviewQuerySchema.shape.limit,
|
|
412
|
+
compact: z3.boolean().default(true).describe("Defaults to true: bounded whole rows omit repeated per-Property engine detail with explicit detailsOmitted. Use compact=false for that detail; preserve all filters when following nextCursor.")
|
|
411
413
|
}).strict().superRefine((input, context) => {
|
|
412
414
|
if (input.scope === "group" && !input.groupKey) {
|
|
413
415
|
context.addIssue({ code: "custom", path: ["groupKey"], message: "Group scope requires groupKey." });
|
|
@@ -435,21 +437,13 @@ var measurementPropertyEvidenceInputSchema = measurementPropertyEvidenceQuerySch
|
|
|
435
437
|
"What one row is. Omit for sources (one row per cited URL). answers gives one row per measured answer with its cited URLs nested, including the answers that cited nothing."
|
|
436
438
|
)
|
|
437
439
|
}).strict();
|
|
438
|
-
var PORTFOLIO_SUMMARY_AGENT_ROWS = 4;
|
|
439
|
-
var PORTFOLIO_SUMMARY_AGENT_LIMIT_MAX = 10;
|
|
440
|
-
function portfolioSummaryForAgents(summary, limitNote) {
|
|
441
|
-
const rows = summary.weakestProperties;
|
|
442
|
-
return {
|
|
443
|
-
...limitNote === void 0 ? {} : { limitNote },
|
|
444
|
-
...summary,
|
|
445
|
-
...Array.isArray(rows) ? { weakestProperties: summary.weakestProperties.map(({ recommendedInstead: _names, recommendedInsteadTotal: _total, recommendedInsteadTruncated: _cut, ...row }) => row) } : {}
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
440
|
var measurementPortfolioSummaryInputSchema = measurementPortfolioSummaryQuerySchema.extend({
|
|
449
441
|
project: projectNameSchema,
|
|
450
442
|
groupKey: measurementPortfolioSummaryQuerySchema.shape.groupKey.describe("A market stable key (a row's metro.groupKey or a markets row's groupKey). Scopes rows to that market and lists its direct submarkets."),
|
|
451
|
-
|
|
452
|
-
|
|
443
|
+
list: measurementPortfolioSummaryQuerySchema.shape.list.describe("Select one complete compact list. Default cursor walks weakest-properties; other first-page lists are bounded summaries. observed-names and cited-domains require answers=not-mentioned. Keep list unchanged across cursors."),
|
|
444
|
+
limit: measurementPortfolioSummaryQuerySchema.shape.limit.describe("Maximum selected-list rows; omitted auto-sizes up to 50 under the byte budget. nextCursor advances only pageList; use list to enumerate another ranking, market or evidence list."),
|
|
445
|
+
compact: z3.boolean().default(true).describe("Defaults to true. Omits detailed evidence with explicit detailsOmitted and returns bounded cursor pages."),
|
|
446
|
+
includeNestedMarkets: measurementPortfolioSummaryQuerySchema.shape.includeNestedMarkets.describe("Include every market level; compact pages preserve market totals and page the rows.")
|
|
453
447
|
}).strict();
|
|
454
448
|
var measurementPropertyQuestionsInputSchema = measurementPropertyQuestionsQuerySchema.extend({
|
|
455
449
|
project: projectNameSchema
|
|
@@ -1105,7 +1099,8 @@ var crawlRunIdSchema = runIdSchema.optional().describe(
|
|
|
1105
1099
|
);
|
|
1106
1100
|
var technicalAeoCrawlInputSchema = z3.object({
|
|
1107
1101
|
project: projectNameSchema,
|
|
1108
|
-
runId: crawlRunIdSchema
|
|
1102
|
+
runId: crawlRunIdSchema,
|
|
1103
|
+
date: calendarDateSchema.optional().describe("Exact UTC scan date YYYY-MM-DD, mutually exclusive with runId. A dated no-data result may include availableScanDates. When the user omitted the year, resolve it from matchingMonthDayDates and request that exact returned date before concluding the scan is missing. Never silently select another date.")
|
|
1109
1104
|
});
|
|
1110
1105
|
var siteHealthPageAuditInputSchema = z3.object({
|
|
1111
1106
|
project: projectNameSchema,
|
|
@@ -1163,6 +1158,7 @@ var siteHealthChangesInputSchema = z3.object({
|
|
|
1163
1158
|
var technicalAeoCrawlPagesInputSchema = z3.object({
|
|
1164
1159
|
project: projectNameSchema,
|
|
1165
1160
|
runId: runIdSchema.optional(),
|
|
1161
|
+
date: calendarDateSchema.optional().describe("Exact UTC scan date YYYY-MM-DD, mutually exclusive with runId. A dated no-data result may include availableScanDates. When the user omitted the year, resolve it from matchingMonthDayDates and request that exact returned date before concluding the scan is missing. Missing data never falls back to another date."),
|
|
1166
1162
|
inventoryEligible: z3.boolean().optional().describe("Filter Canonry technical-inventory eligibility. This is not actual Google index coverage."),
|
|
1167
1163
|
fetchState: z3.string().min(1).optional().describe("Filter crawler fetch state, for example html, redirect, non-html, or fetch-error."),
|
|
1168
1164
|
indexabilityState: z3.string().min(1).optional().describe("Filter crawler-derived indexability state. This is not Google index coverage."),
|
|
@@ -1286,7 +1282,7 @@ var canonryMcpTools = [
|
|
|
1286
1282
|
defineTool({
|
|
1287
1283
|
name: "canonry_sentiment",
|
|
1288
1284
|
title: "Read sentiment and per-query scores",
|
|
1289
|
-
description: 'Read stored model-classified language about frozen Simple identities or Advanced Properties. Returns headline and per-query favorable/mixed/unfavorable rates, judged and selected assessment counts, rated and eligible answers with their share (ratedAnswerRate: answers with a favorable, mixed or unfavorable rating over every eligible answer, admitted or not; each answer counts once), exclusions, intervals and method limitations. Favorable % is favorable / (favorable + mixed + unfavorable); factual and unjudged answers do not enter that denominator. Select branded or non-brand explicitly and keep their metrics separate; the default is branded. Optional queryId selects one frozen query. Query rows are compact and paged: queryLimit (default 25) rows per page, then pass queryPage.nextCursor as queryCursor. Per-engine assessment verdicts and per-location scores are opt-in with include=["assessments","locations"], or come by default when queryId names one query. Advanced rows are per executionNodeKey; pass it to narrow evidence to that node. Optional runIds selects the exact grouped location runs instead of runId. A known subject absent from a non-brand answer is not unfavorable. Branded reads carry criticizedProperties: total Properties with a mixed or unfavorable rating, and keys, the first five property breakdown keys, most criticized first ({total: 0, keys: []} with fewer than two Properties in view; absent for non-brand, disabled or unavailable reads); read their answers on canonry_sentiment_evidence with scope property, that scopeKey and outcome ["mixed","unfavorable"]. Partial values are provisional. Never starts a classifier. Preserve the resolved evaluationDefinitionId and complete selection for later evidence reads.',
|
|
1285
|
+
description: 'Read stored model-classified language about frozen Simple identities or Advanced Properties. Returns headline and per-query favorable/mixed/unfavorable rates, judged and selected assessment counts, rated and eligible answers with their share (ratedAnswerRate: answers with a favorable, mixed or unfavorable rating over every eligible answer, admitted or not; each answer counts once), exclusions, intervals and method limitations. Favorable % is favorable / (favorable + mixed + unfavorable); factual and unjudged answers do not enter that denominator. Select branded or non-brand explicitly and keep their metrics separate; the default is branded. Optional queryId selects one frozen query. Query rows are compact and paged: queryLimit (default 25) rows per page, then pass queryPage.nextCursor as queryCursor. Per-engine assessment verdicts and per-location scores are opt-in with include=["assessments","locations"], or come by default when queryId names one query. Advanced rows are per executionNodeKey; pass it to narrow evidence to that node. Optional runIds selects the exact grouped location runs instead of runId. coverage.subjectNotMentioned counts distinct answers where every assessed subject is absent; this is expected for non-brand queries and is not missing data. A known subject absent from a non-brand answer is not unfavorable. Branded reads carry criticizedProperties: total Properties with a mixed or unfavorable rating, and keys, the first five property breakdown keys, most criticized first ({total: 0, keys: []} with fewer than two Properties in view; absent for non-brand, disabled or unavailable reads); read their answers on canonry_sentiment_evidence with scope property, that scopeKey and outcome ["mixed","unfavorable"]. Partial values are provisional. Never starts a classifier. Preserve the resolved evaluationDefinitionId and complete selection for later evidence reads.',
|
|
1290
1286
|
access: "read",
|
|
1291
1287
|
tier: "monitoring",
|
|
1292
1288
|
inputSchema: sentimentInputSchema,
|
|
@@ -1316,7 +1312,7 @@ var canonryMcpTools = [
|
|
|
1316
1312
|
defineTool({
|
|
1317
1313
|
name: "canonry_sentiment_compare",
|
|
1318
1314
|
title: "Compare matched sentiment periods",
|
|
1319
|
-
description: "Compare stored periods under compatible frozen scope, answer-engine identity and evaluator, with complete classification coverage. Returns matched/excluded units, each denominator, all refusal reasons, a nullable direction, and both period summaries with compact query rows (per-engine detail is on canonry_sentiment). Overlapping Wilson intervals mean no clear change detected; they do not establish equivalence. No provider calls.",
|
|
1315
|
+
description: "fromRunId accepts previous-rated to select the preceding rated run in the same scope within 50 rated candidates; identical run IDs are refused. Population changes, incomplete targets and search-limit refusals are distinct; search-limit asks for an explicit older run. Compare stored periods under compatible frozen scope, answer-engine identity and evaluator, with complete classification coverage. Returns matched/excluded units, each denominator, all refusal reasons, a nullable direction, and both period summaries with compact query rows (per-engine detail is on canonry_sentiment). Overlapping Wilson intervals mean no clear change detected; they do not establish equivalence. No provider calls.",
|
|
1320
1316
|
access: "read",
|
|
1321
1317
|
tier: "monitoring",
|
|
1322
1318
|
inputSchema: sentimentCompareInputSchema,
|
|
@@ -1478,7 +1474,7 @@ var canonryMcpTools = [
|
|
|
1478
1474
|
defineTool({
|
|
1479
1475
|
name: "canonry_project_overview",
|
|
1480
1476
|
title: "Get project overview (composite)",
|
|
1481
|
-
description: `One-call summary for "how is project X doing?". Returns independent mention and citation coverage, separate query-level movement for each signal, query-basket comparability with added/removed counts, latest run and health, insights, provider/model breakdowns, competitors, attention items, and recent history. Movement excludes queries not shared by both sweeps. Filterable by location and time window. Prefer this over fanning out to separate read tools for the same question. queryClassScope says which figures pool branded and non-brand: the coverage, scores (except scores.mentionShare) and movement count queries across both classes, so never report them as one class or as answers. The latest run's status and health are not a completeness check: for expected, executed and missing answers read canonry_measurement_data_quality or, for a plan run, canonry_run_completeness. canonry_doctor checks configuration and integrations, not sweeps. SCOPE LIMIT: this returns nothing about Advanced Measurement: no plan, Targets, Properties, groups, or segments. Its silence is not evidence that a project lacks them. When the system prompt has no Project shape line, call canonry_measurement_plan_get before stating that a project has no Properties, no Advanced plan, or cannot break out per-Property performance; use canonry_measurement_portfolio_summary to rank Properties.`,
|
|
1477
|
+
description: `Sentiment headline is sentiment.branded; nonBrand is a separate exception population. pooledOverall and deprecated overall are explicitly pooled compatibility figures and must never be reported as a class-specific headline. One-call summary for "how is project X doing?". Returns independent mention and citation coverage, separate query-level movement for each signal, query-basket comparability with added/removed counts, latest run and health, insights, provider/model breakdowns, competitors, attention items, and recent history. Movement excludes queries not shared by both sweeps. Filterable by location and time window. Prefer this over fanning out to separate read tools for the same question. queryClassScope says which figures pool branded and non-brand: the coverage, scores (except scores.mentionShare) and movement count queries across both classes, so never report them as one class or as answers. The latest run's status and health are not a completeness check: for expected, executed and missing answers read canonry_measurement_data_quality or, for a plan run, canonry_run_completeness. canonry_doctor checks configuration and integrations, not sweeps. SCOPE LIMIT: this returns nothing about Advanced Measurement: no plan, Targets, Properties, groups, or segments. Its silence is not evidence that a project lacks them. When the system prompt has no Project shape line, call canonry_measurement_plan_get before stating that a project has no Properties, no Advanced plan, or cannot break out per-Property performance; use canonry_measurement_portfolio_summary to rank Properties.`,
|
|
1482
1478
|
access: "read",
|
|
1483
1479
|
tier: "core",
|
|
1484
1480
|
inputSchema: z3.object({
|
|
@@ -1550,7 +1546,7 @@ var canonryMcpTools = [
|
|
|
1550
1546
|
defineTool({
|
|
1551
1547
|
name: "canonry_competitor_landscape",
|
|
1552
1548
|
title: "Get historical competitor landscape",
|
|
1553
|
-
description: "Returns pinned competitors first, then observed direct competitors and other cited sources from stored answer/source evidence only. Mention share is percentage points (0..100) from answer text; citations are independent. The response carries basis (tracked or observed), availability, and reason. Pins exclusively define the comparison set when present. Otherwise at least 3 stored direct competitors must each be mentioned in 3 answers in the selected scope; raw names and platforms never enter that denominator. Without a comparison set the ratio is null, never 100%. The full comparison array is uncapped. Share of voice needs ONE query class: `queryClass=all`, and omitting it, pool branded and non-brand, so every `shareOfVoice` comes back null and only the counts are published. Pass `queryClass=non-brand` (or `branded`) for a ratio, exactly as visibility-stats does. A project with no brand name or alias cannot be split by class, so a class-scoped read on one is refused rather than answered empty. Optional groupBy: model adds provider/requested-model groups with separate served-model evidence and sample counts. Optional model filters one exact requested model ID and requires provider. Unknown historical models remain explicit. Groups are not a matched-query or equal-weight comparison. Advanced reads support one market group or explicit scope: all-markets. On a project with an active v2 measurement plan, queryClass=branded or non-brand with no scope or groupKey defaults to scope=all-markets, so classes come from the plan; pass scope=project to force the text classifier. No provider, discovery, or classifier work runs. Ranked lists are capped at 100 observed/other-source rows per group; pins remain complete. `observedNames` are names written in answer text, not cited sources, capped at the top 50 by answer count; `observedNamesTotal` counts distinct names, never answers (`countUnits` says what each count counts). Model groups are capped at 50 and disclose truncation. For which names answers give instead across a portfolio, read this with queryClass and runId latest: per-Property named-instead lists are samples of weak Properties. With queryClass branded or non-brand and neither runId nor window, this tool reads the latest sweep; pass window to pool sweeps. `runCount` and `runIds` name the runs counted; above 1 the counts pool several runs.",
|
|
1549
|
+
description: "answers=not-mentioned selects only answers where none of their targeted subjects was named, with answerSelection reporting population and selected counts. Returns pinned competitors first, then observed direct competitors and other cited sources from stored answer/source evidence only. Mention share is percentage points (0..100) from answer text; citations are independent. The response carries basis (tracked or observed), availability, and reason. Pins exclusively define the comparison set when present. Otherwise at least 3 stored direct competitors must each be mentioned in 3 answers in the selected scope; raw names and platforms never enter that denominator. Without a comparison set the ratio is null, never 100%. The full comparison array is uncapped. Share of voice needs ONE query class: `queryClass=all`, and omitting it, pool branded and non-brand, so every `shareOfVoice` comes back null and only the counts are published. Pass `queryClass=non-brand` (or `branded`) for a ratio, exactly as visibility-stats does. A project with no brand name or alias cannot be split by class, so a class-scoped read on one is refused rather than answered empty. Optional groupBy: model adds provider/requested-model groups with separate served-model evidence and sample counts. Optional model filters one exact requested model ID and requires provider. Unknown historical models remain explicit. Groups are not a matched-query or equal-weight comparison. Advanced reads support one market group or explicit scope: all-markets. On a project with an active v2 measurement plan, queryClass=branded or non-brand with no scope or groupKey defaults to scope=all-markets, so classes come from the plan; pass scope=project to force the text classifier. No provider, discovery, or classifier work runs. Ranked lists are capped at 100 observed/other-source rows per group; pins remain complete. `observedNames` are names written in answer text, not cited sources, capped at the top 50 by answer count; `observedNamesTotal` counts distinct names, never answers (`countUnits` says what each count counts). Model groups are capped at 50 and disclose truncation. For which names answers give instead across a portfolio, read this with queryClass and runId latest: per-Property named-instead lists are samples of weak Properties. With queryClass branded or non-brand and neither runId nor window, this tool reads the latest sweep; pass window to pool sweeps. answers=not-mentioned publishes names and mention/citation counts only; all competitive shares are null with reason answer-selection because the population is selected by the measured mention outcome. `runCount` and `runIds` name the runs counted; above 1 the counts pool several runs.",
|
|
1554
1550
|
access: "read",
|
|
1555
1551
|
tier: "monitoring",
|
|
1556
1552
|
inputSchema: competitorLandscapeInputSchema,
|
|
@@ -2885,7 +2881,7 @@ var canonryMcpTools = [
|
|
|
2885
2881
|
defineTool({
|
|
2886
2882
|
name: "canonry_measurement_overview",
|
|
2887
2883
|
title: "Get Advanced Measurement overview",
|
|
2888
|
-
description: "For best/worst Property mention rankings, prefer canonry_measurement_portfolio_summary and its mentionRanking. An unavailable aggregate does not invalidate available Property metrics. Return stored, revision-pinned Advanced Measurement metrics and a bounded page of Property rows for all Properties, one reporting group, or one Property. Filter by query class, provider, location, date window, run, or Property search; search filters rows without changing metric denominators. On a schema-v2 plan each Property row carries its metro (and otherMetros when it is in several). It ranks one run snapshot only and never infers a trend or compares across revisions. Choose label-asc (default), label-desc, citationCoverage-asc/desc, or mentionCoverage-asc/desc. For a coverage sort, unavailable rows form the first bucket in either direction; available rows then follow the requested numeric direction. The cursor is sort-aware, pins pagination to the active revision, displayed run, evidence snapshot, and filters even if a newer run completes, and must be reused unchanged with the same sort and filters. Legacy label cursors work only when sort is omitted, while any explicit sort needs a new sort-bound cursor. It never starts provider work or incurs provider cost; page size is at most 100, and it refuses invalid scope keys, cursor combinations, appended evidence, or a run pinned to another revision.",
|
|
2884
|
+
description: "Compact pages are byte-bounded whole rows by default; use nextCursor with unchanged filters. For best/worst Property mention rankings, prefer canonry_measurement_portfolio_summary and its mentionRanking. An unavailable aggregate does not invalidate available Property metrics. Return stored, revision-pinned Advanced Measurement metrics and a bounded page of Property rows for all Properties, one reporting group, or one Property. Filter by query class, provider, location, date window, run, or Property search; search filters rows without changing metric denominators. On a schema-v2 plan each Property row carries its metro (and otherMetros when it is in several). It ranks one run snapshot only and never infers a trend or compares across revisions. Choose label-asc (default), label-desc, citationCoverage-asc/desc, or mentionCoverage-asc/desc. For a coverage sort, unavailable rows form the first bucket in either direction; available rows then follow the requested numeric direction. The cursor is sort-aware, pins pagination to the active revision, displayed run, evidence snapshot, and filters even if a newer run completes, and must be reused unchanged with the same sort and filters. Legacy label cursors work only when sort is omitted, while any explicit sort needs a new sort-bound cursor. It never starts provider work or incurs provider cost; page size is at most 100, and it refuses invalid scope keys, cursor combinations, appended evidence, or a run pinned to another revision.",
|
|
2889
2885
|
access: "read",
|
|
2890
2886
|
tier: "setup",
|
|
2891
2887
|
inputSchema: measurementOverviewInputSchema,
|
|
@@ -2893,7 +2889,7 @@ var canonryMcpTools = [
|
|
|
2893
2889
|
openApiOperations: ["GET /api/v1/projects/{name}/measurement-overview"],
|
|
2894
2890
|
handler: (client, input) => {
|
|
2895
2891
|
const { project, ...query } = input;
|
|
2896
|
-
return client.getMeasurementOverview(project, query);
|
|
2892
|
+
return client.getMeasurementOverview(project, { ...query, compact: query.compact ?? true });
|
|
2897
2893
|
}
|
|
2898
2894
|
}),
|
|
2899
2895
|
defineTool({
|
|
@@ -2913,7 +2909,7 @@ var canonryMcpTools = [
|
|
|
2913
2909
|
defineTool({
|
|
2914
2910
|
name: "canonry_measurement_portfolio_summary",
|
|
2915
2911
|
title: "Summarize measured Properties",
|
|
2916
|
-
description: "
|
|
2912
|
+
description: "Read revision-pinned Property rankings and market rollups from stored answers. Start here for portfolio headlines: metrics.propertiesMentioned, propertiesNeverMentioned, mentionCoverage and citationCoverage. Defaults to non-brand; report branded separately. A never-mentioned Property may still be cited. Compact weakestMarkets ranks actual full top-level market rates; use it for weakest metros. tiedAtWeakest requires both zero mentions and zero citations; its byMetro rows describe only that Property cohort, not full metro rates, and its count is not the never-mentioned count. Compact pages are byte-bounded whole rows by default. nextCursor advances only pageList, default weakest-properties. Other lists are bounded first-page summaries, then omitted; totals remain complete. To enumerate another list, set list to strongest-mentions, weakest-mentions, excluded-mentions, markets, observed-names or cited-domains. Selected-list mode omits sibling row bodies; without limit, pages auto-size up to 50 rows. Follow nextCursor as cursor with unchanged list and filters; detailsOmitted names evidence available on drill-down tools. Mention and citation denominators are independent; preserve unattributed and unchecked counts. answers=not-mentioned returns answerEvidence names and registrable cited domains only from answers naming none of their targeted Properties, with populationSize, answerCount and unknownMentionAnswers. Counts for names and domains count distinct answers. Markets can overlap and must not be summed. Group rows by returned metro/submarkets rather than label. No provider calls.",
|
|
2917
2913
|
access: "read",
|
|
2918
2914
|
tier: "monitoring",
|
|
2919
2915
|
inputSchema: measurementPortfolioSummaryInputSchema,
|
|
@@ -2921,11 +2917,7 @@ var canonryMcpTools = [
|
|
|
2921
2917
|
openApiOperations: ["GET /api/v1/projects/{name}/measurement-portfolio-summary"],
|
|
2922
2918
|
handler: async (client, input) => {
|
|
2923
2919
|
const { project, ...query } = input;
|
|
2924
|
-
|
|
2925
|
-
const lowered = query.limit !== void 0 && query.limit > most;
|
|
2926
|
-
const summary = await client.getMeasurementPortfolioSummary(project, lowered ? { ...query, limit: most } : query);
|
|
2927
|
-
const more = query.groupKey === void 0 ? "For other rows pass groupKey (one metro's weakest), or page canonry_measurement_overview." : "For more rows page canonry_measurement_overview with scope group.";
|
|
2928
|
-
return portfolioSummaryForAgents(summary, lowered ? `limit ${query.limit} was lowered to ${most}, the most rows that fit one result. ${more}` : void 0);
|
|
2920
|
+
return client.getMeasurementPortfolioSummary(project, { ...query, compact: query.compact ?? true });
|
|
2929
2921
|
}
|
|
2930
2922
|
}),
|
|
2931
2923
|
defineTool({
|
|
@@ -3601,13 +3593,13 @@ var canonryMcpTools = [
|
|
|
3601
3593
|
defineTool({
|
|
3602
3594
|
name: "canonry_site_health_overview",
|
|
3603
3595
|
title: "Get Site Health overview",
|
|
3604
|
-
description: "Start a Site Health investigation with the latest or selected scan summary: root URL, completeness, crawl and link counts, budgets, versions, termination, and dead-link check state. Follow with a focused Site Health subgraph, shortest path, or scan changes; never request the interactive graph projection through MCP.",
|
|
3596
|
+
description: "For a requested scan date pass date=YYYY-MM-DD. If the year was omitted and that date has no data, use availableScanDates.matchingMonthDayDates to resolve the year and request the exact returned date before concluding the historical scan is missing. recentDates and matchingMonthDayDates are bounded; totalDates and matchingMonthDayTotal report their full counts. No automatic date fallback. inventorySummary provides complete eligibility counts and disjoint excluded reasons, including terminal redirects. Start a Site Health investigation with the latest or selected scan summary: root URL, completeness, crawl and link counts, budgets, versions, termination, and dead-link check state. Follow with a focused Site Health subgraph, shortest path, or scan changes; never request the interactive graph projection through MCP.",
|
|
3605
3597
|
access: "read",
|
|
3606
3598
|
tier: "monitoring",
|
|
3607
3599
|
inputSchema: technicalAeoCrawlInputSchema,
|
|
3608
3600
|
annotations: readAnnotations(),
|
|
3609
3601
|
openApiOperations: ["GET /api/v1/projects/{name}/technical-aeo/crawl"],
|
|
3610
|
-
handler: (client, input) => client.getTechnicalAeoCrawl(input.project, { runId: input.runId })
|
|
3602
|
+
handler: (client, input) => client.getTechnicalAeoCrawl(input.project, { runId: input.runId, date: input.date })
|
|
3611
3603
|
}),
|
|
3612
3604
|
defineTool({
|
|
3613
3605
|
name: "canonry_site_health_page_audit",
|
|
@@ -3681,7 +3673,7 @@ var canonryMcpTools = [
|
|
|
3681
3673
|
defineTool({
|
|
3682
3674
|
name: "canonry_technical_aeo_score",
|
|
3683
3675
|
title: "Get Technical AEO score",
|
|
3684
|
-
description: "Get the Technical AEO scorecard for a project: the latest site-audit aggregate 0\u2013100 score, per-factor site-level averages (with pass/partial/fail distribution), cross-cutting issues, prioritized fixes, and the delta vs the previous audit. A factor's share of the site score is `sharePct` (0\u2013100, the shares add up to 100; null for a scan that did not record it); its `weight` is relative and never a percentage. When `hasData` is false the
|
|
3676
|
+
description: "pagesFailing and pagesPartial are independent counts; their sum is the number of audited pages below pass for that factor. Any failing page makes a factor fail, independently of its average score or affected-page count. These counts use the audited-page population; crawl eligibility is a separate inventory population. affectedPageRanking orders recorded per-factor below-pass counts from largest to smallest; this is affected-page breadth, not a prediction of mention or citation lift. Factor cohorts overlap, so do not add their counts together. runSelection flags same-date ambiguity and prefers a complete run on the latest scan date, otherwise the largest page sample. Get the Technical AEO scorecard for a project: the latest site-audit aggregate 0\u2013100 score, per-factor site-level averages (with pass/partial/fail distribution), cross-cutting issues, prioritized fixes, and the delta vs the previous audit. A factor's share of the site score is `sharePct` (0\u2013100, the shares add up to 100; null for a scan that did not record it); its `weight` is relative and never a percentage. When `hasData` is false, no scored audit is available for the selected scan; a stored crawl may still exist. Inspect the stored crawl before proposing a new audit.",
|
|
3685
3677
|
access: "read",
|
|
3686
3678
|
tier: "monitoring",
|
|
3687
3679
|
inputSchema: technicalAeoScoreInputSchema,
|
|
@@ -3720,18 +3712,18 @@ var canonryMcpTools = [
|
|
|
3720
3712
|
defineTool({
|
|
3721
3713
|
name: "canonry_technical_aeo_crawl",
|
|
3722
3714
|
title: "Get persisted Technical AEO crawl",
|
|
3723
|
-
description: "Read persisted full-crawl metadata for the latest or selected Technical AEO site-audit run: root URL, crawl/indexability/link-score versions, effective budgets, completeness, counts, and explicit dead-link check state. This does not fabricate a graph from older scorecard-only audits.",
|
|
3715
|
+
description: "For a historical question pass its exact UTC date (YYYY-MM-DD). If the year was omitted and that date has no data, use availableScanDates.matchingMonthDayDates to resolve the year and request the exact returned date before concluding the historical scan is missing. recentDates and matchingMonthDayDates are bounded; totalDates and matchingMonthDayTotal report their full counts. No automatic date fallback. inventorySummary gives whole-snapshot eligibility and disjoint excluded reasons; unknown is not canonical-away. Read persisted full-crawl metadata for the latest or selected Technical AEO site-audit run: root URL, crawl/indexability/link-score versions, effective budgets, completeness, counts, and explicit dead-link check state. This does not fabricate a graph from older scorecard-only audits.",
|
|
3724
3716
|
access: "read",
|
|
3725
3717
|
tier: "monitoring",
|
|
3726
3718
|
inputSchema: technicalAeoCrawlInputSchema,
|
|
3727
3719
|
annotations: readAnnotations(),
|
|
3728
3720
|
openApiOperations: ["GET /api/v1/projects/{name}/technical-aeo/crawl"],
|
|
3729
|
-
handler: (client, input) => client.getTechnicalAeoCrawl(input.project, { runId: input.runId })
|
|
3721
|
+
handler: (client, input) => client.getTechnicalAeoCrawl(input.project, { runId: input.runId, date: input.date })
|
|
3730
3722
|
}),
|
|
3731
3723
|
defineTool({
|
|
3732
3724
|
name: "canonry_technical_aeo_crawl_pages",
|
|
3733
3725
|
title: "List Technical AEO crawl pages",
|
|
3734
|
-
description: "Read one bounded, cursor-paged list of canonical crawl nodes. Filter crawler-derived indexability and audit state, then follow nextCursor; this is technical inventory eligibility, not a claim about Google index coverage. `complete: false` means the crawl stopped at the budget named by `termination`, so a missing page or link is not proof it does not exist.",
|
|
3726
|
+
description: "For a requested scan date pass date=YYYY-MM-DD. If the year was omitted and that date has no data, use availableScanDates.matchingMonthDayDates to resolve the year and request the exact returned date before concluding the historical scan is missing. recentDates and matchingMonthDayDates are bounded; totalDates and matchingMonthDayTotal report their full counts. No automatic date fallback. inventorySummary gives whole-snapshot eligible/excluded counts and disjoint excluded reasons; healthReasonCounts covers the filtered list before paging. Use these exact counts, never infer canonical-away from unknown indexability. healthReason distinguishes terminal redirects from canonical-to-other pages. Default runSelection reports same-date ambiguity and selects complete or most-pages evidence on the latest scan date. Read one bounded, cursor-paged list of canonical crawl nodes. Filter crawler-derived indexability and audit state, then follow nextCursor; this is technical inventory eligibility, not a claim about Google index coverage. `complete: false` means the crawl stopped at the budget named by `termination`, so a missing page or link is not proof it does not exist.",
|
|
3735
3727
|
access: "read",
|
|
3736
3728
|
tier: "monitoring",
|
|
3737
3729
|
inputSchema: technicalAeoCrawlPagesInputSchema,
|
|
@@ -3739,6 +3731,7 @@ var canonryMcpTools = [
|
|
|
3739
3731
|
openApiOperations: ["GET /api/v1/projects/{name}/technical-aeo/crawl/pages"],
|
|
3740
3732
|
handler: (client, input) => client.getTechnicalAeoCrawlPages(input.project, {
|
|
3741
3733
|
runId: input.runId,
|
|
3734
|
+
date: input.date,
|
|
3742
3735
|
inventoryEligible: input.inventoryEligible,
|
|
3743
3736
|
fetchState: input.fetchState,
|
|
3744
3737
|
indexabilityState: input.indexabilityState,
|
package/dist/cli.js
CHANGED
|
@@ -32,11 +32,11 @@ import {
|
|
|
32
32
|
trackCliCommandFinished,
|
|
33
33
|
trackEvent,
|
|
34
34
|
waitForServerRuntimeStartup
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-NU62UT2S.js";
|
|
36
36
|
import {
|
|
37
37
|
autoSyncSkills,
|
|
38
38
|
formatAutoSyncNotice
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-AAMKTL6B.js";
|
|
40
40
|
import {
|
|
41
41
|
configExists,
|
|
42
42
|
createApiClient,
|
|
@@ -53,13 +53,13 @@ import {
|
|
|
53
53
|
saveConfig,
|
|
54
54
|
saveConfigPatch,
|
|
55
55
|
shouldPrintServeSkillsNudge
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-LO56QXPO.js";
|
|
57
57
|
import {
|
|
58
58
|
DEFAULT_DEMO_TRUSTED_PROXIES,
|
|
59
59
|
trustedProxiesProblem,
|
|
60
60
|
trustedProxyProblem
|
|
61
61
|
} from "./chunk-RB2F4D3Z.js";
|
|
62
|
-
import "./chunk-
|
|
62
|
+
import "./chunk-T2V2PESH.js";
|
|
63
63
|
import {
|
|
64
64
|
CliError,
|
|
65
65
|
EXIT_SYSTEM_ERROR,
|
|
@@ -71,7 +71,7 @@ import {
|
|
|
71
71
|
runMeasurementDraftAction,
|
|
72
72
|
systemError,
|
|
73
73
|
usageError
|
|
74
|
-
} from "./chunk-
|
|
74
|
+
} from "./chunk-ZLB7WX5D.js";
|
|
75
75
|
import {
|
|
76
76
|
CLOUDFLARE_WORKER_BINDINGS,
|
|
77
77
|
CLOUDFLARE_WORKER_GENERATED_MARKER,
|
|
@@ -100,7 +100,7 @@ import {
|
|
|
100
100
|
querySnapshots,
|
|
101
101
|
rawEventSamples,
|
|
102
102
|
runs
|
|
103
|
-
} from "./chunk-
|
|
103
|
+
} from "./chunk-KRFTA4I7.js";
|
|
104
104
|
import {
|
|
105
105
|
AdsDeliverySnapshotStatuses,
|
|
106
106
|
AdsHistoricalCampaignRollupStatuses,
|
|
@@ -155,6 +155,7 @@ import {
|
|
|
155
155
|
buildModelChangeNotice,
|
|
156
156
|
canonicalizeGtmAccountId,
|
|
157
157
|
canonicalizeGtmResourceSelection,
|
|
158
|
+
competitorLandscapeQuerySchema,
|
|
158
159
|
computeCompetitorOverlap,
|
|
159
160
|
contentTargetDismissRequestSchema,
|
|
160
161
|
conversionTrackingContractWriteRequestSchema,
|
|
@@ -228,7 +229,7 @@ import {
|
|
|
228
229
|
visibilityCompareSelectionSchema,
|
|
229
230
|
visibilityReportRequestSchema,
|
|
230
231
|
winnabilityClassSchema
|
|
231
|
-
} from "./chunk-
|
|
232
|
+
} from "./chunk-DO7XPDG5.js";
|
|
232
233
|
|
|
233
234
|
// src/cli.ts
|
|
234
235
|
import { pathToFileURL } from "url";
|
|
@@ -685,8 +686,8 @@ var SENTIMENT_CLI_COMMANDS = [
|
|
|
685
686
|
},
|
|
686
687
|
{
|
|
687
688
|
path: ["sentiment", "compare"],
|
|
688
|
-
usage: "canonry sentiment compare <project> --from-run-id <id> --to-run-id <id> [selection options] [--format json]",
|
|
689
|
-
help: `${selectionHelp} Complete matched coverage and compatible evaluators are required for a directional verdict.`,
|
|
689
|
+
usage: "canonry sentiment compare <project> --from-run-id <id|previous-rated> --to-run-id <id> [selection options] [--format json]",
|
|
690
|
+
help: `${selectionHelp} Use --from-run-id previous-rated to resolve the preceding rated run in the selected scope. Self-comparisons are refused. Complete matched coverage and compatible evaluators are required for a directional verdict.`,
|
|
690
691
|
options: { ...selectionOptions, "from-run-id": stringOption(), "to-run-id": stringOption() },
|
|
691
692
|
run: (input) => compareSentiment(project(input, "compare"), parse(sentimentCompareRequestSchema, {
|
|
692
693
|
...selection(input),
|
|
@@ -778,7 +779,7 @@ function parseDemoTrustedProxies(values) {
|
|
|
778
779
|
async function demoCommand(options) {
|
|
779
780
|
const { host, port } = parseDemoListenOptions(options);
|
|
780
781
|
const trustProxy = parseDemoTrustedProxies(options.trustProxy);
|
|
781
|
-
const { createDemoServer } = await import("./demo-server-
|
|
782
|
+
const { createDemoServer } = await import("./demo-server-KDMG3APY.js");
|
|
782
783
|
const app = await createDemoServer({ trustProxy });
|
|
783
784
|
let stopping = false;
|
|
784
785
|
const stop = () => {
|
|
@@ -1192,7 +1193,7 @@ No DB writes performed. Re-run without --dry-run to apply.`);
|
|
|
1192
1193
|
}
|
|
1193
1194
|
}
|
|
1194
1195
|
async function backfillInsightsCommand(project2, opts) {
|
|
1195
|
-
const { IntelligenceService } = await import("./intelligence-service-
|
|
1196
|
+
const { IntelligenceService } = await import("./intelligence-service-LAXJFE3U.js");
|
|
1196
1197
|
const config = loadConfig();
|
|
1197
1198
|
const db = createClient(config.database);
|
|
1198
1199
|
migrate(db);
|
|
@@ -9246,7 +9247,8 @@ async function showCompetitorLandscape(project2, options) {
|
|
|
9246
9247
|
...options.model !== void 0 ? { model: options.model } : {},
|
|
9247
9248
|
queryClass: options.queryClass,
|
|
9248
9249
|
location: options.location,
|
|
9249
|
-
runId: options.runId
|
|
9250
|
+
runId: options.runId,
|
|
9251
|
+
answers: options.answers
|
|
9250
9252
|
});
|
|
9251
9253
|
if (options.format === "json") {
|
|
9252
9254
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -9324,7 +9326,7 @@ function printLandscapeRows(heading, rows, showSampleCount = false, context, que
|
|
|
9324
9326
|
// src/cli-commands/competitor.ts
|
|
9325
9327
|
var ADD_USAGE = "canonry competitor add <project> <domain...> [--alias <name>]... [--format json]";
|
|
9326
9328
|
var ALIASES_USAGE = "canonry competitor aliases <project> <domain> [--set <name>]... [--add <name>]... [--remove <name>]... [--clear] [--format json]";
|
|
9327
|
-
var LANDSCAPE_USAGE = "canonry competitor landscape <project> [--window 7d|30d|90d|all] [--group-key <key>|--scope all-markets] [--by-model] [--provider <provider> [--model <id>]] [--query-class all|branded|non-brand] [--location <label>] [--run-id <id>] [--format json|jsonl]";
|
|
9329
|
+
var LANDSCAPE_USAGE = "canonry competitor landscape <project> [--window 7d|30d|90d|all] [--group-key <key>|--scope all-markets] [--by-model] [--provider <provider> [--model <id>]] [--query-class all|branded|non-brand] [--answers all|not-mentioned] [--location <label>] [--run-id <id>] [--format json|jsonl]";
|
|
9328
9330
|
function parseLandscapeScope(value) {
|
|
9329
9331
|
if (value === void 0 || value === "") return void 0;
|
|
9330
9332
|
if (value !== "all-markets") throw usageError(`--scope must be "all-markets" (got "${value}")`, {
|
|
@@ -9451,7 +9453,8 @@ Usage: ${ALIASES_USAGE}`, {
|
|
|
9451
9453
|
model: stringOption(),
|
|
9452
9454
|
"query-class": stringOption(),
|
|
9453
9455
|
location: stringOption(),
|
|
9454
|
-
"run-id": stringOption()
|
|
9456
|
+
"run-id": stringOption(),
|
|
9457
|
+
answers: stringOption()
|
|
9455
9458
|
},
|
|
9456
9459
|
run: async (input) => {
|
|
9457
9460
|
const project2 = requireProject(input, "competitor.landscape", LANDSCAPE_USAGE);
|
|
@@ -9471,7 +9474,10 @@ Usage: ${ALIASES_USAGE}`, {
|
|
|
9471
9474
|
details: { command: "competitor.landscape", usage: LANDSCAPE_USAGE }
|
|
9472
9475
|
});
|
|
9473
9476
|
}
|
|
9477
|
+
const parsedAnswers = competitorLandscapeQuerySchema.safeParse({ answers: getString(input.values, "answers") });
|
|
9478
|
+
if (!parsedAnswers.success) throw usageError("--answers must be all or not-mentioned");
|
|
9474
9479
|
await showCompetitorLandscape(project2, {
|
|
9480
|
+
answers: parsedAnswers.data.answers,
|
|
9475
9481
|
window: getString(input.values, "window"),
|
|
9476
9482
|
groupKey,
|
|
9477
9483
|
scope,
|
|
@@ -11794,12 +11800,16 @@ async function runAdvancedMeasurementOperation(project2, operation, source, form
|
|
|
11794
11800
|
advancedEmptyInputSchema.parse(input);
|
|
11795
11801
|
result = await client.getMeasurementSetup(project2);
|
|
11796
11802
|
break;
|
|
11797
|
-
case "overview":
|
|
11798
|
-
|
|
11803
|
+
case "overview": {
|
|
11804
|
+
const query = measurementOverviewQuerySchema.parse(input);
|
|
11805
|
+
result = await client.getMeasurementOverview(project2, { ...query, compact: typeof input === "object" && input !== null && "compact" in input ? query.compact : true });
|
|
11799
11806
|
break;
|
|
11800
|
-
|
|
11801
|
-
|
|
11807
|
+
}
|
|
11808
|
+
case "portfolio-summary": {
|
|
11809
|
+
const query = measurementPortfolioSummaryQuerySchema.parse(input);
|
|
11810
|
+
result = await client.getMeasurementPortfolioSummary(project2, { ...query, compact: typeof input === "object" && input !== null && "compact" in input ? query.compact : true });
|
|
11802
11811
|
break;
|
|
11812
|
+
}
|
|
11803
11813
|
case "property-questions":
|
|
11804
11814
|
result = await client.getMeasurementPropertyQuestions(project2, measurementPropertyQuestionsQuerySchema.parse(input));
|
|
11805
11815
|
break;
|
|
@@ -18491,12 +18501,16 @@ async function technicalAeoProgress(project2, opts) {
|
|
|
18491
18501
|
console.log(lines.join("\n"));
|
|
18492
18502
|
}
|
|
18493
18503
|
async function technicalAeoCrawl(project2, opts) {
|
|
18494
|
-
const crawl = await getClient27().getTechnicalAeoCrawl(project2, { runId: opts.runId });
|
|
18504
|
+
const crawl = await getClient27().getTechnicalAeoCrawl(project2, { runId: opts.runId, date: opts.date });
|
|
18495
18505
|
if (isMachineFormat(opts.format)) {
|
|
18496
18506
|
console.log(JSON.stringify(crawl, null, 2));
|
|
18497
18507
|
return;
|
|
18498
18508
|
}
|
|
18499
18509
|
if (!crawl.hasCrawlData) {
|
|
18510
|
+
if (opts.date) {
|
|
18511
|
+
console.log([`No persisted site crawl for "${project2}" on ${opts.date}.`, ...availableScanDateLines(crawl.availableScanDates)].join("\n"));
|
|
18512
|
+
return;
|
|
18513
|
+
}
|
|
18500
18514
|
console.log(`No persisted site crawl for "${project2}". Run \`canonry technical-aeo run ${project2}\` first.`);
|
|
18501
18515
|
return;
|
|
18502
18516
|
}
|
|
@@ -18507,6 +18521,7 @@ async function technicalAeoCrawl(project2, opts) {
|
|
|
18507
18521
|
`Links: ${crawl.counts.edges} internal/external observations \xB7 ${crawl.counts.findings} findings`,
|
|
18508
18522
|
`Dead links: ${formatDeadLinkState(crawl.deadLinks)}`
|
|
18509
18523
|
];
|
|
18524
|
+
lines.push(...inventorySummaryLines(crawl.inventorySummary));
|
|
18510
18525
|
if (crawl.termination) lines.push(`Stopped: ${crawl.termination}`);
|
|
18511
18526
|
console.log(lines.join("\n"));
|
|
18512
18527
|
}
|
|
@@ -18618,6 +18633,21 @@ function partialCrawlNote(res) {
|
|
|
18618
18633
|
if (!res.hasCrawlData || res.complete !== false) return null;
|
|
18619
18634
|
return `Partial crawl (${res.termination ?? "incomplete"}): pages and links beyond the crawl budget were not observed, so a missing page or link is not proof it does not exist.`;
|
|
18620
18635
|
}
|
|
18636
|
+
function availableScanDateLines(dates) {
|
|
18637
|
+
if (!dates) return [];
|
|
18638
|
+
return [
|
|
18639
|
+
`Known scan dates: ${dates.totalDates} distinct dates`,
|
|
18640
|
+
`Recent dates: ${dates.recentDates.join(", ") || "none"}`,
|
|
18641
|
+
`Same month and day: ${dates.matchingMonthDayDates.join(", ") || "none"} (${dates.matchingMonthDayTotal} distinct dates)`
|
|
18642
|
+
];
|
|
18643
|
+
}
|
|
18644
|
+
function inventorySummaryLines(summary) {
|
|
18645
|
+
if (!summary) return [];
|
|
18646
|
+
return [
|
|
18647
|
+
`Stored canonical pages: ${summary.total} total \xB7 ${summary.eligible} eligible \xB7 ${summary.excluded} excluded`,
|
|
18648
|
+
...summary.excludedReasons.map((reason) => `Excluded \xB7 ${reason.healthReason}: ${reason.pages}`)
|
|
18649
|
+
];
|
|
18650
|
+
}
|
|
18621
18651
|
async function technicalAeoCrawlPages(project2, opts) {
|
|
18622
18652
|
const res = await getClient27().getTechnicalAeoCrawlPages(project2, opts);
|
|
18623
18653
|
if (opts.format === "jsonl") {
|
|
@@ -18626,6 +18656,11 @@ async function technicalAeoCrawlPages(project2, opts) {
|
|
|
18626
18656
|
project: project2,
|
|
18627
18657
|
runId: res.runId,
|
|
18628
18658
|
complete: res.complete,
|
|
18659
|
+
runSelection: res.runSelection,
|
|
18660
|
+
requestedDate: res.requestedDate,
|
|
18661
|
+
availableScanDates: res.availableScanDates,
|
|
18662
|
+
inventorySummary: res.inventorySummary,
|
|
18663
|
+
healthReasonCounts: res.healthReasonCounts,
|
|
18629
18664
|
termination: res.termination,
|
|
18630
18665
|
total: res.total,
|
|
18631
18666
|
nextCursor: res.nextCursor
|
|
@@ -18638,14 +18673,16 @@ async function technicalAeoCrawlPages(project2, opts) {
|
|
|
18638
18673
|
return;
|
|
18639
18674
|
}
|
|
18640
18675
|
const note = partialCrawlNote(res);
|
|
18676
|
+
const inventoryLines = inventorySummaryLines(res.inventorySummary);
|
|
18641
18677
|
if (res.pages.length === 0) {
|
|
18642
|
-
console.log([`No persisted crawl pages for "${project2}".`, ...note ? [note] : []].join("\n"));
|
|
18678
|
+
console.log([opts.date && !res.hasCrawlData ? `No persisted crawl pages for "${project2}" on ${opts.date}.` : `No persisted crawl pages for "${project2}".`, ...availableScanDateLines(res.availableScanDates), ...inventoryLines, ...note ? [note] : []].join("\n"));
|
|
18643
18679
|
return;
|
|
18644
18680
|
}
|
|
18645
18681
|
const lines = [`${res.pages.length} of ${res.total} crawl page(s)${res.nextCursor ? " (more available)" : ""}:`, ""];
|
|
18646
|
-
lines.push(
|
|
18682
|
+
lines.push(...inventoryLines);
|
|
18683
|
+
lines.push(`${"Depth".padStart(5)} ${"Score".padStart(5)} ${"Page state".padEnd(21)} URL`);
|
|
18647
18684
|
for (const page of res.pages) {
|
|
18648
|
-
lines.push(`${String(page.depth ?? "-").padStart(5)} ${String(page.auditScore ?? "-").padStart(5)} ${page.indexabilityState.slice(0,
|
|
18685
|
+
lines.push(`${String(page.depth ?? "-").padStart(5)} ${String(page.auditScore ?? "-").padStart(5)} ${(page.healthReason ?? page.healthState ?? page.indexabilityState).slice(0, 21).padEnd(21)} ${page.url}`);
|
|
18649
18686
|
}
|
|
18650
18687
|
if (res.nextCursor) lines.push(`
|
|
18651
18688
|
Next cursor: ${res.nextCursor}`);
|
|
@@ -19003,12 +19040,12 @@ var TECHNICAL_AEO_CLI_COMMANDS_BASE = [
|
|
|
19003
19040
|
},
|
|
19004
19041
|
{
|
|
19005
19042
|
path: ["technical-aeo", "crawl"],
|
|
19006
|
-
usage: "canonry technical-aeo crawl <project> [--run-id <id>] [--format json]",
|
|
19007
|
-
options: { "run-id": stringOption() },
|
|
19043
|
+
usage: "canonry technical-aeo crawl <project> [--run-id <id> | --date YYYY-MM-DD] [--format json]",
|
|
19044
|
+
options: { "run-id": stringOption(), date: stringOption() },
|
|
19008
19045
|
run: async (input) => {
|
|
19009
|
-
const usage = "canonry technical-aeo crawl <project> [--run-id <id>] [--format json]";
|
|
19046
|
+
const usage = "canonry technical-aeo crawl <project> [--run-id <id> | --date YYYY-MM-DD] [--format json]";
|
|
19010
19047
|
const project2 = requireProject(input, "technical-aeo.crawl", usage);
|
|
19011
|
-
await technicalAeoCrawl(project2, { runId: getString(input.values, "run-id"), format: input.format });
|
|
19048
|
+
await technicalAeoCrawl(project2, { runId: getString(input.values, "run-id"), date: getString(input.values, "date"), format: input.format });
|
|
19012
19049
|
}
|
|
19013
19050
|
},
|
|
19014
19051
|
{
|
|
@@ -19148,9 +19185,10 @@ Usage: ${usage}`, {
|
|
|
19148
19185
|
},
|
|
19149
19186
|
{
|
|
19150
19187
|
path: ["technical-aeo", "crawl-pages"],
|
|
19151
|
-
usage: "canonry technical-aeo crawl-pages <project> [--run-id <id>] [--inventory-eligible true|false] [--fetch-state <state>] [--indexability-state <state>] [--audit-state <state>] [--sort url|path|score-asc|score-desc] [--cursor <cursor>] [--limit <n>] [--format json|jsonl]",
|
|
19188
|
+
usage: "canonry technical-aeo crawl-pages <project> [--run-id <id> | --date YYYY-MM-DD] [--inventory-eligible true|false] [--fetch-state <state>] [--indexability-state <state>] [--audit-state <state>] [--sort url|path|score-asc|score-desc] [--cursor <cursor>] [--limit <n>] [--format json|jsonl]",
|
|
19152
19189
|
options: {
|
|
19153
19190
|
"run-id": stringOption(),
|
|
19191
|
+
date: stringOption(),
|
|
19154
19192
|
"inventory-eligible": stringOption(),
|
|
19155
19193
|
"fetch-state": stringOption(),
|
|
19156
19194
|
"indexability-state": stringOption(),
|
|
@@ -19160,7 +19198,7 @@ Usage: ${usage}`, {
|
|
|
19160
19198
|
limit: stringOption()
|
|
19161
19199
|
},
|
|
19162
19200
|
run: async (input) => {
|
|
19163
|
-
const usage = "canonry technical-aeo crawl-pages <project> [--run-id <id>] [--inventory-eligible true|false] [--fetch-state <state>] [--indexability-state <state>] [--audit-state <state>] [--sort url|path|score-asc|score-desc] [--cursor <cursor>] [--limit <n>] [--format json|jsonl]";
|
|
19201
|
+
const usage = "canonry technical-aeo crawl-pages <project> [--run-id <id> | --date YYYY-MM-DD] [--inventory-eligible true|false] [--fetch-state <state>] [--indexability-state <state>] [--audit-state <state>] [--sort url|path|score-asc|score-desc] [--cursor <cursor>] [--limit <n>] [--format json|jsonl]";
|
|
19164
19202
|
const project2 = requireProject(input, "technical-aeo.crawl-pages", usage);
|
|
19165
19203
|
const sort = getString(input.values, "sort");
|
|
19166
19204
|
if (sort !== void 0 && !["url", "path", "score-asc", "score-desc"].includes(sort)) {
|
|
@@ -19172,6 +19210,7 @@ Usage: ${usage}`, {
|
|
|
19172
19210
|
}
|
|
19173
19211
|
await technicalAeoCrawlPages(project2, {
|
|
19174
19212
|
runId: getString(input.values, "run-id"),
|
|
19213
|
+
date: getString(input.values, "date"),
|
|
19175
19214
|
inventoryEligible: parseOptionalBoolean(input, "inventory-eligible", { command: "technical-aeo.crawl-pages", usage }),
|
|
19176
19215
|
fetchState: getString(input.values, "fetch-state"),
|
|
19177
19216
|
indexabilityState: getString(input.values, "indexability-state"),
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
computeFactorAverages,
|
|
6
6
|
layoutSiteCrawlGraphInput,
|
|
7
7
|
persistSiteCrawlGraphLayout
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-T2V2PESH.js";
|
|
9
9
|
import {
|
|
10
10
|
PACKAGE_VERSION,
|
|
11
11
|
canonryMcpTools
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-ZLB7WX5D.js";
|
|
13
13
|
import {
|
|
14
14
|
HISTORY_WINDOW_RUNS,
|
|
15
15
|
IntelligenceService,
|
|
@@ -83,7 +83,7 @@ import {
|
|
|
83
83
|
siteCrawlPages,
|
|
84
84
|
siteCrawlSnapshots,
|
|
85
85
|
trafficSources
|
|
86
|
-
} from "./chunk-
|
|
86
|
+
} from "./chunk-KRFTA4I7.js";
|
|
87
87
|
import {
|
|
88
88
|
RunKinds,
|
|
89
89
|
SiteCrawlFetchStates,
|
|
@@ -112,7 +112,7 @@ import {
|
|
|
112
112
|
roundPreservingTotal,
|
|
113
113
|
siteAuditPageFactorSchema,
|
|
114
114
|
wilsonInterval
|
|
115
|
-
} from "./chunk-
|
|
115
|
+
} from "./chunk-DO7XPDG5.js";
|
|
116
116
|
|
|
117
117
|
// src/demo-server.ts
|
|
118
118
|
import { dirname, join as join2 } from "path";
|
package/dist/index.js
CHANGED
|
@@ -3,14 +3,14 @@ import {
|
|
|
3
3
|
createGoogleMarketingCredentialStore,
|
|
4
4
|
createGoogleMarketingRuntime,
|
|
5
5
|
createServer
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-NU62UT2S.js";
|
|
7
7
|
import {
|
|
8
8
|
loadConfig
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-LO56QXPO.js";
|
|
10
|
+
import "./chunk-T2V2PESH.js";
|
|
11
|
+
import "./chunk-ZLB7WX5D.js";
|
|
12
|
+
import "./chunk-KRFTA4I7.js";
|
|
13
|
+
import "./chunk-DO7XPDG5.js";
|
|
14
14
|
export {
|
|
15
15
|
GoogleMarketingRuntimeError,
|
|
16
16
|
createGoogleMarketingCredentialStore,
|