@ainyc/canonry 4.72.4 → 4.75.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +1 -1
  2. package/assets/agent-workspace/skills/aero/references/orchestration.md +1 -1
  3. package/assets/agent-workspace/skills/canonry/SKILL.md +1 -1
  4. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +17 -0
  5. package/assets/assets/{BacklinksPage-CjfpwZEH.js → BacklinksPage-CwAplOLo.js} +1 -1
  6. package/assets/assets/{ChartPrimitives-Ckf2FrUy.js → ChartPrimitives-EGp5HFxn.js} +1 -1
  7. package/assets/assets/ProjectPage-C-zhkBKK.js +6 -0
  8. package/assets/assets/{RunRow-BuFyG0V_.js → RunRow-YFN2PwH-.js} +1 -1
  9. package/assets/assets/{RunsPage-D-pr000K.js → RunsPage-DlKS8zaS.js} +1 -1
  10. package/assets/assets/{SettingsPage-CiaapCYn.js → SettingsPage-Q0OZKjMD.js} +1 -1
  11. package/assets/assets/{TrafficPage-B40xytJD.js → TrafficPage-BbySUnhy.js} +1 -1
  12. package/assets/assets/{TrafficSourceDetailPage-7hHem-gM.js → TrafficSourceDetailPage-BGzuvTYp.js} +1 -1
  13. package/assets/assets/{extract-error-message-3GkDsu1h.js → extract-error-message-Czt2jFxA.js} +1 -1
  14. package/assets/assets/index-CFVX11lK.css +1 -0
  15. package/assets/assets/{index-BVdH2O9w.js → index-DYsYdWV8.js} +118 -118
  16. package/assets/assets/{server-traffic-CsgPsudZ.js → server-traffic-BzIFKqGS.js} +1 -1
  17. package/assets/assets/{trash-2-B8Ipf9rI.js → trash-2-DKCkbZUb.js} +1 -1
  18. package/assets/index.html +2 -2
  19. package/dist/{chunk-JXFNERK4.js → chunk-JNAKRK77.js} +1103 -998
  20. package/dist/{chunk-HOKVBMOD.js → chunk-JUWU2DV6.js} +402 -81
  21. package/dist/{chunk-SRBO33HB.js → chunk-QY5WZWU4.js} +403 -202
  22. package/dist/{chunk-ZUBBADMR.js → chunk-WFMEK34V.js} +162 -1
  23. package/dist/cli.js +237 -31
  24. package/dist/index.d.ts +10 -0
  25. package/dist/index.js +4 -4
  26. package/dist/{intelligence-service-CSW4R4I7.js → intelligence-service-L2A5MFB4.js} +2 -2
  27. package/dist/mcp.js +2 -2
  28. package/package.json +10 -10
  29. package/assets/assets/ProjectPage-DZeplYeC.js +0 -6
  30. package/assets/assets/index-B3nENtU0.css +0 -1
