@ainyc/canonry 4.83.0 → 4.85.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 (32) hide show
  1. package/assets/agent-workspace/skills/aero/SKILL.md +14 -10
  2. package/assets/agent-workspace/skills/aero/references/aeo-discovery.md +7 -5
  3. package/assets/agent-workspace/skills/aero/references/memory-patterns.md +1 -1
  4. package/assets/agent-workspace/skills/aero/references/orchestration.md +22 -20
  5. package/assets/agent-workspace/skills/aero/references/regression-playbook.md +11 -9
  6. package/assets/agent-workspace/skills/aero/references/reporting.md +14 -9
  7. package/assets/agent-workspace/skills/aero/soul.md +5 -5
  8. package/assets/agent-workspace/skills/canonry/SKILL.md +1 -1
  9. package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +84 -36
  10. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +44 -10
  11. package/assets/assets/{BacklinksPage-OrSg_iPA.js → BacklinksPage-CDAv0ggn.js} +1 -1
  12. package/assets/assets/{ChartPrimitives-DPBhAT_r.js → ChartPrimitives-CnAmsyt7.js} +1 -1
  13. package/assets/assets/{ProjectPage-CpMcEmtw.js → ProjectPage-C9KEgRxD.js} +1 -1
  14. package/assets/assets/{RunRow-2Rty0BAH.js → RunRow-CVZ5o8fg.js} +1 -1
  15. package/assets/assets/{RunsPage-B3ahqf8s.js → RunsPage-Bzy5c0MZ.js} +1 -1
  16. package/assets/assets/{SettingsPage-BIjeI85q.js → SettingsPage-B1ocxPBe.js} +1 -1
  17. package/assets/assets/{TrafficPage-DjGoj691.js → TrafficPage-D2zepQOC.js} +1 -1
  18. package/assets/assets/{TrafficSourceDetailPage-BgKG-2q3.js → TrafficSourceDetailPage-C7JuAkaK.js} +1 -1
  19. package/assets/assets/{arrow-left-Cf7wmru1.js → arrow-left-Bv3CWylm.js} +1 -1
  20. package/assets/assets/{extract-error-message-CANxezte.js → extract-error-message-BtVid5TP.js} +1 -1
  21. package/assets/assets/{index-CGlPx_cu.js → index-DmNti_xn.js} +72 -72
  22. package/assets/assets/{trash-2-6nHJZrvy.js → trash-2-BoimCsYz.js} +1 -1
  23. package/assets/index.html +1 -1
  24. package/dist/{chunk-GOWH42QV.js → chunk-3K3QRSYE.js} +709 -426
  25. package/dist/{chunk-Y3O3HBMN.js → chunk-62YB3ML7.js} +49 -1
  26. package/dist/{chunk-NRACXNI7.js → chunk-7BMSWI2K.js} +6 -4
  27. package/dist/{chunk-BNF3HXBW.js → chunk-I2BJC3DT.js} +1021 -942
  28. package/dist/cli.js +165 -29
  29. package/dist/index.js +4 -4
  30. package/dist/{intelligence-service-FHQM7YMA.js → intelligence-service-AHHBQKRD.js} +2 -2
  31. package/dist/mcp.js +2 -2
  32. package/package.json +7 -7
