@canonry/canonry 5.33.0 → 6.0.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/portfolio-analysis.md +15 -3
- package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +1 -1
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +16 -11
- package/assets/assets/{AuditHistoryPanel-CcCIqJzU.js → AuditHistoryPanel-Bz2NVB6-.js} +1 -1
- package/assets/assets/{BacklinksPage-B4r9SLIP.js → BacklinksPage-Dte5-usf.js} +1 -1
- package/assets/assets/{HistoryPage-DIpbpqWc.js → HistoryPage-C1S4nw3I.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-BF93li8O.js → MeasurementPropertyPage-DWjsHZdt.js} +1 -1
- package/assets/assets/ProjectPage-CAJ9XvNt.js +8 -0
- package/assets/assets/RunsPage-D9wEJFfV.js +1 -0
- package/assets/assets/SettingsPage-CN3Gew7Y.js +1 -0
- package/assets/assets/SiteHealthSection-Bft5TfoU.js +4 -0
- package/assets/assets/TrafficPage-tkNvySQH.js +40 -0
- package/assets/assets/{TrafficSourceDetailPage-psddSEHZ.js → TrafficSourceDetailPage-RhRfPizo.js} +1 -1
- package/assets/assets/{extract-error-message-BbHnZTOx.js → extract-error-message-tfl_SU7q.js} +1 -1
- package/assets/assets/index-B11OXdEC.css +1 -0
- package/assets/assets/index-Cu_xgdb1.js +86 -0
- package/assets/assets/{react-sigma_core.esm.min-6EgecOc3.js → react-sigma_core.esm.min-BUu3mcQJ.js} +1 -1
- package/assets/assets/{v2-overview-adapter-0pVxmjG8.js → v2-overview-adapter-CzeGhaXn.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-UDEZZMV5.js → chunk-4NC5ZAC2.js} +3 -3
- package/dist/{chunk-AQFRQTKY.js → chunk-6M7HSNXJ.js} +2 -2
- package/dist/{chunk-XICZEXE5.js → chunk-F4G7I7T7.js} +23 -135
- package/dist/{chunk-SJX7XJB6.js → chunk-HAALQW4R.js} +121 -18
- package/dist/{chunk-Y23FQK6O.js → chunk-K4356AGP.js} +10 -10
- package/dist/{chunk-G3CMWO26.js → chunk-XGERCNUR.js} +6 -3
- package/dist/{chunk-GIKWL2BQ.js → chunk-ZO62Z2WN.js} +137 -21
- package/dist/cli.js +52 -16
- package/dist/{demo-server-G4BFXA6G.js → demo-server-54OUTZMY.js} +4 -4
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-FVOFIJP3.js → intelligence-service-MEIAMMLT.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +14 -14
- package/assets/assets/ProjectPage-CjadJKsG.js +0 -8
- package/assets/assets/RunRow-C-d8luIA.js +0 -1
- package/assets/assets/RunsPage-DEbjaNYY.js +0 -1
- package/assets/assets/SettingsPage-B5h3wM7i.js +0 -1
- package/assets/assets/SiteHealthSection-Cj2lo1GY.js +0 -4
- package/assets/assets/TrafficPage-CFDh9Rx4.js +0 -40
- package/assets/assets/index-CiWV331j.js +0 -86
- package/assets/assets/index-DX5M4MHr.css +0 -1
|
@@ -3455,6 +3455,9 @@ function brandLabelFromDomain(value) {
|
|
|
3455
3455
|
if (value == null || value.trim() === "") return "";
|
|
3456
3456
|
return getDomainWithoutSuffix2(value, DOMAIN_PARSE_OPTIONS)?.toLowerCase() ?? "";
|
|
3457
3457
|
}
|
|
3458
|
+
function competitorLabelFromDomain(domain) {
|
|
3459
|
+
return brandLabelFromDomain(domain) || domain;
|
|
3460
|
+
}
|
|
3458
3461
|
function hostMatchesDomain(candidate, domain) {
|
|
3459
3462
|
const candidateHost = hostOf(candidate);
|
|
3460
3463
|
const domainHost = hostOf(domain);
|
|
@@ -4126,6 +4129,8 @@ function resolveLocations(projectLocations, requestedLabels) {
|
|
|
4126
4129
|
}
|
|
4127
4130
|
return resolved;
|
|
4128
4131
|
}
|
|
4132
|
+
var PROJECT_QUALIFIED_ALIAS_LIMIT = 20;
|
|
4133
|
+
var PROJECT_QUALIFIED_ALIAS_MAX_LENGTH = 200;
|
|
4129
4134
|
var projectUpsertRequestSchema = z7.object({
|
|
4130
4135
|
displayName: z7.string().min(1),
|
|
4131
4136
|
canonicalDomain: z7.string().min(1),
|
|
@@ -4146,6 +4151,12 @@ var projectUpsertRequestSchema = z7.object({
|
|
|
4146
4151
|
defaultLocation: z7.string().nullable().optional(),
|
|
4147
4152
|
measurement: measurementConfigSchema.optional(),
|
|
4148
4153
|
autoExtractBacklinks: z7.boolean().optional(),
|
|
4154
|
+
/**
|
|
4155
|
+
* Aliases the sentiment evaluator is told are this brand's own names. Each
|
|
4156
|
+
* entry must already be one of `aliases`. Omitted keeps the stored list,
|
|
4157
|
+
* minus names that no longer qualify; `[]` clears.
|
|
4158
|
+
*/
|
|
4159
|
+
qualifiedAliases: z7.array(z7.string().trim().min(1).max(PROJECT_QUALIFIED_ALIAS_MAX_LENGTH)).max(PROJECT_QUALIFIED_ALIAS_LIMIT).optional(),
|
|
4149
4160
|
/**
|
|
4150
4161
|
* Highest star rating that counts as a negative Google review for this
|
|
4151
4162
|
* project's `review.negative` webhook (1-4). Omit to keep the stored value;
|
|
@@ -4167,6 +4178,8 @@ var projectDtoSchema = z7.object({
|
|
|
4167
4178
|
canonicalDomain: z7.string(),
|
|
4168
4179
|
ownedDomains: z7.array(z7.string()).default([]),
|
|
4169
4180
|
aliases: z7.array(z7.string()).default([]),
|
|
4181
|
+
/** Operator-chosen subset of `aliases` that Simple sentiment treats as this brand's own names. */
|
|
4182
|
+
qualifiedAliases: z7.array(z7.string()).default([]),
|
|
4170
4183
|
country: z7.string().length(2),
|
|
4171
4184
|
language: z7.string().min(2),
|
|
4172
4185
|
tags: z7.array(z7.string()).default([]),
|
|
@@ -4259,6 +4272,40 @@ function normalizeProjectAliases(displayName, aliases) {
|
|
|
4259
4272
|
}
|
|
4260
4273
|
return result;
|
|
4261
4274
|
}
|
|
4275
|
+
function resolveProjectQualifiedAliases(identity, requested, competitorNames = []) {
|
|
4276
|
+
const stored = /* @__PURE__ */ new Map();
|
|
4277
|
+
for (const alias of normalizeProjectAliases(identity.displayName, identity.aliases)) {
|
|
4278
|
+
stored.set(alias.toLowerCase(), alias);
|
|
4279
|
+
}
|
|
4280
|
+
const displayKey = brandKeyFromText(identity.displayName ?? "");
|
|
4281
|
+
const competitorKeys = new Set(competitorNames.map(brandKeyFromText).filter((key) => key.length > 0));
|
|
4282
|
+
const value = [];
|
|
4283
|
+
const rejected = [];
|
|
4284
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4285
|
+
for (const raw of requested ?? []) {
|
|
4286
|
+
const name = raw.trim();
|
|
4287
|
+
if (!name) continue;
|
|
4288
|
+
const lower = name.toLowerCase();
|
|
4289
|
+
if (seen.has(lower)) continue;
|
|
4290
|
+
seen.add(lower);
|
|
4291
|
+
const alias = stored.get(lower);
|
|
4292
|
+
if (alias === void 0) {
|
|
4293
|
+
rejected.push({ name, reason: "not-an-alias" });
|
|
4294
|
+
continue;
|
|
4295
|
+
}
|
|
4296
|
+
const key = brandKeyFromText(alias);
|
|
4297
|
+
if (displayKey && key === displayKey) {
|
|
4298
|
+
rejected.push({ name, reason: "display-name" });
|
|
4299
|
+
} else if (key.length < MIN_DOMAIN_BRAND_KEY_LENGTH) {
|
|
4300
|
+
rejected.push({ name, reason: "too-short" });
|
|
4301
|
+
} else if (competitorKeys.has(key)) {
|
|
4302
|
+
rejected.push({ name, reason: "competitor-collision" });
|
|
4303
|
+
} else {
|
|
4304
|
+
value.push(alias);
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
return { value: value.sort(), rejected };
|
|
4308
|
+
}
|
|
4262
4309
|
function effectiveBrandNames(project) {
|
|
4263
4310
|
const names = [];
|
|
4264
4311
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6558,6 +6605,10 @@ var configSpecSchema = z16.object({
|
|
|
6558
6605
|
canonicalDomain: z16.string().min(1),
|
|
6559
6606
|
ownedDomains: z16.array(z16.string().min(1)).optional().default([]),
|
|
6560
6607
|
aliases: z16.array(z16.string().min(1)).optional().default([]),
|
|
6608
|
+
// No default on purpose: an apply that omits it keeps the project's stored
|
|
6609
|
+
// list, minus names that no longer qualify against this spec's `aliases`,
|
|
6610
|
+
// display name and competitors. Each entry must be an alias.
|
|
6611
|
+
qualifiedAliases: z16.array(z16.string().min(1).max(PROJECT_QUALIFIED_ALIAS_MAX_LENGTH)).max(PROJECT_QUALIFIED_ALIAS_LIMIT).optional(),
|
|
6561
6612
|
country: z16.string().length(2),
|
|
6562
6613
|
language: z16.string().min(2),
|
|
6563
6614
|
queries: configQueryListSchema.optional(),
|
|
@@ -6623,7 +6674,12 @@ var simpleMeasurementIdentitySchema = z17.object({
|
|
|
6623
6674
|
displayName: z17.string(),
|
|
6624
6675
|
aliases: z17.array(z17.string()),
|
|
6625
6676
|
canonicalDomain: z17.string(),
|
|
6626
|
-
ownedDomains: z17.array(z17.string())
|
|
6677
|
+
ownedDomains: z17.array(z17.string()),
|
|
6678
|
+
// Sentiment-only: the aliases the evaluator is told are this brand's own
|
|
6679
|
+
// names. Omitted when empty, so a sidecar that never opted in keeps its
|
|
6680
|
+
// checksum. No bounds or matcher rules here: a later change to the write
|
|
6681
|
+
// rules must never make a stored sidecar unreadable.
|
|
6682
|
+
qualifiedAliases: z17.array(z17.string()).optional()
|
|
6627
6683
|
}).strict();
|
|
6628
6684
|
var simpleMeasurementEngineSchema = z17.object({
|
|
6629
6685
|
provider: providerNameSchema.refine((value) => value.trim().length > 0, {
|
|
@@ -6711,9 +6767,17 @@ var simpleMeasurementDefinitionSchema = z17.object({
|
|
|
6711
6767
|
competitors: z17.array(simpleMeasurementCompetitorSchema).optional(),
|
|
6712
6768
|
queries: z17.array(simpleMeasurementQuerySchema)
|
|
6713
6769
|
}).strict().superRefine(addCollectionIssues);
|
|
6770
|
+
function simpleMeasurementCompetitorNames(competitors) {
|
|
6771
|
+
return competitors?.flatMap((competitor) => [competitor.label, ...competitor.aliases]) ?? [];
|
|
6772
|
+
}
|
|
6714
6773
|
function buildSimpleMeasurementDefinition(input) {
|
|
6715
6774
|
const parsed = simpleMeasurementDefinitionInputSchema.parse(input);
|
|
6716
6775
|
const classifier = compileQueryClassifier(effectiveBrandNames(parsed.identity));
|
|
6776
|
+
const qualifiedAliases = resolveProjectQualifiedAliases(
|
|
6777
|
+
{ displayName: parsed.identity.displayName, aliases: parsed.identity.aliases },
|
|
6778
|
+
parsed.identity.qualifiedAliases,
|
|
6779
|
+
simpleMeasurementCompetitorNames(parsed.competitors)
|
|
6780
|
+
).value;
|
|
6717
6781
|
return simpleMeasurementDefinitionSchema.parse({
|
|
6718
6782
|
schemaVersion: SIMPLE_MEASUREMENT_DEFINITION_SCHEMA_VERSION,
|
|
6719
6783
|
capturedAt: parsed.capturedAt,
|
|
@@ -6721,7 +6785,8 @@ function buildSimpleMeasurementDefinition(input) {
|
|
|
6721
6785
|
displayName: parsed.identity.displayName,
|
|
6722
6786
|
aliases: [...parsed.identity.aliases],
|
|
6723
6787
|
canonicalDomain: parsed.identity.canonicalDomain,
|
|
6724
|
-
ownedDomains: [...parsed.identity.ownedDomains]
|
|
6788
|
+
ownedDomains: [...parsed.identity.ownedDomains],
|
|
6789
|
+
...qualifiedAliases.length ? { qualifiedAliases } : {}
|
|
6725
6790
|
},
|
|
6726
6791
|
country: parsed.country,
|
|
6727
6792
|
language: parsed.language,
|
|
@@ -6765,7 +6830,9 @@ function canonicalSimpleMeasurementDefinitionJson(definition) {
|
|
|
6765
6830
|
displayName: parsed.identity.displayName,
|
|
6766
6831
|
aliases: [...parsed.identity.aliases].sort(compareText3),
|
|
6767
6832
|
canonicalDomain: parsed.identity.canonicalDomain,
|
|
6768
|
-
ownedDomains: [...parsed.identity.ownedDomains].sort(compareText3)
|
|
6833
|
+
ownedDomains: [...parsed.identity.ownedDomains].sort(compareText3),
|
|
6834
|
+
// Absent and empty serialize alike, so pre-feature checksums never move.
|
|
6835
|
+
...parsed.identity.qualifiedAliases?.length ? { qualifiedAliases: [...parsed.identity.qualifiedAliases].sort(compareText3) } : {}
|
|
6769
6836
|
},
|
|
6770
6837
|
country: parsed.country,
|
|
6771
6838
|
language: parsed.language,
|
|
@@ -7995,8 +8062,16 @@ var measurementPortfolioAnswerSourcesSchema = z22.object({
|
|
|
7995
8062
|
properties: measurementDemoCountSchema,
|
|
7996
8063
|
answers: measurementDemoCountSchema,
|
|
7997
8064
|
domains: z22.array(measurementPortfolioCountedDomainSchema).max(MEASUREMENT_PORTFOLIO_ANSWER_SOURCES_LIMIT),
|
|
7998
|
-
domainTotal: measurementDemoCountSchema
|
|
8065
|
+
domainTotal: measurementDemoCountSchema,
|
|
8066
|
+
/**
|
|
8067
|
+
* Answers in the basis that cite any of the project's own domains, each
|
|
8068
|
+
* answer once. Optional only so an older server's response still parses.
|
|
8069
|
+
*/
|
|
8070
|
+
ownDomainAnswers: measurementDemoCountSchema.optional()
|
|
7999
8071
|
}).strict().superRefine((sources, ctx) => {
|
|
8072
|
+
if (sources.ownDomainAnswers !== void 0 && sources.ownDomainAnswers > sources.answers) {
|
|
8073
|
+
ctx.addIssue({ code: z22.ZodIssueCode.custom, path: ["ownDomainAnswers"], message: "Own-domain answers cannot exceed the basis" });
|
|
8074
|
+
}
|
|
8000
8075
|
if (sources.domains.length > sources.domainTotal) {
|
|
8001
8076
|
ctx.addIssue({ code: z22.ZodIssueCode.custom, path: ["domainTotal"], message: "Total cannot be smaller than the returned domains" });
|
|
8002
8077
|
}
|
|
@@ -8347,7 +8422,14 @@ var measurementChangedPropertySchema = measurementDemoPropertySchema.extend({
|
|
|
8347
8422
|
* whenever the denominator held. False when a metric was measured in one
|
|
8348
8423
|
* run only.
|
|
8349
8424
|
*/
|
|
8350
|
-
withinNoise: z22.boolean().optional()
|
|
8425
|
+
withinNoise: z22.boolean().optional(),
|
|
8426
|
+
/**
|
|
8427
|
+
* The same noise rule for each signal alone: a Property beyond noise overall
|
|
8428
|
+
* can still have one signal that moved two answers or fewer, which is not a
|
|
8429
|
+
* gain or loss on its own. Null when that signal cannot be sized.
|
|
8430
|
+
*/
|
|
8431
|
+
mentionWithinNoise: z22.boolean().nullable().optional(),
|
|
8432
|
+
citationWithinNoise: z22.boolean().nullable().optional()
|
|
8351
8433
|
}).strict();
|
|
8352
8434
|
var measurementChangesResponseSchema = z22.object({
|
|
8353
8435
|
current: measurementComparableRunSchema,
|
|
@@ -9195,6 +9277,14 @@ function formatDate(iso) {
|
|
|
9195
9277
|
return iso;
|
|
9196
9278
|
}
|
|
9197
9279
|
}
|
|
9280
|
+
var MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
9281
|
+
function formatCalendarDay(day, now = /* @__PURE__ */ new Date()) {
|
|
9282
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(day);
|
|
9283
|
+
const month = match ? MONTH_NAMES[Number(match[2]) - 1] : void 0;
|
|
9284
|
+
if (!match || !month) return day;
|
|
9285
|
+
const label = `${month} ${Number(match[3])}`;
|
|
9286
|
+
return Number(match[1]) === now.getFullYear() ? label : `${label}, ${match[1]}`;
|
|
9287
|
+
}
|
|
9198
9288
|
function formatIsoDate(iso) {
|
|
9199
9289
|
if (!iso) return "\u2014";
|
|
9200
9290
|
try {
|
|
@@ -9573,8 +9663,8 @@ function dayOf(value) {
|
|
|
9573
9663
|
function sortedUnique(days) {
|
|
9574
9664
|
return [...new Set(days.filter((d) => d !== null))].sort();
|
|
9575
9665
|
}
|
|
9576
|
-
function normalizeEngineChange(provider, entry) {
|
|
9577
|
-
const engine =
|
|
9666
|
+
function normalizeEngineChange(provider, entry, engineName) {
|
|
9667
|
+
const engine = engineName(provider);
|
|
9578
9668
|
const perChange = Array.isArray(entry.changes) ? entry.changes : [];
|
|
9579
9669
|
const changeCount = countOf(entry.changeCount) || perChange.length;
|
|
9580
9670
|
const unconfirmedCount = Math.min(
|
|
@@ -9610,8 +9700,8 @@ function joinNames(names) {
|
|
|
9610
9700
|
}
|
|
9611
9701
|
function whenPhrase(dates, count2) {
|
|
9612
9702
|
if (dates.length === 0) return null;
|
|
9613
|
-
if (dates.length === 1 && count2 <= 1) return `on ${dates[0]}`;
|
|
9614
|
-
return `more than once between ${dates[0]} and ${dates[dates.length - 1]}`;
|
|
9703
|
+
if (dates.length === 1 && count2 <= 1) return `on ${formatCalendarDay(dates[0])}`;
|
|
9704
|
+
return `more than once between ${formatCalendarDay(dates[0])} and ${formatCalendarDay(dates[dates.length - 1])}`;
|
|
9615
9705
|
}
|
|
9616
9706
|
function engineClause(change) {
|
|
9617
9707
|
const confirmedWhen = whenPhrase(change.confirmedDates, change.confirmedCount);
|
|
@@ -9633,10 +9723,11 @@ function uncheckedTailSentence(entries) {
|
|
|
9633
9723
|
const unchecked = entries.filter((e) => !e.checkedThroughPeriodEnd && e.knownGoodAsOf !== null);
|
|
9634
9724
|
if (unchecked.length === 0) return null;
|
|
9635
9725
|
const oldest = unchecked.map((e) => e.knownGoodAsOf).sort()[0];
|
|
9636
|
-
return `We last checked for model updates on ${oldest}, and this period runs past that date, so there may be later updates we do not know about.`;
|
|
9726
|
+
return `We last checked for model updates on ${formatCalendarDay(oldest)}, and this period runs past that date, so there may be later updates we do not know about.`;
|
|
9637
9727
|
}
|
|
9638
|
-
function buildModelChangeNotice(disclosures) {
|
|
9639
|
-
const
|
|
9728
|
+
function buildModelChangeNotice(disclosures, options = {}) {
|
|
9729
|
+
const engineName = options.engineName ?? answerEngineName;
|
|
9730
|
+
const entries = Object.entries(disclosures ?? {}).map(([provider, entry]) => normalizeEngineChange(provider, entry ?? {}, engineName)).sort((a, b) => a.engine.localeCompare(b.engine));
|
|
9640
9731
|
if (entries.length === 0) return null;
|
|
9641
9732
|
const changed = entries.filter((e) => e.confirmedCount + e.unconfirmedCount > 0);
|
|
9642
9733
|
if (changed.length === 0) {
|
|
@@ -9644,7 +9735,7 @@ function buildModelChangeNotice(disclosures) {
|
|
|
9644
9735
|
const plural = engines.length > 1;
|
|
9645
9736
|
const tail2 = uncheckedTailSentence(entries);
|
|
9646
9737
|
const checked = [...new Set(entries.map((e) => e.knownGoodAsOf).filter((d) => d !== null))].sort();
|
|
9647
|
-
const freshness = tail2 ?? (checked.length > 0 ? `We last checked for model updates on ${checked[0]}.` : null);
|
|
9738
|
+
const freshness = tail2 ?? (checked.length > 0 ? `We last checked for model updates on ${formatCalendarDay(checked[0])}.` : null);
|
|
9648
9739
|
return {
|
|
9649
9740
|
kind: "no-known-change",
|
|
9650
9741
|
text: `No model updates are on record for ${joinNames(engines)} in this period.`,
|
|
@@ -11653,8 +11744,7 @@ var ProviderIds = {
|
|
|
11653
11744
|
muse: "muse",
|
|
11654
11745
|
local: "local",
|
|
11655
11746
|
cdpChatgpt: "cdp:chatgpt",
|
|
11656
|
-
zai: "zai"
|
|
11657
|
-
deepinfra: "deepinfra"
|
|
11747
|
+
zai: "zai"
|
|
11658
11748
|
};
|
|
11659
11749
|
var PROVIDER_IDS = Object.values(ProviderIds);
|
|
11660
11750
|
var SweepProviderIds = {
|
|
@@ -11671,13 +11761,16 @@ var AgentProviderIds = {
|
|
|
11671
11761
|
claude: ProviderIds.claude,
|
|
11672
11762
|
openai: ProviderIds.openai,
|
|
11673
11763
|
gemini: ProviderIds.gemini,
|
|
11674
|
-
zai: ProviderIds.zai
|
|
11675
|
-
deepinfra: ProviderIds.deepinfra
|
|
11764
|
+
zai: ProviderIds.zai
|
|
11676
11765
|
};
|
|
11677
11766
|
var AGENT_PROVIDER_IDS = Object.values(AgentProviderIds);
|
|
11678
11767
|
function isAgentProviderId(value) {
|
|
11679
11768
|
return AGENT_PROVIDER_IDS.includes(value);
|
|
11680
11769
|
}
|
|
11770
|
+
var RETIRED_AGENT_PROVIDER_IDS = ["deepinfra"];
|
|
11771
|
+
function isRetiredAgentProviderId(value) {
|
|
11772
|
+
return RETIRED_AGENT_PROVIDER_IDS.includes(value);
|
|
11773
|
+
}
|
|
11681
11774
|
var LlmCapabilities = {
|
|
11682
11775
|
agent: "agent",
|
|
11683
11776
|
analyze: "analyze",
|
|
@@ -13981,6 +14074,12 @@ var sourceCategoryCountSchema = z43.object({
|
|
|
13981
14074
|
});
|
|
13982
14075
|
var sourceRankEntrySchema = z43.object({
|
|
13983
14076
|
domain: z43.string(),
|
|
14077
|
+
/**
|
|
14078
|
+
* 1-based position by `count` over the whole scope, before any `limit`.
|
|
14079
|
+
* Domains with equal counts share a rank (competition ranking: 1, 2, 2, 4).
|
|
14080
|
+
* Optional only so an older server's response still parses.
|
|
14081
|
+
*/
|
|
14082
|
+
rank: z43.number().int().positive().optional(),
|
|
13984
14083
|
count: z43.number().int(),
|
|
13985
14084
|
/** Share of the list's `totalCitedSlots`, 0..1 (at wire precision). */
|
|
13986
14085
|
percentage: fraction(),
|
|
@@ -15251,7 +15350,7 @@ function agentVisibilityEvidence(report, project, retrievedAt) {
|
|
|
15251
15350
|
))
|
|
15252
15351
|
};
|
|
15253
15352
|
}
|
|
15254
|
-
var agentProviderIdSchema = z49.enum(["claude", "openai", "gemini", "zai"
|
|
15353
|
+
var agentProviderIdSchema = z49.enum(["claude", "openai", "gemini", "zai"]);
|
|
15255
15354
|
var agentProviderOptionDtoSchema = z49.object({
|
|
15256
15355
|
/** Stable identifier — what clients pass back as `provider` on the prompt endpoint. */
|
|
15257
15356
|
id: agentProviderIdSchema,
|
|
@@ -21247,6 +21346,7 @@ export {
|
|
|
21247
21346
|
hostOf,
|
|
21248
21347
|
registrableDomain,
|
|
21249
21348
|
brandLabelFromDomain,
|
|
21349
|
+
competitorLabelFromDomain,
|
|
21250
21350
|
hostMatchesDomain,
|
|
21251
21351
|
hostMatchesAnyDomain,
|
|
21252
21352
|
extractDomainsFromText,
|
|
@@ -21305,6 +21405,7 @@ export {
|
|
|
21305
21405
|
normalizeProjectDomain,
|
|
21306
21406
|
effectiveDomains,
|
|
21307
21407
|
normalizeProjectAliases,
|
|
21408
|
+
resolveProjectQualifiedAliases,
|
|
21308
21409
|
effectiveBrandNames,
|
|
21309
21410
|
projectConfigSchema,
|
|
21310
21411
|
resolveConfigSpecQueries,
|
|
@@ -21356,6 +21457,7 @@ export {
|
|
|
21356
21457
|
compileMeasurementPlanPreview,
|
|
21357
21458
|
canonicalMeasurementPlanJson,
|
|
21358
21459
|
simpleMeasurementDefinitionSchema,
|
|
21460
|
+
simpleMeasurementCompetitorNames,
|
|
21359
21461
|
buildSimpleMeasurementDefinition,
|
|
21360
21462
|
canonicalSimpleMeasurementDefinitionJson,
|
|
21361
21463
|
visibilityReportRequestSchema,
|
|
@@ -21624,6 +21726,7 @@ export {
|
|
|
21624
21726
|
AgentProviderIds,
|
|
21625
21727
|
AGENT_PROVIDER_IDS,
|
|
21626
21728
|
isAgentProviderId,
|
|
21729
|
+
isRetiredAgentProviderId,
|
|
21627
21730
|
LlmCapabilities,
|
|
21628
21731
|
LLM_CAPABILITIES,
|
|
21629
21732
|
providerSummaryEntryDtoSchema,
|
|
@@ -128,7 +128,7 @@ import {
|
|
|
128
128
|
trafficSeriesGranularitySchema,
|
|
129
129
|
visibilityCompareSelectionSchema,
|
|
130
130
|
visibilityReportRequestSchema
|
|
131
|
-
} from "./chunk-
|
|
131
|
+
} from "./chunk-HAALQW4R.js";
|
|
132
132
|
|
|
133
133
|
// src/cli-error.ts
|
|
134
134
|
function isMachineFormat(format) {
|
|
@@ -456,7 +456,7 @@ var MEASUREMENT_CHANGES_AGENT_ROWS = 20;
|
|
|
456
456
|
var measurementChangesInputSchema = measurementChangesQuerySchema.safeExtend({
|
|
457
457
|
project: projectNameSchema,
|
|
458
458
|
queryClass: measurementQueryClassFilterSchema.default(AGENT_DEFAULT_QUERY_CLASS).describe("Defaults to non-brand. Call once per class; all pools both classes in rows and distribution."),
|
|
459
|
-
limit: z3.number().int().positive().max(MEASUREMENT_CHANGES_AGENT_ROWS).optional().describe(`Changed rows, largest move first. Default
|
|
459
|
+
limit: z3.number().int().positive().max(MEASUREMENT_CHANGES_AGENT_ROWS).optional().describe(`Changed rows, largest move first, every move beyond noise before any within it. Default and maximum ${MEASUREMENT_CHANGES_AGENT_ROWS}; distribution counts every Property whatever the limit.`)
|
|
460
460
|
}).strict();
|
|
461
461
|
var measurementDataQualityInputSchema = measurementDataQualityQuerySchema.extend({
|
|
462
462
|
project: projectNameSchema
|
|
@@ -948,7 +948,7 @@ var contentTargetsInputSchema = z3.object({
|
|
|
948
948
|
var contentBriefInputSchema = z3.object({
|
|
949
949
|
project: projectNameSchema,
|
|
950
950
|
targetRef: z3.string().min(1).describe("Stable target ref from canonry_content_targets. The target must be ownable; ceded targets are rejected."),
|
|
951
|
-
provider: z3.string().optional().describe("Optional provider override (claude|openai|gemini|zai
|
|
951
|
+
provider: z3.string().optional().describe("Optional provider override (claude|openai|gemini|zai)."),
|
|
952
952
|
model: z3.string().optional().describe("Optional model override within the chosen provider."),
|
|
953
953
|
forceRefresh: z3.boolean().optional().describe("Force a fresh synthesis even if a cached brief exists.")
|
|
954
954
|
});
|
|
@@ -1248,7 +1248,7 @@ var canonryMcpTools = [
|
|
|
1248
1248
|
defineTool({
|
|
1249
1249
|
name: "canonry_sentiment_configure",
|
|
1250
1250
|
title: "Configure project sentiment",
|
|
1251
|
-
description: "Install administrator only: explicitly enable or disable stance-and-evidence sentiment. Both install and project switches default off. Enabling processes future eligible complete runs; historical answers require explicit backfill. On dispatch TypeSafe receives the text of each answer with the frozen subject identity (name, aliases and URLs), the tracked query text and class, the answer engine with its requested and served models, the location, and internal query, subject, Property, group and market identifiers. API keys remain local install configuration and are never accepted here.",
|
|
1251
|
+
description: "Install administrator only: explicitly enable or disable stance-and-evidence sentiment. Both install and project switches default off. Enabling processes future eligible complete runs; historical answers require explicit backfill. On dispatch TypeSafe receives the text of each answer with the frozen subject identity (name, aliases, qualified aliases and URLs), the tracked query text and class, the answer engine with its requested and served models, the location, and internal query, subject, Property, group and market identifiers. API keys remain local install configuration and are never accepted here.",
|
|
1252
1252
|
access: "write",
|
|
1253
1253
|
tier: "monitoring",
|
|
1254
1254
|
inputSchema: sentimentConfigureInputSchema,
|
|
@@ -1515,7 +1515,7 @@ var canonryMcpTools = [
|
|
|
1515
1515
|
defineTool({
|
|
1516
1516
|
name: "canonry_analytics_sources",
|
|
1517
1517
|
title: "Get cited-source rankings",
|
|
1518
|
-
description: "Where AI engines cite from for a project: cited domains ranked by how many answers cite them, each tagged with a category and a surface class (own / direct-competitor / ota-aggregator / editorial-media / other), plus a surface-class roll-up and a per-provider breakdown. Project-wide: without `queryClass` it pools branded and non-brand answers, where branded queries inflate your own domain. With `queryClass` set and neither `runId` nor `window`, this tool reads the latest sweep (`runId` latest); pass `window` to pool every sweep in it. `pooledAcrossRuns` true (`runCount` above 1) means the counts sum several runs and `runIds` is their scope; the legacy `runId` field is then only the newest run in the window. On a v2 measurement plan the class comes from the plan, the same answers as canonry_competitor_landscape with scope=all-markets. `countUnits` says what each count counts: `domainTotal` is distinct domains, never answers or citations. Compare engines by answer count or `answerShare`, never by rank position. Counts come from each answer's stored source list, so every engine is included, Gemini too. A domain counts at most once per answer; `answerShare` is the share of all answers in scope that cite it. `providersWithoutSources` names engines that answered but cited nothing. `limit` caps each ranked list (default 10 for this tool, which keeps each engine list whole); a long-tail rollup keeps the totals. The per-query breakdown is omitted unless `includeByQuery` is true. No LLM calls; probe runs excluded.",
|
|
1518
|
+
description: "Where AI engines cite from for a project: cited domains ranked by how many answers cite them, each tagged with a category and a surface class (own / direct-competitor / ota-aggregator / editorial-media / other), plus a surface-class roll-up and a per-provider breakdown. Every entry carries rank, its position by count in that list over the whole scope (equal counts share a rank); state where a domain places only from its rank, never by counting rows. Project-wide: without `queryClass` it pools branded and non-brand answers, where branded queries inflate your own domain. With `queryClass` set and neither `runId` nor `window`, this tool reads the latest sweep (`runId` latest); pass `window` to pool every sweep in it. `pooledAcrossRuns` true (`runCount` above 1) means the counts sum several runs and `runIds` is their scope; the legacy `runId` field is then only the newest run in the window. On a v2 measurement plan the class comes from the plan, the same answers as canonry_competitor_landscape with scope=all-markets. `countUnits` says what each count counts: `domainTotal` is distinct domains, never answers or citations. Compare engines by answer count or `answerShare`, never by rank position. Counts come from each answer's stored source list, so every engine is included, Gemini too. A domain counts at most once per answer; `answerShare` is the share of all answers in scope that cite it. `providersWithoutSources` names engines that answered but cited nothing. `limit` caps each ranked list (default 10 for this tool, which keeps each engine list whole); a long-tail rollup keeps the totals. The per-query breakdown is omitted unless `includeByQuery` is true. No LLM calls; probe runs excluded.",
|
|
1519
1519
|
access: "read",
|
|
1520
1520
|
tier: "monitoring",
|
|
1521
1521
|
inputSchema: z3.object({
|
|
@@ -2626,7 +2626,7 @@ var canonryMcpTools = [
|
|
|
2626
2626
|
defineTool({
|
|
2627
2627
|
name: "canonry_project_upsert",
|
|
2628
2628
|
title: "Create or replace project",
|
|
2629
|
-
description: "Create or replace a Canonry project. PUT semantics
|
|
2629
|
+
description: "Create or replace a Canonry project. PUT semantics: fields not in the request are reset to their defaults. Provide the full intended project shape. Exceptions: an omitted providerDispatchModes (provider \u2192 sync|batch for scheduled sweeps) keeps the stored preference; send {} to clear it. An omitted qualifiedAliases (the aliases Simple sentiment treats as this brand's own names) keeps the stored list, minus names that no longer qualify (a removed alias, a spelling of the display name, or a competitor's name); send [] to clear it.",
|
|
2630
2630
|
access: "write",
|
|
2631
2631
|
tier: "setup",
|
|
2632
2632
|
inputSchema: projectUpsertInputSchema,
|
|
@@ -2637,7 +2637,7 @@ var canonryMcpTools = [
|
|
|
2637
2637
|
defineTool({
|
|
2638
2638
|
name: "canonry_apply_config",
|
|
2639
2639
|
title: "Apply project config",
|
|
2640
|
-
description: "Apply one Canonry config-as-code project document. Replaces the project to match the config
|
|
2640
|
+
description: "Apply one Canonry config-as-code project document. Replaces the project to match the config; fields omitted from the spec are reset to defaults, with these exceptions: a spec with neither queries nor keywords leaves the tracked-query basket unchanged (to clear the basket, pass an explicit empty queries list); an omitted providerDispatchModes keeps the stored preference; and an omitted qualifiedAliases keeps the stored list, minus names that no longer qualify (a removed alias, a spelling of the display name, or a competitor's name); send [] to clear it. For multi-document YAML, call this tool once per project document.",
|
|
2641
2641
|
access: "write",
|
|
2642
2642
|
tier: "core",
|
|
2643
2643
|
inputSchema: applyConfigInputSchema,
|
|
@@ -2842,7 +2842,7 @@ var canonryMcpTools = [
|
|
|
2842
2842
|
defineTool({
|
|
2843
2843
|
name: "canonry_measurement_portfolio_summary",
|
|
2844
2844
|
title: "Summarize measured Properties",
|
|
2845
|
-
description: "Start here for best and worst Properties. One call returns the weakest Properties, the names answers wrote instead of them, and the domains engines cited for them. Defaults to non-brand queries; state the returned queryClass and never pool branded with non-brand. Returns at most 4 Property rows per list, the most that fit one result; a larger limit is lowered and limitNote says so. For other rows pass groupKey (one metro's weakest), or page canonry_measurement_overview. Mention (answer text names the Property) and citation (a source URL on its page) are separate: report each as numerator/denominator, with any unattributed count beside mention. Denominators count answers, one per query per engine (queries x engines = answers). Group Properties only by each row's metro and submarkets, never by label. namedInsteadInAnswerText lists names written in the answer text of answers that neither named nor cited the Property, counted by answer: they are not citations, and namedInsteadInAnswerTextTotal counts distinct names, not answers. citedDomains and weakestAnswerSources count answers citing each domain, every engine included; weakestAnswerSources pools the weakest rows plus every tied Property, each answer once, and its domainTotal counts distinct domains, not citations. When tiedAtWeakest is set, tiedAtWeakest.count Properties share the weakest rates and the rows are the first of them by name, not a rank: report the count, tiedAtWeakest.byMetro (tied Properties per metro over the whole tie; a Property in two metros counts in both) and tiedAtWeakest.namedInstead (names written instead across the whole tie, counted by distinct answer; namedInsteadTotal is distinct names), and call the rows examples. mentionRanking.strongest/.weakest rank every Property with an available mention rate; .excluded lists the rest with reasons. markets lists every market at one level, worst-first (every metro, or a groupKey's submarkets), whatever the limit. Markets may share Properties and never sum to portfolio totals. Reads stored data only; never starts provider work.",
|
|
2845
|
+
description: "Start here for best and worst Properties. One call returns the weakest Properties, the names answers wrote instead of them, and the domains engines cited for them. Defaults to non-brand queries; state the returned queryClass and never pool branded with non-brand. Returns at most 4 Property rows per list, the most that fit one result; a larger limit is lowered and limitNote says so. For other rows pass groupKey (one metro's weakest), or page canonry_measurement_overview. Mention (answer text names the Property) and citation (a source URL on its page) are separate: report each as numerator/denominator, with any unattributed count beside mention. Denominators count answers, one per query per engine (queries x engines = answers). Group Properties only by each row's metro and submarkets, never by label. namedInsteadInAnswerText lists names written in the answer text of answers that neither named nor cited the Property, counted by answer: they are not citations, and namedInsteadInAnswerTextTotal counts distinct names, not answers. citedDomains and weakestAnswerSources count answers citing each domain, every engine included; weakestAnswerSources pools the weakest rows plus every tied Property, each answer once, and its domainTotal counts distinct domains, not citations. weakestAnswerSources.ownDomainAnswers counts those answers that cite any of your own domains. A Property's citationCoverage counts only answers citing that Property's own page, so 0% citation does not mean your domain went uncited; use ownDomainAnswers for that. When tiedAtWeakest is set, tiedAtWeakest.count Properties share the weakest rates and the rows are the first of them by name, not a rank: report the count, tiedAtWeakest.byMetro (tied Properties per metro over the whole tie; a Property in two metros counts in both) and tiedAtWeakest.namedInstead (names written instead across the whole tie, counted by distinct answer; namedInsteadTotal is distinct names), and call the rows examples. mentionRanking.strongest/.weakest rank every Property with an available mention rate; .excluded lists the rest with reasons. markets lists every market at one level, worst-first (every metro, or a groupKey's submarkets), whatever the limit. Markets may share Properties and never sum to portfolio totals. Reads stored data only; never starts provider work.",
|
|
2846
2846
|
access: "read",
|
|
2847
2847
|
tier: "monitoring",
|
|
2848
2848
|
inputSchema: measurementPortfolioSummaryInputSchema,
|
|
@@ -2902,7 +2902,7 @@ var canonryMcpTools = [
|
|
|
2902
2902
|
defineTool({
|
|
2903
2903
|
name: "canonry_measurement_changes",
|
|
2904
2904
|
title: "Compare stored measurement changes",
|
|
2905
|
-
description: "What changed since the previous sweep: compares the displayed run with the previous run of the same plan revision and execution identity; an incompatible pair returns unavailable with a reason, never a trend. Defaults to non-brand queries: call once per class (queryClass branded for the branded move) and never report one class as the whole. queryClass all pools both classes in rows and distribution and adds metricsByClass, the headline metrics per class; report those per class, never the pooled metrics alone. changedProperties come largest move first (answers that named the Property, then answers that cited it; sort label for alphabetical),
|
|
2905
|
+
description: "What changed since the previous sweep: compares the displayed run with the previous run of the same plan revision and execution identity; an incompatible pair returns unavailable with a reason, never a trend. Defaults to non-brand queries: call once per class (queryClass branded for the branded move) and never report one class as the whole. queryClass all pools both classes in rows and distribution and adds metricsByClass, the headline metrics per class; report those per class, never the pooled metrics alone. changedProperties come largest move first (answers that named the Property, then answers that cited it; sort label for alphabetical), every move beyond noise before any within it, 20 rows by default and at most; totalProperties above the rows returned means rows you did not see. distribution counts every Property in scope: improved, declined, mixed, withinNoise, unchanged, notComparable. mentionAnswersDelta and citationAnswersDelta are signed answer counts; denominatorChanged true means a rate was taken over a different number of answers, so those counts are not like for like and the coverage rates give the direction. A move is the rate change times the larger answer count (the count change when the denominator held). withinNoise true means every move was 2 answers or fewer: within noise, never a gain, loss, trend or regression. mentionWithinNoise and citationWithinNoise apply the same rule to each signal alone: a Property beyond noise can still have one signal within noise, and that signal is not a gain or loss. Report every headline metric, branded included, as previous numerator/denominator to current numerator/denominator. Reads stored data only; it never starts provider work.",
|
|
2906
2906
|
access: "read",
|
|
2907
2907
|
tier: "monitoring",
|
|
2908
2908
|
inputSchema: measurementChangesInputSchema,
|
|
@@ -2910,7 +2910,7 @@ var canonryMcpTools = [
|
|
|
2910
2910
|
openApiOperations: ["GET /api/v1/projects/{name}/measurement-changes"],
|
|
2911
2911
|
handler: (client, input) => {
|
|
2912
2912
|
const { project, ...query } = input;
|
|
2913
|
-
return client.getMeasurementChanges(project, { ...query, queryClass: agentQueryClass(query.queryClass) });
|
|
2913
|
+
return client.getMeasurementChanges(project, { ...query, limit: query.limit ?? MEASUREMENT_CHANGES_AGENT_ROWS, queryClass: agentQueryClass(query.queryClass) });
|
|
2914
2914
|
}
|
|
2915
2915
|
}),
|
|
2916
2916
|
defineTool({
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
PACKAGE_VERSION,
|
|
6
6
|
canonryMcpTools,
|
|
7
7
|
isMachineFormat
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-K4356AGP.js";
|
|
9
9
|
import {
|
|
10
10
|
AGENT_PROVIDER_IDS,
|
|
11
11
|
CANONRY_NPM_PACKAGE_URL,
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
formatCanonryPluginVersionMismatch,
|
|
29
29
|
isInstallMethod,
|
|
30
30
|
isReadOnlyKey,
|
|
31
|
+
isRetiredAgentProviderId,
|
|
31
32
|
isSensitiveDiagnosticQueryKey,
|
|
32
33
|
isStrictSemver,
|
|
33
34
|
normalizeAgentSlug,
|
|
@@ -49,7 +50,7 @@ import {
|
|
|
49
50
|
skillsClientSchema,
|
|
50
51
|
upgradeCaveatFor,
|
|
51
52
|
upgradeCommandFor
|
|
52
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-HAALQW4R.js";
|
|
53
54
|
|
|
54
55
|
// src/config.ts
|
|
55
56
|
import fs from "fs";
|
|
@@ -429,9 +430,11 @@ Do not use "canonry init --force" for recovery. It replaces credentials without
|
|
|
429
430
|
});
|
|
430
431
|
}
|
|
431
432
|
if (!agentProviderSchema.safeParse(parsed.agent?.provider).success) {
|
|
433
|
+
const pinned = parsed.agent?.provider;
|
|
434
|
+
const retired = typeof pinned === "string" && isRetiredAgentProviderId(pinned);
|
|
432
435
|
throw new CliError({
|
|
433
436
|
code: "CONFIG_INVALID",
|
|
434
|
-
message: `Invalid config at ${configPath}: agent.provider must be one of ${AGENT_PROVIDER_IDS.join(", ")}, or left blank.`
|
|
437
|
+
message: retired ? `Invalid config at ${configPath}: agent.provider ${pinned} was removed. Pin one of ${AGENT_PROVIDER_IDS.join(", ")} (with a key under providers.<name>.apiKey), or leave it blank.` : `Invalid config at ${configPath}: agent.provider must be one of ${AGENT_PROVIDER_IDS.join(", ")}, or left blank.`
|
|
435
438
|
});
|
|
436
439
|
}
|
|
437
440
|
if (!agentModelSchema.safeParse(parsed.agent?.model).success) {
|