@ainyc/canonry 2.10.1 → 2.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,7 @@ import {
6
6
  ApiClient,
7
7
  AppError,
8
8
  CcReleaseSyncStatuses,
9
+ CitationStates,
9
10
  MemorySources,
10
11
  RunKinds,
11
12
  RunStatuses,
@@ -54,7 +55,7 @@ import {
54
55
  visibilityStateFromAnswerMentioned,
55
56
  windowCutoff,
56
57
  wordpressEnvSchema
57
- } from "./chunk-KWQCQMPY.js";
58
+ } from "./chunk-Z3BWDCBJ.js";
58
59
  import {
59
60
  IntelligenceService,
60
61
  agentMemory,
@@ -65,6 +66,10 @@ import {
65
66
  backlinkSummaries,
66
67
  bingCoverageSnapshots,
67
68
  bingUrlInspections,
69
+ buildContentGapRows,
70
+ buildContentSourceRows,
71
+ buildContentTargetRows,
72
+ buildInventory,
68
73
  ccReleaseSyncs,
69
74
  competitors,
70
75
  createLogger,
@@ -79,6 +84,7 @@ import {
79
84
  gscUrlInspections,
80
85
  healthSnapshots,
81
86
  insights,
87
+ isBlogShapedQuery,
82
88
  keywords,
83
89
  notifications,
84
90
  parseJsonColumn,
@@ -87,7 +93,7 @@ import {
87
93
  runs,
88
94
  schedules,
89
95
  usageCounters
90
- } from "./chunk-PYHANJ3B.js";
96
+ } from "./chunk-UM6RDSRJ.js";
91
97
 
92
98
  // src/telemetry.ts
93
99
  import crypto from "crypto";
@@ -167,7 +173,7 @@ import crypto28 from "crypto";
167
173
  import fs12 from "fs";
168
174
  import path14 from "path";
169
175
  import { fileURLToPath as fileURLToPath2 } from "url";
170
- import { eq as eq30 } from "drizzle-orm";
176
+ import { eq as eq31 } from "drizzle-orm";
171
177
  import Fastify from "fastify";
172
178
 
173
179
  // ../api-routes/src/auth.ts
@@ -2347,6 +2353,337 @@ async function intelligenceRoutes(app) {
2347
2353
  });
2348
2354
  }
2349
2355
 
2356
+ // ../api-routes/src/content-data.ts
2357
+ import { and as and3, eq as eq12, desc as desc5, inArray as inArray3 } from "drizzle-orm";
2358
+ var RECENT_RUNS_WINDOW = 5;
2359
+ function loadOrchestratorInput(db, project) {
2360
+ const projectId = project.id;
2361
+ const ownDomain = normalizeDomain(project.canonicalDomain);
2362
+ const ownedDomains = parseJsonColumn(project.ownedDomains, []);
2363
+ const ourDomains = /* @__PURE__ */ new Set([ownDomain, ...ownedDomains.map(normalizeDomain)]);
2364
+ const trackedKeywords = listKeywords(db, projectId);
2365
+ const candidateQueryStrings = trackedKeywords.filter(isBlogShapedQuery);
2366
+ const trackedCompetitors = listCompetitorDomains(db, projectId).map(normalizeDomain);
2367
+ const competitorSet = new Set(trackedCompetitors);
2368
+ const recentRunIds = listRecentAnswerVisibilityRunIds(db, projectId, RECENT_RUNS_WINDOW);
2369
+ const latestRunId = recentRunIds[0] ?? "";
2370
+ const latestRunTimestamp = latestRunId ? lookupRunTimestamp(db, latestRunId) : "";
2371
+ const candidateQueries = buildCandidateQueries({
2372
+ db,
2373
+ projectId,
2374
+ candidateQueryStrings,
2375
+ recentRunIds,
2376
+ latestRunId,
2377
+ ourDomains,
2378
+ competitorSet
2379
+ });
2380
+ const inventory = buildInventory({
2381
+ gscPages: listGscPagesForProject(db, projectId),
2382
+ ga4LandingPages: listGa4LandingPagesForProject(db, projectId),
2383
+ sitemapUrls: [],
2384
+ wpPosts: []
2385
+ });
2386
+ const gaTrafficByPage = buildGaTrafficByPage(db, projectId);
2387
+ const totalAiReferralSessions = sumAiReferralSessions(db, projectId);
2388
+ return {
2389
+ projectId,
2390
+ ownDomain,
2391
+ competitors: trackedCompetitors,
2392
+ candidateQueries,
2393
+ inventory,
2394
+ wpSchemaAudit: /* @__PURE__ */ new Map(),
2395
+ gaTrafficByPage,
2396
+ totalAiReferralSessions,
2397
+ latestRunId,
2398
+ latestRunTimestamp,
2399
+ inProgressActions: /* @__PURE__ */ new Map()
2400
+ };
2401
+ }
2402
+ function listKeywords(db, projectId) {
2403
+ const rows = db.select({ text: keywords.keyword }).from(keywords).where(eq12(keywords.projectId, projectId)).all();
2404
+ return rows.map((r) => r.text);
2405
+ }
2406
+ function listCompetitorDomains(db, projectId) {
2407
+ const rows = db.select({ domain: competitors.domain }).from(competitors).where(eq12(competitors.projectId, projectId)).all();
2408
+ return rows.map((r) => r.domain);
2409
+ }
2410
+ function listRecentAnswerVisibilityRunIds(db, projectId, limit) {
2411
+ const rows = db.select({ id: runs.id }).from(runs).where(
2412
+ and3(
2413
+ eq12(runs.projectId, projectId),
2414
+ eq12(runs.kind, RunKinds["answer-visibility"]),
2415
+ // Queued/running/failed/cancelled runs may have partial or no
2416
+ // snapshots; including them risks pointing latestRunId at a run with
2417
+ // no usable evidence.
2418
+ inArray3(runs.status, [RunStatuses.completed, RunStatuses.partial])
2419
+ )
2420
+ ).orderBy(desc5(runs.createdAt)).limit(limit).all();
2421
+ return rows.map((r) => r.id);
2422
+ }
2423
+ function lookupRunTimestamp(db, runId) {
2424
+ const row = db.select({ createdAt: runs.createdAt }).from(runs).where(eq12(runs.id, runId)).get();
2425
+ return row?.createdAt ?? "";
2426
+ }
2427
+ function listGscPagesForProject(db, projectId) {
2428
+ const rows = db.selectDistinct({ page: gscSearchData.page }).from(gscSearchData).where(eq12(gscSearchData.projectId, projectId)).all();
2429
+ return rows.map((r) => r.page);
2430
+ }
2431
+ function listGa4LandingPagesForProject(db, projectId) {
2432
+ const rows = db.selectDistinct({ landingPage: gaTrafficSnapshots.landingPage }).from(gaTrafficSnapshots).where(eq12(gaTrafficSnapshots.projectId, projectId)).all();
2433
+ return rows.map((r) => r.landingPage);
2434
+ }
2435
+ function buildGaTrafficByPage(db, projectId) {
2436
+ const rows = db.select({
2437
+ landingPage: gaTrafficSnapshots.landingPage,
2438
+ sessions: gaTrafficSnapshots.sessions
2439
+ }).from(gaTrafficSnapshots).where(eq12(gaTrafficSnapshots.projectId, projectId)).all();
2440
+ const map = /* @__PURE__ */ new Map();
2441
+ for (const row of rows) {
2442
+ const path15 = extractPath(row.landingPage);
2443
+ if (!path15) continue;
2444
+ map.set(path15, (map.get(path15) ?? 0) + (row.sessions ?? 0));
2445
+ }
2446
+ return map;
2447
+ }
2448
+ function sumAiReferralSessions(db, projectId) {
2449
+ const rows = db.select({ sessions: gaAiReferrals.sessions }).from(gaAiReferrals).where(eq12(gaAiReferrals.projectId, projectId)).all();
2450
+ return rows.reduce((acc, r) => acc + (r.sessions ?? 0), 0);
2451
+ }
2452
+ function buildCandidateQueries(opts) {
2453
+ if (opts.candidateQueryStrings.length === 0 || opts.recentRunIds.length === 0) {
2454
+ return opts.candidateQueryStrings.map((query) => emptyCandidate(query));
2455
+ }
2456
+ const keywordRows = opts.db.select({ id: keywords.id, text: keywords.keyword }).from(keywords).where(eq12(keywords.projectId, opts.projectId)).all();
2457
+ const keywordIdByText = new Map(keywordRows.map((r) => [r.text, r.id]));
2458
+ const candidateKeywordIds = opts.candidateQueryStrings.map((q) => keywordIdByText.get(q)).filter((id) => Boolean(id));
2459
+ const snapshotRows = opts.db.select().from(querySnapshots).where(inArray3(querySnapshots.runId, opts.recentRunIds)).all().filter((r) => candidateKeywordIds.includes(r.keywordId));
2460
+ const snapshotsByKeyword = /* @__PURE__ */ new Map();
2461
+ for (const row of snapshotRows) {
2462
+ const list = snapshotsByKeyword.get(row.keywordId) ?? [];
2463
+ list.push(row);
2464
+ snapshotsByKeyword.set(row.keywordId, list);
2465
+ }
2466
+ const gscRows = opts.db.select().from(gscSearchData).where(eq12(gscSearchData.projectId, opts.projectId)).all();
2467
+ const gscByQuery = aggregateGscByQuery(gscRows);
2468
+ return opts.candidateQueryStrings.map((query) => {
2469
+ const keywordId = keywordIdByText.get(query);
2470
+ const snaps = keywordId ? snapshotsByKeyword.get(keywordId) ?? [] : [];
2471
+ const gsc = gscByQuery.get(query) ?? null;
2472
+ return aggregateCandidate({
2473
+ query,
2474
+ snapshots: snaps,
2475
+ gsc,
2476
+ ourDomains: opts.ourDomains,
2477
+ competitorSet: opts.competitorSet,
2478
+ latestRunId: opts.latestRunId
2479
+ });
2480
+ });
2481
+ }
2482
+ function aggregateGscByQuery(rows) {
2483
+ const byQuery = /* @__PURE__ */ new Map();
2484
+ for (const r of rows) {
2485
+ const existing = byQuery.get(r.query);
2486
+ const candidate = {
2487
+ // GSC stores `page` as a full URL for url-prefix properties; normalize to
2488
+ // a path so it can be joined against `gaTrafficByPage` (which is keyed by
2489
+ // path) and so `ourBestPage.url` / `targetRef` stay consistent regardless
2490
+ // of whether the page is sourced from GSC or from inventory.
2491
+ page: extractPath(r.page),
2492
+ position: Number(r.position) || 0,
2493
+ impressions: r.impressions,
2494
+ clicks: r.clicks,
2495
+ ctr: Number(r.ctr) || 0
2496
+ };
2497
+ if (!existing) {
2498
+ byQuery.set(r.query, candidate);
2499
+ continue;
2500
+ }
2501
+ if (candidate.impressions > existing.impressions) {
2502
+ byQuery.set(r.query, candidate);
2503
+ }
2504
+ }
2505
+ return byQuery;
2506
+ }
2507
+ function aggregateCandidate(opts) {
2508
+ const totalSnaps = opts.snapshots.length;
2509
+ if (totalSnaps === 0) {
2510
+ return {
2511
+ ...emptyCandidate(opts.query),
2512
+ gscPage: opts.gsc?.page ?? null,
2513
+ gscPosition: opts.gsc ? opts.gsc.position : null,
2514
+ gscImpressions: opts.gsc?.impressions ?? 0,
2515
+ gscClicks: opts.gsc?.clicks ?? 0,
2516
+ gscCtr: opts.gsc?.ctr ?? 0
2517
+ };
2518
+ }
2519
+ const citedCount = opts.snapshots.filter((s) => s.citationState === CitationStates.cited).length;
2520
+ const ourCitedRate = citedCount / totalSnaps;
2521
+ const recentMissRate = 1 - ourCitedRate;
2522
+ const competitorTally = /* @__PURE__ */ new Map();
2523
+ const competitorGroundingTally = /* @__PURE__ */ new Map();
2524
+ const ourGroundingTally = /* @__PURE__ */ new Map();
2525
+ let ourCitedInLatestRun = false;
2526
+ for (const snap of opts.snapshots) {
2527
+ const isLatestRun = snap.runId === opts.latestRunId;
2528
+ const competitorOverlap = parseJsonColumn(snap.competitorOverlap, []);
2529
+ for (const domain of competitorOverlap) {
2530
+ const normalized = normalizeDomain(domain);
2531
+ if (!opts.competitorSet.has(normalized)) continue;
2532
+ competitorTally.set(normalized, (competitorTally.get(normalized) ?? 0) + 1);
2533
+ }
2534
+ const grounding = extractGroundingSources(snap.rawResponse);
2535
+ for (const g of grounding) {
2536
+ const domain = normalizeDomain(extractHostFromUri(g.uri));
2537
+ if (!domain) continue;
2538
+ if (opts.ourDomains.has(domain)) {
2539
+ if (isLatestRun) ourCitedInLatestRun = true;
2540
+ recordGroundingHit(ourGroundingTally, g, domain, snap.provider);
2541
+ continue;
2542
+ }
2543
+ if (!opts.competitorSet.has(domain)) continue;
2544
+ recordGroundingHit(competitorGroundingTally, g, domain, snap.provider);
2545
+ }
2546
+ }
2547
+ return {
2548
+ query: opts.query,
2549
+ gscPage: opts.gsc?.page ?? null,
2550
+ gscPosition: opts.gsc ? opts.gsc.position : null,
2551
+ gscImpressions: opts.gsc?.impressions ?? 0,
2552
+ gscClicks: opts.gsc?.clicks ?? 0,
2553
+ gscCtr: opts.gsc?.ctr ?? 0,
2554
+ ourCitedRate,
2555
+ ourCitedInLatestRun,
2556
+ competitorDomains: Array.from(competitorTally.keys()),
2557
+ competitorCitationCount: Array.from(competitorTally.values()).reduce((a, b) => a + b, 0),
2558
+ recentMissRate,
2559
+ ourGroundingUrls: Array.from(ourGroundingTally.values()),
2560
+ competitorGroundingUrls: Array.from(competitorGroundingTally.values()),
2561
+ runsOfHistory: new Set(opts.snapshots.map((s) => s.runId)).size
2562
+ };
2563
+ }
2564
+ function recordGroundingHit(tally, g, domain, provider) {
2565
+ const existing = tally.get(g.uri);
2566
+ if (existing) {
2567
+ existing.citationCount += 1;
2568
+ if (provider && !existing.providers.includes(provider)) {
2569
+ existing.providers.push(provider);
2570
+ }
2571
+ return;
2572
+ }
2573
+ tally.set(g.uri, {
2574
+ uri: g.uri,
2575
+ title: g.title,
2576
+ domain,
2577
+ citationCount: 1,
2578
+ providers: provider ? [provider] : []
2579
+ });
2580
+ }
2581
+ function emptyCandidate(query) {
2582
+ return {
2583
+ query,
2584
+ gscPage: null,
2585
+ gscPosition: null,
2586
+ gscImpressions: 0,
2587
+ gscClicks: 0,
2588
+ gscCtr: 0,
2589
+ ourCitedRate: 0,
2590
+ ourCitedInLatestRun: false,
2591
+ competitorDomains: [],
2592
+ competitorCitationCount: 0,
2593
+ recentMissRate: 0,
2594
+ ourGroundingUrls: [],
2595
+ competitorGroundingUrls: [],
2596
+ runsOfHistory: 0
2597
+ };
2598
+ }
2599
+ function extractGroundingSources(rawResponse) {
2600
+ if (!rawResponse) return [];
2601
+ try {
2602
+ const parsed = JSON.parse(rawResponse);
2603
+ if (parsed && typeof parsed === "object" && "groundingSources" in parsed) {
2604
+ const grounding = parsed.groundingSources;
2605
+ if (Array.isArray(grounding)) {
2606
+ return grounding.filter(
2607
+ (g) => typeof g === "object" && g !== null && typeof g.uri === "string"
2608
+ ).map((g) => ({ uri: g.uri, title: g.title ?? "" }));
2609
+ }
2610
+ }
2611
+ } catch {
2612
+ }
2613
+ return [];
2614
+ }
2615
+ function extractHostFromUri(uri) {
2616
+ try {
2617
+ return new URL(uri).hostname;
2618
+ } catch {
2619
+ return "";
2620
+ }
2621
+ }
2622
+ function normalizeDomain(domain) {
2623
+ return domain.toLowerCase().replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/$/, "");
2624
+ }
2625
+ function extractPath(url) {
2626
+ if (!url) return "";
2627
+ const match = /^https?:\/\/[^/]+(.*)$/.exec(url.trim());
2628
+ const path15 = match ? match[1] : url.trim();
2629
+ const stripped = path15.replace(/\/+$/, "");
2630
+ return stripped || "/";
2631
+ }
2632
+
2633
+ // ../api-routes/src/content.ts
2634
+ async function contentRoutes(app) {
2635
+ app.get("/projects/:name/content/targets", async (request) => {
2636
+ const project = resolveProject(app.db, request.params.name);
2637
+ const includeInProgress = request.query["include-in-progress"] === "true";
2638
+ const limit = parseLimitParam(request.query.limit);
2639
+ const input = loadOrchestratorInput(app.db, project);
2640
+ let rows = buildContentTargetRows(input);
2641
+ if (!includeInProgress) {
2642
+ rows = rows.filter((r) => r.existingAction === null);
2643
+ }
2644
+ if (limit !== void 0) {
2645
+ rows = rows.slice(0, limit);
2646
+ }
2647
+ const response = {
2648
+ targets: rows,
2649
+ contextMetrics: {
2650
+ totalAiReferralSessions: input.totalAiReferralSessions,
2651
+ latestRunId: input.latestRunId,
2652
+ runTimestamp: input.latestRunTimestamp
2653
+ }
2654
+ };
2655
+ return response;
2656
+ });
2657
+ app.get("/projects/:name/content/sources", async (request) => {
2658
+ const project = resolveProject(app.db, request.params.name);
2659
+ const input = loadOrchestratorInput(app.db, project);
2660
+ const rows = buildContentSourceRows(input);
2661
+ const response = {
2662
+ sources: rows,
2663
+ latestRunId: input.latestRunId
2664
+ };
2665
+ return response;
2666
+ });
2667
+ app.get("/projects/:name/content/gaps", async (request) => {
2668
+ const project = resolveProject(app.db, request.params.name);
2669
+ const input = loadOrchestratorInput(app.db, project);
2670
+ const rows = buildContentGapRows(input);
2671
+ const response = {
2672
+ gaps: rows,
2673
+ latestRunId: input.latestRunId
2674
+ };
2675
+ return response;
2676
+ });
2677
+ }
2678
+ function parseLimitParam(raw) {
2679
+ if (raw === void 0) return void 0;
2680
+ const parsed = Number(raw);
2681
+ if (!Number.isFinite(parsed) || parsed < 0 || !Number.isInteger(parsed)) {
2682
+ throw validationError('"limit" must be a non-negative integer');
2683
+ }
2684
+ return parsed;
2685
+ }
2686
+
2350
2687
  // ../api-routes/src/openapi.ts