@@ -169,6 +169,7 @@ import {
169
169
  notFound,
170
170
  notImplemented,
171
171
  notificationDtoSchema,
172
+ parseInclusiveEndMs,
172
173
  parseRunError,
173
174
  parseWindow,
174
175
  pickClusterRepresentative,
@@ -230,6 +231,7 @@ import {
230
231
  unsupportedKind,
231
232
  validationError,
232
233
  visibilityStateFromAnswerMentioned,
234
+ visibilityStatsDtoSchema,
233
235
  windowCutoff,
234
236
  winnabilityClassLabel,
235
237
  winnabilityClassSchema,
@@ -246,10 +248,10 @@ import {
246
248
  wordpressSchemaDeployResultDtoSchema,
247
249
  wordpressSchemaStatusResultDtoSchema,
248
250
  wordpressStatusDtoSchema
249
- } from "./chunk-BNF3HXBW.js";
251
+ } from "./chunk-I2BJC3DT.js";
250
252
 
251
253
  // src/intelligence-service.ts
252
- import { eq as eq36, desc as desc17, asc as asc5, and as and26, ne as ne5, or as or5, inArray as inArray13, gte as gte7, lte as lte4 } from "drizzle-orm";
254
+ import { eq as eq37, desc as desc18, asc as asc5, and as and27, ne as ne5, or as or5, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
253
255
 
254
256
  // ../db/src/client.ts
255
257
  import { mkdirSync } from "fs";
@@ -805,8 +807,16 @@ var healthSnapshots = sqliteTable("health_snapshots", {
805
807
  projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
806
808
  runId: text("run_id").references(() => runs.id, { onDelete: "cascade" }),
807
809
  overallCitedRate: text("overall_cited_rate").notNull(),
810
+ // Answer-text mention rate, independent of citation. Nullable because the
811
+ // column is added by migration v80 via ALTER TABLE ADD COLUMN — rows
812
+ // persisted before v80 read back as NULL ("not measured"); readers coalesce
813
+ // NULL→0. New writes always populate it (see intelligence-service persist).
814
+ overallMentionRate: text("overall_mention_rate"),
808
815
  totalPairs: integer("total_pairs").notNull(),
809
816
  citedPairs: integer("cited_pairs").notNull(),
817
+ // Count of pairs MENTIONED in the answer text. Nullable for the same
818
+ // legacy-row reason as overall_mention_rate; coalesced NULL→0 on read.
819
+ mentionedPairs: integer("mentioned_pairs"),
810
820
  providerBreakdown: text("provider_breakdown", { mode: "json" }).$type().notNull().default({}),
811
821
  createdAt: text("created_at").notNull()
812
822
  }, (table) => [
@@ -3089,6 +3099,31 @@ var MIGRATION_VERSIONS = [
3089
3099
  statements: [
3090
3100
  `ALTER TABLE discovery_probes ADD COLUMN answer_mentioned INTEGER`
3091
3101
  ]
3102
+ },
3103
+ {
3104
+ // Mention-rate columns on the persisted health snapshot, mirroring the
3105
+ // existing cited columns (overall_cited_rate / cited_pairs) for the
3106
+ // independent answer-text mention signal. Nullable: rows written before
3107
+ // this version have no mention math, so they read back as NULL ("not
3108
+ // measured") and readers coalesce NULL→0.
3109
+ //
3110
+ // Guarded `run` rather than bare `statements` (the v66 pattern): the
3111
+ // table-existence check makes this a no-op when `health_snapshots` is
3112
+ // absent — only possible on a legacy fixture whose recorded
3113
+ // `_migrations` version skips v23's `CREATE TABLE` (the bootstrap is
3114
+ // bypassed). The column-existence check keeps a replay idempotent.
3115
+ version: 80,
3116
+ name: "health-snapshots-mention-rate",
3117
+ statements: [],
3118
+ run: (db) => {
3119
+ if (!tableExists(db, "health_snapshots")) return;
3120
+ if (!columnExists(db, "health_snapshots", "overall_mention_rate")) {
3121
+ db.run(sql.raw(`ALTER TABLE health_snapshots ADD COLUMN overall_mention_rate TEXT`));
3122
+ }
3123
+ if (!columnExists(db, "health_snapshots", "mentioned_pairs")) {
3124
+ db.run(sql.raw(`ALTER TABLE health_snapshots ADD COLUMN mentioned_pairs INTEGER`));
3125
+ }
3126
+ }
3092
3127
  }
3093
3128
  ];
3094
3129
  function rebuildBacklinkTableWithSource(tx, table) {
@@ -3412,26 +3447,33 @@ function computeHealth(run) {
3412
3447
  const providerStats = /* @__PURE__ */ new Map();
3413
3448
  let totalPairs = 0;
3414
3449
  let citedPairs = 0;
3450
+ let mentionedPairs = 0;
3415
3451
  for (const snap of run.snapshots) {
3416
3452
  totalPairs++;
3417
3453
  if (snap.cited) citedPairs++;
3418
- const stats = providerStats.get(snap.provider) ?? { cited: 0, total: 0 };
3454
+ if (snap.answerMentioned === true) mentionedPairs++;
3455
+ const stats = providerStats.get(snap.provider) ?? { cited: 0, mentioned: 0, total: 0 };
3419
3456
  stats.total++;
3420
3457
  if (snap.cited) stats.cited++;
3458
+ if (snap.answerMentioned === true) stats.mentioned++;
3421
3459
  providerStats.set(snap.provider, stats);
3422
3460
  }
3423
3461
  const providerBreakdown = {};
3424
3462
  for (const [provider, stats] of providerStats) {
3425
3463
  providerBreakdown[provider] = {
3426
3464
  citedRate: stats.total > 0 ? stats.cited / stats.total : 0,
3465
+ mentionRate: stats.total > 0 ? stats.mentioned / stats.total : 0,
3427
3466
  cited: stats.cited,
3467
+ mentioned: stats.mentioned,
3428
3468
  total: stats.total
3429
3469
  };
3430
3470
  }
3431
3471
  return {
3432
3472
  overallCitedRate: totalPairs > 0 ? citedPairs / totalPairs : 0,
3473
+ overallMentionRate: totalPairs > 0 ? mentionedPairs / totalPairs : 0,
3433
3474
  totalPairs,
3434
3475
  citedPairs,
3476
+ mentionedPairs,
3435
3477
  providerBreakdown
3436
3478
  };
3437
3479
  }
@@ -4299,18 +4341,18 @@ function buildCitationScorecard(snapshots, queryLookup) {
4299
4341
  answerMentioned: snap.answerMentioned ?? null,
4300
4342
  model: snap.model
4301
4343
  };
4302
- const counts = providerCounts.get(snap.provider) ?? { cited: 0, total: 0 };
4303
- counts.total++;
4304
- if (snap.citationState === CitationStates.cited) counts.cited++;
4305
- providerCounts.set(snap.provider, counts);
4344
+ const counts2 = providerCounts.get(snap.provider) ?? { cited: 0, total: 0 };
4345
+ counts2.total++;
4346
+ if (snap.citationState === CitationStates.cited) counts2.cited++;
4347
+ providerCounts.set(snap.provider, counts2);
4306
4348
  }
4307
4349
  const providerRates = providerList.map((provider) => {
4308
- const counts = providerCounts.get(provider) ?? { cited: 0, total: 0 };
4309
- const citationRate = counts.total > 0 ? Math.round(counts.cited / counts.total * 100) : 0;
4350
+ const counts2 = providerCounts.get(provider) ?? { cited: 0, total: 0 };
4351
+ const citationRate = counts2.total > 0 ? Math.round(counts2.cited / counts2.total * 100) : 0;
4310
4352
  return {
4311
4353
  provider,
4312
- citedCount: counts.cited,
4313
- totalCount: counts.total,
4354
+ citedCount: counts2.cited,
4355
+ totalCount: counts2.total,
4314
4356
  citationRate
4315
4357
  };
4316
4358
  });
@@ -7878,13 +7920,13 @@ function buildRankedList(domains, limit) {
7878
7920
  bySurfaceClass
7879
7921
  };
7880
7922
  }
7881
- function buildCategoryCounts(counts) {
7923
+ function buildCategoryCounts(counts2) {
7882
7924
  let grandTotal = 0;
7883
- for (const domains of counts.values()) {
7925
+ for (const domains of counts2.values()) {
7884
7926
  for (const count2 of domains.values()) grandTotal += count2;
7885
7927
  }
7886
7928
  const result = [];
7887
- for (const [category, domains] of counts) {
7929
+ for (const [category, domains] of counts2) {
7888
7930
  let categoryTotal = 0;
7889
7931
  const domainEntries = [];
7890
7932
  for (const [domain, count2] of domains) {
@@ -7912,8 +7954,10 @@ function emptyHealthSnapshot(projectId) {
7912
7954
  projectId,
7913
7955
  runId: null,
7914
7956
  overallCitedRate: 0,
7957
+ overallMentionRate: 0,
7915
7958
  totalPairs: 0,
7916
7959
  citedPairs: 0,
7960
+ mentionedPairs: 0,
7917
7961
  providerBreakdown: {},
7918
7962
  createdAt: "",
7919
7963
  status: "no-data",
@@ -7936,15 +7980,31 @@ function mapInsightRow(r) {
7936
7980
  createdAt: r.createdAt
7937
7981
  };
7938
7982
  }
7983
+ function coalesceProviderBreakdown(breakdown) {
7984
+ const out = {};
7985
+ for (const [provider, entry] of Object.entries(breakdown)) {
7986
+ out[provider] = {
7987
+ citedRate: entry.citedRate,
7988
+ mentionRate: entry.mentionRate ?? 0,
7989
+ cited: entry.cited,
7990
+ mentioned: entry.mentioned ?? 0,
7991
+ total: entry.total
7992
+ };
7993
+ }
7994
+ return out;
7995
+ }
7939
7996
  function mapHealthRow(r) {
7940
7997
  return {
7941
7998
  id: r.id,
7942
7999
  projectId: r.projectId,
7943
8000
  runId: r.runId ?? null,
7944
8001
  overallCitedRate: Number(r.overallCitedRate),
8002
+ // Legacy rows (persisted before v80) have NULL mention columns → 0.
8003
+ overallMentionRate: r.overallMentionRate == null ? 0 : Number(r.overallMentionRate),
7945
8004
  totalPairs: r.totalPairs,
7946
8005
  citedPairs: r.citedPairs,
7947
- providerBreakdown: r.providerBreakdown,
8006
+ mentionedPairs: r.mentionedPairs ?? 0,
8007
+ providerBreakdown: coalesceProviderBreakdown(r.providerBreakdown),
7948
8008
  createdAt: r.createdAt,
7949
8009
  status: "ready"
7950
8010
  };
@@ -7953,26 +8013,31 @@ function aggregateHealthSnapshots(projectId, rows) {
7953
8013
  if (rows.length === 1) return mapHealthRow(rows[0]);
7954
8014
  let totalPairs = 0;
7955
8015
  let citedPairs = 0;
8016
+ let mentionedPairs = 0;
7956
8017
  const mergedProviders = {};
7957
8018
  let newestCreatedAt = "";
7958
8019
  const runIds = [];
7959
8020
  for (const row of rows) {
7960
8021
  totalPairs += row.totalPairs;
7961
8022
  citedPairs += row.citedPairs;
8023
+ mentionedPairs += row.mentionedPairs ?? 0;
7962
8024
  if (row.createdAt > newestCreatedAt) newestCreatedAt = row.createdAt;
7963
8025
  if (row.runId) runIds.push(row.runId);
7964
8026
  const providerBreakdown = row.providerBreakdown;
7965
8027
  for (const [provider, entry] of Object.entries(providerBreakdown)) {
7966
- const existing = mergedProviders[provider] ?? { total: 0, cited: 0, citedRate: 0 };
8028
+ const existing = mergedProviders[provider] ?? { total: 0, cited: 0, mentioned: 0, citedRate: 0, mentionRate: 0 };
7967
8029
  existing.total += entry.total;
7968
8030
  existing.cited += entry.cited;
8031
+ existing.mentioned += entry.mentioned ?? 0;
7969
8032
  mergedProviders[provider] = existing;
7970
8033
  }
7971
8034
  }
7972
8035
  for (const entry of Object.values(mergedProviders)) {
7973
8036
  entry.citedRate = entry.total > 0 ? entry.cited / entry.total : 0;
8037
+ entry.mentionRate = entry.total > 0 ? entry.mentioned / entry.total : 0;
7974
8038
  }
7975
8039
  const overallCitedRate = totalPairs > 0 ? citedPairs / totalPairs : 0;
8040
+ const overallMentionRate = totalPairs > 0 ? mentionedPairs / totalPairs : 0;
7976
8041
  return {
7977
8042
  // Synthetic id so consumers can tell this is an aggregate; concatenate
7978
8043
  // source runIds for traceability without inventing a new schema column.
@@ -7980,8 +8045,10 @@ function aggregateHealthSnapshots(projectId, rows) {
7980
8045
  projectId,
7981
8046
  runId: runIds[0] ?? null,
7982
8047
  overallCitedRate,
8048
+ overallMentionRate,
7983
8049
  totalPairs,
7984
8050
  citedPairs,
8051
+ mentionedPairs,
7985
8052
  providerBreakdown: mergedProviders,
7986
8053
  createdAt: newestCreatedAt,
7987
8054
  status: "ready"
@@ -11673,19 +11740,19 @@ function buildCitationsTrend(db, projectId, queryLookup, locationFilter) {
11673
11740
  considered++;
11674
11741
  if (snap.citationState === CitationStates.cited) citedQueryIds.add(snap.queryId);
11675
11742
  if (snap.answerMentioned) mentionedQueryIds.add(snap.queryId);
11676
- const counts = providerCounts.get(snap.provider) ?? { cited: 0, total: 0 };
11677
- counts.total++;
11678
- if (snap.citationState === CitationStates.cited) counts.cited++;
11679
- providerCounts.set(snap.provider, counts);
11743
+ const counts2 = providerCounts.get(snap.provider) ?? { cited: 0, total: 0 };
11744
+ counts2.total++;
11745
+ if (snap.citationState === CitationStates.cited) counts2.cited++;
11746
+ providerCounts.set(snap.provider, counts2);
11680
11747
  }
11681
11748
  if (considered === 0) continue;
11682
11749
  const citedQueryCount = citedQueryIds.size;
11683
11750
  const mentionedQueryCount = mentionedQueryIds.size;
11684
11751
  const citationRate = totalQueries > 0 ? Math.round(citedQueryCount / totalQueries * 100) : 0;
11685
11752
  const mentionRate = totalQueries > 0 ? Math.round(mentionedQueryCount / totalQueries * 100) : 0;
11686
- const providerRates = [...providerCounts.entries()].map(([provider, counts]) => ({
11753
+ const providerRates = [...providerCounts.entries()].map(([provider, counts2]) => ({
11687
11754
  provider,
11688
- citationRate: counts.total > 0 ? Math.round(counts.cited / counts.total * 100) : 0
11755
+ citationRate: counts2.total > 0 ? Math.round(counts2.cited / counts2.total * 100) : 0
11689
11756
  })).sort((a, b) => a.provider.localeCompare(b.provider));
11690
11757
  points.push({
11691
11758
  runId: run.id,
@@ -12655,8 +12722,165 @@ function normalizeDomain2(domain) {
12655
12722
  return domain.toLowerCase().trim().replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/$/, "");
12656
12723
  }
12657
12724
 
12725
+ // ../api-routes/src/visibility-stats.ts
12726
+ import { and as and11, desc as desc8, eq as eq16, inArray as inArray8 } from "drizzle-orm";
12727
+ function round42(value) {
12728
+ return Math.round(value * 1e4) / 1e4;
12729
+ }
12730
+ function emptyAgg() {
12731
+ return { total: 0, checked: 0, mentioned: 0, cited: 0, first: null, last: null };
12732
+ }
12733
+ function addSnapshot(agg, snap) {
12734
+ agg.total++;
12735
+ if (snap.answerMentioned === true || snap.answerMentioned === false) agg.checked++;
12736
+ if (snap.answerMentioned === true) agg.mentioned++;
12737
+ if (snap.citationState === CitationStates.cited) agg.cited++;
12738
+ if (agg.first === null || snap.createdAt < agg.first) agg.first = snap.createdAt;
12739
+ if (agg.last === null || snap.createdAt > agg.last) agg.last = snap.createdAt;
12740
+ }
12741
+ function counts(agg) {
12742
+ return {
12743
+ total: agg.total,
12744
+ checked: agg.checked,
12745
+ mentioned: agg.mentioned,
12746
+ cited: agg.cited,
12747
+ // mention proportion is over the CHECKED sample; citation proportion is
12748
+ // over the full total (every snapshot is checked for citation).
12749
+ mentionRate: agg.checked > 0 ? round42(agg.mentioned / agg.checked) : null,
12750
+ citedRate: agg.total > 0 ? round42(agg.cited / agg.total) : null
12751
+ };
12752
+ }
12753
+ function providerEntries(byProvider) {
12754
+ return [...byProvider.entries()].map(([provider, agg]) => ({
12755
+ provider,
12756
+ ...counts(agg),
12757
+ // first/last are non-null once at least one snapshot landed in the agg,
12758
+ // which is guaranteed for any provider that made it into the map.
12759
+ firstObserved: agg.first ?? "",
12760
+ lastObserved: agg.last ?? ""
12761
+ })).sort((a, b) => a.provider.localeCompare(b.provider));
12762
+ }
12763
+ function computeVisibilityStats(input) {
12764
+ const { groupBy } = input;
12765
+ const wantProviders = groupBy === "provider";
12766
+ const queryById = /* @__PURE__ */ new Map();
12767
+ const queryByText = /* @__PURE__ */ new Map();
12768
+ for (const q of input.queries) {
12769
+ queryById.set(q.id, q);
12770
+ queryByText.set(q.query, q);
12771
+ }
12772
+ const byQuery = /* @__PURE__ */ new Map();
12773
+ const totals = emptyAgg();
12774
+ const totalsByProvider = /* @__PURE__ */ new Map();
12775
+ for (const snap of input.snapshots) {
12776
+ let resolved;
12777
+ if (snap.queryId && queryById.has(snap.queryId)) resolved = queryById.get(snap.queryId);
12778
+ else if (snap.queryText && queryByText.has(snap.queryText)) resolved = queryByText.get(snap.queryText);
12779
+ if (!resolved) continue;
12780
+ let bucket = byQuery.get(resolved.id);
12781
+ if (!bucket) {
12782
+ bucket = { id: resolved.id, query: resolved.query, agg: emptyAgg(), byProvider: /* @__PURE__ */ new Map() };
12783
+ byQuery.set(resolved.id, bucket);
12784
+ }
12785
+ addSnapshot(bucket.agg, snap);
12786
+ addSnapshot(totals, snap);
12787
+ if (wantProviders) {
12788
+ const qpAgg = bucket.byProvider.get(snap.provider) ?? emptyAgg();
12789
+ addSnapshot(qpAgg, snap);
12790
+ bucket.byProvider.set(snap.provider, qpAgg);
12791
+ const tpAgg = totalsByProvider.get(snap.provider) ?? emptyAgg();
12792
+ addSnapshot(tpAgg, snap);
12793
+ totalsByProvider.set(snap.provider, tpAgg);
12794
+ }
12795
+ }
12796
+ const queryEntries = [...byQuery.values()].map((bucket) => ({
12797
+ queryId: bucket.id,
12798
+ query: bucket.query,
12799
+ ...counts(bucket.agg),
12800
+ firstObserved: bucket.agg.first ?? "",
12801
+ lastObserved: bucket.agg.last ?? "",
12802
+ ...wantProviders ? { providers: providerEntries(bucket.byProvider) } : {}
12803
+ })).sort((a, b) => a.query.localeCompare(b.query));
12804
+ return {
12805
+ totals: counts(totals),
12806
+ ...wantProviders ? { byProvider: providerEntries(totalsByProvider) } : {},
12807
+ queries: queryEntries
12808
+ };
12809
+ }
12810
+ async function visibilityStatsRoutes(app) {
12811
+ app.get("/projects/:name/visibility-stats", async (request, reply) => {
12812
+ const project = resolveProject(app.db, request.params.name);
12813
+ const { since: sinceRaw, until: untilRaw, lastRuns: lastRunsRaw, groupBy: groupByRaw } = request.query;
12814
+ let groupBy = null;
12815
+ if (groupByRaw !== void 0 && groupByRaw !== "") {
12816
+ if (groupByRaw !== "provider") throw validationError('"groupBy" must be "provider"');
12817
+ groupBy = "provider";
12818
+ }
12819
+ const hasSince = sinceRaw !== void 0 && sinceRaw !== "";
12820
+ const hasUntil = untilRaw !== void 0 && untilRaw !== "";
12821
+ const hasLastRuns = lastRunsRaw !== void 0 && lastRunsRaw !== "";
12822
+ if (hasLastRuns && (hasSince || hasUntil)) {
12823
+ throw validationError('"lastRuns" cannot be combined with "since"/"until" \u2014 use one or the other');
12824
+ }
12825
+ let sinceMs = null;
12826
+ let untilMs = null;
12827
+ if (hasSince) {
12828
+ const ms = Date.parse(sinceRaw);
12829
+ if (Number.isNaN(ms)) throw validationError('"since" must be an ISO 8601 date/time');
12830
+ sinceMs = ms;
12831
+ }
12832
+ if (hasUntil) {
12833
+ const ms = parseInclusiveEndMs(untilRaw);
12834
+ if (ms === null) throw validationError('"until" must be an ISO 8601 date/time');
12835
+ untilMs = ms;
12836
+ }
12837
+ if (sinceMs !== null && untilMs !== null && untilMs < sinceMs) {
12838
+ throw validationError('"until" must be on or after "since"');
12839
+ }
12840
+ let lastRuns = null;
12841
+ if (hasLastRuns) {
12842
+ const n = Number(lastRunsRaw);
12843
+ if (!Number.isInteger(n) || n <= 0) throw validationError('"lastRuns" must be a positive integer');
12844
+ lastRuns = n;
12845
+ }
12846
+ const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq16(queries.projectId, project.id)).all();
12847
+ let projectRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt, status: runs.status }).from(runs).where(and11(eq16(runs.projectId, project.id), eq16(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(desc8(runs.createdAt)).all().filter((r) => r.status === RunStatuses.completed || r.status === RunStatuses.partial);
12848
+ if (sinceMs !== null) projectRuns = projectRuns.filter((r) => Date.parse(r.createdAt) >= sinceMs);
12849
+ if (untilMs !== null) projectRuns = projectRuns.filter((r) => Date.parse(r.createdAt) <= untilMs);
12850
+ if (lastRuns !== null) projectRuns = projectRuns.slice(0, lastRuns);
12851
+ const runCount = projectRuns.length;
12852
+ const runIds = projectRuns.map((r) => r.id);
12853
+ const snapshots = runIds.length > 0 && projectQueries.length > 0 ? app.db.select({
12854
+ queryId: querySnapshots.queryId,
12855
+ queryText: querySnapshots.queryText,
12856
+ provider: querySnapshots.provider,
12857
+ citationState: querySnapshots.citationState,
12858
+ answerMentioned: querySnapshots.answerMentioned,
12859
+ createdAt: querySnapshots.createdAt
12860
+ }).from(querySnapshots).where(inArray8(querySnapshots.runId, runIds)).all() : [];
12861
+ const stats = computeVisibilityStats({ queries: projectQueries, snapshots, groupBy });
12862
+ const response = {
12863
+ project: project.name,
12864
+ window: {
12865
+ since: hasSince ? sinceRaw : null,
12866
+ until: hasUntil ? untilRaw : null,
12867
+ lastRuns,
12868
+ runCount
12869
+ },
12870
+ totals: stats.totals,
12871
+ queries: stats.queries,
12872
+ // `groupBy` + `byProvider` appear together only when a breakdown was
12873
+ // requested; both are OMITTED otherwise (absent = no breakdown) so the
12874
+ // SDK types `groupBy` as `groupBy?: 'provider'` rather than a misleading
12875
+ // always-present literal.
12876
+ ...groupBy === "provider" ? { groupBy, byProvider: stats.byProvider ?? [] } : {}
12877
+ };
12878
+ return reply.send(response);
12879
+ });
12880
+ }
12881
+
12658
12882
  // ../api-routes/src/composites.ts
12659
- import { eq as eq16, and as and11, desc as desc8, sql as sql7, like, or as or4, inArray as inArray8 } from "drizzle-orm";
12883
+ import { eq as eq17, and as and12, desc as desc9, sql as sql7, like, or as or4, inArray as inArray9 } from "drizzle-orm";
12660
12884
  var TOP_INSIGHT_LIMIT = 5;
12661
12885
  var SEARCH_HIT_HARD_LIMIT = 50;
12662
12886
  var SEARCH_SNIPPET_RADIUS = 80;
@@ -12674,7 +12898,7 @@ async function compositeRoutes(app) {
12674
12898
  const project = resolveProject(app.db, request.params.name);
12675
12899
  const filterLocation = (request.query.location ?? "").trim() || null;
12676
12900
  const sinceIso = parseSinceFilter(request.query.since);
12677
- const allRunsRaw = app.db.select().from(runs).where(and11(eq16(runs.projectId, project.id), notProbeRun())).orderBy(desc8(runs.createdAt), desc8(runs.id)).all();
12901
+ const allRunsRaw = app.db.select().from(runs).where(and12(eq17(runs.projectId, project.id), notProbeRun())).orderBy(desc9(runs.createdAt), desc9(runs.id)).all();
12678
12902
  const allRuns = allRunsRaw.filter((r) => runMatchesFilters(r, filterLocation, sinceIso));
12679
12903
  const totalRuns = allRuns.length;
12680
12904
  const visibilityRuns = allRuns.filter((r) => r.kind === RunKinds["answer-visibility"]);
@@ -12687,9 +12911,9 @@ async function compositeRoutes(app) {
12687
12911
  const previousVisibilityRun = pickGroupRepresentative(previousVisRunGroup);
12688
12912
  const latestRunRow = allRuns[0] ?? null;
12689
12913
  const latestRun = latestRunRow ? { totalRuns, run: summarizeRun(latestRunRow) } : { totalRuns: 0, run: null };
12690
- const healthRow = app.db.select().from(healthSnapshots).where(eq16(healthSnapshots.projectId, project.id)).orderBy(desc8(healthSnapshots.createdAt)).limit(1).get();
12914
+ const healthRow = app.db.select().from(healthSnapshots).where(eq17(healthSnapshots.projectId, project.id)).orderBy(desc9(healthSnapshots.createdAt)).limit(1).get();
12691
12915
  const health = healthRow ? mapHealthRow2(healthRow) : null;
12692
- const insightRows = app.db.select().from(insights).where(eq16(insights.projectId, project.id)).orderBy(desc8(insights.createdAt)).all();
12916
+ const insightRows = app.db.select().from(insights).where(eq17(insights.projectId, project.id)).orderBy(desc9(insights.createdAt)).all();
12693
12917
  const topInsights = insightRows.filter((row) => !row.dismissed).slice(0, TOP_INSIGHT_LIMIT).map(mapInsightRow2);
12694
12918
  const sparklineRunIds = visibilityRuns.slice(0, DEFAULT_RUN_HISTORY_LIMIT).map((r) => r.id);
12695
12919
  const snapshotRunIds = new Set(sparklineRunIds);
@@ -12704,8 +12928,8 @@ async function compositeRoutes(app) {
12704
12928
  previousSnapshots,
12705
12929
  previousVisibilityRun?.createdAt ?? null
12706
12930
  );
12707
- const competitorRows = app.db.select().from(competitors).where(eq16(competitors.projectId, project.id)).all();
12708
- const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq16(queries.projectId, project.id)).all();
12931
+ const competitorRows = app.db.select().from(competitors).where(eq17(competitors.projectId, project.id)).all();
12932
+ const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq17(queries.projectId, project.id)).all();
12709
12933
  const queryLookup = { byId: new Map(projectQueries.map((q) => [q.id, q.query])) };
12710
12934
  const configuredApiProviders = project.providers.filter((p) => !p.startsWith("cdp:"));
12711
12935
  const mentionShareCompetitors = competitorRows.map((c) => ({
@@ -12804,9 +13028,9 @@ async function compositeRoutes(app) {
12804
13028
  citedDomains: querySnapshots.citedDomains,
12805
13029
  rawResponse: querySnapshots.rawResponse,
12806
13030
  createdAt: querySnapshots.createdAt
12807
- }).from(querySnapshots).innerJoin(queries, eq16(querySnapshots.queryId, queries.id)).where(
12808
- and11(
12809
- eq16(queries.projectId, project.id),
13031
+ }).from(querySnapshots).innerJoin(queries, eq17(querySnapshots.queryId, queries.id)).where(
13032
+ and12(
13033
+ eq17(queries.projectId, project.id),
12810
13034
  or4(
12811
13035
  sql7`${querySnapshots.answerText} LIKE ${pattern} ESCAPE '\\'`,
12812
13036
  sql7`${querySnapshots.citedDomains} LIKE ${pattern} ESCAPE '\\'`,
@@ -12814,10 +13038,10 @@ async function compositeRoutes(app) {
12814
13038
  like(queries.query, pattern)
12815
13039
  )
12816
13040
  )
12817
- ).orderBy(desc8(querySnapshots.createdAt)).limit(limit + 1).all());
13041
+ ).orderBy(desc9(querySnapshots.createdAt)).limit(limit + 1).all());
12818
13042
  const insightMatches = app.db.select().from(insights).where(
12819
- and11(
12820
- eq16(insights.projectId, project.id),
13043
+ and12(
13044
+ eq17(insights.projectId, project.id),
12821
13045
  or4(
12822
13046
  like(insights.title, pattern),
12823
13047
  like(insights.query, pattern),
@@ -12825,7 +13049,7 @@ async function compositeRoutes(app) {
12825
13049
  sql7`${insights.cause} LIKE ${pattern} ESCAPE '\\'`
12826
13050
  )
12827
13051
  )
12828
- ).orderBy(desc8(insights.createdAt)).limit(limit + 1).all();
13052
+ ).orderBy(desc9(insights.createdAt)).limit(limit + 1).all();
12829
13053
  const hits = [];
12830
13054
  for (const row of snapshotMatches) {
12831
13055
  hits.push(buildSnapshotHit(row, rawQuery));
@@ -12895,7 +13119,7 @@ function loadSnapshotsByRunIds(app, runIds) {
12895
13119
  answerText: querySnapshots.answerText,
12896
13120
  competitorOverlap: querySnapshots.competitorOverlap,
12897
13121
  citedDomains: querySnapshots.citedDomains
12898
- }).from(querySnapshots).where(inArray8(querySnapshots.runId, [...runIds])).all());
13122
+ }).from(querySnapshots).where(inArray9(querySnapshots.runId, [...runIds])).all());
12899
13123
  for (const row of rows) {
12900
13124
  const list = result.get(row.runId) ?? [];
12901
13125
  list.push({
@@ -13013,8 +13237,8 @@ function buildSuggestedQueriesFromGsc(app, projectId, trackedQueries) {
13013
13237
  // NULLIF guards the degenerate impressions=0 case (SQLite returns NULL,
13014
13238
  // which the JS coerces to 0 — caught by the impression floor anyway).
13015
13239
  avgPosition: sql7`COALESCE(SUM(${gscSearchData.position} * ${gscSearchData.impressions}) * 1.0 / NULLIF(SUM(${gscSearchData.impressions}), 0), 0)`
13016
- }).from(gscSearchData).where(and11(
13017
- eq16(gscSearchData.projectId, projectId),
13240
+ }).from(gscSearchData).where(and12(
13241
+ eq17(gscSearchData.projectId, projectId),
13018
13242
  sql7`${gscSearchData.date} >= ${cutoff}`,
13019
13243
  sql7`${gscSearchData.impressions} > 0`
13020
13244
  )).groupBy(gscSearchData.query).orderBy(sql7`SUM(${gscSearchData.impressions}) DESC`).limit(100).all();
@@ -13037,8 +13261,8 @@ function buildIndexCoverageScore(app, projectId) {
13037
13261
  tooltip,
13038
13262
  trend: []
13039
13263
  };
13040
- const gscRow = app.db.select().from(gscCoverageSnapshots).where(eq16(gscCoverageSnapshots.projectId, projectId)).orderBy(desc8(gscCoverageSnapshots.date)).limit(1).get();
13041
- const bingRow = app.db.select().from(bingCoverageSnapshots).where(eq16(bingCoverageSnapshots.projectId, projectId)).orderBy(desc8(bingCoverageSnapshots.date)).limit(1).get();
13264
+ const gscRow = app.db.select().from(gscCoverageSnapshots).where(eq17(gscCoverageSnapshots.projectId, projectId)).orderBy(desc9(gscCoverageSnapshots.date)).limit(1).get();
13265
+ const bingRow = app.db.select().from(bingCoverageSnapshots).where(eq17(bingCoverageSnapshots.projectId, projectId)).orderBy(desc9(bingCoverageSnapshots.date)).limit(1).get();
13042
13266
  const chosen = pickIndexCoverageRow(gscRow, bingRow);
13043
13267
  if (!chosen) return empty;
13044
13268
  const total = chosen.indexed + chosen.notIndexed;
@@ -13064,7 +13288,7 @@ function countGoogleDeindexedUrls(app, projectId) {
13064
13288
  url: gscUrlInspections.url,
13065
13289
  indexingState: gscUrlInspections.indexingState,
13066
13290
  inspectedAt: gscUrlInspections.inspectedAt
13067
- }).from(gscUrlInspections).where(eq16(gscUrlInspections.projectId, projectId)).orderBy(desc8(gscUrlInspections.inspectedAt)).all();
13291
+ }).from(gscUrlInspections).where(eq17(gscUrlInspections.projectId, projectId)).orderBy(desc9(gscUrlInspections.inspectedAt)).all();
13068
13292
  if (rows.length === 0) return 0;
13069
13293
  const canonicalUrl = (url) => url.replace(/^http:\/\//, "https://");
13070
13294
  const historyByUrl = /* @__PURE__ */ new Map();
@@ -13178,14 +13402,27 @@ function mapInsightRow2(r) {
13178
13402
  };
13179
13403
  }
13180
13404
  function mapHealthRow2(r) {
13405
+ const providerBreakdown = {};
13406
+ for (const [provider, entry] of Object.entries(r.providerBreakdown)) {
13407
+ providerBreakdown[provider] = {
13408
+ citedRate: entry.citedRate,
13409
+ mentionRate: entry.mentionRate ?? 0,
13410
+ cited: entry.cited,
13411
+ mentioned: entry.mentioned ?? 0,
13412
+ total: entry.total
13413
+ };
13414
+ }
13181
13415
  return {
13182
13416
  id: r.id,
13183
13417
  projectId: r.projectId,
13184
13418
  runId: r.runId ?? null,
13185
13419
  overallCitedRate: Number(r.overallCitedRate),
13420
+ // Legacy rows (persisted before v80) have NULL mention columns → 0.
13421
+ overallMentionRate: r.overallMentionRate == null ? 0 : Number(r.overallMentionRate),
13186
13422
  totalPairs: r.totalPairs,
13187
13423
  citedPairs: r.citedPairs,
13188
- providerBreakdown: r.providerBreakdown,
13424
+ mentionedPairs: r.mentionedPairs ?? 0,
13425
+ providerBreakdown,
13189
13426
  createdAt: r.createdAt,
13190
13427
  status: "ready"
13191
13428
  };
@@ -13397,6 +13634,7 @@ var SCHEMA_TABLE = {
13397
13634
  TrafficSourceListResponse: trafficSourceListResponseSchema,
13398
13635
  TrafficStatusResponse: trafficStatusResponseSchema,
13399
13636
  TrafficSyncResponse: trafficSyncResponseSchema,
13637
+ VisibilityStatsDto: visibilityStatsDtoSchema,
13400
13638
  WordpressAuditPageDto: wordpressAuditPageDtoSchema,
13401
13639
  WordpressBulkMetaResultDto: wordpressBulkMetaResultDtoSchema,
13402
13640
  WordpressDiffDto: wordpressDiffDtoSchema,
@@ -13622,6 +13860,30 @@ var analyticsWindowParameter = {
13622
13860
  description: "Time window for analytics queries.",
13623
13861
  schema: { type: "string", enum: ["7d", "30d", "90d", "all"] }
13624
13862
  };
13863
+ var sinceQueryParameter = {
13864
+ name: "since",
13865
+ in: "query",
13866
+ description: 'Inclusive lower bound on run createdAt (ISO 8601). A date-only value (YYYY-MM-DD) is the start of that UTC day. Mutually exclusive with "lastRuns".',
13867
+ schema: stringSchema
13868
+ };
13869
+ var untilQueryParameter = {
13870
+ name: "until",
13871
+ in: "query",
13872
+ description: 'Inclusive upper bound on run createdAt (ISO 8601). A date-only value (YYYY-MM-DD) covers the whole UTC day (through 23:59:59.999). Mutually exclusive with "lastRuns".',
13873
+ schema: stringSchema
13874
+ };
13875
+ var lastRunsQueryParameter = {
13876
+ name: "lastRuns",
13877
+ in: "query",
13878
+ description: 'Aggregate only the most recent N answer-visibility runs. Mutually exclusive with "since"/"until".',
13879
+ schema: integerSchema
13880
+ };
13881
+ var groupByProviderQueryParameter = {
13882
+ name: "groupBy",
13883
+ in: "query",
13884
+ description: 'Set to "provider" to include a per-provider breakdown whose counts sum to the pooled counts.',
13885
+ schema: { type: "string", enum: ["provider"] }
13886
+ };
13625
13887
  var wordpressEnvQueryParameter = {
13626
13888
  name: "env",
13627
13889
  in: "query",
@@ -14348,6 +14610,19 @@ var routeCatalog = [
14348
14610
  404: errorResponse("Project not found.")
14349
14611
  }
14350
14612
  },
14613
+ {
14614
+ method: "get",
14615
+ path: "/api/v1/projects/{name}/visibility-stats",
14616
+ summary: "Get aggregated mention/citation stats per query",
14617
+ description: "Per-query mention (answer-text) and citation (source-list) counts with a sample size, pooled across many answer-visibility runs (probe-excluded). Tri-state aware: `checked` counts only snapshots where answerMentioned was recorded (null = not checked is excluded). Lets a consumer compute confidence-aware (e.g. Wilson) proportions without N+1 run fetches. With no since/until/lastRuns, EVERY completed/partial answer-visibility run is pooled \u2014 `window.runCount` reports how many; bound the window with lastRuns or since/until for a recent sample. Set groupBy=provider for a per-provider breakdown whose counts sum to the pooled counts.",
14618
+ tags: ["analytics"],
14619
+ parameters: [nameParameter, sinceQueryParameter, untilQueryParameter, lastRunsQueryParameter, groupByProviderQueryParameter],
14620
+ responses: {
14621
+ 200: jsonResponse("Aggregated visibility stats returned.", "VisibilityStatsDto"),
14622
+ 400: errorResponse("Invalid query parameters."),
14623
+ 404: errorResponse("Project not found.")
14624
+ }
14625
+ },
14351
14626
  {
14352
14627
  method: "get",
14353
14628
  path: "/api/v1/projects/{name}/snapshots/diff",
@@ -17613,7 +17888,7 @@ async function settingsRoutes(app, opts) {
17613
17888
 
17614
17889
  // ../api-routes/src/keys.ts
17615
17890
  import crypto12 from "crypto";
17616
- import { desc as desc9, eq as eq17 } from "drizzle-orm";
17891
+ import { desc as desc10, eq as eq18 } from "drizzle-orm";
17617
17892
  var KEYS_WRITE_SCOPE = "keys.write";
17618
17893
  function toApiKeyDto(row) {
17619
17894
  const scopes = Array.isArray(row.scopes) ? row.scopes : [];
@@ -17630,7 +17905,7 @@ function toApiKeyDto(row) {
17630
17905
  }
17631
17906
  async function keysRoutes(app) {
17632
17907
  app.get("/keys", async () => {
17633
- const rows = app.db.select().from(apiKeys).orderBy(desc9(apiKeys.createdAt)).all();
17908
+ const rows = app.db.select().from(apiKeys).orderBy(desc10(apiKeys.createdAt)).all();
17634
17909
  return { keys: rows.map(toApiKeyDto) };
17635
17910
  });
17636
17911
  app.get("/keys/self", async (request) => {
@@ -17638,7 +17913,7 @@ async function keysRoutes(app) {
17638
17913
  if (!id) {
17639
17914
  throw notFound("API key", "self");
17640
17915
  }
17641
- const row = app.db.select().from(apiKeys).where(eq17(apiKeys.id, id)).get();
17916
+ const row = app.db.select().from(apiKeys).where(eq18(apiKeys.id, id)).get();
17642
17917
  if (!row) {
17643
17918
  throw notFound("API key", id);
17644
17919
  }
@@ -17690,7 +17965,7 @@ async function keysRoutes(app) {
17690
17965
  app.post("/keys/:id/revoke", async (request) => {
17691
17966
  requireScope(request, KEYS_WRITE_SCOPE);
17692
17967
  const { id } = request.params;
17693
- const row = app.db.select().from(apiKeys).where(eq17(apiKeys.id, id)).get();
17968
+ const row = app.db.select().from(apiKeys).where(eq18(apiKeys.id, id)).get();
17694
17969
  if (!row) {
17695
17970
  throw notFound("API key", id);
17696
17971
  }
@@ -17702,7 +17977,7 @@ async function keysRoutes(app) {
17702
17977
  }
17703
17978
  const now = (/* @__PURE__ */ new Date()).toISOString();
17704
17979
  app.db.transaction((tx) => {
17705
- tx.update(apiKeys).set({ revokedAt: now }).where(eq17(apiKeys.id, id)).run();
17980
+ tx.update(apiKeys).set({ revokedAt: now }).where(eq18(apiKeys.id, id)).run();
17706
17981
  writeAuditLog(tx, auditFromRequest(request, {
17707
17982
  actor: "api",
17708
17983
  action: "api-key.revoked",
@@ -17775,7 +18050,7 @@ async function telemetryRoutes(app, opts) {
17775
18050
 
17776
18051
  // ../api-routes/src/schedules.ts
17777
18052
  import crypto13 from "crypto";
17778
- import { and as and12, eq as eq18 } from "drizzle-orm";
18053
+ import { and as and13, eq as eq19 } from "drizzle-orm";
17779
18054
  function parseKindParam(raw) {
17780
18055
  if (raw === void 0 || raw === null || raw === "") return SchedulableRunKinds["answer-visibility"];
17781
18056
  const parsed = schedulableRunKindSchema.safeParse(raw);
@@ -17802,7 +18077,7 @@ async function scheduleRoutes(app, opts) {
17802
18077
  if (!sourceId) {
17803
18078
  throw validationError('"sourceId" is required when kind is "traffic-sync"');
17804
18079
  }
17805
- const sourceRow = app.db.select().from(trafficSources).where(eq18(trafficSources.id, sourceId)).get();
18080
+ const sourceRow = app.db.select().from(trafficSources).where(eq19(trafficSources.id, sourceId)).get();
17806
18081
  if (!sourceRow || sourceRow.projectId !== project.id) {
17807
18082
  throw notFound("Traffic source", sourceId);
17808
18083
  }
@@ -17847,7 +18122,7 @@ async function scheduleRoutes(app, opts) {
17847
18122
  }
17848
18123
  const now = (/* @__PURE__ */ new Date()).toISOString();
17849
18124
  const enabledBool = enabled !== false;
17850
- const existing = app.db.select().from(schedules).where(and12(eq18(schedules.projectId, project.id), eq18(schedules.kind, kind))).get();
18125
+ const existing = app.db.select().from(schedules).where(and13(eq19(schedules.projectId, project.id), eq19(schedules.kind, kind))).get();
17851
18126
  if (existing) {
17852
18127
  app.db.update(schedules).set({
17853
18128
  cronExpr,
@@ -17857,7 +18132,7 @@ async function scheduleRoutes(app, opts) {
17857
18132
  sourceId: sourceId ?? null,
17858
18133
  enabled: enabledBool,
17859
18134
  updatedAt: now
17860
- }).where(eq18(schedules.id, existing.id)).run();
18135
+ }).where(eq19(schedules.id, existing.id)).run();
17861
18136
  } else {
17862
18137
  app.db.insert(schedules).values({
17863
18138
  id: crypto13.randomUUID(),
@@ -17881,13 +18156,13 @@ async function scheduleRoutes(app, opts) {
17881
18156
  diff: { kind, cronExpr, preset, timezone, providers, sourceId }
17882
18157
  });
17883
18158
  opts.onScheduleUpdated?.("upsert", project.id, kind);
17884
- const schedule = app.db.select().from(schedules).where(and12(eq18(schedules.projectId, project.id), eq18(schedules.kind, kind))).get();
18159
+ const schedule = app.db.select().from(schedules).where(and13(eq19(schedules.projectId, project.id), eq19(schedules.kind, kind))).get();
17885
18160
  return reply.status(existing ? 200 : 201).send(formatSchedule(schedule));
17886
18161
  });
17887
18162
  app.get("/projects/:name/schedule", async (request, reply) => {
17888
18163
  const project = resolveProject(app.db, request.params.name);
17889
18164
  const kind = parseKindParam(request.query?.kind);
17890
- const schedule = app.db.select().from(schedules).where(and12(eq18(schedules.projectId, project.id), eq18(schedules.kind, kind))).get();
18165
+ const schedule = app.db.select().from(schedules).where(and13(eq19(schedules.projectId, project.id), eq19(schedules.kind, kind))).get();
17891
18166
  if (!schedule) {
17892
18167
  throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
17893
18168
  }
@@ -17896,11 +18171,11 @@ async function scheduleRoutes(app, opts) {
17896
18171
  app.delete("/projects/:name/schedule", async (request, reply) => {
17897
18172
  const project = resolveProject(app.db, request.params.name);
17898
18173
  const kind = parseKindParam(request.query?.kind);
17899
- const schedule = app.db.select().from(schedules).where(and12(eq18(schedules.projectId, project.id), eq18(schedules.kind, kind))).get();
18174
+ const schedule = app.db.select().from(schedules).where(and13(eq19(schedules.projectId, project.id), eq19(schedules.kind, kind))).get();
17900
18175
  if (!schedule) {
17901
18176
  throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
17902
18177
  }
17903
- app.db.delete(schedules).where(eq18(schedules.id, schedule.id)).run();
18178
+ app.db.delete(schedules).where(eq19(schedules.id, schedule.id)).run();
17904
18179
  writeAuditLog(app.db, {
17905
18180
  projectId: project.id,
17906
18181
  actor: "api",
@@ -17933,7 +18208,7 @@ function formatSchedule(row) {
17933
18208
 
17934
18209
  // ../api-routes/src/notifications.ts
17935
18210
  import crypto14 from "crypto";
17936
- import { eq as eq19 } from "drizzle-orm";
18211
+ import { eq as eq20 } from "drizzle-orm";
17937
18212
  var VALID_EVENTS = ["citation.lost", "citation.gained", "run.completed", "run.failed", "insight.critical", "insight.high"];
17938
18213
  async function notificationRoutes(app, opts = {}) {
17939
18214
  const allowLoopback = opts.allowLoopbackWebhooks === true;
@@ -17973,22 +18248,22 @@ async function notificationRoutes(app, opts = {}) {
17973
18248
  diff: { channel, ...redactNotificationUrl(url), events }
17974
18249
  });
17975
18250
  return reply.status(201).send({
17976
- ...formatNotification(app.db.select().from(notifications).where(eq19(notifications.id, id)).get()),
18251
+ ...formatNotification(app.db.select().from(notifications).where(eq20(notifications.id, id)).get()),
17977
18252
  webhookSecret
17978
18253
  });
17979
18254
  });
17980
18255
  app.get("/projects/:name/notifications", async (request, reply) => {
17981
18256
  const project = resolveProject(app.db, request.params.name);
17982
- const rows = app.db.select().from(notifications).where(eq19(notifications.projectId, project.id)).all();
18257
+ const rows = app.db.select().from(notifications).where(eq20(notifications.projectId, project.id)).all();
17983
18258
  return reply.send(rows.map(formatNotification));
17984
18259
  });
17985
18260
  app.delete("/projects/:name/notifications/:id", async (request, reply) => {
17986
18261
  const project = resolveProject(app.db, request.params.name);
17987
- const notification = app.db.select().from(notifications).where(eq19(notifications.id, request.params.id)).get();
18262
+ const notification = app.db.select().from(notifications).where(eq20(notifications.id, request.params.id)).get();
17988
18263
  if (!notification || notification.projectId !== project.id) {
17989
18264
  throw notFound("Notification", request.params.id);
17990
18265
  }
17991
- app.db.delete(notifications).where(eq19(notifications.id, notification.id)).run();
18266
+ app.db.delete(notifications).where(eq20(notifications.id, notification.id)).run();
17992
18267
  writeAuditLog(app.db, {
17993
18268
  projectId: project.id,
17994
18269
  actor: "api",
@@ -18000,7 +18275,7 @@ async function notificationRoutes(app, opts = {}) {
18000
18275
  });
18001
18276
  app.post("/projects/:name/notifications/:id/test", async (request, reply) => {
18002
18277
  const project = resolveProject(app.db, request.params.name);
18003
- const notification = app.db.select().from(notifications).where(eq19(notifications.id, request.params.id)).get();
18278
+ const notification = app.db.select().from(notifications).where(eq20(notifications.id, request.params.id)).get();
18004
18279
  if (!notification || notification.projectId !== project.id) {
18005
18280
  throw notFound("Notification", request.params.id);
18006
18281
  }
@@ -18053,7 +18328,7 @@ function formatNotification(row) {
18053
18328
 
18054
18329
  // ../api-routes/src/google.ts
18055
18330
  import crypto17 from "crypto";
18056
- import { eq as eq20, and as and13, desc as desc10, sql as sql8, inArray as inArray9 } from "drizzle-orm";
18331
+ import { eq as eq21, and as and14, desc as desc11, sql as sql8, inArray as inArray10 } from "drizzle-orm";
18057
18332
 
18058
18333
  // ../api-routes/src/gbp-summary.ts
18059
18334
  function computeMetricTotals(rows) {
@@ -19779,7 +20054,7 @@ async function googleRoutes(app, opts) {
19779
20054
  if (!projectId) {
19780
20055
  return reply.status(400).send("Stale OAuth state \u2014 restart the connect flow.");
19781
20056
  }
19782
- const project = app.db.select().from(projects).where(eq20(projects.id, projectId)).get();
20057
+ const project = app.db.select().from(projects).where(eq21(projects.id, projectId)).get();
19783
20058
  if (!project) {
19784
20059
  return reply.status(400).send("Project no longer exists. Restart the connect flow.");
19785
20060
  }
@@ -19911,14 +20186,14 @@ async function googleRoutes(app, opts) {
19911
20186
  if (opts.onGscSyncRequested) {
19912
20187
  opts.onGscSyncRequested(runId, project.id, { days, full });
19913
20188
  }
19914
- const run = app.db.select().from(runs).where(eq20(runs.id, runId)).get();
20189
+ const run = app.db.select().from(runs).where(eq21(runs.id, runId)).get();
19915
20190
  return run;
19916
20191
  });
19917
20192
  app.get("/projects/:name/google/gsc/performance", async (request) => {
19918
20193
  const project = resolveProject(app.db, request.params.name);
19919
20194
  const { startDate, endDate, query, page, limit, offset } = request.query;
19920
20195
  const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
19921
- const conditions = [eq20(gscSearchData.projectId, project.id)];
20196
+ const conditions = [eq21(gscSearchData.projectId, project.id)];
19922
20197
  if (startDate) conditions.push(sql8`${gscSearchData.date} >= ${startDate}`);
19923
20198
  else if (cutoffDate) conditions.push(sql8`${gscSearchData.date} >= ${cutoffDate}`);
19924
20199
  if (endDate) conditions.push(sql8`${gscSearchData.date} <= ${endDate}`);
@@ -19926,7 +20201,7 @@ async function googleRoutes(app, opts) {
19926
20201
  if (page) conditions.push(sql8`${gscSearchData.page} LIKE ${"%" + escapeLikePattern(page) + "%"} ESCAPE '\\'`);
19927
20202
  const limitVal = Math.max(parseInt(limit ?? "500", 10) || 0, 1);
19928
20203
  const offsetVal = Math.max(parseInt(offset ?? "0", 10) || 0, 0);
19929
- const rows = app.db.select().from(gscSearchData).where(and13(...conditions)).orderBy(desc10(gscSearchData.date)).limit(limitVal).offset(offsetVal).all();
20204
+ const rows = app.db.select().from(gscSearchData).where(and14(...conditions)).orderBy(desc11(gscSearchData.date)).limit(limitVal).offset(offsetVal).all();
19930
20205
  return rows.map((r) => ({
19931
20206
  date: r.date,
19932
20207
  query: r.query,
@@ -19943,7 +20218,7 @@ async function googleRoutes(app, opts) {
19943
20218
  const project = resolveProject(app.db, request.params.name);
19944
20219
  const { startDate, endDate } = request.query;
19945
20220
  const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
19946
- const conditions = [eq20(gscSearchData.projectId, project.id)];
20221
+ const conditions = [eq21(gscSearchData.projectId, project.id)];
19947
20222
  if (startDate) conditions.push(sql8`${gscSearchData.date} >= ${startDate}`);
19948
20223
  else if (cutoffDate) conditions.push(sql8`${gscSearchData.date} >= ${cutoffDate}`);
19949
20224
  if (endDate) conditions.push(sql8`${gscSearchData.date} <= ${endDate}`);
@@ -19951,7 +20226,7 @@ async function googleRoutes(app, opts) {
19951
20226
  date: gscSearchData.date,
19952
20227
  clicks: sql8`COALESCE(SUM(${gscSearchData.clicks}), 0)`,
19953
20228
  impressions: sql8`COALESCE(SUM(${gscSearchData.impressions}), 0)`
19954
- }).from(gscSearchData).where(and13(...conditions)).groupBy(gscSearchData.date).orderBy(gscSearchData.date).all();
20229
+ }).from(gscSearchData).where(and14(...conditions)).groupBy(gscSearchData.date).orderBy(gscSearchData.date).all();
19955
20230
  const daily = rows.map((r) => ({
19956
20231
  date: r.date,
19957
20232
  clicks: r.clicks,
@@ -20034,9 +20309,9 @@ async function googleRoutes(app, opts) {
20034
20309
  app.get("/projects/:name/google/gsc/inspections", async (request) => {
20035
20310
  const project = resolveProject(app.db, request.params.name);
20036
20311
  const { url, limit } = request.query;
20037
- const conditions = [eq20(gscUrlInspections.projectId, project.id)];
20038
- if (url) conditions.push(eq20(gscUrlInspections.url, url));
20039
- const rows = app.db.select().from(gscUrlInspections).where(and13(...conditions)).orderBy(desc10(gscUrlInspections.inspectedAt)).limit(parseInt(limit ?? "100", 10)).all();
20312
+ const conditions = [eq21(gscUrlInspections.projectId, project.id)];
20313
+ if (url) conditions.push(eq21(gscUrlInspections.url, url));
20314
+ const rows = app.db.select().from(gscUrlInspections).where(and14(...conditions)).orderBy(desc11(gscUrlInspections.inspectedAt)).limit(parseInt(limit ?? "100", 10)).all();
20040
20315
  return rows.map((r) => ({
20041
20316
  id: r.id,
20042
20317
  url: r.url,
@@ -20055,7 +20330,7 @@ async function googleRoutes(app, opts) {
20055
20330
  });
20056
20331
  app.get("/projects/:name/google/gsc/deindexed", async (request) => {
20057
20332
  const project = resolveProject(app.db, request.params.name);
20058
- const allInspections = app.db.select().from(gscUrlInspections).where(eq20(gscUrlInspections.projectId, project.id)).orderBy(desc10(gscUrlInspections.inspectedAt)).all();
20333
+ const allInspections = app.db.select().from(gscUrlInspections).where(eq21(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
20059
20334
  const byUrl = /* @__PURE__ */ new Map();
20060
20335
  for (const row of allInspections) {
20061
20336
  const existing = byUrl.get(row.url);
@@ -20083,7 +20358,7 @@ async function googleRoutes(app, opts) {
20083
20358
  });
20084
20359
  app.get("/projects/:name/google/gsc/coverage", async (request) => {
20085
20360
  const project = resolveProject(app.db, request.params.name);
20086
- const allInspections = app.db.select().from(gscUrlInspections).where(eq20(gscUrlInspections.projectId, project.id)).orderBy(desc10(gscUrlInspections.inspectedAt)).all();
20361
+ const allInspections = app.db.select().from(gscUrlInspections).where(eq21(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
20087
20362
  const canonicalUrl = (url) => url.replace(/^http:\/\//, "https://");
20088
20363
  const latestByUrl = /* @__PURE__ */ new Map();
20089
20364
  const historyByUrl = /* @__PURE__ */ new Map();
@@ -20132,7 +20407,7 @@ async function googleRoutes(app, opts) {
20132
20407
  const total = latestByUrl.size;
20133
20408
  const indexed = indexedUrls.length;
20134
20409
  const notIndexed = notIndexedUrls.length;
20135
- const latestSnapshot = app.db.select({ createdAt: gscCoverageSnapshots.createdAt }).from(gscCoverageSnapshots).where(eq20(gscCoverageSnapshots.projectId, project.id)).orderBy(desc10(gscCoverageSnapshots.createdAt)).limit(1).get();
20410
+ const latestSnapshot = app.db.select({ createdAt: gscCoverageSnapshots.createdAt }).from(gscCoverageSnapshots).where(eq21(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.createdAt)).limit(1).get();
20136
20411
  const lastSyncedAt = latestSnapshot?.createdAt ?? null;
20137
20412
  const formatRow = (r) => ({
20138
20413
  id: r.id,
@@ -20183,7 +20458,7 @@ async function googleRoutes(app, opts) {
20183
20458
  const project = resolveProject(app.db, request.params.name);
20184
20459
  const parsed = parseInt(request.query.limit ?? "90", 10);
20185
20460
  const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
20186
- const rows = app.db.select().from(gscCoverageSnapshots).where(eq20(gscCoverageSnapshots.projectId, project.id)).orderBy(desc10(gscCoverageSnapshots.date)).limit(limit).all();
20461
+ const rows = app.db.select().from(gscCoverageSnapshots).where(eq21(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.date)).limit(limit).all();
20187
20462
  return rows.map((r) => ({
20188
20463
  date: r.date,
20189
20464
  indexed: r.indexed,
@@ -20252,7 +20527,7 @@ async function googleRoutes(app, opts) {
20252
20527
  if (opts.onInspectSitemapRequested) {
20253
20528
  opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl });
20254
20529
  }
20255
- const run = app.db.select().from(runs).where(eq20(runs.id, runId)).get();
20530
+ const run = app.db.select().from(runs).where(eq21(runs.id, runId)).get();
20256
20531
  return { sitemaps, primarySitemapUrl: sitemapUrl, run };
20257
20532
  });
20258
20533
  app.post("/projects/:name/google/gsc/inspect-sitemap", async (request) => {
@@ -20279,7 +20554,7 @@ async function googleRoutes(app, opts) {
20279
20554
  if (opts.onInspectSitemapRequested) {
20280
20555
  opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl: sitemapUrl ?? void 0 });
20281
20556
  }
20282
- const run = app.db.select().from(runs).where(eq20(runs.id, runId)).get();
20557
+ const run = app.db.select().from(runs).where(eq21(runs.id, runId)).get();
20283
20558
  return run;
20284
20559
  });
20285
20560
  app.put("/projects/:name/google/connections/:type/sitemap", async (request) => {
@@ -20326,7 +20601,7 @@ async function googleRoutes(app, opts) {
20326
20601
  const { accessToken } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
20327
20602
  let urlsToNotify = request.body?.urls ?? [];
20328
20603
  if (request.body?.allUnindexed) {
20329
- const allInspections = app.db.select().from(gscUrlInspections).where(eq20(gscUrlInspections.projectId, project.id)).orderBy(desc10(gscUrlInspections.inspectedAt)).all();
20604
+ const allInspections = app.db.select().from(gscUrlInspections).where(eq21(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
20330
20605
  const latestByUrl = /* @__PURE__ */ new Map();
20331
20606
  for (const row of allInspections) {
20332
20607
  if (!latestByUrl.has(row.url)) {
@@ -20437,7 +20712,7 @@ async function googleRoutes(app, opts) {
20437
20712
  };
20438
20713
  }
20439
20714
  function listSelectionResponse(projectId) {
20440
- const rows = app.db.select().from(gbpLocations).where(eq20(gbpLocations.projectId, projectId)).all();
20715
+ const rows = app.db.select().from(gbpLocations).where(eq21(gbpLocations.projectId, projectId)).all();
20441
20716
  const dtos = rows.map(rowToDto2);
20442
20717
  return {
20443
20718
  locations: dtos,
@@ -20446,15 +20721,15 @@ async function googleRoutes(app, opts) {
20446
20721
  };
20447
20722
  }
20448
20723
  function clearGbpProjectData(tx, projectId) {
20449
- tx.delete(gbpDailyMetrics).where(eq20(gbpDailyMetrics.projectId, projectId)).run();
20450
- tx.delete(gbpKeywordImpressions).where(eq20(gbpKeywordImpressions.projectId, projectId)).run();
20451
- tx.delete(gbpKeywordMonthly).where(eq20(gbpKeywordMonthly.projectId, projectId)).run();
20452
- tx.delete(gbpPlaceActions).where(eq20(gbpPlaceActions.projectId, projectId)).run();
20453
- tx.delete(gbpLodgingSnapshots).where(eq20(gbpLodgingSnapshots.projectId, projectId)).run();
20454
- tx.delete(gbpLocations).where(eq20(gbpLocations.projectId, projectId)).run();
20724
+ tx.delete(gbpDailyMetrics).where(eq21(gbpDailyMetrics.projectId, projectId)).run();
20725
+ tx.delete(gbpKeywordImpressions).where(eq21(gbpKeywordImpressions.projectId, projectId)).run();
20726
+ tx.delete(gbpKeywordMonthly).where(eq21(gbpKeywordMonthly.projectId, projectId)).run();
20727
+ tx.delete(gbpPlaceActions).where(eq21(gbpPlaceActions.projectId, projectId)).run();
20728
+ tx.delete(gbpLodgingSnapshots).where(eq21(gbpLodgingSnapshots.projectId, projectId)).run();
20729
+ tx.delete(gbpLocations).where(eq21(gbpLocations.projectId, projectId)).run();
20455
20730
  }
20456
20731
  function currentProjectAccount(projectId) {
20457
- const row = app.db.select({ accountName: gbpLocations.accountName }).from(gbpLocations).where(eq20(gbpLocations.projectId, projectId)).limit(1).get();
20732
+ const row = app.db.select({ accountName: gbpLocations.accountName }).from(gbpLocations).where(eq21(gbpLocations.projectId, projectId)).limit(1).get();
20458
20733
  return row?.accountName ?? null;
20459
20734
  }
20460
20735
  app.post("/projects/:name/gbp/locations/discover", async (request) => {
@@ -20524,7 +20799,7 @@ async function googleRoutes(app, opts) {
20524
20799
  app.db.transaction((tx) => {
20525
20800
  if (switching) clearGbpProjectData(tx, project.id);
20526
20801
  for (const remote of remoteLocations) {
20527
- const existing = tx.select().from(gbpLocations).where(and13(eq20(gbpLocations.projectId, project.id), eq20(gbpLocations.locationName, remote.name))).get();
20802
+ const existing = tx.select().from(gbpLocations).where(and14(eq21(gbpLocations.projectId, project.id), eq21(gbpLocations.locationName, remote.name))).get();
20528
20803
  if (existing) {
20529
20804
  tx.update(gbpLocations).set({
20530
20805
  accountName,
@@ -20535,7 +20810,7 @@ async function googleRoutes(app, opts) {
20535
20810
  placeId: remote.metadata?.placeId ?? null,
20536
20811
  mapsUri: remote.metadata?.mapsUri ?? null,
20537
20812
  updatedAt: now
20538
- }).where(eq20(gbpLocations.id, existing.id)).run();
20813
+ }).where(eq21(gbpLocations.id, existing.id)).run();
20539
20814
  } else {
20540
20815
  tx.insert(gbpLocations).values({
20541
20816
  id: crypto17.randomUUID(),
@@ -20618,11 +20893,11 @@ async function googleRoutes(app, opts) {
20618
20893
  throw validationError(parsed.error.issues[0]?.message ?? "Invalid selection request");
20619
20894
  }
20620
20895
  const { selected } = parsed.data;
20621
- const existing = app.db.select().from(gbpLocations).where(and13(eq20(gbpLocations.projectId, project.id), eq20(gbpLocations.locationName, locationName))).get();
20896
+ const existing = app.db.select().from(gbpLocations).where(and14(eq21(gbpLocations.projectId, project.id), eq21(gbpLocations.locationName, locationName))).get();
20622
20897
  if (!existing) throw notFound("GBP location", locationName);
20623
20898
  const now = (/* @__PURE__ */ new Date()).toISOString();
20624
20899
  app.db.transaction((tx) => {
20625
- tx.update(gbpLocations).set({ selected, updatedAt: now }).where(eq20(gbpLocations.id, existing.id)).run();
20900
+ tx.update(gbpLocations).set({ selected, updatedAt: now }).where(eq21(gbpLocations.id, existing.id)).run();
20626
20901
  writeAuditLog(tx, {
20627
20902
  projectId: project.id,
20628
20903
  actor: "api",
@@ -20631,7 +20906,7 @@ async function googleRoutes(app, opts) {
20631
20906
  entityId: locationName
20632
20907
  });
20633
20908
  });
20634
- const refreshed = app.db.select().from(gbpLocations).where(eq20(gbpLocations.id, existing.id)).get();
20909
+ const refreshed = app.db.select().from(gbpLocations).where(eq21(gbpLocations.id, existing.id)).get();
20635
20910
  return rowToDto2(refreshed);
20636
20911
  });
20637
20912
  app.delete("/projects/:name/gbp/connection", async (request, reply) => {
@@ -20675,10 +20950,10 @@ async function googleRoutes(app, opts) {
20675
20950
  });
20676
20951
  app.get("/projects/:name/gbp/metrics", async (request) => {
20677
20952
  const project = resolveProject(app.db, request.params.name);
20678
- const conditions = [eq20(gbpDailyMetrics.projectId, project.id)];
20679
- if (request.query.locationName) conditions.push(eq20(gbpDailyMetrics.locationName, request.query.locationName));
20680
- if (request.query.metric) conditions.push(eq20(gbpDailyMetrics.metric, request.query.metric));
20681
- const rows = app.db.select().from(gbpDailyMetrics).where(and13(...conditions)).orderBy(desc10(gbpDailyMetrics.date)).all();
20953
+ const conditions = [eq21(gbpDailyMetrics.projectId, project.id)];
20954
+ if (request.query.locationName) conditions.push(eq21(gbpDailyMetrics.locationName, request.query.locationName));
20955
+ if (request.query.metric) conditions.push(eq21(gbpDailyMetrics.metric, request.query.metric));
20956
+ const rows = app.db.select().from(gbpDailyMetrics).where(and14(...conditions)).orderBy(desc11(gbpDailyMetrics.date)).all();
20682
20957
  return {
20683
20958
  metrics: rows.map((r) => ({ locationName: r.locationName, date: r.date, metric: r.metric, value: r.value })),
20684
20959
  total: rows.length
@@ -20686,9 +20961,9 @@ async function googleRoutes(app, opts) {
20686
20961
  });
20687
20962
  app.get("/projects/:name/gbp/keywords", async (request) => {
20688
20963
  const project = resolveProject(app.db, request.params.name);
20689
- const conditions = [eq20(gbpKeywordImpressions.projectId, project.id)];
20690
- if (request.query.locationName) conditions.push(eq20(gbpKeywordImpressions.locationName, request.query.locationName));
20691
- const rows = app.db.select().from(gbpKeywordImpressions).where(and13(...conditions)).all();
20964
+ const conditions = [eq21(gbpKeywordImpressions.projectId, project.id)];
20965
+ if (request.query.locationName) conditions.push(eq21(gbpKeywordImpressions.locationName, request.query.locationName));
20966
+ const rows = app.db.select().from(gbpKeywordImpressions).where(and14(...conditions)).all();
20692
20967
  rows.sort((a, b) => (b.valueCount ?? -1) - (a.valueCount ?? -1));
20693
20968
  const thresholded = rows.filter((r) => r.valueThreshold !== null).length;
20694
20969
  return {
@@ -20706,9 +20981,9 @@ async function googleRoutes(app, opts) {
20706
20981
  });
20707
20982
  app.get("/projects/:name/gbp/place-actions", async (request) => {
20708
20983
  const project = resolveProject(app.db, request.params.name);
20709
- const conditions = [eq20(gbpPlaceActions.projectId, project.id)];
20710
- if (request.query.locationName) conditions.push(eq20(gbpPlaceActions.locationName, request.query.locationName));
20711
- const rows = app.db.select().from(gbpPlaceActions).where(and13(...conditions)).all();
20984
+ const conditions = [eq21(gbpPlaceActions.projectId, project.id)];
20985
+ if (request.query.locationName) conditions.push(eq21(gbpPlaceActions.locationName, request.query.locationName));
20986
+ const rows = app.db.select().from(gbpPlaceActions).where(and14(...conditions)).all();
20712
20987
  return {
20713
20988
  placeActions: rows.map((r) => ({
20714
20989
  locationName: r.locationName,
@@ -20723,9 +20998,9 @@ async function googleRoutes(app, opts) {
20723
20998
  });
20724
20999
  app.get("/projects/:name/gbp/lodging", async (request) => {
20725
21000
  const project = resolveProject(app.db, request.params.name);
20726
- const conditions = [eq20(gbpLodgingSnapshots.projectId, project.id)];
20727
- if (request.query.locationName) conditions.push(eq20(gbpLodgingSnapshots.locationName, request.query.locationName));
20728
- const rows = app.db.select().from(gbpLodgingSnapshots).where(and13(...conditions)).orderBy(desc10(gbpLodgingSnapshots.syncedAt)).all();
21001
+ const conditions = [eq21(gbpLodgingSnapshots.projectId, project.id)];
21002
+ if (request.query.locationName) conditions.push(eq21(gbpLodgingSnapshots.locationName, request.query.locationName));
21003
+ const rows = app.db.select().from(gbpLodgingSnapshots).where(and14(...conditions)).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
20729
21004
  const latestByLocation = /* @__PURE__ */ new Map();
20730
21005
  for (const row of rows) {
20731
21006
  if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
@@ -20740,9 +21015,9 @@ async function googleRoutes(app, opts) {
20740
21015
  });
20741
21016
  app.get("/projects/:name/gbp/places", async (request) => {
20742
21017
  const project = resolveProject(app.db, request.params.name);
20743
- const conditions = [eq20(gbpPlaceDetails.projectId, project.id)];
20744
- if (request.query.locationName) conditions.push(eq20(gbpPlaceDetails.locationName, request.query.locationName));
20745
- const rows = app.db.select().from(gbpPlaceDetails).where(and13(...conditions)).orderBy(desc10(gbpPlaceDetails.syncedAt)).all();
21018
+ const conditions = [eq21(gbpPlaceDetails.projectId, project.id)];
21019
+ if (request.query.locationName) conditions.push(eq21(gbpPlaceDetails.locationName, request.query.locationName));
21020
+ const rows = app.db.select().from(gbpPlaceDetails).where(and14(...conditions)).orderBy(desc11(gbpPlaceDetails.syncedAt)).all();
20746
21021
  const latestByLocation = /* @__PURE__ */ new Map();
20747
21022
  for (const row of rows) {
20748
21023
  if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
@@ -20761,7 +21036,7 @@ async function googleRoutes(app, opts) {
20761
21036
  app.get("/projects/:name/gbp/summary", async (request) => {
20762
21037
  const project = resolveProject(app.db, request.params.name);
20763
21038
  const locationName = request.query.locationName ?? null;
20764
- const locationNames = locationName ? [locationName] : app.db.select({ n: gbpLocations.locationName }).from(gbpLocations).where(and13(eq20(gbpLocations.projectId, project.id), eq20(gbpLocations.selected, true))).all().map((r) => r.n);
21039
+ const locationNames = locationName ? [locationName] : app.db.select({ n: gbpLocations.locationName }).from(gbpLocations).where(and14(eq21(gbpLocations.projectId, project.id), eq21(gbpLocations.selected, true))).all().map((r) => r.n);
20765
21040
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
20766
21041
  if (locationNames.length === 0) {
20767
21042
  return buildGbpSummary({
@@ -20774,10 +21049,10 @@ async function googleRoutes(app, opts) {
20774
21049
  lodging: []
20775
21050
  });
20776
21051
  }
20777
- const metricRows = app.db.select().from(gbpDailyMetrics).where(and13(eq20(gbpDailyMetrics.projectId, project.id), inArray9(gbpDailyMetrics.locationName, locationNames))).all();
20778
- const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and13(eq20(gbpKeywordImpressions.projectId, project.id), inArray9(gbpKeywordImpressions.locationName, locationNames))).all();
20779
- const placeActionRows = app.db.select().from(gbpPlaceActions).where(and13(eq20(gbpPlaceActions.projectId, project.id), inArray9(gbpPlaceActions.locationName, locationNames))).all();
20780
- const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and13(eq20(gbpLodgingSnapshots.projectId, project.id), inArray9(gbpLodgingSnapshots.locationName, locationNames))).orderBy(desc10(gbpLodgingSnapshots.syncedAt)).all();
21052
+ const metricRows = app.db.select().from(gbpDailyMetrics).where(and14(eq21(gbpDailyMetrics.projectId, project.id), inArray10(gbpDailyMetrics.locationName, locationNames))).all();
21053
+ const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and14(eq21(gbpKeywordImpressions.projectId, project.id), inArray10(gbpKeywordImpressions.locationName, locationNames))).all();
21054
+ const placeActionRows = app.db.select().from(gbpPlaceActions).where(and14(eq21(gbpPlaceActions.projectId, project.id), inArray10(gbpPlaceActions.locationName, locationNames))).all();
21055
+ const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and14(eq21(gbpLodgingSnapshots.projectId, project.id), inArray10(gbpLodgingSnapshots.locationName, locationNames))).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
20781
21056
  const latestLodgingByLocation = /* @__PURE__ */ new Map();
20782
21057
  for (const row of lodgingRows) {
20783
21058
  if (!latestLodgingByLocation.has(row.locationName)) {
@@ -20798,7 +21073,7 @@ async function googleRoutes(app, opts) {
20798
21073
 
20799
21074
  // ../api-routes/src/ads.ts
20800
21075
  import crypto18 from "crypto";
20801
- import { eq as eq21, and as and14, asc as asc2, gte as gte3, lte as lte2, inArray as inArray10 } from "drizzle-orm";
21076
+ import { eq as eq22, and as and15, asc as asc2, gte as gte3, lte as lte2, inArray as inArray11 } from "drizzle-orm";
20802
21077
  function statusDto(row) {
20803
21078
  if (!row) return { connected: false };
20804
21079
  return {
@@ -20847,7 +21122,7 @@ async function adsRoutes(app, opts) {
20847
21122
  createdAt: existingCfg?.createdAt ?? now,
20848
21123
  updatedAt: now
20849
21124
  });
20850
- const existingRow = app.db.select().from(adsConnections).where(eq21(adsConnections.projectId, project.id)).get();
21125
+ const existingRow = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
20851
21126
  app.db.transaction((tx) => {
20852
21127
  if (existingRow) {
20853
21128
  tx.update(adsConnections).set({
@@ -20857,7 +21132,7 @@ async function adsRoutes(app, opts) {
20857
21132
  timezone: account.timezone,
20858
21133
  status: account.status,
20859
21134
  updatedAt: now
20860
- }).where(eq21(adsConnections.id, existingRow.id)).run();
21135
+ }).where(eq22(adsConnections.id, existingRow.id)).run();
20861
21136
  } else {
20862
21137
  tx.insert(adsConnections).values({
20863
21138
  id: crypto18.randomUUID(),
@@ -20879,16 +21154,16 @@ async function adsRoutes(app, opts) {
20879
21154
  entityId: account.id
20880
21155
  }));
20881
21156
  });
20882
- const row = app.db.select().from(adsConnections).where(eq21(adsConnections.projectId, project.id)).get();
21157
+ const row = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
20883
21158
  return statusDto(row);
20884
21159
  }
20885
21160
  );
20886
21161
  app.delete("/projects/:name/ads/connection", async (request) => {
20887
21162
  const project = resolveProject(app.db, request.params.name);
20888
- const row = app.db.select().from(adsConnections).where(eq21(adsConnections.projectId, project.id)).get();
21163
+ const row = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
20889
21164
  if (row) {
20890
21165
  app.db.transaction((tx) => {
20891
- tx.delete(adsConnections).where(eq21(adsConnections.id, row.id)).run();
21166
+ tx.delete(adsConnections).where(eq22(adsConnections.id, row.id)).run();
20892
21167
  writeAuditLog(tx, auditFromRequest(request, {
20893
21168
  projectId: project.id,
20894
21169
  actor: "api",
@@ -20904,19 +21179,19 @@ async function adsRoutes(app, opts) {
20904
21179
  });
20905
21180
  app.get("/projects/:name/ads/status", async (request) => {
20906
21181
  const project = resolveProject(app.db, request.params.name);
20907
- const row = app.db.select().from(adsConnections).where(eq21(adsConnections.projectId, project.id)).get();
21182
+ const row = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
20908
21183
  return statusDto(row);
20909
21184
  });
20910
21185
  app.post("/projects/:name/ads/sync", async (request) => {
20911
21186
  const project = resolveProject(app.db, request.params.name);
20912
- const row = app.db.select().from(adsConnections).where(eq21(adsConnections.projectId, project.id)).get();
21187
+ const row = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
20913
21188
  if (!row) {
20914
21189
  throw validationError('No ads connection for this project. Run "canonry ads connect" first.');
20915
21190
  }
20916
- const inFlight = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and14(
20917
- eq21(runs.projectId, project.id),
20918
- eq21(runs.kind, RunKinds["ads-sync"]),
20919
- inArray10(runs.status, [RunStatuses.queued, RunStatuses.running])
21191
+ const inFlight = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and15(
21192
+ eq22(runs.projectId, project.id),
21193
+ eq22(runs.kind, RunKinds["ads-sync"]),
21194
+ inArray11(runs.status, [RunStatuses.queued, RunStatuses.running])
20920
21195
  )).get();
20921
21196
  if (inFlight) {
20922
21197
  const existing = { runId: inFlight.id, status: inFlight.status };
@@ -20937,9 +21212,9 @@ async function adsRoutes(app, opts) {
20937
21212
  });
20938
21213
  app.get("/projects/:name/ads/campaigns", async (request) => {
20939
21214
  const project = resolveProject(app.db, request.params.name);
20940
- const campaignRows = app.db.select().from(adsCampaigns).where(eq21(adsCampaigns.projectId, project.id)).all();
20941
- const groupRows = app.db.select().from(adsAdGroups).where(eq21(adsAdGroups.projectId, project.id)).all();
20942
- const adRows = app.db.select().from(adsAds).where(eq21(adsAds.projectId, project.id)).all();
21215
+ const campaignRows = app.db.select().from(adsCampaigns).where(eq22(adsCampaigns.projectId, project.id)).all();
21216
+ const groupRows = app.db.select().from(adsAdGroups).where(eq22(adsAdGroups.projectId, project.id)).all();
21217
+ const adRows = app.db.select().from(adsAds).where(eq22(adsAds.projectId, project.id)).all();
20943
21218
  const adsByGroup = /* @__PURE__ */ new Map();
20944
21219
  for (const ad of adRows) {
20945
21220
  const dto = {
@@ -20993,12 +21268,12 @@ async function adsRoutes(app, opts) {
20993
21268
  }
20994
21269
  parsedLevel = result.data;
20995
21270
  }
20996
- const conditions = [eq21(adsInsightsDaily.projectId, project.id)];
20997
- if (parsedLevel) conditions.push(eq21(adsInsightsDaily.level, parsedLevel));
20998
- if (entityId) conditions.push(eq21(adsInsightsDaily.entityId, entityId));
21271
+ const conditions = [eq22(adsInsightsDaily.projectId, project.id)];
21272
+ if (parsedLevel) conditions.push(eq22(adsInsightsDaily.level, parsedLevel));
21273
+ if (entityId) conditions.push(eq22(adsInsightsDaily.entityId, entityId));
20999
21274
  if (from) conditions.push(gte3(adsInsightsDaily.date, from));
21000
21275
  if (to) conditions.push(lte2(adsInsightsDaily.date, to));
21001
- const rows = app.db.select().from(adsInsightsDaily).where(and14(...conditions)).orderBy(asc2(adsInsightsDaily.date)).all();
21276
+ const rows = app.db.select().from(adsInsightsDaily).where(and15(...conditions)).orderBy(asc2(adsInsightsDaily.date)).all();
21002
21277
  const dtoRows = rows.map((row) => ({
21003
21278
  level: row.level,
21004
21279
  entityId: row.entityId,
@@ -21009,19 +21284,19 @@ async function adsRoutes(app, opts) {
21009
21284
  ctr: adsCtr(row.clicks, row.impressions),
21010
21285
  cpcMicros: adsCpcMicros(row.spendMicros, row.clicks)
21011
21286
  }));
21012
- const conn = app.db.select().from(adsConnections).where(eq21(adsConnections.projectId, project.id)).get();
21287
+ const conn = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
21013
21288
  const response = { rows: dtoRows, currencyCode: conn?.currencyCode ?? null };
21014
21289
  return response;
21015
21290
  });
21016
21291
  app.get("/projects/:name/ads/summary", async (request) => {
21017
21292
  const project = resolveProject(app.db, request.params.name);
21018
- const row = app.db.select().from(adsConnections).where(eq21(adsConnections.projectId, project.id)).get();
21019
- const campaignCount = app.db.select().from(adsCampaigns).where(eq21(adsCampaigns.projectId, project.id)).all().length;
21020
- const adGroupCount = app.db.select().from(adsAdGroups).where(eq21(adsAdGroups.projectId, project.id)).all().length;
21021
- const adCount = app.db.select().from(adsAds).where(eq21(adsAds.projectId, project.id)).all().length;
21022
- const campaignInsights = app.db.select().from(adsInsightsDaily).where(and14(
21023
- eq21(adsInsightsDaily.projectId, project.id),
21024
- eq21(adsInsightsDaily.level, "campaign")
21293
+ const row = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
21294
+ const campaignCount = app.db.select().from(adsCampaigns).where(eq22(adsCampaigns.projectId, project.id)).all().length;
21295
+ const adGroupCount = app.db.select().from(adsAdGroups).where(eq22(adsAdGroups.projectId, project.id)).all().length;
21296
+ const adCount = app.db.select().from(adsAds).where(eq22(adsAds.projectId, project.id)).all().length;
21297
+ const campaignInsights = app.db.select().from(adsInsightsDaily).where(and15(
21298
+ eq22(adsInsightsDaily.projectId, project.id),
21299
+ eq22(adsInsightsDaily.level, "campaign")
21025
21300
  )).all();
21026
21301
  let impressions = 0;
21027
21302
  let clicks = 0;
@@ -21058,7 +21333,7 @@ async function adsRoutes(app, opts) {
21058
21333
 
21059
21334
  // ../api-routes/src/bing.ts
21060
21335
  import crypto19 from "crypto";
21061
- import { eq as eq22, and as and15, desc as desc11 } from "drizzle-orm";
21336
+ import { eq as eq23, and as and16, desc as desc12 } from "drizzle-orm";
21062
21337
 
21063
21338
  // ../integration-bing/src/constants.ts
21064
21339
  var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
@@ -21432,7 +21707,7 @@ async function bingRoutes(app, opts) {
21432
21707
  const store = requireConnectionStore();
21433
21708
  const project = resolveProject(app.db, request.params.name);
21434
21709
  requireConnection(store, project.canonicalDomain);
21435
- const allInspections = app.db.select().from(bingUrlInspections).where(eq22(bingUrlInspections.projectId, project.id)).orderBy(desc11(bingUrlInspections.inspectedAt)).all();
21710
+ const allInspections = app.db.select().from(bingUrlInspections).where(eq23(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
21436
21711
  const latestByUrl = /* @__PURE__ */ new Map();
21437
21712
  const definitiveByUrl = /* @__PURE__ */ new Map();
21438
21713
  for (const row of allInspections) {
@@ -21521,7 +21796,7 @@ async function bingRoutes(app, opts) {
21521
21796
  const project = resolveProject(app.db, request.params.name);
21522
21797
  const parsed = parseInt(request.query.limit ?? "90", 10);
21523
21798
  const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
21524
- const rows = app.db.select().from(bingCoverageSnapshots).where(eq22(bingCoverageSnapshots.projectId, project.id)).orderBy(desc11(bingCoverageSnapshots.date)).limit(limit).all();
21799
+ const rows = app.db.select().from(bingCoverageSnapshots).where(eq23(bingCoverageSnapshots.projectId, project.id)).orderBy(desc12(bingCoverageSnapshots.date)).limit(limit).all();
21525
21800
  return rows.map((r) => ({
21526
21801
  date: r.date,
21527
21802
  indexed: r.indexed,
@@ -21533,8 +21808,8 @@ async function bingRoutes(app, opts) {
21533
21808
  requireConnectionStore();
21534
21809
  const project = resolveProject(app.db, request.params.name);
21535
21810
  const { url, limit } = request.query;
21536
- const whereClause = url ? and15(eq22(bingUrlInspections.projectId, project.id), eq22(bingUrlInspections.url, url)) : eq22(bingUrlInspections.projectId, project.id);
21537
- const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(desc11(bingUrlInspections.inspectedAt)).limit(Math.max(1, Math.min(parseInt(limit ?? "100", 10) || 100, 1e3))).all();
21811
+ const whereClause = url ? and16(eq23(bingUrlInspections.projectId, project.id), eq23(bingUrlInspections.url, url)) : eq23(bingUrlInspections.projectId, project.id);
21812
+ const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(desc12(bingUrlInspections.inspectedAt)).limit(Math.max(1, Math.min(parseInt(limit ?? "100", 10) || 100, 1e3))).all();
21538
21813
  return filtered.map((r) => ({
21539
21814
  id: r.id,
21540
21815
  url: r.url,
@@ -21623,7 +21898,7 @@ async function bingRoutes(app, opts) {
21623
21898
  anchorCount: result.AnchorCount ?? null,
21624
21899
  discoveryDate
21625
21900
  }).run();
21626
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq22(runs.id, runId)).run();
21901
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq23(runs.id, runId)).run();
21627
21902
  return {
21628
21903
  id,
21629
21904
  url,
@@ -21639,7 +21914,7 @@ async function bingRoutes(app, opts) {
21639
21914
  } catch (e) {
21640
21915
  const msg = e instanceof Error ? e.message : String(e);
21641
21916
  bingLog("error", "inspect-url.failed", { domain: project.canonicalDomain, url, error: msg });
21642
- app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq22(runs.id, runId)).run();
21917
+ app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq23(runs.id, runId)).run();
21643
21918
  throw e;
21644
21919
  }
21645
21920
  });
@@ -21666,7 +21941,7 @@ async function bingRoutes(app, opts) {
21666
21941
  } else {
21667
21942
  bingLog("warn", "inspect-sitemap.no-callback", { domain: project.canonicalDomain, runId });
21668
21943
  }
21669
- const run = app.db.select().from(runs).where(eq22(runs.id, runId)).get();
21944
+ const run = app.db.select().from(runs).where(eq23(runs.id, runId)).get();
21670
21945
  return run;
21671
21946
  });
21672
21947
  app.post("/projects/:name/bing/request-indexing", async (request) => {
@@ -21678,7 +21953,7 @@ async function bingRoutes(app, opts) {
21678
21953
  }
21679
21954
  let urlsToSubmit = request.body?.urls ?? [];
21680
21955
  if (request.body?.allUnindexed) {
21681
- const allInspections = app.db.select().from(bingUrlInspections).where(eq22(bingUrlInspections.projectId, project.id)).orderBy(desc11(bingUrlInspections.inspectedAt)).all();
21956
+ const allInspections = app.db.select().from(bingUrlInspections).where(eq23(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
21682
21957
  const latestByUrl = /* @__PURE__ */ new Map();
21683
21958
  for (const row of allInspections) {
21684
21959
  if (!latestByUrl.has(row.url)) {
@@ -21765,14 +22040,14 @@ async function bingRoutes(app, opts) {
21765
22040
  import fs from "fs";
21766
22041
  import path from "path";
21767
22042
  import os from "os";
21768
- import { eq as eq23, and as and16 } from "drizzle-orm";
22043
+ import { eq as eq24, and as and17 } from "drizzle-orm";
21769
22044
  function getScreenshotDir() {
21770
22045
  return path.join(os.homedir(), ".canonry", "screenshots");
21771
22046
  }
21772
22047
  async function cdpRoutes(app, opts) {
21773
22048
  app.get("/screenshots/:snapshotId", async (request, reply) => {
21774
22049
  const { snapshotId } = request.params;
21775
- const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath }).from(querySnapshots).where(eq23(querySnapshots.id, snapshotId)).get();
22050
+ const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath }).from(querySnapshots).where(eq24(querySnapshots.id, snapshotId)).get();
21776
22051
  if (!snapshot?.screenshotPath) {
21777
22052
  const err = notFound("Screenshot", snapshotId);
21778
22053
  return reply.code(err.statusCode).send(err.toJSON());
@@ -21839,7 +22114,7 @@ async function cdpRoutes(app, opts) {
21839
22114
  async (request, reply) => {
21840
22115
  const project = resolveProject(app.db, request.params.name);
21841
22116
  const { runId } = request.params;
21842
- const run = app.db.select().from(runs).where(and16(eq23(runs.id, runId), eq23(runs.projectId, project.id))).get();
22117
+ const run = app.db.select().from(runs).where(and17(eq24(runs.id, runId), eq24(runs.projectId, project.id))).get();
21843
22118
  if (!run) {
21844
22119
  const err = notFound("Run", runId);
21845
22120
  return reply.code(err.statusCode).send(err.toJSON());
@@ -21852,8 +22127,8 @@ async function cdpRoutes(app, opts) {
21852
22127
  citedDomains: querySnapshots.citedDomains,
21853
22128
  screenshotPath: querySnapshots.screenshotPath,
21854
22129
  rawResponse: querySnapshots.rawResponse
21855
- }).from(querySnapshots).where(eq23(querySnapshots.runId, runId)).all());
21856
- const queryRows = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq23(queries.projectId, project.id)).all();
22130
+ }).from(querySnapshots).where(eq24(querySnapshots.runId, runId)).all());
22131
+ const queryRows = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq24(queries.projectId, project.id)).all();
21857
22132
  const queryMap = new Map(queryRows.map((q) => [q.id, q.query]));
21858
22133
  const byQuery = /* @__PURE__ */ new Map();
21859
22134
  for (const snap of snapshots) {
@@ -21936,7 +22211,7 @@ async function cdpRoutes(app, opts) {
21936
22211
 
21937
22212
  // ../api-routes/src/ga.ts
21938
22213
  import crypto20 from "crypto";
21939
- import { eq as eq24, desc as desc12, and as and17, sql as sql9 } from "drizzle-orm";
22214
+ import { eq as eq25, desc as desc13, and as and18, sql as sql9 } from "drizzle-orm";
21940
22215
  function gaLog(level, action, ctx) {
21941
22216
  const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
21942
22217
  const stream = level === "error" ? process.stderr : process.stdout;
@@ -22143,10 +22418,10 @@ async function ga4Routes(app, opts) {
22143
22418
  if (!saConn && !oauthConn) {
22144
22419
  throw notFound("GA4 connection", project.name);
22145
22420
  }
22146
- app.db.delete(gaTrafficSnapshots).where(eq24(gaTrafficSnapshots.projectId, project.id)).run();
22147
- app.db.delete(gaTrafficSummaries).where(eq24(gaTrafficSummaries.projectId, project.id)).run();
22148
- app.db.delete(gaAiReferrals).where(eq24(gaAiReferrals.projectId, project.id)).run();
22149
- app.db.delete(gaSocialReferrals).where(eq24(gaSocialReferrals.projectId, project.id)).run();
22421
+ app.db.delete(gaTrafficSnapshots).where(eq25(gaTrafficSnapshots.projectId, project.id)).run();
22422
+ app.db.delete(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).run();
22423
+ app.db.delete(gaAiReferrals).where(eq25(gaAiReferrals.projectId, project.id)).run();
22424
+ app.db.delete(gaSocialReferrals).where(eq25(gaSocialReferrals.projectId, project.id)).run();
22150
22425
  const propertyId = saConn?.propertyId ?? oauthConn?.propertyId ?? null;
22151
22426
  opts.ga4CredentialStore?.deleteConnection(project.name);
22152
22427
  opts.googleConnectionStore?.deleteConnection(project.canonicalDomain, "ga4");
@@ -22167,7 +22442,7 @@ async function ga4Routes(app, opts) {
22167
22442
  if (!connected) {
22168
22443
  return { connected: false, propertyId: null, clientEmail: null, authMethod: null, lastSyncedAt: null };
22169
22444
  }
22170
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq24(gaTrafficSummaries.projectId, project.id)).orderBy(desc12(gaTrafficSummaries.syncedAt)).limit(1).get();
22445
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
22171
22446
  return {
22172
22447
  connected: true,
22173
22448
  propertyId: saConn?.propertyId ?? oauthConn?.propertyId ?? null,
@@ -22231,8 +22506,8 @@ async function ga4Routes(app, opts) {
22231
22506
  app.db.transaction((tx) => {
22232
22507
  if (syncTraffic) {
22233
22508
  tx.delete(gaTrafficSnapshots).where(
22234
- and17(
22235
- eq24(gaTrafficSnapshots.projectId, project.id),
22509
+ and18(
22510
+ eq25(gaTrafficSnapshots.projectId, project.id),
22236
22511
  sql9`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
22237
22512
  sql9`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
22238
22513
  )
@@ -22255,8 +22530,8 @@ async function ga4Routes(app, opts) {
22255
22530
  }
22256
22531
  if (syncAi) {
22257
22532
  tx.delete(gaAiReferrals).where(
22258
- and17(
22259
- eq24(gaAiReferrals.projectId, project.id),
22533
+ and18(
22534
+ eq25(gaAiReferrals.projectId, project.id),
22260
22535
  sql9`${gaAiReferrals.date} >= ${summary.periodStart}`,
22261
22536
  sql9`${gaAiReferrals.date} <= ${summary.periodEnd}`
22262
22537
  )
@@ -22281,8 +22556,8 @@ async function ga4Routes(app, opts) {
22281
22556
  }
22282
22557
  if (syncSocial) {
22283
22558
  tx.delete(gaSocialReferrals).where(
22284
- and17(
22285
- eq24(gaSocialReferrals.projectId, project.id),
22559
+ and18(
22560
+ eq25(gaSocialReferrals.projectId, project.id),
22286
22561
  sql9`${gaSocialReferrals.date} >= ${summary.periodStart}`,
22287
22562
  sql9`${gaSocialReferrals.date} <= ${summary.periodEnd}`
22288
22563
  )
@@ -22303,7 +22578,7 @@ async function ga4Routes(app, opts) {
22303
22578
  }
22304
22579
  }
22305
22580
  if (syncSummary) {
22306
- tx.delete(gaTrafficSummaries).where(eq24(gaTrafficSummaries.projectId, project.id)).run();
22581
+ tx.delete(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).run();
22307
22582
  tx.insert(gaTrafficSummaries).values({
22308
22583
  id: crypto20.randomUUID(),
22309
22584
  projectId: project.id,
@@ -22315,7 +22590,7 @@ async function ga4Routes(app, opts) {
22315
22590
  syncedAt: now,
22316
22591
  syncRunId: runId
22317
22592
  }).run();
22318
- tx.delete(gaTrafficWindowSummaries).where(eq24(gaTrafficWindowSummaries.projectId, project.id)).run();
22593
+ tx.delete(gaTrafficWindowSummaries).where(eq25(gaTrafficWindowSummaries.projectId, project.id)).run();
22319
22594
  for (const ws of windowSummaries) {
22320
22595
  tx.insert(gaTrafficWindowSummaries).values({
22321
22596
  id: crypto20.randomUUID(),
@@ -22333,7 +22608,7 @@ async function ga4Routes(app, opts) {
22333
22608
  }
22334
22609
  }
22335
22610
  });
22336
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq24(runs.id, runId)).run();
22611
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq25(runs.id, runId)).run();
22337
22612
  const syncedComponents = only ? [
22338
22613
  ...syncTraffic ? ["traffic"] : [],
22339
22614
  ...syncSummary ? ["summary"] : [],
@@ -22362,7 +22637,7 @@ async function ga4Routes(app, opts) {
22362
22637
  } catch (e) {
22363
22638
  const msg = e instanceof Error ? e.message : String(e);
22364
22639
  gaLog("error", "sync.fetch-failed", { projectId: project.id, runId, error: msg });
22365
- app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq24(runs.id, runId)).run();
22640
+ app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq25(runs.id, runId)).run();
22366
22641
  throw e;
22367
22642
  }
22368
22643
  });
@@ -22373,11 +22648,11 @@ async function ga4Routes(app, opts) {
22373
22648
  const window = parseWindow(request.query.window);
22374
22649
  const cutoff = windowCutoff(window);
22375
22650
  const cutoffDate = cutoff?.slice(0, 10) ?? null;
22376
- const snapshotConditions = [eq24(gaTrafficSnapshots.projectId, project.id)];
22651
+ const snapshotConditions = [eq25(gaTrafficSnapshots.projectId, project.id)];
22377
22652
  if (cutoffDate) snapshotConditions.push(sql9`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
22378
- const aiConditions = [eq24(gaAiReferrals.projectId, project.id)];
22653
+ const aiConditions = [eq25(gaAiReferrals.projectId, project.id)];
22379
22654
  if (cutoffDate) aiConditions.push(sql9`${gaAiReferrals.date} >= ${cutoffDate}`);
22380
- const socialConditions = [eq24(gaSocialReferrals.projectId, project.id)];
22655
+ const socialConditions = [eq25(gaSocialReferrals.projectId, project.id)];
22381
22656
  if (cutoffDate) socialConditions.push(sql9`${gaSocialReferrals.date} >= ${cutoffDate}`);
22382
22657
  const windowSummaryRow = cutoffDate ? app.db.select({
22383
22658
  totalSessions: gaTrafficWindowSummaries.totalSessions,
@@ -22385,42 +22660,42 @@ async function ga4Routes(app, opts) {
22385
22660
  totalDirectSessions: gaTrafficWindowSummaries.totalDirectSessions,
22386
22661
  totalUsers: gaTrafficWindowSummaries.totalUsers
22387
22662
  }).from(gaTrafficWindowSummaries).where(
22388
- and17(
22389
- eq24(gaTrafficWindowSummaries.projectId, project.id),
22390
- eq24(gaTrafficWindowSummaries.windowKey, window)
22663
+ and18(
22664
+ eq25(gaTrafficWindowSummaries.projectId, project.id),
22665
+ eq25(gaTrafficWindowSummaries.windowKey, window)
22391
22666
  )
22392
22667
  ).get() : null;
22393
22668
  const snapshotTotalsRow = cutoffDate && !windowSummaryRow ? app.db.select({
22394
22669
  totalSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
22395
22670
  totalOrganicSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
22396
22671
  totalUsers: sql9`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
22397
- }).from(gaTrafficSnapshots).where(and17(...snapshotConditions)).get() : null;
22672
+ }).from(gaTrafficSnapshots).where(and18(...snapshotConditions)).get() : null;
22398
22673
  const summaryRow = cutoffDate ? windowSummaryRow ?? snapshotTotalsRow : app.db.select({
22399
22674
  totalSessions: gaTrafficSummaries.totalSessions,
22400
22675
  totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
22401
22676
  totalUsers: gaTrafficSummaries.totalUsers
22402
- }).from(gaTrafficSummaries).where(eq24(gaTrafficSummaries.projectId, project.id)).get();
22677
+ }).from(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).get();
22403
22678
  const directTotalRow = windowSummaryRow ? { totalDirectSessions: windowSummaryRow.totalDirectSessions } : app.db.select({
22404
22679
  totalDirectSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
22405
- }).from(gaTrafficSnapshots).where(and17(...snapshotConditions)).get();
22680
+ }).from(gaTrafficSnapshots).where(and18(...snapshotConditions)).get();
22406
22681
  const summaryMeta = app.db.select({
22407
22682
  periodStart: gaTrafficSummaries.periodStart,
22408
22683
  periodEnd: gaTrafficSummaries.periodEnd
22409
- }).from(gaTrafficSummaries).where(eq24(gaTrafficSummaries.projectId, project.id)).get();
22684
+ }).from(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).get();
22410
22685
  const rows = app.db.select({
22411
22686
  landingPage: sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
22412
22687
  sessions: sql9`SUM(${gaTrafficSnapshots.sessions})`,
22413
22688
  organicSessions: sql9`SUM(${gaTrafficSnapshots.organicSessions})`,
22414
22689
  directSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`,
22415
22690
  users: sql9`SUM(${gaTrafficSnapshots.users})`
22416
- }).from(gaTrafficSnapshots).where(and17(...snapshotConditions)).groupBy(sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql9`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
22691
+ }).from(gaTrafficSnapshots).where(and18(...snapshotConditions)).groupBy(sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql9`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
22417
22692
  const aiReferralRows = app.db.select({
22418
22693
  source: gaAiReferrals.source,
22419
22694
  medium: gaAiReferrals.medium,
22420
22695
  sourceDimension: gaAiReferrals.sourceDimension,
22421
22696
  sessions: sql9`SUM(${gaAiReferrals.sessions})`,
22422
22697
  users: sql9`SUM(${gaAiReferrals.users})`
22423
- }).from(gaAiReferrals).where(and17(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).all();
22698
+ }).from(gaAiReferrals).where(and18(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).all();
22424
22699
  const aiReferralLandingPageRows = app.db.select({
22425
22700
  source: gaAiReferrals.source,
22426
22701
  medium: gaAiReferrals.medium,
@@ -22428,7 +22703,7 @@ async function ga4Routes(app, opts) {
22428
22703
  landingPage: sql9`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
22429
22704
  sessions: sql9`SUM(${gaAiReferrals.sessions})`,
22430
22705
  users: sql9`SUM(${gaAiReferrals.users})`
22431
- }).from(gaAiReferrals).where(and17(...aiConditions)).groupBy(
22706
+ }).from(gaAiReferrals).where(and18(...aiConditions)).groupBy(
22432
22707
  gaAiReferrals.source,
22433
22708
  gaAiReferrals.medium,
22434
22709
  gaAiReferrals.sourceDimension,
@@ -22465,7 +22740,7 @@ async function ga4Routes(app, opts) {
22465
22740
  channelGroup: gaAiReferrals.channelGroup,
22466
22741
  sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)`,
22467
22742
  users: sql9`COALESCE(SUM(${gaAiReferrals.users}), 0)`
22468
- }).from(gaAiReferrals).where(and17(...aiConditions, eq24(gaAiReferrals.sourceDimension, "session"))).groupBy(gaAiReferrals.channelGroup).all();
22743
+ }).from(gaAiReferrals).where(and18(...aiConditions, eq25(gaAiReferrals.sourceDimension, "session"))).groupBy(gaAiReferrals.channelGroup).all();
22469
22744
  const aiSessionsByChannelGroup = /* @__PURE__ */ new Map();
22470
22745
  let aiBySessionUsers = 0;
22471
22746
  for (const row of aiBySessionRows) {
@@ -22479,12 +22754,12 @@ async function ga4Routes(app, opts) {
22479
22754
  channelGroup: gaSocialReferrals.channelGroup,
22480
22755
  sessions: sql9`SUM(${gaSocialReferrals.sessions})`,
22481
22756
  users: sql9`SUM(${gaSocialReferrals.users})`
22482
- }).from(gaSocialReferrals).where(and17(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql9`SUM(${gaSocialReferrals.sessions}) DESC`).all();
22757
+ }).from(gaSocialReferrals).where(and18(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql9`SUM(${gaSocialReferrals.sessions}) DESC`).all();
22483
22758
  const socialTotals = app.db.select({
22484
22759
  sessions: sql9`SUM(${gaSocialReferrals.sessions})`,
22485
22760
  users: sql9`SUM(${gaSocialReferrals.users})`
22486
- }).from(gaSocialReferrals).where(and17(...socialConditions)).get();
22487
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq24(gaTrafficSummaries.projectId, project.id)).orderBy(desc12(gaTrafficSummaries.syncedAt)).limit(1).get();
22761
+ }).from(gaSocialReferrals).where(and18(...socialConditions)).get();
22762
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
22488
22763
  const total = summaryRow?.totalSessions ?? 0;
22489
22764
  const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
22490
22765
  const totalOrganicSessions = summaryRow?.totalOrganicSessions ?? 0;
@@ -22564,7 +22839,7 @@ async function ga4Routes(app, opts) {
22564
22839
  const project = resolveProject(app.db, request.params.name);
22565
22840
  requireGa4Connection(opts, project.name, project.canonicalDomain);
22566
22841
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
22567
- const conditions = [eq24(gaAiReferrals.projectId, project.id)];
22842
+ const conditions = [eq25(gaAiReferrals.projectId, project.id)];
22568
22843
  if (cutoffDate) conditions.push(sql9`${gaAiReferrals.date} >= ${cutoffDate}`);
22569
22844
  const rows = app.db.select({
22570
22845
  date: gaAiReferrals.date,
@@ -22574,7 +22849,7 @@ async function ga4Routes(app, opts) {
22574
22849
  sourceDimension: gaAiReferrals.sourceDimension,
22575
22850
  sessions: sql9`SUM(${gaAiReferrals.sessions})`,
22576
22851
  users: sql9`SUM(${gaAiReferrals.users})`
22577
- }).from(gaAiReferrals).where(and17(...conditions)).groupBy(
22852
+ }).from(gaAiReferrals).where(and18(...conditions)).groupBy(
22578
22853
  gaAiReferrals.date,
22579
22854
  gaAiReferrals.source,
22580
22855
  gaAiReferrals.medium,
@@ -22587,7 +22862,7 @@ async function ga4Routes(app, opts) {
22587
22862
  const project = resolveProject(app.db, request.params.name);
22588
22863
  requireGa4Connection(opts, project.name, project.canonicalDomain);
22589
22864
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
22590
- const conditions = [eq24(gaSocialReferrals.projectId, project.id)];
22865
+ const conditions = [eq25(gaSocialReferrals.projectId, project.id)];
22591
22866
  if (cutoffDate) conditions.push(sql9`${gaSocialReferrals.date} >= ${cutoffDate}`);
22592
22867
  const rows = app.db.select({
22593
22868
  date: gaSocialReferrals.date,
@@ -22596,7 +22871,7 @@ async function ga4Routes(app, opts) {
22596
22871
  channelGroup: gaSocialReferrals.channelGroup,
22597
22872
  sessions: gaSocialReferrals.sessions,
22598
22873
  users: gaSocialReferrals.users
22599
- }).from(gaSocialReferrals).where(and17(...conditions)).orderBy(gaSocialReferrals.date).all();
22874
+ }).from(gaSocialReferrals).where(and18(...conditions)).orderBy(gaSocialReferrals.date).all();
22600
22875
  return rows;
22601
22876
  });
22602
22877
  app.get("/projects/:name/ga/social-referral-trend", async (request, _reply) => {
@@ -22609,8 +22884,8 @@ async function ga4Routes(app, opts) {
22609
22884
  d.setDate(d.getDate() - n);
22610
22885
  return fmt(d);
22611
22886
  };
22612
- const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and17(
22613
- eq24(gaSocialReferrals.projectId, project.id),
22887
+ const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and18(
22888
+ eq25(gaSocialReferrals.projectId, project.id),
22614
22889
  sql9`${gaSocialReferrals.date} >= ${from}`,
22615
22890
  sql9`${gaSocialReferrals.date} < ${to}`
22616
22891
  )).get();
@@ -22622,16 +22897,16 @@ async function ga4Routes(app, opts) {
22622
22897
  const sourceCurrent = app.db.select({
22623
22898
  source: gaSocialReferrals.source,
22624
22899
  sessions: sql9`SUM(${gaSocialReferrals.sessions})`
22625
- }).from(gaSocialReferrals).where(and17(
22626
- eq24(gaSocialReferrals.projectId, project.id),
22900
+ }).from(gaSocialReferrals).where(and18(
22901
+ eq25(gaSocialReferrals.projectId, project.id),
22627
22902
  sql9`${gaSocialReferrals.date} >= ${daysAgo(7)}`,
22628
22903
  sql9`${gaSocialReferrals.date} < ${fmt(today)}`
22629
22904
  )).groupBy(gaSocialReferrals.source).all();
22630
22905
  const sourcePrev = app.db.select({
22631
22906
  source: gaSocialReferrals.source,
22632
22907
  sessions: sql9`SUM(${gaSocialReferrals.sessions})`
22633
- }).from(gaSocialReferrals).where(and17(
22634
- eq24(gaSocialReferrals.projectId, project.id),
22908
+ }).from(gaSocialReferrals).where(and18(
22909
+ eq25(gaSocialReferrals.projectId, project.id),
22635
22910
  sql9`${gaSocialReferrals.date} >= ${daysAgo(14)}`,
22636
22911
  sql9`${gaSocialReferrals.date} < ${daysAgo(7)}`
22637
22912
  )).groupBy(gaSocialReferrals.source).all();
@@ -22672,16 +22947,16 @@ async function ga4Routes(app, opts) {
22672
22947
  return fmt(d);
22673
22948
  };
22674
22949
  const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
22675
- const sumTotal = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and17(eq24(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
22676
- const sumOrganic = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and17(eq24(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
22677
- const sumDirect = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and17(eq24(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
22678
- const sumAi = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and17(
22679
- eq24(gaAiReferrals.projectId, project.id),
22950
+ const sumTotal = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and18(eq25(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
22951
+ const sumOrganic = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and18(eq25(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
22952
+ const sumDirect = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and18(eq25(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
22953
+ const sumAi = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and18(
22954
+ eq25(gaAiReferrals.projectId, project.id),
22680
22955
  sql9`${gaAiReferrals.date} >= ${from}`,
22681
22956
  sql9`${gaAiReferrals.date} < ${to}`,
22682
- eq24(gaAiReferrals.sourceDimension, "session")
22957
+ eq25(gaAiReferrals.sourceDimension, "session")
22683
22958
  )).get();
22684
- const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and17(eq24(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${from}`, sql9`${gaSocialReferrals.date} < ${to}`)).get();
22959
+ const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and18(eq25(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${from}`, sql9`${gaSocialReferrals.date} < ${to}`)).get();
22685
22960
  const todayStr = fmt(today);
22686
22961
  const buildTrend = (sum) => {
22687
22962
  const c7 = sum(daysAgo(7), todayStr)?.sessions ?? 0;
@@ -22690,17 +22965,17 @@ async function ga4Routes(app, opts) {
22690
22965
  const p30 = sum(daysAgo(60), daysAgo(30))?.sessions ?? 0;
22691
22966
  return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: pct(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: pct(c30, p30) };
22692
22967
  };
22693
- const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and17(
22694
- eq24(gaAiReferrals.projectId, project.id),
22968
+ const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and18(
22969
+ eq25(gaAiReferrals.projectId, project.id),
22695
22970
  sql9`${gaAiReferrals.date} >= ${daysAgo(7)}`,
22696
22971
  sql9`${gaAiReferrals.date} < ${todayStr}`,
22697
- eq24(gaAiReferrals.sourceDimension, "session")
22972
+ eq25(gaAiReferrals.sourceDimension, "session")
22698
22973
  )).groupBy(gaAiReferrals.source).all();
22699
- const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and17(
22700
- eq24(gaAiReferrals.projectId, project.id),
22974
+ const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and18(
22975
+ eq25(gaAiReferrals.projectId, project.id),
22701
22976
  sql9`${gaAiReferrals.date} >= ${daysAgo(14)}`,
22702
22977
  sql9`${gaAiReferrals.date} < ${daysAgo(7)}`,
22703
- eq24(gaAiReferrals.sourceDimension, "session")
22978
+ eq25(gaAiReferrals.sourceDimension, "session")
22704
22979
  )).groupBy(gaAiReferrals.source).all();
22705
22980
  const findBiggestMover = (current, prev) => {
22706
22981
  const prevMap = new Map(prev.map((r) => [r.source, r.sessions]));
@@ -22716,8 +22991,8 @@ async function ga4Routes(app, opts) {
22716
22991
  }
22717
22992
  return mover;
22718
22993
  };
22719
- const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and17(eq24(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql9`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
22720
- const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and17(eq24(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql9`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
22994
+ const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and18(eq25(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql9`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
22995
+ const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and18(eq25(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql9`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
22721
22996
  return {
22722
22997
  total: buildTrend(sumTotal),
22723
22998
  organic: buildTrend(sumOrganic),
@@ -22732,14 +23007,14 @@ async function ga4Routes(app, opts) {
22732
23007
  const project = resolveProject(app.db, request.params.name);
22733
23008
  requireGa4Connection(opts, project.name, project.canonicalDomain);
22734
23009
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
22735
- const conditions = [eq24(gaTrafficSnapshots.projectId, project.id)];
23010
+ const conditions = [eq25(gaTrafficSnapshots.projectId, project.id)];
22736
23011
  if (cutoffDate) conditions.push(sql9`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
22737
23012
  const rows = app.db.select({
22738
23013
  date: gaTrafficSnapshots.date,
22739
23014
  sessions: sql9`SUM(${gaTrafficSnapshots.sessions})`,
22740
23015
  organicSessions: sql9`SUM(${gaTrafficSnapshots.organicSessions})`,
22741
23016
  users: sql9`SUM(${gaTrafficSnapshots.users})`
22742
- }).from(gaTrafficSnapshots).where(and17(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
23017
+ }).from(gaTrafficSnapshots).where(and18(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
22743
23018
  return rows.map((r) => ({
22744
23019
  date: r.date,
22745
23020
  sessions: r.sessions ?? 0,
@@ -22755,7 +23030,7 @@ async function ga4Routes(app, opts) {
22755
23030
  sessions: sql9`SUM(${gaTrafficSnapshots.sessions})`,
22756
23031
  organicSessions: sql9`SUM(${gaTrafficSnapshots.organicSessions})`,
22757
23032
  users: sql9`SUM(${gaTrafficSnapshots.users})`
22758
- }).from(gaTrafficSnapshots).where(eq24(gaTrafficSnapshots.projectId, project.id)).groupBy(sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql9`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
23033
+ }).from(gaTrafficSnapshots).where(eq25(gaTrafficSnapshots.projectId, project.id)).groupBy(sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql9`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
22759
23034
  return {
22760
23035
  pages: trafficPages.map((r) => ({
22761
23036
  landingPage: r.landingPage,
@@ -24403,7 +24678,7 @@ async function wordpressRoutes(app, opts) {
24403
24678
 
24404
24679
  // ../api-routes/src/backlinks.ts
24405
24680
  import crypto22 from "crypto";
24406
- import { and as and19, asc as asc3, desc as desc13, eq as eq25, sql as sql10 } from "drizzle-orm";
24681
+ import { and as and20, asc as asc3, desc as desc14, eq as eq26, sql as sql10 } from "drizzle-orm";
24407
24682
 
24408
24683
  // ../integration-commoncrawl/src/constants.ts
24409
24684
  import os2 from "os";
@@ -24806,7 +25081,7 @@ function pruneCachedRelease(release, opts = {}) {
24806
25081
  }
24807
25082
 
24808
25083
  // ../api-routes/src/backlinks-filter.ts
24809
- import { and as and18, ne as ne3, notLike } from "drizzle-orm";
25084
+ import { and as and19, ne as ne3, notLike } from "drizzle-orm";
24810
25085
  var BACKLINK_FILTER_PATTERNS = [
24811
25086
  "*.google.com",
24812
25087
  "*.googleusercontent.com",
@@ -24829,7 +25104,7 @@ function backlinkCrawlerExclusionClause() {
24829
25104
  conditions.push(ne3(backlinkDomains.linkingDomain, pattern));
24830
25105
  }
24831
25106
  }
24832
- const combined = and18(...conditions);
25107
+ const combined = and19(...conditions);
24833
25108
  if (!combined) throw new Error("BACKLINK_FILTER_PATTERNS is unexpectedly empty");
24834
25109
  return combined;
24835
25110
  }
@@ -24900,11 +25175,11 @@ function mapRunRow(row) {
24900
25175
  }
24901
25176
  function latestSummaryForProject(db, projectId, source, release) {
24902
25177
  const conditions = [
24903
- eq25(backlinkSummaries.projectId, projectId),
24904
- eq25(backlinkSummaries.source, source)
25178
+ eq26(backlinkSummaries.projectId, projectId),
25179
+ eq26(backlinkSummaries.source, source)
24905
25180
  ];
24906
- if (release) conditions.push(eq25(backlinkSummaries.release, release));
24907
- return db.select().from(backlinkSummaries).where(and19(...conditions)).orderBy(desc13(backlinkSummaries.queriedAt)).limit(1).get();
25181
+ if (release) conditions.push(eq26(backlinkSummaries.release, release));
25182
+ return db.select().from(backlinkSummaries).where(and20(...conditions)).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
24908
25183
  }
24909
25184
  function parseExcludeCrawlers(value) {
24910
25185
  if (!value) return false;
@@ -24912,12 +25187,12 @@ function parseExcludeCrawlers(value) {
24912
25187
  return lower === "1" || lower === "true" || lower === "yes";
24913
25188
  }
24914
25189
  function computeFilteredSummary(db, base) {
24915
- const baseDomainCondition = and19(
24916
- eq25(backlinkDomains.projectId, base.projectId),
24917
- eq25(backlinkDomains.source, base.source),
24918
- eq25(backlinkDomains.release, base.release)
25190
+ const baseDomainCondition = and20(
25191
+ eq26(backlinkDomains.projectId, base.projectId),
25192
+ eq26(backlinkDomains.source, base.source),
25193
+ eq26(backlinkDomains.release, base.release)
24919
25194
  );
24920
- const filteredCondition = and19(baseDomainCondition, backlinkCrawlerExclusionClause());
25195
+ const filteredCondition = and20(baseDomainCondition, backlinkCrawlerExclusionClause());
24921
25196
  const unfilteredAgg = db.select({
24922
25197
  count: sql10`count(*)`,
24923
25198
  total: sql10`coalesce(sum(${backlinkDomains.numHosts}), 0)`
@@ -24926,7 +25201,7 @@ function computeFilteredSummary(db, base) {
24926
25201
  count: sql10`count(*)`,
24927
25202
  total: sql10`coalesce(sum(${backlinkDomains.numHosts}), 0)`
24928
25203
  }).from(backlinkDomains).where(filteredCondition).get();
24929
- const top10Rows = db.select({ numHosts: backlinkDomains.numHosts }).from(backlinkDomains).where(filteredCondition).orderBy(desc13(backlinkDomains.numHosts)).limit(10).all();
25204
+ const top10Rows = db.select({ numHosts: backlinkDomains.numHosts }).from(backlinkDomains).where(filteredCondition).orderBy(desc14(backlinkDomains.numHosts)).limit(10).all();
24930
25205
  const totalLinkingDomains = Number(filteredAgg?.count ?? 0);
24931
25206
  const totalHosts = Number(filteredAgg?.total ?? 0);
24932
25207
  const unfilteredLinkingDomains = Number(unfilteredAgg?.count ?? 0);
@@ -24947,13 +25222,13 @@ function computeFilteredSummary(db, base) {
24947
25222
  };
24948
25223
  }
24949
25224
  function buildSourceAvailability(db, projectId, source, connected, excludeCrawlers) {
24950
- const summary = db.select().from(backlinkSummaries).where(and19(eq25(backlinkSummaries.projectId, projectId), eq25(backlinkSummaries.source, source))).orderBy(desc13(backlinkSummaries.queriedAt)).limit(1).get();
25225
+ const summary = db.select().from(backlinkSummaries).where(and20(eq26(backlinkSummaries.projectId, projectId), eq26(backlinkSummaries.source, source))).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
24951
25226
  let totalLinkingDomains = summary?.totalLinkingDomains ?? 0;
24952
25227
  if (summary && excludeCrawlers) {
24953
- const filtered = db.select({ count: sql10`count(*)` }).from(backlinkDomains).where(and19(
24954
- eq25(backlinkDomains.projectId, projectId),
24955
- eq25(backlinkDomains.source, source),
24956
- eq25(backlinkDomains.release, summary.release),
25228
+ const filtered = db.select({ count: sql10`count(*)` }).from(backlinkDomains).where(and20(
25229
+ eq26(backlinkDomains.projectId, projectId),
25230
+ eq26(backlinkDomains.source, source),
25231
+ eq26(backlinkDomains.release, summary.release),
24957
25232
  backlinkCrawlerExclusionClause()
24958
25233
  )).get();
24959
25234
  totalLinkingDomains = Number(filtered?.count ?? 0);
@@ -24968,7 +25243,7 @@ function buildSourceAvailability(db, projectId, source, connected, excludeCrawle
24968
25243
  };
24969
25244
  }
24970
25245
  function computeSourceAvailability(db, project, bingStore, excludeCrawlers) {
24971
- const ccReadySync = db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq25(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
25246
+ const ccReadySync = db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq26(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
24972
25247
  const ccConnected = project.autoExtractBacklinks === true && !!ccReadySync;
24973
25248
  const bingConnected = !!bingStore?.getConnection(project.canonicalDomain);
24974
25249
  const sources = [
@@ -25024,7 +25299,7 @@ async function backlinksRoutes(app, opts) {
25024
25299
  "@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
25025
25300
  );
25026
25301
  }
25027
- const existing = app.db.select().from(ccReleaseSyncs).where(eq25(ccReleaseSyncs.release, release)).get();
25302
+ const existing = app.db.select().from(ccReleaseSyncs).where(eq26(ccReleaseSyncs.release, release)).get();
25028
25303
  const now = (/* @__PURE__ */ new Date()).toISOString();
25029
25304
  if (existing) {
25030
25305
  if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
@@ -25035,9 +25310,9 @@ async function backlinksRoutes(app, opts) {
25035
25310
  phaseDetail: null,
25036
25311
  error: null,
25037
25312
  updatedAt: now
25038
- }).where(eq25(ccReleaseSyncs.id, existing.id)).run();
25313
+ }).where(eq26(ccReleaseSyncs.id, existing.id)).run();
25039
25314
  opts.onReleaseSyncRequested(existing.id, release);
25040
- const refreshed = app.db.select().from(ccReleaseSyncs).where(eq25(ccReleaseSyncs.id, existing.id)).get();
25315
+ const refreshed = app.db.select().from(ccReleaseSyncs).where(eq26(ccReleaseSyncs.id, existing.id)).get();
25041
25316
  return reply.status(200).send(mapSyncRow(refreshed));
25042
25317
  }
25043
25318
  const id = crypto22.randomUUID();
@@ -25049,15 +25324,15 @@ async function backlinksRoutes(app, opts) {
25049
25324
  updatedAt: now
25050
25325
  }).run();
25051
25326
  opts.onReleaseSyncRequested(id, release);
25052
- const inserted = app.db.select().from(ccReleaseSyncs).where(eq25(ccReleaseSyncs.id, id)).get();
25327
+ const inserted = app.db.select().from(ccReleaseSyncs).where(eq26(ccReleaseSyncs.id, id)).get();
25053
25328
  return reply.status(201).send(mapSyncRow(inserted));
25054
25329
  });
25055
25330
  app.get("/backlinks/syncs/latest", async (_request, reply) => {
25056
- const row = app.db.select().from(ccReleaseSyncs).orderBy(desc13(ccReleaseSyncs.updatedAt)).limit(1).get();
25331
+ const row = app.db.select().from(ccReleaseSyncs).orderBy(desc14(ccReleaseSyncs.updatedAt)).limit(1).get();
25057
25332
  return reply.send(row ? mapSyncRow(row) : null);
25058
25333
  });
25059
25334
  app.get("/backlinks/syncs", async (_request, reply) => {
25060
- const rows = app.db.select().from(ccReleaseSyncs).orderBy(desc13(ccReleaseSyncs.updatedAt)).all();
25335
+ const rows = app.db.select().from(ccReleaseSyncs).orderBy(desc14(ccReleaseSyncs.updatedAt)).all();
25061
25336
  return reply.send(rows.map(mapSyncRow));
25062
25337
  });
25063
25338
  app.get("/backlinks/releases", async (_request, reply) => {
@@ -25107,7 +25382,7 @@ async function backlinksRoutes(app, opts) {
25107
25382
  createdAt: now
25108
25383
  }).run();
25109
25384
  opts.onBacklinkExtractRequested(runId, project.id, release);
25110
- const run = app.db.select().from(runs).where(eq25(runs.id, runId)).get();
25385
+ const run = app.db.select().from(runs).where(eq26(runs.id, runId)).get();
25111
25386
  return reply.status(201).send(mapRunRow(run));
25112
25387
  });
25113
25388
  app.get(
@@ -25133,18 +25408,18 @@ async function backlinksRoutes(app, opts) {
25133
25408
  const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
25134
25409
  const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
25135
25410
  const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
25136
- const baseDomainCondition = and19(
25137
- eq25(backlinkDomains.projectId, project.id),
25138
- eq25(backlinkDomains.source, source),
25139
- eq25(backlinkDomains.release, targetRelease)
25411
+ const baseDomainCondition = and20(
25412
+ eq26(backlinkDomains.projectId, project.id),
25413
+ eq26(backlinkDomains.source, source),
25414
+ eq26(backlinkDomains.release, targetRelease)
25140
25415
  );
25141
- const domainCondition = excludeCrawlers ? and19(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
25416
+ const domainCondition = excludeCrawlers ? and20(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
25142
25417
  const totalRow = app.db.select({ count: sql10`count(*)` }).from(backlinkDomains).where(domainCondition).get();
25143
25418
  const rows = app.db.select({
25144
25419
  linkingDomain: backlinkDomains.linkingDomain,
25145
25420
  numHosts: backlinkDomains.numHosts,
25146
25421
  source: backlinkDomains.source
25147
- }).from(backlinkDomains).where(domainCondition).orderBy(desc13(backlinkDomains.numHosts)).limit(limit).offset(offset).all();
25422
+ }).from(backlinkDomains).where(domainCondition).orderBy(desc14(backlinkDomains.numHosts)).limit(limit).offset(offset).all();
25148
25423
  let summary = null;
25149
25424
  if (summaryRow) {
25150
25425
  summary = excludeCrawlers ? computeFilteredSummary(app.db, summaryRow) : mapSummaryRow(summaryRow);
@@ -25162,7 +25437,7 @@ async function backlinksRoutes(app, opts) {
25162
25437
  async (request, reply) => {
25163
25438
  const project = resolveProject(app.db, request.params.name);
25164
25439
  const source = parseSourceParam(request.query.source);
25165
- const rows = app.db.select().from(backlinkSummaries).where(and19(eq25(backlinkSummaries.projectId, project.id), eq25(backlinkSummaries.source, source))).orderBy(asc3(backlinkSummaries.queriedAt)).all();
25440
+ const rows = app.db.select().from(backlinkSummaries).where(and20(eq26(backlinkSummaries.projectId, project.id), eq26(backlinkSummaries.source, source))).orderBy(asc3(backlinkSummaries.queriedAt)).all();
25166
25441
  const response = rows.map((r) => ({
25167
25442
  release: r.release,
25168
25443
  totalLinkingDomains: r.totalLinkingDomains,
@@ -25209,7 +25484,7 @@ async function backlinksRoutes(app, opts) {
25209
25484
  createdAt: now
25210
25485
  }).run();
25211
25486
  opts.onBingBacklinkSyncRequested(runId, project.id);
25212
- const run = app.db.select().from(runs).where(eq25(runs.id, runId)).get();
25487
+ const run = app.db.select().from(runs).where(eq26(runs.id, runId)).get();
25213
25488
  return reply.status(201).send(mapRunRow(run));
25214
25489
  }
25215
25490
  );
@@ -25218,7 +25493,7 @@ async function backlinksRoutes(app, opts) {
25218
25493
  // ../api-routes/src/traffic.ts
25219
25494
  import crypto24 from "crypto";
25220
25495
  import { Agent as UndiciAgent } from "undici";
25221
- import { and as and20, desc as desc14, eq as eq26, gte as gte4, lte as lte3, sql as sql11 } from "drizzle-orm";
25496
+ import { and as and21, desc as desc15, eq as eq27, gte as gte4, lte as lte3, sql as sql11 } from "drizzle-orm";
25222
25497
 
25223
25498
  // ../integration-cloud-run/src/auth.ts
25224
25499
  import crypto23 from "crypto";
@@ -29035,8 +29310,8 @@ async function runBackfillTask(options) {
29035
29310
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
29036
29311
  try {
29037
29312
  app.db.transaction((tx) => {
29038
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq26(runs.id, runId)).run();
29039
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq26(trafficSources.id, sourceRow.id)).run();
29313
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq27(runs.id, runId)).run();
29314
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq27(trafficSources.id, sourceRow.id)).run();
29040
29315
  });
29041
29316
  } catch {
29042
29317
  }
@@ -29051,7 +29326,7 @@ async function runBackfillTask(options) {
29051
29326
  if (allEvents.length === 0) {
29052
29327
  const finishedAt2 = (/* @__PURE__ */ new Date()).toISOString();
29053
29328
  try {
29054
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(eq26(runs.id, runId)).run();
29329
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(eq27(runs.id, runId)).run();
29055
29330
  } catch {
29056
29331
  }
29057
29332
  return;
@@ -29073,29 +29348,29 @@ async function runBackfillTask(options) {
29073
29348
  try {
29074
29349
  app.db.transaction((tx) => {
29075
29350
  tx.delete(crawlerEventsHourly).where(
29076
- and20(
29077
- eq26(crawlerEventsHourly.sourceId, sourceRow.id),
29351
+ and21(
29352
+ eq27(crawlerEventsHourly.sourceId, sourceRow.id),
29078
29353
  gte4(crawlerEventsHourly.tsHour, windowStartIso),
29079
29354
  lte3(crawlerEventsHourly.tsHour, windowEndIso)
29080
29355
  )
29081
29356
  ).run();
29082
29357
  tx.delete(aiUserFetchEventsHourly).where(
29083
- and20(
29084
- eq26(aiUserFetchEventsHourly.sourceId, sourceRow.id),
29358
+ and21(
29359
+ eq27(aiUserFetchEventsHourly.sourceId, sourceRow.id),
29085
29360
  gte4(aiUserFetchEventsHourly.tsHour, windowStartIso),
29086
29361
  lte3(aiUserFetchEventsHourly.tsHour, windowEndIso)
29087
29362
  )
29088
29363
  ).run();
29089
29364
  tx.delete(aiReferralEventsHourly).where(
29090
- and20(
29091
- eq26(aiReferralEventsHourly.sourceId, sourceRow.id),
29365
+ and21(
29366
+ eq27(aiReferralEventsHourly.sourceId, sourceRow.id),
29092
29367
  gte4(aiReferralEventsHourly.tsHour, windowStartIso),
29093
29368
  lte3(aiReferralEventsHourly.tsHour, windowEndIso)
29094
29369
  )
29095
29370
  ).run();
29096
29371
  tx.delete(rawEventSamples).where(
29097
- and20(
29098
- eq26(rawEventSamples.sourceId, sourceRow.id),
29372
+ and21(
29373
+ eq27(rawEventSamples.sourceId, sourceRow.id),
29099
29374
  gte4(rawEventSamples.ts, windowStartIso),
29100
29375
  lte3(rawEventSamples.ts, windowEndIso)
29101
29376
  )
@@ -29184,8 +29459,8 @@ async function runBackfillTask(options) {
29184
29459
  lastError: null,
29185
29460
  lastEventIds: newRingBuffer,
29186
29461
  updatedAt: finishedAt
29187
- }).where(eq26(trafficSources.id, sourceRow.id)).run();
29188
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq26(runs.id, runId)).run();
29462
+ }).where(eq27(trafficSources.id, sourceRow.id)).run();
29463
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq27(runs.id, runId)).run();
29189
29464
  });
29190
29465
  } catch (e) {
29191
29466
  markFailed(`Backfill rollup write failed: ${e instanceof Error ? e.message : String(e)}`);
@@ -29267,7 +29542,7 @@ async function trafficRoutes(app, opts) {
29267
29542
  createdAt: existing?.createdAt ?? now,
29268
29543
  updatedAt: now
29269
29544
  });
29270
- const activeSource = app.db.select().from(trafficSources).where(eq26(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
29545
+ const activeSource = app.db.select().from(trafficSources).where(eq27(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
29271
29546
  const config = {
29272
29547
  gcpProjectId,
29273
29548
  serviceName: serviceName ?? null,
@@ -29283,8 +29558,8 @@ async function trafficRoutes(app, opts) {
29283
29558
  lastError: null,
29284
29559
  configJson: config,
29285
29560
  updatedAt: now
29286
- }).where(eq26(trafficSources.id, activeSource.id)).run();
29287
- sourceRow = app.db.select().from(trafficSources).where(eq26(trafficSources.id, activeSource.id)).get();
29561
+ }).where(eq27(trafficSources.id, activeSource.id)).run();
29562
+ sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, activeSource.id)).get();
29288
29563
  } else {
29289
29564
  const newId = crypto24.randomUUID();
29290
29565
  app.db.insert(trafficSources).values({
@@ -29301,7 +29576,7 @@ async function trafficRoutes(app, opts) {
29301
29576
  createdAt: now,
29302
29577
  updatedAt: now
29303
29578
  }).run();
29304
- sourceRow = app.db.select().from(trafficSources).where(eq26(trafficSources.id, newId)).get();
29579
+ sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, newId)).get();
29305
29580
  }
29306
29581
  writeAuditLog(app.db, {
29307
29582
  projectId: project.id,
@@ -29353,7 +29628,7 @@ async function trafficRoutes(app, opts) {
29353
29628
  createdAt: existing?.createdAt ?? now,
29354
29629
  updatedAt: now
29355
29630
  });
29356
- const activeSource = app.db.select().from(trafficSources).where(eq26(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
29631
+ const activeSource = app.db.select().from(trafficSources).where(eq27(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
29357
29632
  const config = { baseUrl, username };
29358
29633
  const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
29359
29634
  let sourceRow;
@@ -29364,8 +29639,8 @@ async function trafficRoutes(app, opts) {
29364
29639
  lastError: null,
29365
29640
  configJson: config,
29366
29641
  updatedAt: now
29367
- }).where(eq26(trafficSources.id, activeSource.id)).run();
29368
- sourceRow = app.db.select().from(trafficSources).where(eq26(trafficSources.id, activeSource.id)).get();
29642
+ }).where(eq27(trafficSources.id, activeSource.id)).run();
29643
+ sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, activeSource.id)).get();
29369
29644
  } else {
29370
29645
  const newId = crypto24.randomUUID();
29371
29646
  app.db.insert(trafficSources).values({
@@ -29382,7 +29657,7 @@ async function trafficRoutes(app, opts) {
29382
29657
  createdAt: now,
29383
29658
  updatedAt: now
29384
29659
  }).run();
29385
- sourceRow = app.db.select().from(trafficSources).where(eq26(trafficSources.id, newId)).get();
29660
+ sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, newId)).get();
29386
29661
  }
29387
29662
  writeAuditLog(app.db, {
29388
29663
  projectId: project.id,
@@ -29436,7 +29711,7 @@ async function trafficRoutes(app, opts) {
29436
29711
  createdAt: existing?.createdAt ?? now,
29437
29712
  updatedAt: now
29438
29713
  });
29439
- const activeSource = app.db.select().from(trafficSources).where(eq26(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.vercel && row.status !== TrafficSourceStatuses.archived);
29714
+ const activeSource = app.db.select().from(trafficSources).where(eq27(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.vercel && row.status !== TrafficSourceStatuses.archived);
29440
29715
  const config = { projectId, teamId, environment };
29441
29716
  const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
29442
29717
  const { sourceRow, scheduleCreated } = app.db.transaction((tx) => {
@@ -29448,8 +29723,8 @@ async function trafficRoutes(app, opts) {
29448
29723
  lastError: null,
29449
29724
  configJson: config,
29450
29725
  updatedAt: now
29451
- }).where(eq26(trafficSources.id, activeSource.id)).run();
29452
- row = tx.select().from(trafficSources).where(eq26(trafficSources.id, activeSource.id)).get();
29726
+ }).where(eq27(trafficSources.id, activeSource.id)).run();
29727
+ row = tx.select().from(trafficSources).where(eq27(trafficSources.id, activeSource.id)).get();
29453
29728
  } else {
29454
29729
  const newId = crypto24.randomUUID();
29455
29730
  tx.insert(trafficSources).values({
@@ -29474,12 +29749,12 @@ async function trafficRoutes(app, opts) {
29474
29749
  createdAt: now,
29475
29750
  updatedAt: now
29476
29751
  }).run();
29477
- row = tx.select().from(trafficSources).where(eq26(trafficSources.id, newId)).get();
29752
+ row = tx.select().from(trafficSources).where(eq27(trafficSources.id, newId)).get();
29478
29753
  }
29479
29754
  const existingSchedule = tx.select().from(schedules).where(
29480
- and20(
29481
- eq26(schedules.projectId, project.id),
29482
- eq26(schedules.kind, SchedulableRunKinds["traffic-sync"])
29755
+ and21(
29756
+ eq27(schedules.projectId, project.id),
29757
+ eq27(schedules.kind, SchedulableRunKinds["traffic-sync"])
29483
29758
  )
29484
29759
  ).get();
29485
29760
  let created = false;
@@ -29528,7 +29803,7 @@ async function trafficRoutes(app, opts) {
29528
29803
  });
29529
29804
  app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
29530
29805
  const project = resolveProject(app.db, request.params.name);
29531
- const sourceRow = app.db.select().from(trafficSources).where(eq26(trafficSources.id, request.params.id)).get();
29806
+ const sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, request.params.id)).get();
29532
29807
  if (!sourceRow || sourceRow.projectId !== project.id) {
29533
29808
  throw notFound("Traffic source", request.params.id);
29534
29809
  }
@@ -29554,8 +29829,8 @@ async function trafficRoutes(app, opts) {
29554
29829
  const markFailed = (msg, errorCode) => {
29555
29830
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
29556
29831
  app.db.transaction((tx) => {
29557
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq26(runs.id, runId)).run();
29558
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq26(trafficSources.id, sourceRow.id)).run();
29832
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq27(runs.id, runId)).run();
29833
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq27(trafficSources.id, sourceRow.id)).run();
29559
29834
  });
29560
29835
  try {
29561
29836
  opts.onTrafficSynced?.({
@@ -29636,7 +29911,7 @@ async function trafficRoutes(app, opts) {
29636
29911
  }
29637
29912
  const credential = credentialStore.getConnection(project.name);
29638
29913
  if (!credential) {
29639
- app.db.delete(runs).where(eq26(runs.id, runId)).run();
29914
+ app.db.delete(runs).where(eq27(runs.id, runId)).run();
29640
29915
  throw validationError(
29641
29916
  `No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
29642
29917
  );
@@ -29685,12 +29960,12 @@ async function trafficRoutes(app, opts) {
29685
29960
  auditAction = "traffic.vercel.synced";
29686
29961
  const credentialStore = opts.vercelTrafficCredentialStore;
29687
29962
  if (!credentialStore) {
29688
- app.db.delete(runs).where(eq26(runs.id, runId)).run();
29963
+ app.db.delete(runs).where(eq27(runs.id, runId)).run();
29689
29964
  throw validationError("Vercel traffic credential storage is not configured for this deployment");
29690
29965
  }
29691
29966
  const credential = credentialStore.getConnection(project.name);
29692
29967
  if (!credential) {
29693
- app.db.delete(runs).where(eq26(runs.id, runId)).run();
29968
+ app.db.delete(runs).where(eq27(runs.id, runId)).run();
29694
29969
  throw validationError(
29695
29970
  `No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
29696
29971
  );
@@ -29790,7 +30065,7 @@ async function trafficRoutes(app, opts) {
29790
30065
  let aiReferralHitsCount = 0;
29791
30066
  let unknownHitsCount = 0;
29792
30067
  app.db.transaction((tx) => {
29793
- const latestRow = tx.select().from(trafficSources).where(eq26(trafficSources.id, sourceRow.id)).get();
30068
+ const latestRow = tx.select().from(trafficSources).where(eq27(trafficSources.id, sourceRow.id)).get();
29794
30069
  const previousIds = latestRow.lastEventIds ?? [];
29795
30070
  const seenEventIds = new Set(previousIds);
29796
30071
  const dedupedEvents = seenEventIds.size === 0 ? allEvents : allEvents.filter((e) => !seenEventIds.has(e.eventId));
@@ -29958,8 +30233,8 @@ async function trafficRoutes(app, opts) {
29958
30233
  if (sourceRow.sourceType === TrafficSourceTypes.wordpress) {
29959
30234
  sourceUpdate.lastCursor = nextCursor ?? null;
29960
30235
  }
29961
- tx.update(trafficSources).set(sourceUpdate).where(eq26(trafficSources.id, sourceRow.id)).run();
29962
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq26(runs.id, runId)).run();
30236
+ tx.update(trafficSources).set(sourceUpdate).where(eq27(trafficSources.id, sourceRow.id)).run();
30237
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq27(runs.id, runId)).run();
29963
30238
  });
29964
30239
  writeAuditLog(app.db, {
29965
30240
  projectId: project.id,
@@ -30011,7 +30286,7 @@ async function trafficRoutes(app, opts) {
30011
30286
  });
30012
30287
  app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => {
30013
30288
  const project = resolveProject(app.db, request.params.name);
30014
- const sourceRow = app.db.select().from(trafficSources).where(eq26(trafficSources.id, request.params.id)).get();
30289
+ const sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, request.params.id)).get();
30015
30290
  if (!sourceRow || sourceRow.projectId !== project.id) {
30016
30291
  throw notFound("Traffic source", request.params.id);
30017
30292
  }
@@ -30196,36 +30471,36 @@ async function trafficRoutes(app, opts) {
30196
30471
  });
30197
30472
  function buildSourceDetail(projectId, row, since) {
30198
30473
  const crawlerTotals = app.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
30199
- and20(
30200
- eq26(crawlerEventsHourly.sourceId, row.id),
30474
+ and21(
30475
+ eq27(crawlerEventsHourly.sourceId, row.id),
30201
30476
  gte4(crawlerEventsHourly.tsHour, since)
30202
30477
  )
30203
30478
  ).get();
30204
30479
  const aiUserFetchTotals = app.db.select({ total: sql11`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
30205
- and20(
30206
- eq26(aiUserFetchEventsHourly.sourceId, row.id),
30480
+ and21(
30481
+ eq27(aiUserFetchEventsHourly.sourceId, row.id),
30207
30482
  gte4(aiUserFetchEventsHourly.tsHour, since)
30208
30483
  )
30209
30484
  ).get();
30210
30485
  const aiTotals = app.db.select({ total: sql11`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
30211
- and20(
30212
- eq26(aiReferralEventsHourly.sourceId, row.id),
30486
+ and21(
30487
+ eq27(aiReferralEventsHourly.sourceId, row.id),
30213
30488
  gte4(aiReferralEventsHourly.tsHour, since)
30214
30489
  )
30215
30490
  ).get();
30216
30491
  const sampleTotals = app.db.select({ total: sql11`COUNT(*)` }).from(rawEventSamples).where(
30217
- and20(
30218
- eq26(rawEventSamples.sourceId, row.id),
30492
+ and21(
30493
+ eq27(rawEventSamples.sourceId, row.id),
30219
30494
  gte4(rawEventSamples.ts, since)
30220
30495
  )
30221
30496
  ).get();
30222
30497
  const latestRun = app.db.select().from(runs).where(
30223
- and20(
30224
- eq26(runs.projectId, projectId),
30225
- eq26(runs.kind, RunKinds["traffic-sync"]),
30226
- eq26(runs.sourceId, row.id)
30498
+ and21(
30499
+ eq27(runs.projectId, projectId),
30500
+ eq27(runs.kind, RunKinds["traffic-sync"]),
30501
+ eq27(runs.sourceId, row.id)
30227
30502
  )
30228
- ).orderBy(desc14(runs.startedAt)).limit(1).get();
30503
+ ).orderBy(desc15(runs.startedAt)).limit(1).get();
30229
30504
  return {
30230
30505
  ...rowToDto(row),
30231
30506
  totals24h: {
@@ -30251,7 +30526,7 @@ async function trafficRoutes(app, opts) {
30251
30526
  "`advanceToNow` must be `true`. There is no implicit reset."
30252
30527
  );
30253
30528
  }
30254
- const sourceRow = app.db.select().from(trafficSources).where(and20(eq26(trafficSources.projectId, project.id), eq26(trafficSources.id, request.params.id))).get();
30529
+ const sourceRow = app.db.select().from(trafficSources).where(and21(eq27(trafficSources.projectId, project.id), eq27(trafficSources.id, request.params.id))).get();
30255
30530
  if (!sourceRow) {
30256
30531
  throw notFound("traffic source", request.params.id);
30257
30532
  }
@@ -30268,7 +30543,7 @@ async function trafficRoutes(app, opts) {
30268
30543
  status: TrafficSourceStatuses.connected,
30269
30544
  lastError: null,
30270
30545
  updatedAt: now
30271
- }).where(eq26(trafficSources.id, sourceRow.id)).run();
30546
+ }).where(eq27(trafficSources.id, sourceRow.id)).run();
30272
30547
  writeAuditLog(tx, auditFromRequest(request, {
30273
30548
  projectId: project.id,
30274
30549
  actor: "api",
@@ -30276,20 +30551,20 @@ async function trafficRoutes(app, opts) {
30276
30551
  entityType: "traffic_source",
30277
30552
  entityId: sourceRow.id
30278
30553
  }));
30279
- updatedRow = tx.select().from(trafficSources).where(eq26(trafficSources.id, sourceRow.id)).get();
30554
+ updatedRow = tx.select().from(trafficSources).where(eq27(trafficSources.id, sourceRow.id)).get();
30280
30555
  });
30281
30556
  return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
30282
30557
  });
30283
30558
  app.get("/projects/:name/traffic/sources", async (request) => {
30284
30559
  const project = resolveProject(app.db, request.params.name);
30285
- const rows = app.db.select().from(trafficSources).where(eq26(trafficSources.projectId, project.id)).orderBy(desc14(trafficSources.createdAt)).all();
30560
+ const rows = app.db.select().from(trafficSources).where(eq27(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
30286
30561
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
30287
30562
  const response = { sources };
30288
30563
  return response;
30289
30564
  });
30290
30565
  app.get("/projects/:name/traffic/status", async (request) => {
30291
30566
  const project = resolveProject(app.db, request.params.name);
30292
- const rows = app.db.select().from(trafficSources).where(eq26(trafficSources.projectId, project.id)).orderBy(desc14(trafficSources.createdAt)).all();
30567
+ const rows = app.db.select().from(trafficSources).where(eq27(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
30293
30568
  const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
30294
30569
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
30295
30570
  const response = { sources };
@@ -30299,7 +30574,7 @@ async function trafficRoutes(app, opts) {
30299
30574
  "/projects/:name/traffic/sources/:id",
30300
30575
  async (request) => {
30301
30576
  const project = resolveProject(app.db, request.params.name);
30302
- const row = app.db.select().from(trafficSources).where(eq26(trafficSources.id, request.params.id)).get();
30577
+ const row = app.db.select().from(trafficSources).where(eq27(trafficSources.id, request.params.id)).get();
30303
30578
  if (!row || row.projectId !== project.id) {
30304
30579
  throw notFound("Traffic source", request.params.id);
30305
30580
  }
@@ -30350,15 +30625,15 @@ async function trafficRoutes(app, opts) {
30350
30625
  let aiReferralTotal = 0;
30351
30626
  if (kind === "all" || kind === TrafficEventKinds.crawler) {
30352
30627
  const crawlerFilters = [
30353
- eq26(crawlerEventsHourly.projectId, project.id),
30628
+ eq27(crawlerEventsHourly.projectId, project.id),
30354
30629
  gte4(crawlerEventsHourly.tsHour, sinceIso),
30355
30630
  lte3(crawlerEventsHourly.tsHour, untilIso)
30356
30631
  ];
30357
- if (sourceIdParam) crawlerFilters.push(eq26(crawlerEventsHourly.sourceId, sourceIdParam));
30358
- const crawlerWhere = and20(...crawlerFilters);
30632
+ if (sourceIdParam) crawlerFilters.push(eq27(crawlerEventsHourly.sourceId, sourceIdParam));
30633
+ const crawlerWhere = and21(...crawlerFilters);
30359
30634
  const total = app.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(crawlerWhere).get();
30360
30635
  crawlerTotal = Number(total?.total ?? 0);
30361
- const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(desc14(crawlerEventsHourly.tsHour)).limit(limit).all();
30636
+ const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(desc15(crawlerEventsHourly.tsHour)).limit(limit).all();
30362
30637
  for (const r of rows) {
30363
30638
  events.push({
30364
30639
  kind: TrafficEventKinds.crawler,
@@ -30375,15 +30650,15 @@ async function trafficRoutes(app, opts) {
30375
30650
  }
30376
30651
  if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
30377
30652
  const userFetchFilters = [
30378
- eq26(aiUserFetchEventsHourly.projectId, project.id),
30653
+ eq27(aiUserFetchEventsHourly.projectId, project.id),
30379
30654
  gte4(aiUserFetchEventsHourly.tsHour, sinceIso),
30380
30655
  lte3(aiUserFetchEventsHourly.tsHour, untilIso)
30381
30656
  ];
30382
- if (sourceIdParam) userFetchFilters.push(eq26(aiUserFetchEventsHourly.sourceId, sourceIdParam));
30383
- const userFetchWhere = and20(...userFetchFilters);
30657
+ if (sourceIdParam) userFetchFilters.push(eq27(aiUserFetchEventsHourly.sourceId, sourceIdParam));
30658
+ const userFetchWhere = and21(...userFetchFilters);
30384
30659
  const total = app.db.select({ total: sql11`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
30385
30660
  aiUserFetchTotal = Number(total?.total ?? 0);
30386
- const rows = app.db.select().from(aiUserFetchEventsHourly).where(userFetchWhere).orderBy(desc14(aiUserFetchEventsHourly.tsHour)).limit(limit).all();
30661
+ const rows = app.db.select().from(aiUserFetchEventsHourly).where(userFetchWhere).orderBy(desc15(aiUserFetchEventsHourly.tsHour)).limit(limit).all();
30387
30662
  for (const r of rows) {
30388
30663
  events.push({
30389
30664
  kind: TrafficEventKinds["ai-user-fetch"],
@@ -30400,15 +30675,15 @@ async function trafficRoutes(app, opts) {
30400
30675
  }
30401
30676
  if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
30402
30677
  const aiFilters = [
30403
- eq26(aiReferralEventsHourly.projectId, project.id),
30678
+ eq27(aiReferralEventsHourly.projectId, project.id),
30404
30679
  gte4(aiReferralEventsHourly.tsHour, sinceIso),
30405
30680
  lte3(aiReferralEventsHourly.tsHour, untilIso)
30406
30681
  ];
30407
- if (sourceIdParam) aiFilters.push(eq26(aiReferralEventsHourly.sourceId, sourceIdParam));
30408
- const aiWhere = and20(...aiFilters);
30682
+ if (sourceIdParam) aiFilters.push(eq27(aiReferralEventsHourly.sourceId, sourceIdParam));
30683
+ const aiWhere = and21(...aiFilters);
30409
30684
  const total = app.db.select({ total: sql11`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(aiWhere).get();
30410
30685
  aiReferralTotal = Number(total?.total ?? 0);
30411
- const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(desc14(aiReferralEventsHourly.tsHour)).limit(limit).all();
30686
+ const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(desc15(aiReferralEventsHourly.tsHour)).limit(limit).all();
30412
30687
  for (const r of rows) {
30413
30688
  events.push({
30414
30689
  kind: TrafficEventKinds["ai-referral"],
@@ -30609,7 +30884,7 @@ function readInstalledManifest(skillDir) {
30609
30884
  var AGENT_CHECKS = [skillsInstalledCheck, skillsCurrentCheck];
30610
30885
 
30611
30886
  // ../api-routes/src/doctor/checks/backlinks.ts
30612
- import { and as and21, eq as eq27 } from "drizzle-orm";
30887
+ import { and as and22, eq as eq28 } from "drizzle-orm";
30613
30888
  function skippedNoProject() {
30614
30889
  return {
30615
30890
  status: CheckStatuses.skipped,
@@ -30625,8 +30900,8 @@ var BACKLINKS_CHECKS = [
30625
30900
  title: "Backlinks source connected",
30626
30901
  run: (ctx) => {
30627
30902
  if (!ctx.project) return skippedNoProject();
30628
- const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(eq27(projects.id, ctx.project.id)).get();
30629
- const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq27(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
30903
+ const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(eq28(projects.id, ctx.project.id)).get();
30904
+ const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq28(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
30630
30905
  const ccConnected = projectRow?.autoExtract === true && !!readySync;
30631
30906
  const bingConnected = !!ctx.bingConnectionStore?.getConnection(ctx.project.canonicalDomain);
30632
30907
  const connected = [];
@@ -30641,9 +30916,9 @@ var BACKLINKS_CHECKS = [
30641
30916
  details: { commoncrawl: ccConnected, bingWebmaster: bingConnected }
30642
30917
  };
30643
30918
  }
30644
- const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and21(
30645
- eq27(backlinkSummaries.projectId, ctx.project.id),
30646
- eq27(backlinkSummaries.source, BacklinkSources.commoncrawl)
30919
+ const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and22(
30920
+ eq28(backlinkSummaries.projectId, ctx.project.id),
30921
+ eq28(backlinkSummaries.source, BacklinkSources.commoncrawl)
30647
30922
  )).limit(1).get() : false;
30648
30923
  return {
30649
30924
  status: CheckStatuses.ok,
@@ -30803,7 +31078,7 @@ var BING_AUTH_CHECKS = [
30803
31078
  ];
30804
31079
 
30805
31080
  // ../api-routes/src/doctor/checks/content.ts
30806
- import { eq as eq28 } from "drizzle-orm";
31081
+ import { eq as eq29 } from "drizzle-orm";
30807
31082
  var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
30808
31083
  var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
30809
31084
  function skippedNoProject2() {
@@ -30816,7 +31091,7 @@ function skippedNoProject2() {
30816
31091
  }
30817
31092
  function loadProject(ctx) {
30818
31093
  if (!ctx.project) return null;
30819
- return ctx.db.select().from(projects).where(eq28(projects.id, ctx.project.id)).get() ?? null;
31094
+ return ctx.db.select().from(projects).where(eq29(projects.id, ctx.project.id)).get() ?? null;
30820
31095
  }
30821
31096
  function percent(value) {
30822
31097
  return Math.round(value * 100);
@@ -30908,7 +31183,7 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
30908
31183
  );
30909
31184
 
30910
31185
  // ../api-routes/src/doctor/checks/ads.ts
30911
- import { eq as eq29 } from "drizzle-orm";
31186
+ import { eq as eq30 } from "drizzle-orm";
30912
31187
  var RECENT_SYNC_WARN_DAYS = 7;
30913
31188
  var RECENT_SYNC_FAIL_DAYS = 30;
30914
31189
  var adsConnectionCheck = {
@@ -30925,7 +31200,7 @@ var adsConnectionCheck = {
30925
31200
  remediation: null
30926
31201
  };
30927
31202
  }
30928
- const row = ctx.db.select().from(adsConnections).where(eq29(adsConnections.projectId, ctx.project.id)).get();
31203
+ const row = ctx.db.select().from(adsConnections).where(eq30(adsConnections.projectId, ctx.project.id)).get();
30929
31204
  if (!row) {
30930
31205
  return {
30931
31206
  status: CheckStatuses.skipped,
@@ -30975,7 +31250,7 @@ var adsRecentSyncCheck = {
30975
31250
  remediation: null
30976
31251
  };
30977
31252
  }
30978
- const row = ctx.db.select().from(adsConnections).where(eq29(adsConnections.projectId, ctx.project.id)).get();
31253
+ const row = ctx.db.select().from(adsConnections).where(eq30(adsConnections.projectId, ctx.project.id)).get();
30979
31254
  if (!row) {
30980
31255
  return {
30981
31256
  status: CheckStatuses.skipped,
@@ -31166,7 +31441,7 @@ var ga4ConnectionCheck = {
31166
31441
  var GA_AUTH_CHECKS = [ga4ConnectionCheck];
31167
31442
 
31168
31443
  // ../api-routes/src/doctor/checks/gbp-auth.ts
31169
- import { and as and22, eq as eq30 } from "drizzle-orm";
31444
+ import { and as and23, eq as eq31 } from "drizzle-orm";
31170
31445
  var RECENT_SYNC_WARN_DAYS2 = 7;
31171
31446
  var RECENT_SYNC_FAIL_DAYS2 = 30;
31172
31447
  function skippedNoProject3() {
@@ -31399,7 +31674,7 @@ var recentSyncCheck = {
31399
31674
  title: "GBP recent sync",
31400
31675
  run: (ctx) => {
31401
31676
  if (!ctx.project) return skippedNoProject3();
31402
- const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and22(eq30(gbpLocations.projectId, ctx.project.id), eq30(gbpLocations.selected, true))).all();
31677
+ const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and23(eq31(gbpLocations.projectId, ctx.project.id), eq31(gbpLocations.selected, true))).all();
31403
31678
  if (selected.length === 0) {
31404
31679
  return {
31405
31680
  status: CheckStatuses.skipped,
@@ -31459,7 +31734,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
31459
31734
  );
31460
31735
 
31461
31736
  // ../api-routes/src/doctor/checks/places.ts
31462
- import { eq as eq31 } from "drizzle-orm";
31737
+ import { eq as eq32 } from "drizzle-orm";
31463
31738
  var apiKeyCheck = {
31464
31739
  id: "gbp.places.api-key",
31465
31740
  category: CheckCategories.auth,
@@ -31504,7 +31779,7 @@ var apiKeyCheck = {
31504
31779
  details: { tier: cfg.tier }
31505
31780
  };
31506
31781
  }
31507
- const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq31(gbpLocations.projectId, ctx.project.id)).all();
31782
+ const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq32(gbpLocations.projectId, ctx.project.id)).all();
31508
31783
  const selected = rows.filter((r) => r.selected);
31509
31784
  const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
31510
31785
  const details = {
@@ -31955,7 +32230,7 @@ var RUNTIME_STATE_CHECKS = [
31955
32230
  ];
31956
32231
 
31957
32232
  // ../api-routes/src/doctor/checks/traffic-source.ts
31958
- import { and as and23, eq as eq32, gte as gte5, ne as ne4, sql as sql12 } from "drizzle-orm";
32233
+ import { and as and24, eq as eq33, gte as gte5, ne as ne4, sql as sql12 } from "drizzle-orm";
31959
32234
  var RECENT_DATA_WARN_DAYS = 7;
31960
32235
  var RECENT_DATA_FAIL_DAYS = 30;
31961
32236
  function skippedNoProject5() {
@@ -31969,8 +32244,8 @@ function skippedNoProject5() {
31969
32244
  function loadProbes(ctx) {
31970
32245
  if (!ctx.project) return [];
31971
32246
  const rows = ctx.db.select().from(trafficSources).where(
31972
- and23(
31973
- eq32(trafficSources.projectId, ctx.project.id),
32247
+ and24(
32248
+ eq33(trafficSources.projectId, ctx.project.id),
31974
32249
  ne4(trafficSources.status, TrafficSourceStatuses.archived)
31975
32250
  )
31976
32251
  ).all();
@@ -32050,16 +32325,16 @@ var recentDataCheck = {
32050
32325
  const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
32051
32326
  const recentCrawlers = Number(
32052
32327
  ctx.db.select({ total: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
32053
- and23(
32054
- eq32(crawlerEventsHourly.projectId, ctx.project.id),
32328
+ and24(
32329
+ eq33(crawlerEventsHourly.projectId, ctx.project.id),
32055
32330
  gte5(crawlerEventsHourly.tsHour, warnCutoff)
32056
32331
  )
32057
32332
  ).get()?.total ?? 0
32058
32333
  );
32059
32334
  const recentReferrals = Number(
32060
32335
  ctx.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
32061
- and23(
32062
- eq32(aiReferralEventsHourly.projectId, ctx.project.id),
32336
+ and24(
32337
+ eq33(aiReferralEventsHourly.projectId, ctx.project.id),
32063
32338
  gte5(aiReferralEventsHourly.tsHour, warnCutoff)
32064
32339
  )
32065
32340
  ).get()?.total ?? 0
@@ -32074,16 +32349,16 @@ var recentDataCheck = {
32074
32349
  }
32075
32350
  const olderCrawlers = Number(
32076
32351
  ctx.db.select({ total: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
32077
- and23(
32078
- eq32(crawlerEventsHourly.projectId, ctx.project.id),
32352
+ and24(
32353
+ eq33(crawlerEventsHourly.projectId, ctx.project.id),
32079
32354
  gte5(crawlerEventsHourly.tsHour, failCutoff)
32080
32355
  )
32081
32356
  ).get()?.total ?? 0
32082
32357
  );
32083
32358
  const olderReferrals = Number(
32084
32359
  ctx.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
32085
- and23(
32086
- eq32(aiReferralEventsHourly.projectId, ctx.project.id),
32360
+ and24(
32361
+ eq33(aiReferralEventsHourly.projectId, ctx.project.id),
32087
32362
  gte5(aiReferralEventsHourly.tsHour, failCutoff)
32088
32363
  )
32089
32364
  ).get()?.total ?? 0
@@ -32497,7 +32772,7 @@ async function doctorRoutes(app, opts) {
32497
32772
 
32498
32773
  // ../api-routes/src/discovery/routes.ts
32499
32774
  import crypto26 from "crypto";
32500
- import { and as and24, desc as desc15, eq as eq33, gte as gte6, inArray as inArray11 } from "drizzle-orm";
32775
+ import { and as and25, desc as desc16, eq as eq34, gte as gte6, inArray as inArray12 } from "drizzle-orm";
32501
32776
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
32502
32777
  async function discoveryRoutes(app, opts) {
32503
32778
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -32529,16 +32804,16 @@ async function discoveryRoutes(app, opts) {
32529
32804
  const now = (/* @__PURE__ */ new Date()).toISOString();
32530
32805
  const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
32531
32806
  const decision = app.db.transaction((tx) => {
32532
- const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and24(
32533
- eq33(discoverySessions.projectId, project.id),
32534
- eq33(discoverySessions.icpDescription, icpDescription),
32535
- inArray11(discoverySessions.status, [
32807
+ const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and25(
32808
+ eq34(discoverySessions.projectId, project.id),
32809
+ eq34(discoverySessions.icpDescription, icpDescription),
32810
+ inArray12(discoverySessions.status, [
32536
32811
  DiscoverySessionStatuses.queued,
32537
32812
  DiscoverySessionStatuses.seeding,
32538
32813
  DiscoverySessionStatuses.probing
32539
32814
  ]),
32540
32815
  gte6(discoverySessions.createdAt, ageFloorIso)
32541
- )).orderBy(desc15(discoverySessions.createdAt)).get();
32816
+ )).orderBy(desc16(discoverySessions.createdAt)).get();
32542
32817
  if (existing && existing.runId) {
32543
32818
  return { reused: true, sessionId: existing.id, runId: existing.runId };
32544
32819
  }
@@ -32601,7 +32876,7 @@ async function discoveryRoutes(app, opts) {
32601
32876
  const project = resolveProject(app.db, request.params.name);
32602
32877
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
32603
32878
  const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
32604
- const rows = app.db.select().from(discoverySessions).where(eq33(discoverySessions.projectId, project.id)).orderBy(desc15(discoverySessions.createdAt)).limit(limit).all();
32879
+ const rows = app.db.select().from(discoverySessions).where(eq34(discoverySessions.projectId, project.id)).orderBy(desc16(discoverySessions.createdAt)).limit(limit).all();
32605
32880
  return reply.send(rows.map(serializeSession));
32606
32881
  }
32607
32882
  );
@@ -32609,11 +32884,11 @@ async function discoveryRoutes(app, opts) {
32609
32884
  "/projects/:name/discover/sessions/:id",
32610
32885
  async (request, reply) => {
32611
32886
  const project = resolveProject(app.db, request.params.name);
32612
- const session = app.db.select().from(discoverySessions).where(eq33(discoverySessions.id, request.params.id)).get();
32887
+ const session = app.db.select().from(discoverySessions).where(eq34(discoverySessions.id, request.params.id)).get();
32613
32888
  if (!session || session.projectId !== project.id) {
32614
32889
  throw notFound("Discovery session", request.params.id);
32615
32890
  }
32616
- const probeRows = app.db.select().from(discoveryProbes).where(eq33(discoveryProbes.sessionId, session.id)).all();
32891
+ const probeRows = app.db.select().from(discoveryProbes).where(eq34(discoveryProbes.sessionId, session.id)).all();
32617
32892
  const detail = {
32618
32893
  ...serializeSession(session),
32619
32894
  probes: probeRows.map(serializeProbe)
@@ -32625,12 +32900,12 @@ async function discoveryRoutes(app, opts) {
32625
32900
  "/projects/:name/discover/sessions/:id/promote",
32626
32901
  async (request, reply) => {
32627
32902
  const project = resolveProject(app.db, request.params.name);
32628
- const session = app.db.select().from(discoverySessions).where(eq33(discoverySessions.id, request.params.id)).get();
32903
+ const session = app.db.select().from(discoverySessions).where(eq34(discoverySessions.id, request.params.id)).get();
32629
32904
  if (!session || session.projectId !== project.id) {
32630
32905
  throw notFound("Discovery session", request.params.id);
32631
32906
  }
32632
- const probeRows = app.db.select().from(discoveryProbes).where(eq33(discoveryProbes.sessionId, session.id)).all();
32633
- const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq33(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
32907
+ const probeRows = app.db.select().from(discoveryProbes).where(eq34(discoveryProbes.sessionId, session.id)).all();
32908
+ const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq34(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
32634
32909
  const seenCompetitors = new Set(existingCompetitors);
32635
32910
  const cited = /* @__PURE__ */ new Set();
32636
32911
  const aspirational = /* @__PURE__ */ new Set();
@@ -32659,7 +32934,7 @@ async function discoveryRoutes(app, opts) {
32659
32934
  );
32660
32935
  app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
32661
32936
  const project = resolveProject(app.db, request.params.name);
32662
- const session = app.db.select().from(discoverySessions).where(eq33(discoverySessions.id, request.params.id)).get();
32937
+ const session = app.db.select().from(discoverySessions).where(eq34(discoverySessions.id, request.params.id)).get();
32663
32938
  if (!session || session.projectId !== project.id) {
32664
32939
  throw notFound("Discovery session", request.params.id);
32665
32940
  }
@@ -32682,7 +32957,7 @@ async function discoveryRoutes(app, opts) {
32682
32957
  const bucketSet = new Set(buckets);
32683
32958
  const includeCompetitors = parsed.data.includeCompetitors ?? true;
32684
32959
  const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
32685
- const probeRows = app.db.select().from(discoveryProbes).where(eq33(discoveryProbes.sessionId, session.id)).all();
32960
+ const probeRows = app.db.select().from(discoveryProbes).where(eq34(discoveryProbes.sessionId, session.id)).all();
32686
32961
  const candidateQueries = /* @__PURE__ */ new Set();
32687
32962
  for (const probe of probeRows) {
32688
32963
  if (!probe.bucket) continue;
@@ -32690,7 +32965,7 @@ async function discoveryRoutes(app, opts) {
32690
32965
  if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
32691
32966
  }
32692
32967
  const existingQueries = new Set(
32693
- app.db.select({ query: queries.query }).from(queries).where(eq33(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
32968
+ app.db.select({ query: queries.query }).from(queries).where(eq34(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
32694
32969
  );
32695
32970
  const promotedQueries = [];
32696
32971
  const skippedQueries = [];
@@ -32706,7 +32981,7 @@ async function discoveryRoutes(app, opts) {
32706
32981
  const skippedCompetitors = [];
32707
32982
  if (includeCompetitors) {
32708
32983
  const existingCompetitors = new Set(
32709
- app.db.select({ domain: competitors.domain }).from(competitors).where(eq33(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
32984
+ app.db.select({ domain: competitors.domain }).from(competitors).where(eq34(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
32710
32985
  );
32711
32986
  const competitorMap = parseCompetitorMap(session.competitorMap);
32712
32987
  for (const entry of selectEligibleCompetitors(competitorMap, competitorTypes)) {
@@ -32813,7 +33088,7 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
32813
33088
 
32814
33089
  // ../api-routes/src/discovery/orchestrate.ts
32815
33090
  import crypto27 from "crypto";
32816
- import { eq as eq34 } from "drizzle-orm";
33091
+ import { eq as eq35 } from "drizzle-orm";
32817
33092
  var DEFAULT_MAX_PROBES = 100;
32818
33093
  var ABSOLUTE_MAX_PROBES = 500;
32819
33094
  function classifyProbeBucket(input) {
@@ -32826,7 +33101,7 @@ function classifyProbeBucket(input) {
32826
33101
  }
32827
33102
  function buildCompetitorMap(probes, project, classification = {}) {
32828
33103
  const canonical = new Set(project.canonicalDomains.map((d) => d.toLowerCase()));
32829
- const counts = /* @__PURE__ */ new Map();
33104
+ const counts2 = /* @__PURE__ */ new Map();
32830
33105
  for (const probe of probes) {
32831
33106
  const seenInProbe = /* @__PURE__ */ new Set();
32832
33107
  for (const raw of probe.citedDomains) {
@@ -32834,10 +33109,10 @@ function buildCompetitorMap(probes, project, classification = {}) {
32834
33109
  if (canonical.has(domain)) continue;
32835
33110
  if (seenInProbe.has(domain)) continue;
32836
33111
  seenInProbe.add(domain);
32837
- counts.set(domain, (counts.get(domain) ?? 0) + 1);
33112
+ counts2.set(domain, (counts2.get(domain) ?? 0) + 1);
32838
33113
  }
32839
33114
  }
32840
- return Array.from(counts.entries()).map(([domain, hits]) => ({
33115
+ return Array.from(counts2.entries()).map(([domain, hits]) => ({
32841
33116
  domain,
32842
33117
  hits,
32843
33118
  competitorType: classification[domain] ?? DiscoveryCompetitorTypes.unknown
@@ -32867,7 +33142,7 @@ async function executeDiscovery(opts) {
32867
33142
  status: DiscoverySessionStatuses.seeding,
32868
33143
  dedupThreshold,
32869
33144
  startedAt
32870
- }).where(eq34(discoverySessions.id, opts.sessionId)).run();
33145
+ }).where(eq35(discoverySessions.id, opts.sessionId)).run();
32871
33146
  const seedResult = await opts.deps.seed({
32872
33147
  project: opts.project,
32873
33148
  icpDescription: opts.icpDescription,
@@ -32893,7 +33168,7 @@ async function executeDiscovery(opts) {
32893
33168
  seedCountRaw,
32894
33169
  seedCount,
32895
33170
  warning
32896
- }).where(eq34(discoverySessions.id, opts.sessionId)).run();
33171
+ }).where(eq35(discoverySessions.id, opts.sessionId)).run();
32897
33172
  const probeRows = [];
32898
33173
  const buckets = { cited: 0, aspirational: 0, "wasted-surface": 0 };
32899
33174
  for (const query of probedCanonicals) {
@@ -32934,7 +33209,7 @@ async function executeDiscovery(opts) {
32934
33209
  wastedCount: buckets["wasted-surface"],
32935
33210
  competitorMap,
32936
33211
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
32937
- }).where(eq34(discoverySessions.id, opts.sessionId)).run();
33212
+ }).where(eq35(discoverySessions.id, opts.sessionId)).run();
32938
33213
  upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
32939
33214
  return {
32940
33215
  buckets,
@@ -32974,7 +33249,7 @@ function markSessionFailed(db, sessionId, error) {
32974
33249
  status: DiscoverySessionStatuses.failed,
32975
33250
  error,
32976
33251
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
32977
- }).where(eq34(discoverySessions.id, sessionId)).run();
33252
+ }).where(eq35(discoverySessions.id, sessionId)).run();
32978
33253
  }
32979
33254
  function dedupeStrings(input) {
32980
33255
  const seen = /* @__PURE__ */ new Set();
@@ -32992,7 +33267,7 @@ function dedupeStrings(input) {
32992
33267
 
32993
33268
  // ../api-routes/src/technical-aeo.ts
32994
33269
  import crypto28 from "crypto";
32995
- import { and as and25, asc as asc4, count, desc as desc16, eq as eq35, inArray as inArray12 } from "drizzle-orm";
33270
+ import { and as and26, asc as asc4, count, desc as desc17, eq as eq36, inArray as inArray13 } from "drizzle-orm";
32996
33271
  var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
32997
33272
  function emptyScore(projectName) {
32998
33273
  return {
@@ -33024,12 +33299,12 @@ function parsePositiveInt(value, fallback, max) {
33024
33299
  async function technicalAeoRoutes(app, opts) {
33025
33300
  app.get("/projects/:name/technical-aeo", async (request) => {
33026
33301
  const project = resolveProject(app.db, request.params.name);
33027
- const rows = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq35(siteAuditSnapshots.runId, runs.id)).where(and25(
33028
- eq35(siteAuditSnapshots.projectId, project.id),
33029
- eq35(runs.kind, RunKinds["site-audit"]),
33030
- inArray12(runs.status, SURFACEABLE_STATUSES),
33302
+ const rows = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq36(siteAuditSnapshots.runId, runs.id)).where(and26(
33303
+ eq36(siteAuditSnapshots.projectId, project.id),
33304
+ eq36(runs.kind, RunKinds["site-audit"]),
33305
+ inArray13(runs.status, SURFACEABLE_STATUSES),
33031
33306
  notProbeRun()
33032
- )).orderBy(desc16(siteAuditSnapshots.createdAt)).limit(2).all();
33307
+ )).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(2).all();
33033
33308
  const latest = rows[0];
33034
33309
  if (!latest) return emptyScore(project.name);
33035
33310
  const snap = latest.snap;
@@ -33059,24 +33334,24 @@ async function technicalAeoRoutes(app, opts) {
33059
33334
  });
33060
33335
  app.get("/projects/:name/technical-aeo/pages", async (request) => {
33061
33336
  const project = resolveProject(app.db, request.params.name);
33062
- const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq35(siteAuditSnapshots.runId, runs.id)).where(and25(
33063
- eq35(siteAuditSnapshots.projectId, project.id),
33064
- eq35(runs.kind, RunKinds["site-audit"]),
33065
- inArray12(runs.status, SURFACEABLE_STATUSES),
33337
+ const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq36(siteAuditSnapshots.runId, runs.id)).where(and26(
33338
+ eq36(siteAuditSnapshots.projectId, project.id),
33339
+ eq36(runs.kind, RunKinds["site-audit"]),
33340
+ inArray13(runs.status, SURFACEABLE_STATUSES),
33066
33341
  notProbeRun()
33067
- )).orderBy(desc16(siteAuditSnapshots.createdAt)).limit(1).get();
33342
+ )).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(1).get();
33068
33343
  if (!latest) {
33069
33344
  return { project: project.name, runId: null, auditedAt: null, total: 0, pages: [] };
33070
33345
  }
33071
33346
  const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
33072
- const conds = [eq35(siteAuditPages.runId, latest.runId)];
33073
- if (statusFilter) conds.push(eq35(siteAuditPages.status, statusFilter));
33074
- const where = and25(...conds);
33347
+ const conds = [eq36(siteAuditPages.runId, latest.runId)];
33348
+ if (statusFilter) conds.push(eq36(siteAuditPages.status, statusFilter));
33349
+ const where = and26(...conds);
33075
33350
  const totalRow = app.db.select({ value: count() }).from(siteAuditPages).where(where).get();
33076
33351
  const total = totalRow?.value ?? 0;
33077
33352
  const limit = parsePositiveInt(request.query.limit, 100, 500);
33078
33353
  const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
33079
- const orderBy = request.query.sort === "score-desc" ? desc16(siteAuditPages.overallScore) : request.query.sort === "url" ? asc4(siteAuditPages.url) : asc4(siteAuditPages.overallScore);
33354
+ const orderBy = request.query.sort === "score-desc" ? desc17(siteAuditPages.overallScore) : request.query.sort === "url" ? asc4(siteAuditPages.url) : asc4(siteAuditPages.overallScore);
33080
33355
  const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
33081
33356
  const pages = rows.map((row) => ({
33082
33357
  url: row.url,
@@ -33095,12 +33370,12 @@ async function technicalAeoRoutes(app, opts) {
33095
33370
  auditedAt: siteAuditSnapshots.auditedAt,
33096
33371
  aggregateScore: siteAuditSnapshots.aggregateScore,
33097
33372
  pagesAudited: siteAuditSnapshots.pagesAudited
33098
- }).from(siteAuditSnapshots).innerJoin(runs, eq35(siteAuditSnapshots.runId, runs.id)).where(and25(
33099
- eq35(siteAuditSnapshots.projectId, project.id),
33100
- eq35(runs.kind, RunKinds["site-audit"]),
33101
- inArray12(runs.status, SURFACEABLE_STATUSES),
33373
+ }).from(siteAuditSnapshots).innerJoin(runs, eq36(siteAuditSnapshots.runId, runs.id)).where(and26(
33374
+ eq36(siteAuditSnapshots.projectId, project.id),
33375
+ eq36(runs.kind, RunKinds["site-audit"]),
33376
+ inArray13(runs.status, SURFACEABLE_STATUSES),
33102
33377
  notProbeRun()
33103
- )).orderBy(desc16(siteAuditSnapshots.createdAt)).limit(limit).all();
33378
+ )).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(limit).all();
33104
33379
  return { project: project.name, points: rows.reverse() };
33105
33380
  });
33106
33381
  app.post("/projects/:name/technical-aeo/runs", async (request) => {
@@ -33109,10 +33384,10 @@ async function technicalAeoRoutes(app, opts) {
33109
33384
  if (!parsed.success) {
33110
33385
  throw validationError(parsed.error.issues[0]?.message ?? "Invalid site-audit request");
33111
33386
  }
33112
- const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and25(
33113
- eq35(runs.projectId, project.id),
33114
- eq35(runs.kind, RunKinds["site-audit"]),
33115
- inArray12(runs.status, [RunStatuses.queued, RunStatuses.running])
33387
+ const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and26(
33388
+ eq36(runs.projectId, project.id),
33389
+ eq36(runs.kind, RunKinds["site-audit"]),
33390
+ inArray13(runs.status, [RunStatuses.queued, RunStatuses.running])
33116
33391
  )).get();
33117
33392
  if (existing) {
33118
33393
  return { runId: existing.id, status: existing.status };
@@ -33215,6 +33490,7 @@ async function apiRoutes(app, opts) {
33215
33490
  await api.register(intelligenceRoutes);
33216
33491
  await api.register(reportRoutes);
33217
33492
  await api.register(citationRoutes);
33493
+ await api.register(visibilityStatsRoutes);
33218
33494
  await api.register(compositeRoutes);
33219
33495
  await api.register(contentRoutes, {
33220
33496
  explainContentRecommendation: opts.explainContentRecommendation,
@@ -33698,16 +33974,16 @@ var IntelligenceService = class {
33698
33974
  */
33699
33975
  analyzeAndPersist(runId, projectId) {
33700
33976
  const recentRuns = this.db.select().from(runs).where(
33701
- and26(
33702
- eq36(runs.projectId, projectId),
33703
- or5(eq36(runs.status, "completed"), eq36(runs.status, "partial")),
33977
+ and27(
33978
+ eq37(runs.projectId, projectId),
33979
+ or5(eq37(runs.status, "completed"), eq37(runs.status, "partial")),
33704
33980
  // Defensive: RunCoordinator already skips probes before this is
33705
33981
  // called, but if a future call site invokes analyzeAndPersist
33706
33982
  // directly for a probe, probes still must not pollute the
33707
33983
  // intelligence window.
33708
33984
  ne5(runs.trigger, RunTriggers.probe)
33709
33985
  )
33710
- ).orderBy(desc17(runs.finishedAt), desc17(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
33986
+ ).orderBy(desc18(runs.finishedAt), desc18(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
33711
33987
  if (recentRuns.length === 0) {
33712
33988
  log.info("intelligence.skip", { runId, reason: "no completed runs" });
33713
33989
  return null;
@@ -33782,7 +34058,7 @@ var IntelligenceService = class {
33782
34058
  * Returns the persisted insights so the coordinator can count critical/high.
33783
34059
  */
33784
34060
  analyzeAndPersistGbp(runId, projectId) {
33785
- const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq36(runs.id, runId)).get();
34061
+ const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq37(runs.id, runId)).get();
33786
34062
  if (!runRow) {
33787
34063
  log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
33788
34064
  this.persistGbpInsights(runId, projectId, [], []);
@@ -33790,9 +34066,9 @@ var IntelligenceService = class {
33790
34066
  }
33791
34067
  const windowStart = runRow.startedAt ?? runRow.createdAt;
33792
34068
  const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
33793
- const selected = this.db.select().from(gbpLocations).where(and26(
33794
- eq36(gbpLocations.projectId, projectId),
33795
- eq36(gbpLocations.selected, true),
34069
+ const selected = this.db.select().from(gbpLocations).where(and27(
34070
+ eq37(gbpLocations.projectId, projectId),
34071
+ eq37(gbpLocations.selected, true),
33796
34072
  gte7(gbpLocations.syncedAt, windowStart),
33797
34073
  lte4(gbpLocations.syncedAt, windowEnd)
33798
34074
  )).all();
@@ -33827,10 +34103,10 @@ var IntelligenceService = class {
33827
34103
  }
33828
34104
  /** Build the per-location signal bundle the GBP analyzer consumes. */
33829
34105
  buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
33830
- const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and26(eq36(gbpDailyMetrics.projectId, projectId), eq36(gbpDailyMetrics.locationName, locationName))).all();
33831
- const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and26(eq36(gbpPlaceActions.projectId, projectId), eq36(gbpPlaceActions.locationName, locationName))).all();
33832
- const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and26(eq36(gbpLodgingSnapshots.projectId, projectId), eq36(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc17(gbpLodgingSnapshots.syncedAt)).limit(1).get();
33833
- const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and26(eq36(gbpPlaceDetails.projectId, projectId), eq36(gbpPlaceDetails.locationName, locationName))).orderBy(desc17(gbpPlaceDetails.syncedAt)).limit(1).get();
34106
+ const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and27(eq37(gbpDailyMetrics.projectId, projectId), eq37(gbpDailyMetrics.locationName, locationName))).all();
34107
+ const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and27(eq37(gbpPlaceActions.projectId, projectId), eq37(gbpPlaceActions.locationName, locationName))).all();
34108
+ const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and27(eq37(gbpLodgingSnapshots.projectId, projectId), eq37(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc18(gbpLodgingSnapshots.syncedAt)).limit(1).get();
34109
+ const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and27(eq37(gbpPlaceDetails.projectId, projectId), eq37(gbpPlaceDetails.locationName, locationName))).orderBy(desc18(gbpPlaceDetails.syncedAt)).limit(1).get();
33834
34110
  const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
33835
34111
  const summary = buildGbpSummary({
33836
34112
  locationName,
@@ -33862,7 +34138,7 @@ var IntelligenceService = class {
33862
34138
  /** Build the month-over-month keyword series for a location from the
33863
34139
  * accumulating gbp_keyword_monthly table (latest complete month vs prior). */
33864
34140
  buildGbpKeywordTrend(projectId, locationName) {
33865
- const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and26(eq36(gbpKeywordMonthly.projectId, projectId), eq36(gbpKeywordMonthly.locationName, locationName))).all();
34141
+ const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and27(eq37(gbpKeywordMonthly.projectId, projectId), eq37(gbpKeywordMonthly.locationName, locationName))).all();
33866
34142
  if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
33867
34143
  const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
33868
34144
  const recentMonth = months[0] ?? null;
@@ -33893,7 +34169,7 @@ var IntelligenceService = class {
33893
34169
  */
33894
34170
  persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
33895
34171
  const covered = new Set(coveredLocationNames);
33896
- const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and26(eq36(insights.projectId, projectId), eq36(insights.provider, GBP_INSIGHT_PROVIDER))).all();
34172
+ const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and27(eq37(insights.projectId, projectId), eq37(insights.provider, GBP_INSIGHT_PROVIDER))).all();
33897
34173
  const staleIds = [];
33898
34174
  const dismissedSlots = /* @__PURE__ */ new Set();
33899
34175
  for (const row of existing) {
@@ -33904,7 +34180,7 @@ var IntelligenceService = class {
33904
34180
  }
33905
34181
  this.db.transaction((tx) => {
33906
34182
  for (const id of staleIds) {
33907
- tx.delete(insights).where(eq36(insights.id, id)).run();
34183
+ tx.delete(insights).where(eq37(insights.id, id)).run();
33908
34184
  }
33909
34185
  for (const insight of gbpInsights) {
33910
34186
  const parsed = parseGbpInsightId(insight.id);
@@ -33982,7 +34258,7 @@ var IntelligenceService = class {
33982
34258
  * create per run + aggregate). DB is left untouched.
33983
34259
  */
33984
34260
  backfill(projectName, opts, onProgress) {
33985
- const project = this.db.select().from(projects).where(eq36(projects.name, projectName)).get();
34261
+ const project = this.db.select().from(projects).where(eq37(projects.name, projectName)).get();
33986
34262
  if (!project) {
33987
34263
  throw new Error(`Project "${projectName}" not found`);
33988
34264
  }
@@ -33995,9 +34271,9 @@ var IntelligenceService = class {
33995
34271
  sinceTimestamp = parsed;
33996
34272
  }
33997
34273
  const allRuns = this.db.select().from(runs).where(
33998
- and26(
33999
- eq36(runs.projectId, project.id),
34000
- or5(eq36(runs.status, "completed"), eq36(runs.status, "partial")),
34274
+ and27(
34275
+ eq37(runs.projectId, project.id),
34276
+ or5(eq37(runs.status, "completed"), eq37(runs.status, "partial")),
34001
34277
  // Backfill must not replay probe runs as if they were real sweeps.
34002
34278
  ne5(runs.trigger, RunTriggers.probe)
34003
34279
  )
@@ -34030,7 +34306,7 @@ var IntelligenceService = class {
34030
34306
  let wouldDeleteTotal = 0;
34031
34307
  const existingByRunId = /* @__PURE__ */ new Map();
34032
34308
  if (isDryRun && targetRuns.length > 0) {
34033
- const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray13(insights.runId, targetRuns.map((r) => r.id))).all();
34309
+ const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray14(insights.runId, targetRuns.map((r) => r.id))).all();
34034
34310
  for (const r of rows) {
34035
34311
  if (r.runId == null) continue;
34036
34312
  existingByRunId.set(r.runId, (existingByRunId.get(r.runId) ?? 0) + 1);
@@ -34076,7 +34352,7 @@ var IntelligenceService = class {
34076
34352
  return { processed, skipped, totalInsights };
34077
34353
  }
34078
34354
  loadTrackedCompetitors(projectId) {
34079
- return this.db.select({ domain: competitors.domain }).from(competitors).where(eq36(competitors.projectId, projectId)).all().map((r) => r.domain);
34355
+ return this.db.select({ domain: competitors.domain }).from(competitors).where(eq37(competitors.projectId, projectId)).all().map((r) => r.domain);
34080
34356
  }
34081
34357
  /**
34082
34358
  * Wipe transition signals from an analysis result while keeping health.
@@ -34097,15 +34373,15 @@ var IntelligenceService = class {
34097
34373
  }
34098
34374
  persistResult(result, runId, projectId) {
34099
34375
  const previouslyDismissed = /* @__PURE__ */ new Set();
34100
- const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq36(insights.runId, runId)).all();
34376
+ const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq37(insights.runId, runId)).all();
34101
34377
  for (const row of existingInsights) {
34102
34378
  if (row.dismissed) {
34103
34379
  previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
34104
34380
  }
34105
34381
  }
34106
34382
  this.db.transaction((tx) => {
34107
- tx.delete(insights).where(eq36(insights.runId, runId)).run();
34108
- tx.delete(healthSnapshots).where(eq36(healthSnapshots.runId, runId)).run();
34383
+ tx.delete(insights).where(eq37(insights.runId, runId)).run();
34384
+ tx.delete(healthSnapshots).where(eq37(healthSnapshots.runId, runId)).run();
34109
34385
  const now = (/* @__PURE__ */ new Date()).toISOString();
34110
34386
  for (const insight of result.insights) {
34111
34387
  const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
@@ -34129,8 +34405,10 @@ var IntelligenceService = class {
34129
34405
  projectId,
34130
34406
  runId,
34131
34407
  overallCitedRate: String(result.health.overallCitedRate),
34408
+ overallMentionRate: String(result.health.overallMentionRate),
34132
34409
  totalPairs: result.health.totalPairs,
34133
34410
  citedPairs: result.health.citedPairs,
34411
+ mentionedPairs: result.health.mentionedPairs,
34134
34412
  providerBreakdown: result.health.providerBreakdown,
34135
34413
  createdAt: now
34136
34414
  }).run();
@@ -34156,28 +34434,28 @@ var IntelligenceService = class {
34156
34434
  applySeverityTiering(rawInsights, excludeRunId, projectId) {
34157
34435
  const regressions = rawInsights.filter((i) => i.type === "regression");
34158
34436
  if (regressions.length === 0) return rawInsights;
34159
- const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(eq36(gscSearchData.projectId, projectId)).all();
34437
+ const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(eq37(gscSearchData.projectId, projectId)).all();
34160
34438
  const gscConnected = gscRows.length > 0;
34161
34439
  const gscImpressionsByQuery = /* @__PURE__ */ new Map();
34162
34440
  for (const row of gscRows) {
34163
34441
  const key = row.query.toLowerCase();
34164
34442
  gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
34165
34443
  }
34166
- const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq36(projects.id, projectId)).get();
34444
+ const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq37(projects.id, projectId)).get();
34167
34445
  const locationCount = Math.max(
34168
34446
  1,
34169
34447
  (projectRow?.locations ?? []).length
34170
34448
  );
34171
34449
  const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
34172
34450
  const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
34173
- and26(
34174
- eq36(runs.projectId, projectId),
34175
- eq36(runs.kind, RunKinds["answer-visibility"]),
34176
- or5(eq36(runs.status, "completed"), eq36(runs.status, "partial")),
34451
+ and27(
34452
+ eq37(runs.projectId, projectId),
34453
+ eq37(runs.kind, RunKinds["answer-visibility"]),
34454
+ or5(eq37(runs.status, "completed"), eq37(runs.status, "partial")),
34177
34455
  // Defensive — see top of file.
34178
34456
  ne5(runs.trigger, RunTriggers.probe)
34179
34457
  )
34180
- ).orderBy(desc17(runs.createdAt), desc17(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
34458
+ ).orderBy(desc18(runs.createdAt), desc18(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
34181
34459
  const recentGroups = groupRunsByCreatedAt(recentRunRows);
34182
34460
  const recentRunIds = [];
34183
34461
  const recentRunIdToCreatedAt = /* @__PURE__ */ new Map();
@@ -34193,7 +34471,7 @@ var IntelligenceService = class {
34193
34471
  const haveHistory = recentRunIds.length > 0;
34194
34472
  const priorRegressionsByPair = /* @__PURE__ */ new Map();
34195
34473
  if (haveHistory) {
34196
- const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and26(eq36(insights.type, "regression"), inArray13(insights.runId, recentRunIds))).all();
34474
+ const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and27(eq37(insights.type, "regression"), inArray14(insights.runId, recentRunIds))).all();
34197
34475
  const regressionGroups = /* @__PURE__ */ new Map();
34198
34476
  for (const row of priorRows) {
34199
34477
  if (!row.runId) continue;
@@ -34222,7 +34500,7 @@ var IntelligenceService = class {
34222
34500
  });
34223
34501
  }
34224
34502
  buildRunData(runId, projectId, completedAt, location = null) {
34225
- const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq36(projects.id, projectId)).get();
34503
+ const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq37(projects.id, projectId)).get();
34226
34504
  const projectDomains = projectDomainRow ? effectiveDomains({
34227
34505
  canonicalDomain: projectDomainRow.canonicalDomain,
34228
34506
  ownedDomains: projectDomainRow.ownedDomains
@@ -34235,10 +34513,11 @@ var IntelligenceService = class {
34235
34513
  queryText: querySnapshots.queryText,
34236
34514
  provider: querySnapshots.provider,
34237
34515
  citationState: querySnapshots.citationState,
34516
+ answerMentioned: querySnapshots.answerMentioned,
34238
34517
  citedDomains: querySnapshots.citedDomains,
34239
34518
  competitorOverlap: querySnapshots.competitorOverlap,
34240
34519
  snapshotLocation: querySnapshots.location
34241
- }).from(querySnapshots).leftJoin(queries, eq36(querySnapshots.queryId, queries.id)).where(eq36(querySnapshots.runId, runId)).all();
34520
+ }).from(querySnapshots).leftJoin(queries, eq37(querySnapshots.queryId, queries.id)).where(eq37(querySnapshots.runId, runId)).all();
34242
34521
  const snapshots = [];
34243
34522
  let orphanCount = 0;
34244
34523
  for (const r of rows) {
@@ -34253,6 +34532,10 @@ var IntelligenceService = class {
34253
34532
  query: resolvedQuery,
34254
34533
  provider: r.provider,
34255
34534
  cited: r.citationState === CitationStates.cited,
34535
+ // Independent answer-text signal. Tri-state passes through untouched
34536
+ // (true / false / null = "not checked"); computeHealth counts only
34537
+ // exact `true`. Never coerce null→false here.
34538
+ answerMentioned: r.answerMentioned,
34256
34539
  // The project's OWN cited domain — never a co-cited competitor that
34257
34540
  // happens to sort first in the full citedDomains set.
34258
34541
  citationUrl: pickProjectCitedDomain(domains, projectDomains),