@ainyc/canonry 2.9.0 → 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.
@@ -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
  };
@@ -376,6 +376,18 @@ var runTriggerRequestSchema = z2.object({
376
376
  (data) => Number(Boolean(data.location)) + Number(Boolean(data.allLocations)) + Number(Boolean(data.noLocation)) <= 1,
377
377
  { message: 'Only one of "location", "allLocations", or "noLocation" may be provided' }
378
378
  );
379
+ var runProviderErrorSchema = z2.object({
380
+ /** Human-readable error message (best-effort extracted from `raw.error.message` / `raw.message`, otherwise the raw text with any `[provider-X]` prefix stripped). */
381
+ message: z2.string(),
382
+ /** Original provider response payload, if the underlying error body parsed as JSON. Use this for structured fields like HTTP status, error code, etc. */
383
+ raw: z2.unknown().optional()
384
+ });
385
+ var runErrorSchema = z2.object({
386
+ /** Top-level message for runs that failed without a per-provider error (e.g. user cancellation, internal scheduling failures). */
387
+ message: z2.string().optional(),
388
+ /** Per-provider errors for visibility-sweep runs that had at least one provider fail. */
389
+ providers: z2.record(z2.string(), runProviderErrorSchema).optional()
390
+ });
379
391
  var runDtoSchema = z2.object({
380
392
  id: z2.string(),
381
393
  projectId: z2.string(),
@@ -385,9 +397,70 @@ var runDtoSchema = z2.object({
385
397
  location: z2.string().nullable().optional(),
386
398
  startedAt: z2.string().nullable().optional(),
387
399
  finishedAt: z2.string().nullable().optional(),
388
- error: z2.string().nullable().optional(),
400
+ error: runErrorSchema.nullable().optional(),
389
401
  createdAt: z2.string()
390
402
  });
403
+ var PROVIDER_PREFIX = /^\[provider-[a-zA-Z0-9_-]+\]\s+/;
404
+ function parseProviderErrorMessage(msg) {
405
+ const stripped = msg.replace(PROVIDER_PREFIX, "");
406
+ try {
407
+ const raw = JSON.parse(stripped);
408
+ if (raw && typeof raw === "object") {
409
+ const inner = raw;
410
+ const fromErrorMessage = typeof inner.error?.message === "string" ? inner.error.message : void 0;
411
+ const fromMessage = typeof inner.message === "string" ? inner.message : void 0;
412
+ return { message: fromErrorMessage ?? fromMessage ?? stripped, raw };
413
+ }
414
+ } catch {
415
+ }
416
+ return { message: stripped };
417
+ }
418
+ function parseRunError(raw) {
419
+ if (!raw) return null;
420
+ let parsed;
421
+ try {
422
+ parsed = JSON.parse(raw);
423
+ } catch {
424
+ return { message: raw };
425
+ }
426
+ if (!parsed || typeof parsed !== "object") {
427
+ return { message: raw };
428
+ }
429
+ const obj = parsed;
430
+ const hasProviders = obj.providers && typeof obj.providers === "object";
431
+ const hasMessage = typeof obj.message === "string";
432
+ if (hasProviders || hasMessage) {
433
+ return parsed;
434
+ }
435
+ const providers = {};
436
+ for (const [name, val] of Object.entries(obj)) {
437
+ providers[name] = parseProviderErrorMessage(typeof val === "string" ? val : JSON.stringify(val));
438
+ }
439
+ return { providers };
440
+ }
441
+ function buildRunErrorFromMessages(messages) {
442
+ const providers = {};
443
+ for (const [name, msg] of messages) {
444
+ providers[name] = parseProviderErrorMessage(msg);
445
+ }
446
+ return { providers };
447
+ }
448
+ function serializeRunError(err) {
449
+ return JSON.stringify(err);
450
+ }
451
+ function formatRunErrorOneLine(err) {
452
+ if (err.providers) {
453
+ const entries = Object.entries(err.providers);
454
+ if (entries.length === 1) {
455
+ const [provider, detail] = entries[0];
456
+ return `${provider}: ${detail.message}`;
457
+ }
458
+ if (entries.length > 1) {
459
+ return entries.map(([p, d]) => `${p}: ${d.message}`).join(" \u2022 ");
460
+ }
461
+ }
462
+ return err.message ?? "Run failed.";
463
+ }
391
464
  var groundingSourceSchema = z2.object({
392
465
  uri: z2.string(),
393
466
  title: z2.string()
@@ -1467,6 +1540,108 @@ var ccCachedReleaseSchema = z14.object({
1467
1540
  lastUsedAt: z14.string().nullable()
1468
1541
  });
1469
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
+
1470
1645
  // src/client.ts
1471
1646
  function createApiClient() {
1472
1647
  const config = loadConfig();
@@ -2032,6 +2207,29 @@ var ApiClient = class {
2032
2207
  async dismissInsight(project, id) {
2033
2208
  return this.request("POST", `/projects/${encodeURIComponent(project)}/insights/${encodeURIComponent(id)}/dismiss`);
2034
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
+ }
2035
2233
  async getHealth(project) {
2036
2234
  return this.request("GET", `/projects/${encodeURIComponent(project)}/health/latest`);
2037
2235
  }
@@ -2130,7 +2328,12 @@ export {
2130
2328
  RunStatuses,
2131
2329
  RunKinds,
2132
2330
  RunTriggers,
2331
+ CitationStates,
2133
2332
  runTriggerRequestSchema,
2333
+ parseRunError,
2334
+ buildRunErrorFromMessages,
2335
+ serializeRunError,
2336
+ formatRunErrorOneLine,
2134
2337
  snapshotRequestSchema,
2135
2338
  scheduleUpsertRequestSchema,
2136
2339
  parseWindow,