@canonry/canonry 4.133.3 → 4.134.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.
Files changed (26) hide show
  1. package/README.md +0 -13
  2. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +18 -0
  3. package/assets/assets/{AuditHistoryPanel-DTHQGvkW.js → AuditHistoryPanel-DBUWbfNk.js} +1 -1
  4. package/assets/assets/{BacklinksPage-DuPI3wt9.js → BacklinksPage-BFm0VdUO.js} +1 -1
  5. package/assets/assets/{ChartPrimitives-T8mxOObc.js → ChartPrimitives-oV_nduTH.js} +1 -1
  6. package/assets/assets/{HistoryPage-T1W40xKG.js → HistoryPage-IF2FMem3.js} +1 -1
  7. package/assets/assets/{ProjectPage-DYwM8-Rp.js → ProjectPage-ClwxX9Ga.js} +2 -2
  8. package/assets/assets/{RunRow-nHr4wW9B.js → RunRow-De5zdch5.js} +1 -1
  9. package/assets/assets/{RunsPage-Cbusy_84.js → RunsPage-xtRF78r8.js} +1 -1
  10. package/assets/assets/{SettingsPage-BVcbl4gn.js → SettingsPage-B_GAxaWu.js} +1 -1
  11. package/assets/assets/{TrafficPage-CZ4NTWI6.js → TrafficPage-E8I5ineJ.js} +1 -1
  12. package/assets/assets/{TrafficSourceDetailPage-xW-KvZY1.js → TrafficSourceDetailPage-DGk2MqSp.js} +1 -1
  13. package/assets/assets/{arrow-left-flskyFPK.js → arrow-left-D-4QMxi9.js} +1 -1
  14. package/assets/assets/{extract-error-message-BHfCTm0H.js → extract-error-message-DuqIFU8Z.js} +1 -1
  15. package/assets/assets/{index-CQzGDGMB.js → index-De1MKbc7.js} +88 -88
  16. package/assets/assets/{trash-2-LGfqtSzZ.js → trash-2-BVh__r6G.js} +1 -1
  17. package/assets/index.html +1 -1
  18. package/dist/{chunk-HENMGNLT.js → chunk-LIXWDIDR.js} +496 -3
  19. package/dist/{chunk-YRRQFOAH.js → chunk-PC7AYXND.js} +233 -2
  20. package/dist/{chunk-6ON6GUAL.js → chunk-TKXWTDV6.js} +68 -6
  21. package/dist/{chunk-A6BYMSZL.js → chunk-ZUQPHGEN.js} +45 -2
  22. package/dist/cli.js +68 -4
  23. package/dist/index.js +4 -4
  24. package/dist/{intelligence-service-OSLFG5S2.js → intelligence-service-4XOFUN5E.js} +2 -2
  25. package/dist/mcp.js +2 -2
  26. package/package.json +7 -7
