@canonry/canonry 5.13.0 → 5.15.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 (28) hide show
  1. package/assets/assets/{AuditHistoryPanel-DWDOPf8c.js → AuditHistoryPanel-BZJn-wvp.js} +1 -1
  2. package/assets/assets/{BacklinksPage-DGxq-iU6.js → BacklinksPage-DsQAKO31.js} +1 -1
  3. package/assets/assets/{HistoryPage-DR91m-gT.js → HistoryPage-DWn459Gu.js} +1 -1
  4. package/assets/assets/{MeasurementPropertyPage-a8poltfM.js → MeasurementPropertyPage-D6pUcBiT.js} +1 -1
  5. package/assets/assets/{ProjectPage-CCCJE7rA.js → ProjectPage-D-lDBpPV.js} +1 -1
  6. package/assets/assets/{RunRow-Xe63DyPK.js → RunRow-BJxx50Ar.js} +1 -1
  7. package/assets/assets/{RunsPage-DK119ZZw.js → RunsPage-DvoNDx6z.js} +1 -1
  8. package/assets/assets/{SettingsPage-G461foD-.js → SettingsPage-DXbhM5Lv.js} +1 -1
  9. package/assets/assets/{SiteHealthSection-CJnHkGM2.js → SiteHealthSection-4mMfc6Rb.js} +3 -3
  10. package/assets/assets/{TrafficPage-AsWgc7Ot.js → TrafficPage-EYSxLxkQ.js} +1 -1
  11. package/assets/assets/{TrafficSourceDetailPage-D10yBQoD.js → TrafficSourceDetailPage-DY4sV_BX.js} +1 -1
  12. package/assets/assets/{extract-error-message-PP4HD_Dp.js → extract-error-message-BMpWRqf9.js} +1 -1
  13. package/assets/assets/{index-CCIeAE4J.js → index-KCAlcuMN.js} +22 -22
  14. package/assets/assets/{react-sigma_core.esm.min-CkyyYkKU.js → react-sigma_core.esm.min-CHepSGqZ.js} +1 -1
  15. package/assets/assets/{v2-overview-adapter-DW-In1hp.js → v2-overview-adapter-CpM-FjB_.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-Q35D2R2P.js → chunk-3YAPVVFN.js} +173 -52
  18. package/dist/{chunk-IDZQ5H3J.js → chunk-CDQDPUY4.js} +2 -2
  19. package/dist/{chunk-6YLNO2T5.js → chunk-JO5HHEJV.js} +2 -2
  20. package/dist/{chunk-UWJAATGC.js → chunk-L36TC6LB.js} +90 -7
  21. package/dist/{chunk-TDHRUXN3.js → chunk-SJ7ZOFL5.js} +92 -31
  22. package/dist/{chunk-IPGBK7XR.js → chunk-ZGZSROSR.js} +1 -1
  23. package/dist/cli.js +7 -7
  24. package/dist/{demo-server-IZFTJI7E.js → demo-server-QLQUVMKJ.js} +3 -3
  25. package/dist/index.js +5 -5
  26. package/dist/{intelligence-service-PM7MC5VC.js → intelligence-service-EAUKUVVM.js} +2 -2
  27. package/dist/mcp.js +3 -3
  28. package/package.json +7 -7
@@ -12143,6 +12143,33 @@ function categorizeSourceWithCompetitors(uri, competitorDomains, isCompetitorMat
12143
12143
  }
12144
12144
  return base;
12145
12145
  }
12146
+ var LISTING_MARKETPLACE_DOMAINS = [
12147
+ "apartmentfinder.com",
12148
+ "apartmentguide.com",
12149
+ "apartmenthomeliving.com",
12150
+ "apartmentlist.com",
12151
+ "apartmentratings.com",
12152
+ "apartments.com",
12153
+ "craigslist.org",
12154
+ "forrent.com",
12155
+ "homes.com",
12156
+ "hotpads.com",
12157
+ "padmapper.com",
12158
+ "realtor.com",
12159
+ "redfin.com",
12160
+ "rent.com",
12161
+ "rentable.co",
12162
+ "rentals.com",
12163
+ "rentcafe.com",
12164
+ "student.com",
12165
+ "trulia.com",
12166
+ "zillow.com",
12167
+ "zumper.com"
12168
+ ];
12169
+ function isListingMarketplace(domainOrUri) {
12170
+ const domain = hostOf(domainOrUri) ?? domainOrUri.trim().toLowerCase();
12171
+ return LISTING_MARKETPLACE_DOMAINS.some((marketplace) => hostMatchesDomain(domain, marketplace));
12172
+ }
12146
12173
  function categoryLabel(category) {
12147
12174
  return CATEGORY_LABELS[category];
12148
12175
  }
@@ -17007,6 +17034,42 @@ var adsSyncResponseSchema = z59.object({
17007
17034
  runId: z59.string(),
17008
17035
  status: z59.string()
17009
17036
  });
