@ainyc/canonry 2.10.1 → 2.10.3
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/README.md +3 -0
- package/assets/assets/{index-PhLDQh1e.js → index-VW6VB3cO.js} +99 -99
- package/assets/index.html +1 -1
- package/dist/{chunk-SZSWQG3J.js → chunk-FAP76VXF.js} +643 -207
- package/dist/{chunk-PYHANJ3B.js → chunk-UM6RDSRJ.js} +327 -0
- package/dist/{chunk-KWQCQMPY.js → chunk-Z3BWDCBJ.js} +126 -0
- package/dist/cli.js +136 -4
- package/dist/index.js +3 -3
- package/dist/{intelligence-service-2ZABHNR4.js → intelligence-service-54F3NGPM.js} +1 -1
- package/dist/mcp.js +1 -1
- package/package.json +9 -9
|
@@ -1329,6 +1329,328 @@ function analyzeRuns(currentRun, previousRun, allRuns) {
|
|
|
1329
1329
|
};
|
|
1330
1330
|
}
|
|
1331
1331
|
|
|
1332
|
+
// ../intelligence/src/query-shape.ts
|
|
1333
|
+
var TRANSACTIONAL = /\b(buy|price|pricing|cost|cheap|discount|coupon|deal|sale|trial|plan)\b/i;
|
|
1334
|
+
var NAVIGATIONAL = /\b(login|sign[- ]?in|contact|support|help|download|app|homepage)\b|\.(com|io|net|org|app|ai)\b/i;
|
|
1335
|
+
function isBlogShapedQuery(query) {
|
|
1336
|
+
const trimmed = query.trim();
|
|
1337
|
+
if (!trimmed) return false;
|
|
1338
|
+
if (TRANSACTIONAL.test(trimmed)) return false;
|
|
1339
|
+
if (NAVIGATIONAL.test(trimmed)) return false;
|
|
1340
|
+
return true;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
// ../intelligence/src/site-inventory.ts
|
|
1344
|
+
var BLOG_SHAPED_PATH_PREFIXES = [
|
|
1345
|
+
"/blog/",
|
|
1346
|
+
"/posts/",
|
|
1347
|
+
"/articles/",
|
|
1348
|
+
"/guides/",
|
|
1349
|
+
"/learn/",
|
|
1350
|
+
"/resources/",
|
|
1351
|
+
"/glossary/"
|
|
1352
|
+
];
|
|
1353
|
+
function buildInventory(input) {
|
|
1354
|
+
const map = /* @__PURE__ */ new Map();
|
|
1355
|
+
const addPage = (rawUrl, source) => {
|
|
1356
|
+
const path = extractPath(rawUrl);
|
|
1357
|
+
if (!path) return;
|
|
1358
|
+
if (!isBlogShaped(path)) return;
|
|
1359
|
+
let sources = map.get(path);
|
|
1360
|
+
if (!sources) {
|
|
1361
|
+
sources = /* @__PURE__ */ new Set();
|
|
1362
|
+
map.set(path, sources);
|
|
1363
|
+
}
|
|
1364
|
+
sources.add(source);
|
|
1365
|
+
};
|
|
1366
|
+
for (const url of input.gscPages) addPage(url, "gsc");
|
|
1367
|
+
for (const url of input.ga4LandingPages) addPage(url, "ga4");
|
|
1368
|
+
for (const url of input.sitemapUrls) addPage(url, "sitemap");
|
|
1369
|
+
for (const url of input.wpPosts) addPage(url, "wp");
|
|
1370
|
+
return Array.from(map.entries()).map(([url, sources]) => ({
|
|
1371
|
+
url,
|
|
1372
|
+
sources: Array.from(sources)
|
|
1373
|
+
}));
|
|
1374
|
+
}
|
|
1375
|
+
function extractPath(url) {
|
|
1376
|
+
const trimmed = url.trim();
|
|
1377
|
+
if (!trimmed) return "";
|
|
1378
|
+
const match = /^https?:\/\/[^/]+(.*)$/.exec(trimmed);
|
|
1379
|
+
const path = match ? match[1] : trimmed;
|
|
1380
|
+
const stripped = path.replace(/\/+$/, "");
|
|
1381
|
+
return stripped || "/";
|
|
1382
|
+
}
|
|
1383
|
+
function isBlogShaped(path) {
|
|
1384
|
+
return BLOG_SHAPED_PATH_PREFIXES.some((prefix) => path.startsWith(prefix));
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// ../intelligence/src/content-classifier.ts
|
|
1388
|
+
var SEO_STRONG_THRESHOLD = 10;
|
|
1389
|
+
var SEO_WEAK_THRESHOLD = 30;
|
|
1390
|
+
function classifyContentAction(input) {
|
|
1391
|
+
const { ourPage, ourPageInGroundingSources, ourPageHasSchema } = input;
|
|
1392
|
+
if (!ourPage) return "create";
|
|
1393
|
+
if (ourPageInGroundingSources) {
|
|
1394
|
+
if (ourPageHasSchema === false) return "add-schema";
|
|
1395
|
+
return null;
|
|
1396
|
+
}
|
|
1397
|
+
if (ourPage.position <= SEO_STRONG_THRESHOLD) return "refresh";
|
|
1398
|
+
if (ourPage.position <= SEO_WEAK_THRESHOLD) return "expand";
|
|
1399
|
+
return "create";
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// ../intelligence/src/content-scorer.ts
|
|
1403
|
+
var SEVERITY_BY_ACTION = {
|
|
1404
|
+
create: 1,
|
|
1405
|
+
"add-schema": 0.7,
|
|
1406
|
+
expand: 0.6,
|
|
1407
|
+
refresh: 0.4
|
|
1408
|
+
};
|
|
1409
|
+
function scoreContentTarget(input) {
|
|
1410
|
+
const demand = computeDemandComponent(input.gscImpressions, input.aiReferralFactor);
|
|
1411
|
+
const competitor = computeCompetitorComponent(
|
|
1412
|
+
input.competitorCount,
|
|
1413
|
+
input.recentMissRate,
|
|
1414
|
+
input.citationCount
|
|
1415
|
+
);
|
|
1416
|
+
const absence = clamp01(1 - input.ourCitedRate);
|
|
1417
|
+
const gapSeverity = input.action ? SEVERITY_BY_ACTION[input.action] : 0;
|
|
1418
|
+
const score = (demand + competitor) * absence * gapSeverity;
|
|
1419
|
+
return {
|
|
1420
|
+
score,
|
|
1421
|
+
scoreBreakdown: { demand, competitor, absence, gapSeverity },
|
|
1422
|
+
drivers: buildDrivers(input),
|
|
1423
|
+
demandSource: classifyDemandSource(input.gscImpressions, input.competitorCount)
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
function computeDemandComponent(gscImpressions, aiReferralFactor) {
|
|
1427
|
+
const logImpressions = Math.log(Math.max(gscImpressions, 0) + 1);
|
|
1428
|
+
const aiBoost = 1 + Math.max(aiReferralFactor, 0);
|
|
1429
|
+
return logImpressions * aiBoost;
|
|
1430
|
+
}
|
|
1431
|
+
function computeCompetitorComponent(competitorCount, recentMissRate, citationCount) {
|
|
1432
|
+
if (competitorCount <= 0) return 0;
|
|
1433
|
+
const logCompetitors = Math.log(competitorCount + 1);
|
|
1434
|
+
return logCompetitors * clamp01(recentMissRate) * Math.max(citationCount, 0);
|
|
1435
|
+
}
|
|
1436
|
+
function classifyDemandSource(gscImpressions, competitorCount) {
|
|
1437
|
+
const hasGsc = gscImpressions > 0;
|
|
1438
|
+
const hasCompetitor = competitorCount > 0;
|
|
1439
|
+
if (hasGsc && hasCompetitor) return "both";
|
|
1440
|
+
if (hasCompetitor) return "competitor-evidence";
|
|
1441
|
+
return "gsc";
|
|
1442
|
+
}
|
|
1443
|
+
function clamp01(value) {
|
|
1444
|
+
if (value < 0) return 0;
|
|
1445
|
+
if (value > 1) return 1;
|
|
1446
|
+
return value;
|
|
1447
|
+
}
|
|
1448
|
+
function buildDrivers(input) {
|
|
1449
|
+
const drivers = [];
|
|
1450
|
+
if (input.competitorCount > 0) {
|
|
1451
|
+
const noun = input.competitorCount === 1 ? "competitor" : "competitors";
|
|
1452
|
+
drivers.push(`${input.competitorCount} ${noun} cited`);
|
|
1453
|
+
}
|
|
1454
|
+
if (input.gscImpressions > 0) {
|
|
1455
|
+
drivers.push(`${formatImpressions(input.gscImpressions)} GSC impressions`);
|
|
1456
|
+
}
|
|
1457
|
+
if (input.recentMissRate >= 0.5 && input.competitorCount > 0) {
|
|
1458
|
+
const pct = Math.round(clamp01(input.recentMissRate) * 100);
|
|
1459
|
+
drivers.push(`missed in ${pct}% of recent runs`);
|
|
1460
|
+
}
|
|
1461
|
+
if (input.action === "create" && input.position === null) {
|
|
1462
|
+
drivers.push("no existing page");
|
|
1463
|
+
}
|
|
1464
|
+
if (input.position !== null && input.position > 30) {
|
|
1465
|
+
drivers.push(`page ranks #${input.position} (effectively invisible)`);
|
|
1466
|
+
} else if (input.position !== null && input.position > 10) {
|
|
1467
|
+
drivers.push(`page ranks #${input.position}`);
|
|
1468
|
+
}
|
|
1469
|
+
if (input.action === "add-schema") {
|
|
1470
|
+
drivers.push("cited by LLMs but lacks structured data");
|
|
1471
|
+
}
|
|
1472
|
+
return drivers;
|
|
1473
|
+
}
|
|
1474
|
+
function formatImpressions(impressions) {
|
|
1475
|
+
if (impressions >= 1e3) {
|
|
1476
|
+
return `${Math.round(impressions / 100) / 10}k`;
|
|
1477
|
+
}
|
|
1478
|
+
return String(impressions);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
// ../intelligence/src/content-confidence.ts
|
|
1482
|
+
var GSC_DENSE_IMPRESSIONS_THRESHOLD = 100;
|
|
1483
|
+
var RUN_HISTORY_HIGH_CONFIDENCE_THRESHOLD = 3;
|
|
1484
|
+
function calculateActionConfidence(input) {
|
|
1485
|
+
const gscDense = input.hasGsc && input.gscImpressions >= GSC_DENSE_IMPRESSIONS_THRESHOLD;
|
|
1486
|
+
const historyDeep = input.runsOfHistory >= RUN_HISTORY_HIGH_CONFIDENCE_THRESHOLD;
|
|
1487
|
+
if (gscDense && historyDeep) return "high";
|
|
1488
|
+
if (!input.hasGsc && !input.hasInventoryMatch) {
|
|
1489
|
+
return "low";
|
|
1490
|
+
}
|
|
1491
|
+
return "medium";
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// ../intelligence/src/content-targets.ts
|
|
1495
|
+
function buildContentTargetRows(input) {
|
|
1496
|
+
const rows = [];
|
|
1497
|
+
for (const cq of input.candidateQueries) {
|
|
1498
|
+
const ourPage = resolveOurPage(cq, input.inventory);
|
|
1499
|
+
const ourPageInGroundingSources = cq.ourCitedInLatestRun;
|
|
1500
|
+
const ourPageHasSchema = ourPage ? input.wpSchemaAudit.get(ourPage.url) ?? null : null;
|
|
1501
|
+
const action = classifyContentAction({
|
|
1502
|
+
ourPage,
|
|
1503
|
+
ourPageInGroundingSources,
|
|
1504
|
+
ourPageHasSchema
|
|
1505
|
+
});
|
|
1506
|
+
if (!action) continue;
|
|
1507
|
+
const hasGsc = cq.gscImpressions > 0;
|
|
1508
|
+
const hasCompetitor = cq.competitorDomains.length > 0;
|
|
1509
|
+
if (!hasGsc && !hasCompetitor && !cq.ourCitedInLatestRun) continue;
|
|
1510
|
+
const aiReferralFactor = computeAiReferralFactor(
|
|
1511
|
+
input.totalAiReferralSessions,
|
|
1512
|
+
cq.competitorCitationCount
|
|
1513
|
+
);
|
|
1514
|
+
const scoring = scoreContentTarget({
|
|
1515
|
+
gscImpressions: cq.gscImpressions,
|
|
1516
|
+
aiReferralFactor,
|
|
1517
|
+
competitorCount: cq.competitorDomains.length,
|
|
1518
|
+
recentMissRate: cq.recentMissRate,
|
|
1519
|
+
citationCount: cq.competitorCitationCount,
|
|
1520
|
+
ourCitedRate: cq.ourCitedRate,
|
|
1521
|
+
action,
|
|
1522
|
+
position: ourPage?.position ?? null
|
|
1523
|
+
});
|
|
1524
|
+
const actionConfidence = calculateActionConfidence({
|
|
1525
|
+
hasGsc: cq.gscPage !== null,
|
|
1526
|
+
gscImpressions: cq.gscImpressions,
|
|
1527
|
+
runsOfHistory: cq.runsOfHistory,
|
|
1528
|
+
hasCompetitorEvidence: cq.competitorDomains.length > 0,
|
|
1529
|
+
hasInventoryMatch: ourPage?.source === "inventory"
|
|
1530
|
+
});
|
|
1531
|
+
const targetRef = computeTargetRef({
|
|
1532
|
+
projectId: input.projectId,
|
|
1533
|
+
query: cq.query,
|
|
1534
|
+
action,
|
|
1535
|
+
targetPage: ourPage?.url ?? null
|
|
1536
|
+
});
|
|
1537
|
+
const winningCompetitor = pickTopCompetitor(cq.competitorGroundingUrls);
|
|
1538
|
+
const ourBestPage = ourPage ? {
|
|
1539
|
+
url: ourPage.url,
|
|
1540
|
+
gscImpressions: cq.gscImpressions,
|
|
1541
|
+
gscClicks: cq.gscClicks,
|
|
1542
|
+
gscAvgPosition: cq.gscPosition,
|
|
1543
|
+
organicSessions: input.gaTrafficByPage.get(ourPage.url) ?? 0
|
|
1544
|
+
} : null;
|
|
1545
|
+
rows.push({
|
|
1546
|
+
targetRef,
|
|
1547
|
+
query: cq.query,
|
|
1548
|
+
action,
|
|
1549
|
+
ourBestPage,
|
|
1550
|
+
winningCompetitor,
|
|
1551
|
+
score: scoring.score,
|
|
1552
|
+
scoreBreakdown: scoring.scoreBreakdown,
|
|
1553
|
+
drivers: scoring.drivers,
|
|
1554
|
+
demandSource: scoring.demandSource,
|
|
1555
|
+
actionConfidence,
|
|
1556
|
+
existingAction: input.inProgressActions.get(targetRef) ?? null
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
return rows.sort((a, b) => b.score - a.score);
|
|
1560
|
+
}
|
|
1561
|
+
function buildContentSourceRows(input) {
|
|
1562
|
+
return input.candidateQueries.map((cq) => ({
|
|
1563
|
+
query: cq.query,
|
|
1564
|
+
groundingSources: [
|
|
1565
|
+
...cq.ourGroundingUrls.map((g) => ({
|
|
1566
|
+
uri: g.uri,
|
|
1567
|
+
title: g.title,
|
|
1568
|
+
domain: g.domain,
|
|
1569
|
+
isOurDomain: true,
|
|
1570
|
+
isCompetitor: false,
|
|
1571
|
+
citationCount: g.citationCount,
|
|
1572
|
+
providers: g.providers
|
|
1573
|
+
})),
|
|
1574
|
+
...cq.competitorGroundingUrls.map((g) => ({
|
|
1575
|
+
uri: g.uri,
|
|
1576
|
+
title: g.title,
|
|
1577
|
+
domain: g.domain,
|
|
1578
|
+
isOurDomain: false,
|
|
1579
|
+
isCompetitor: true,
|
|
1580
|
+
citationCount: g.citationCount,
|
|
1581
|
+
providers: g.providers
|
|
1582
|
+
}))
|
|
1583
|
+
]
|
|
1584
|
+
}));
|
|
1585
|
+
}
|
|
1586
|
+
function buildContentGapRows(input) {
|
|
1587
|
+
const gaps = [];
|
|
1588
|
+
for (const cq of input.candidateQueries) {
|
|
1589
|
+
if (cq.competitorDomains.length === 0) continue;
|
|
1590
|
+
if (cq.ourCitedRate >= 1) continue;
|
|
1591
|
+
gaps.push({
|
|
1592
|
+
query: cq.query,
|
|
1593
|
+
competitorDomains: cq.competitorDomains,
|
|
1594
|
+
competitorCount: cq.competitorDomains.length,
|
|
1595
|
+
missRate: clamp012(cq.recentMissRate),
|
|
1596
|
+
lastSeenInRunId: input.latestRunId
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
return gaps.sort((a, b) => {
|
|
1600
|
+
if (b.missRate !== a.missRate) return b.missRate - a.missRate;
|
|
1601
|
+
return b.competitorCount - a.competitorCount;
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
function resolveOurPage(cq, inventory) {
|
|
1605
|
+
if (cq.gscPage && cq.gscPosition !== null) {
|
|
1606
|
+
return { url: cq.gscPage, position: cq.gscPosition, source: "gsc" };
|
|
1607
|
+
}
|
|
1608
|
+
for (const page of inventory) {
|
|
1609
|
+
if (slugMatchesQuery(page.url, cq.query)) {
|
|
1610
|
+
return { url: page.url, position: 100, source: "inventory" };
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
return null;
|
|
1614
|
+
}
|
|
1615
|
+
function slugMatchesQuery(url, query) {
|
|
1616
|
+
const slug = url.toLowerCase();
|
|
1617
|
+
const queryAsSlug = query.toLowerCase().trim().replace(/\s+/g, "-");
|
|
1618
|
+
if (slug.includes(queryAsSlug)) return true;
|
|
1619
|
+
const queryTokens = query.toLowerCase().split(/\s+/).filter((t) => t.length > 2);
|
|
1620
|
+
const slugTokens = new Set(slug.split(/[/\s\-_.]+/));
|
|
1621
|
+
const overlap = queryTokens.filter((t) => slugTokens.has(t)).length;
|
|
1622
|
+
return overlap >= 2;
|
|
1623
|
+
}
|
|
1624
|
+
function computeAiReferralFactor(totalAiReferralSessions, competitorCount) {
|
|
1625
|
+
if (totalAiReferralSessions <= 0) return 0;
|
|
1626
|
+
const baseline = Math.min(totalAiReferralSessions / 1e3, 0.5);
|
|
1627
|
+
const competitorBoost = competitorCount > 0 ? 0.1 : 0;
|
|
1628
|
+
return Math.min(baseline + competitorBoost, 1);
|
|
1629
|
+
}
|
|
1630
|
+
function pickTopCompetitor(competitors2) {
|
|
1631
|
+
if (competitors2.length === 0) return null;
|
|
1632
|
+
const top = [...competitors2].sort((a, b) => b.citationCount - a.citationCount)[0];
|
|
1633
|
+
return {
|
|
1634
|
+
domain: top.domain,
|
|
1635
|
+
url: top.uri,
|
|
1636
|
+
title: top.title,
|
|
1637
|
+
citationCount: top.citationCount
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
function computeTargetRef(input) {
|
|
1641
|
+
const key = [input.projectId, input.query, input.action, input.targetPage ?? ""].join("|");
|
|
1642
|
+
let hash = 0;
|
|
1643
|
+
for (let i = 0; i < key.length; i++) {
|
|
1644
|
+
hash = (hash << 5) - hash + key.charCodeAt(i) | 0;
|
|
1645
|
+
}
|
|
1646
|
+
return `tgt_${(hash >>> 0).toString(36)}`;
|
|
1647
|
+
}
|
|
1648
|
+
function clamp012(value) {
|
|
1649
|
+
if (value < 0) return 0;
|
|
1650
|
+
if (value > 1) return 1;
|
|
1651
|
+
return value;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1332
1654
|
// src/intelligence-service.ts
|
|
1333
1655
|
import crypto from "crypto";
|
|
1334
1656
|
|
|
@@ -1589,6 +1911,11 @@ export {
|
|
|
1589
1911
|
extractLegacyCredentials,
|
|
1590
1912
|
dropLegacyCredentialColumns,
|
|
1591
1913
|
migrate,
|
|
1914
|
+
isBlogShapedQuery,
|
|
1915
|
+
buildInventory,
|
|
1916
|
+
buildContentTargetRows,
|
|
1917
|
+
buildContentSourceRows,
|
|
1918
|
+
buildContentGapRows,
|
|
1592
1919
|
createLogger,
|
|
1593
1920
|
IntelligenceService
|
|
1594
1921
|
};
|
|
@@ -1540,6 +1540,108 @@ var ccCachedReleaseSchema = z14.object({
|
|
|
1540
1540
|
lastUsedAt: z14.string().nullable()
|
|
1541
1541
|
});
|
|
1542
1542
|
|
|
1543
|
+
// ../contracts/src/content.ts
|
|
1544
|
+
import { z as z15 } from "zod";
|
|
1545
|
+
var contentActionSchema = z15.enum(["create", "expand", "refresh", "add-schema"]);
|
|
1546
|
+
var ContentActions = contentActionSchema.enum;
|
|
1547
|
+
var demandSourceSchema = z15.enum(["gsc", "competitor-evidence", "both"]);
|
|
1548
|
+
var DemandSources = demandSourceSchema.enum;
|
|
1549
|
+
var actionConfidenceSchema = z15.enum(["high", "medium", "low"]);
|
|
1550
|
+
var ActionConfidences = actionConfidenceSchema.enum;
|
|
1551
|
+
var pageTypeSchema = z15.enum([
|
|
1552
|
+
"blog-post",
|
|
1553
|
+
"comparison",
|
|
1554
|
+
"listicle",
|
|
1555
|
+
"how-to",
|
|
1556
|
+
"guide",
|
|
1557
|
+
"glossary"
|
|
1558
|
+
]);
|
|
1559
|
+
var PageTypes = pageTypeSchema.enum;
|
|
1560
|
+
var contentActionStateSchema = z15.enum([
|
|
1561
|
+
"proposed",
|
|
1562
|
+
"briefed",
|
|
1563
|
+
"payload-generated",
|
|
1564
|
+
"draft-created",
|
|
1565
|
+
"published",
|
|
1566
|
+
"validated",
|
|
1567
|
+
"dismissed"
|
|
1568
|
+
]);
|
|
1569
|
+
var ContentActionStates = contentActionStateSchema.enum;
|
|
1570
|
+
var ourBestPageSchema = z15.object({
|
|
1571
|
+
url: z15.string(),
|
|
1572
|
+
gscImpressions: z15.number().nonnegative(),
|
|
1573
|
+
gscClicks: z15.number().nonnegative(),
|
|
1574
|
+
// Null when the page came from the inventory fallback (no GSC ranking data).
|
|
1575
|
+
gscAvgPosition: z15.number().nonnegative().nullable(),
|
|
1576
|
+
organicSessions: z15.number().nonnegative()
|
|
1577
|
+
});
|
|
1578
|
+
var winningCompetitorSchema = z15.object({
|
|
1579
|
+
domain: z15.string(),
|
|
1580
|
+
url: z15.string(),
|
|
1581
|
+
title: z15.string(),
|
|
1582
|
+
citationCount: z15.number().int().nonnegative()
|
|
1583
|
+
});
|
|
1584
|
+
var scoreBreakdownSchema = z15.object({
|
|
1585
|
+
demand: z15.number(),
|
|
1586
|
+
competitor: z15.number(),
|
|
1587
|
+
absence: z15.number(),
|
|
1588
|
+
gapSeverity: z15.number()
|
|
1589
|
+
});
|
|
1590
|
+
var existingActionRefSchema = z15.object({
|
|
1591
|
+
actionId: z15.string(),
|
|
1592
|
+
state: contentActionStateSchema,
|
|
1593
|
+
lastUpdated: z15.string()
|
|
1594
|
+
});
|
|
1595
|
+
var contentTargetRowDtoSchema = z15.object({
|
|
1596
|
+
targetRef: z15.string(),
|
|
1597
|
+
query: z15.string(),
|
|
1598
|
+
action: contentActionSchema,
|
|
1599
|
+
ourBestPage: ourBestPageSchema.nullable(),
|
|
1600
|
+
winningCompetitor: winningCompetitorSchema.nullable(),
|
|
1601
|
+
score: z15.number(),
|
|
1602
|
+
scoreBreakdown: scoreBreakdownSchema,
|
|
1603
|
+
drivers: z15.array(z15.string()),
|
|
1604
|
+
demandSource: demandSourceSchema,
|
|
1605
|
+
actionConfidence: actionConfidenceSchema,
|
|
1606
|
+
existingAction: existingActionRefSchema.nullable()
|
|
1607
|
+
});
|
|
1608
|
+
var contentTargetsResponseDtoSchema = z15.object({
|
|
1609
|
+
targets: z15.array(contentTargetRowDtoSchema),
|
|
1610
|
+
contextMetrics: z15.object({
|
|
1611
|
+
totalAiReferralSessions: z15.number().int().nonnegative(),
|
|
1612
|
+
latestRunId: z15.string(),
|
|
1613
|
+
runTimestamp: z15.string()
|
|
1614
|
+
})
|
|
1615
|
+
});
|
|
1616
|
+
var contentGroundingSourceSchema = z15.object({
|
|
1617
|
+
uri: z15.string(),
|
|
1618
|
+
title: z15.string(),
|
|
1619
|
+
domain: z15.string(),
|
|
1620
|
+
isOurDomain: z15.boolean(),
|
|
1621
|
+
isCompetitor: z15.boolean(),
|
|
1622
|
+
citationCount: z15.number().int().nonnegative(),
|
|
1623
|
+
providers: z15.array(providerNameSchema)
|
|
1624
|
+
});
|
|
1625
|
+
var contentSourceRowDtoSchema = z15.object({
|
|
1626
|
+
query: z15.string(),
|
|
1627
|
+
groundingSources: z15.array(contentGroundingSourceSchema)
|
|
1628
|
+
});
|
|
1629
|
+
var contentSourcesResponseDtoSchema = z15.object({
|
|
1630
|
+
sources: z15.array(contentSourceRowDtoSchema),
|
|
1631
|
+
latestRunId: z15.string()
|
|
1632
|
+
});
|
|
1633
|
+
var contentGapRowDtoSchema = z15.object({
|
|
1634
|
+
query: z15.string(),
|
|
1635
|
+
competitorDomains: z15.array(z15.string()),
|
|
1636
|
+
competitorCount: z15.number().int().nonnegative(),
|
|
1637
|
+
missRate: z15.number().min(0).max(1),
|
|
1638
|
+
lastSeenInRunId: z15.string()
|
|
1639
|
+
});
|
|
1640
|
+
var contentGapsResponseDtoSchema = z15.object({
|
|
1641
|
+
gaps: z15.array(contentGapRowDtoSchema),
|
|
1642
|
+
latestRunId: z15.string()
|
|
1643
|
+
});
|
|
1644
|
+
|
|
1543
1645
|
// src/client.ts
|
|
1544
1646
|
function createApiClient() {
|
|
1545
1647
|
const config = loadConfig();
|
|
@@ -2105,6 +2207,29 @@ var ApiClient = class {
|
|
|
2105
2207
|
async dismissInsight(project, id) {
|
|
2106
2208
|
return this.request("POST", `/projects/${encodeURIComponent(project)}/insights/${encodeURIComponent(id)}/dismiss`);
|
|
2107
2209
|
}
|
|
2210
|
+
// ── Content ──────────────────────────────────────────────────────────
|
|
2211
|
+
async getContentTargets(project, opts) {
|
|
2212
|
+
const params = new URLSearchParams();
|
|
2213
|
+
if (opts?.limit !== void 0) params.set("limit", String(opts.limit));
|
|
2214
|
+
if (opts?.includeInProgress) params.set("include-in-progress", "true");
|
|
2215
|
+
const qs = params.toString();
|
|
2216
|
+
return this.request(
|
|
2217
|
+
"GET",
|
|
2218
|
+
`/projects/${encodeURIComponent(project)}/content/targets${qs ? `?${qs}` : ""}`
|
|
2219
|
+
);
|
|
2220
|
+
}
|
|
2221
|
+
async getContentSources(project) {
|
|
2222
|
+
return this.request(
|
|
2223
|
+
"GET",
|
|
2224
|
+
`/projects/${encodeURIComponent(project)}/content/sources`
|
|
2225
|
+
);
|
|
2226
|
+
}
|
|
2227
|
+
async getContentGaps(project) {
|
|
2228
|
+
return this.request(
|
|
2229
|
+
"GET",
|
|
2230
|
+
`/projects/${encodeURIComponent(project)}/content/gaps`
|
|
2231
|
+
);
|
|
2232
|
+
}
|
|
2108
2233
|
async getHealth(project) {
|
|
2109
2234
|
return this.request("GET", `/projects/${encodeURIComponent(project)}/health/latest`);
|
|
2110
2235
|
}
|
|
@@ -2203,6 +2328,7 @@ export {
|
|
|
2203
2328
|
RunStatuses,
|
|
2204
2329
|
RunKinds,
|
|
2205
2330
|
RunTriggers,
|
|
2331
|
+
CitationStates,
|
|
2206
2332
|
runTriggerRequestSchema,
|
|
2207
2333
|
parseRunError,
|
|
2208
2334
|
buildRunErrorFromMessages,
|
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
setGoogleAuthConfig,
|
|
18
18
|
showFirstRunNotice,
|
|
19
19
|
trackEvent
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-FAP76VXF.js";
|
|
21
21
|
import {
|
|
22
22
|
CcReleaseSyncStatuses,
|
|
23
23
|
CliError,
|
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
saveConfig,
|
|
42
42
|
saveConfigPatch,
|
|
43
43
|
usageError
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-Z3BWDCBJ.js";
|
|
45
45
|
import {
|
|
46
46
|
apiKeys,
|
|
47
47
|
competitors,
|
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
projects,
|
|
52
52
|
querySnapshots,
|
|
53
53
|
runs
|
|
54
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-UM6RDSRJ.js";
|
|
55
55
|
import "./chunk-MLKGABMK.js";
|
|
56
56
|
|
|
57
57
|
// src/cli.ts
|
|
@@ -299,7 +299,7 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
299
299
|
console.log(` Errors: ${providerErrors}`);
|
|
300
300
|
}
|
|
301
301
|
async function backfillInsightsCommand(project, opts) {
|
|
302
|
-
const { IntelligenceService } = await import("./intelligence-service-
|
|
302
|
+
const { IntelligenceService } = await import("./intelligence-service-54F3NGPM.js");
|
|
303
303
|
const config = loadConfig();
|
|
304
304
|
const db = createClient(config.database);
|
|
305
305
|
migrate(db);
|
|
@@ -6075,6 +6075,137 @@ var INTELLIGENCE_CLI_COMMANDS = [
|
|
|
6075
6075
|
}
|
|
6076
6076
|
];
|
|
6077
6077
|
|
|
6078
|
+
// src/commands/content.ts
|
|
6079
|
+
async function listContentTargets(project, opts) {
|
|
6080
|
+
const client = createApiClient();
|
|
6081
|
+
const response = await client.getContentTargets(project, {
|
|
6082
|
+
limit: opts.limit,
|
|
6083
|
+
includeInProgress: opts.includeInProgress
|
|
6084
|
+
});
|
|
6085
|
+
if (opts.format === "json") {
|
|
6086
|
+
console.log(JSON.stringify(response, null, 2));
|
|
6087
|
+
return;
|
|
6088
|
+
}
|
|
6089
|
+
if (response.targets.length === 0) {
|
|
6090
|
+
console.log("No content targets surfaced. (Run `canonry run` to generate fresh signal.)");
|
|
6091
|
+
return;
|
|
6092
|
+
}
|
|
6093
|
+
console.log(
|
|
6094
|
+
`${response.targets.length} target${response.targets.length === 1 ? "" : "s"} (latestRunId=${response.contextMetrics.latestRunId})`
|
|
6095
|
+
);
|
|
6096
|
+
console.log("");
|
|
6097
|
+
for (const target of response.targets) {
|
|
6098
|
+
const action = target.action.toUpperCase().padEnd(11);
|
|
6099
|
+
const score = target.score.toFixed(1).padStart(6);
|
|
6100
|
+
const conf = target.actionConfidence.padEnd(6);
|
|
6101
|
+
console.log(`${action} ${score} conf=${conf} ${target.query}`);
|
|
6102
|
+
if (target.ourBestPage) {
|
|
6103
|
+
const posLabel = target.ourBestPage.gscAvgPosition !== null ? `pos #${target.ourBestPage.gscAvgPosition}` : "no GSC ranking";
|
|
6104
|
+
console.log(` our page: ${target.ourBestPage.url} (${posLabel})`);
|
|
6105
|
+
}
|
|
6106
|
+
if (target.winningCompetitor) {
|
|
6107
|
+
console.log(` winning: ${target.winningCompetitor.url} (${target.winningCompetitor.citationCount}\xD7 cited)`);
|
|
6108
|
+
}
|
|
6109
|
+
if (target.drivers.length > 0) {
|
|
6110
|
+
console.log(` why: ${target.drivers.join(" \xB7 ")}`);
|
|
6111
|
+
}
|
|
6112
|
+
if (target.existingAction) {
|
|
6113
|
+
console.log(` in-flight action: ${target.existingAction.actionId} (${target.existingAction.state})`);
|
|
6114
|
+
}
|
|
6115
|
+
console.log("");
|
|
6116
|
+
}
|
|
6117
|
+
}
|
|
6118
|
+
async function listContentSources(project, opts) {
|
|
6119
|
+
const client = createApiClient();
|
|
6120
|
+
const response = await client.getContentSources(project);
|
|
6121
|
+
if (opts.format === "json") {
|
|
6122
|
+
console.log(JSON.stringify(response, null, 2));
|
|
6123
|
+
return;
|
|
6124
|
+
}
|
|
6125
|
+
if (response.sources.length === 0) {
|
|
6126
|
+
console.log("No grounding sources captured yet.");
|
|
6127
|
+
return;
|
|
6128
|
+
}
|
|
6129
|
+
for (const row of response.sources) {
|
|
6130
|
+
console.log(`Q: ${row.query}`);
|
|
6131
|
+
if (row.groundingSources.length === 0) {
|
|
6132
|
+
console.log(" (no grounding sources)");
|
|
6133
|
+
} else {
|
|
6134
|
+
for (const g of row.groundingSources) {
|
|
6135
|
+
const tag = g.isOurDomain ? "OURS " : g.isCompetitor ? "COMP " : "OTHER ";
|
|
6136
|
+
console.log(` ${tag} ${g.uri} (${g.citationCount}\xD7)`);
|
|
6137
|
+
}
|
|
6138
|
+
}
|
|
6139
|
+
console.log("");
|
|
6140
|
+
}
|
|
6141
|
+
}
|
|
6142
|
+
async function listContentGaps(project, opts) {
|
|
6143
|
+
const client = createApiClient();
|
|
6144
|
+
const response = await client.getContentGaps(project);
|
|
6145
|
+
if (opts.format === "json") {
|
|
6146
|
+
console.log(JSON.stringify(response, null, 2));
|
|
6147
|
+
return;
|
|
6148
|
+
}
|
|
6149
|
+
if (response.gaps.length === 0) {
|
|
6150
|
+
console.log("No competitor-only-cited queries detected.");
|
|
6151
|
+
return;
|
|
6152
|
+
}
|
|
6153
|
+
console.log(`${response.gaps.length} gap${response.gaps.length === 1 ? "" : "s"} found`);
|
|
6154
|
+
console.log("");
|
|
6155
|
+
for (const gap of response.gaps) {
|
|
6156
|
+
const missPct = Math.round(gap.missRate * 100);
|
|
6157
|
+
console.log(`${missPct.toString().padStart(3)}% ${gap.competitorCount} competitor(s) ${gap.query}`);
|
|
6158
|
+
console.log(` competitors: ${gap.competitorDomains.join(", ")}`);
|
|
6159
|
+
console.log("");
|
|
6160
|
+
}
|
|
6161
|
+
}
|
|
6162
|
+
|
|
6163
|
+
// src/cli-commands/content.ts
|
|
6164
|
+
var CONTENT_CLI_COMMANDS = [
|
|
6165
|
+
{
|
|
6166
|
+
path: ["content", "targets"],
|
|
6167
|
+
usage: "canonry content targets <project> [--limit <n>] [--include-in-progress] [--format json]",
|
|
6168
|
+
options: {
|
|
6169
|
+
limit: { type: "string" },
|
|
6170
|
+
"include-in-progress": { type: "boolean" }
|
|
6171
|
+
},
|
|
6172
|
+
run: async (input) => {
|
|
6173
|
+
const usage = "canonry content targets <project> [--limit <n>] [--include-in-progress] [--format json]";
|
|
6174
|
+
const project = requireProject(input, "content targets", usage);
|
|
6175
|
+
const limit = parseIntegerOption(input, "limit", {
|
|
6176
|
+
command: "content targets",
|
|
6177
|
+
usage,
|
|
6178
|
+
message: "--limit must be a non-negative integer"
|
|
6179
|
+
});
|
|
6180
|
+
await listContentTargets(project, {
|
|
6181
|
+
limit,
|
|
6182
|
+
includeInProgress: input.values["include-in-progress"] === true,
|
|
6183
|
+
format: input.format
|
|
6184
|
+
});
|
|
6185
|
+
}
|
|
6186
|
+
},
|
|
6187
|
+
{
|
|
6188
|
+
path: ["content", "sources"],
|
|
6189
|
+
usage: "canonry content sources <project> [--format json]",
|
|
6190
|
+
options: {},
|
|
6191
|
+
run: async (input) => {
|
|
6192
|
+
const usage = "canonry content sources <project> [--format json]";
|
|
6193
|
+
const project = requireProject(input, "content sources", usage);
|
|
6194
|
+
await listContentSources(project, { format: input.format });
|
|
6195
|
+
}
|
|
6196
|
+
},
|
|
6197
|
+
{
|
|
6198
|
+
path: ["content", "gaps"],
|
|
6199
|
+
usage: "canonry content gaps <project> [--format json]",
|
|
6200
|
+
options: {},
|
|
6201
|
+
run: async (input) => {
|
|
6202
|
+
const usage = "canonry content gaps <project> [--format json]";
|
|
6203
|
+
const project = requireProject(input, "content gaps", usage);
|
|
6204
|
+
await listContentGaps(project, { format: input.format });
|
|
6205
|
+
}
|
|
6206
|
+
}
|
|
6207
|
+
];
|
|
6208
|
+
|
|
6078
6209
|
// src/commands/bootstrap.ts
|
|
6079
6210
|
import crypto from "crypto";
|
|
6080
6211
|
import path5 from "path";
|
|
@@ -8567,6 +8698,7 @@ var REGISTERED_CLI_COMMANDS = [
|
|
|
8567
8698
|
...CDP_CLI_COMMANDS,
|
|
8568
8699
|
...GA_CLI_COMMANDS,
|
|
8569
8700
|
...INTELLIGENCE_CLI_COMMANDS,
|
|
8701
|
+
...CONTENT_CLI_COMMANDS,
|
|
8570
8702
|
...AGENT_CLI_COMMANDS,
|
|
8571
8703
|
...MCP_CLI_COMMANDS
|
|
8572
8704
|
];
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-FAP76VXF.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-Z3BWDCBJ.js";
|
|
7
|
+
import "./chunk-UM6RDSRJ.js";
|
|
8
8
|
import "./chunk-MLKGABMK.js";
|
|
9
9
|
export {
|
|
10
10
|
createServer,
|