@@ -47,8 +47,8 @@ function authInvalid() {
47
47
  function forbidden(message = "Forbidden", details) {
48
48
  return new AppError("FORBIDDEN", message, 403, details);
49
49
  }
50
- function quotaExceeded(metric) {
51
- return new AppError("QUOTA_EXCEEDED", `Quota exceeded for ${metric}`, 429);
50
+ function quotaExceeded(metric, details) {
51
+ return new AppError("QUOTA_EXCEEDED", `Quota exceeded for ${metric}`, 429, details);
52
52
  }
53
53
  function providerError(message, details) {
54
54
  return new AppError("PROVIDER_ERROR", message, 502, details);
@@ -960,6 +960,111 @@ function formatIsoDate(iso) {
960
960
  return iso;
961
961
  }
962
962
  }
963
+ function zonedClockFormat(timeZone) {
964
+ return new Intl.DateTimeFormat("en-US", {
965
+ timeZone,
966
+ year: "numeric",
967
+ month: "2-digit",
968
+ day: "2-digit",
969
+ hour: "2-digit",
970
+ minute: "2-digit",
971
+ second: "2-digit",
972
+ hourCycle: "h23"
973
+ });
974
+ }
975
+ function readZonedParts(format, date) {
976
+ const values = {};
977
+ for (const part of format.formatToParts(date)) values[part.type] = part.value;
978
+ return values;
979
+ }
980
+ function readZonedClock(format, date) {
981
+ const parts = readZonedParts(format, date);
982
+ const value = (raw) => {
983
+ if (raw === void 0) return null;
984
+ const parsed = Number(raw);
985
+ return Number.isFinite(parsed) ? parsed : null;
986
+ };
987
+ const year = value(parts.year);
988
+ const month = value(parts.month);
989
+ const day = value(parts.day);
990
+ const hour = value(parts.hour);
991
+ const minute = value(parts.minute);
992
+ const second = value(parts.second);
993
+ if (year === null || month === null || day === null) return null;
994
+ if (hour === null || minute === null || second === null) return null;
995
+ return { year, month, day, hour, minute, second };
996
+ }
997
+ function formatIsoDateInTimeZone(iso, timeZone) {
998
+ if (!iso) return formatIsoDate(iso);
999
+ const d = new Date(iso);
1000
+ if (Number.isNaN(d.getTime())) return iso;
1001
+ try {
1002
+ const parts = readZonedParts(zonedClockFormat(timeZone), d);
1003
+ const yyyy = parts.year;
1004
+ const mm = parts.month;
1005
+ const dd = parts.day;
1006
+ if (!yyyy || !mm || !dd) return formatIsoDate(iso);
1007
+ return `${yyyy}-${mm}-${dd}`;
1008
+ } catch {
1009
+ return formatIsoDate(iso);
1010
+ }
1011
+ }
1012
+ function shiftIsoCalendarDate(isoDate, days) {
1013
+ if (!Number.isFinite(days)) return isoDate;
1014
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(isoDate);
1015
+ if (!match?.[1] || !match[2] || !match[3]) return isoDate;
1016
+ const shifted = new Date(Date.UTC(
1017
+ Number(match[1]),
1018
+ Number(match[2]) - 1,
1019
+ Number(match[3]) + Math.trunc(days)
1020
+ ));
1021
+ if (Number.isNaN(shifted.getTime())) return isoDate;
1022
+ const yyyy = shifted.getUTCFullYear();
1023
+ const mm = String(shifted.getUTCMonth() + 1).padStart(2, "0");
1024
+ const dd = String(shifted.getUTCDate()).padStart(2, "0");
1025
+ return `${yyyy}-${mm}-${dd}`;
1026
+ }
1027
+ function isoDateDaysBeforeInTimeZone(iso, days, timeZone) {
1028
+ return shiftIsoCalendarDate(formatIsoDateInTimeZone(iso, timeZone), -days);
1029
+ }
1030
+ var HOURS_PER_DAY = 24;
1031
+ var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
1032
+ function localHourExists(format, year, month, day, hour) {
1033
+ const asIfUtcMs = Date.UTC(year, month - 1, day, hour);
1034
+ for (const probeMs of [asIfUtcMs - ONE_DAY_MS, asIfUtcMs + ONE_DAY_MS]) {
1035
+ const probeClock = readZonedClock(format, new Date(probeMs));
1036
+ if (!probeClock) continue;
1037
+ const offsetMs = Date.UTC(
1038
+ probeClock.year,
1039
+ probeClock.month - 1,
1040
+ probeClock.day,
1041
+ probeClock.hour,
1042
+ probeClock.minute,
1043
+ probeClock.second
1044
+ ) - probeMs;
1045
+ const candidate = readZonedClock(format, new Date(asIfUtcMs - offsetMs));
1046
+ if (!candidate) continue;
1047
+ if (candidate.year === year && candidate.month === month && candidate.day === day && candidate.hour === hour && candidate.minute === 0 && candidate.second === 0) return true;
1048
+ }
1049
+ return false;
1050
+ }
1051
+ function startOfDayHourInTimeZone(isoDate, timeZone) {
1052
+ const hourBoundary = (hour) => `${isoDate}T${String(hour).padStart(2, "0")}`;
1053
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(isoDate);
1054
+ if (!match?.[1] || !match[2] || !match[3]) return hourBoundary(0);
1055
+ const year = Number(match[1]);
1056
+ const month = Number(match[2]);
1057
+ const day = Number(match[3]);
1058
+ try {
1059
+ const format = zonedClockFormat(timeZone);
1060
+ for (let hour = 0; hour < HOURS_PER_DAY; hour += 1) {
1061
+ if (localHourExists(format, year, month, day, hour)) return hourBoundary(hour);
1062
+ }
1063
+ return hourBoundary(0);
1064
+ } catch {
1065
+ return hourBoundary(0);
1066
+ }
1067
+ }
963
1068
  function formatDateRange(start, end) {
964
1069
  if (!start && !end) return "";
965
1070
  if (start && end) return `${formatDate(start)} \u2192 ${formatDate(end)}`;
@@ -6518,6 +6623,8 @@ var researchRunQuerySchema = z37.object({
6518
6623
  groundingSources: z37.array(groundingSourceSchema),
6519
6624
  citedDomains: z37.array(z37.string()),
6520
6625
  searchQueries: z37.array(z37.string()),
6626
+ namedCompetitors: z37.array(z37.string()).default([]),
6627
+ citedCompetitorDomains: z37.array(z37.string()).default([]),
6521
6628
  answerMentioned: z37.boolean().nullable(),
6522
6629
  citationState: citationStateSchema.nullable(),
6523
6630
  error: z37.string().nullable(),
@@ -7333,6 +7440,122 @@ var adsOperationReconcileResponseSchema = z38.object({
7333
7440
  operation: adsOperationDtoSchema,
7334
7441
  resolved: z38.boolean()
7335
7442
  });
7443
+ var adsLiveDeliveryQuerySchema = z38.object({
7444
+ campaignId: z38.string().min(1).max(200).optional(),
7445
+ lookbackDays: z38.coerce.number().int().min(1).max(30).default(7)
7446
+ });
7447
+ var adsLiveEntityTypeSchema = z38.enum(["campaign", "ad_group", "ad"]);
7448
+ var AdsLiveEntityTypes = adsLiveEntityTypeSchema.enum;
7449
+ var adsLivePresenceSchema = z38.enum(["both", "live-only", "stored-only"]);
7450
+ var AdsLivePresences = adsLivePresenceSchema.enum;
7451
+ var adsLiveDeliveryBasisSchema = z38.literal("live-provider-read");
7452
+ var AdsLiveDeliveryBases = { liveProviderRead: "live-provider-read" };
7453
+ var adsLiveMetricRowSchema = z38.object({
7454
+ date: z38.string().nullable(),
7455
+ startTime: z38.number().nullable(),
7456
+ endTime: z38.number().nullable(),
7457
+ impressions: z38.number().nullable(),
7458
+ clicks: z38.number().nullable(),
7459
+ spend: z38.number().nullable(),
7460
+ conversions: z38.number().nullable(),
7461
+ ctr: z38.number().nullable(),
7462
+ cpc: z38.number().nullable(),
7463
+ cpm: z38.number().nullable()
7464
+ });
7465
+ var adsLiveMetricValuesSchema = z38.object({
7466
+ impressions: z38.number().int(),
7467
+ clicks: z38.number().int(),
7468
+ spendMicros: z38.number().int(),
7469
+ conversions: z38.number().int()
7470
+ });
7471
+ var adsLiveMetricDeltaSchema = z38.object({
7472
+ date: z38.string(),
7473
+ live: adsLiveMetricValuesSchema.nullable(),
7474
+ stored: adsLiveMetricValuesSchema.nullable(),
7475
+ drifted: z38.boolean()
7476
+ });
7477
+ var adsLiveFieldDeltaSchema = z38.object({
7478
+ field: z38.string(),
7479
+ live: z38.string().nullable(),
7480
+ stored: z38.string().nullable()
7481
+ });
7482
+ var adsLiveEntityStateSchema = z38.object({
7483
+ name: z38.string().nullable(),
7484
+ status: z38.string(),
7485
+ reviewStatus: z38.string().nullable(),
7486
+ mode: z38.string().nullable(),
7487
+ updatedAt: z38.number().nullable()
7488
+ });
7489
+ var adsStoredEntityStateSchema = z38.object({
7490
+ name: z38.string(),
7491
+ status: z38.string(),
7492
+ reviewStatus: z38.string().nullable(),
7493
+ upstreamUpdatedAt: z38.number().nullable(),
7494
+ syncedAt: z38.string()
7495
+ });
7496
+ var adsLiveEntityComparisonSchema = z38.object({
7497
+ entityType: adsLiveEntityTypeSchema,
7498
+ id: z38.string(),
7499
+ parentId: z38.string().nullable(),
7500
+ presence: adsLivePresenceSchema,
7501
+ live: adsLiveEntityStateSchema.nullable(),
7502
+ stored: adsStoredEntityStateSchema.nullable(),
7503
+ fieldDeltas: z38.array(adsLiveFieldDeltaSchema),
7504
+ /** Provider rows, unaggregated. Null for ads (no per-ad insights surface). */
7505
+ liveMetrics: z38.array(adsLiveMetricRowSchema).nullable(),
7506
+ metricDeltas: z38.array(adsLiveMetricDeltaSchema).nullable(),
7507
+ drifted: z38.boolean()
7508
+ });
7509
+ var adsLiveReadFailureSchema = z38.object({
7510
+ surface: z38.string(),
7511
+ entityId: z38.string().nullable(),
7512
+ upstreamStatus: z38.number().int().nullable()
7513
+ });
7514
+ var adsLiveDeliveryDtoSchema = z38.object({
7515
+ basis: adsLiveDeliveryBasisSchema,
7516
+ /** Instant the live read was issued; the metrics window is measured back from it. */
7517
+ fetchedAt: z38.string(),
7518
+ adAccountId: z38.string(),
7519
+ storedSnapshotSyncedAt: z38.string().nullable(),
7520
+ metricsWindow: z38.object({ lookbackDays: z38.number().int().positive() }),
7521
+ /**
7522
+ * Two different units live here, and confusing them understates the cost of
7523
+ * this endpoint by two orders of magnitude.
7524
+ *
7525
+ * A READER CALL is one logical list or insight read. Every list/insight
7526
+ * reader call is an auto-paginating walk, so one reader call can issue up to
7527
+ * `maxPagesPerReaderCall` upstream HTTP requests before the client gives up.
7528
+ * `maxUpstreamHttpRequests` is the honest worst-case ceiling in HTTP
7529
+ * requests (`maxReaderCalls * maxPagesPerReaderCall`, about 4000 at the
7530
+ * shipped defaults, not 40).
7531
+ *
7532
+ * `readerCalls` is an observed count of reader calls. There is deliberately
7533
+ * no observed HTTP-request count: the pagination happens inside the provider
7534
+ * client, below the injected reader seam, so the route cannot see it.
7535
+ */
7536
+ bounds: z38.object({
7537
+ maxCampaigns: z38.number().int().positive(),
7538
+ maxAdGroupsPerCampaign: z38.number().int().positive(),
7539
+ maxAdsPerAdGroup: z38.number().int().positive(),
7540
+ /** Budget in logical reader calls, NOT in upstream HTTP requests. */
7541
+ maxReaderCalls: z38.number().int().positive(),
7542
+ /** Reader calls this read actually issued. Observed, not a bound. */
7543
+ readerCalls: z38.number().int().nonnegative(),
7544
+ /** Pages one paginated reader call may fetch before the client gives up. */
7545
+ maxPagesPerReaderCall: z38.number().int().positive(),
7546
+ /** Worst-case upstream HTTP requests. A documented bound, not an observation. */
7547
+ maxUpstreamHttpRequests: z38.number().int().positive(),
7548
+ truncated: z38.boolean()
7549
+ }),
7550
+ entities: z38.array(adsLiveEntityComparisonSchema),
7551
+ drift: z38.object({
7552
+ entitiesCompared: z38.number().int().nonnegative(),
7553
+ driftedEntities: z38.number().int().nonnegative(),
7554
+ statusDrifted: z38.number().int().nonnegative(),
7555
+ metricsDrifted: z38.number().int().nonnegative()
7556
+ }),
7557
+ errors: z38.array(adsLiveReadFailureSchema)
7558
+ });
7336
7559
  function adsCtr(clicks, impressions) {
7337
7560
  return impressions > 0 ? clicks / impressions : null;
7338
7561
  }
@@ -7583,6 +7806,9 @@ export {
7583
7806
  formatNumber,
7584
7807
  formatDate,
7585
7808
  formatIsoDate,
7809
+ formatIsoDateInTimeZone,
7810
+ isoDateDaysBeforeInTimeZone,
7811
+ startOfDayHourInTimeZone,
7586
7812
  formatDateRange,
7587
7813
  parseInclusiveEndMs,
7588
7814
  deltaPercent,
@@ -7902,6 +8128,11 @@ export {
7902
8128
  adsUnresolvedOperationListResponseSchema,
7903
8129
  adsOperationReconcileRequestSchema,
7904
8130
  adsOperationReconcileResponseSchema,
8131
+ adsLiveDeliveryQuerySchema,
8132
+ AdsLiveEntityTypes,
8133
+ AdsLivePresences,
8134
+ AdsLiveDeliveryBases,
8135
+ adsLiveDeliveryDtoSchema,
7905
8136
  adsCtr,
7906
8137
  adsCpcMicros,
7907
8138
  dollarsToMicros,
@@ -10,7 +10,7 @@ import {
10
10
  loadConfig,
11
11
  loadConfigRaw,
12
12
  saveConfigPatch
13
- } from "./chunk-A6BYMSZL.js";
13
+ } from "./chunk-ZUQPHGEN.js";
14
14
  import {
15
15
  CC_CACHE_DIR,
16
16
  DUCKDB_SPEC,
@@ -40,6 +40,7 @@ import {
40
40
  classifyAiUserFetch,
41
41
  classifyCrawler,
42
42
  competitors,
43
+ computeCitedCompetitorDomains,
43
44
  computeCompetitorOverlap,
44
45
  countAttributes,
45
46
  countPopulatedGroups,
@@ -118,7 +119,7 @@ import {
118
119
  siteAuditPages,
119
120
  siteAuditSnapshots,
120
121
  usageCounters
121
- } from "./chunk-HENMGNLT.js";
122
+ } from "./chunk-LIXWDIDR.js";
122
123
  import {
123
124
  AGENT_MEMORY_VALUE_MAX_BYTES,
124
125
  AGENT_PROVIDER_IDS,
@@ -188,10 +189,11 @@ import {
188
189
  serializeRunError,
189
190
  skillsClientSchema,
190
191
  splitList,
192
+ startOfDayHourInTimeZone,
191
193
  validationError,
192
194
  winnabilityClassLabel,
193
195
  withRetry
194
- } from "./chunk-YRRQFOAH.js";
196
+ } from "./chunk-PC7AYXND.js";
195
197
 
196
198
  // src/telemetry.ts
197
199
  import crypto from "crypto";
@@ -4877,14 +4879,22 @@ function accountHour(date, timezone) {
4877
4879
  };
4878
4880
  return `${value("year")}-${value("month")}-${value("day")}T${value("hour")}`;
4879
4881
  }
4880
- function trailingAdsInsightHourRange(now, timezone) {
4882
+ function trailingAdsInsightHourRange(now, timezone, lookbackMs = INSIGHTS_LOOKBACK_MS) {
4881
4883
  return {
4882
4884
  type: "hour_range",
4883
- since: accountHour(new Date(now.getTime() - INSIGHTS_LOOKBACK_MS), timezone),
4885
+ since: accountHour(new Date(now.getTime() - lookbackMs), timezone),
4884
4886
  until: accountHour(now, timezone),
4885
4887
  timezone
4886
4888
  };
4887
4889
  }
4890
+ function liveAdsInsightHourRange(request) {
4891
+ return {
4892
+ type: "hour_range",
4893
+ since: startOfDayHourInTimeZone(request.startDate, request.timezone),
4894
+ until: accountHour(new Date(request.fetchedAtMs), request.timezone),
4895
+ timezone: request.timezone
4896
+ };
4897
+ }
4888
4898
  function toInsightUpserts(level, entityId, rows) {
4889
4899
  const upserts = [];
4890
4900
  for (const row of rows) {
@@ -7000,7 +7010,7 @@ function readStoredGroundingSources(rawResponse) {
7000
7010
  return result;
7001
7011
  }
7002
7012
  async function backfillInsightsCommand(project, opts) {
7003
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-OSLFG5S2.js");
7013
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-4XOFUN5E.js");
7004
7014
  const config = loadConfig();
7005
7015
  const db = createClient(config.database);
7006
7016
  migrate(db);
@@ -9114,6 +9124,7 @@ var AERO_ADS_OPERATOR_MCP_TOOL_NAMES = /* @__PURE__ */ new Set([
9114
9124
  CanonryMcpToolNames.canonry_ads_insights,
9115
9125
  CanonryMcpToolNames.canonry_ads_summary,
9116
9126
  CanonryMcpToolNames.canonry_ads_delivery_diagnostics,
9127
+ CanonryMcpToolNames.canonry_ads_live_delivery,
9117
9128
  CanonryMcpToolNames.canonry_ads_operations_unresolved,
9118
9129
  CanonryMcpToolNames.canonry_ads_operation_get,
9119
9130
  CanonryMcpToolNames.canonry_ads_operation_reconcile,
@@ -11592,6 +11603,14 @@ async function executeResearchRun(db, registry, runId, projectId) {
11592
11603
  return provider.adapter.executeTrackedQuery({ query: row.queryText, canonicalDomains: domains, competitorDomains, ...run.location ? { location: run.location } : {} }, config);
11593
11604
  });
11594
11605
  const normalized = provider.adapter.normalizeResult(raw);
11606
+ const namedCompetitors = extractRecommendedCompetitors(
11607
+ normalized.answerText,
11608
+ domains,
11609
+ normalized.citedDomains,
11610
+ competitorDomains,
11611
+ brands
11612
+ );
11613
+ const citedCompetitorDomains = computeCitedCompetitorDomains(normalized.citedDomains, competitorDomains);
11595
11614
  const completed = db.update(researchRunQueries).set({
11596
11615
  status: ResearchQueryStatuses.completed,
11597
11616
  servedModel: raw.servedModel ?? null,
@@ -11599,6 +11618,8 @@ async function executeResearchRun(db, registry, runId, projectId) {
11599
11618
  groundingSources: normalized.groundingSources,
11600
11619
  citedDomains: normalized.citedDomains,
11601
11620
  searchQueries: normalized.searchQueries,
11621
+ namedCompetitors,
11622
+ citedCompetitorDomains,
11602
11623
  answerMentioned: determineAnswerMentioned(normalized.answerText, brands, domains),
11603
11624
  citationState: determineCitationState(normalized, domains),
11604
11625
  rawResponse: raw.rawResponse,
@@ -12204,6 +12225,42 @@ async function createServer(opts) {
12204
12225
  activateAd: async (apiKey, id) => adsAdEntityResult(await activateAd(apiKey, id)),
12205
12226
  pauseAd: async (apiKey, id) => adsAdEntityResult(await pauseAd(apiKey, id))
12206
12227
  };
12228
+ const adsLiveEntity = (entity, extra) => ({
12229
+ id: entity.id,
12230
+ name: entity.name,
12231
+ status: entity.status,
12232
+ reviewStatus: extra?.reviewStatus ?? null,
12233
+ mode: extra?.mode ?? null,
12234
+ updatedAt: entity.updated_at
12235
+ });
12236
+ const adsLiveMetricRow = (row) => ({
12237
+ date: row.readable_time ?? null,
12238
+ startTime: row.start_time ?? null,
12239
+ endTime: row.end_time ?? null,
12240
+ impressions: row.impressions ?? null,
12241
+ clicks: row.clicks ?? null,
12242
+ // Provider units: the insights API returns decimal currency for spend.
12243
+ spend: row.spend ?? null,
12244
+ conversions: row.conversions ?? null,
12245
+ ctr: row.ctr ?? null,
12246
+ cpc: row.cpc ?? null,
12247
+ cpm: row.cpm ?? null
12248
+ });
12249
+ const adsLiveDeliveryReader = {
12250
+ listCampaigns: async (apiKey) => (await listCampaigns(apiKey)).map((campaign) => adsLiveEntity(campaign, { mode: campaign.mode })),
12251
+ listAdGroups: async (apiKey, campaignId) => (await listAdGroups(apiKey, campaignId)).map((adGroup) => adsLiveEntity(adGroup)),
12252
+ listAds: async (apiKey, adGroupId) => (await listAds(apiKey, adGroupId)).map(
12253
+ (ad) => adsLiveEntity(ad, { reviewStatus: ad.review_status ?? ad.review?.status ?? null })
12254
+ ),
12255
+ campaignInsights: async (apiKey, campaignId, request) => (await getCampaignInsights(apiKey, campaignId, {
12256
+ fields: CAMPAIGN_INSIGHT_FIELDS,
12257
+ timeRanges: [liveAdsInsightHourRange(request)]
12258
+ })).map(adsLiveMetricRow),
12259
+ adGroupInsights: async (apiKey, adGroupId, request) => (await getAdGroupInsights(apiKey, adGroupId, {
12260
+ fields: AD_GROUP_INSIGHT_FIELDS,
12261
+ timeRanges: [liveAdsInsightHourRange(request)]
12262
+ })).map(adsLiveMetricRow)
12263
+ };
12207
12264
  const scheduler = new Scheduler(opts.db, {
12208
12265
  onRunCreated: (runId, projectId, providers2, location) => {
12209
12266
  jobRunner.executeRun(runId, projectId, providers2, location).catch((err) => {
@@ -12803,6 +12860,11 @@ async function createServer(opts) {
12803
12860
  adsCredentialStore,
12804
12861
  verifyAdsAccount,
12805
12862
  adsReader,
12863
+ adsLiveDeliveryReader,
12864
+ // Reporting only: lets the route state the true upstream HTTP ceiling
12865
+ // (reader-call budget x pages per reader call) instead of implying that a
12866
+ // reader call is one request.
12867
+ adsLiveDeliveryMaxPagesPerReaderCall: OPENAI_ADS_MAX_PAGES,
12806
12868
  adsOperator,
12807
12869
  onAdsSyncRequested: (runId, projectId) => {
12808
12870
  runAdsSync(runId, projectId);
@@ -42,7 +42,7 @@ import {
42
42
  trafficConnectWordpressRequestSchema,
43
43
  trafficEventKindSchema,
44
44
  trafficSeriesGranularitySchema
45
- } from "./chunk-YRRQFOAH.js";
45
+ } from "./chunk-PC7AYXND.js";
46
46
 
47
47
  // src/config.ts
48
48
  import fs from "fs";
@@ -2739,6 +2739,18 @@ var getApiV1ProjectsByNameAdsDeliveryDiagnostics = (options) => {
2739
2739
  ...options
2740
2740
  });
2741
2741
  };
2742
+ var getApiV1ProjectsByNameAdsLiveDelivery = (options) => {
2743
+ return (options.client ?? client).get({
2744
+ security: [
2745
+ {
2746
+ scheme: "bearer",
2747
+ type: "http"
2748
+ }
2749
+ ],
2750
+ url: "/api/v1/projects/{name}/ads/live-delivery",
2751
+ ...options
2752
+ });
2753
+ };
2742
2754
  var postApiV1ProjectsByNameBingConnect = (options) => {
2743
2755
  return (options.client ?? client).post({
2744
2756
  security: [
@@ -4871,6 +4883,18 @@ var ApiClient = class {
4871
4883
  () => getApiV1ProjectsByNameAdsDeliveryDiagnostics({ client: this.heyClient, path: { name: project } })
4872
4884
  );
4873
4885
  }
4886
+ async getAdsLiveDelivery(project, opts) {
4887
+ return this.invoke(
4888
+ () => getApiV1ProjectsByNameAdsLiveDelivery({
4889
+ client: this.heyClient,
4890
+ path: { name: project },
4891
+ query: {
4892
+ ...opts?.campaignId === void 0 ? {} : { campaignId: opts.campaignId },
4893
+ ...opts?.lookbackDays === void 0 ? {} : { lookbackDays: opts.lookbackDays }
4894
+ }
4895
+ })
4896
+ );
4897
+ }
4874
4898
  async getAdsOperation(project, operationKey) {
4875
4899
  return this.invoke(
4876
4900
  () => getApiV1ProjectsByNameAdsOperationsByOperationKey({
@@ -6169,6 +6193,11 @@ var adsInsightsInputSchema = z2.object({
6169
6193
  var adsGeoSearchInputSchema = adsGeoSearchQuerySchema.extend({
6170
6194
  project: projectNameSchema
6171
6195
  });
6196
+ var adsLiveDeliveryInputSchema = z2.object({
6197
+ project: projectNameSchema,
6198
+ campaignId: z2.string().min(1).max(200).optional(),
6199
+ lookbackDays: z2.number().int().min(1).max(30).optional()
6200
+ });
6172
6201
  var adsOperationInputSchema = z2.object({
6173
6202
  project: projectNameSchema,
6174
6203
  operationKey: z2.string().min(8).max(128)
@@ -7710,7 +7739,7 @@ var canonryMcpTools = [
7710
7739
  defineTool({
7711
7740
  name: "canonry_research_run_get",
7712
7741
  title: "Get research query run",
7713
- description: "Get the saved per-query answers, sources, cited domains, and independent mention/citation results for one research run. It is read-only and does not promote or track any query.",
7742
+ description: "Get saved per-query answers, source links, cited domains, answer-text named competitors, cited competitor domains, and independent mention/citation results for one research run. It is read-only and does not promote or track any query.",
7714
7743
  access: "read",
7715
7744
  tier: "discovery",
7716
7745
  inputSchema: researchRunIdInputSchema,
@@ -7940,6 +7969,20 @@ var canonryMcpTools = [
7940
7969
  openApiOperations: ["GET /api/v1/projects/{name}/ads/delivery-diagnostics"],
7941
7970
  handler: (client2, input) => client2.getAdsDeliveryDiagnostics(input.project)
7942
7971
  }),
7972
+ defineTool({
7973
+ name: "canonry_ads_live_delivery",
7974
+ title: "Live ads read with stored-snapshot delta",
7975
+ description: "Call the OpenAI Ads API right now and return the provider's current status and metrics per campaign / ad group / ad, unaggregated, alongside the stored snapshot values and an explicit per-entity delta. Use this when stored data is suspected of being stale or contradicted by the advertiser UI. Read-only: it mutates nothing and does not wait for a sync. The walk is bounded and one project may issue at most one live read per minute, so prefer canonry_ads_delivery_diagnostics for routine checks.",
7976
+ access: "read",
7977
+ tier: "ads",
7978
+ inputSchema: adsLiveDeliveryInputSchema,
7979
+ annotations: readAnnotations(),
7980
+ openApiOperations: ["GET /api/v1/projects/{name}/ads/live-delivery"],
7981
+ handler: (client2, input) => client2.getAdsLiveDelivery(input.project, {
7982
+ campaignId: input.campaignId,
7983
+ lookbackDays: input.lookbackDays
7984
+ })
7985
+ }),
7943
7986
  defineTool({
7944
7987
  name: "canonry_ads_operations_unresolved",
7945
7988
  title: "List unresolved ads mutation receipts",
package/dist/cli.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  setTelemetrySource,
30
30
  showFirstRunNotice,
31
31
  trackEvent
32
- } from "./chunk-6ON6GUAL.js";
32
+ } from "./chunk-TKXWTDV6.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-A6BYMSZL.js";
49
+ } from "./chunk-ZUQPHGEN.js";
50
50
  import {
51
51
  apiKeys,
52
52
  createClient,
@@ -54,10 +54,11 @@ import {
54
54
  projects,
55
55
  queries,
56
56
  renderReportHtml
57
- } from "./chunk-HENMGNLT.js";
57
+ } from "./chunk-LIXWDIDR.js";
58
58
  import {
59
59
  AdsDeliverySnapshotStatuses,
60
60
  AdsHistoricalCampaignRollupStatuses,
61
+ AdsLiveEntityTypes,
61
62
  AdsOperationKinds,
62
63
  AdsOperationStates,
63
64
  AdsOperationStepStates,
@@ -99,7 +100,7 @@ import {
99
100
  providerQuotaPolicySchema,
100
101
  resolveProviderInput,
101
102
  winnabilityClassSchema
102
- } from "./chunk-YRRQFOAH.js";
103
+ } from "./chunk-PC7AYXND.js";
103
104
 
104
105
  // src/cli.ts
105
106
  import { pathToFileURL } from "url";
@@ -2692,6 +2693,12 @@ function printDetail(project, detail, format) {
2692
2693
  console.log(` ${query.query.slice(0, 58).padEnd(58)} ${query.status.padEnd(10)} ${cited.padEnd(5)} ${mentioned}`);
2693
2694
  if (query.error) console.log(` Error: ${query.error}`);
2694
2695
  if (query.answerText) console.log(` Answer: ${query.answerText}`);
2696
+ if (query.namedCompetitors.length > 0) {
2697
+ console.log(` Named competitors: ${query.namedCompetitors.join(", ")}`);
2698
+ }
2699
+ if (query.citedCompetitorDomains.length > 0) {
2700
+ console.log(` Cited competitor domains: ${query.citedCompetitorDomains.join(", ")}`);
2701
+ }
2695
2702
  if (query.groundingSources.length > 0) {
2696
2703
  console.log(" Sources:");
2697
2704
  for (const source of query.groundingSources) {
@@ -3414,6 +3421,42 @@ async function adsDeliveryDiagnostics(project, opts) {
3414
3421
  }
3415
3422
  }
3416
3423
  }
3424
+ async function adsLiveDelivery(project, opts) {
3425
+ const result = await getClient6().getAdsLiveDelivery(project, {
3426
+ campaignId: opts?.campaignId,
3427
+ lookbackDays: opts?.lookbackDays
3428
+ });
3429
+ if (isMachineFormat(opts?.format)) {
3430
+ console.log(JSON.stringify(result, null, 2));
3431
+ return;
3432
+ }
3433
+ console.log(`Read at: ${result.fetchedAt} (live provider read)`);
3434
+ console.log(`Ad account: ${result.adAccountId}`);
3435
+ console.log(`Snapshot: last synced ${result.storedSnapshotSyncedAt ?? "never"}`);
3436
+ console.log(`Metrics: last ${result.metricsWindow.lookbackDays}d, as the provider reported them`);
3437
+ console.log(`Provider: ${result.bounds.readerCalls}/${result.bounds.maxReaderCalls} reader calls, up to ${result.bounds.maxUpstreamHttpRequests} upstream HTTP requests${result.bounds.truncated ? " (TRUNCATED, walk hit a cap)" : ""}`);
3438
+ console.log(`Drift: ${result.drift.driftedEntities}/${result.drift.entitiesCompared} entities (${result.drift.statusDrifted} status, ${result.drift.metricsDrifted} metrics)`);
3439
+ for (const failure of result.errors) {
3440
+ console.log(`Read failed: ${failure.surface}${failure.entityId ? ` ${failure.entityId}` : ""}${failure.upstreamStatus === null ? "" : ` [HTTP ${failure.upstreamStatus}]`}`);
3441
+ }
3442
+ for (const entity of result.entities) {
3443
+ const indent = entity.entityType === AdsLiveEntityTypes.campaign ? "" : entity.entityType === AdsLiveEntityTypes.ad_group ? " " : " ";
3444
+ const label = entity.live?.name ?? entity.stored?.name ?? entity.id;
3445
+ const liveStatus = entity.live === null ? "absent upstream" : entity.live.status;
3446
+ const storedStatus = entity.stored === null ? "absent locally" : entity.stored.status;
3447
+ const marker = entity.drifted ? "DRIFT" : "match";
3448
+ console.log(`${indent}${label} [${entity.entityType}]: live ${liveStatus} / stored ${storedStatus} (${marker})`);
3449
+ for (const delta of entity.fieldDeltas) {
3450
+ console.log(`${indent} ${delta.field}: live ${delta.live ?? "none"} / stored ${delta.stored ?? "none"}`);
3451
+ }
3452
+ for (const delta of entity.metricDeltas ?? []) {
3453
+ if (!delta.drifted) continue;
3454
+ const live = delta.live === null ? "no provider row" : `${delta.live.impressions} impr / ${delta.live.clicks} clicks / ${formatMicros(delta.live.spendMicros)}`;
3455
+ const stored = delta.stored === null ? "no stored row" : `${delta.stored.impressions} impr / ${delta.stored.clicks} clicks / ${formatMicros(delta.stored.spendMicros)}`;
3456
+ console.log(`${indent} ${delta.date}: live ${live} / stored ${stored}`);
3457
+ }
3458
+ }
3459
+ }
3417
3460
 
3418
3461
  // src/cli-commands/ads.ts
3419
3462
  var ADS_CLI_COMMANDS = [
@@ -3770,6 +3813,27 @@ Usage: ${usage}`, {
3770
3813
  await adsSummary(project, { format: input.format });
3771
3814
  }
3772
3815
  },
3816
+ {
3817
+ path: ["ads", "live-delivery"],
3818
+ usage: "canonry ads live-delivery <project> [--campaign <id>] [--lookback-days <n>] [--format json]",
3819
+ options: {
3820
+ campaign: stringOption(),
3821
+ "lookback-days": stringOption()
3822
+ },
3823
+ run: async (input) => {
3824
+ const usage = "canonry ads live-delivery <project> [--campaign <id>] [--lookback-days <n>] [--format json]";
3825
+ const project = requireProject(input, "ads.live-delivery", usage);
3826
+ await adsLiveDelivery(project, {
3827
+ campaignId: getString(input.values, "campaign"),
3828
+ lookbackDays: parseIntegerOption(input, "lookback-days", {
3829
+ command: "ads.live-delivery",
3830
+ message: "--lookback-days must be an integer from 1 to 30",
3831
+ usage
3832
+ }),
3833
+ format: input.format
3834
+ });
3835
+ }
3836
+ },
3773
3837
  {
3774
3838
  path: ["ads", "delivery-diagnostics"],
3775
3839
  usage: "canonry ads delivery-diagnostics <project> [--format json]",
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-6ON6GUAL.js";
3
+ } from "./chunk-TKXWTDV6.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-A6BYMSZL.js";
7
- import "./chunk-HENMGNLT.js";
8
- import "./chunk-YRRQFOAH.js";
6
+ } from "./chunk-ZUQPHGEN.js";
7
+ import "./chunk-LIXWDIDR.js";
8
+ import "./chunk-PC7AYXND.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-HENMGNLT.js";
4
- import "./chunk-YRRQFOAH.js";
3
+ } from "./chunk-LIXWDIDR.js";
4
+ import "./chunk-PC7AYXND.js";
5
5
  export {
6
6
  IntelligenceService
7
7
  };
package/dist/mcp.js CHANGED
@@ -3,10 +3,10 @@ import {
3
3
  PACKAGE_VERSION,
4
4
  canonryMcpTools,
5
5
  createApiClient
6
- } from "./chunk-A6BYMSZL.js";
6
+ } from "./chunk-ZUQPHGEN.js";
7
7
  import {
8
8
  isReadOnlyKey
9
- } from "./chunk-YRRQFOAH.js";
9
+ } from "./chunk-PC7AYXND.js";
10
10
 
11
11
  // src/mcp/cli.ts
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";