@ainyc/canonry 4.102.0 → 4.103.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/canonry/references/canonry-cli.md +5 -3
- package/assets/assets/{BacklinksPage-lXt19SLR.js → BacklinksPage-CAzeiWX-.js} +1 -1
- package/assets/assets/{ChartPrimitives-C-sLgrDV.js → ChartPrimitives-Cl91UJcT.js} +1 -1
- package/assets/assets/{ProjectPage-wtvaWIRA.js → ProjectPage-6n6qXa-G.js} +1 -1
- package/assets/assets/{RunRow-Db32k-uH.js → RunRow-DqumjxZP.js} +1 -1
- package/assets/assets/{RunsPage-DwW3Mely.js → RunsPage-DOdjY-ia.js} +1 -1
- package/assets/assets/{SettingsPage-gXn6OGsu.js → SettingsPage-DYW3Lfre.js} +1 -1
- package/assets/assets/{TrafficPage-I49ci4gj.js → TrafficPage-DX8A16LI.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-BCvLztx9.js → TrafficSourceDetailPage-COLKpEny.js} +1 -1
- package/assets/assets/{arrow-left-BpA06YY0.js → arrow-left-DsQ3GWJh.js} +1 -1
- package/assets/assets/{extract-error-message-BZSIQ5iV.js → extract-error-message-DQEs2_vw.js} +1 -1
- package/assets/assets/{index-DSSLug80.js → index-CY4Xe0F8.js} +58 -58
- package/assets/assets/{index-BCuWbxUj.css → index-LsyRFLMG.css} +1 -1
- package/assets/assets/{trash-2-CLkUNdvb.js → trash-2-BARaYs9Y.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-2FUODJQR.js → chunk-23DCY7DL.js} +14 -8
- package/dist/{chunk-3PVCILSN.js → chunk-BTJY73GW.js} +4 -4
- package/dist/{chunk-B2H2RRIT.js → chunk-GNMZTR6Y.js} +102 -16
- package/dist/{chunk-EOQXB63S.js → chunk-Q7FFDQU4.js} +33 -2
- package/dist/cli.js +24 -7
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-RC4BY5HF.js → intelligence-service-GM3RXE2F.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +10 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c}from"./index-
|
|
1
|
+
import{c}from"./index-CY4Xe0F8.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
|
package/assets/index.html
CHANGED
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
|
|
13
13
|
<link rel="apple-touch-icon" href="./apple-touch-icon.png" />
|
|
14
14
|
<title>Canonry</title>
|
|
15
|
-
<script type="module" crossorigin src="./assets/index-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-CY4Xe0F8.js"></script>
|
|
16
16
|
<link rel="modulepreload" crossorigin href="./assets/vendor-tanstack-Be8JdV5M.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="./assets/vendor-radix-CQ77EO2y.js">
|
|
18
18
|
<link rel="modulepreload" crossorigin href="./assets/vendor-recharts-C9EZkgbP.js">
|
|
19
19
|
<link rel="modulepreload" crossorigin href="./assets/vendor-markdown-6dwjPOKK.js">
|
|
20
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
20
|
+
<link rel="stylesheet" crossorigin href="./assets/index-LsyRFLMG.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="root"></div>
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
trafficConnectVercelRequestSchema,
|
|
25
25
|
trafficConnectWordpressRequestSchema,
|
|
26
26
|
trafficEventKindSchema
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-Q7FFDQU4.js";
|
|
28
28
|
|
|
29
29
|
// src/config.ts
|
|
30
30
|
import fs from "fs";
|
|
@@ -5163,7 +5163,9 @@ var ApiClient = class {
|
|
|
5163
5163
|
since: opts.since,
|
|
5164
5164
|
until: opts.until,
|
|
5165
5165
|
lastRuns: opts.lastRuns,
|
|
5166
|
-
groupBy: opts.groupBy
|
|
5166
|
+
groupBy: opts.groupBy,
|
|
5167
|
+
month: opts.month,
|
|
5168
|
+
shareOfVoice: opts.shareOfVoice ? "1" : void 0
|
|
5167
5169
|
}
|
|
5168
5170
|
})
|
|
5169
5171
|
);
|
|
@@ -5886,15 +5888,17 @@ var canonryMcpTools = [
|
|
|
5886
5888
|
defineTool({
|
|
5887
5889
|
name: "canonry_visibility_stats",
|
|
5888
5890
|
title: "Get aggregated mention/citation stats",
|
|
5889
|
-
description: 'Per-query mention (answer-text) and citation (source-list) counts WITH a sample size, pooled across many answer-visibility runs (probe-excluded) \u2014 the data to compute a confidence-aware (Wilson) proportion or detect drift without fetching every run. Tri-state aware: `checked` (the n for the mention proportion) counts only snapshots where answerMentioned was recorded; `null` ("not checked") is excluded, never counted as not-mentioned. Returns per-query `total`/`checked`/`mentioned`/`cited` + derived `mentionRate` (mentioned/checked) and `citedRate` (cited/total), `firstObserved`/`lastObserved`, and pooled `totals`. Window with `since`/`until` (ISO)
|
|
5891
|
+
description: 'Per-query mention (answer-text) and citation (source-list) counts WITH a sample size, pooled across many answer-visibility runs (probe-excluded) \u2014 the data to compute a confidence-aware (Wilson) proportion or detect drift without fetching every run. Tri-state aware: `checked` (the n for the mention proportion) counts only snapshots where answerMentioned was recorded; `null` ("not checked") is excluded, never counted as not-mentioned. Returns per-query `total`/`checked`/`mentioned`/`cited` + derived `mentionRate` (mentioned/checked) and `citedRate` (cited/total), `firstObserved`/`lastObserved`, and pooled `totals`. Window with `since`/`until` (ISO), `lastRuns`, or `month=YYYY-MM` (mutually exclusive); with none set, EVERY completed/partial run is pooled (`window.runCount` says how many) \u2014 pass `lastRuns` for a recent sample. Set `groupBy=provider` for a per-provider breakdown whose counts sum to the pooled counts (`groupBy` is omitted from the response otherwise). Set `shareOfVoice=true` for pooled project-vs-tracked-competitor brand-mention share across the same attributed snapshot set.',
|
|
5890
5892
|
access: "read",
|
|
5891
5893
|
tier: "monitoring",
|
|
5892
5894
|
inputSchema: z2.object({
|
|
5893
5895
|
project: projectNameSchema,
|
|
5894
|
-
since: z2.string().optional().describe("Inclusive lower bound on run createdAt (ISO 8601). A date-only value (YYYY-MM-DD) is the start of that UTC day. Mutually exclusive with lastRuns."),
|
|
5895
|
-
until: z2.string().optional().describe("Inclusive upper bound on run createdAt (ISO 8601). A date-only value (YYYY-MM-DD) covers the whole UTC day (through 23:59:59.999). Mutually exclusive with lastRuns."),
|
|
5896
|
-
lastRuns: z2.number().int().positive().optional().describe("Aggregate only the most recent N answer-visibility runs. Mutually exclusive with since/until."),
|
|
5897
|
-
|
|
5896
|
+
since: z2.string().optional().describe("Inclusive lower bound on run createdAt (ISO 8601). A date-only value (YYYY-MM-DD) is the start of that UTC day. Mutually exclusive with lastRuns/month."),
|
|
5897
|
+
until: z2.string().optional().describe("Inclusive upper bound on run createdAt (ISO 8601). A date-only value (YYYY-MM-DD) covers the whole UTC day (through 23:59:59.999). Mutually exclusive with lastRuns/month."),
|
|
5898
|
+
lastRuns: z2.number().int().positive().optional().describe("Aggregate only the most recent N answer-visibility runs. Mutually exclusive with since/until/month."),
|
|
5899
|
+
month: z2.string().optional().describe("Aggregate one calendar month (YYYY-MM), expanded to that month's inclusive UTC bounds. Mutually exclusive with since/until/lastRuns."),
|
|
5900
|
+
groupBy: z2.enum(["provider"]).optional().describe('Set to "provider" for a per-provider breakdown.'),
|
|
5901
|
+
shareOfVoice: z2.boolean().optional().describe("Include pooled project-vs-tracked-competitor brand-mention share across the same window.")
|
|
5898
5902
|
}),
|
|
5899
5903
|
annotations: readAnnotations(),
|
|
5900
5904
|
openApiOperations: ["GET /api/v1/projects/{name}/visibility-stats"],
|
|
@@ -5902,7 +5906,9 @@ var canonryMcpTools = [
|
|
|
5902
5906
|
since: input.since,
|
|
5903
5907
|
until: input.until,
|
|
5904
5908
|
lastRuns: input.lastRuns,
|
|
5905
|
-
groupBy: input.groupBy
|
|
5909
|
+
groupBy: input.groupBy,
|
|
5910
|
+
month: input.month,
|
|
5911
|
+
shareOfVoice: input.shareOfVoice
|
|
5906
5912
|
})
|
|
5907
5913
|
}),
|
|
5908
5914
|
defineTool({
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
loadConfig,
|
|
11
11
|
loadConfigRaw,
|
|
12
12
|
saveConfigPatch
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-23DCY7DL.js";
|
|
14
14
|
import {
|
|
15
15
|
CC_CACHE_DIR,
|
|
16
16
|
DUCKDB_SPEC,
|
|
@@ -115,7 +115,7 @@ import {
|
|
|
115
115
|
siteAuditPages,
|
|
116
116
|
siteAuditSnapshots,
|
|
117
117
|
usageCounters
|
|
118
|
-
} from "./chunk-
|
|
118
|
+
} from "./chunk-GNMZTR6Y.js";
|
|
119
119
|
import {
|
|
120
120
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
121
121
|
AGENT_PROVIDER_IDS,
|
|
@@ -176,7 +176,7 @@ import {
|
|
|
176
176
|
validationError,
|
|
177
177
|
winnabilityClassLabel,
|
|
178
178
|
withRetry
|
|
179
|
-
} from "./chunk-
|
|
179
|
+
} from "./chunk-Q7FFDQU4.js";
|
|
180
180
|
|
|
181
181
|
// src/telemetry.ts
|
|
182
182
|
import crypto from "crypto";
|
|
@@ -6438,7 +6438,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
6438
6438
|
return result;
|
|
6439
6439
|
}
|
|
6440
6440
|
async function backfillInsightsCommand(project, opts) {
|
|
6441
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
6441
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-GM3RXE2F.js");
|
|
6442
6442
|
const config = loadConfig();
|
|
6443
6443
|
const db = createClient(config.database);
|
|
6444
6444
|
migrate(db);
|
|
@@ -73,6 +73,7 @@ import {
|
|
|
73
73
|
brandLabelFromDomain,
|
|
74
74
|
brandMetricsDtoSchema,
|
|
75
75
|
buildHarvestAnchorTerms,
|
|
76
|
+
calendarMonthBounds,
|
|
76
77
|
categorizeSource,
|
|
77
78
|
categorizeSourceWithCompetitors,
|
|
78
79
|
categoryLabel,
|
|
@@ -263,7 +264,7 @@ import {
|
|
|
263
264
|
wordpressSchemaDeployResultDtoSchema,
|
|
264
265
|
wordpressSchemaStatusResultDtoSchema,
|
|
265
266
|
wordpressStatusDtoSchema
|
|
266
|
-
} from "./chunk-
|
|
267
|
+
} from "./chunk-Q7FFDQU4.js";
|
|
267
268
|
|
|
268
269
|
// src/intelligence-service.ts
|
|
269
270
|
import { eq as eq38, desc as desc18, asc as asc6, and as and28, ne as ne5, or as or5, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
|
|
@@ -13258,22 +13259,29 @@ function providerEntries(byProvider) {
|
|
|
13258
13259
|
lastObserved: agg.last ?? ""
|
|
13259
13260
|
})).sort((a, b) => a.provider.localeCompare(b.provider));
|
|
13260
13261
|
}
|
|
13262
|
+
function buildQueryAttribution(projectQueries) {
|
|
13263
|
+
const byId = /* @__PURE__ */ new Map();
|
|
13264
|
+
const byText = /* @__PURE__ */ new Map();
|
|
13265
|
+
for (const q of projectQueries) {
|
|
13266
|
+
byId.set(q.id, q);
|
|
13267
|
+
byText.set(q.query, q);
|
|
13268
|
+
}
|
|
13269
|
+
return { byId, byText };
|
|
13270
|
+
}
|
|
13271
|
+
function resolveCurrentQuery(attribution, snap) {
|
|
13272
|
+
if (snap.queryId && attribution.byId.has(snap.queryId)) return attribution.byId.get(snap.queryId);
|
|
13273
|
+
if (snap.queryText && attribution.byText.has(snap.queryText)) return attribution.byText.get(snap.queryText);
|
|
13274
|
+
return void 0;
|
|
13275
|
+
}
|
|
13261
13276
|
function computeVisibilityStats(input) {
|
|
13262
13277
|
const { groupBy } = input;
|
|
13263
13278
|
const wantProviders = groupBy === "provider";
|
|
13264
|
-
const
|
|
13265
|
-
const queryByText = /* @__PURE__ */ new Map();
|
|
13266
|
-
for (const q of input.queries) {
|
|
13267
|
-
queryById.set(q.id, q);
|
|
13268
|
-
queryByText.set(q.query, q);
|
|
13269
|
-
}
|
|
13279
|
+
const attribution = buildQueryAttribution(input.queries);
|
|
13270
13280
|
const byQuery = /* @__PURE__ */ new Map();
|
|
13271
13281
|
const totals = emptyAgg();
|
|
13272
13282
|
const totalsByProvider = /* @__PURE__ */ new Map();
|
|
13273
13283
|
for (const snap of input.snapshots) {
|
|
13274
|
-
|
|
13275
|
-
if (snap.queryId && queryById.has(snap.queryId)) resolved = queryById.get(snap.queryId);
|
|
13276
|
-
else if (snap.queryText && queryByText.has(snap.queryText)) resolved = queryByText.get(snap.queryText);
|
|
13284
|
+
const resolved = resolveCurrentQuery(attribution, snap);
|
|
13277
13285
|
if (!resolved) continue;
|
|
13278
13286
|
let bucket = byQuery.get(resolved.id);
|
|
13279
13287
|
if (!bucket) {
|
|
@@ -13308,7 +13316,14 @@ function computeVisibilityStats(input) {
|
|
|
13308
13316
|
async function visibilityStatsRoutes(app) {
|
|
13309
13317
|
app.get("/projects/:name/visibility-stats", async (request, reply) => {
|
|
13310
13318
|
const project = resolveProject(app.db, request.params.name);
|
|
13311
|
-
const {
|
|
13319
|
+
const {
|
|
13320
|
+
since: sinceRaw,
|
|
13321
|
+
until: untilRaw,
|
|
13322
|
+
lastRuns: lastRunsRaw,
|
|
13323
|
+
groupBy: groupByRaw,
|
|
13324
|
+
month: monthRaw,
|
|
13325
|
+
shareOfVoice: shareOfVoiceRaw
|
|
13326
|
+
} = request.query;
|
|
13312
13327
|
let groupBy = null;
|
|
13313
13328
|
if (groupByRaw !== void 0 && groupByRaw !== "") {
|
|
13314
13329
|
if (groupByRaw !== "provider") throw validationError('"groupBy" must be "provider"');
|
|
@@ -13317,11 +13332,17 @@ async function visibilityStatsRoutes(app) {
|
|
|
13317
13332
|
const hasSince = sinceRaw !== void 0 && sinceRaw !== "";
|
|
13318
13333
|
const hasUntil = untilRaw !== void 0 && untilRaw !== "";
|
|
13319
13334
|
const hasLastRuns = lastRunsRaw !== void 0 && lastRunsRaw !== "";
|
|
13335
|
+
const hasMonth = monthRaw !== void 0 && monthRaw !== "";
|
|
13336
|
+
const wantShareOfVoice = shareOfVoiceRaw === "1" || shareOfVoiceRaw === "true";
|
|
13320
13337
|
if (hasLastRuns && (hasSince || hasUntil)) {
|
|
13321
13338
|
throw validationError('"lastRuns" cannot be combined with "since"/"until" \u2014 use one or the other');
|
|
13322
13339
|
}
|
|
13340
|
+
if (hasMonth && (hasSince || hasUntil || hasLastRuns)) {
|
|
13341
|
+
throw validationError('"month" cannot be combined with "since"/"until"/"lastRuns" \u2014 use one or the other');
|
|
13342
|
+
}
|
|
13323
13343
|
let sinceMs = null;
|
|
13324
13344
|
let untilMs = null;
|
|
13345
|
+
let resolvedMonthWindow = null;
|
|
13325
13346
|
if (hasSince) {
|
|
13326
13347
|
const ms = Date.parse(sinceRaw);
|
|
13327
13348
|
if (Number.isNaN(ms)) throw validationError('"since" must be an ISO 8601 date/time');
|
|
@@ -13332,6 +13353,17 @@ async function visibilityStatsRoutes(app) {
|
|
|
13332
13353
|
if (ms === null) throw validationError('"until" must be an ISO 8601 date/time');
|
|
13333
13354
|
untilMs = ms;
|
|
13334
13355
|
}
|
|
13356
|
+
if (hasMonth) {
|
|
13357
|
+
let bounds;
|
|
13358
|
+
try {
|
|
13359
|
+
bounds = calendarMonthBounds(monthRaw);
|
|
13360
|
+
} catch (err) {
|
|
13361
|
+
throw validationError(err instanceof RangeError ? err.message : '"month" must be in YYYY-MM format');
|
|
13362
|
+
}
|
|
13363
|
+
sinceMs = Date.parse(bounds.since);
|
|
13364
|
+
untilMs = Date.parse(bounds.until);
|
|
13365
|
+
resolvedMonthWindow = bounds;
|
|
13366
|
+
}
|
|
13335
13367
|
if (sinceMs !== null && untilMs !== null && untilMs < sinceMs) {
|
|
13336
13368
|
throw validationError('"until" must be on or after "since"');
|
|
13337
13369
|
}
|
|
@@ -13357,11 +13389,44 @@ async function visibilityStatsRoutes(app) {
|
|
|
13357
13389
|
createdAt: querySnapshots.createdAt
|
|
13358
13390
|
}).from(querySnapshots).where(inArray8(querySnapshots.runId, runIds)).all() : [];
|
|
13359
13391
|
const stats = computeVisibilityStats({ queries: projectQueries, snapshots, groupBy });
|
|
13392
|
+
const queryAttribution = buildQueryAttribution(projectQueries);
|
|
13393
|
+
let shareOfVoice;
|
|
13394
|
+
if (wantShareOfVoice) {
|
|
13395
|
+
const competitorRows = app.db.select({ domain: competitors.domain }).from(competitors).where(eq17(competitors.projectId, project.id)).all();
|
|
13396
|
+
const mentionShareCompetitors = competitorRows.map((c) => ({
|
|
13397
|
+
domain: c.domain,
|
|
13398
|
+
brandTokens: [brandLabelFromDomain(c.domain)].filter((t) => t.length >= 3)
|
|
13399
|
+
}));
|
|
13400
|
+
const sovSnapshots = runIds.length > 0 ? app.db.select({
|
|
13401
|
+
queryId: querySnapshots.queryId,
|
|
13402
|
+
queryText: querySnapshots.queryText,
|
|
13403
|
+
answerMentioned: querySnapshots.answerMentioned,
|
|
13404
|
+
answerText: querySnapshots.answerText
|
|
13405
|
+
}).from(querySnapshots).where(inArray8(querySnapshots.runId, runIds)).all() : [];
|
|
13406
|
+
const attributedSovSnapshots = sovSnapshots.filter((s) => resolveCurrentQuery(queryAttribution, s) !== void 0);
|
|
13407
|
+
const result = buildMentionShare(
|
|
13408
|
+
attributedSovSnapshots.map((s) => ({ projectMentioned: s.answerMentioned === true, answerText: s.answerText })),
|
|
13409
|
+
{ competitors: mentionShareCompetitors }
|
|
13410
|
+
);
|
|
13411
|
+
const b = result.breakdown;
|
|
13412
|
+
const denom = b.projectMentionSnapshots + b.competitorMentionSnapshots;
|
|
13413
|
+
shareOfVoice = {
|
|
13414
|
+
// `null` (not 0) when there is no competitive frame configured — a 0 here
|
|
13415
|
+
// would read as "losing" when the head-to-head metric is simply undefined.
|
|
13416
|
+
percent: competitorRows.length === 0 ? null : denom > 0 ? Math.round(b.projectMentionSnapshots / denom * 100) : 0,
|
|
13417
|
+
projectMentions: b.projectMentionSnapshots,
|
|
13418
|
+
competitorMentions: b.competitorMentionSnapshots,
|
|
13419
|
+
snapshotsWithAnswerText: b.snapshotsWithAnswerText,
|
|
13420
|
+
perCompetitor: b.perCompetitor.map((c) => ({ domain: c.domain, mentions: c.mentionSnapshots }))
|
|
13421
|
+
};
|
|
13422
|
+
}
|
|
13360
13423
|
const response = {
|
|
13361
13424
|
project: project.name,
|
|
13362
13425
|
window: {
|
|
13363
|
-
|
|
13364
|
-
|
|
13426
|
+
// A `month` request echoes the resolved inclusive bounds it expanded to,
|
|
13427
|
+
// so the window is self-documenting regardless of which input was used.
|
|
13428
|
+
since: resolvedMonthWindow ? resolvedMonthWindow.since : hasSince ? sinceRaw : null,
|
|
13429
|
+
until: resolvedMonthWindow ? resolvedMonthWindow.until : hasUntil ? untilRaw : null,
|
|
13365
13430
|
lastRuns,
|
|
13366
13431
|
runCount
|
|
13367
13432
|
},
|
|
@@ -13371,7 +13436,8 @@ async function visibilityStatsRoutes(app) {
|
|
|
13371
13436
|
// requested; both are OMITTED otherwise (absent = no breakdown) so the
|
|
13372
13437
|
// SDK types `groupBy` as `groupBy?: 'provider'` rather than a misleading
|
|
13373
13438
|
// always-present literal.
|
|
13374
|
-
...groupBy === "provider" ? { groupBy, byProvider: stats.byProvider ?? [] } : {}
|
|
13439
|
+
...groupBy === "provider" ? { groupBy, byProvider: stats.byProvider ?? [] } : {},
|
|
13440
|
+
...shareOfVoice ? { shareOfVoice } : {}
|
|
13375
13441
|
};
|
|
13376
13442
|
return reply.send(response);
|
|
13377
13443
|
});
|
|
@@ -14436,6 +14502,18 @@ var groupByProviderQueryParameter = {
|
|
|
14436
14502
|
description: 'Set to "provider" to include a per-provider breakdown whose counts sum to the pooled counts.',
|
|
14437
14503
|
schema: { type: "string", enum: ["provider"] }
|
|
14438
14504
|
};
|
|
14505
|
+
var monthQueryParameter = {
|
|
14506
|
+
name: "month",
|
|
14507
|
+
in: "query",
|
|
14508
|
+
description: `Aggregate a single calendar month (YYYY-MM), expanded to that month's inclusive UTC bounds. Mutually exclusive with "since"/"until"/"lastRuns".`,
|
|
14509
|
+
schema: stringSchema
|
|
14510
|
+
};
|
|
14511
|
+
var shareOfVoiceQueryParameter = {
|
|
14512
|
+
name: "shareOfVoice",
|
|
14513
|
+
in: "query",
|
|
14514
|
+
description: 'Set to "1" to include pooled share of voice (project vs tracked-competitor brand mentions in answer text) across the window.',
|
|
14515
|
+
schema: { type: "string", enum: ["1"] }
|
|
14516
|
+
};
|
|
14439
14517
|
var wordpressEnvQueryParameter = {
|
|
14440
14518
|
name: "env",
|
|
14441
14519
|
in: "query",
|
|
@@ -15166,9 +15244,17 @@ var routeCatalog = [
|
|
|
15166
15244
|
method: "get",
|
|
15167
15245
|
path: "/api/v1/projects/{name}/visibility-stats",
|
|
15168
15246
|
summary: "Get aggregated mention/citation stats per query",
|
|
15169
|
-
description: "Per-query mention (answer-text) and citation (source-list) counts with a sample size, pooled across many answer-visibility runs (probe-excluded). Tri-state aware: `checked` counts only snapshots where answerMentioned was recorded (null = not checked is excluded). Lets a consumer compute confidence-aware (e.g. Wilson) proportions without N+1 run fetches. With no since/until/lastRuns, EVERY completed/partial answer-visibility run is pooled \u2014 `window.runCount` reports how many; bound the window with lastRuns
|
|
15247
|
+
description: "Per-query mention (answer-text) and citation (source-list) counts with a sample size, pooled across many answer-visibility runs (probe-excluded). Tri-state aware: `checked` counts only snapshots where answerMentioned was recorded (null = not checked is excluded). Lets a consumer compute confidence-aware (e.g. Wilson) proportions without N+1 run fetches. With no since/until/lastRuns, EVERY completed/partial answer-visibility run is pooled \u2014 `window.runCount` reports how many; bound the window with lastRuns, since/until, or month=YYYY-MM for a recent sample. Set groupBy=provider for a per-provider breakdown whose counts sum to the pooled counts, or shareOfVoice=1 to add pooled share of voice vs tracked competitors.",
|
|
15170
15248
|
tags: ["analytics"],
|
|
15171
|
-
parameters: [
|
|
15249
|
+
parameters: [
|
|
15250
|
+
nameParameter,
|
|
15251
|
+
sinceQueryParameter,
|
|
15252
|
+
untilQueryParameter,
|
|
15253
|
+
lastRunsQueryParameter,
|
|
15254
|
+
groupByProviderQueryParameter,
|
|
15255
|
+
monthQueryParameter,
|
|
15256
|
+
shareOfVoiceQueryParameter
|
|
15257
|
+
],
|
|
15172
15258
|
responses: {
|
|
15173
15259
|
200: jsonResponse("Aggregated visibility stats returned.", "VisibilityStatsDto"),
|
|
15174
15260
|
400: errorResponse("Invalid query parameters."),
|
|
@@ -2686,6 +2686,23 @@ var visibilityStatsWindowSchema = z20.object({
|
|
|
2686
2686
|
runCount: z20.number().int()
|
|
2687
2687
|
});
|
|
2688
2688
|
var visibilityStatsGroupBySchema = z20.enum(["provider"]);
|
|
2689
|
+
var visibilityStatsShareCompetitorSchema = z20.object({
|
|
2690
|
+
domain: z20.string(),
|
|
2691
|
+
/** Snapshots (with answer text) in the window where this competitor's brand appeared. */
|
|
2692
|
+
mentions: z20.number().int()
|
|
2693
|
+
});
|
|
2694
|
+
var visibilityStatsShareOfVoiceSchema = z20.object({
|
|
2695
|
+
/** `projectMentions / (projectMentions + competitorMentions)` as 0-100; `null` when no competitors configured. */
|
|
2696
|
+
percent: z20.number().nullable(),
|
|
2697
|
+
/** Snapshots (with answer text) where the project's brand appeared in the answer. */
|
|
2698
|
+
projectMentions: z20.number().int(),
|
|
2699
|
+
/** Snapshots (with answer text) where any tracked competitor's brand appeared. */
|
|
2700
|
+
competitorMentions: z20.number().int(),
|
|
2701
|
+
/** Snapshots that carried answer text — the universe the mentions were counted over. */
|
|
2702
|
+
snapshotsWithAnswerText: z20.number().int(),
|
|
2703
|
+
/** Per-competitor mention counts, descending; only competitors with ≥1 mention appear. */
|
|
2704
|
+
perCompetitor: z20.array(visibilityStatsShareCompetitorSchema)
|
|
2705
|
+
});
|
|
2689
2706
|
var visibilityStatsDtoSchema = z20.object({
|
|
2690
2707
|
project: z20.string(),
|
|
2691
2708
|
/**
|
|
@@ -2704,8 +2721,21 @@ var visibilityStatsDtoSchema = z20.object({
|
|
|
2704
2721
|
*/
|
|
2705
2722
|
byProvider: z20.array(visibilityStatsProviderEntrySchema).optional(),
|
|
2706
2723
|
/** Per-query stats, sorted by query text. Only queries with ≥1 snapshot in the window appear. */
|
|
2707
|
-
queries: z20.array(visibilityStatsQueryEntrySchema)
|
|
2708
|
-
|
|
2724
|
+
queries: z20.array(visibilityStatsQueryEntrySchema),
|
|
2725
|
+
/** Pooled share of voice vs tracked competitors — present only when `shareOfVoice=1` was requested. */
|
|
2726
|
+
shareOfVoice: visibilityStatsShareOfVoiceSchema.optional()
|
|
2727
|
+
});
|
|
2728
|
+
function calendarMonthBounds(month) {
|
|
2729
|
+
const match = /^(\d{4})-(\d{2})$/.exec(month);
|
|
2730
|
+
if (!match) throw new RangeError("month must be in YYYY-MM format");
|
|
2731
|
+
const year = Number(match[1]);
|
|
2732
|
+
const mon = Number(match[2]);
|
|
2733
|
+
if (mon < 1 || mon > 12) throw new RangeError("month must be between 01 and 12");
|
|
2734
|
+
return {
|
|
2735
|
+
since: new Date(Date.UTC(year, mon - 1, 1)).toISOString(),
|
|
2736
|
+
until: new Date(Date.UTC(year, mon, 1) - 1).toISOString()
|
|
2737
|
+
};
|
|
2738
|
+
}
|
|
2709
2739
|
|
|
2710
2740
|
// ../contracts/src/ga.ts
|
|
2711
2741
|
import { z as z21 } from "zod";
|
|
@@ -5577,6 +5607,7 @@ export {
|
|
|
5577
5607
|
parseWindow,
|
|
5578
5608
|
windowCutoff,
|
|
5579
5609
|
visibilityStatsDtoSchema,
|
|
5610
|
+
calendarMonthBounds,
|
|
5580
5611
|
ga4StatusDtoSchema,
|
|
5581
5612
|
ga4SyncResponseDtoSchema,
|
|
5582
5613
|
ga4AiReferralHistoryEntrySchema,
|
package/dist/cli.js
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
setTelemetrySource,
|
|
30
30
|
showFirstRunNotice,
|
|
31
31
|
trackEvent
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-BTJY73GW.js";
|
|
33
33
|
import {
|
|
34
34
|
CliError,
|
|
35
35
|
EXIT_SYSTEM_ERROR,
|
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
saveConfig,
|
|
47
47
|
saveConfigPatch,
|
|
48
48
|
usageError
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-23DCY7DL.js";
|
|
50
50
|
import {
|
|
51
51
|
apiKeys,
|
|
52
52
|
createClient,
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
projects,
|
|
55
55
|
queries,
|
|
56
56
|
renderReportHtml
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-GNMZTR6Y.js";
|
|
58
58
|
import {
|
|
59
59
|
BacklinkSources,
|
|
60
60
|
CcReleaseSyncStatuses,
|
|
@@ -80,7 +80,7 @@ import {
|
|
|
80
80
|
providerQuotaPolicySchema,
|
|
81
81
|
resolveProviderInput,
|
|
82
82
|
winnabilityClassSchema
|
|
83
|
-
} from "./chunk-
|
|
83
|
+
} from "./chunk-Q7FFDQU4.js";
|
|
84
84
|
|
|
85
85
|
// src/cli.ts
|
|
86
86
|
import { pathToFileURL } from "url";
|
|
@@ -11447,8 +11447,10 @@ async function showVisibilityStats(project, opts) {
|
|
|
11447
11447
|
const data = await client.getVisibilityStats(project, {
|
|
11448
11448
|
since: opts.since,
|
|
11449
11449
|
until: opts.until,
|
|
11450
|
+
month: opts.month,
|
|
11450
11451
|
lastRuns: opts.lastRuns,
|
|
11451
|
-
groupBy: opts.byProvider ? "provider" : void 0
|
|
11452
|
+
groupBy: opts.byProvider ? "provider" : void 0,
|
|
11453
|
+
shareOfVoice: opts.shareOfVoice
|
|
11452
11454
|
});
|
|
11453
11455
|
if (opts.format === "jsonl") {
|
|
11454
11456
|
emitJsonl(data.queries.map((q) => ({ project: data.project, runCount: data.window.runCount, ...q })));
|
|
@@ -11539,10 +11541,21 @@ function printVisibilityStats(data) {
|
|
|
11539
11541
|
);
|
|
11540
11542
|
}
|
|
11541
11543
|
}
|
|
11544
|
+
const sov = data.shareOfVoice;
|
|
11545
|
+
if (sov) {
|
|
11546
|
+
console.log("");
|
|
11547
|
+
const pctStr = sov.percent === null ? "\u2014 (no competitors configured)" : `${sov.percent}%`;
|
|
11548
|
+
console.log(
|
|
11549
|
+
`Share of voice: ${pctStr} (you ${sov.projectMentions} vs competitors ${sov.competitorMentions} brand mentions across ${sov.snapshotsWithAnswerText} answers)`
|
|
11550
|
+
);
|
|
11551
|
+
for (const c of sov.perCompetitor.slice(0, 8)) {
|
|
11552
|
+
console.log(` ${c.domain}: ${c.mentions}`);
|
|
11553
|
+
}
|
|
11554
|
+
}
|
|
11542
11555
|
}
|
|
11543
11556
|
|
|
11544
11557
|
// src/cli-commands/visibility-stats.ts
|
|
11545
|
-
var USAGE4 = "canonry visibility-stats <project> [--since <iso>] [--until <iso>] [--last-runs <n>] [--by-provider] [--format json|jsonl]";
|
|
11558
|
+
var USAGE4 = "canonry visibility-stats <project> [--since <iso>] [--until <iso>] [--month <YYYY-MM>] [--last-runs <n>] [--by-provider] [--share-of-voice] [--format json|jsonl]";
|
|
11546
11559
|
var VISIBILITY_STATS_CLI_COMMANDS = [
|
|
11547
11560
|
{
|
|
11548
11561
|
path: ["visibility-stats"],
|
|
@@ -11550,20 +11563,24 @@ var VISIBILITY_STATS_CLI_COMMANDS = [
|
|
|
11550
11563
|
options: {
|
|
11551
11564
|
since: stringOption(),
|
|
11552
11565
|
until: stringOption(),
|
|
11566
|
+
month: stringOption(),
|
|
11553
11567
|
"last-runs": stringOption(),
|
|
11554
|
-
"by-provider": { type: "boolean", default: false }
|
|
11568
|
+
"by-provider": { type: "boolean", default: false },
|
|
11569
|
+
"share-of-voice": { type: "boolean", default: false }
|
|
11555
11570
|
},
|
|
11556
11571
|
run: async (input) => {
|
|
11557
11572
|
const project = requireProject(input, "visibility-stats", USAGE4);
|
|
11558
11573
|
await showVisibilityStats(project, {
|
|
11559
11574
|
since: getString(input.values, "since"),
|
|
11560
11575
|
until: getString(input.values, "until"),
|
|
11576
|
+
month: getString(input.values, "month"),
|
|
11561
11577
|
lastRuns: parseIntegerOption(input, "last-runs", {
|
|
11562
11578
|
command: "visibility-stats",
|
|
11563
11579
|
usage: USAGE4,
|
|
11564
11580
|
message: "--last-runs must be an integer"
|
|
11565
11581
|
}),
|
|
11566
11582
|
byProvider: getBoolean(input.values, "by-provider"),
|
|
11583
|
+
shareOfVoice: getBoolean(input.values, "share-of-voice"),
|
|
11567
11584
|
format: input.format
|
|
11568
11585
|
});
|
|
11569
11586
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-BTJY73GW.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-23DCY7DL.js";
|
|
7
|
+
import "./chunk-GNMZTR6Y.js";
|
|
8
|
+
import "./chunk-Q7FFDQU4.js";
|
|
9
9
|
export {
|
|
10
10
|
createServer,
|
|
11
11
|
loadConfig
|
package/dist/mcp.js
CHANGED
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
PACKAGE_VERSION,
|
|
4
4
|
canonryMcpTools,
|
|
5
5
|
createApiClient
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-23DCY7DL.js";
|
|
7
7
|
import {
|
|
8
8
|
isReadOnlyKey
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-Q7FFDQU4.js";
|
|
10
10
|
|
|
11
11
|
// src/mcp/cli.ts
|
|
12
12
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainyc/canonry",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.103.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
|
|
6
6
|
"license": "FSL-1.1-ALv2",
|
|
@@ -63,26 +63,26 @@
|
|
|
63
63
|
"tsup": "^8.5.1",
|
|
64
64
|
"tsx": "^4.19.0",
|
|
65
65
|
"@ainyc/canonry-api-client": "0.0.0",
|
|
66
|
+
"@ainyc/canonry-api-routes": "0.0.0",
|
|
66
67
|
"@ainyc/canonry-contracts": "0.0.0",
|
|
67
|
-
"@ainyc/canonry-config": "0.0.0",
|
|
68
68
|
"@ainyc/canonry-db": "0.0.0",
|
|
69
|
-
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
70
69
|
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
70
|
+
"@ainyc/canonry-config": "0.0.0",
|
|
71
71
|
"@ainyc/canonry-integration-openai-ads": "0.0.0",
|
|
72
72
|
"@ainyc/canonry-integration-google": "0.0.0",
|
|
73
|
+
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
74
|
+
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
73
75
|
"@ainyc/canonry-integration-google-business-profile": "0.0.0",
|
|
74
76
|
"@ainyc/canonry-integration-google-places": "0.0.0",
|
|
75
|
-
"@ainyc/canonry-integration-traffic": "0.0.0",
|
|
76
|
-
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
77
77
|
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
78
|
-
"@ainyc/canonry-api-routes": "0.0.0",
|
|
79
78
|
"@ainyc/canonry-intelligence": "0.0.0",
|
|
79
|
+
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
80
80
|
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
81
|
-
"@ainyc/canonry-provider-local": "0.0.0",
|
|
82
|
-
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
83
81
|
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
84
|
-
"@ainyc/canonry-
|
|
85
|
-
"@ainyc/canonry-provider-
|
|
82
|
+
"@ainyc/canonry-integration-traffic": "0.0.0",
|
|
83
|
+
"@ainyc/canonry-provider-local": "0.0.0",
|
|
84
|
+
"@ainyc/canonry-provider-perplexity": "0.0.0",
|
|
85
|
+
"@ainyc/canonry-provider-openai": "0.0.0"
|
|
86
86
|
},
|
|
87
87
|
"scripts": {
|
|
88
88
|
"build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",
|