17037
+ var ADS_QUERY_STRING_TEMPLATE_MACROS = [
17038
+ "campaign_id",
17039
+ "ad_group_id",
17040
+ "ad_id",
17041
+ "ad_account_id",
17042
+ "oppref"
17043
+ ];
17044
+ var ADS_QUERY_STRING_PAIR = /^[\w.-]+=[^&#\s]*$/;
17045
+ var ADS_QUERY_STRING_MACRO = /\{([^}]*)\}/g;
17046
+ var adsQueryStringTemplateSchema = z59.string().min(1).max(1e3).superRefine((value, ctx) => {
17047
+ const fail = (message) => ctx.addIssue({ code: "custom", message });
17048
+ if (value.startsWith("?") || value.startsWith("&")) {
17049
+ fail("Tracking template must not start with ? or &");
17050
+ return;
17051
+ }
17052
+ const seen = /* @__PURE__ */ new Set();
17053
+ for (const pair of value.split("&")) {
17054
+ if (!ADS_QUERY_STRING_PAIR.test(pair)) {
17055
+ fail(`Tracking template pair '${pair}' must be key=value without whitespace, # or &`);
17056
+ return;
17057
+ }
17058
+ const key = pair.slice(0, pair.indexOf("="));
17059
+ if (seen.has(key)) {
17060
+ fail(`Tracking template repeats the parameter '${key}'`);
17061
+ return;
17062
+ }
17063
+ seen.add(key);
17064
+ }
17065
+ for (const match of value.matchAll(ADS_QUERY_STRING_MACRO)) {
17066
+ const macro = match[1];
17067
+ if (!ADS_QUERY_STRING_TEMPLATE_MACROS.includes(macro)) {
17068
+ fail(`Tracking template macro '{${macro}}' is not supported`);
17069
+ return;
17070
+ }
17071
+ }
17072
+ });
17010
17073
  var adsCreativeDtoSchema = z59.object({
17011
17074
  type: z59.string().nullable().optional(),
17012
17075
  title: z59.string().nullable().optional(),
@@ -17025,6 +17088,8 @@ var adsAdDtoSchema = z59.object({
17025
17088
  status: z59.string(),
17026
17089
  reviewStatus: z59.string().nullable().optional(),
17027
17090
  creative: adsCreativeDtoSchema.nullable().optional(),
17091
+ /** Provider tracking parameters appended to this entity's click URLs. */
17092
+ landingPageQueryStringTemplate: z59.string().nullable().optional(),
17028
17093
  upstreamUpdatedAt: z59.number().int().nullable().optional(),
17029
17094
  syncedAt: z59.string().optional()
17030
17095
  });
@@ -17042,6 +17107,8 @@ var adsAdGroupDtoSchema = z59.object({
17042
17107
  */
17043
17108
  contextHints: z59.array(z59.string()).default([]),
17044
17109
  ads: z59.array(adsAdDtoSchema).default([]),
17110
+ /** Provider tracking parameters appended to this entity's click URLs. */
17111
+ landingPageQueryStringTemplate: z59.string().nullable().optional(),
17045
17112
  upstreamUpdatedAt: z59.number().int().nullable().optional(),
17046
17113
  syncedAt: z59.string().optional()
17047
17114
  });
@@ -17058,6 +17125,8 @@ var adsCampaignDtoSchema = z59.object({
17058
17125
  conversionEventSettingIds: z59.array(z59.string()).default([]),
17059
17126
  locationIds: z59.array(z59.string()).optional(),
17060
17127
  adGroups: z59.array(adsAdGroupDtoSchema).default([]),
17128
+ /** Provider tracking parameters appended to this entity's click URLs. */
17129
+ landingPageQueryStringTemplate: z59.string().nullable().optional(),
17061
17130
  upstreamUpdatedAt: z59.number().int().nullable().optional(),
17062
17131
  syncedAt: z59.string().optional()
17063
17132
  });
@@ -17368,7 +17437,8 @@ var adsReconcileFieldsSchema = z59.object({
17368
17437
  maxBidMicros: adsMicrosSchema.max(1e8).optional(),
17369
17438
  billingEventType: adsAdGroupBillingEventTypeSchema.optional(),
17370
17439
  adGroupId: adsEntityIdSchema.optional(),
17371
- creativeFingerprint: adsSha256Schema.optional()
17440
+ creativeFingerprint: adsSha256Schema.optional(),
17441
+ landingPageQueryStringTemplate: adsQueryStringTemplateSchema.nullable().optional()
17372
17442
  }).strict();
17373
17443
  var adsImageUploadRequestSchema = z59.object({
17374
17444
  operationKey: adsOperationKeySchema,
@@ -17388,7 +17458,9 @@ var adsCampaignCreateRequestSchema = z59.object({
17388
17458
  // and the provider accepts a clicks campaign carrying no conversion event
17389
17459
  // settings, so neither field is conditioned on the other here.
17390
17460
  biddingType: adsCampaignBiddingTypeSchema.optional(),
17391
- conversionEventSettingIds: adsConversionEventSettingIdsSchema.optional()
17461
+ conversionEventSettingIds: adsConversionEventSettingIdsSchema.optional(),
17462
+ /** Optional tracking parameters applied to click URLs under this entity. */
17463
+ landingPageQueryStringTemplate: adsQueryStringTemplateSchema.optional()
17392
17464
  }).superRefine((value, ctx) => {
17393
17465
  if (value.startTime !== void 0 && value.endTime !== void 0 && value.endTime <= value.startTime) {
17394
17466
  ctx.addIssue({ code: "custom", path: ["endTime"], message: "endTime must be after startTime" });
@@ -17401,7 +17473,9 @@ var adsAdGroupCreateRequestSchema = z59.object({
17401
17473
  description: z59.string().max(4e3).optional(),
17402
17474
  contextHints: z59.array(z59.string().min(1).max(1e3)).min(1).max(100),
17403
17475
  maxBidMicros: adsMicrosSchema.max(1e8),
17404
- billingEventType: adsAdGroupBillingEventTypeSchema.optional()
17476
+ billingEventType: adsAdGroupBillingEventTypeSchema.optional(),
17477
+ /** Optional tracking parameters applied to click URLs under this entity. */
17478
+ landingPageQueryStringTemplate: adsQueryStringTemplateSchema.optional()
17405
17479
  });
17406
17480
  var adsChatCardCreativeRequestSchema = z59.object({
17407
17481
  title: z59.string().min(3).max(50),
@@ -17413,7 +17487,9 @@ var adsAdCreateRequestSchema = z59.object({
17413
17487
  operationKey: adsOperationKeySchema,
17414
17488
  adGroupId: adsEntityIdSchema,
17415
17489
  name: adsNameSchema,
17416
- creative: adsChatCardCreativeRequestSchema
17490
+ creative: adsChatCardCreativeRequestSchema,
17491
+ /** Optional tracking parameters applied to click URLs under this entity. */
17492
+ landingPageQueryStringTemplate: adsQueryStringTemplateSchema.optional()
17417
17493
  });
17418
17494
  function hasMutationField(value) {
17419
17495
  return Object.keys(value).some((key) => key !== "operationKey" && key !== "expectedUpdatedAt");
@@ -17426,7 +17502,9 @@ var adsCampaignUpdateRequestSchema = z59.object({
17426
17502
  startTime: adsTimestampSchema.nullable().optional(),
17427
17503
  endTime: adsTimestampSchema.nullable().optional(),
17428
17504
  lifetimeSpendLimitMicros: adsMicrosSchema.min(1e6).optional(),
17429
- locationIds: z59.array(adsEntityIdSchema).min(1).max(100).optional()
17505
+ locationIds: z59.array(adsEntityIdSchema).min(1).max(100).optional(),
17506
+ /** Tracking parameters for this entity's click URLs; null clears them. */
17507
+ landingPageQueryStringTemplate: adsQueryStringTemplateSchema.nullable().optional()
17430
17508
  }).refine(hasMutationField, { message: "At least one campaign field must be updated" });
17431
17509
  var adsAdGroupUpdateRequestSchema = z59.object({
17432
17510
  operationKey: adsOperationKeySchema,
@@ -17434,13 +17512,17 @@ var adsAdGroupUpdateRequestSchema = z59.object({
17434
17512
  name: adsNameSchema.optional(),
17435
17513
  description: z59.string().max(4e3).nullable().optional(),
17436
17514
  contextHints: z59.array(z59.string().min(1).max(1e3)).min(1).max(100).optional(),
17437
- maxBidMicros: adsMicrosSchema.max(1e8).optional()
17515
+ maxBidMicros: adsMicrosSchema.max(1e8).optional(),
17516
+ /** Tracking parameters for this entity's click URLs; null clears them. */
17517
+ landingPageQueryStringTemplate: adsQueryStringTemplateSchema.nullable().optional()
17438
17518
  }).refine(hasMutationField, { message: "At least one ad group field must be updated" });
17439
17519
  var adsAdUpdateRequestSchema = z59.object({
17440
17520
  operationKey: adsOperationKeySchema,
17441
17521
  expectedUpdatedAt: z59.number().int().nonnegative(),
17442
17522
  name: adsNameSchema.optional(),
17443
- creative: adsChatCardCreativeRequestSchema.optional()
17523
+ creative: adsChatCardCreativeRequestSchema.optional(),
17524
+ /** Tracking parameters for this entity's click URLs; null clears them. */
17525
+ landingPageQueryStringTemplate: adsQueryStringTemplateSchema.nullable().optional()
17444
17526
  }).refine(hasMutationField, { message: "At least one ad field must be updated" });
17445
17527
  var adsPauseRequestSchema = z59.object({
17446
17528
  operationKey: adsOperationKeySchema
@@ -19739,6 +19821,7 @@ export {
19739
19821
  shareOfVoiceSummary,
19740
19822
  categorizeSource,
19741
19823
  categorizeSourceWithCompetitors,
19824
+ isListingMarketplace,
19742
19825
  categoryLabel,
19743
19826
  cosineSimilarity,
19744
19827
  clusterByCosine,
@@ -17,12 +17,12 @@ import {
17
17
  resolveOperatorApiKeyIds,
18
18
  runWithUsageTags,
19
19
  saveConfigPatch
20
- } from "./chunk-IPGBK7XR.js";
20
+ } from "./chunk-ZGZSROSR.js";
21
21
  import {
22
22
  deleteSiteCrawlGraphLayout,
23
23
  persistSiteCrawlGraphLayout,
24
24
  prepareSiteCrawlGraphLayout
25
- } from "./chunk-IDZQ5H3J.js";
25
+ } from "./chunk-CDQDPUY4.js";
26
26
  import {
27
27
  PACKAGE_VERSION
28
28
  } from "./chunk-AOJU5LKX.js";
@@ -73,6 +73,7 @@ import {
73
73
  createCredentialChecker,
74
74
  createFastifyLogger,
75
75
  createLogger,
76
+ createRunCompetitorResolver,
76
77
  createUserSession,
77
78
  deliverWebhook,
78
79
  determineCitationState,
@@ -183,7 +184,7 @@ import {
183
184
  siteLivenessState,
184
185
  toAlertView,
185
186
  usageCounters
186
- } from "./chunk-Q35D2R2P.js";
187
+ } from "./chunk-3YAPVVFN.js";
187
188
  import {
188
189
  AGENT_MEMORY_VALUE_MAX_BYTES,
189
190
  AGENT_NONE,
@@ -344,7 +345,7 @@ import {
344
345
  visibilityReportRequestSchema,
345
346
  winnabilityClassLabel,
346
347
  withRetry
347
- } from "./chunk-UWJAATGC.js";
348
+ } from "./chunk-L36TC6LB.js";
348
349
 
349
350
  // src/runtime-context.ts
350
351
  function cliRuntimeContext(env = process.env, stdio = process) {
@@ -6490,7 +6491,7 @@ function nonBlank(value) {
6490
6491
  return trimmed ? trimmed : void 0;
6491
6492
  }
6492
6493
  function resolveBuildCommit(env = process.env) {
6493
- const embedded = true ? "894086f6a60be75c0ade892884adf57ef7a38291" : void 0;
6494
+ const embedded = true ? "979d2ea6821a78ddc2d2df35600c4b208e4ecff7" : void 0;
6494
6495
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
6495
6496
  }
6496
6497
  function resolveInstanceIdentity(env = process.env) {
@@ -6966,6 +6967,8 @@ var JobRunner = class {
6966
6967
  log2.info("run.dispatch", { runId, providerCount: activeProviders.length, providers: activeProviders.map((p) => p.adapter.name) });
6967
6968
  const projectCompetitors = this.db.select().from(competitors).where(eq2(competitors.projectId, projectId)).all();
6968
6969
  const competitorDomains = projectCompetitors.map((c) => c.domain);
6970
+ const resolveCompetitors = createRunCompetitorResolver(this.db, competitorDomains);
6971
+ const competitorsFor = (executionId) => resolveCompetitors(existingRun.measurementPlanVersionId, executionId);
6969
6972
  const allDomains = effectiveDomains({
6970
6973
  canonicalDomain: project.canonicalDomain,
6971
6974
  ownedDomains: project.ownedDomains
@@ -7168,7 +7171,7 @@ var JobRunner = class {
7168
7171
  const slotContext = {
7169
7172
  runId,
7170
7173
  allDomains,
7171
- competitorDomains,
7174
+ competitorsFor,
7172
7175
  allBrandNames,
7173
7176
  executionGates,
7174
7177
  providerDispatchCounts,
@@ -7426,7 +7429,8 @@ var JobRunner = class {
7426
7429
  else providerErrors.set(provider, `No ${provider} provider is configured on this instance, so its missing answers did not run.`);
7427
7430
  }
7428
7431
  const projectCompetitors = this.db.select().from(competitors).where(eq2(competitors.projectId, projectId)).all();
7429
- const competitorDomains = projectCompetitors.map((c) => c.domain);
7432
+ const resolveCompetitors = createRunCompetitorResolver(this.db, projectCompetitors.map((c) => c.domain));
7433
+ const competitorsFor = (executionId) => resolveCompetitors(run.measurementPlanVersionId, executionId);
7430
7434
  const allDomains = effectiveDomains({ canonicalDomain: project.canonicalDomain, ownedDomains: project.ownedDomains });
7431
7435
  const allBrandNames = effectiveBrandNames({ displayName: project.displayName, aliases: project.aliases });
7432
7436
  const todayPeriod = getCurrentUsageDay();
@@ -7480,7 +7484,7 @@ var JobRunner = class {
7480
7484
  const ctx = {
7481
7485
  runId,
7482
7486
  allDomains,
7483
- competitorDomains,
7487
+ competitorsFor,
7484
7488
  allBrandNames,
7485
7489
  executionGates,
7486
7490
  providerDispatchCounts,
@@ -7580,6 +7584,7 @@ var JobRunner = class {
7580
7584
  async executePlanSlot(ctx, registeredProvider, unit) {
7581
7585
  const { adapter, config: providerConfig } = registeredProvider;
7582
7586
  const providerName = adapter.name;
7587
+ const { domains: competitorDomains, aliases: competitorAliases } = ctx.competitorsFor(unit.executionId);
7583
7588
  const gate = ctx.executionGates.get(providerName);
7584
7589
  if (!gate) {
7585
7590
  throw new Error(`Missing execution gate for provider ${providerName}`);
@@ -7597,7 +7602,7 @@ var JobRunner = class {
7597
7602
  {
7598
7603
  query: unit.queryText,
7599
7604
  canonicalDomains: ctx.allDomains,
7600
- competitorDomains: ctx.competitorDomains,
7605
+ competitorDomains,
7601
7606
  location: requestedContext ?? void 0
7602
7607
  },
7603
7608
  config
@@ -7634,13 +7639,14 @@ var JobRunner = class {
7634
7639
  ctx.allBrandNames,
7635
7640
  ctx.allDomains
7636
7641
  );
7637
- const overlap = computeCompetitorOverlap(normalized, ctx.competitorDomains);
7642
+ const overlap = computeCompetitorOverlap(normalized, competitorDomains, competitorAliases);
7638
7643
  const extractedCompetitors = extractRecommendedCompetitors(
7639
7644
  normalized.answerText,
7640
7645
  ctx.allDomains,
7641
7646
  normalized.citedDomains,
7642
- ctx.competitorDomains,
7643
- ctx.allBrandNames
7647
+ competitorDomains,
7648
+ ctx.allBrandNames,
7649
+ competitorAliases
7644
7650
  );
7645
7651
  const snapshotId = crypto6.randomUUID();
7646
7652
  let screenshotRelPath = null;
@@ -8601,6 +8607,30 @@ function validateChatCardCreative(value) {
8601
8607
  validateId(value.file_id, "Ad creative file id");
8602
8608
  validateHttpUrl(value.target_url, "Ad creative target URL");
8603
8609
  }
8610
+ var MAX_QUERY_STRING_TEMPLATE_LENGTH = 1e3;
8611
+ function validateLandingPageConfiguration(value, label) {
8612
+ if (value === void 0 || value === null) return;
8613
+ validateRequestObject(value, `${label} landing_page_configuration`);
8614
+ const template = value.query_string_template;
8615
+ if (template === null) return;
8616
+ if (typeof template !== "string" || template.trim().length === 0 || template.length > MAX_QUERY_STRING_TEMPLATE_LENGTH) {
8617
+ throw new OpenAiAdsApiError(
8618
+ `${label} query_string_template must be 1-${MAX_QUERY_STRING_TEMPLATE_LENGTH} characters`,
8619
+ 400
8620
+ );
8621
+ }
8622
+ if (/\s/.test(template) || template.startsWith("?") || template.startsWith("&")) {
8623
+ throw new OpenAiAdsApiError(
8624
+ `${label} query_string_template must be a bare query string without whitespace or a leading ? or &`,
8625
+ 400
8626
+ );
8627
+ }
8628
+ }
8629
+ function parseLandingPageQueryStringTemplate(value) {
8630
+ if (!isRecord(value)) return null;
8631
+ const template = value.query_string_template;
8632
+ return typeof template === "string" && template.trim().length > 0 ? template : null;
8633
+ }
8604
8634
  function validateCreateCampaignRequest(request) {
8605
8635
  validateRequestObject(request, "Campaign create request");
8606
8636
  validateEntityName(request.name, "Campaign name");
@@ -8610,6 +8640,7 @@ function validateCreateCampaignRequest(request) {
8610
8640
  validateCampaignTimestamp(request.end_time, "Campaign end_time");
8611
8641
  validateCampaignBidding(request);
8612
8642
  validateCampaignTargeting(request.targeting);
8643
+ validateLandingPageConfiguration(request.landing_page_configuration, "Campaign create request");
8613
8644
  }
8614
8645
  function validateUpdateCampaignRequest(request) {
8615
8646
  validatePublicUpdateRequest(request, "Campaign update request");
@@ -8618,6 +8649,7 @@ function validateUpdateCampaignRequest(request) {
8618
8649
  validateCampaignTimestamp(request.start_time, "Campaign start_time");
8619
8650
  validateCampaignTimestamp(request.end_time, "Campaign end_time");
8620
8651
  validateCampaignTargeting(request.targeting);
8652
+ validateLandingPageConfiguration(request.landing_page_configuration, "Campaign update request");
8621
8653
  }
8622
8654
  function validateCreateAdGroupRequest(request) {
8623
8655
  validateRequestObject(request, "Ad group create request");
@@ -8626,12 +8658,14 @@ function validateCreateAdGroupRequest(request) {
8626
8658
  validatePausedCreateStatus(request.status, "Ad group create request");
8627
8659
  validateContextHints(request.context_hints);
8628
8660
  validateBiddingConfig(request.bidding_config);
8661
+ validateLandingPageConfiguration(request.landing_page_configuration, "Ad group create request");
8629
8662
  }
8630
8663
  function validateUpdateAdGroupRequest(request) {
8631
8664
  validatePublicUpdateRequest(request, "Ad group update request");
8632
8665
  if (request.name !== void 0) validateEntityName(request.name, "Ad group name");
8633
8666
  validateContextHints(request.context_hints);
8634
8667
  if (request.bidding_config !== void 0) validateBiddingConfig(request.bidding_config);
8668
+ validateLandingPageConfiguration(request.landing_page_configuration, "Ad group update request");
8635
8669
  }
8636
8670
  function validateCreateAdRequest(request) {
8637
8671
  validateRequestObject(request, "Ad create request");
@@ -8639,11 +8673,13 @@ function validateCreateAdRequest(request) {
8639
8673
  validateEntityName(request.name, "Ad name");
8640
8674
  validatePausedCreateStatus(request.status, "Ad create request");
8641
8675
  validateChatCardCreative(request.creative);
8676
+ validateLandingPageConfiguration(request.landing_page_configuration, "Ad create request");
8642
8677
  }
8643
8678
  function validateUpdateAdRequest(request) {
8644
8679
  validatePublicUpdateRequest(request, "Ad update request");
8645
8680
  if (request.name !== void 0) validateEntityName(request.name, "Ad name");
8646
8681
  if (request.creative !== void 0) validateChatCardCreative(request.creative);
8682
+ validateLandingPageConfiguration(request.landing_page_configuration, "Ad update request");
8647
8683
  }
8648
8684
  function adsClientLog(level, action, ctx) {
8649
8685
  const entry = {
@@ -9088,6 +9124,7 @@ async function executeAdsSync(db, runId, projectId, opts) {
9088
9124
  lifetimeSpendLimitMicros: campaign.budget?.lifetime_spend_limit_micros ?? null,
9089
9125
  conversionEventSettingIds: campaign.conversion_event_setting_ids ?? [],
9090
9126
  targeting: campaign.targeting,
9127
+ landingPageQueryStringTemplate: parseLandingPageQueryStringTemplate(campaign.landing_page_configuration),
9091
9128
  upstreamCreatedAt: campaign.created_at,
9092
9129
  upstreamUpdatedAt: campaign.updated_at,
9093
9130
  syncRunId: runId,
@@ -9104,6 +9141,7 @@ async function executeAdsSync(db, runId, projectId, opts) {
9104
9141
  billingEventType: group.bidding_config?.billing_event_type ?? null,
9105
9142
  maxBidMicros: group.bidding_config?.max_bid_micros ?? null,
9106
9143
  contextHints: group.context_hints,
9144
+ landingPageQueryStringTemplate: parseLandingPageQueryStringTemplate(group.landing_page_configuration),
9107
9145
  upstreamCreatedAt: group.created_at,
9108
9146
  upstreamUpdatedAt: group.updated_at,
9109
9147
  syncRunId: runId,
@@ -9118,6 +9156,7 @@ async function executeAdsSync(db, runId, projectId, opts) {
9118
9156
  status: ad.status,
9119
9157
  creative: ad.creative,
9120
9158
  reviewStatus: ad.review_status ?? ad.review?.status ?? null,
9159
+ landingPageQueryStringTemplate: parseLandingPageQueryStringTemplate(ad.landing_page_configuration),
9121
9160
  upstreamCreatedAt: ad.created_at,
9122
9161
  upstreamUpdatedAt: ad.updated_at,
9123
9162
  syncRunId: runId,
@@ -11568,10 +11607,11 @@ async function backfillAnswerVisibilityCommand(opts) {
11568
11607
  let reparsed = 0;
11569
11608
  let providerErrors = 0;
11570
11609
  if (scopedProjects.length > 0) {
11571
- const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and13(
11610
+ const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId, planVersionId: runs.measurementPlanVersionId }).from(runs).where(and13(
11572
11611
  eq15(runs.kind, RunKinds["answer-visibility"]),
11573
11612
  inArray6(runs.projectId, scopedProjects.map((project) => project.id))
11574
- )).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(eq15(runs.kind, RunKinds["answer-visibility"])).all();
11613
+ )).all() : db.select({ id: runs.id, projectId: runs.projectId, planVersionId: runs.measurementPlanVersionId }).from(runs).where(eq15(runs.kind, RunKinds["answer-visibility"])).all();
11614
+ const planVersionByRun = new Map(runRows.map((run) => [run.id, run.planVersionId]));
11575
11615
  const runIdsByProject = /* @__PURE__ */ new Map();
11576
11616
  for (const run of runRows) {
11577
11617
  const existing = runIdsByProject.get(run.projectId);
@@ -11580,6 +11620,7 @@ async function backfillAnswerVisibilityCommand(opts) {
11580
11620
  }
11581
11621
  for (const project of scopedProjects) {
11582
11622
  const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq15(competitors.projectId, project.id)).all().map((row) => row.domain);
11623
+ const competitorsForRun = createRunCompetitorResolver(db, competitorDomains);
11583
11624
  const runIds = runIdsByProject.get(project.id) ?? [];
11584
11625
  if (runIds.length === 0) continue;
11585
11626
  const projectDomains = effectiveDomains({
@@ -11594,6 +11635,8 @@ async function backfillAnswerVisibilityCommand(opts) {
11594
11635
  const batchRunIds = runIds.slice(offset, offset + SNAPSHOT_BATCH_SIZE);
11595
11636
  const snapshotRows = db.select({
11596
11637
  id: querySnapshots.id,
11638
+ runId: querySnapshots.runId,
11639
+ executionId: querySnapshots.measurementExecutionId,
11597
11640
  provider: querySnapshots.provider,
11598
11641
  citationState: querySnapshots.citationState,
11599
11642
  answerMentioned: querySnapshots.answerMentioned,
@@ -11634,13 +11677,15 @@ async function backfillAnswerVisibilityCommand(opts) {
11634
11677
  };
11635
11678
  const nextCitationState = determineCitationState(normalized, projectDomains);
11636
11679
  const nextCitedDomains = reparsedResult.citedDomains;
11637
- const nextCompetitorOverlap = computeCompetitorOverlap(normalized, competitorDomains);
11680
+ const runCompetitors = competitorsForRun(planVersionByRun.get(snapshot.runId), snapshot.executionId);
11681
+ const nextCompetitorOverlap = computeCompetitorOverlap(normalized, runCompetitors.domains, runCompetitors.aliases);
11638
11682
  const nextRecommendedCompetitors = extractRecommendedCompetitors(
11639
11683
  normalized.answerText,
11640
11684
  projectDomains,
11641
11685
  normalized.citedDomains,
11642
- competitorDomains,
11643
- projectBrandNames
11686
+ runCompetitors.domains,
11687
+ projectBrandNames,
11688
+ runCompetitors.aliases
11644
11689
  );
11645
11690
  const nextRawResponse = stringifyStoredSnapshotEnvelope(
11646
11691
  snapshot.rawResponse,
@@ -11867,8 +11912,10 @@ function backfillProjectAnswerMentions(db, projectId, opts) {
11867
11912
  const project = db.select().from(projects).where(eq15(projects.id, projectId)).get();
11868
11913
  if (!project) return { examined: 0, updated: 0, mentioned: 0 };
11869
11914
  const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq15(competitors.projectId, projectId)).all().map((row) => row.domain);
11870
- const runRows = db.select({ id: runs.id }).from(runs).where(and13(eq15(runs.kind, RunKinds["answer-visibility"]), eq15(runs.projectId, projectId))).all();
11915
+ const runRows = db.select({ id: runs.id, planVersionId: runs.measurementPlanVersionId }).from(runs).where(and13(eq15(runs.kind, RunKinds["answer-visibility"]), eq15(runs.projectId, projectId))).all();
11871
11916
  const runIds = runRows.map((r) => r.id);
11917
+ const planVersionByRun = new Map(runRows.map((run) => [run.id, run.planVersionId]));
11918
+ const competitorsForRun = createRunCompetitorResolver(db, competitorDomains);
11872
11919
  let examined = 0;
11873
11920
  let updated = 0;
11874
11921
  let wouldUpdate = 0;
@@ -11888,6 +11935,8 @@ function backfillProjectAnswerMentions(db, projectId, opts) {
11888
11935
  const batchRunIds = runIds.slice(offset, offset + SNAPSHOT_BATCH_SIZE);
11889
11936
  const snapshotRows = db.select({
11890
11937
  id: querySnapshots.id,
11938
+ runId: querySnapshots.runId,
11939
+ executionId: querySnapshots.measurementExecutionId,
11891
11940
  provider: querySnapshots.provider,
11892
11941
  answerMentioned: querySnapshots.answerMentioned,
11893
11942
  answerText: querySnapshots.answerText,
@@ -11913,13 +11962,15 @@ function backfillProjectAnswerMentions(db, projectId, opts) {
11913
11962
  retrievalStatus: "unknown",
11914
11963
  searchQueries: []
11915
11964
  };
11916
- const nextCompetitorOverlap = computeCompetitorOverlap(normalized, competitorDomains);
11965
+ const runCompetitors = competitorsForRun(planVersionByRun.get(snapshot.runId), snapshot.executionId);
11966
+ const nextCompetitorOverlap = computeCompetitorOverlap(normalized, runCompetitors.domains, runCompetitors.aliases);
11917
11967
  const nextRecommendedCompetitors = extractRecommendedCompetitors(
11918
11968
  answerText,
11919
11969
  projectDomains,
11920
11970
  citedDomains,
11921
- competitorDomains,
11922
- projectBrandNames
11971
+ runCompetitors.domains,
11972
+ projectBrandNames,
11973
+ runCompetitors.aliases
11923
11974
  );
11924
11975
  const nextPatch = {};
11925
11976
  if (snapshot.answerMentioned !== nextAnswerMentioned) {
@@ -12016,7 +12067,7 @@ function readStoredGroundingSources(rawResponse) {
12016
12067
  return result2;
12017
12068
  }
12018
12069
  async function backfillInsightsCommand(project, opts) {
12019
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-PM7MC5VC.js");
12070
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-EAUKUVVM.js");
12020
12071
  const config = loadConfig();
12021
12072
  const db = createClient(config.database);
12022
12073
  migrate(db);
@@ -18469,7 +18520,8 @@ async function createServer(opts) {
18469
18520
  lifetimeSpendLimitMicros: entity.budget?.lifetime_spend_limit_micros ?? null,
18470
18521
  locationIds: entity.targeting?.locations?.include?.map((location) => location.id) ?? [],
18471
18522
  biddingType: entity.bidding_type,
18472
- conversionEventSettingIds: entity.conversion_event_setting_ids
18523
+ conversionEventSettingIds: entity.conversion_event_setting_ids,
18524
+ landingPageQueryStringTemplate: parseLandingPageQueryStringTemplate(entity.landing_page_configuration)
18473
18525
  });
18474
18526
  const adsAdGroupEntityResult = (entity, campaignId) => ({
18475
18527
  ...adsEntityResult(entity),
@@ -18477,12 +18529,15 @@ async function createServer(opts) {
18477
18529
  campaignId: campaignId ?? null,
18478
18530
  contextHints: entity.context_hints,
18479
18531
  maxBidMicros: entity.bidding_config?.max_bid_micros ?? null,
18480
- billingEventType: entity.bidding_config?.billing_event_type ?? null
18532
+ billingEventType: entity.bidding_config?.billing_event_type ?? null,
18533
+ landingPageQueryStringTemplate: parseLandingPageQueryStringTemplate(entity.landing_page_configuration)
18481
18534
  });
18482
18535
  const adsAdEntityResult = (entity, adGroupId) => ({
18483
18536
  ...adsEntityResult(entity),
18484
- adGroupId: adGroupId ?? null
18537
+ adGroupId: adGroupId ?? null,
18538
+ landingPageQueryStringTemplate: parseLandingPageQueryStringTemplate(entity.landing_page_configuration)
18485
18539
  });
18540
+ const landingPageConfigurationUpdate = (template) => template === void 0 ? void 0 : template === null ? null : { query_string_template: template };
18486
18541
  const adsOperator = {
18487
18542
  uploadImage: async (apiKey, imageUrl) => {
18488
18543
  const result2 = await uploadImageFromUrl(apiKey, imageUrl);
@@ -18499,7 +18554,8 @@ async function createServer(opts) {
18499
18554
  budget: { lifetime_spend_limit_micros: input.lifetimeSpendLimitMicros },
18500
18555
  bidding_type: input.biddingType,
18501
18556
  conversion_event_setting_ids: input.conversionEventSettingIds,
18502
- targeting: { locations: { include: input.locationIds.map((id) => ({ id })) } }
18557
+ targeting: { locations: { include: input.locationIds.map((id) => ({ id })) } },
18558
+ landing_page_configuration: input.landingPageQueryStringTemplate === void 0 ? void 0 : { query_string_template: input.landingPageQueryStringTemplate }
18503
18559
  })),
18504
18560
  updateCampaign: async (apiKey, id, input) => adsCampaignEntityResult(await updateCampaign(apiKey, id, {
18505
18561
  name: input.name,
@@ -18507,7 +18563,8 @@ async function createServer(opts) {
18507
18563
  start_time: input.startTime,
18508
18564
  end_time: input.endTime,
18509
18565
  budget: input.lifetimeSpendLimitMicros === void 0 ? void 0 : { lifetime_spend_limit_micros: input.lifetimeSpendLimitMicros },
18510
- targeting: input.locationIds === void 0 ? void 0 : { locations: { include: input.locationIds.map((locationId) => ({ id: locationId })) } }
18566
+ targeting: input.locationIds === void 0 ? void 0 : { locations: { include: input.locationIds.map((locationId) => ({ id: locationId })) } },
18567
+ landing_page_configuration: landingPageConfigurationUpdate(input.landingPageQueryStringTemplate)
18511
18568
  })),
18512
18569
  activateCampaign: async (apiKey, id) => adsCampaignEntityResult(await activateCampaign(apiKey, id)),
18513
18570
  pauseCampaign: async (apiKey, id) => adsCampaignEntityResult(await pauseCampaign(apiKey, id)),
@@ -18523,7 +18580,8 @@ async function createServer(opts) {
18523
18580
  bidding_config: {
18524
18581
  billing_event_type: input.billingEventType,
18525
18582
  max_bid_micros: input.maxBidMicros
18526
- }
18583
+ },
18584
+ landing_page_configuration: input.landingPageQueryStringTemplate === void 0 ? void 0 : { query_string_template: input.landingPageQueryStringTemplate }
18527
18585
  })),
18528
18586
  updateAdGroup: async (apiKey, id, input) => {
18529
18587
  let biddingConfig;
@@ -18540,7 +18598,8 @@ async function createServer(opts) {
18540
18598
  name: input.name,
18541
18599
  description: input.description,
18542
18600
  context_hints: input.contextHints,
18543
- bidding_config: biddingConfig
18601
+ bidding_config: biddingConfig,
18602
+ landing_page_configuration: landingPageConfigurationUpdate(input.landingPageQueryStringTemplate)
18544
18603
  }));
18545
18604
  },
18546
18605
  activateAdGroup: async (apiKey, id) => adsAdGroupEntityResult(await activateAdGroup(apiKey, id)),
@@ -18558,7 +18617,8 @@ async function createServer(opts) {
18558
18617
  body: input.creative.body,
18559
18618
  target_url: input.creative.targetUrl,
18560
18619
  file_id: input.creative.fileId
18561
- }
18620
+ },
18621
+ landing_page_configuration: input.landingPageQueryStringTemplate === void 0 ? void 0 : { query_string_template: input.landingPageQueryStringTemplate }
18562
18622
  })),
18563
18623
  updateAd: async (apiKey, id, input) => adsAdEntityResult(await updateAd(apiKey, id, {
18564
18624
  name: input.name,
@@ -18568,7 +18628,8 @@ async function createServer(opts) {
18568
18628
  body: input.creative.body,
18569
18629
  target_url: input.creative.targetUrl,
18570
18630
  file_id: input.creative.fileId
18571
- } : void 0
18631
+ } : void 0,
18632
+ landing_page_configuration: landingPageConfigurationUpdate(input.landingPageQueryStringTemplate)
18572
18633
  })),
18573
18634
  activateAd: async (apiKey, id) => adsAdEntityResult(await activateAd(apiKey, id)),
18574
18635
  pauseAd: async (apiKey, id) => adsAdEntityResult(await pauseAd(apiKey, id)),
@@ -133,7 +133,7 @@ import {
133
133
  upgradeCaveatFor,
134
134
  upgradeCommandFor,
135
135
  visibilityReportRequestSchema
136
- } from "./chunk-UWJAATGC.js";
136
+ } from "./chunk-L36TC6LB.js";
137
137
 
138
138
  // src/cli-error.ts
139
139
  function isMachineFormat(format) {
package/dist/cli.js CHANGED
@@ -35,11 +35,11 @@ import {
35
35
  trackCliCommandFinished,
36
36
  trackEvent,
37
37
  waitForServerRuntimeStartup
38
- } from "./chunk-TDHRUXN3.js";
38
+ } from "./chunk-SJ7ZOFL5.js";
39
39
  import {
40
40
  autoSyncSkills,
41
41
  formatAutoSyncNotice
42
- } from "./chunk-6YLNO2T5.js";
42
+ } from "./chunk-JO5HHEJV.js";
43
43
  import {
44
44
  CliError,
45
45
  EXIT_SYSTEM_ERROR,
@@ -66,13 +66,13 @@ import {
66
66
  shouldPrintServeSkillsNudge,
67
67
  systemError,
68
68
  usageError
69
- } from "./chunk-IPGBK7XR.js";
69
+ } from "./chunk-ZGZSROSR.js";
70
70
  import {
71
71
  DEFAULT_DEMO_TRUSTED_PROXIES,
72
72
  trustedProxiesProblem,
73
73
  trustedProxyProblem
74
74
  } from "./chunk-RB2F4D3Z.js";
75
- import "./chunk-IDZQ5H3J.js";
75
+ import "./chunk-CDQDPUY4.js";
76
76
  import "./chunk-AOJU5LKX.js";
77
77
  import {
78
78
  CLOUDFLARE_WORKER_BINDINGS,
@@ -88,7 +88,7 @@ import {
88
88
  queries,
89
89
  renderReportHtml,
90
90
  runs
91
- } from "./chunk-Q35D2R2P.js";
91
+ } from "./chunk-3YAPVVFN.js";
92
92
  import {
93
93
  AdsDeliverySnapshotStatuses,
94
94
  AdsHistoricalCampaignRollupStatuses,
@@ -173,7 +173,7 @@ import {
173
173
  snapshotProviderModeSchema,
174
174
  visibilityReportRequestSchema,
175
175
  winnabilityClassSchema
176
- } from "./chunk-UWJAATGC.js";
176
+ } from "./chunk-L36TC6LB.js";
177
177
 
178
178
  // src/cli.ts
179
179
  import { pathToFileURL } from "url";
@@ -438,7 +438,7 @@ function parseDemoTrustedProxies(values) {
438
438
  async function demoCommand(options) {
439
439
  const { host, port } = parseDemoListenOptions(options);
440
440
  const trustProxy = parseDemoTrustedProxies(options.trustProxy);
441
- const { createDemoServer } = await import("./demo-server-IZFTJI7E.js");
441
+ const { createDemoServer } = await import("./demo-server-QLQUVMKJ.js");
442
442
  const app = await createDemoServer({ trustProxy });
443
443
  let stopping = false;
444
444
  const stop = () => {
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  layoutSiteCrawlGraphInput,
6
6
  persistSiteCrawlGraphLayout
7
- } from "./chunk-IDZQ5H3J.js";
7
+ } from "./chunk-CDQDPUY4.js";
8
8
  import {
9
9
  PACKAGE_VERSION
10
10
  } from "./chunk-AOJU5LKX.js";
@@ -83,7 +83,7 @@ import {
83
83
  siteCrawlPages,
84
84
  siteCrawlSnapshots,
85
85
  trafficSources
86
- } from "./chunk-Q35D2R2P.js";
86
+ } from "./chunk-3YAPVVFN.js";
87
87
  import {
88
88
  RunKinds,
89
89
  SiteCrawlFetchStates,
@@ -104,7 +104,7 @@ import {
104
104
  measurementPlanV2Schema,
105
105
  placementLinkDecision,
106
106
  siteAuditPageFactorSchema
107
- } from "./chunk-UWJAATGC.js";
107
+ } from "./chunk-L36TC6LB.js";
108
108
 
109
109
  // src/demo-server.ts
110
110
  import { dirname, join as join2 } from "path";