@@ -9,7 +9,7 @@ import {
9
9
  loadConfig,
10
10
  loadConfigRaw,
11
11
  saveConfigPatch
12
- } from "./chunk-ZUBBADMR.js";
12
+ } from "./chunk-WFMEK34V.js";
13
13
  import {
14
14
  CC_CACHE_DIR,
15
15
  DUCKDB_SPEC,
@@ -94,8 +94,10 @@ import {
94
94
  resolveWebhookTarget,
95
95
  runs,
96
96
  schedules,
97
+ siteAuditPages,
98
+ siteAuditSnapshots,
97
99
  usageCounters
98
- } from "./chunk-HOKVBMOD.js";
100
+ } from "./chunk-JUWU2DV6.js";
99
101
  import {
100
102
  AGENT_MEMORY_VALUE_MAX_BYTES,
101
103
  AGENT_PROVIDER_IDS,
@@ -125,6 +127,7 @@ import {
125
127
  agentMemoryDeleteRequestSchema,
126
128
  agentMemoryUpsertRequestSchema,
127
129
  authInvalid,
130
+ authRequired,
128
131
  buildRunErrorFromMessages,
129
132
  classifySkillFile,
130
133
  coerceSkillManifest,
@@ -133,6 +136,7 @@ import {
133
136
  determineAnswerMentioned,
134
137
  effectiveBrandNames,
135
138
  effectiveDomains,
139
+ factorStatusFromScore,
136
140
  isAgentProviderId,
137
141
  isBrowserProvider,
138
142
  isRetryableHttpError,
@@ -145,7 +149,7 @@ import {
145
149
  validationError,
146
150
  winnabilityClassLabel,
147
151
  withRetry
148
- } from "./chunk-JXFNERK4.js";
152
+ } from "./chunk-JNAKRK77.js";
149
153
 
150
154
  // src/telemetry.ts
151
155
  import crypto from "crypto";
@@ -433,11 +437,11 @@ function checkLatestVersionForServer(opts) {
433
437
 
434
438
  // src/server.ts
435
439
  import { createRequire as createRequire3 } from "module";
436
- import crypto17 from "crypto";
440
+ import crypto18 from "crypto";
437
441
  import fs8 from "fs";
438
442
  import path9 from "path";
439
443
  import { fileURLToPath as fileURLToPath3 } from "url";
440
- import { and as and13, eq as eq17 } from "drizzle-orm";
444
+ import { and as and13, eq as eq18 } from "drizzle-orm";
441
445
  import Fastify from "fastify";
442
446
  import os5 from "os";
443
447
 
@@ -4065,31 +4069,10 @@ import { eq as eq4, and as and4 } from "drizzle-orm";
4065
4069
  var log4 = createLogger("SitemapParser");
4066
4070
  var LOC_REGEX = /<loc>([^<]+)<\/loc>/gi;
4067
4071
  var SITEMAP_TAG_REGEX = /<sitemap>[\s\S]*?<\/sitemap>/gi;
4068
- var PRIVATE_IP_PATTERNS = [
4069
- /^169\.254\./,
4070
- // link-local (AWS metadata endpoint etc.)
4071
- /^10\./,
4072
- // private class A
4073
- /^172\.(1[6-9]|2\d|3[01])\./,
4074
- // private class B
4075
- /^192\.168\./
4076
- // private class C
4077
- ];
4078
- function validateSitemapUrl(url) {
4079
- let parsed;
4080
- try {
4081
- parsed = new URL(url);
4082
- } catch {
4083
- throw new Error(`Invalid sitemap URL: ${url}`);
4084
- }
4085
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
4086
- throw new Error(`Sitemap URL must use http or https protocol: ${url}`);
4087
- }
4088
- const host = parsed.hostname.toLowerCase();
4089
- for (const pattern of PRIVATE_IP_PATTERNS) {
4090
- if (pattern.test(host)) {
4091
- throw new Error(`Sitemap URL points to a private or reserved IP range: ${url}`);
4092
- }
4072
+ async function validateSitemapUrl(url) {
4073
+ const check = await resolveWebhookTarget(url, { allowLoopback: true });
4074
+ if (!check.ok) {
4075
+ throw new Error(`Sitemap URL rejected: ${check.message.replace(/^"url" /, "")} (${url})`);
4093
4076
  }
4094
4077
  }
4095
4078
  async function readSitemapBody(res) {
@@ -4119,9 +4102,9 @@ async function parseSitemapRecursive(url, urls, visited, depth, isChild) {
4119
4102
  if (depth > 3) return;
4120
4103
  if (visited.has(url)) return;
4121
4104
  visited.add(url);
4122
- validateSitemapUrl(url);
4123
4105
  let res;
4124
4106
  try {
4107
+ await validateSitemapUrl(url);
4125
4108
  res = await fetch(url);
4126
4109
  } catch (err) {
4127
4110
  if (!isChild) throw err;
@@ -5162,8 +5145,166 @@ function buildDiscoveryInsightTitle(input) {
5162
5145
  return parts.join(" \u2022 ");
5163
5146
  }
5164
5147
 
5148
+ // src/execute-site-audit.ts
5149
+ import crypto12 from "crypto";
5150
+ import { eq as eq10 } from "drizzle-orm";
5151
+ import { runSitemapAudit } from "@ainyc/aeo-audit";
5152
+ var log11 = createLogger("SiteAudit");
5153
+ var SITE_AUDIT_DEFAULT_PAGE_LIMIT = 500;
5154
+ var SITE_AUDIT_MAX_PAGE_LIMIT = 2e3;
5155
+ function toHomepageUrl(canonicalDomain) {
5156
+ const trimmed = canonicalDomain.trim().replace(/\/+$/, "");
5157
+ return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
5158
+ }
5159
+ async function assertSiteAuditUrlAllowed(rawUrl, field) {
5160
+ const check = await resolveWebhookTarget(rawUrl);
5161
+ if (!check.ok) {
5162
+ throw new Error(`${field} ${check.message.replace(/^"url" /, "")}`);
5163
+ }
5164
+ }
5165
+ function clampSiteAuditLimit(limit) {
5166
+ if (limit == null || !Number.isFinite(limit)) return SITE_AUDIT_DEFAULT_PAGE_LIMIT;
5167
+ return Math.max(1, Math.min(SITE_AUDIT_MAX_PAGE_LIMIT, Math.floor(limit)));
5168
+ }
5169
+ function toPageFactor(factor) {
5170
+ return {
5171
+ id: factor.id,
5172
+ name: factor.name,
5173
+ weight: factor.weight,
5174
+ score: factor.score
5175
+ };
5176
+ }
5177
+ function computeFactorAverages(pages) {
5178
+ const byId = /* @__PURE__ */ new Map();
5179
+ for (const page of pages) {
5180
+ if (page.status !== "success" || !page.factors) continue;
5181
+ for (const factor of page.factors) {
5182
+ let entry = byId.get(factor.id);
5183
+ if (!entry) {
5184
+ entry = { name: factor.name, weight: factor.weight, scores: [], pass: 0, partial: 0, fail: 0 };
5185
+ byId.set(factor.id, entry);
5186
+ }
5187
+ entry.scores.push(factor.score);
5188
+ const status = factorStatusFromScore(factor.score);
5189
+ if (status === "pass") entry.pass++;
5190
+ else if (status === "partial") entry.partial++;
5191
+ else entry.fail++;
5192
+ }
5193
+ }
5194
+ const summaries = [];
5195
+ for (const [id, entry] of byId) {
5196
+ const avgScore = entry.scores.length ? Math.round(entry.scores.reduce((sum, score) => sum + score, 0) / entry.scores.length) : 0;
5197
+ summaries.push({
5198
+ id,
5199
+ name: entry.name,
5200
+ weight: entry.weight,
5201
+ avgScore,
5202
+ status: factorStatusFromScore(avgScore),
5203
+ pagesPassing: entry.pass,
5204
+ pagesPartial: entry.partial,
5205
+ pagesFailing: entry.fail
5206
+ });
5207
+ }
5208
+ summaries.sort((a, b) => b.weight - a.weight || a.name.localeCompare(b.name));
5209
+ return summaries;
5210
+ }
5211
+ async function executeSiteAudit(db, runId, projectId, opts = {}) {
5212
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
5213
+ db.update(runs).set({ status: "running", startedAt }).where(eq10(runs.id, runId)).run();
5214
+ try {
5215
+ const project = db.select().from(projects).where(eq10(projects.id, projectId)).get();
5216
+ if (!project) {
5217
+ throw new Error(`Project not found: ${projectId}`);
5218
+ }
5219
+ const homepageUrl = toHomepageUrl(project.canonicalDomain);
5220
+ const limit = clampSiteAuditLimit(opts.limit);
5221
+ log11.info("start", { runId, projectId, homepageUrl, sitemapUrl: opts.sitemapUrl ?? null, limit });
5222
+ await assertSiteAuditUrlAllowed(homepageUrl, "canonicalDomain");
5223
+ if (opts.sitemapUrl) await assertSiteAuditUrlAllowed(opts.sitemapUrl, "sitemapUrl");
5224
+ const report = await runSitemapAudit(homepageUrl, { sitemapUrl: opts.sitemapUrl, limit });
5225
+ const successCount = report.pages.filter((page) => page.status === "success").length;
5226
+ const pagesErrored = report.pages.filter((page) => page.status === "error").length;
5227
+ const auditable = report.pagesDiscovered - report.pagesSkipped;
5228
+ if (auditable > report.pagesAudited) {
5229
+ log11.info("truncated", {
5230
+ runId,
5231
+ projectId,
5232
+ auditable,
5233
+ audited: report.pagesAudited,
5234
+ dropped: auditable - report.pagesAudited,
5235
+ limit
5236
+ });
5237
+ }
5238
+ if (successCount === 0) {
5239
+ throw new Error(
5240
+ `Site audit could not successfully audit any of ${report.pagesAudited} page(s) from ${report.sitemapUrl}.`
5241
+ );
5242
+ }
5243
+ const factorAverages = computeFactorAverages(report.pages);
5244
+ const status = pagesErrored === 0 ? "completed" : "partial";
5245
+ const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
5246
+ db.transaction((tx) => {
5247
+ tx.insert(siteAuditSnapshots).values({
5248
+ id: crypto12.randomUUID(),
5249
+ projectId,
5250
+ runId,
5251
+ sitemapUrl: report.sitemapUrl,
5252
+ auditedAt: report.auditedAt,
5253
+ aggregateScore: report.aggregateScore,
5254
+ pagesDiscovered: report.pagesDiscovered,
5255
+ pagesAudited: report.pagesAudited,
5256
+ pagesSkipped: report.pagesSkipped,
5257
+ pagesErrored,
5258
+ factorAverages,
5259
+ // aeo-audit v3 enriches these (topIssues, avgGrade-free); keep only the
5260
+ // fields our DTO exposes so the stored JSON stays lean.
5261
+ crossCuttingIssues: report.crossCuttingIssues.map((issue) => ({
5262
+ factorId: issue.factorId,
5263
+ factorName: issue.factorName,
5264
+ avgScore: issue.avgScore,
5265
+ affectedPages: issue.affectedPages,
5266
+ totalPages: issue.totalPages,
5267
+ affectedPct: issue.totalPages > 0 ? Math.round(issue.affectedPages / issue.totalPages * 100) : 0,
5268
+ topRecommendations: issue.topRecommendations
5269
+ })),
5270
+ // v3 prioritizedFixes are structured PrioritizedFix objects; persist the
5271
+ // ready-to-display one-line summary to keep the DTO a string list.
5272
+ prioritizedFixes: report.prioritizedFixes.map((fix) => fix.summary),
5273
+ createdAt: finishedAt
5274
+ }).run();
5275
+ for (const page of report.pages) {
5276
+ tx.insert(siteAuditPages).values({
5277
+ id: crypto12.randomUUID(),
5278
+ projectId,
5279
+ runId,
5280
+ url: page.url,
5281
+ overallScore: page.overallScore,
5282
+ status: page.status,
5283
+ error: page.error ?? null,
5284
+ factors: (page.factors ?? []).map(toPageFactor),
5285
+ createdAt: finishedAt
5286
+ }).run();
5287
+ }
5288
+ tx.update(runs).set({ status, finishedAt }).where(eq10(runs.id, runId)).run();
5289
+ });
5290
+ log11.info("completed", {
5291
+ runId,
5292
+ projectId,
5293
+ status,
5294
+ score: report.aggregateScore,
5295
+ audited: report.pagesAudited,
5296
+ errored: pagesErrored
5297
+ });
5298
+ } catch (err) {
5299
+ const errorMsg = err instanceof Error ? err.message : String(err);
5300
+ db.update(runs).set({ status: "failed", error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq10(runs.id, runId)).run();
5301
+ log11.error("failed", { runId, projectId, error: errorMsg });
5302
+ throw err;
5303
+ }
5304
+ }
5305
+
5165
5306
  // src/commands/backfill.ts
5166
- import { and as and9, eq as eq10, inArray as inArray4, isNull, sql as sql4 } from "drizzle-orm";
5307
+ import { and as and9, eq as eq11, inArray as inArray4, isNull, sql as sql4 } from "drizzle-orm";
5167
5308
  var SNAPSHOT_BATCH_SIZE = 500;
5168
5309
  async function backfillAnswerVisibilityCommand(opts) {
5169
5310
  const config = loadConfig();
@@ -5171,7 +5312,7 @@ async function backfillAnswerVisibilityCommand(opts) {
5171
5312
  migrate(db);
5172
5313
  const projectFilter = opts?.project?.trim();
5173
5314
  const isDryRun = opts?.dryRun === true;
5174
- const scopedProjects = projectFilter ? db.select().from(projects).where(eq10(projects.name, projectFilter)).all() : db.select().from(projects).all();
5315
+ const scopedProjects = projectFilter ? db.select().from(projects).where(eq11(projects.name, projectFilter)).all() : db.select().from(projects).all();
5175
5316
  let examined = 0;
5176
5317
  let updated = 0;
5177
5318
  let wouldUpdate = 0;
@@ -5180,9 +5321,9 @@ async function backfillAnswerVisibilityCommand(opts) {
5180
5321
  let providerErrors = 0;
5181
5322
  if (scopedProjects.length > 0) {
5182
5323
  const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and9(
5183
- eq10(runs.kind, RunKinds["answer-visibility"]),
5324
+ eq11(runs.kind, RunKinds["answer-visibility"]),
5184
5325
  inArray4(runs.projectId, scopedProjects.map((project) => project.id))
5185
- )).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(eq10(runs.kind, RunKinds["answer-visibility"])).all();
5326
+ )).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(eq11(runs.kind, RunKinds["answer-visibility"])).all();
5186
5327
  const runIdsByProject = /* @__PURE__ */ new Map();
5187
5328
  for (const run of runRows) {
5188
5329
  const existing = runIdsByProject.get(run.projectId);
@@ -5190,7 +5331,7 @@ async function backfillAnswerVisibilityCommand(opts) {
5190
5331
  else runIdsByProject.set(run.projectId, [run.id]);
5191
5332
  }
5192
5333
  for (const project of scopedProjects) {
5193
- const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq10(competitors.projectId, project.id)).all().map((row) => row.domain);
5334
+ const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq11(competitors.projectId, project.id)).all().map((row) => row.domain);
5194
5335
  const runIds = runIdsByProject.get(project.id) ?? [];
5195
5336
  if (runIds.length === 0) continue;
5196
5337
  const projectDomains = effectiveDomains({
@@ -5278,7 +5419,7 @@ async function backfillAnswerVisibilityCommand(opts) {
5278
5419
  } else {
5279
5420
  db.transaction((tx) => {
5280
5421
  for (const update of pendingUpdates) {
5281
- tx.update(querySnapshots).set(update.patch).where(eq10(querySnapshots.id, update.id)).run();
5422
+ tx.update(querySnapshots).set(update.patch).where(eq11(querySnapshots.id, update.id)).run();
5282
5423
  }
5283
5424
  });
5284
5425
  updated += pendingUpdates.length;
@@ -5327,7 +5468,7 @@ No DB writes performed. Re-run without --dry-run to apply.`);
5327
5468
  function backfillNormalizedPaths(db, opts) {
5328
5469
  const baseConditions = [];
5329
5470
  if (opts?.projectId) {
5330
- baseConditions.push(eq10(gaTrafficSnapshots.projectId, opts.projectId));
5471
+ baseConditions.push(eq11(gaTrafficSnapshots.projectId, opts.projectId));
5331
5472
  }
5332
5473
  const rows = db.select({
5333
5474
  id: gaTrafficSnapshots.id,
@@ -5348,7 +5489,7 @@ function backfillNormalizedPaths(db, opts) {
5348
5489
  unchanged++;
5349
5490
  continue;
5350
5491
  }
5351
- tx.update(gaTrafficSnapshots).set({ landingPageNormalized: next }).where(eq10(gaTrafficSnapshots.id, row.id)).run();
5492
+ tx.update(gaTrafficSnapshots).set({ landingPageNormalized: next }).where(eq11(gaTrafficSnapshots.id, row.id)).run();
5352
5493
  updated++;
5353
5494
  }
5354
5495
  });
@@ -5362,7 +5503,7 @@ async function backfillNormalizedPathsCommand(opts) {
5362
5503
  const projectFilter = opts?.project?.trim();
5363
5504
  let projectId;
5364
5505
  if (projectFilter) {
5365
- const project = db.select({ id: projects.id }).from(projects).where(eq10(projects.name, projectFilter)).get();
5506
+ const project = db.select({ id: projects.id }).from(projects).where(eq11(projects.name, projectFilter)).get();
5366
5507
  if (!project) {
5367
5508
  const result2 = {
5368
5509
  project: projectFilter,
@@ -5399,7 +5540,7 @@ async function backfillNormalizedPathsCommand(opts) {
5399
5540
  function backfillAiReferralPaths(db, opts) {
5400
5541
  const baseConditions = [];
5401
5542
  if (opts?.projectId) {
5402
- baseConditions.push(eq10(gaAiReferrals.projectId, opts.projectId));
5543
+ baseConditions.push(eq11(gaAiReferrals.projectId, opts.projectId));
5403
5544
  }
5404
5545
  const rows = db.select({
5405
5546
  id: gaAiReferrals.id,
@@ -5420,7 +5561,7 @@ function backfillAiReferralPaths(db, opts) {
5420
5561
  unchanged++;
5421
5562
  continue;
5422
5563
  }
5423
- tx.update(gaAiReferrals).set({ landingPageNormalized: next }).where(eq10(gaAiReferrals.id, row.id)).run();
5564
+ tx.update(gaAiReferrals).set({ landingPageNormalized: next }).where(eq11(gaAiReferrals.id, row.id)).run();
5424
5565
  updated++;
5425
5566
  }
5426
5567
  });
@@ -5434,7 +5575,7 @@ async function backfillAiReferralPathsCommand(opts) {
5434
5575
  const projectFilter = opts?.project?.trim();
5435
5576
  let projectId;
5436
5577
  if (projectFilter) {
5437
- const project = db.select({ id: projects.id }).from(projects).where(eq10(projects.name, projectFilter)).get();
5578
+ const project = db.select({ id: projects.id }).from(projects).where(eq11(projects.name, projectFilter)).get();
5438
5579
  if (!project) {
5439
5580
  const result2 = {
5440
5581
  project: projectFilter,
@@ -5470,10 +5611,10 @@ async function backfillAiReferralPathsCommand(opts) {
5470
5611
  }
5471
5612
  function backfillProjectAnswerMentions(db, projectId, opts) {
5472
5613
  const isDryRun = opts?.dryRun === true;
5473
- const project = db.select().from(projects).where(eq10(projects.id, projectId)).get();
5614
+ const project = db.select().from(projects).where(eq11(projects.id, projectId)).get();
5474
5615
  if (!project) return { examined: 0, updated: 0, mentioned: 0 };
5475
- const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq10(competitors.projectId, projectId)).all().map((row) => row.domain);
5476
- const runRows = db.select({ id: runs.id }).from(runs).where(and9(eq10(runs.kind, RunKinds["answer-visibility"]), eq10(runs.projectId, projectId))).all();
5616
+ const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq11(competitors.projectId, projectId)).all().map((row) => row.domain);
5617
+ const runRows = db.select({ id: runs.id }).from(runs).where(and9(eq11(runs.kind, RunKinds["answer-visibility"]), eq11(runs.projectId, projectId))).all();
5477
5618
  const runIds = runRows.map((r) => r.id);
5478
5619
  let examined = 0;
5479
5620
  let updated = 0;
@@ -5545,7 +5686,7 @@ function backfillProjectAnswerMentions(db, projectId, opts) {
5545
5686
  } else {
5546
5687
  db.transaction((tx) => {
5547
5688
  for (const update of pendingUpdates) {
5548
- tx.update(querySnapshots).set(update.patch).where(eq10(querySnapshots.id, update.id)).run();
5689
+ tx.update(querySnapshots).set(update.patch).where(eq11(querySnapshots.id, update.id)).run();
5549
5690
  }
5550
5691
  });
5551
5692
  updated += pendingUpdates.length;
@@ -5560,7 +5701,7 @@ async function backfillAnswerMentionsCommand(opts) {
5560
5701
  migrate(db);
5561
5702
  const projectFilter = opts?.project?.trim();
5562
5703
  const isDryRun = opts?.dryRun === true;
5563
- const scopedProjects = projectFilter ? db.select().from(projects).where(eq10(projects.name, projectFilter)).all() : db.select().from(projects).all();
5704
+ const scopedProjects = projectFilter ? db.select().from(projects).where(eq11(projects.name, projectFilter)).all() : db.select().from(projects).all();
5564
5705
  let examined = 0;
5565
5706
  let updated = 0;
5566
5707
  let wouldUpdate = 0;
@@ -5620,7 +5761,7 @@ function readStoredGroundingSources(rawResponse) {
5620
5761
  return result;
5621
5762
  }
5622
5763
  async function backfillInsightsCommand(project, opts) {
5623
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-CSW4R4I7.js");
5764
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-L2A5MFB4.js");
5624
5765
  const config = loadConfig();
5625
5766
  const db = createClient(config.database);
5626
5767
  migrate(db);
@@ -5779,7 +5920,7 @@ async function backfillSnapshotAttributionCommand(opts) {
5779
5920
  const config = loadConfig();
5780
5921
  const db = createClient(config.database);
5781
5922
  migrate(db);
5782
- const project = db.select().from(projects).where(eq10(projects.name, opts.project)).get();
5923
+ const project = db.select().from(projects).where(eq11(projects.name, opts.project)).get();
5783
5924
  if (!project) {
5784
5925
  throw new Error(`Project "${opts.project}" not found`);
5785
5926
  }
@@ -5791,7 +5932,7 @@ async function backfillSnapshotAttributionCommand(opts) {
5791
5932
  `);
5792
5933
  }
5793
5934
  const events = db.select({ createdAt: auditLog.createdAt, action: auditLog.action, diff: auditLog.diff }).from(auditLog).where(and9(
5794
- eq10(auditLog.projectId, project.id),
5935
+ eq11(auditLog.projectId, project.id),
5795
5936
  inArray4(auditLog.action, ["keywords.appended", "keywords.deleted", "queries.appended", "queries.deleted", "queries.replaced"])
5796
5937
  )).orderBy(auditLog.createdAt).all();
5797
5938
  const history = replayQueryAuditLog(events);
@@ -5799,8 +5940,8 @@ async function backfillSnapshotAttributionCommand(opts) {
5799
5940
  runId: runs.id,
5800
5941
  createdAt: runs.createdAt,
5801
5942
  location: runs.location
5802
- }).from(runs).innerJoin(querySnapshots, eq10(querySnapshots.runId, runs.id)).where(and9(
5803
- eq10(runs.projectId, project.id),
5943
+ }).from(runs).innerJoin(querySnapshots, eq11(querySnapshots.runId, runs.id)).where(and9(
5944
+ eq11(runs.projectId, project.id),
5804
5945
  isNull(querySnapshots.queryId),
5805
5946
  isNull(querySnapshots.queryText)
5806
5947
  )).groupBy(runs.id).orderBy(runs.createdAt).all();
@@ -5823,7 +5964,7 @@ async function backfillSnapshotAttributionCommand(opts) {
5823
5964
  createdAt: querySnapshots.createdAt,
5824
5965
  answerText: querySnapshots.answerText
5825
5966
  }).from(querySnapshots).where(and9(
5826
- eq10(querySnapshots.runId, run.runId),
5967
+ eq11(querySnapshots.runId, run.runId),
5827
5968
  isNull(querySnapshots.queryId),
5828
5969
  isNull(querySnapshots.queryText)
5829
5970
  )).orderBy(querySnapshots.provider, querySnapshots.createdAt).all();
@@ -5889,7 +6030,7 @@ async function backfillSnapshotAttributionCommand(opts) {
5889
6030
  if (!isDryRun && updates.length > 0) {
5890
6031
  db.transaction((tx) => {
5891
6032
  for (const u of updates) {
5892
- tx.update(querySnapshots).set({ queryText: u.queryText }).where(eq10(querySnapshots.id, u.id)).run();
6033
+ tx.update(querySnapshots).set({ queryText: u.queryText }).where(eq11(querySnapshots.id, u.id)).run();
5893
6034
  }
5894
6035
  });
5895
6036
  }
@@ -5963,7 +6104,7 @@ async function backfillTrafficClassificationCommand(opts) {
5963
6104
  const projectFilter = opts?.project?.trim();
5964
6105
  const isDryRun = opts?.dryRun === true;
5965
6106
  const isJson = isMachineFormat(opts?.format);
5966
- const scopedProjects = projectFilter ? db.select().from(projects).where(eq10(projects.name, projectFilter)).all() : db.select().from(projects).all();
6107
+ const scopedProjects = projectFilter ? db.select().from(projects).where(eq11(projects.name, projectFilter)).all() : db.select().from(projects).all();
5967
6108
  if (scopedProjects.length === 0) {
5968
6109
  if (projectFilter && !isJson) {
5969
6110
  process.stderr.write(`No project named "${projectFilter}".
@@ -5989,7 +6130,7 @@ async function backfillTrafficClassificationCommand(opts) {
5989
6130
  byBot: {}
5990
6131
  };
5991
6132
  const unknownCountRow = db.select({ n: sql4`count(*)` }).from(rawEventSamples).where(and9(
5992
- eq10(rawEventSamples.eventType, "unknown"),
6133
+ eq11(rawEventSamples.eventType, "unknown"),
5993
6134
  inArray4(rawEventSamples.projectId, projectIds)
5994
6135
  )).get();
5995
6136
  result.unknownBefore = Number(unknownCountRow?.n ?? 0);
@@ -6002,7 +6143,7 @@ async function backfillTrafficClassificationCommand(opts) {
6002
6143
  pathNormalized: rawEventSamples.pathNormalized,
6003
6144
  status: rawEventSamples.status
6004
6145
  }).from(rawEventSamples).where(and9(
6005
- eq10(rawEventSamples.eventType, "unknown"),
6146
+ eq11(rawEventSamples.eventType, "unknown"),
6006
6147
  inArray4(rawEventSamples.projectId, projectIds)
6007
6148
  )).all();
6008
6149
  result.examined = unknownSamples.length;
@@ -6041,7 +6182,7 @@ async function backfillTrafficClassificationCommand(opts) {
6041
6182
  result.reclassified++;
6042
6183
  result.byBot[classified.botId] = (result.byBot[classified.botId] ?? 0) + 1;
6043
6184
  if (isDryRun) continue;
6044
- db.update(rawEventSamples).set({ eventType: userFetch ? TrafficEventKinds["ai-user-fetch"] : TrafficEventKinds.crawler }).where(eq10(rawEventSamples.id, snap.id)).run();
6185
+ db.update(rawEventSamples).set({ eventType: userFetch ? TrafficEventKinds["ai-user-fetch"] : TrafficEventKinds.crawler }).where(eq11(rawEventSamples.id, snap.id)).run();
6045
6186
  const tsHour = new Date(snap.ts);
6046
6187
  tsHour.setUTCMinutes(0, 0, 0);
6047
6188
  if (userFetch) {
@@ -6106,7 +6247,7 @@ async function backfillTrafficClassificationCommand(opts) {
6106
6247
  }
6107
6248
  if (!isDryRun) {
6108
6249
  const afterRow = db.select({ n: sql4`count(*)` }).from(rawEventSamples).where(and9(
6109
- eq10(rawEventSamples.eventType, "unknown"),
6250
+ eq11(rawEventSamples.eventType, "unknown"),
6110
6251
  inArray4(rawEventSamples.projectId, projectIds)
6111
6252
  )).get();
6112
6253
  result.unknownAfter = Number(afterRow?.n ?? 0);
@@ -6141,7 +6282,7 @@ No DB writes performed. Re-run without --dry-run to apply.`);
6141
6282
  }
6142
6283
 
6143
6284
  // src/commands/skills.ts
6144
- import crypto12 from "crypto";
6285
+ import crypto13 from "crypto";
6145
6286
  import fs4 from "fs";
6146
6287
  import os4 from "os";
6147
6288
  import path5 from "path";
@@ -6196,7 +6337,7 @@ function walkRelative(dir, prefix = "") {
6196
6337
  return out.sort();
6197
6338
  }
6198
6339
  function sha256File(filePath) {
6199
- return crypto12.createHash("sha256").update(fs4.readFileSync(filePath)).digest("hex");
6340
+ return crypto13.createHash("sha256").update(fs4.readFileSync(filePath)).digest("hex");
6200
6341
  }
6201
6342
  function readSkillManifest(skillDir) {
6202
6343
  try {
@@ -6519,10 +6660,10 @@ var ProviderRegistry = class {
6519
6660
  };
6520
6661
 
6521
6662
  // src/scheduler.ts
6522
- import crypto13 from "crypto";
6663
+ import crypto14 from "crypto";
6523
6664
  import cron from "node-cron";
6524
- import { and as and10, eq as eq11 } from "drizzle-orm";
6525
- var log11 = createLogger("Scheduler");
6665
+ import { and as and10, eq as eq12, inArray as inArray5 } from "drizzle-orm";
6666
+ var log12 = createLogger("Scheduler");
6526
6667
  function taskKey(projectId, kind) {
6527
6668
  return `${projectId}::${kind}`;
6528
6669
  }
@@ -6536,16 +6677,16 @@ var Scheduler = class {
6536
6677
  }
6537
6678
  /** Load all enabled schedules from DB and register cron jobs. */
6538
6679
  start() {
6539
- const allSchedules = this.db.select().from(schedules).where(eq11(schedules.enabled, true)).all();
6680
+ const allSchedules = this.db.select().from(schedules).where(eq12(schedules.enabled, true)).all();
6540
6681
  for (const schedule of allSchedules) {
6541
6682
  const missedRunAt = schedule.nextRunAt;
6542
6683
  this.registerCronTask(schedule);
6543
6684
  if (missedRunAt && new Date(missedRunAt) < /* @__PURE__ */ new Date()) {
6544
- log11.info("run.catch-up", { projectId: schedule.projectId, kind: schedule.kind, missedRunAt });
6685
+ log12.info("run.catch-up", { projectId: schedule.projectId, kind: schedule.kind, missedRunAt });
6545
6686
  this.triggerRun(schedule.id, schedule.projectId, schedule.kind);
6546
6687
  }
6547
6688
  }
6548
- log11.info("started", { scheduleCount: allSchedules.length });
6689
+ log12.info("started", { scheduleCount: allSchedules.length });
6549
6690
  }
6550
6691
  /** Stop all cron tasks for graceful shutdown. */
6551
6692
  stop() {
@@ -6566,7 +6707,7 @@ var Scheduler = class {
6566
6707
  this.stopTask(key, existing, "Stopped");
6567
6708
  this.tasks.delete(key);
6568
6709
  }
6569
- const schedule = this.db.select().from(schedules).where(and10(eq11(schedules.projectId, projectId), eq11(schedules.kind, kind))).get();
6710
+ const schedule = this.db.select().from(schedules).where(and10(eq12(schedules.projectId, projectId), eq12(schedules.kind, kind))).get();
6570
6711
  if (schedule && schedule.enabled) {
6571
6712
  this.registerCronTask(schedule);
6572
6713
  }
@@ -6589,13 +6730,13 @@ var Scheduler = class {
6589
6730
  stopTask(key, task, verb) {
6590
6731
  void task.stop();
6591
6732
  void task.destroy();
6592
- log11.info(`task.${verb.toLowerCase()}`, { key });
6733
+ log12.info(`task.${verb.toLowerCase()}`, { key });
6593
6734
  }
6594
6735
  registerCronTask(schedule) {
6595
6736
  const { id: scheduleId, projectId, cronExpr, timezone } = schedule;
6596
6737
  const kind = schedule.kind;
6597
6738
  if (!cron.validate(cronExpr)) {
6598
- log11.error("cron.invalid", { projectId, kind, cronExpr });
6739
+ log12.error("cron.invalid", { projectId, kind, cronExpr });
6599
6740
  return;
6600
6741
  }
6601
6742
  const task = cron.schedule(cronExpr, () => {
@@ -6607,51 +6748,51 @@ var Scheduler = class {
6607
6748
  this.db.update(schedules).set({
6608
6749
  nextRunAt: nextRunFromCron(cronExpr, timezone),
6609
6750
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
6610
- }).where(eq11(schedules.id, scheduleId)).run();
6751
+ }).where(eq12(schedules.id, scheduleId)).run();
6611
6752
  const label = schedule.preset ?? cronExpr;
6612
- log11.info("cron.registered", { projectId, kind, schedule: label, timezone });
6753
+ log12.info("cron.registered", { projectId, kind, schedule: label, timezone });
6613
6754
  }
6614
6755
  triggerRun(scheduleId, projectId, kind) {
6615
6756
  try {
6616
6757
  const now = (/* @__PURE__ */ new Date()).toISOString();
6617
- const currentSchedule = this.db.select().from(schedules).where(eq11(schedules.id, scheduleId)).get();
6758
+ const currentSchedule = this.db.select().from(schedules).where(eq12(schedules.id, scheduleId)).get();
6618
6759
  if (!currentSchedule || !currentSchedule.enabled) {
6619
- log11.warn("schedule.stale", { scheduleId, projectId, kind, msg: "schedule no longer exists or is disabled" });
6760
+ log12.warn("schedule.stale", { scheduleId, projectId, kind, msg: "schedule no longer exists or is disabled" });
6620
6761
  this.remove(projectId, kind);
6621
6762
  return;
6622
6763
  }
6623
6764
  const nextRunAt = nextRunFromCron(currentSchedule.cronExpr, currentSchedule.timezone);
6624
- const project = this.db.select().from(projects).where(eq11(projects.id, projectId)).get();
6765
+ const project = this.db.select().from(projects).where(eq12(projects.id, projectId)).get();
6625
6766
  if (!project) {
6626
- log11.error("project.not-found", { projectId, kind, msg: "skipping scheduled run" });
6767
+ log12.error("project.not-found", { projectId, kind, msg: "skipping scheduled run" });
6627
6768
  this.remove(projectId, kind);
6628
6769
  return;
6629
6770
  }
6630
6771
  if (kind === SchedulableRunKinds["traffic-sync"]) {
6631
6772
  const sourceId = currentSchedule.sourceId;
6632
6773
  if (!sourceId) {
6633
- log11.warn("traffic-sync.missing-source", { scheduleId, projectId });
6774
+ log12.warn("traffic-sync.missing-source", { scheduleId, projectId });
6634
6775
  return;
6635
6776
  }
6636
6777
  if (!this.callbacks.onTrafficSyncRequested) {
6637
- log11.warn("traffic-sync.no-callback", { scheduleId, projectId, msg: "host did not register onTrafficSyncRequested" });
6778
+ log12.warn("traffic-sync.no-callback", { scheduleId, projectId, msg: "host did not register onTrafficSyncRequested" });
6638
6779
  return;
6639
6780
  }
6640
6781
  this.db.update(schedules).set({
6641
6782
  lastRunAt: now,
6642
6783
  nextRunAt,
6643
6784
  updatedAt: now
6644
- }).where(eq11(schedules.id, currentSchedule.id)).run();
6645
- log11.info("traffic-sync.triggered", { projectName: project.name, sourceId });
6785
+ }).where(eq12(schedules.id, currentSchedule.id)).run();
6786
+ log12.info("traffic-sync.triggered", { projectName: project.name, sourceId });
6646
6787
  this.callbacks.onTrafficSyncRequested(project.name, sourceId);
6647
6788
  return;
6648
6789
  }
6649
6790
  if (kind === SchedulableRunKinds["gbp-sync"]) {
6650
6791
  if (!this.callbacks.onGbpSyncRequested) {
6651
- log11.warn("gbp-sync.no-callback", { scheduleId, projectId, msg: "host did not register onGbpSyncRequested" });
6792
+ log12.warn("gbp-sync.no-callback", { scheduleId, projectId, msg: "host did not register onGbpSyncRequested" });
6652
6793
  return;
6653
6794
  }
6654
- const runId2 = crypto13.randomUUID();
6795
+ const runId2 = crypto14.randomUUID();
6655
6796
  this.db.insert(runs).values({
6656
6797
  id: runId2,
6657
6798
  projectId,
@@ -6664,45 +6805,78 @@ var Scheduler = class {
6664
6805
  lastRunAt: now,
6665
6806
  nextRunAt,
6666
6807
  updatedAt: now
6667
- }).where(eq11(schedules.id, currentSchedule.id)).run();
6668
- log11.info("gbp-sync.triggered", { runId: runId2, projectName: project.name });
6808
+ }).where(eq12(schedules.id, currentSchedule.id)).run();
6809
+ log12.info("gbp-sync.triggered", { runId: runId2, projectName: project.name });
6669
6810
  this.callbacks.onGbpSyncRequested(runId2, projectId);
6670
6811
  return;
6671
6812
  }
6672
6813
  if (kind === SchedulableRunKinds["data-refresh"]) {
6673
6814
  if (!this.callbacks.onDataRefreshRequested) {
6674
- log11.warn("data-refresh.no-callback", { scheduleId, projectId, msg: "host did not register onDataRefreshRequested" });
6815
+ log12.warn("data-refresh.no-callback", { scheduleId, projectId, msg: "host did not register onDataRefreshRequested" });
6675
6816
  return;
6676
6817
  }
6677
6818
  this.db.update(schedules).set({
6678
6819
  lastRunAt: now,
6679
6820
  nextRunAt,
6680
6821
  updatedAt: now
6681
- }).where(eq11(schedules.id, currentSchedule.id)).run();
6682
- log11.info("data-refresh.triggered", { projectName: project.name });
6822
+ }).where(eq12(schedules.id, currentSchedule.id)).run();
6823
+ log12.info("data-refresh.triggered", { projectName: project.name });
6683
6824
  this.callbacks.onDataRefreshRequested(project.name);
6684
6825
  return;
6685
6826
  }
6686
6827
  if (kind === SchedulableRunKinds["backlinks-sync"]) {
6687
6828
  if (!this.callbacks.onBacklinksSyncRequested) {
6688
- log11.warn("backlinks-sync.no-callback", { scheduleId, projectId, msg: "host did not register onBacklinksSyncRequested" });
6829
+ log12.warn("backlinks-sync.no-callback", { scheduleId, projectId, msg: "host did not register onBacklinksSyncRequested" });
6689
6830
  return;
6690
6831
  }
6691
6832
  this.db.update(schedules).set({
6692
6833
  lastRunAt: now,
6693
6834
  nextRunAt,
6694
6835
  updatedAt: now
6695
- }).where(eq11(schedules.id, currentSchedule.id)).run();
6696
- log11.info("backlinks-sync.triggered", { projectName: project.name });
6836
+ }).where(eq12(schedules.id, currentSchedule.id)).run();
6837
+ log12.info("backlinks-sync.triggered", { projectName: project.name });
6697
6838
  this.callbacks.onBacklinksSyncRequested(project.name);
6698
6839
  return;
6699
6840
  }
6841
+ if (kind === SchedulableRunKinds["site-audit"]) {
6842
+ if (!this.callbacks.onSiteAuditRequested) {
6843
+ log12.warn("site-audit.no-callback", { scheduleId, projectId, msg: "host did not register onSiteAuditRequested" });
6844
+ return;
6845
+ }
6846
+ const active = this.db.select({ id: runs.id }).from(runs).where(and10(
6847
+ eq12(runs.projectId, projectId),
6848
+ eq12(runs.kind, RunKinds["site-audit"]),
6849
+ inArray5(runs.status, [RunStatuses.queued, RunStatuses.running])
6850
+ )).get();
6851
+ if (active) {
6852
+ log12.info("site-audit.skipped-active", { projectName: project.name, activeRunId: active.id });
6853
+ this.db.update(schedules).set({ nextRunAt, updatedAt: now }).where(eq12(schedules.id, currentSchedule.id)).run();
6854
+ return;
6855
+ }
6856
+ const runId2 = crypto14.randomUUID();
6857
+ this.db.insert(runs).values({
6858
+ id: runId2,
6859
+ projectId,
6860
+ kind: RunKinds["site-audit"],
6861
+ status: RunStatuses.queued,
6862
+ trigger: RunTriggers.scheduled,
6863
+ createdAt: now
6864
+ }).run();
6865
+ this.db.update(schedules).set({
6866
+ lastRunAt: now,
6867
+ nextRunAt,
6868
+ updatedAt: now
6869
+ }).where(eq12(schedules.id, currentSchedule.id)).run();
6870
+ log12.info("site-audit.triggered", { runId: runId2, projectName: project.name });
6871
+ this.callbacks.onSiteAuditRequested(runId2, projectId);
6872
+ return;
6873
+ }
6700
6874
  const projectLocations = project.locations;
6701
6875
  let resolvedLocation;
6702
6876
  if (project.defaultLocation) {
6703
6877
  const loc = projectLocations.find((l) => l.label === project.defaultLocation);
6704
6878
  if (!loc) {
6705
- log11.warn("default-location.stale", { scheduleId, projectId, label: project.defaultLocation });
6879
+ log12.warn("default-location.stale", { scheduleId, projectId, label: project.defaultLocation });
6706
6880
  return;
6707
6881
  }
6708
6882
  resolvedLocation = loc;
@@ -6716,11 +6890,11 @@ var Scheduler = class {
6716
6890
  location: locationLabel
6717
6891
  });
6718
6892
  if (queueResult.conflict) {
6719
- log11.info("run.skipped-active", { projectName: project.name, activeRunId: queueResult.activeRunId });
6893
+ log12.info("run.skipped-active", { projectName: project.name, activeRunId: queueResult.activeRunId });
6720
6894
  this.db.update(schedules).set({
6721
6895
  nextRunAt,
6722
6896
  updatedAt: now
6723
- }).where(eq11(schedules.id, currentSchedule.id)).run();
6897
+ }).where(eq12(schedules.id, currentSchedule.id)).run();
6724
6898
  return;
6725
6899
  }
6726
6900
  const runId = queueResult.runId;
@@ -6728,19 +6902,19 @@ var Scheduler = class {
6728
6902
  lastRunAt: now,
6729
6903
  nextRunAt,
6730
6904
  updatedAt: now
6731
- }).where(eq11(schedules.id, currentSchedule.id)).run();
6905
+ }).where(eq12(schedules.id, currentSchedule.id)).run();
6732
6906
  const scheduleProviders = currentSchedule.providers;
6733
6907
  const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
6734
- log11.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
6908
+ log12.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
6735
6909
  this.callbacks.onRunCreated(runId, projectId, providers, resolvedLocation);
6736
6910
  } catch (err) {
6737
- log11.error("trigger.error", { scheduleId, projectId, kind, error: err instanceof Error ? err.message : String(err) });
6911
+ log12.error("trigger.error", { scheduleId, projectId, kind, error: err instanceof Error ? err.message : String(err) });
6738
6912
  }
6739
6913
  }
6740
6914
  };
6741
6915
 
6742
6916
  // src/data-refresh.ts
6743
- var log12 = createLogger("DataRefresh");
6917
+ var log13 = createLogger("DataRefresh");
6744
6918
  async function refreshAllIntegrations(client, projectName) {
6745
6919
  const integrations = [
6746
6920
  { name: "gsc", run: () => client.gscSync(projectName, {}) },
@@ -6752,19 +6926,19 @@ async function refreshAllIntegrations(client, projectName) {
6752
6926
  results.forEach((result, idx) => {
6753
6927
  const integration = integrations[idx].name;
6754
6928
  if (result.status === "fulfilled") {
6755
- log12.info("integration.refreshed", { projectName, integration });
6929
+ log13.info("integration.refreshed", { projectName, integration });
6756
6930
  } else {
6757
6931
  const reason = result.reason;
6758
6932
  const message = reason instanceof Error ? reason.message : String(reason);
6759
- log12.warn("integration.refresh-failed", { projectName, integration, error: message });
6933
+ log13.warn("integration.refresh-failed", { projectName, integration, error: message });
6760
6934
  }
6761
6935
  });
6762
6936
  }
6763
6937
 
6764
6938
  // src/notifier.ts
6765
- import { eq as eq12, desc as desc5, and as and11, inArray as inArray5, or } from "drizzle-orm";
6766
- import crypto14 from "crypto";
6767
- var log13 = createLogger("Notifier");
6939
+ import { eq as eq13, desc as desc5, and as and11, inArray as inArray6, or } from "drizzle-orm";
6940
+ import crypto15 from "crypto";
6941
+ var log14 = createLogger("Notifier");
6768
6942
  var Notifier = class {
6769
6943
  db;
6770
6944
  serverUrl;
@@ -6774,26 +6948,26 @@ var Notifier = class {
6774
6948
  }
6775
6949
  /** Called after a run completes (success, partial, or failed). */
6776
6950
  async onRunCompleted(runId, projectId) {
6777
- log13.info("run.completed", { runId, projectId });
6778
- const notifs = this.db.select().from(notifications).where(eq12(notifications.projectId, projectId)).all().filter((n) => n.enabled);
6951
+ log14.info("run.completed", { runId, projectId });
6952
+ const notifs = this.db.select().from(notifications).where(eq13(notifications.projectId, projectId)).all().filter((n) => n.enabled);
6779
6953
  if (notifs.length === 0) {
6780
- log13.info("notifications.none-enabled", { projectId });
6954
+ log14.info("notifications.none-enabled", { projectId });
6781
6955
  return;
6782
6956
  }
6783
- log13.info("notifications.found", { projectId, count: notifs.length });
6784
- const run = this.db.select().from(runs).where(eq12(runs.id, runId)).get();
6957
+ log14.info("notifications.found", { projectId, count: notifs.length });
6958
+ const run = this.db.select().from(runs).where(eq13(runs.id, runId)).get();
6785
6959
  if (!run) {
6786
- log13.error("run.not-found", { runId, msg: "skipping notification dispatch" });
6960
+ log14.error("run.not-found", { runId, msg: "skipping notification dispatch" });
6787
6961
  return;
6788
6962
  }
6789
- const project = this.db.select().from(projects).where(eq12(projects.id, projectId)).get();
6963
+ const project = this.db.select().from(projects).where(eq13(projects.id, projectId)).get();
6790
6964
  if (!project) {
6791
- log13.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
6965
+ log14.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
6792
6966
  return;
6793
6967
  }
6794
6968
  const transitions = this.computeTransitions(runId, projectId);
6795
6969
  const events = [];
6796
- log13.info("run.status", { runId: run.id, status: run.status, projectId });
6970
+ log14.info("run.status", { runId: run.id, status: run.status, projectId });
6797
6971
  if (run.status === "completed" || run.status === "partial") {
6798
6972
  events.push("run.completed");
6799
6973
  }
@@ -6809,7 +6983,7 @@ var Notifier = class {
6809
6983
  if (!config.url) continue;
6810
6984
  const subscribedEvents = config.events;
6811
6985
  const matchingEvents = events.filter((e) => subscribedEvents.includes(e));
6812
- log13.info("notification.match", { notificationId: notif.id, subscribedEvents, matchedEvents: matchingEvents });
6986
+ log14.info("notification.match", { notificationId: notif.id, subscribedEvents, matchedEvents: matchingEvents });
6813
6987
  if (matchingEvents.length === 0) continue;
6814
6988
  for (const event of matchingEvents) {
6815
6989
  const relevantTransitions = event === "citation.lost" ? lostTransitions : event === "citation.gained" ? gainedTransitions : transitions;
@@ -6833,11 +7007,11 @@ var Notifier = class {
6833
7007
  if (criticalInsights.length > 0) insightEvents.push("insight.critical");
6834
7008
  if (highInsights.length > 0) insightEvents.push("insight.high");
6835
7009
  if (insightEvents.length === 0) return;
6836
- const notifs = this.db.select().from(notifications).where(eq12(notifications.projectId, projectId)).all().filter((n) => n.enabled);
7010
+ const notifs = this.db.select().from(notifications).where(eq13(notifications.projectId, projectId)).all().filter((n) => n.enabled);
6837
7011
  if (notifs.length === 0) return;
6838
- const run = this.db.select().from(runs).where(eq12(runs.id, runId)).get();
7012
+ const run = this.db.select().from(runs).where(eq13(runs.id, runId)).get();
6839
7013
  if (!run) return;
6840
- const project = this.db.select().from(projects).where(eq12(projects.id, projectId)).get();
7014
+ const project = this.db.select().from(projects).where(eq13(projects.id, projectId)).get();
6841
7015
  if (!project) return;
6842
7016
  for (const notif of notifs) {
6843
7017
  const config = notif.config;
@@ -6867,12 +7041,12 @@ var Notifier = class {
6867
7041
  }
6868
7042
  }
6869
7043
  computeTransitions(runId, projectId) {
6870
- const thisRun = this.db.select().from(runs).where(eq12(runs.id, runId)).get();
7044
+ const thisRun = this.db.select().from(runs).where(eq13(runs.id, runId)).get();
6871
7045
  if (!thisRun) return [];
6872
7046
  const groupSiblings = this.db.select().from(runs).where(and11(
6873
- eq12(runs.projectId, projectId),
6874
- eq12(runs.kind, thisRun.kind),
6875
- eq12(runs.createdAt, thisRun.createdAt)
7047
+ eq13(runs.projectId, projectId),
7048
+ eq13(runs.kind, thisRun.kind),
7049
+ eq13(runs.createdAt, thisRun.createdAt)
6876
7050
  )).all();
6877
7051
  const stillPending = groupSiblings.some((r) => r.status === "queued" || r.status === "running");
6878
7052
  if (stillPending) return [];
@@ -6888,7 +7062,7 @@ var Notifier = class {
6888
7062
  return candidate.id > best.id ? candidate : best;
6889
7063
  });
6890
7064
  if (winner.id !== runId) return [];
6891
- const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(eq12(projects.id, projectId)).get();
7065
+ const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(eq13(projects.id, projectId)).get();
6892
7066
  const locationCount = Math.max(
6893
7067
  1,
6894
7068
  (projectLocations?.locations ?? []).length
@@ -6896,9 +7070,9 @@ var Notifier = class {
6896
7070
  const RECENT_FETCH_LIMIT = Math.max(8, locationCount * 4);
6897
7071
  const recentRuns = this.db.select().from(runs).where(
6898
7072
  and11(
6899
- eq12(runs.projectId, projectId),
6900
- eq12(runs.kind, thisRun.kind),
6901
- or(eq12(runs.status, "completed"), eq12(runs.status, "partial"))
7073
+ eq13(runs.projectId, projectId),
7074
+ eq13(runs.kind, thisRun.kind),
7075
+ or(eq13(runs.status, "completed"), eq13(runs.status, "partial"))
6902
7076
  )
6903
7077
  ).orderBy(desc5(runs.createdAt), desc5(runs.id)).limit(RECENT_FETCH_LIMIT).all();
6904
7078
  const groups = groupRunsByCreatedAt(recentRuns);
@@ -6915,13 +7089,13 @@ var Notifier = class {
6915
7089
  provider: querySnapshots.provider,
6916
7090
  location: querySnapshots.location,
6917
7091
  citationState: querySnapshots.citationState
6918
- }).from(querySnapshots).leftJoin(queries, eq12(querySnapshots.queryId, queries.id)).where(inArray5(querySnapshots.runId, currentRunIds)).all();
7092
+ }).from(querySnapshots).leftJoin(queries, eq13(querySnapshots.queryId, queries.id)).where(inArray6(querySnapshots.runId, currentRunIds)).all();
6919
7093
  const previousSnapshots = this.db.select({
6920
7094
  queryId: querySnapshots.queryId,
6921
7095
  provider: querySnapshots.provider,
6922
7096
  location: querySnapshots.location,
6923
7097
  citationState: querySnapshots.citationState
6924
- }).from(querySnapshots).where(inArray5(querySnapshots.runId, previousRunIds)).all();
7098
+ }).from(querySnapshots).where(inArray6(querySnapshots.runId, previousRunIds)).all();
6925
7099
  const prevMap = /* @__PURE__ */ new Map();
6926
7100
  for (const s of previousSnapshots) {
6927
7101
  if (s.queryId == null) continue;
@@ -6948,23 +7122,23 @@ var Notifier = class {
6948
7122
  const targetLabel = redactNotificationUrl(url).urlDisplay;
6949
7123
  const targetCheck = await resolveWebhookTarget(url);
6950
7124
  if (!targetCheck.ok) {
6951
- log13.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
7125
+ log14.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
6952
7126
  this.logDelivery(projectId, notificationId, payload.event, "failed", `SSRF: ${targetCheck.message}`);
6953
7127
  return;
6954
7128
  }
6955
- log13.info("webhook.send", { event: payload.event, url: targetLabel });
7129
+ log14.info("webhook.send", { event: payload.event, url: targetLabel });
6956
7130
  const maxRetries = 3;
6957
7131
  const delays = [1e3, 4e3, 16e3];
6958
7132
  for (let attempt = 0; attempt < maxRetries; attempt++) {
6959
7133
  try {
6960
7134
  const response = await deliverWebhook(targetCheck.target, payload, webhookSecret);
6961
7135
  if (response.status >= 200 && response.status < 300) {
6962
- log13.info("webhook.delivered", { event: payload.event, url: targetLabel, httpStatus: response.status });
7136
+ log14.info("webhook.delivered", { event: payload.event, url: targetLabel, httpStatus: response.status });
6963
7137
  this.logDelivery(projectId, notificationId, payload.event, "sent", null);
6964
7138
  return;
6965
7139
  }
6966
7140
  const errorDetail = response.error ?? `HTTP ${response.status}`;
6967
- log13.warn("webhook.attempt-failed", { event: payload.event, url: targetLabel, attempt: attempt + 1, maxRetries, httpStatus: response.status, error: errorDetail });
7141
+ log14.warn("webhook.attempt-failed", { event: payload.event, url: targetLabel, attempt: attempt + 1, maxRetries, httpStatus: response.status, error: errorDetail });
6968
7142
  if (attempt === maxRetries - 1) {
6969
7143
  this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
6970
7144
  }
@@ -6972,7 +7146,7 @@ var Notifier = class {
6972
7146
  const errorDetail = err instanceof Error ? err.message : String(err);
6973
7147
  if (attempt === maxRetries - 1) {
6974
7148
  this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
6975
- log13.error("webhook.exhausted", { event: payload.event, url: targetLabel, maxRetries, error: errorDetail });
7149
+ log14.error("webhook.exhausted", { event: payload.event, url: targetLabel, maxRetries, error: errorDetail });
6976
7150
  }
6977
7151
  }
6978
7152
  if (attempt < maxRetries - 1) {
@@ -6982,7 +7156,7 @@ var Notifier = class {
6982
7156
  }
6983
7157
  logDelivery(projectId, notificationId, event, status, error) {
6984
7158
  this.db.insert(auditLog).values({
6985
- id: crypto14.randomUUID(),
7159
+ id: crypto15.randomUUID(),
6986
7160
  projectId,
6987
7161
  actor: "scheduler",
6988
7162
  action: `notification.${status}`,
@@ -6995,8 +7169,8 @@ var Notifier = class {
6995
7169
  };
6996
7170
 
6997
7171
  // src/run-coordinator.ts
6998
- import { eq as eq13 } from "drizzle-orm";
6999
- var log14 = createLogger("RunCoordinator");
7172
+ import { eq as eq14 } from "drizzle-orm";
7173
+ var log15 = createLogger("RunCoordinator");
7000
7174
  var RunCoordinator = class {
7001
7175
  constructor(db, notifier, intelligenceService, onInsightsGenerated, onAeroEvent) {
7002
7176
  this.db = db;
@@ -7006,10 +7180,10 @@ var RunCoordinator = class {
7006
7180
  this.onAeroEvent = onAeroEvent;
7007
7181
  }
7008
7182
  async onRunCompleted(runId, projectId) {
7009
- const runRow = this.db.select().from(runs).where(eq13(runs.id, runId)).get();
7183
+ const runRow = this.db.select().from(runs).where(eq14(runs.id, runId)).get();
7010
7184
  const kind = runRow?.kind ?? RunKinds["answer-visibility"];
7011
7185
  if (runRow?.trigger === RunTriggers.probe) {
7012
- log14.info("probe.skip-side-effects", { runId, projectId, kind });
7186
+ log15.info("probe.skip-side-effects", { runId, projectId, kind });
7013
7187
  return;
7014
7188
  }
7015
7189
  let insightCount = 0;
@@ -7026,12 +7200,12 @@ var RunCoordinator = class {
7026
7200
  try {
7027
7201
  await this.onInsightsGenerated(runId, projectId, result);
7028
7202
  } catch (err) {
7029
- log14.error("insight-webhook.failed", { runId, error: err instanceof Error ? err.message : String(err) });
7203
+ log15.error("insight-webhook.failed", { runId, error: err instanceof Error ? err.message : String(err) });
7030
7204
  }
7031
7205
  }
7032
7206
  }
7033
7207
  } catch (err) {
7034
- log14.error("intelligence.failed", { runId, error: err instanceof Error ? err.message : String(err) });
7208
+ log15.error("intelligence.failed", { runId, error: err instanceof Error ? err.message : String(err) });
7035
7209
  }
7036
7210
  } else if (kind === RunKinds["gbp-sync"]) {
7037
7211
  try {
@@ -7044,17 +7218,17 @@ var RunCoordinator = class {
7044
7218
  try {
7045
7219
  await this.onInsightsGenerated(runId, projectId, analysisResultFromInsights(gbpInsights));
7046
7220
  } catch (err) {
7047
- log14.error("gbp-insight-webhook.failed", { runId, error: err instanceof Error ? err.message : String(err) });
7221
+ log15.error("gbp-insight-webhook.failed", { runId, error: err instanceof Error ? err.message : String(err) });
7048
7222
  }
7049
7223
  }
7050
7224
  } catch (err) {
7051
- log14.error("gbp-intelligence.failed", { runId, error: err instanceof Error ? err.message : String(err) });
7225
+ log15.error("gbp-intelligence.failed", { runId, error: err instanceof Error ? err.message : String(err) });
7052
7226
  }
7053
7227
  }
7054
7228
  try {
7055
7229
  await this.notifier.onRunCompleted(runId, projectId);
7056
7230
  } catch (err) {
7057
- log14.error("notifier.failed", { runId, error: err instanceof Error ? err.message : String(err) });
7231
+ log15.error("notifier.failed", { runId, error: err instanceof Error ? err.message : String(err) });
7058
7232
  }
7059
7233
  if (this.onAeroEvent) {
7060
7234
  try {
@@ -7067,7 +7241,7 @@ var RunCoordinator = class {
7067
7241
  };
7068
7242
  await this.onAeroEvent(ctx);
7069
7243
  } catch (err) {
7070
- log14.error("aero.failed", { runId, error: err instanceof Error ? err.message : String(err) });
7244
+ log15.error("aero.failed", { runId, error: err instanceof Error ? err.message : String(err) });
7071
7245
  }
7072
7246
  }
7073
7247
  }
@@ -7082,7 +7256,7 @@ var RunCoordinator = class {
7082
7256
  * so the Aero queue is never starved of a follow-up.
7083
7257
  */
7084
7258
  buildDiscoveryAeroContext(runId, projectId, status, error) {
7085
- const session = this.db.select().from(discoverySessions).where(eq13(discoverySessions.runId, runId)).get();
7259
+ const session = this.db.select().from(discoverySessions).where(eq14(discoverySessions.runId, runId)).get();
7086
7260
  const competitorMap = session ? session.competitorMap : [];
7087
7261
  return {
7088
7262
  kind: RunKinds["aeo-discover-probe"],
@@ -7122,8 +7296,8 @@ function analysisResultFromInsights(insights2) {
7122
7296
  }
7123
7297
 
7124
7298
  // src/agent/session-registry.ts
7125
- import crypto16 from "crypto";
7126
- import { eq as eq15 } from "drizzle-orm";
7299
+ import crypto17 from "crypto";
7300
+ import { eq as eq16 } from "drizzle-orm";
7127
7301
 
7128
7302
  // src/agent/session.ts
7129
7303
  import fs7 from "fs";
@@ -7511,8 +7685,8 @@ function resolveSessionProviderAndModel(config, opts) {
7511
7685
  }
7512
7686
 
7513
7687
  // src/agent/memory-store.ts
7514
- import crypto15 from "crypto";
7515
- import { and as and12, desc as desc6, eq as eq14, like, sql as sql5 } from "drizzle-orm";
7688
+ import crypto16 from "crypto";
7689
+ import { and as and12, desc as desc6, eq as eq15, like, sql as sql5 } from "drizzle-orm";
7516
7690
  var COMPACTION_KEY_PREFIX = "compaction:";
7517
7691
  var COMPACTION_NOTES_PER_SESSION = 3;
7518
7692
  function rowToDto(row) {
@@ -7526,7 +7700,7 @@ function rowToDto(row) {
7526
7700
  };
7527
7701
  }
7528
7702
  function listMemoryEntries(db, projectId, opts = {}) {
7529
- const query = db.select().from(agentMemory).where(eq14(agentMemory.projectId, projectId)).orderBy(desc6(agentMemory.updatedAt));
7703
+ const query = db.select().from(agentMemory).where(eq15(agentMemory.projectId, projectId)).orderBy(desc6(agentMemory.updatedAt));
7530
7704
  const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
7531
7705
  return rows.map(rowToDto);
7532
7706
  }
@@ -7540,7 +7714,7 @@ function upsertMemoryEntry(db, args) {
7540
7714
  throw new Error(`memory key prefix "${COMPACTION_KEY_PREFIX}" is reserved for compaction notes`);
7541
7715
  }
7542
7716
  const now = (/* @__PURE__ */ new Date()).toISOString();
7543
- const id = crypto15.randomUUID();
7717
+ const id = crypto16.randomUUID();
7544
7718
  db.insert(agentMemory).values({
7545
7719
  id,
7546
7720
  projectId: args.projectId,
@@ -7557,12 +7731,12 @@ function upsertMemoryEntry(db, args) {
7557
7731
  updatedAt: now
7558
7732
  }
7559
7733
  }).run();
7560
- const row = db.select().from(agentMemory).where(and12(eq14(agentMemory.projectId, args.projectId), eq14(agentMemory.key, args.key))).get();
7734
+ const row = db.select().from(agentMemory).where(and12(eq15(agentMemory.projectId, args.projectId), eq15(agentMemory.key, args.key))).get();
7561
7735
  if (!row) throw new Error("memory upsert produced no row");
7562
7736
  return rowToDto(row);
7563
7737
  }
7564
7738
  function deleteMemoryEntry(db, projectId, key) {
7565
- const result = db.delete(agentMemory).where(and12(eq14(agentMemory.projectId, projectId), eq14(agentMemory.key, key))).run();
7739
+ const result = db.delete(agentMemory).where(and12(eq15(agentMemory.projectId, projectId), eq15(agentMemory.key, key))).run();
7566
7740
  const changes = result.changes ?? 0;
7567
7741
  return changes > 0;
7568
7742
  }
@@ -7577,7 +7751,7 @@ function writeCompactionNote(db, args) {
7577
7751
  }
7578
7752
  const now = (/* @__PURE__ */ new Date()).toISOString();
7579
7753
  const key = `${COMPACTION_KEY_PREFIX}${args.sessionId}:${now}`;
7580
- const id = crypto15.randomUUID();
7754
+ const id = crypto16.randomUUID();
7581
7755
  let inserted;
7582
7756
  db.transaction((tx) => {
7583
7757
  tx.insert(agentMemory).values({
@@ -7592,7 +7766,7 @@ function writeCompactionNote(db, args) {
7592
7766
  const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
7593
7767
  const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
7594
7768
  and12(
7595
- eq14(agentMemory.projectId, args.projectId),
7769
+ eq15(agentMemory.projectId, args.projectId),
7596
7770
  like(agentMemory.key, `${sessionPrefix}%`)
7597
7771
  )
7598
7772
  ).orderBy(desc6(agentMemory.updatedAt)).all();
@@ -7600,7 +7774,7 @@ function writeCompactionNote(db, args) {
7600
7774
  if (stale.length > 0) {
7601
7775
  tx.delete(agentMemory).where(sql5`${agentMemory.id} IN (${sql5.join(stale.map((s) => sql5`${s}`), sql5`, `)})`).run();
7602
7776
  }
7603
- const row = tx.select().from(agentMemory).where(and12(eq14(agentMemory.projectId, args.projectId), eq14(agentMemory.key, key))).get();
7777
+ const row = tx.select().from(agentMemory).where(and12(eq15(agentMemory.projectId, args.projectId), eq15(agentMemory.key, key))).get();
7604
7778
  if (row) inserted = rowToDto(row);
7605
7779
  });
7606
7780
  if (!inserted) throw new Error("compaction note write produced no row");
@@ -7733,7 +7907,7 @@ async function compactMessages(args) {
7733
7907
  }
7734
7908
 
7735
7909
  // src/agent/session-registry.ts
7736
- var log15 = createLogger("SessionRegistry");
7910
+ var log16 = createLogger("SessionRegistry");
7737
7911
  var MAX_HYDRATE_NOTES = 20;
7738
7912
  var MAX_HYDRATE_BYTES = 32 * 1024;
7739
7913
  function escapeMemoryFragment(value) {
@@ -7782,7 +7956,7 @@ var SessionRegistry = class {
7782
7956
  modelProvider: effectiveProvider,
7783
7957
  modelId: effectiveModelId,
7784
7958
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
7785
- }).where(eq15(agentSessions.projectId, projectId)).run();
7959
+ }).where(eq16(agentSessions.projectId, projectId)).run();
7786
7960
  }
7787
7961
  const agent2 = createAeroSession({
7788
7962
  projectName,
@@ -7960,13 +8134,13 @@ ${lines.join("\n")}
7960
8134
  agent.state.messages = result.messages;
7961
8135
  agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, row.systemPrompt);
7962
8136
  this.save(projectName);
7963
- log15.info("compaction.completed", {
8137
+ log16.info("compaction.completed", {
7964
8138
  projectName,
7965
8139
  removedCount: result.removedCount,
7966
8140
  summaryBytes: Buffer.byteLength(result.summary, "utf8")
7967
8141
  });
7968
8142
  } catch (err) {
7969
- log15.error("compaction.failed", {
8143
+ log16.error("compaction.failed", {
7970
8144
  projectName,
7971
8145
  error: err instanceof Error ? err.message : String(err)
7972
8146
  });
@@ -7996,7 +8170,7 @@ ${lines.join("\n")}
7996
8170
  modelProvider: nextProvider,
7997
8171
  modelId: nextModelId,
7998
8172
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
7999
- }).where(eq15(agentSessions.projectId, projectId)).run();
8173
+ }).where(eq16(agentSessions.projectId, projectId)).run();
8000
8174
  }
8001
8175
  /** Persist a session's transcript back to the DB. Call after any run settles. */
8002
8176
  save(projectName) {
@@ -8063,7 +8237,7 @@ ${lines.join("\n")}
8063
8237
  await agent.prompt(msgs);
8064
8238
  this.save(projectName);
8065
8239
  } catch (err) {
8066
- log15.error("drain.failed", {
8240
+ log16.error("drain.failed", {
8067
8241
  projectName,
8068
8242
  error: err instanceof Error ? err.message : String(err)
8069
8243
  });
@@ -8158,17 +8332,17 @@ ${lines.join("\n")}
8158
8332
  return id;
8159
8333
  }
8160
8334
  tryResolveProjectId(projectName) {
8161
- const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq15(projects.name, projectName)).get();
8335
+ const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq16(projects.name, projectName)).get();
8162
8336
  return row?.id;
8163
8337
  }
8164
8338
  loadRow(projectId) {
8165
- const row = this.opts.db.select().from(agentSessions).where(eq15(agentSessions.projectId, projectId)).get();
8339
+ const row = this.opts.db.select().from(agentSessions).where(eq16(agentSessions.projectId, projectId)).get();
8166
8340
  return row ?? null;
8167
8341
  }
8168
8342
  insertRow(params) {
8169
8343
  const now = (/* @__PURE__ */ new Date()).toISOString();
8170
8344
  this.opts.db.insert(agentSessions).values({
8171
- id: crypto16.randomUUID(),
8345
+ id: crypto17.randomUUID(),
8172
8346
  projectId: params.projectId,
8173
8347
  systemPrompt: params.systemPrompt,
8174
8348
  modelProvider: params.provider ?? params.modelProvider ?? AgentProviderIds.claude,
@@ -8181,14 +8355,14 @@ ${lines.join("\n")}
8181
8355
  }
8182
8356
  updateRow(projectId, patch) {
8183
8357
  const now = (/* @__PURE__ */ new Date()).toISOString();
8184
- this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq15(agentSessions.projectId, projectId)).run();
8358
+ this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq16(agentSessions.projectId, projectId)).run();
8185
8359
  }
8186
8360
  };
8187
8361
 
8188
8362
  // src/agent/agent-routes.ts
8189
- import { eq as eq16 } from "drizzle-orm";
8363
+ import { eq as eq17 } from "drizzle-orm";
8190
8364
  function resolveProject(db, name) {
8191
- const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq16(projects.name, name)).get();
8365
+ const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq17(projects.name, name)).get();
8192
8366
  if (!row) throw notFound("project", name);
8193
8367
  return row;
8194
8368
  }
@@ -8197,7 +8371,7 @@ function registerAgentRoutes(app, opts) {
8197
8371
  "/projects/:name/agent/transcript",
8198
8372
  async (request) => {
8199
8373
  const project = resolveProject(opts.db, request.params.name);
8200
- const row = opts.db.select().from(agentSessions).where(eq16(agentSessions.projectId, project.id)).get();
8374
+ const row = opts.db.select().from(agentSessions).where(eq17(agentSessions.projectId, project.id)).get();
8201
8375
  if (!row) {
8202
8376
  return { messages: [], modelProvider: null, modelId: null, updatedAt: null };
8203
8377
  }
@@ -8221,7 +8395,7 @@ function registerAgentRoutes(app, opts) {
8221
8395
  async (request) => {
8222
8396
  const project = resolveProject(opts.db, request.params.name);
8223
8397
  opts.sessionRegistry.reset(project.name);
8224
- opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq16(agentSessions.projectId, project.id)).run();
8398
+ opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq17(agentSessions.projectId, project.id)).run();
8225
8399
  return { status: "reset" };
8226
8400
  }
8227
8401
  );
@@ -8661,7 +8835,7 @@ function formatAuditFactorScore(factor) {
8661
8835
  }
8662
8836
 
8663
8837
  // src/snapshot-service.ts
8664
- var log16 = createLogger("Snapshot");
8838
+ var log17 = createLogger("Snapshot");
8665
8839
  var ANALYSIS_PROVIDER_PRIORITY = ["openai", "claude", "gemini", "perplexity", "local"];
8666
8840
  var SNAPSHOT_QUERY_COUNT = 6;
8667
8841
  var ProviderExecutionGate2 = class {
@@ -8804,13 +8978,12 @@ var SnapshotService = class {
8804
8978
  return mapAuditReport(report);
8805
8979
  } catch (err) {
8806
8980
  const message = err instanceof Error ? err.message : String(err);
8807
- log16.warn("audit.failed", { homepageUrl, error: message });
8981
+ log17.warn("audit.failed", { homepageUrl, error: message });
8808
8982
  return {
8809
8983
  url: homepageUrl,
8810
8984
  finalUrl: homepageUrl,
8811
8985
  auditedAt: (/* @__PURE__ */ new Date()).toISOString(),
8812
8986
  overallScore: 0,
8813
- overallGrade: "N/A",
8814
8987
  summary: `Technical audit unavailable: ${message}`,
8815
8988
  factors: []
8816
8989
  };
@@ -8834,7 +9007,7 @@ var SnapshotService = class {
8834
9007
  queries: parsedQueries
8835
9008
  };
8836
9009
  } catch (err) {
8837
- log16.warn("profile.generation-failed", {
9010
+ log17.warn("profile.generation-failed", {
8838
9011
  domain: ctx.domain,
8839
9012
  provider: ctx.analysisProvider.adapter.name,
8840
9013
  error: err instanceof Error ? err.message : String(err)
@@ -8976,7 +9149,7 @@ var SnapshotService = class {
8976
9149
  recommendedActions: uniqueStrings(parsed.recommendedActions ?? []).slice(0, 4)
8977
9150
  };
8978
9151
  } catch (err) {
8979
- log16.warn("response.analysis-failed", {
9152
+ log17.warn("response.analysis-failed", {
8980
9153
  provider: ctx.analysisProvider.adapter.name,
8981
9154
  error: err instanceof Error ? err.message : String(err)
8982
9155
  });
@@ -9036,7 +9209,7 @@ function buildBatchAnalysisPrompt(ctx) {
9036
9209
  `Services: ${ctx.profile.services.join(", ") || "unknown"}`,
9037
9210
  `Category terms: ${ctx.profile.categoryTerms.join(", ") || "unknown"}`,
9038
9211
  `Manual competitor hints: ${ctx.manualCompetitors.join(", ") || "none"}`,
9039
- `Technical audit: ${ctx.audit.overallScore}/100 (${ctx.audit.overallGrade}) \u2014 ${ctx.audit.summary}`,
9212
+ `Technical audit: ${ctx.audit.overallScore}/100 \u2014 ${ctx.audit.summary}`,
9040
9213
  "",
9041
9214
  "Responses JSON:",
9042
9215
  JSON.stringify(ctx.responses, null, 2)
@@ -9047,7 +9220,7 @@ function buildFallbackBatchAssessment(companyName, audit) {
9047
9220
  assessments: [],
9048
9221
  whatThisMeans: [
9049
9222
  `${companyName} needs category-level visibility, not just branded comprehension.`,
9050
- `The technical baseline is ${audit.overallScore}/100 (${audit.overallGrade}), so weak site signals may be making AI systems prefer better-structured alternatives.`
9223
+ `The technical baseline is ${audit.overallScore}/100, so weak site signals may be making AI systems prefer better-structured alternatives.`
9051
9224
  ],
9052
9225
  recommendedActions: buildFallbackRecommendedActions(audit)
9053
9226
  };
@@ -9191,7 +9364,6 @@ function mapAuditReport(report) {
9191
9364
  finalUrl: report.finalUrl,
9192
9365
  auditedAt: report.auditedAt,
9193
9366
  overallScore: report.overallScore,
9194
- overallGrade: report.overallGrade,
9195
9367
  summary: report.summary,
9196
9368
  factors: report.factors.map(mapAuditFactor)
9197
9369
  };
@@ -9202,8 +9374,6 @@ function mapAuditFactor(factor) {
9202
9374
  name: factor.name,
9203
9375
  weight: factor.weight,
9204
9376
  score: factor.score,
9205
- grade: factor.grade,
9206
- status: factor.status,
9207
9377
  findings: factor.findings.map((finding) => ({
9208
9378
  type: finding.type,
9209
9379
  message: finding.message
@@ -9261,7 +9431,7 @@ function clipText(value, length) {
9261
9431
  // src/server.ts
9262
9432
  var _require3 = createRequire3(import.meta.url);
9263
9433
  var { version: PKG_VERSION2 } = _require3("../package.json");
9264
- var log17 = createLogger("Server");
9434
+ var log18 = createLogger("Server");
9265
9435
  var DEFAULT_QUOTA = {
9266
9436
  maxConcurrency: 2,
9267
9437
  maxRequestsPerMinute: 10,
@@ -9291,14 +9461,14 @@ function summarizeProviderConfig(provider, config) {
9291
9461
  };
9292
9462
  }
9293
9463
  function hashApiKey(key) {
9294
- return crypto17.createHash("sha256").update(key).digest("hex");
9464
+ return crypto18.createHash("sha256").update(key).digest("hex");
9295
9465
  }
9296
9466
  var DASHBOARD_SCRYPT_KEYLEN = 64;
9297
9467
  var DASHBOARD_SCRYPT_COST = 1 << 15;
9298
9468
  var DASHBOARD_SCRYPT_MAXMEM = 64 * 1024 * 1024;
9299
9469
  function hashDashboardPassword(password) {
9300
- const salt = crypto17.randomBytes(16);
9301
- const derived = crypto17.scryptSync(password, salt, DASHBOARD_SCRYPT_KEYLEN, {
9470
+ const salt = crypto18.randomBytes(16);
9471
+ const derived = crypto18.scryptSync(password, salt, DASHBOARD_SCRYPT_KEYLEN, {
9302
9472
  N: DASHBOARD_SCRYPT_COST,
9303
9473
  maxmem: DASHBOARD_SCRYPT_MAXMEM
9304
9474
  });
@@ -9319,18 +9489,18 @@ function verifyDashboardPassword(password, storedHash) {
9319
9489
  } catch {
9320
9490
  return { ok: false, needsRehash: false };
9321
9491
  }
9322
- const derived = crypto17.scryptSync(password, salt, expected.length, {
9492
+ const derived = crypto18.scryptSync(password, salt, expected.length, {
9323
9493
  N: DASHBOARD_SCRYPT_COST,
9324
9494
  maxmem: DASHBOARD_SCRYPT_MAXMEM
9325
9495
  });
9326
9496
  if (derived.length !== expected.length) return { ok: false, needsRehash: false };
9327
- return { ok: crypto17.timingSafeEqual(derived, expected), needsRehash: false };
9497
+ return { ok: crypto18.timingSafeEqual(derived, expected), needsRehash: false };
9328
9498
  }
9329
9499
  if (/^[a-f0-9]{64}$/i.test(storedHash)) {
9330
9500
  const candidate = Buffer.from(hashApiKey(password), "hex");
9331
9501
  const expected = Buffer.from(storedHash, "hex");
9332
9502
  if (candidate.length !== expected.length) return { ok: false, needsRehash: false };
9333
- const ok = crypto17.timingSafeEqual(candidate, expected);
9503
+ const ok = crypto18.timingSafeEqual(candidate, expected);
9334
9504
  return { ok, needsRehash: ok };
9335
9505
  }
9336
9506
  return { ok: false, needsRehash: false };
@@ -9389,7 +9559,7 @@ function applyLegacyCredentials(rows, config) {
9389
9559
  }
9390
9560
  if (migratedGoogle > 0) {
9391
9561
  saveConfigPatch({ google: config.google });
9392
- log17.info("credentials.migrated", { type: "google", count: migratedGoogle });
9562
+ log18.info("credentials.migrated", { type: "google", count: migratedGoogle });
9393
9563
  }
9394
9564
  let migratedGa4 = 0;
9395
9565
  for (const row of rows.ga4) {
@@ -9407,9 +9577,16 @@ function applyLegacyCredentials(rows, config) {
9407
9577
  }
9408
9578
  if (migratedGa4 > 0) {
9409
9579
  saveConfigPatch({ ga4: config.ga4 });
9410
- log17.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
9580
+ log18.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
9411
9581
  }
9412
9582
  }
9583
+ function isLoopbackBindHost(host) {
9584
+ if (host == null || host === "") return true;
9585
+ const normalized = host.trim().toLowerCase().replace(/^\[|\]$/g, "");
9586
+ if (normalized === "localhost" || normalized === "::1") return true;
9587
+ if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(normalized)) return true;
9588
+ return false;
9589
+ }
9413
9590
  async function createServer(opts) {
9414
9591
  const logger = opts.logger === false ? false : process.stdout.isTTY ? {
9415
9592
  transport: {
@@ -9439,11 +9616,11 @@ async function createServer(opts) {
9439
9616
  applyLegacyCredentials(legacyRows, opts.config);
9440
9617
  dropLegacyCredentialColumns(opts.db);
9441
9618
  } catch (err) {
9442
- log17.warn("credentials.migration.failed", {
9619
+ log18.warn("credentials.migration.failed", {
9443
9620
  error: err instanceof Error ? err.message : String(err)
9444
9621
  });
9445
9622
  }
9446
- log17.info("providers.configured", { providers: Object.keys(providers).filter((k) => {
9623
+ log18.info("providers.configured", { providers: Object.keys(providers).filter((k) => {
9447
9624
  const p = providers[k];
9448
9625
  return p?.apiKey || p?.baseUrl || p?.vertexProject;
9449
9626
  }) });
@@ -9492,7 +9669,7 @@ async function createServer(opts) {
9492
9669
  intelligenceService,
9493
9670
  (runId, projectId, result) => notifier.dispatchInsightWebhooks(runId, projectId, result),
9494
9671
  async (ctx) => {
9495
- const project = opts.db.select({ name: projects.name }).from(projects).where(eq17(projects.id, ctx.projectId)).get();
9672
+ const project = opts.db.select({ name: projects.name }).from(projects).where(eq18(projects.id, ctx.projectId)).get();
9496
9673
  if (!project) return;
9497
9674
  let content;
9498
9675
  if (ctx.kind === RunKinds["aeo-discover-probe"]) {
@@ -9535,6 +9712,11 @@ async function createServer(opts) {
9535
9712
  app.log.error({ runId, err }, "GBP sync failed");
9536
9713
  });
9537
9714
  };
9715
+ const runSiteAudit = (runId, projectId, auditOpts) => {
9716
+ executeSiteAudit(opts.db, runId, projectId, auditOpts ?? {}).then(() => runCoordinator.onRunCompleted(runId, projectId)).catch((err) => {
9717
+ app.log.error({ runId, err }, "Site audit failed");
9718
+ });
9719
+ };
9538
9720
  const scheduler = new Scheduler(opts.db, {
9539
9721
  onRunCreated: (runId, projectId, providers2, location) => {
9540
9722
  jobRunner.executeRun(runId, projectId, providers2, location).catch((err) => {
@@ -9560,8 +9742,8 @@ async function createServer(opts) {
9560
9742
  });
9561
9743
  if (!probed) return;
9562
9744
  const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(and13(
9563
- eq17(ccReleaseSyncs.release, probed.release),
9564
- eq17(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)
9745
+ eq18(ccReleaseSyncs.release, probed.release),
9746
+ eq18(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)
9565
9747
  )).limit(1).get();
9566
9748
  if (alreadySynced) {
9567
9749
  app.log.info({ projectName, release: probed.release }, "Scheduled backlinks sync: already up to date, skipping");
@@ -9574,6 +9756,9 @@ async function createServer(opts) {
9574
9756
  );
9575
9757
  });
9576
9758
  })();
9759
+ },
9760
+ onSiteAuditRequested: (runId, projectId) => {
9761
+ runSiteAudit(runId, projectId);
9577
9762
  }
9578
9763
  });
9579
9764
  const providerSummary = API_ADAPTERS.map((adapter) => ({
@@ -9691,7 +9876,7 @@ async function createServer(opts) {
9691
9876
  return removed;
9692
9877
  }
9693
9878
  };
9694
- const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto17.randomBytes(32).toString("hex");
9879
+ const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto18.randomBytes(32).toString("hex");
9695
9880
  const googleConnectionStore = {
9696
9881
  listConnections: (domain) => listGoogleConnections(opts.config, domain),
9697
9882
  getConnection: (domain, connectionType) => getGoogleConnection(opts.config, domain, connectionType),
@@ -9737,11 +9922,11 @@ async function createServer(opts) {
9737
9922
  const apiPrefix = basePath ? `${basePath}api/v1` : "/api/v1";
9738
9923
  if (opts.config.apiKey) {
9739
9924
  const keyHash = hashApiKey(opts.config.apiKey);
9740
- const existing = opts.db.select().from(apiKeys).where(eq17(apiKeys.keyHash, keyHash)).get();
9925
+ const existing = opts.db.select().from(apiKeys).where(eq18(apiKeys.keyHash, keyHash)).get();
9741
9926
  if (!existing) {
9742
9927
  const prefix = opts.config.apiKey.slice(0, 12);
9743
9928
  opts.db.insert(apiKeys).values({
9744
- id: `key_${crypto17.randomBytes(8).toString("hex")}`,
9929
+ id: `key_${crypto18.randomBytes(8).toString("hex")}`,
9745
9930
  name: "default",
9746
9931
  keyHash,
9747
9932
  keyPrefix: prefix,
@@ -9765,7 +9950,7 @@ async function createServer(opts) {
9765
9950
  };
9766
9951
  const createSession = (apiKeyId) => {
9767
9952
  pruneExpiredSessions();
9768
- const sessionId = crypto17.randomBytes(32).toString("hex");
9953
+ const sessionId = crypto18.randomBytes(32).toString("hex");
9769
9954
  sessions.set(sessionId, {
9770
9955
  apiKeyId,
9771
9956
  expiresAt: Date.now() + SESSION_TTL_MS
@@ -9789,7 +9974,7 @@ async function createServer(opts) {
9789
9974
  };
9790
9975
  const getDefaultApiKey = () => {
9791
9976
  if (!opts.config.apiKey) return void 0;
9792
- return opts.db.select().from(apiKeys).where(eq17(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
9977
+ return opts.db.select().from(apiKeys).where(eq18(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
9793
9978
  };
9794
9979
  const createPasswordSession = (reply) => {
9795
9980
  const key = getDefaultApiKey();
@@ -9804,6 +9989,15 @@ async function createServer(opts) {
9804
9989
  }));
9805
9990
  return true;
9806
9991
  };
9992
+ const boundToLoopback = isLoopbackBindHost(opts.host);
9993
+ const requestHasValidApiKey = (request) => {
9994
+ const header = request.headers.authorization;
9995
+ if (!header) return false;
9996
+ const parts = header.split(" ");
9997
+ if (parts.length !== 2 || parts[0] !== "Bearer") return false;
9998
+ const key = opts.db.select().from(apiKeys).where(eq18(apiKeys.keyHash, hashApiKey(parts[1]))).get();
9999
+ return Boolean(key && !key.revokedAt);
10000
+ };
9807
10001
  app.get(apiPrefix + "/session", async (request, reply) => {
9808
10002
  const sessionId = parseCookies(request.headers.cookie)[SESSION_COOKIE_NAME];
9809
10003
  return reply.send({
@@ -9812,6 +10006,10 @@ async function createServer(opts) {
9812
10006
  });
9813
10007
  });
9814
10008
  app.post(apiPrefix + "/session/setup", async (request, reply) => {
10009
+ if (!boundToLoopback && !requestHasValidApiKey(request)) {
10010
+ const err = authRequired("This server is network-reachable; setting the dashboard password requires a valid API key.");
10011
+ return reply.status(err.statusCode).send(err.toJSON());
10012
+ }
9815
10013
  if (opts.config.dashboardPasswordHash) {
9816
10014
  const err = validationError("Dashboard password is already configured");
9817
10015
  return reply.status(err.statusCode).send(err.toJSON());
@@ -9851,12 +10049,12 @@ async function createServer(opts) {
9851
10049
  return reply.send({ authenticated: true });
9852
10050
  }
9853
10051
  if (apiKey) {
9854
- const key = opts.db.select().from(apiKeys).where(eq17(apiKeys.keyHash, hashApiKey(apiKey))).get();
10052
+ const key = opts.db.select().from(apiKeys).where(eq18(apiKeys.keyHash, hashApiKey(apiKey))).get();
9855
10053
  if (!key || key.revokedAt) {
9856
10054
  const err2 = authInvalid();
9857
10055
  return reply.status(err2.statusCode).send(err2.toJSON());
9858
10056
  }
9859
- opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq17(apiKeys.id, key.id)).run();
10057
+ opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq18(apiKeys.id, key.id)).run();
9860
10058
  const sessionId = createSession(key.id);
9861
10059
  reply.header("set-cookie", serializeSessionCookie({
9862
10060
  name: SESSION_COOKIE_NAME,
@@ -10010,7 +10208,7 @@ async function createServer(opts) {
10010
10208
  deps: {
10011
10209
  enqueueAutoExtract: ({ projectId, release: r }) => {
10012
10210
  const now = (/* @__PURE__ */ new Date()).toISOString();
10013
- const runId = crypto17.randomUUID();
10211
+ const runId = crypto18.randomUUID();
10014
10212
  opts.db.insert(runs).values({
10015
10213
  id: runId,
10016
10214
  projectId,
@@ -10048,6 +10246,9 @@ async function createServer(opts) {
10048
10246
  app.log.error({ runId: input.runId, err }, "Discovery run failed");
10049
10247
  });
10050
10248
  },
10249
+ onSiteAuditRequested: (runId, projectId, auditOpts) => {
10250
+ runSiteAudit(runId, projectId, auditOpts);
10251
+ },
10051
10252
  onBacklinksPruneCache: (release) => {
10052
10253
  try {
10053
10254
  pruneCachedRelease(release);
@@ -10093,7 +10294,7 @@ async function createServer(opts) {
10093
10294
  ...inspectOpts,
10094
10295
  config: opts.config
10095
10296
  }).then(() => {
10096
- const finished = opts.db.select({ status: runs.status }).from(runs).where(eq17(runs.id, runId)).get();
10297
+ const finished = opts.db.select({ status: runs.status }).from(runs).where(eq18(runs.id, runId)).get();
10097
10298
  if (finished?.status === RunStatuses.completed || finished?.status === RunStatuses.partial) {
10098
10299
  return maybeRefreshGscCoverage(opts.db, opts.config, projectId);
10099
10300
  }
@@ -10181,7 +10382,7 @@ async function createServer(opts) {
10181
10382
  const targetProjectIds = affectedProjectIds.length > 0 ? affectedProjectIds : [null];
10182
10383
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
10183
10384
  opts.db.insert(auditLog).values(targetProjectIds.map((projectId) => ({
10184
- id: crypto17.randomUUID(),
10385
+ id: crypto18.randomUUID(),
10185
10386
  projectId,
10186
10387
  actor: "api",
10187
10388
  action: existing ? "provider.updated" : "provider.created",