2351
2688
  var stringSchema = { type: "string" };
2352
2689
  var booleanSchema = { type: "boolean" };
@@ -4573,6 +4910,48 @@ var routeCatalog = [
4573
4910
  404: { description: "Project not found." }
4574
4911
  }
4575
4912
  },
4913
+ // Content opportunity engine
4914
+ {
4915
+ method: "get",
4916
+ path: "/api/v1/projects/{name}/content/targets",
4917
+ summary: "Ranked, action-typed content opportunities",
4918
+ description: "Returns the canonical opportunity list. Each row is `{query, action, ourBestPage?, winningCompetitor?, score, scoreBreakdown, drivers[], demandSource, actionConfidence, existingAction?}`. Hides rows with in-progress actions by default; pass `?include-in-progress=true` to include them annotated.",
4919
+ tags: ["content"],
4920
+ parameters: [
4921
+ nameParameter,
4922
+ { name: "limit", in: "query", description: "Max rows returned.", schema: stringSchema },
4923
+ { name: "include-in-progress", in: "query", description: "Include rows with in-flight tracked actions.", schema: stringSchema }
4924
+ ],
4925
+ responses: {
4926
+ 200: { description: "Targets returned." },
4927
+ 400: { description: "Invalid limit." },
4928
+ 404: { description: "Project not found." }
4929
+ }
4930
+ },
4931
+ {
4932
+ method: "get",
4933
+ path: "/api/v1/projects/{name}/content/sources",
4934
+ summary: "URL-level competitive grounding-source map per query",
4935
+ description: "Returns one row per blog-shaped query containing the grounding URLs the LLM cited. Distinguishes our domain (isOurDomain) from competitor URLs (isCompetitor). Pure DB read \u2014 canonry surfaces URLs but never fetches them.",
4936
+ tags: ["content"],
4937
+ parameters: [nameParameter],
4938
+ responses: {
4939
+ 200: { description: "Sources returned." },
4940
+ 404: { description: "Project not found." }
4941
+ }
4942
+ },
4943
+ {
4944
+ method: "get",
4945
+ path: "/api/v1/projects/{name}/content/gaps",
4946
+ summary: "Queries where competitors are cited but you are not",
4947
+ description: "Returns gap rows ranked by miss rate then by competitor count. Excludes queries with no competitor citations and queries where our cited rate is 100%.",
4948
+ tags: ["content"],
4949
+ parameters: [nameParameter],
4950
+ responses: {
4951
+ 200: { description: "Gaps returned." },
4952
+ 404: { description: "Project not found." }
4953
+ }
4954
+ },
4576
4955
  {
4577
4956
  method: "get",
4578
4957
  path: "/api/v1/backlinks/status",
@@ -5031,7 +5410,7 @@ async function telemetryRoutes(app, opts) {
5031
5410
 
5032
5411
  // ../api-routes/src/schedules.ts
5033
5412
  import crypto11 from "crypto";
5034
- import { eq as eq12 } from "drizzle-orm";
5413
+ import { eq as eq13 } from "drizzle-orm";
5035
5414
  async function scheduleRoutes(app, opts) {
5036
5415
  app.put("/projects/:name/schedule", async (request, reply) => {
5037
5416
  const project = resolveProject(app.db, request.params.name);
@@ -5074,7 +5453,7 @@ async function scheduleRoutes(app, opts) {
5074
5453
  }
5075
5454
  const now = (/* @__PURE__ */ new Date()).toISOString();
5076
5455
  const enabledInt = enabled === false ? 0 : 1;
5077
- const existing = app.db.select().from(schedules).where(eq12(schedules.projectId, project.id)).get();
5456
+ const existing = app.db.select().from(schedules).where(eq13(schedules.projectId, project.id)).get();
5078
5457
  if (existing) {
5079
5458
  app.db.update(schedules).set({
5080
5459
  cronExpr,
@@ -5083,7 +5462,7 @@ async function scheduleRoutes(app, opts) {
5083
5462
  providers: JSON.stringify(providers),
5084
5463
  enabled: enabledInt,
5085
5464
  updatedAt: now
5086
- }).where(eq12(schedules.id, existing.id)).run();
5465
+ }).where(eq13(schedules.id, existing.id)).run();
5087
5466
  } else {
5088
5467
  app.db.insert(schedules).values({
5089
5468
  id: crypto11.randomUUID(),
@@ -5105,12 +5484,12 @@ async function scheduleRoutes(app, opts) {
5105
5484
  diff: { cronExpr, preset, timezone, providers }
5106
5485
  });
5107
5486
  opts.onScheduleUpdated?.("upsert", project.id);
5108
- const schedule = app.db.select().from(schedules).where(eq12(schedules.projectId, project.id)).get();
5487
+ const schedule = app.db.select().from(schedules).where(eq13(schedules.projectId, project.id)).get();
5109
5488
  return reply.status(existing ? 200 : 201).send(formatSchedule(schedule));
5110
5489
  });
5111
5490
  app.get("/projects/:name/schedule", async (request, reply) => {
5112
5491
  const project = resolveProject(app.db, request.params.name);
5113
- const schedule = app.db.select().from(schedules).where(eq12(schedules.projectId, project.id)).get();
5492
+ const schedule = app.db.select().from(schedules).where(eq13(schedules.projectId, project.id)).get();
5114
5493
  if (!schedule) {
5115
5494
  throw notFound("Schedule", request.params.name);
5116
5495
  }
@@ -5118,11 +5497,11 @@ async function scheduleRoutes(app, opts) {
5118
5497
  });
5119
5498
  app.delete("/projects/:name/schedule", async (request, reply) => {
5120
5499
  const project = resolveProject(app.db, request.params.name);
5121
- const schedule = app.db.select().from(schedules).where(eq12(schedules.projectId, project.id)).get();
5500
+ const schedule = app.db.select().from(schedules).where(eq13(schedules.projectId, project.id)).get();
5122
5501
  if (!schedule) {
5123
5502
  throw notFound("Schedule", request.params.name);
5124
5503
  }
5125
- app.db.delete(schedules).where(eq12(schedules.id, schedule.id)).run();
5504
+ app.db.delete(schedules).where(eq13(schedules.id, schedule.id)).run();
5126
5505
  writeAuditLog(app.db, {
5127
5506
  projectId: project.id,
5128
5507
  actor: "api",
@@ -5152,7 +5531,7 @@ function formatSchedule(row) {
5152
5531
 
5153
5532
  // ../api-routes/src/notifications.ts
5154
5533
  import crypto12 from "crypto";
5155
- import { eq as eq13 } from "drizzle-orm";
5534
+ import { eq as eq14 } from "drizzle-orm";
5156
5535
  var VALID_EVENTS = ["citation.lost", "citation.gained", "run.completed", "run.failed", "insight.critical", "insight.high"];
5157
5536
  async function notificationRoutes(app) {
5158
5537
  app.get("/notifications/events", async (_request, reply) => {
@@ -5191,22 +5570,22 @@ async function notificationRoutes(app) {
5191
5570
  diff: { channel, ...redactNotificationUrl(url), events }
5192
5571
  });
5193
5572
  return reply.status(201).send({
5194
- ...formatNotification(app.db.select().from(notifications).where(eq13(notifications.id, id)).get()),
5573
+ ...formatNotification(app.db.select().from(notifications).where(eq14(notifications.id, id)).get()),
5195
5574
  webhookSecret
5196
5575
  });
5197
5576
  });
5198
5577
  app.get("/projects/:name/notifications", async (request, reply) => {
5199
5578
  const project = resolveProject(app.db, request.params.name);
5200
- const rows = app.db.select().from(notifications).where(eq13(notifications.projectId, project.id)).all();
5579
+ const rows = app.db.select().from(notifications).where(eq14(notifications.projectId, project.id)).all();
5201
5580
  return reply.send(rows.map(formatNotification));
5202
5581
  });
5203
5582
  app.delete("/projects/:name/notifications/:id", async (request, reply) => {
5204
5583
  const project = resolveProject(app.db, request.params.name);
5205
- const notification = app.db.select().from(notifications).where(eq13(notifications.id, request.params.id)).get();
5584
+ const notification = app.db.select().from(notifications).where(eq14(notifications.id, request.params.id)).get();
5206
5585
  if (!notification || notification.projectId !== project.id) {
5207
5586
  throw notFound("Notification", request.params.id);
5208
5587
  }
5209
- app.db.delete(notifications).where(eq13(notifications.id, notification.id)).run();
5588
+ app.db.delete(notifications).where(eq14(notifications.id, notification.id)).run();
5210
5589
  writeAuditLog(app.db, {
5211
5590
  projectId: project.id,
5212
5591
  actor: "api",
@@ -5218,7 +5597,7 @@ async function notificationRoutes(app) {
5218
5597
  });
5219
5598
  app.post("/projects/:name/notifications/:id/test", async (request, reply) => {
5220
5599
  const project = resolveProject(app.db, request.params.name);
5221
- const notification = app.db.select().from(notifications).where(eq13(notifications.id, request.params.id)).get();
5600
+ const notification = app.db.select().from(notifications).where(eq14(notifications.id, request.params.id)).get();
5222
5601
  if (!notification || notification.projectId !== project.id) {
5223
5602
  throw notFound("Notification", request.params.id);
5224
5603
  }
@@ -5271,7 +5650,7 @@ function formatNotification(row) {
5271
5650
 
5272
5651
  // ../api-routes/src/google.ts
5273
5652
  import crypto14 from "crypto";
5274
- import { eq as eq14, and as and3, desc as desc5, sql as sql3 } from "drizzle-orm";
5653
+ import { eq as eq15, and as and4, desc as desc6, sql as sql3 } from "drizzle-orm";
5275
5654
 
5276
5655
  // ../integration-google/src/constants.ts
5277
5656
  var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
@@ -6375,20 +6754,20 @@ async function googleRoutes(app, opts) {
6375
6754
  if (opts.onGscSyncRequested) {
6376
6755
  opts.onGscSyncRequested(runId, project.id, { days, full });
6377
6756
  }
6378
- const run = app.db.select().from(runs).where(eq14(runs.id, runId)).get();
6757
+ const run = app.db.select().from(runs).where(eq15(runs.id, runId)).get();
6379
6758
  return run;
6380
6759
  });
6381
6760
  app.get("/projects/:name/google/gsc/performance", async (request) => {
6382
6761
  const project = resolveProject(app.db, request.params.name);
6383
6762
  const { startDate, endDate, query, page, limit } = request.query;
6384
6763
  const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
6385
- const conditions = [eq14(gscSearchData.projectId, project.id)];
6764
+ const conditions = [eq15(gscSearchData.projectId, project.id)];
6386
6765
  if (startDate) conditions.push(sql3`${gscSearchData.date} >= ${startDate}`);
6387
6766
  else if (cutoffDate) conditions.push(sql3`${gscSearchData.date} >= ${cutoffDate}`);
6388
6767
  if (endDate) conditions.push(sql3`${gscSearchData.date} <= ${endDate}`);
6389
6768
  if (query) conditions.push(sql3`${gscSearchData.query} LIKE ${"%" + query + "%"}`);
6390
6769
  if (page) conditions.push(sql3`${gscSearchData.page} LIKE ${"%" + page + "%"}`);
6391
- const rows = app.db.select().from(gscSearchData).where(and3(...conditions)).orderBy(desc5(gscSearchData.date)).limit(parseInt(limit ?? "500", 10)).all();
6770
+ const rows = app.db.select().from(gscSearchData).where(and4(...conditions)).orderBy(desc6(gscSearchData.date)).limit(parseInt(limit ?? "500", 10)).all();
6392
6771
  return rows.map((r) => ({
6393
6772
  date: r.date,
6394
6773
  query: r.query,
@@ -6460,9 +6839,9 @@ async function googleRoutes(app, opts) {
6460
6839
  app.get("/projects/:name/google/gsc/inspections", async (request) => {
6461
6840
  const project = resolveProject(app.db, request.params.name);
6462
6841
  const { url, limit } = request.query;
6463
- const conditions = [eq14(gscUrlInspections.projectId, project.id)];
6464
- if (url) conditions.push(eq14(gscUrlInspections.url, url));
6465
- const rows = app.db.select().from(gscUrlInspections).where(and3(...conditions)).orderBy(desc5(gscUrlInspections.inspectedAt)).limit(parseInt(limit ?? "100", 10)).all();
6842
+ const conditions = [eq15(gscUrlInspections.projectId, project.id)];
6843
+ if (url) conditions.push(eq15(gscUrlInspections.url, url));
6844
+ const rows = app.db.select().from(gscUrlInspections).where(and4(...conditions)).orderBy(desc6(gscUrlInspections.inspectedAt)).limit(parseInt(limit ?? "100", 10)).all();
6466
6845
  return rows.map((r) => ({
6467
6846
  id: r.id,
6468
6847
  url: r.url,
@@ -6481,7 +6860,7 @@ async function googleRoutes(app, opts) {
6481
6860
  });
6482
6861
  app.get("/projects/:name/google/gsc/deindexed", async (request) => {
6483
6862
  const project = resolveProject(app.db, request.params.name);
6484
- const allInspections = app.db.select().from(gscUrlInspections).where(eq14(gscUrlInspections.projectId, project.id)).orderBy(desc5(gscUrlInspections.inspectedAt)).all();
6863
+ const allInspections = app.db.select().from(gscUrlInspections).where(eq15(gscUrlInspections.projectId, project.id)).orderBy(desc6(gscUrlInspections.inspectedAt)).all();
6485
6864
  const byUrl = /* @__PURE__ */ new Map();
6486
6865
  for (const row of allInspections) {
6487
6866
  const existing = byUrl.get(row.url);
@@ -6509,7 +6888,7 @@ async function googleRoutes(app, opts) {
6509
6888
  });
6510
6889
  app.get("/projects/:name/google/gsc/coverage", async (request) => {
6511
6890
  const project = resolveProject(app.db, request.params.name);
6512
- const allInspections = app.db.select().from(gscUrlInspections).where(eq14(gscUrlInspections.projectId, project.id)).orderBy(desc5(gscUrlInspections.inspectedAt)).all();
6891
+ const allInspections = app.db.select().from(gscUrlInspections).where(eq15(gscUrlInspections.projectId, project.id)).orderBy(desc6(gscUrlInspections.inspectedAt)).all();
6513
6892
  const canonicalUrl = (url) => url.replace(/^http:\/\//, "https://");
6514
6893
  const latestByUrl = /* @__PURE__ */ new Map();
6515
6894
  const historyByUrl = /* @__PURE__ */ new Map();
@@ -6606,7 +6985,7 @@ async function googleRoutes(app, opts) {
6606
6985
  const project = resolveProject(app.db, request.params.name);
6607
6986
  const parsed = parseInt(request.query.limit ?? "90", 10);
6608
6987
  const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
6609
- const rows = app.db.select().from(gscCoverageSnapshots).where(eq14(gscCoverageSnapshots.projectId, project.id)).orderBy(desc5(gscCoverageSnapshots.date)).limit(limit).all();
6988
+ const rows = app.db.select().from(gscCoverageSnapshots).where(eq15(gscCoverageSnapshots.projectId, project.id)).orderBy(desc6(gscCoverageSnapshots.date)).limit(limit).all();
6610
6989
  return rows.map((r) => ({
6611
6990
  date: r.date,
6612
6991
  indexed: r.indexed,
@@ -6666,7 +7045,7 @@ async function googleRoutes(app, opts) {
6666
7045
  if (opts.onInspectSitemapRequested) {
6667
7046
  opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl });
6668
7047
  }
6669
- const run = app.db.select().from(runs).where(eq14(runs.id, runId)).get();
7048
+ const run = app.db.select().from(runs).where(eq15(runs.id, runId)).get();
6670
7049
  return { sitemaps, primarySitemapUrl: sitemapUrl, run };
6671
7050
  });
6672
7051
  app.post("/projects/:name/google/gsc/inspect-sitemap", async (request) => {
@@ -6693,7 +7072,7 @@ async function googleRoutes(app, opts) {
6693
7072
  if (opts.onInspectSitemapRequested) {
6694
7073
  opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl: sitemapUrl ?? void 0 });
6695
7074
  }
6696
- const run = app.db.select().from(runs).where(eq14(runs.id, runId)).get();
7075
+ const run = app.db.select().from(runs).where(eq15(runs.id, runId)).get();
6697
7076
  return run;
6698
7077
  });
6699
7078
  app.put("/projects/:name/google/connections/:type/sitemap", async (request) => {
@@ -6740,7 +7119,7 @@ async function googleRoutes(app, opts) {
6740
7119
  const { accessToken } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
6741
7120
  let urlsToNotify = request.body?.urls ?? [];
6742
7121
  if (request.body?.allUnindexed) {
6743
- const allInspections = app.db.select().from(gscUrlInspections).where(eq14(gscUrlInspections.projectId, project.id)).orderBy(desc5(gscUrlInspections.inspectedAt)).all();
7122
+ const allInspections = app.db.select().from(gscUrlInspections).where(eq15(gscUrlInspections.projectId, project.id)).orderBy(desc6(gscUrlInspections.inspectedAt)).all();
6744
7123
  const latestByUrl = /* @__PURE__ */ new Map();
6745
7124
  for (const row of allInspections) {
6746
7125
  if (!latestByUrl.has(row.url)) {
@@ -6811,7 +7190,7 @@ async function googleRoutes(app, opts) {
6811
7190
 
6812
7191
  // ../api-routes/src/bing.ts
6813
7192
  import crypto15 from "crypto";
6814
- import { eq as eq15, and as and4, desc as desc6 } from "drizzle-orm";
7193
+ import { eq as eq16, and as and5, desc as desc7 } from "drizzle-orm";
6815
7194
 
6816
7195
  // ../integration-bing/src/constants.ts
6817
7196
  var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
@@ -7124,7 +7503,7 @@ async function bingRoutes(app, opts) {
7124
7503
  const store = requireConnectionStore();
7125
7504
  const project = resolveProject(app.db, request.params.name);
7126
7505
  requireConnection(store, project.canonicalDomain);
7127
- const allInspections = app.db.select().from(bingUrlInspections).where(eq15(bingUrlInspections.projectId, project.id)).orderBy(desc6(bingUrlInspections.inspectedAt)).all();
7506
+ const allInspections = app.db.select().from(bingUrlInspections).where(eq16(bingUrlInspections.projectId, project.id)).orderBy(desc7(bingUrlInspections.inspectedAt)).all();
7128
7507
  const latestByUrl = /* @__PURE__ */ new Map();
7129
7508
  const definitiveByUrl = /* @__PURE__ */ new Map();
7130
7509
  for (const row of allInspections) {
@@ -7213,7 +7592,7 @@ async function bingRoutes(app, opts) {
7213
7592
  const project = resolveProject(app.db, request.params.name);
7214
7593
  const parsed = parseInt(request.query.limit ?? "90", 10);
7215
7594
  const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
7216
- const rows = app.db.select().from(bingCoverageSnapshots).where(eq15(bingCoverageSnapshots.projectId, project.id)).orderBy(desc6(bingCoverageSnapshots.date)).limit(limit).all();
7595
+ const rows = app.db.select().from(bingCoverageSnapshots).where(eq16(bingCoverageSnapshots.projectId, project.id)).orderBy(desc7(bingCoverageSnapshots.date)).limit(limit).all();
7217
7596
  return rows.map((r) => ({
7218
7597
  date: r.date,
7219
7598
  indexed: r.indexed,
@@ -7225,8 +7604,8 @@ async function bingRoutes(app, opts) {
7225
7604
  requireConnectionStore();
7226
7605
  const project = resolveProject(app.db, request.params.name);
7227
7606
  const { url, limit } = request.query;
7228
- const whereClause = url ? and4(eq15(bingUrlInspections.projectId, project.id), eq15(bingUrlInspections.url, url)) : eq15(bingUrlInspections.projectId, project.id);
7229
- const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(desc6(bingUrlInspections.inspectedAt)).limit(Math.max(1, Math.min(parseInt(limit ?? "100", 10) || 100, 1e3))).all();
7607
+ const whereClause = url ? and5(eq16(bingUrlInspections.projectId, project.id), eq16(bingUrlInspections.url, url)) : eq16(bingUrlInspections.projectId, project.id);
7608
+ const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(desc7(bingUrlInspections.inspectedAt)).limit(Math.max(1, Math.min(parseInt(limit ?? "100", 10) || 100, 1e3))).all();
7230
7609
  return filtered.map((r) => ({
7231
7610
  id: r.id,
7232
7611
  url: r.url,
@@ -7315,7 +7694,7 @@ async function bingRoutes(app, opts) {
7315
7694
  anchorCount: result.AnchorCount ?? null,
7316
7695
  discoveryDate
7317
7696
  }).run();
7318
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq15(runs.id, runId)).run();
7697
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq16(runs.id, runId)).run();
7319
7698
  return {
7320
7699
  id,
7321
7700
  url,
@@ -7331,7 +7710,7 @@ async function bingRoutes(app, opts) {
7331
7710
  } catch (e) {
7332
7711
  const msg = e instanceof Error ? e.message : String(e);
7333
7712
  bingLog("error", "inspect-url.failed", { domain: project.canonicalDomain, url, error: msg });
7334
- app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq15(runs.id, runId)).run();
7713
+ app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq16(runs.id, runId)).run();
7335
7714
  throw e;
7336
7715
  }
7337
7716
  });
@@ -7358,7 +7737,7 @@ async function bingRoutes(app, opts) {
7358
7737
  } else {
7359
7738
  bingLog("warn", "inspect-sitemap.no-callback", { domain: project.canonicalDomain, runId });
7360
7739
  }
7361
- const run = app.db.select().from(runs).where(eq15(runs.id, runId)).get();
7740
+ const run = app.db.select().from(runs).where(eq16(runs.id, runId)).get();
7362
7741
  return run;
7363
7742
  });
7364
7743
  app.post("/projects/:name/bing/request-indexing", async (request) => {
@@ -7370,7 +7749,7 @@ async function bingRoutes(app, opts) {
7370
7749
  }
7371
7750
  let urlsToSubmit = request.body?.urls ?? [];
7372
7751
  if (request.body?.allUnindexed) {
7373
- const allInspections = app.db.select().from(bingUrlInspections).where(eq15(bingUrlInspections.projectId, project.id)).orderBy(desc6(bingUrlInspections.inspectedAt)).all();
7752
+ const allInspections = app.db.select().from(bingUrlInspections).where(eq16(bingUrlInspections.projectId, project.id)).orderBy(desc7(bingUrlInspections.inspectedAt)).all();
7374
7753
  const latestByUrl = /* @__PURE__ */ new Map();
7375
7754
  for (const row of allInspections) {
7376
7755
  if (!latestByUrl.has(row.url)) {
@@ -7457,14 +7836,14 @@ async function bingRoutes(app, opts) {
7457
7836
  import fs from "fs";
7458
7837
  import path from "path";
7459
7838
  import os from "os";
7460
- import { eq as eq16, and as and5 } from "drizzle-orm";
7839
+ import { eq as eq17, and as and6 } from "drizzle-orm";
7461
7840
  function getScreenshotDir() {
7462
7841
  return path.join(os.homedir(), ".canonry", "screenshots");
7463
7842
  }
7464
7843
  async function cdpRoutes(app, opts) {
7465
7844
  app.get("/screenshots/:snapshotId", async (request, reply) => {
7466
7845
  const { snapshotId } = request.params;
7467
- const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath }).from(querySnapshots).where(eq16(querySnapshots.id, snapshotId)).get();
7846
+ const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath }).from(querySnapshots).where(eq17(querySnapshots.id, snapshotId)).get();
7468
7847
  if (!snapshot?.screenshotPath) {
7469
7848
  const err = notFound("Screenshot", snapshotId);
7470
7849
  return reply.code(err.statusCode).send(err.toJSON());
@@ -7530,7 +7909,7 @@ async function cdpRoutes(app, opts) {
7530
7909
  async (request, reply) => {
7531
7910
  const project = resolveProject(app.db, request.params.name);
7532
7911
  const { runId } = request.params;
7533
- const run = app.db.select().from(runs).where(and5(eq16(runs.id, runId), eq16(runs.projectId, project.id))).get();
7912
+ const run = app.db.select().from(runs).where(and6(eq17(runs.id, runId), eq17(runs.projectId, project.id))).get();
7534
7913
  if (!run) {
7535
7914
  const err = notFound("Run", runId);
7536
7915
  return reply.code(err.statusCode).send(err.toJSON());
@@ -7543,8 +7922,8 @@ async function cdpRoutes(app, opts) {
7543
7922
  citedDomains: querySnapshots.citedDomains,
7544
7923
  screenshotPath: querySnapshots.screenshotPath,
7545
7924
  rawResponse: querySnapshots.rawResponse
7546
- }).from(querySnapshots).where(eq16(querySnapshots.runId, runId)).all();
7547
- const keywordRows = app.db.select({ id: keywords.id, keyword: keywords.keyword }).from(keywords).where(eq16(keywords.projectId, project.id)).all();
7925
+ }).from(querySnapshots).where(eq17(querySnapshots.runId, runId)).all();
7926
+ const keywordRows = app.db.select({ id: keywords.id, keyword: keywords.keyword }).from(keywords).where(eq17(keywords.projectId, project.id)).all();
7548
7927
  const keywordMap = new Map(keywordRows.map((k) => [k.id, k.keyword]));
7549
7928
  const byKeyword = /* @__PURE__ */ new Map();
7550
7929
  for (const snap of snapshots) {
@@ -7627,7 +8006,7 @@ async function cdpRoutes(app, opts) {
7627
8006
 
7628
8007
  // ../api-routes/src/ga.ts
7629
8008
  import crypto16 from "crypto";
7630
- import { eq as eq17, desc as desc7, and as and6, sql as sql4 } from "drizzle-orm";
8009
+ import { eq as eq18, desc as desc8, and as and7, sql as sql4 } from "drizzle-orm";
7631
8010
  function gaLog(level, action, ctx) {
7632
8011
  const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
7633
8012
  const stream = level === "error" ? process.stderr : process.stdout;
@@ -7784,10 +8163,10 @@ async function ga4Routes(app, opts) {
7784
8163
  if (!saConn && !oauthConn) {
7785
8164
  throw notFound("GA4 connection", project.name);
7786
8165
  }
7787
- app.db.delete(gaTrafficSnapshots).where(eq17(gaTrafficSnapshots.projectId, project.id)).run();
7788
- app.db.delete(gaTrafficSummaries).where(eq17(gaTrafficSummaries.projectId, project.id)).run();
7789
- app.db.delete(gaAiReferrals).where(eq17(gaAiReferrals.projectId, project.id)).run();
7790
- app.db.delete(gaSocialReferrals).where(eq17(gaSocialReferrals.projectId, project.id)).run();
8166
+ app.db.delete(gaTrafficSnapshots).where(eq18(gaTrafficSnapshots.projectId, project.id)).run();
8167
+ app.db.delete(gaTrafficSummaries).where(eq18(gaTrafficSummaries.projectId, project.id)).run();
8168
+ app.db.delete(gaAiReferrals).where(eq18(gaAiReferrals.projectId, project.id)).run();
8169
+ app.db.delete(gaSocialReferrals).where(eq18(gaSocialReferrals.projectId, project.id)).run();
7791
8170
  const propertyId = saConn?.propertyId ?? oauthConn?.propertyId ?? null;
7792
8171
  opts.ga4CredentialStore?.deleteConnection(project.name);
7793
8172
  opts.googleConnectionStore?.deleteConnection(project.canonicalDomain, "ga4");
@@ -7808,7 +8187,7 @@ async function ga4Routes(app, opts) {
7808
8187
  if (!connected) {
7809
8188
  return { connected: false, propertyId: null, clientEmail: null, authMethod: null, lastSyncedAt: null };
7810
8189
  }
7811
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq17(gaTrafficSummaries.projectId, project.id)).orderBy(desc7(gaTrafficSummaries.syncedAt)).limit(1).get();
8190
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq18(gaTrafficSummaries.projectId, project.id)).orderBy(desc8(gaTrafficSummaries.syncedAt)).limit(1).get();
7812
8191
  return {
7813
8192
  connected: true,
7814
8193
  propertyId: saConn?.propertyId ?? oauthConn?.propertyId ?? null,
@@ -7867,8 +8246,8 @@ async function ga4Routes(app, opts) {
7867
8246
  app.db.transaction((tx) => {
7868
8247
  if (syncTraffic) {
7869
8248
  tx.delete(gaTrafficSnapshots).where(
7870
- and6(
7871
- eq17(gaTrafficSnapshots.projectId, project.id),
8249
+ and7(
8250
+ eq18(gaTrafficSnapshots.projectId, project.id),
7872
8251
  sql4`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
7873
8252
  sql4`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
7874
8253
  )
@@ -7889,8 +8268,8 @@ async function ga4Routes(app, opts) {
7889
8268
  }
7890
8269
  if (syncAi) {
7891
8270
  tx.delete(gaAiReferrals).where(
7892
- and6(
7893
- eq17(gaAiReferrals.projectId, project.id),
8271
+ and7(
8272
+ eq18(gaAiReferrals.projectId, project.id),
7894
8273
  sql4`${gaAiReferrals.date} >= ${summary.periodStart}`,
7895
8274
  sql4`${gaAiReferrals.date} <= ${summary.periodEnd}`
7896
8275
  )
@@ -7912,8 +8291,8 @@ async function ga4Routes(app, opts) {
7912
8291
  }
7913
8292
  if (syncSocial) {
7914
8293
  tx.delete(gaSocialReferrals).where(
7915
- and6(
7916
- eq17(gaSocialReferrals.projectId, project.id),
8294
+ and7(
8295
+ eq18(gaSocialReferrals.projectId, project.id),
7917
8296
  sql4`${gaSocialReferrals.date} >= ${summary.periodStart}`,
7918
8297
  sql4`${gaSocialReferrals.date} <= ${summary.periodEnd}`
7919
8298
  )
@@ -7934,7 +8313,7 @@ async function ga4Routes(app, opts) {
7934
8313
  }
7935
8314
  }
7936
8315
  if (syncSummary) {
7937
- tx.delete(gaTrafficSummaries).where(eq17(gaTrafficSummaries.projectId, project.id)).run();
8316
+ tx.delete(gaTrafficSummaries).where(eq18(gaTrafficSummaries.projectId, project.id)).run();
7938
8317
  tx.insert(gaTrafficSummaries).values({
7939
8318
  id: crypto16.randomUUID(),
7940
8319
  projectId: project.id,
@@ -7948,7 +8327,7 @@ async function ga4Routes(app, opts) {
7948
8327
  }).run();
7949
8328
  }
7950
8329
  });
7951
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq17(runs.id, runId)).run();
8330
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq18(runs.id, runId)).run();
7952
8331
  const syncedComponents = only ? [only, ...only !== "social" && only !== "ai" && only !== "traffic" ? [] : []] : void 0;
7953
8332
  gaLog("info", "sync.complete", {
7954
8333
  projectId: project.id,
@@ -7972,7 +8351,7 @@ async function ga4Routes(app, opts) {
7972
8351
  } catch (e) {
7973
8352
  const msg = e instanceof Error ? e.message : String(e);
7974
8353
  gaLog("error", "sync.fetch-failed", { projectId: project.id, runId, error: msg });
7975
- app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq17(runs.id, runId)).run();
8354
+ app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq18(runs.id, runId)).run();
7976
8355
  throw e;
7977
8356
  }
7978
8357
  });
@@ -7983,38 +8362,38 @@ async function ga4Routes(app, opts) {
7983
8362
  const window = parseWindow(request.query.window);
7984
8363
  const cutoff = windowCutoff(window);
7985
8364
  const cutoffDate = cutoff?.slice(0, 10) ?? null;
7986
- const snapshotConditions = [eq17(gaTrafficSnapshots.projectId, project.id)];
8365
+ const snapshotConditions = [eq18(gaTrafficSnapshots.projectId, project.id)];
7987
8366
  if (cutoffDate) snapshotConditions.push(sql4`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
7988
- const aiConditions = [eq17(gaAiReferrals.projectId, project.id)];
8367
+ const aiConditions = [eq18(gaAiReferrals.projectId, project.id)];
7989
8368
  if (cutoffDate) aiConditions.push(sql4`${gaAiReferrals.date} >= ${cutoffDate}`);
7990
- const socialConditions = [eq17(gaSocialReferrals.projectId, project.id)];
8369
+ const socialConditions = [eq18(gaSocialReferrals.projectId, project.id)];
7991
8370
  if (cutoffDate) socialConditions.push(sql4`${gaSocialReferrals.date} >= ${cutoffDate}`);
7992
8371
  const summaryRow = cutoffDate ? app.db.select({
7993
8372
  totalSessions: sql4`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
7994
8373
  totalOrganicSessions: sql4`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
7995
8374
  totalUsers: sql4`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
7996
- }).from(gaTrafficSnapshots).where(and6(...snapshotConditions)).get() : app.db.select({
8375
+ }).from(gaTrafficSnapshots).where(and7(...snapshotConditions)).get() : app.db.select({
7997
8376
  totalSessions: gaTrafficSummaries.totalSessions,
7998
8377
  totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
7999
8378
  totalUsers: gaTrafficSummaries.totalUsers
8000
- }).from(gaTrafficSummaries).where(eq17(gaTrafficSummaries.projectId, project.id)).get();
8379
+ }).from(gaTrafficSummaries).where(eq18(gaTrafficSummaries.projectId, project.id)).get();
8001
8380
  const summaryMeta = app.db.select({
8002
8381
  periodStart: gaTrafficSummaries.periodStart,
8003
8382
  periodEnd: gaTrafficSummaries.periodEnd
8004
- }).from(gaTrafficSummaries).where(eq17(gaTrafficSummaries.projectId, project.id)).get();
8383
+ }).from(gaTrafficSummaries).where(eq18(gaTrafficSummaries.projectId, project.id)).get();
8005
8384
  const rows = app.db.select({
8006
8385
  landingPage: gaTrafficSnapshots.landingPage,
8007
8386
  sessions: sql4`SUM(${gaTrafficSnapshots.sessions})`,
8008
8387
  organicSessions: sql4`SUM(${gaTrafficSnapshots.organicSessions})`,
8009
8388
  users: sql4`SUM(${gaTrafficSnapshots.users})`
8010
- }).from(gaTrafficSnapshots).where(and6(...snapshotConditions)).groupBy(gaTrafficSnapshots.landingPage).orderBy(sql4`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
8389
+ }).from(gaTrafficSnapshots).where(and7(...snapshotConditions)).groupBy(gaTrafficSnapshots.landingPage).orderBy(sql4`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
8011
8390
  const aiReferrals = app.db.select({
8012
8391
  source: gaAiReferrals.source,
8013
8392
  medium: gaAiReferrals.medium,
8014
8393
  sourceDimension: gaAiReferrals.sourceDimension,
8015
8394
  sessions: sql4`SUM(${gaAiReferrals.sessions})`,
8016
8395
  users: sql4`SUM(${gaAiReferrals.users})`
8017
- }).from(gaAiReferrals).where(and6(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).orderBy(sql4`SUM(${gaAiReferrals.sessions}) DESC`).all();
8396
+ }).from(gaAiReferrals).where(and7(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).orderBy(sql4`SUM(${gaAiReferrals.sessions}) DESC`).all();
8018
8397
  const aiDeduped = app.db.select({
8019
8398
  sessions: sql4`SUM(max_sessions)`,
8020
8399
  users: sql4`SUM(max_users)`
@@ -8034,12 +8413,12 @@ async function ga4Routes(app, opts) {
8034
8413
  channelGroup: gaSocialReferrals.channelGroup,
8035
8414
  sessions: sql4`SUM(${gaSocialReferrals.sessions})`,
8036
8415
  users: sql4`SUM(${gaSocialReferrals.users})`
8037
- }).from(gaSocialReferrals).where(and6(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql4`SUM(${gaSocialReferrals.sessions}) DESC`).all();
8416
+ }).from(gaSocialReferrals).where(and7(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql4`SUM(${gaSocialReferrals.sessions}) DESC`).all();
8038
8417
  const socialTotals = app.db.select({
8039
8418
  sessions: sql4`SUM(${gaSocialReferrals.sessions})`,
8040
8419
  users: sql4`SUM(${gaSocialReferrals.users})`
8041
- }).from(gaSocialReferrals).where(and6(...socialConditions)).get();
8042
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq17(gaTrafficSummaries.projectId, project.id)).orderBy(desc7(gaTrafficSummaries.syncedAt)).limit(1).get();
8420
+ }).from(gaSocialReferrals).where(and7(...socialConditions)).get();
8421
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq18(gaTrafficSummaries.projectId, project.id)).orderBy(desc8(gaTrafficSummaries.syncedAt)).limit(1).get();
8043
8422
  const total = summaryRow?.totalSessions ?? 0;
8044
8423
  return {
8045
8424
  totalSessions: total,
@@ -8086,7 +8465,7 @@ async function ga4Routes(app, opts) {
8086
8465
  const project = resolveProject(app.db, request.params.name);
8087
8466
  requireGa4Connection(opts, project.name, project.canonicalDomain);
8088
8467
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
8089
- const conditions = [eq17(gaAiReferrals.projectId, project.id)];
8468
+ const conditions = [eq18(gaAiReferrals.projectId, project.id)];
8090
8469
  if (cutoffDate) conditions.push(sql4`${gaAiReferrals.date} >= ${cutoffDate}`);
8091
8470
  const rows = app.db.select({
8092
8471
  date: gaAiReferrals.date,
@@ -8095,14 +8474,14 @@ async function ga4Routes(app, opts) {
8095
8474
  sourceDimension: gaAiReferrals.sourceDimension,
8096
8475
  sessions: gaAiReferrals.sessions,
8097
8476
  users: gaAiReferrals.users
8098
- }).from(gaAiReferrals).where(and6(...conditions)).orderBy(gaAiReferrals.date).all();
8477
+ }).from(gaAiReferrals).where(and7(...conditions)).orderBy(gaAiReferrals.date).all();
8099
8478
  return rows;
8100
8479
  });
8101
8480
  app.get("/projects/:name/ga/social-referral-history", async (request, _reply) => {
8102
8481
  const project = resolveProject(app.db, request.params.name);
8103
8482
  requireGa4Connection(opts, project.name, project.canonicalDomain);
8104
8483
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
8105
- const conditions = [eq17(gaSocialReferrals.projectId, project.id)];
8484
+ const conditions = [eq18(gaSocialReferrals.projectId, project.id)];
8106
8485
  if (cutoffDate) conditions.push(sql4`${gaSocialReferrals.date} >= ${cutoffDate}`);
8107
8486
  const rows = app.db.select({
8108
8487
  date: gaSocialReferrals.date,
@@ -8111,7 +8490,7 @@ async function ga4Routes(app, opts) {
8111
8490
  channelGroup: gaSocialReferrals.channelGroup,
8112
8491
  sessions: gaSocialReferrals.sessions,
8113
8492
  users: gaSocialReferrals.users
8114
- }).from(gaSocialReferrals).where(and6(...conditions)).orderBy(gaSocialReferrals.date).all();
8493
+ }).from(gaSocialReferrals).where(and7(...conditions)).orderBy(gaSocialReferrals.date).all();
8115
8494
  return rows;
8116
8495
  });
8117
8496
  app.get("/projects/:name/ga/social-referral-trend", async (request, _reply) => {
@@ -8124,8 +8503,8 @@ async function ga4Routes(app, opts) {
8124
8503
  d.setDate(d.getDate() - n);
8125
8504
  return fmt(d);
8126
8505
  };
8127
- const sumSocial = (from, to) => app.db.select({ sessions: sql4`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and6(
8128
- eq17(gaSocialReferrals.projectId, project.id),
8506
+ const sumSocial = (from, to) => app.db.select({ sessions: sql4`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and7(
8507
+ eq18(gaSocialReferrals.projectId, project.id),
8129
8508
  sql4`${gaSocialReferrals.date} >= ${from}`,
8130
8509
  sql4`${gaSocialReferrals.date} < ${to}`
8131
8510
  )).get();
@@ -8137,16 +8516,16 @@ async function ga4Routes(app, opts) {
8137
8516
  const sourceCurrent = app.db.select({
8138
8517
  source: gaSocialReferrals.source,
8139
8518
  sessions: sql4`SUM(${gaSocialReferrals.sessions})`
8140
- }).from(gaSocialReferrals).where(and6(
8141
- eq17(gaSocialReferrals.projectId, project.id),
8519
+ }).from(gaSocialReferrals).where(and7(
8520
+ eq18(gaSocialReferrals.projectId, project.id),
8142
8521
  sql4`${gaSocialReferrals.date} >= ${daysAgo2(7)}`,
8143
8522
  sql4`${gaSocialReferrals.date} < ${fmt(today)}`
8144
8523
  )).groupBy(gaSocialReferrals.source).all();
8145
8524
  const sourcePrev = app.db.select({
8146
8525
  source: gaSocialReferrals.source,
8147
8526
  sessions: sql4`SUM(${gaSocialReferrals.sessions})`
8148
- }).from(gaSocialReferrals).where(and6(
8149
- eq17(gaSocialReferrals.projectId, project.id),
8527
+ }).from(gaSocialReferrals).where(and7(
8528
+ eq18(gaSocialReferrals.projectId, project.id),
8150
8529
  sql4`${gaSocialReferrals.date} >= ${daysAgo2(14)}`,
8151
8530
  sql4`${gaSocialReferrals.date} < ${daysAgo2(7)}`
8152
8531
  )).groupBy(gaSocialReferrals.source).all();
@@ -8187,15 +8566,15 @@ async function ga4Routes(app, opts) {
8187
8566
  return fmt(d);
8188
8567
  };
8189
8568
  const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
8190
- const sumTotal = (from, to) => app.db.select({ sessions: sql4`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and6(eq17(gaTrafficSnapshots.projectId, project.id), sql4`${gaTrafficSnapshots.date} >= ${from}`, sql4`${gaTrafficSnapshots.date} < ${to}`)).get();
8191
- const sumOrganic = (from, to) => app.db.select({ sessions: sql4`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and6(eq17(gaTrafficSnapshots.projectId, project.id), sql4`${gaTrafficSnapshots.date} >= ${from}`, sql4`${gaTrafficSnapshots.date} < ${to}`)).get();
8569
+ const sumTotal = (from, to) => app.db.select({ sessions: sql4`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and7(eq18(gaTrafficSnapshots.projectId, project.id), sql4`${gaTrafficSnapshots.date} >= ${from}`, sql4`${gaTrafficSnapshots.date} < ${to}`)).get();
8570
+ const sumOrganic = (from, to) => app.db.select({ sessions: sql4`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and7(eq18(gaTrafficSnapshots.projectId, project.id), sql4`${gaTrafficSnapshots.date} >= ${from}`, sql4`${gaTrafficSnapshots.date} < ${to}`)).get();
8192
8571
  const sumAi = (from, to) => app.db.select({ sessions: sql4`COALESCE(SUM(max_sessions), 0)` }).from(sql4`(
8193
8572
  SELECT date, source, medium, MAX(sessions) AS max_sessions
8194
8573
  FROM ga_ai_referrals
8195
8574
  WHERE project_id = ${project.id} AND date >= ${from} AND date < ${to}
8196
8575
  GROUP BY date, source, medium
8197
8576
  )`).get();
8198
- const sumSocial = (from, to) => app.db.select({ sessions: sql4`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and6(eq17(gaSocialReferrals.projectId, project.id), sql4`${gaSocialReferrals.date} >= ${from}`, sql4`${gaSocialReferrals.date} < ${to}`)).get();
8577
+ const sumSocial = (from, to) => app.db.select({ sessions: sql4`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and7(eq18(gaSocialReferrals.projectId, project.id), sql4`${gaSocialReferrals.date} >= ${from}`, sql4`${gaSocialReferrals.date} < ${to}`)).get();
8199
8578
  const todayStr = fmt(today);
8200
8579
  const buildTrend = (sum) => {
8201
8580
  const c7 = sum(daysAgo2(7), todayStr)?.sessions ?? 0;
@@ -8230,8 +8609,8 @@ async function ga4Routes(app, opts) {
8230
8609
  }
8231
8610
  return mover;
8232
8611
  };
8233
- const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql4`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and6(eq17(gaSocialReferrals.projectId, project.id), sql4`${gaSocialReferrals.date} >= ${daysAgo2(7)}`, sql4`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
8234
- const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql4`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and6(eq17(gaSocialReferrals.projectId, project.id), sql4`${gaSocialReferrals.date} >= ${daysAgo2(14)}`, sql4`${gaSocialReferrals.date} < ${daysAgo2(7)}`)).groupBy(gaSocialReferrals.source).all();
8612
+ const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql4`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and7(eq18(gaSocialReferrals.projectId, project.id), sql4`${gaSocialReferrals.date} >= ${daysAgo2(7)}`, sql4`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
8613
+ const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql4`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and7(eq18(gaSocialReferrals.projectId, project.id), sql4`${gaSocialReferrals.date} >= ${daysAgo2(14)}`, sql4`${gaSocialReferrals.date} < ${daysAgo2(7)}`)).groupBy(gaSocialReferrals.source).all();
8235
8614
  return {
8236
8615
  total: buildTrend(sumTotal),
8237
8616
  organic: buildTrend(sumOrganic),
@@ -8245,14 +8624,14 @@ async function ga4Routes(app, opts) {
8245
8624
  const project = resolveProject(app.db, request.params.name);
8246
8625
  requireGa4Connection(opts, project.name, project.canonicalDomain);
8247
8626
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
8248
- const conditions = [eq17(gaTrafficSnapshots.projectId, project.id)];
8627
+ const conditions = [eq18(gaTrafficSnapshots.projectId, project.id)];
8249
8628
  if (cutoffDate) conditions.push(sql4`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
8250
8629
  const rows = app.db.select({
8251
8630
  date: gaTrafficSnapshots.date,
8252
8631
  sessions: sql4`SUM(${gaTrafficSnapshots.sessions})`,
8253
8632
  organicSessions: sql4`SUM(${gaTrafficSnapshots.organicSessions})`,
8254
8633
  users: sql4`SUM(${gaTrafficSnapshots.users})`
8255
- }).from(gaTrafficSnapshots).where(and6(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
8634
+ }).from(gaTrafficSnapshots).where(and7(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
8256
8635
  return rows.map((r) => ({
8257
8636
  date: r.date,
8258
8637
  sessions: r.sessions ?? 0,
@@ -8268,7 +8647,7 @@ async function ga4Routes(app, opts) {
8268
8647
  sessions: sql4`SUM(${gaTrafficSnapshots.sessions})`,
8269
8648
  organicSessions: sql4`SUM(${gaTrafficSnapshots.organicSessions})`,
8270
8649
  users: sql4`SUM(${gaTrafficSnapshots.users})`
8271
- }).from(gaTrafficSnapshots).where(eq17(gaTrafficSnapshots.projectId, project.id)).groupBy(gaTrafficSnapshots.landingPage).orderBy(sql4`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
8650
+ }).from(gaTrafficSnapshots).where(eq18(gaTrafficSnapshots.projectId, project.id)).groupBy(gaTrafficSnapshots.landingPage).orderBy(sql4`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
8272
8651
  return {
8273
8652
  pages: trafficPages.map((r) => ({
8274
8653
  landingPage: r.landingPage,
@@ -9905,7 +10284,7 @@ async function wordpressRoutes(app, opts) {
9905
10284
 
9906
10285
  // ../api-routes/src/backlinks.ts
9907
10286
  import crypto18 from "crypto";
9908
- import { and as and7, asc as asc2, desc as desc8, eq as eq18, sql as sql5 } from "drizzle-orm";
10287
+ import { and as and8, asc as asc2, desc as desc9, eq as eq19, sql as sql5 } from "drizzle-orm";
9909
10288
 
9910
10289
  // ../integration-commoncrawl/src/constants.ts
9911
10290
  import os2 from "os";
@@ -10303,8 +10682,8 @@ function mapRunRow(row) {
10303
10682
  };
10304
10683
  }
10305
10684
  function latestSummaryForProject(db, projectId, release) {
10306
- const condition = release ? and7(eq18(backlinkSummaries.projectId, projectId), eq18(backlinkSummaries.release, release)) : eq18(backlinkSummaries.projectId, projectId);
10307
- return db.select().from(backlinkSummaries).where(condition).orderBy(desc8(backlinkSummaries.queriedAt)).limit(1).get();
10685
+ const condition = release ? and8(eq19(backlinkSummaries.projectId, projectId), eq19(backlinkSummaries.release, release)) : eq19(backlinkSummaries.projectId, projectId);
10686
+ return db.select().from(backlinkSummaries).where(condition).orderBy(desc9(backlinkSummaries.queriedAt)).limit(1).get();
10308
10687
  }
10309
10688
  async function backlinksRoutes(app, opts) {
10310
10689
  app.get("/backlinks/status", async (_request, reply) => {
@@ -10333,7 +10712,7 @@ async function backlinksRoutes(app, opts) {
10333
10712
  "@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
10334
10713
  );
10335
10714
  }
10336
- const existing = app.db.select().from(ccReleaseSyncs).where(eq18(ccReleaseSyncs.release, release)).get();
10715
+ const existing = app.db.select().from(ccReleaseSyncs).where(eq19(ccReleaseSyncs.release, release)).get();
10337
10716
  const now = (/* @__PURE__ */ new Date()).toISOString();
10338
10717
  if (existing) {
10339
10718
  if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
@@ -10344,9 +10723,9 @@ async function backlinksRoutes(app, opts) {
10344
10723
  phaseDetail: null,
10345
10724
  error: null,
10346
10725
  updatedAt: now
10347
- }).where(eq18(ccReleaseSyncs.id, existing.id)).run();
10726
+ }).where(eq19(ccReleaseSyncs.id, existing.id)).run();
10348
10727
  opts.onReleaseSyncRequested(existing.id, release);
10349
- const refreshed = app.db.select().from(ccReleaseSyncs).where(eq18(ccReleaseSyncs.id, existing.id)).get();
10728
+ const refreshed = app.db.select().from(ccReleaseSyncs).where(eq19(ccReleaseSyncs.id, existing.id)).get();
10350
10729
  return reply.status(200).send(mapSyncRow(refreshed));
10351
10730
  }
10352
10731
  const id = crypto18.randomUUID();
@@ -10358,15 +10737,15 @@ async function backlinksRoutes(app, opts) {
10358
10737
  updatedAt: now
10359
10738
  }).run();
10360
10739
  opts.onReleaseSyncRequested(id, release);
10361
- const inserted = app.db.select().from(ccReleaseSyncs).where(eq18(ccReleaseSyncs.id, id)).get();
10740
+ const inserted = app.db.select().from(ccReleaseSyncs).where(eq19(ccReleaseSyncs.id, id)).get();
10362
10741
  return reply.status(201).send(mapSyncRow(inserted));
10363
10742
  });
10364
10743
  app.get("/backlinks/syncs/latest", async (_request, reply) => {
10365
- const row = app.db.select().from(ccReleaseSyncs).orderBy(desc8(ccReleaseSyncs.updatedAt)).limit(1).get();
10744
+ const row = app.db.select().from(ccReleaseSyncs).orderBy(desc9(ccReleaseSyncs.updatedAt)).limit(1).get();
10366
10745
  return reply.send(row ? mapSyncRow(row) : null);
10367
10746
  });
10368
10747
  app.get("/backlinks/syncs", async (_request, reply) => {
10369
- const rows = app.db.select().from(ccReleaseSyncs).orderBy(desc8(ccReleaseSyncs.updatedAt)).all();
10748
+ const rows = app.db.select().from(ccReleaseSyncs).orderBy(desc9(ccReleaseSyncs.updatedAt)).all();
10370
10749
  return reply.send(rows.map(mapSyncRow));
10371
10750
  });
10372
10751
  app.get("/backlinks/releases", async (_request, reply) => {
@@ -10409,7 +10788,7 @@ async function backlinksRoutes(app, opts) {
10409
10788
  createdAt: now
10410
10789
  }).run();
10411
10790
  opts.onBacklinkExtractRequested(runId, project.id, release);
10412
- const run = app.db.select().from(runs).where(eq18(runs.id, runId)).get();
10791
+ const run = app.db.select().from(runs).where(eq19(runs.id, runId)).get();
10413
10792
  return reply.status(201).send(mapRunRow(run));
10414
10793
  });
10415
10794
  app.get(
@@ -10430,15 +10809,15 @@ async function backlinksRoutes(app, opts) {
10430
10809
  }
10431
10810
  const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
10432
10811
  const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
10433
- const domainCondition = and7(
10434
- eq18(backlinkDomains.projectId, project.id),
10435
- eq18(backlinkDomains.release, targetRelease)
10812
+ const domainCondition = and8(
10813
+ eq19(backlinkDomains.projectId, project.id),
10814
+ eq19(backlinkDomains.release, targetRelease)
10436
10815
  );
10437
10816
  const totalRow = app.db.select({ count: sql5`count(*)` }).from(backlinkDomains).where(domainCondition).get();
10438
10817
  const rows = app.db.select({
10439
10818
  linkingDomain: backlinkDomains.linkingDomain,
10440
10819
  numHosts: backlinkDomains.numHosts
10441
- }).from(backlinkDomains).where(domainCondition).orderBy(desc8(backlinkDomains.numHosts)).limit(limit).offset(offset).all();
10820
+ }).from(backlinkDomains).where(domainCondition).orderBy(desc9(backlinkDomains.numHosts)).limit(limit).offset(offset).all();
10442
10821
  const response = {
10443
10822
  summary: summaryRow ? mapSummaryRow(summaryRow) : null,
10444
10823
  total: Number(totalRow?.count ?? 0),
@@ -10450,7 +10829,7 @@ async function backlinksRoutes(app, opts) {
10450
10829
  "/projects/:name/backlinks/history",
10451
10830
  async (request, reply) => {
10452
10831
  const project = resolveProject(app.db, request.params.name);
10453
- const rows = app.db.select().from(backlinkSummaries).where(eq18(backlinkSummaries.projectId, project.id)).orderBy(asc2(backlinkSummaries.queriedAt)).all();
10832
+ const rows = app.db.select().from(backlinkSummaries).where(eq19(backlinkSummaries.projectId, project.id)).orderBy(asc2(backlinkSummaries.queriedAt)).all();
10454
10833
  const response = rows.map((r) => ({
10455
10834
  release: r.release,
10456
10835
  totalLinkingDomains: r.totalLinkingDomains,
@@ -10523,6 +10902,7 @@ async function apiRoutes(app, opts) {
10523
10902
  await api.register(historyRoutes);
10524
10903
  await api.register(analyticsRoutes);
10525
10904
  await api.register(intelligenceRoutes);
10905
+ await api.register(contentRoutes);
10526
10906
  await api.register(settingsRoutes, {
10527
10907
  providerSummary: opts.providerSummary,
10528
10908
  providerAdapters: opts.providerAdapters,
@@ -12668,7 +13048,7 @@ function hasParsedResponseContent4(rawResponse) {
12668
13048
  return Array.isArray(nestedResponse.choices) && nestedResponse.choices.length > 0 || Array.isArray(nestedResponse.search_results) && nestedResponse.search_results.length > 0 || Array.isArray(nestedResponse.citations) && nestedResponse.citations.length > 0;
12669
13049
  }
12670
13050
  function reparseStoredResult4(rawResponse) {
12671
- const groundingSources = extractGroundingSources(rawResponse);
13051
+ const groundingSources = extractGroundingSources2(rawResponse);
12672
13052
  return {
12673
13053
  provider: "perplexity",
12674
13054
  answerText: extractAnswerText3(rawResponse),
@@ -12699,7 +13079,7 @@ function extractCitations(rawResponse) {
12699
13079
  }
12700
13080
  return [];
12701
13081
  }
12702
- function extractGroundingSources(rawResponse) {
13082
+ function extractGroundingSources2(rawResponse) {
12703
13083
  const searchResults = extractSearchResults(rawResponse);
12704
13084
  if (searchResults.length > 0) {
12705
13085
  const seen = /* @__PURE__ */ new Set();
@@ -13044,7 +13424,7 @@ import crypto19 from "crypto";
13044
13424
  import fs7 from "fs";
13045
13425
  import path9 from "path";
13046
13426
  import os4 from "os";
13047
- import { and as and8, eq as eq19, inArray as inArray3, sql as sql6 } from "drizzle-orm";
13427
+ import { and as and9, eq as eq20, inArray as inArray4, sql as sql6 } from "drizzle-orm";
13048
13428
 
13049
13429
  // src/citation-utils.ts
13050
13430
  function domainMatches(domain, canonicalDomain) {
@@ -13296,11 +13676,11 @@ var JobRunner = class {
13296
13676
  this.registry = registry;
13297
13677
  }
13298
13678
  recoverStaleRuns() {
13299
- const stale = this.db.select({ id: runs.id, status: runs.status }).from(runs).where(inArray3(runs.status, ["running", "queued"])).all();
13679
+ const stale = this.db.select({ id: runs.id, status: runs.status }).from(runs).where(inArray4(runs.status, ["running", "queued"])).all();
13300
13680
  if (stale.length === 0) return;
13301
13681
  const now = (/* @__PURE__ */ new Date()).toISOString();
13302
13682
  for (const run of stale) {
13303
- this.db.update(runs).set({ status: "failed", finishedAt: now, error: "Server restarted while run was in progress" }).where(eq19(runs.id, run.id)).run();
13683
+ this.db.update(runs).set({ status: "failed", finishedAt: now, error: "Server restarted while run was in progress" }).where(eq20(runs.id, run.id)).run();
13304
13684
  log.warn("run.recovered-stale", { runId: run.id, previousStatus: run.status });
13305
13685
  }
13306
13686
  }
@@ -13328,10 +13708,10 @@ var JobRunner = class {
13328
13708
  throw new Error(`Run ${runId} is not executable from status '${existingRun.status}'`);
13329
13709
  }
13330
13710
  if (existingRun.status === "queued") {
13331
- this.db.update(runs).set({ status: "running", startedAt: now }).where(and8(eq19(runs.id, runId), eq19(runs.status, "queued"))).run();
13711
+ this.db.update(runs).set({ status: "running", startedAt: now }).where(and9(eq20(runs.id, runId), eq20(runs.status, "queued"))).run();
13332
13712
  }
13333
13713
  this.throwIfRunCancelled(runId);
13334
- const project = this.db.select().from(projects).where(eq19(projects.id, projectId)).get();
13714
+ const project = this.db.select().from(projects).where(eq20(projects.id, projectId)).get();
13335
13715
  if (!project) {
13336
13716
  throw new Error(`Project ${projectId} not found`);
13337
13717
  }
@@ -13351,8 +13731,8 @@ var JobRunner = class {
13351
13731
  throw new Error("No providers configured. Add at least one provider API key.");
13352
13732
  }
13353
13733
  log.info("run.dispatch", { runId, providerCount: activeProviders.length, providers: activeProviders.map((p) => p.adapter.name) });
13354
- projectKeywords = this.db.select().from(keywords).where(eq19(keywords.projectId, projectId)).all();
13355
- const projectCompetitors = this.db.select().from(competitors).where(eq19(competitors.projectId, projectId)).all();
13734
+ projectKeywords = this.db.select().from(keywords).where(eq20(keywords.projectId, projectId)).all();
13735
+ const projectCompetitors = this.db.select().from(competitors).where(eq20(competitors.projectId, projectId)).all();
13356
13736
  const competitorDomains = projectCompetitors.map((c) => c.domain);
13357
13737
  const allDomains = effectiveDomains({
13358
13738
  canonicalDomain: project.canonicalDomain,
@@ -13368,7 +13748,7 @@ var JobRunner = class {
13368
13748
  const todayPeriod = getCurrentUsageDay();
13369
13749
  for (const p of activeProviders) {
13370
13750
  const providerScope = `${projectId}:${p.adapter.name}`;
13371
- const providerUsage = this.db.select().from(usageCounters).where(eq19(usageCounters.scope, providerScope)).all().filter((r) => r.period === todayPeriod && r.metric === "queries").reduce((sum, r) => sum + r.count, 0);
13751
+ const providerUsage = this.db.select().from(usageCounters).where(eq20(usageCounters.scope, providerScope)).all().filter((r) => r.period === todayPeriod && r.metric === "queries").reduce((sum, r) => sum + r.count, 0);
13372
13752
  const limit = p.config.quotaPolicy.maxRequestsPerDay;
13373
13753
  if (providerUsage + queriesPerProvider > limit) {
13374
13754
  throw new Error(
@@ -13509,12 +13889,12 @@ var JobRunner = class {
13509
13889
  const someFailed = providerErrors.size > 0;
13510
13890
  if (allFailed) {
13511
13891
  const errorDetail = serializeRunError(buildRunErrorFromMessages(providerErrors));
13512
- this.db.update(runs).set({ status: "failed", finishedAt: (/* @__PURE__ */ new Date()).toISOString(), error: errorDetail }).where(eq19(runs.id, runId)).run();
13892
+ this.db.update(runs).set({ status: "failed", finishedAt: (/* @__PURE__ */ new Date()).toISOString(), error: errorDetail }).where(eq20(runs.id, runId)).run();
13513
13893
  } else if (someFailed) {
13514
13894
  const errorDetail = serializeRunError(buildRunErrorFromMessages(providerErrors));
13515
- this.db.update(runs).set({ status: "partial", finishedAt: (/* @__PURE__ */ new Date()).toISOString(), error: errorDetail }).where(eq19(runs.id, runId)).run();
13895
+ this.db.update(runs).set({ status: "partial", finishedAt: (/* @__PURE__ */ new Date()).toISOString(), error: errorDetail }).where(eq20(runs.id, runId)).run();
13516
13896
  } else {
13517
- this.db.update(runs).set({ status: "completed", finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq19(runs.id, runId)).run();
13897
+ this.db.update(runs).set({ status: "completed", finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq20(runs.id, runId)).run();
13518
13898
  }
13519
13899
  this.flushProviderUsage(projectId, providerDispatchCounts);
13520
13900
  const finalStatus = allFailed ? "failed" : someFailed ? "partial" : "completed";
@@ -13549,7 +13929,7 @@ var JobRunner = class {
13549
13929
  status: "failed",
13550
13930
  finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
13551
13931
  error: errorMessage
13552
- }).where(eq19(runs.id, runId)).run();
13932
+ }).where(eq20(runs.id, runId)).run();
13553
13933
  this.flushProviderUsage(projectId, providerDispatchCounts);
13554
13934
  trackEvent("run.completed", {
13555
13935
  status: "failed",
@@ -13592,7 +13972,7 @@ var JobRunner = class {
13592
13972
  status: runs.status,
13593
13973
  finishedAt: runs.finishedAt,
13594
13974
  error: runs.error
13595
- }).from(runs).where(eq19(runs.id, runId)).get();
13975
+ }).from(runs).where(eq20(runs.id, runId)).get();
13596
13976
  }
13597
13977
  isRunCancelled(runId) {
13598
13978
  return this.getRunState(runId)?.status === "cancelled";
@@ -13608,7 +13988,7 @@ var JobRunner = class {
13608
13988
  this.db.update(runs).set({
13609
13989
  finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
13610
13990
  error: currentRun.error ?? "Cancelled by user"
13611
- }).where(eq19(runs.id, runId)).run();
13991
+ }).where(eq20(runs.id, runId)).run();
13612
13992
  }
13613
13993
  trackEvent("run.completed", {
13614
13994
  status: "cancelled",
@@ -13631,7 +14011,7 @@ function getCurrentUsageDay() {
13631
14011
 
13632
14012
  // src/gsc-sync.ts
13633
14013
  import crypto20 from "crypto";
13634
- import { eq as eq20, and as and9, sql as sql7 } from "drizzle-orm";
14014
+ import { eq as eq21, and as and10, sql as sql7 } from "drizzle-orm";
13635
14015
  var log2 = createLogger("GscSync");
13636
14016
  function formatDate2(d) {
13637
14017
  return d.toISOString().split("T")[0];
@@ -13643,13 +14023,13 @@ function daysAgo(n) {
13643
14023
  }
13644
14024
  async function executeGscSync(db, runId, projectId, opts) {
13645
14025
  const now = (/* @__PURE__ */ new Date()).toISOString();
13646
- db.update(runs).set({ status: "running", startedAt: now }).where(eq20(runs.id, runId)).run();
14026
+ db.update(runs).set({ status: "running", startedAt: now }).where(eq21(runs.id, runId)).run();
13647
14027
  try {
13648
14028
  const { clientId: googleClientId, clientSecret: googleClientSecret } = getGoogleAuthConfig(opts.config);
13649
14029
  if (!googleClientId || !googleClientSecret) {
13650
14030
  throw new Error("Google OAuth is not configured in the local Canonry config");
13651
14031
  }
13652
- const project = db.select().from(projects).where(eq20(projects.id, projectId)).get();
14032
+ const project = db.select().from(projects).where(eq21(projects.id, projectId)).get();
13653
14033
  if (!project) {
13654
14034
  throw new Error(`Project not found: ${projectId}`);
13655
14035
  }
@@ -13683,8 +14063,8 @@ async function executeGscSync(db, runId, projectId, opts) {
13683
14063
  });
13684
14064
  log2.info("fetch.complete", { runId, projectId, rowCount: rows.length });
13685
14065
  db.delete(gscSearchData).where(
13686
- and9(
13687
- eq20(gscSearchData.projectId, projectId),
14066
+ and10(
14067
+ eq21(gscSearchData.projectId, projectId),
13688
14068
  sql7`${gscSearchData.date} >= ${startDate}`,
13689
14069
  sql7`${gscSearchData.date} <= ${endDate}`
13690
14070
  )
@@ -13751,7 +14131,7 @@ async function executeGscSync(db, runId, projectId, opts) {
13751
14131
  log2.error("inspect.url-failed", { runId, projectId, url: pageUrl, error: err instanceof Error ? err.message : String(err) });
13752
14132
  }
13753
14133
  }
13754
- const allInspections = db.select().from(gscUrlInspections).where(eq20(gscUrlInspections.projectId, projectId)).all();
14134
+ const allInspections = db.select().from(gscUrlInspections).where(eq21(gscUrlInspections.projectId, projectId)).all();
13755
14135
  const latestByUrl = /* @__PURE__ */ new Map();
13756
14136
  for (const row of allInspections) {
13757
14137
  const existing = latestByUrl.get(row.url);
@@ -13772,7 +14152,7 @@ async function executeGscSync(db, runId, projectId, opts) {
13772
14152
  }
13773
14153
  }
13774
14154
  const snapshotDate = formatDate2(/* @__PURE__ */ new Date());
13775
- db.delete(gscCoverageSnapshots).where(and9(eq20(gscCoverageSnapshots.projectId, projectId), eq20(gscCoverageSnapshots.date, snapshotDate))).run();
14155
+ db.delete(gscCoverageSnapshots).where(and10(eq21(gscCoverageSnapshots.projectId, projectId), eq21(gscCoverageSnapshots.date, snapshotDate))).run();
13776
14156
  db.insert(gscCoverageSnapshots).values({
13777
14157
  id: crypto20.randomUUID(),
13778
14158
  projectId,
@@ -13783,11 +14163,11 @@ async function executeGscSync(db, runId, projectId, opts) {
13783
14163
  reasonBreakdown: JSON.stringify(reasonCounts),
13784
14164
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
13785
14165
  }).run();
13786
- db.update(runs).set({ status: "completed", finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq20(runs.id, runId)).run();
14166
+ db.update(runs).set({ status: "completed", finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq21(runs.id, runId)).run();
13787
14167
  log2.info("sync.completed", { runId, projectId, searchDataRows: rows.length, urlInspections: topPages.length, indexed: snapIndexed, notIndexed: snapNotIndexed });
13788
14168
  } catch (err) {
13789
14169
  const errorMsg = err instanceof Error ? err.message : String(err);
13790
- db.update(runs).set({ status: "failed", error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq20(runs.id, runId)).run();
14170
+ db.update(runs).set({ status: "failed", error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq21(runs.id, runId)).run();
13791
14171
  log2.error("sync.failed", { runId, projectId, error: errorMsg });
13792
14172
  throw err;
13793
14173
  }
@@ -13795,7 +14175,7 @@ async function executeGscSync(db, runId, projectId, opts) {
13795
14175
 
13796
14176
  // src/gsc-inspect-sitemap.ts
13797
14177
  import crypto21 from "crypto";
13798
- import { eq as eq21, and as and10 } from "drizzle-orm";
14178
+ import { eq as eq22, and as and11 } from "drizzle-orm";
13799
14179
 
13800
14180
  // src/sitemap-parser.ts
13801
14181
  var log3 = createLogger("SitemapParser");
@@ -13916,13 +14296,13 @@ async function parseSitemapRecursive(url, urls, visited, depth, isChild) {
13916
14296
  var log4 = createLogger("InspectSitemap");
13917
14297
  async function executeInspectSitemap(db, runId, projectId, opts) {
13918
14298
  const now = (/* @__PURE__ */ new Date()).toISOString();
13919
- db.update(runs).set({ status: "running", startedAt: now }).where(eq21(runs.id, runId)).run();
14299
+ db.update(runs).set({ status: "running", startedAt: now }).where(eq22(runs.id, runId)).run();
13920
14300
  try {
13921
14301
  const { clientId: googleClientId, clientSecret: googleClientSecret } = getGoogleAuthConfig(opts.config);
13922
14302
  if (!googleClientId || !googleClientSecret) {
13923
14303
  throw new Error("Google OAuth is not configured in the local Canonry config");
13924
14304
  }
13925
- const project = db.select().from(projects).where(eq21(projects.id, projectId)).get();
14305
+ const project = db.select().from(projects).where(eq22(projects.id, projectId)).get();
13926
14306
  if (!project) {
13927
14307
  throw new Error(`Project not found: ${projectId}`);
13928
14308
  }
@@ -13990,7 +14370,7 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
13990
14370
  await new Promise((r) => setTimeout(r, 1e3));
13991
14371
  }
13992
14372
  }
13993
- const allInspections = db.select().from(gscUrlInspections).where(eq21(gscUrlInspections.projectId, projectId)).all();
14373
+ const allInspections = db.select().from(gscUrlInspections).where(eq22(gscUrlInspections.projectId, projectId)).all();
13994
14374
  const latestByUrl = /* @__PURE__ */ new Map();
13995
14375
  for (const row of allInspections) {
13996
14376
  const existing = latestByUrl.get(row.url);
@@ -14011,7 +14391,7 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
14011
14391
  }
14012
14392
  }
14013
14393
  const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
14014
- db.delete(gscCoverageSnapshots).where(and10(eq21(gscCoverageSnapshots.projectId, projectId), eq21(gscCoverageSnapshots.date, snapshotDate))).run();
14394
+ db.delete(gscCoverageSnapshots).where(and11(eq22(gscCoverageSnapshots.projectId, projectId), eq22(gscCoverageSnapshots.date, snapshotDate))).run();
14015
14395
  db.insert(gscCoverageSnapshots).values({
14016
14396
  id: crypto21.randomUUID(),
14017
14397
  projectId,
@@ -14023,11 +14403,11 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
14023
14403
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
14024
14404
  }).run();
14025
14405
  const status = errors > 0 && inspected > 0 ? "partial" : errors === urls.length ? "failed" : "completed";
14026
- db.update(runs).set({ status, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq21(runs.id, runId)).run();
14406
+ db.update(runs).set({ status, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq22(runs.id, runId)).run();
14027
14407
  log4.info("inspect.completed", { runId, projectId, inspected, errors, total: urls.length, indexed: snapIndexed, notIndexed: snapNotIndexed });
14028
14408
  } catch (err) {
14029
14409
  const errorMsg = err instanceof Error ? err.message : String(err);
14030
- db.update(runs).set({ status: "failed", error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq21(runs.id, runId)).run();
14410
+ db.update(runs).set({ status: "failed", error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq22(runs.id, runId)).run();
14031
14411
  log4.error("inspect.failed", { runId, projectId, error: errorMsg });
14032
14412
  throw err;
14033
14413
  }
@@ -14035,7 +14415,7 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
14035
14415
 
14036
14416
  // src/bing-inspect-sitemap.ts
14037
14417
  import crypto22 from "crypto";
14038
- import { eq as eq22, desc as desc9 } from "drizzle-orm";
14418
+ import { eq as eq23, desc as desc10 } from "drizzle-orm";
14039
14419
  var log5 = createLogger("BingInspectSitemap");
14040
14420
  function parseBingDate2(value) {
14041
14421
  if (!value) return null;
@@ -14053,9 +14433,9 @@ function isBlockingIssueType2(issueType) {
14053
14433
  }
14054
14434
  async function executeBingInspectSitemap(db, runId, projectId, opts) {
14055
14435
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
14056
- db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq22(runs.id, runId)).run();
14436
+ db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq23(runs.id, runId)).run();
14057
14437
  try {
14058
- const project = db.select().from(projects).where(eq22(projects.id, projectId)).get();
14438
+ const project = db.select().from(projects).where(eq23(projects.id, projectId)).get();
14059
14439
  if (!project) {
14060
14440
  throw new Error(`Project not found: ${projectId}`);
14061
14441
  }
@@ -14073,7 +14453,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
14073
14453
  if (sitemapUrls.length === 0) {
14074
14454
  throw new Error("No URLs found in sitemap");
14075
14455
  }
14076
- const trackedRows = db.select({ url: bingUrlInspections.url }).from(bingUrlInspections).where(eq22(bingUrlInspections.projectId, projectId)).all();
14456
+ const trackedRows = db.select({ url: bingUrlInspections.url }).from(bingUrlInspections).where(eq23(bingUrlInspections.projectId, projectId)).all();
14077
14457
  const trackedUrls = new Set(trackedRows.map((r) => r.url));
14078
14458
  const discovered = sitemapUrls.filter((u) => !trackedUrls.has(u));
14079
14459
  log5.info("sitemap.diff", {
@@ -14156,7 +14536,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
14156
14536
  await new Promise((r) => setTimeout(r, 1e3));
14157
14537
  }
14158
14538
  }
14159
- const allInspections = db.select().from(bingUrlInspections).where(eq22(bingUrlInspections.projectId, projectId)).orderBy(desc9(bingUrlInspections.inspectedAt)).all();
14539
+ const allInspections = db.select().from(bingUrlInspections).where(eq23(bingUrlInspections.projectId, projectId)).orderBy(desc10(bingUrlInspections.inspectedAt)).all();
14160
14540
  const latestByUrl = /* @__PURE__ */ new Map();
14161
14541
  const definitiveByUrl = /* @__PURE__ */ new Map();
14162
14542
  for (const row of allInspections) {
@@ -14199,7 +14579,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
14199
14579
  }
14200
14580
  }).run();
14201
14581
  const status = errors === sitemapUrls.length ? RunStatuses.failed : errors > 0 ? RunStatuses.partial : RunStatuses.completed;
14202
- db.update(runs).set({ status, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq22(runs.id, runId)).run();
14582
+ db.update(runs).set({ status, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq23(runs.id, runId)).run();
14203
14583
  log5.info("inspect.completed", {
14204
14584
  runId,
14205
14585
  projectId,
@@ -14213,7 +14593,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
14213
14593
  });
14214
14594
  } catch (err) {
14215
14595
  const errorMsg = err instanceof Error ? err.message : String(err);
14216
- db.update(runs).set({ status: RunStatuses.failed, error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq22(runs.id, runId)).run();
14596
+ db.update(runs).set({ status: RunStatuses.failed, error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq23(runs.id, runId)).run();
14217
14597
  log5.error("inspect.failed", { runId, projectId, error: errorMsg });
14218
14598
  throw err;
14219
14599
  }
@@ -14222,7 +14602,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
14222
14602
  // src/commoncrawl-sync.ts
14223
14603
  import crypto23 from "crypto";
14224
14604
  import path10 from "path";
14225
- import { and as and11, eq as eq23, sql as sql8 } from "drizzle-orm";
14605
+ import { and as and12, eq as eq24, sql as sql8 } from "drizzle-orm";
14226
14606
  var log6 = createLogger("CommonCrawlSync");
14227
14607
  var INSERT_CHUNK_SIZE = 1e4;
14228
14608
  function defaultDeps() {
@@ -14248,7 +14628,7 @@ async function executeReleaseSync(db, syncId, opts) {
14248
14628
  phaseDetail: "downloading vertices + edges",
14249
14629
  updatedAt: downloadStartedAt,
14250
14630
  error: null
14251
- }).where(eq23(ccReleaseSyncs.id, syncId)).run();
14631
+ }).where(eq24(ccReleaseSyncs.id, syncId)).run();
14252
14632
  const paths = ccReleasePaths(release);
14253
14633
  const releaseCacheDir = path10.join(deps.cacheDir, release);
14254
14634
  const vertexPath = path10.join(releaseCacheDir, paths.vertexFilename);
@@ -14271,7 +14651,7 @@ async function executeReleaseSync(db, syncId, opts) {
14271
14651
  vertexSha256: vertex.sha256,
14272
14652
  edgesSha256: edges.sha256,
14273
14653
  updatedAt: downloadFinishedAt
14274
- }).where(eq23(ccReleaseSyncs.id, syncId)).run();
14654
+ }).where(eq24(ccReleaseSyncs.id, syncId)).run();
14275
14655
  const allProjects = db.select().from(projects).all();
14276
14656
  const targets = Array.from(new Set(allProjects.map((p) => p.canonicalDomain)));
14277
14657
  let rows = [];
@@ -14287,8 +14667,8 @@ async function executeReleaseSync(db, syncId, opts) {
14287
14667
  }
14288
14668
  const queriedAt = deps.now().toISOString();
14289
14669
  db.transaction((tx) => {
14290
- tx.delete(backlinkDomains).where(eq23(backlinkDomains.releaseSyncId, syncId)).run();
14291
- tx.delete(backlinkSummaries).where(eq23(backlinkSummaries.releaseSyncId, syncId)).run();
14670
+ tx.delete(backlinkDomains).where(eq24(backlinkDomains.releaseSyncId, syncId)).run();
14671
+ tx.delete(backlinkSummaries).where(eq24(backlinkSummaries.releaseSyncId, syncId)).run();
14292
14672
  const expanded = [];
14293
14673
  for (const r of rows) {
14294
14674
  const projectIds = projectsByDomain.get(r.targetDomain);
@@ -14347,7 +14727,7 @@ async function executeReleaseSync(db, syncId, opts) {
14347
14727
  domainsDiscovered: rows.length,
14348
14728
  updatedAt: finishedAt,
14349
14729
  error: null
14350
- }).where(eq23(ccReleaseSyncs.id, syncId)).run();
14730
+ }).where(eq24(ccReleaseSyncs.id, syncId)).run();
14351
14731
  log6.info("sync.completed", {
14352
14732
  syncId,
14353
14733
  release,
@@ -14377,7 +14757,7 @@ async function executeReleaseSync(db, syncId, opts) {
14377
14757
  error: errorMsg,
14378
14758
  phaseDetail: null,
14379
14759
  updatedAt: finishedAt
14380
- }).where(eq23(ccReleaseSyncs.id, syncId)).run();
14760
+ }).where(eq24(ccReleaseSyncs.id, syncId)).run();
14381
14761
  log6.error("sync.failed", { syncId, release, error: errorMsg });
14382
14762
  throw err;
14383
14763
  }
@@ -14413,7 +14793,7 @@ function computeSummary(rows) {
14413
14793
  // src/backlink-extract.ts
14414
14794
  import crypto24 from "crypto";
14415
14795
  import fs8 from "fs";
14416
- import { and as and12, desc as desc10, eq as eq24 } from "drizzle-orm";
14796
+ import { and as and13, desc as desc11, eq as eq25 } from "drizzle-orm";
14417
14797
  var log7 = createLogger("BacklinkExtract");
14418
14798
  function defaultDeps2() {
14419
14799
  return {
@@ -14425,13 +14805,13 @@ function defaultDeps2() {
14425
14805
  async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
14426
14806
  const deps = { ...defaultDeps2(), ...opts.deps };
14427
14807
  const startedAt = deps.now().toISOString();
14428
- db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq24(runs.id, runId)).run();
14808
+ db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq25(runs.id, runId)).run();
14429
14809
  try {
14430
- const project = db.select().from(projects).where(eq24(projects.id, projectId)).get();
14810
+ const project = db.select().from(projects).where(eq25(projects.id, projectId)).get();
14431
14811
  if (!project) {
14432
14812
  throw new Error(`Project not found: ${projectId}`);
14433
14813
  }
14434
- const sync = opts.release ? db.select().from(ccReleaseSyncs).where(eq24(ccReleaseSyncs.release, opts.release)).get() : db.select().from(ccReleaseSyncs).where(eq24(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).orderBy(desc10(ccReleaseSyncs.createdAt)).limit(1).get();
14814
+ const sync = opts.release ? db.select().from(ccReleaseSyncs).where(eq25(ccReleaseSyncs.release, opts.release)).get() : db.select().from(ccReleaseSyncs).where(eq25(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).orderBy(desc11(ccReleaseSyncs.createdAt)).limit(1).get();
14435
14815
  if (!sync) {
14436
14816
  throw new Error("No ready release sync available \u2014 run `canonry backlinks sync` first");
14437
14817
  }
@@ -14459,7 +14839,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
14459
14839
  const targetDomain = project.canonicalDomain;
14460
14840
  db.transaction((tx) => {
14461
14841
  tx.delete(backlinkDomains).where(
14462
- and12(eq24(backlinkDomains.projectId, projectId), eq24(backlinkDomains.release, release))
14842
+ and13(eq25(backlinkDomains.projectId, projectId), eq25(backlinkDomains.release, release))
14463
14843
  ).run();
14464
14844
  if (rows.length > 0) {
14465
14845
  const values = rows.map((r) => ({
@@ -14499,7 +14879,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
14499
14879
  }).run();
14500
14880
  });
14501
14881
  const finishedAt = deps.now().toISOString();
14502
- db.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq24(runs.id, runId)).run();
14882
+ db.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq25(runs.id, runId)).run();
14503
14883
  log7.info("extract.completed", { runId, projectId, release, rows: rows.length });
14504
14884
  } catch (err) {
14505
14885
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -14508,7 +14888,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
14508
14888
  status: RunStatuses.failed,
14509
14889
  error: errorMsg,
14510
14890
  finishedAt
14511
- }).where(eq24(runs.id, runId)).run();
14891
+ }).where(eq25(runs.id, runId)).run();
14512
14892
  log7.error("extract.failed", { runId, projectId, error: errorMsg });
14513
14893
  throw err;
14514
14894
  }
@@ -14581,7 +14961,7 @@ var ProviderRegistry = class {
14581
14961
 
14582
14962
  // src/scheduler.ts
14583
14963
  import cron from "node-cron";
14584
- import { eq as eq25 } from "drizzle-orm";
14964
+ import { eq as eq26 } from "drizzle-orm";
14585
14965
  var log8 = createLogger("Scheduler");
14586
14966
  var Scheduler = class {
14587
14967
  db;
@@ -14593,7 +14973,7 @@ var Scheduler = class {
14593
14973
  }
14594
14974
  /** Load all enabled schedules from DB and register cron jobs. */
14595
14975
  start() {
14596
- const allSchedules = this.db.select().from(schedules).where(eq25(schedules.enabled, 1)).all();
14976
+ const allSchedules = this.db.select().from(schedules).where(eq26(schedules.enabled, 1)).all();
14597
14977
  for (const schedule of allSchedules) {
14598
14978
  const missedRunAt = schedule.nextRunAt;
14599
14979
  this.registerCronTask(schedule);
@@ -14618,7 +14998,7 @@ var Scheduler = class {
14618
14998
  this.stopTask(projectId, existing, "Stopped");
14619
14999
  this.tasks.delete(projectId);
14620
15000
  }
14621
- const schedule = this.db.select().from(schedules).where(eq25(schedules.projectId, projectId)).get();
15001
+ const schedule = this.db.select().from(schedules).where(eq26(schedules.projectId, projectId)).get();
14622
15002
  if (schedule && schedule.enabled === 1) {
14623
15003
  this.registerCronTask(schedule);
14624
15004
  }
@@ -14651,14 +15031,14 @@ var Scheduler = class {
14651
15031
  this.db.update(schedules).set({
14652
15032
  nextRunAt: task.getNextRun()?.toISOString() ?? null,
14653
15033
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
14654
- }).where(eq25(schedules.id, scheduleId)).run();
15034
+ }).where(eq26(schedules.id, scheduleId)).run();
14655
15035
  const label = schedule.preset ?? cronExpr;
14656
15036
  log8.info("cron.registered", { projectId, schedule: label, timezone });
14657
15037
  }
14658
15038
  triggerRun(scheduleId, projectId) {
14659
15039
  try {
14660
15040
  const now = (/* @__PURE__ */ new Date()).toISOString();
14661
- const currentSchedule = this.db.select().from(schedules).where(eq25(schedules.id, scheduleId)).get();
15041
+ const currentSchedule = this.db.select().from(schedules).where(eq26(schedules.id, scheduleId)).get();
14662
15042
  if (!currentSchedule || currentSchedule.enabled !== 1) {
14663
15043
  log8.warn("schedule.stale", { scheduleId, projectId, msg: "schedule no longer exists or is disabled" });
14664
15044
  this.remove(projectId);
@@ -14666,7 +15046,7 @@ var Scheduler = class {
14666
15046
  }
14667
15047
  const task = this.tasks.get(projectId);
14668
15048
  const nextRunAt = task?.getNextRun()?.toISOString() ?? null;
14669
- const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
15049
+ const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
14670
15050
  if (!project) {
14671
15051
  log8.error("project.not-found", { projectId, msg: "skipping scheduled run" });
14672
15052
  this.remove(projectId);
@@ -14695,7 +15075,7 @@ var Scheduler = class {
14695
15075
  this.db.update(schedules).set({
14696
15076
  nextRunAt,
14697
15077
  updatedAt: now
14698
- }).where(eq25(schedules.id, currentSchedule.id)).run();
15078
+ }).where(eq26(schedules.id, currentSchedule.id)).run();
14699
15079
  return;
14700
15080
  }
14701
15081
  const runId = queueResult.runId;
@@ -14703,7 +15083,7 @@ var Scheduler = class {
14703
15083
  lastRunAt: now,
14704
15084
  nextRunAt,
14705
15085
  updatedAt: now
14706
- }).where(eq25(schedules.id, currentSchedule.id)).run();
15086
+ }).where(eq26(schedules.id, currentSchedule.id)).run();
14707
15087
  const scheduleProviders = parseJsonColumn(currentSchedule.providers, []);
14708
15088
  const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
14709
15089
  log8.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
@@ -14715,7 +15095,7 @@ var Scheduler = class {
14715
15095
  };
14716
15096
 
14717
15097
  // src/notifier.ts
14718
- import { eq as eq26, desc as desc11, and as and13, or as or2 } from "drizzle-orm";
15098
+ import { eq as eq27, desc as desc12, and as and14, or as or2 } from "drizzle-orm";
14719
15099
  import crypto25 from "crypto";
14720
15100
  var log9 = createLogger("Notifier");
14721
15101
  var Notifier = class {
@@ -14728,18 +15108,18 @@ var Notifier = class {
14728
15108
  /** Called after a run completes (success, partial, or failed). */
14729
15109
  async onRunCompleted(runId, projectId) {
14730
15110
  log9.info("run.completed", { runId, projectId });
14731
- const notifs = this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled === 1);
15111
+ const notifs = this.db.select().from(notifications).where(eq27(notifications.projectId, projectId)).all().filter((n) => n.enabled === 1);
14732
15112
  if (notifs.length === 0) {
14733
15113
  log9.info("notifications.none-enabled", { projectId });
14734
15114
  return;
14735
15115
  }
14736
15116
  log9.info("notifications.found", { projectId, count: notifs.length });
14737
- const run = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
15117
+ const run = this.db.select().from(runs).where(eq27(runs.id, runId)).get();
14738
15118
  if (!run) {
14739
15119
  log9.error("run.not-found", { runId, msg: "skipping notification dispatch" });
14740
15120
  return;
14741
15121
  }
14742
- const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
15122
+ const project = this.db.select().from(projects).where(eq27(projects.id, projectId)).get();
14743
15123
  if (!project) {
14744
15124
  log9.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
14745
15125
  return;
@@ -14786,11 +15166,11 @@ var Notifier = class {
14786
15166
  if (criticalInsights.length > 0) insightEvents.push("insight.critical");
14787
15167
  if (highInsights.length > 0) insightEvents.push("insight.high");
14788
15168
  if (insightEvents.length === 0) return;
14789
- const notifs = this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled === 1);
15169
+ const notifs = this.db.select().from(notifications).where(eq27(notifications.projectId, projectId)).all().filter((n) => n.enabled === 1);
14790
15170
  if (notifs.length === 0) return;
14791
- const run = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
15171
+ const run = this.db.select().from(runs).where(eq27(runs.id, runId)).get();
14792
15172
  if (!run) return;
14793
- const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
15173
+ const project = this.db.select().from(projects).where(eq27(projects.id, projectId)).get();
14794
15174
  if (!project) return;
14795
15175
  for (const notif of notifs) {
14796
15176
  const config = parseJsonColumn(notif.config, { url: "", events: [] });
@@ -14821,11 +15201,11 @@ var Notifier = class {
14821
15201
  }
14822
15202
  computeTransitions(runId, projectId) {
14823
15203
  const recentRuns = this.db.select().from(runs).where(
14824
- and13(
14825
- eq26(runs.projectId, projectId),
14826
- or2(eq26(runs.status, "completed"), eq26(runs.status, "partial"))
15204
+ and14(
15205
+ eq27(runs.projectId, projectId),
15206
+ or2(eq27(runs.status, "completed"), eq27(runs.status, "partial"))
14827
15207
  )
14828
- ).orderBy(desc11(runs.createdAt)).limit(2).all();
15208
+ ).orderBy(desc12(runs.createdAt)).limit(2).all();
14829
15209
  if (recentRuns.length < 2) return [];
14830
15210
  const currentRunId = recentRuns[0].id;
14831
15211
  const previousRunId = recentRuns[1].id;
@@ -14835,12 +15215,12 @@ var Notifier = class {
14835
15215
  keyword: keywords.keyword,
14836
15216
  provider: querySnapshots.provider,
14837
15217
  citationState: querySnapshots.citationState
14838
- }).from(querySnapshots).leftJoin(keywords, eq26(querySnapshots.keywordId, keywords.id)).where(eq26(querySnapshots.runId, currentRunId)).all();
15218
+ }).from(querySnapshots).leftJoin(keywords, eq27(querySnapshots.keywordId, keywords.id)).where(eq27(querySnapshots.runId, currentRunId)).all();
14839
15219
  const previousSnapshots = this.db.select({
14840
15220
  keywordId: querySnapshots.keywordId,
14841
15221
  provider: querySnapshots.provider,
14842
15222
  citationState: querySnapshots.citationState
14843
- }).from(querySnapshots).where(eq26(querySnapshots.runId, previousRunId)).all();
15223
+ }).from(querySnapshots).where(eq27(querySnapshots.runId, previousRunId)).all();
14844
15224
  const prevMap = /* @__PURE__ */ new Map();
14845
15225
  for (const s of previousSnapshots) {
14846
15226
  prevMap.set(`${s.keywordId}:${s.provider}`, s.citationState);
@@ -14957,7 +15337,7 @@ var RunCoordinator = class {
14957
15337
 
14958
15338
  // src/agent/session-registry.ts
14959
15339
  import crypto27 from "crypto";
14960
- import { eq as eq28 } from "drizzle-orm";
15340
+ import { eq as eq29 } from "drizzle-orm";
14961
15341
 
14962
15342
  // src/agent/session.ts
14963
15343
  import fs11 from "fs";
@@ -15177,7 +15557,7 @@ import { Type as Type2 } from "@sinclair/typebox";
15177
15557
 
15178
15558
  // src/agent/memory-store.ts
15179
15559
  import crypto26 from "crypto";
15180
- import { and as and14, desc as desc12, eq as eq27, like, sql as sql9 } from "drizzle-orm";
15560
+ import { and as and15, desc as desc13, eq as eq28, like, sql as sql9 } from "drizzle-orm";
15181
15561
  var COMPACTION_KEY_PREFIX = "compaction:";
15182
15562
  var COMPACTION_NOTES_PER_SESSION = 3;
15183
15563
  function rowToDto(row) {
@@ -15191,7 +15571,7 @@ function rowToDto(row) {
15191
15571
  };
15192
15572
  }
15193
15573
  function listMemoryEntries(db, projectId, opts = {}) {
15194
- const query = db.select().from(agentMemory).where(eq27(agentMemory.projectId, projectId)).orderBy(desc12(agentMemory.updatedAt));
15574
+ const query = db.select().from(agentMemory).where(eq28(agentMemory.projectId, projectId)).orderBy(desc13(agentMemory.updatedAt));
15195
15575
  const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
15196
15576
  return rows.map(rowToDto);
15197
15577
  }
@@ -15222,12 +15602,12 @@ function upsertMemoryEntry(db, args) {
15222
15602
  updatedAt: now
15223
15603
  }
15224
15604
  }).run();
15225
- const row = db.select().from(agentMemory).where(and14(eq27(agentMemory.projectId, args.projectId), eq27(agentMemory.key, args.key))).get();
15605
+ const row = db.select().from(agentMemory).where(and15(eq28(agentMemory.projectId, args.projectId), eq28(agentMemory.key, args.key))).get();
15226
15606
  if (!row) throw new Error("memory upsert produced no row");
15227
15607
  return rowToDto(row);
15228
15608
  }
15229
15609
  function deleteMemoryEntry(db, projectId, key) {
15230
- const result = db.delete(agentMemory).where(and14(eq27(agentMemory.projectId, projectId), eq27(agentMemory.key, key))).run();
15610
+ const result = db.delete(agentMemory).where(and15(eq28(agentMemory.projectId, projectId), eq28(agentMemory.key, key))).run();
15231
15611
  const changes = result.changes ?? 0;
15232
15612
  return changes > 0;
15233
15613
  }
@@ -15256,16 +15636,16 @@ function writeCompactionNote(db, args) {
15256
15636
  }).run();
15257
15637
  const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
15258
15638
  const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
15259
- and14(
15260
- eq27(agentMemory.projectId, args.projectId),
15639
+ and15(
15640
+ eq28(agentMemory.projectId, args.projectId),
15261
15641
  like(agentMemory.key, `${sessionPrefix}%`)
15262
15642
  )
15263
- ).orderBy(desc12(agentMemory.updatedAt)).all();
15643
+ ).orderBy(desc13(agentMemory.updatedAt)).all();
15264
15644
  const stale = existing.slice(COMPACTION_NOTES_PER_SESSION).map((r) => r.id);
15265
15645
  if (stale.length > 0) {
15266
15646
  tx.delete(agentMemory).where(sql9`${agentMemory.id} IN (${sql9.join(stale.map((s) => sql9`${s}`), sql9`, `)})`).run();
15267
15647
  }
15268
- const row = tx.select().from(agentMemory).where(and14(eq27(agentMemory.projectId, args.projectId), eq27(agentMemory.key, key))).get();
15648
+ const row = tx.select().from(agentMemory).where(and15(eq28(agentMemory.projectId, args.projectId), eq28(agentMemory.key, key))).get();
15269
15649
  if (row) inserted = rowToDto(row);
15270
15650
  });
15271
15651
  if (!inserted) throw new Error("compaction note write produced no row");
@@ -15395,6 +15775,59 @@ function buildListCompetitorsTool(ctx) {
15395
15775
  }
15396
15776
  };
15397
15777
  }
15778
+ var ContentTargetsSchema = Type2.Object({
15779
+ limit: Type2.Optional(
15780
+ Type2.Number({
15781
+ description: "Max rows. Defaults to all. Use a small number (3\u201310) when summarizing for the user."
15782
+ })
15783
+ ),
15784
+ includeInProgress: Type2.Optional(
15785
+ Type2.Boolean({
15786
+ description: "Include rows that already have an in-flight tracked action. Default false."
15787
+ })
15788
+ )
15789
+ });
15790
+ function buildGetContentTargetsTool(ctx) {
15791
+ return {
15792
+ name: "get_content_targets",
15793
+ label: "Get content targets",
15794
+ description: "Ranked, action-typed content opportunities. Each row is `{query, action \u2208 create|expand|refresh|add-schema, ourBestPage?, winningCompetitor?, score, scoreBreakdown, drivers[], demandSource, actionConfidence}`. Use this to recommend which post the user should write/refresh next.",
15795
+ parameters: ContentTargetsSchema,
15796
+ execute: async (_toolCallId, params) => {
15797
+ const response = await ctx.client.getContentTargets(ctx.projectName, {
15798
+ limit: params.limit,
15799
+ includeInProgress: params.includeInProgress === true
15800
+ });
15801
+ return textResult2(response);
15802
+ }
15803
+ };
15804
+ }
15805
+ var ContentSourcesSchema = Type2.Object({});
15806
+ function buildGetContentSourcesTool(ctx) {
15807
+ return {
15808
+ name: "get_grounding_sources",
15809
+ label: "Get grounding sources",
15810
+ description: "URL-level competitive grounding-source map. Per query, lists every URL the LLM cited (our domain vs competitors), with citation count and providers. Read this to understand what specific competitor URL is winning a query.",
15811
+ parameters: ContentSourcesSchema,
15812
+ execute: async () => {
15813
+ const response = await ctx.client.getContentSources(ctx.projectName);
15814
+ return textResult2(response);
15815
+ }
15816
+ };
15817
+ }
15818
+ var ContentGapsSchema = Type2.Object({});
15819
+ function buildGetContentGapsTool(ctx) {
15820
+ return {
15821
+ name: "get_content_gaps",
15822
+ label: "Get content gaps",
15823
+ description: 'Queries where competitors are cited but our domain is not. Ranked by miss rate. The blunt-instrument view of "what competitors are winning that we are not." Use `get_content_targets` for action-typed recommendations on the same data.',
15824
+ parameters: ContentGapsSchema,
15825
+ execute: async () => {
15826
+ const response = await ctx.client.getContentGaps(ctx.projectName);
15827
+ return textResult2(response);
15828
+ }
15829
+ };
15830
+ }
15398
15831
  var RunDetailSchema = Type2.Object({
15399
15832
  runId: Type2.String({
15400
15833
  description: "Run id (UUID) to fetch. Typically obtained from get_status runs[].id."
@@ -15472,7 +15905,10 @@ function buildReadTools(ctx) {
15472
15905
  buildListCompetitorsTool(ctx),
15473
15906
  buildGetRunTool(ctx),
15474
15907
  buildRecallTool(ctx),
15475
- buildListBacklinksTool(ctx)
15908
+ buildListBacklinksTool(ctx),
15909
+ buildGetContentTargetsTool(ctx),
15910
+ buildGetContentSourcesTool(ctx),
15911
+ buildGetContentGapsTool(ctx)
15476
15912
  ];
15477
15913
  }
15478
15914
  var RunSweepSchema = Type2.Object({
@@ -15941,7 +16377,7 @@ var SessionRegistry = class {
15941
16377
  modelProvider: effectiveProvider,
15942
16378
  modelId: effectiveModelId,
15943
16379
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
15944
- }).where(eq28(agentSessions.projectId, projectId)).run();
16380
+ }).where(eq29(agentSessions.projectId, projectId)).run();
15945
16381
  }
15946
16382
  const agent2 = createAeroSession({
15947
16383
  projectName,
@@ -16159,7 +16595,7 @@ ${lines.join("\n")}
16159
16595
  modelProvider: nextProvider,
16160
16596
  modelId: nextModelId,
16161
16597
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
16162
- }).where(eq28(agentSessions.projectId, projectId)).run();
16598
+ }).where(eq29(agentSessions.projectId, projectId)).run();
16163
16599
  }
16164
16600
  /** Persist a session's transcript back to the DB. Call after any run settles. */
16165
16601
  save(projectName) {
@@ -16321,11 +16757,11 @@ ${lines.join("\n")}
16321
16757
  return id;
16322
16758
  }
16323
16759
  tryResolveProjectId(projectName) {
16324
- const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq28(projects.name, projectName)).get();
16760
+ const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq29(projects.name, projectName)).get();
16325
16761
  return row?.id;
16326
16762
  }
16327
16763
  loadRow(projectId) {
16328
- const row = this.opts.db.select().from(agentSessions).where(eq28(agentSessions.projectId, projectId)).get();
16764
+ const row = this.opts.db.select().from(agentSessions).where(eq29(agentSessions.projectId, projectId)).get();
16329
16765
  return row ?? null;
16330
16766
  }
16331
16767
  insertRow(params) {
@@ -16344,14 +16780,14 @@ ${lines.join("\n")}
16344
16780
  }
16345
16781
  updateRow(projectId, patch) {
16346
16782
  const now = (/* @__PURE__ */ new Date()).toISOString();
16347
- this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq28(agentSessions.projectId, projectId)).run();
16783
+ this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq29(agentSessions.projectId, projectId)).run();
16348
16784
  }
16349
16785
  };
16350
16786
 
16351
16787
  // src/agent/agent-routes.ts
16352
- import { eq as eq29 } from "drizzle-orm";
16788
+ import { eq as eq30 } from "drizzle-orm";
16353
16789
  function resolveProject2(db, name) {
16354
- const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq29(projects.name, name)).get();
16790
+ const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq30(projects.name, name)).get();
16355
16791
  if (!row) throw notFound("project", name);
16356
16792
  return row;
16357
16793
  }
@@ -16360,7 +16796,7 @@ function registerAgentRoutes(app, opts) {
16360
16796
  "/projects/:name/agent/transcript",
16361
16797
  async (request) => {
16362
16798
  const project = resolveProject2(opts.db, request.params.name);
16363
- const row = opts.db.select().from(agentSessions).where(eq29(agentSessions.projectId, project.id)).get();
16799
+ const row = opts.db.select().from(agentSessions).where(eq30(agentSessions.projectId, project.id)).get();
16364
16800
  if (!row) {
16365
16801
  return { messages: [], modelProvider: null, modelId: null, updatedAt: null };
16366
16802
  }
@@ -16384,7 +16820,7 @@ function registerAgentRoutes(app, opts) {
16384
16820
  async (request) => {
16385
16821
  const project = resolveProject2(opts.db, request.params.name);
16386
16822
  opts.sessionRegistry.reset(project.name);
16387
- opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq29(agentSessions.projectId, project.id)).run();
16823
+ opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq30(agentSessions.projectId, project.id)).run();
16388
16824
  return { status: "reset" };
16389
16825
  }
16390
16826
  );
@@ -16688,7 +17124,7 @@ var SnapshotService = class {
16688
17124
  }
16689
17125
  async createReport(input) {
16690
17126
  const companyName = input.companyName.trim();
16691
- const domain = normalizeDomain(input.domain);
17127
+ const domain = normalizeDomain2(input.domain);
16692
17128
  const manualPhrases = normalizeStringList(input.phrases ?? []);
16693
17129
  const manualCompetitors = normalizeStringList(input.competitors ?? []);
16694
17130
  const providers = this.registry.getAll();
@@ -17128,7 +17564,7 @@ function extractCompetitorsFromResponse(ctx) {
17128
17564
  const targetDomain = extractHostname2(ctx.targetDomain);
17129
17565
  for (const hint of ctx.manualCompetitors) {
17130
17566
  if (isDomainLike(hint)) {
17131
- const normalizedHint = normalizeDomain(hint);
17567
+ const normalizedHint = normalizeDomain2(hint);
17132
17568
  if (domainMatches2(normalizedHint, targetDomain)) continue;
17133
17569
  if (ctx.citedDomains.some((domain) => domainMatches2(domain, normalizedHint)) || lowerAnswer.includes(normalizedHint.toLowerCase())) {
17134
17570
  competitors2.add(normalizedHint);
@@ -17187,7 +17623,7 @@ function uniqueStrings2(values) {
17187
17623
  values.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean)
17188
17624
  )];
17189
17625
  }
17190
- function normalizeDomain(value) {
17626
+ function normalizeDomain2(value) {
17191
17627
  const trimmed = value.trim();
17192
17628
  if (!trimmed) return trimmed;
17193
17629
  try {
@@ -17198,15 +17634,15 @@ function normalizeDomain(value) {
17198
17634
  }
17199
17635
  }
17200
17636
  function extractHostname2(value) {
17201
- return normalizeDomain(value);
17637
+ return normalizeDomain2(value);
17202
17638
  }
17203
17639
  function domainMatches2(candidate, target) {
17204
- const normalizedCandidate = normalizeDomain(candidate);
17205
- const normalizedTarget = normalizeDomain(target);
17640
+ const normalizedCandidate = normalizeDomain2(candidate);
17641
+ const normalizedTarget = normalizeDomain2(target);
17206
17642
  return normalizedCandidate === normalizedTarget || normalizedCandidate.endsWith(`.${normalizedTarget}`);
17207
17643
  }
17208
17644
  function isDomainLike(value) {
17209
- const normalized = normalizeDomain(value);
17645
+ const normalized = normalizeDomain2(value);
17210
17646
  return normalized.includes(".") && !normalized.includes(" ");
17211
17647
  }
17212
17648
  function clipText(value, length) {
@@ -17406,7 +17842,7 @@ async function createServer(opts) {
17406
17842
  intelligenceService,
17407
17843
  (runId, projectId, result) => notifier.dispatchInsightWebhooks(runId, projectId, result),
17408
17844
  async ({ runId, projectId, insightCount, criticalOrHigh }) => {
17409
- const project = opts.db.select({ name: projects.name }).from(projects).where(eq30(projects.id, projectId)).get();
17845
+ const project = opts.db.select({ name: projects.name }).from(projects).where(eq31(projects.id, projectId)).get();
17410
17846
  if (!project) return;
17411
17847
  sessionRegistry.queueFollowUp(project.name, {
17412
17848
  role: "user",
@@ -17546,7 +17982,7 @@ async function createServer(opts) {
17546
17982
  const apiPrefix = basePath ? `${basePath}api/v1` : "/api/v1";
17547
17983
  if (opts.config.apiKey) {
17548
17984
  const keyHash = hashApiKey(opts.config.apiKey);
17549
- const existing = opts.db.select().from(apiKeys).where(eq30(apiKeys.keyHash, keyHash)).get();
17985
+ const existing = opts.db.select().from(apiKeys).where(eq31(apiKeys.keyHash, keyHash)).get();
17550
17986
  if (!existing) {
17551
17987
  const prefix = opts.config.apiKey.slice(0, 12);
17552
17988
  opts.db.insert(apiKeys).values({
@@ -17598,7 +18034,7 @@ async function createServer(opts) {
17598
18034
  };
17599
18035
  const getDefaultApiKey = () => {
17600
18036
  if (!opts.config.apiKey) return void 0;
17601
- return opts.db.select().from(apiKeys).where(eq30(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
18037
+ return opts.db.select().from(apiKeys).where(eq31(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
17602
18038
  };
17603
18039
  const createPasswordSession = (reply) => {
17604
18040
  const key = getDefaultApiKey();
@@ -17655,12 +18091,12 @@ async function createServer(opts) {
17655
18091
  return reply.send({ authenticated: true });
17656
18092
  }
17657
18093
  if (apiKey) {
17658
- const key = opts.db.select().from(apiKeys).where(eq30(apiKeys.keyHash, hashApiKey(apiKey))).get();
18094
+ const key = opts.db.select().from(apiKeys).where(eq31(apiKeys.keyHash, hashApiKey(apiKey))).get();
17659
18095
  if (!key || key.revokedAt) {
17660
18096
  const err2 = authInvalid();
17661
18097
  return reply.status(err2.statusCode).send(err2.toJSON());
17662
18098
  }
17663
- opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq30(apiKeys.id, key.id)).run();
18099
+ opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq31(apiKeys.id, key.id)).run();
17664
18100
  const sessionId = createSession(key.id);
17665
18101
  reply.header("set-cookie", serializeSessionCookie({
17666
18102
  name: SESSION_COOKIE_NAME,