@ainyc/canonry 4.187.0 → 4.188.1

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 (29) hide show
  1. package/README.md +5 -1
  2. package/assets/assets/{AuditHistoryPanel-G2iVWlbn.js → AuditHistoryPanel-BCpFULQp.js} +1 -1
  3. package/assets/assets/{BacklinksPage-B13UlDT7.js → BacklinksPage-CFHiZFhZ.js} +1 -1
  4. package/assets/assets/{HistoryPage-DZoRKfzJ.js → HistoryPage-THnDCEnv.js} +1 -1
  5. package/assets/assets/{MeasurementPropertyPage-DNxsAgEA.js → MeasurementPropertyPage-DWWwA2kL.js} +1 -1
  6. package/assets/assets/ProjectPage-CP57_Lqf.js +9 -0
  7. package/assets/assets/{RunRow-C9u35nrm.js → RunRow-CDWArf9X.js} +1 -1
  8. package/assets/assets/{RunsPage-DST-BpAu.js → RunsPage-Ctsw14Vg.js} +1 -1
  9. package/assets/assets/{SettingsPage-BC05JomS.js → SettingsPage-IHYGgD0e.js} +1 -1
  10. package/assets/assets/{SiteHealthSection-DIeYA2HJ.js → SiteHealthSection-DjFHZqBF.js} +3 -3
  11. package/assets/assets/{TrafficPage-DezdhoxL.js → TrafficPage-Cb1Zwgc5.js} +1 -1
  12. package/assets/assets/{TrafficSourceDetailPage-C0FIPjXi.js → TrafficSourceDetailPage-CRdjdiAa.js} +1 -1
  13. package/assets/assets/{extract-error-message-B5OYNTJO.js → extract-error-message-CNs6e1oz.js} +1 -1
  14. package/assets/assets/{index-DJWMbOc5.js → index-B64f9mVI.js} +29 -29
  15. package/assets/assets/index-DTblya6d.css +1 -0
  16. package/assets/assets/{react-sigma_core.esm.min-B06R8tYw.js → react-sigma_core.esm.min-CzJ-HG1j.js} +1 -1
  17. package/assets/index.html +2 -2
  18. package/dist/{chunk-B7WQ5Z4L.js → chunk-6YJQGOR6.js} +6 -5
  19. package/dist/{chunk-IPALTNIE.js → chunk-A47HAZDJ.js} +2 -2
  20. package/dist/{chunk-EMSDPSQM.js → chunk-GPTEXYYL.js} +116 -62
  21. package/dist/{chunk-LNBX7RTN.js → chunk-HIIOVQSH.js} +30 -1
  22. package/dist/{chunk-5BUAG4CN.js → chunk-QPYMAXLJ.js} +37 -1
  23. package/dist/cli.js +32 -5
  24. package/dist/index.js +4 -4
  25. package/dist/{intelligence-service-UZGA24EF.js → intelligence-service-4KMWHEYP.js} +2 -2
  26. package/dist/mcp.js +3 -3
  27. package/package.json +13 -13
  28. package/assets/assets/ProjectPage-BdfsfLkU.js +0 -9
  29. package/assets/assets/index-DJ8_LvRn.css +0 -1
package/assets/index.html CHANGED
@@ -12,14 +12,14 @@
12
12
  <link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
13
13
  <link rel="apple-touch-icon" href="./apple-touch-icon.png" />
14
14
  <title>Canonry</title>
15
- <script type="module" crossorigin src="./assets/index-DJWMbOc5.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-B64f9mVI.js"></script>
16
16
  <link rel="modulepreload" crossorigin href="./assets/vendor-tanstack-CYBjbqVr.js">
17
17
  <link rel="modulepreload" crossorigin href="./assets/vendor-radix-BpmvLvpp.js">
18
18
  <link rel="modulepreload" crossorigin href="./assets/vendor-recharts-FA0uySU7.js">
19
19
  <link rel="modulepreload" crossorigin href="./assets/vendor-lucide-BovNTc41.js">
20
20
  <link rel="modulepreload" crossorigin href="./assets/vendor-markdown-9tTeP8v7.js">
21
21
  <link rel="modulepreload" crossorigin href="./assets/vendor-yaml-GWJz5H2G.js">
22
- <link rel="stylesheet" crossorigin href="./assets/index-DJ8_LvRn.css">
22
+ <link rel="stylesheet" crossorigin href="./assets/index-DTblya6d.css">
23
23
  </head>
24
24
  <body>
25
25
  <div id="root"></div>
@@ -13,7 +13,7 @@ import {
13
13
  loadConfig,
14
14
  loadConfigRaw,
15
15
  saveConfigPatch
16
- } from "./chunk-5BUAG4CN.js";
16
+ } from "./chunk-QPYMAXLJ.js";
17
17
  import {
18
18
  CC_CACHE_DIR,
19
19
  DUCKDB_SPEC,
@@ -160,7 +160,7 @@ import {
160
160
  siteCrawlSnapshots,
161
161
  toAlertView,
162
162
  usageCounters
163
- } from "./chunk-EMSDPSQM.js";
163
+ } from "./chunk-GPTEXYYL.js";
164
164
  import {
165
165
  AGENT_MEMORY_VALUE_MAX_BYTES,
166
166
  AGENT_PROVIDER_IDS,
@@ -302,7 +302,7 @@ import {
302
302
  validationError,
303
303
  winnabilityClassLabel,
304
304
  withRetry
305
- } from "./chunk-LNBX7RTN.js";
305
+ } from "./chunk-HIIOVQSH.js";
306
306
 
307
307
  // src/telemetry.ts
308
308
  import crypto from "crypto";
@@ -11924,7 +11924,7 @@ function readStoredGroundingSources(rawResponse) {
11924
11924
  return result;
11925
11925
  }
11926
11926
  async function backfillInsightsCommand(project, opts) {
11927
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-UZGA24EF.js");
11927
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-4KMWHEYP.js");
11928
11928
  const config = loadConfig();
11929
11929
  const db = createClient(config.database);
11930
11930
  migrate(db);
@@ -13996,7 +13996,8 @@ function mcpToAgentTool(tool, ctx) {
13996
13996
  };
13997
13997
  }
13998
13998
  var AERO_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
13999
- CanonryMcpToolNames.canonry_agent_clear
13999
+ CanonryMcpToolNames.canonry_agent_clear,
14000
+ CanonryMcpToolNames.canonry_results_clear
14000
14001
  ]);
14001
14002
  function buildMcpAgentTools(registry, ctx, opts = {}) {
14002
14003
  return registry.filter((tool) => !AERO_EXCLUDED_MCP_TOOLS.has(tool.name)).filter((tool) => opts.includeNames ? opts.includeNames.has(tool.name) : true).filter((tool) => opts.readOnly ? tool.access === "read" : true).map((tool) => mcpToAgentTool(tool, ctx));
@@ -5,11 +5,11 @@ import {
5
5
  installSkills,
6
6
  loadConfigRaw,
7
7
  saveConfigPatch
8
- } from "./chunk-5BUAG4CN.js";
8
+ } from "./chunk-QPYMAXLJ.js";
9
9
  import {
10
10
  SKILL_MANIFEST_FILENAME,
11
11
  SkillsClients
12
- } from "./chunk-LNBX7RTN.js";
12
+ } from "./chunk-HIIOVQSH.js";
13
13
 
14
14
  // src/skills-autosync.ts
15
15
  import fs from "fs";
@@ -564,6 +564,8 @@ import {
564
564
  resolveMeasurementRunQueryScope,
565
565
  resolveMeasurementRunScope,
566
566
  resolveSnapshotRequestQueries,
567
+ resultsClearRequestSchema,
568
+ resultsClearResponseSchema,
567
569
  resultsExportDtoSchema,
568
570
  retryAfterDelayMs,
569
571
  runDetailDtoSchema,
@@ -668,7 +670,7 @@ import {
668
670
  wordpressSchemaDeployResultDtoSchema,
669
671
  wordpressSchemaStatusResultDtoSchema,
670
672
  wordpressStatusDtoSchema
671
- } from "./chunk-LNBX7RTN.js";
673
+ } from "./chunk-HIIOVQSH.js";
672
674
 
673
675
  // src/intelligence-service.ts
674
676
  import { eq as eq67, desc as desc29, asc as asc12, and as and56, ne as ne10, or as or14, inArray as inArray25, gte as gte18, lte as lte15, isNull as isNull12, sql as sql26, exists } from "drizzle-orm";
@@ -9179,15 +9181,15 @@ function buildAiSourceOrigin(snapshots, projectDomains, competitorDomains, topDo
9179
9181
  totalCitations++;
9180
9182
  }
9181
9183
  }
9182
- const categories = [...categoryCounts.entries()].map(([category, { label, count: count3 }]) => ({
9184
+ const categories = [...categoryCounts.entries()].map(([category, { label, count: count4 }]) => ({
9183
9185
  category,
9184
9186
  label,
9185
- count: count3,
9186
- sharePct: totalCitations > 0 ? Math.round(count3 / totalCitations * 100) : 0
9187
+ count: count4,
9188
+ sharePct: totalCitations > 0 ? Math.round(count4 / totalCitations * 100) : 0
9187
9189
  })).sort((a, b) => b.count - a.count);
9188
- const topDomains = [...domainCounts.entries()].map(([domain, count3]) => ({
9190
+ const topDomains = [...domainCounts.entries()].map(([domain, count4]) => ({
9189
9191
  domain,
9190
- count: count3,
9192
+ count: count4,
9191
9193
  isCompetitor: citedDomainBelongsToProject(domain, competitorDomains)
9192
9194
  })).sort((a, b) => b.count - a.count).slice(0, topDomainsLimit);
9193
9195
  return { categories, topDomains };
@@ -12019,7 +12021,7 @@ async function projectRoutes(app, opts) {
12019
12021
  app.get("/projects/:name/delete-preview", async (request, reply) => {
12020
12022
  const project = resolveProject(app.db, request.params.name);
12021
12023
  const pid = project.id;
12022
- const count3 = (n) => n ?? 0;
12024
+ const count4 = (n) => n ?? 0;
12023
12025
  const queryCount = app.db.select({ n: sql4`count(*)` }).from(queries).where(eq5(queries.projectId, pid)).get();
12024
12026
  const competitorCount = app.db.select({ n: sql4`count(*)` }).from(competitors).where(eq5(competitors.projectId, pid)).get();
12025
12027
  const runCount = app.db.select({ n: sql4`count(*)` }).from(runs).where(eq5(runs.projectId, pid)).get();
@@ -12029,14 +12031,14 @@ async function projectRoutes(app, opts) {
12029
12031
  return reply.send({
12030
12032
  project: { id: project.id, name: project.name },
12031
12033
  cascadeRows: {
12032
- queries: count3(queryCount?.n),
12033
- competitors: count3(competitorCount?.n),
12034
- runs: count3(runCount?.n),
12035
- snapshots: count3(snapshotCount?.n),
12036
- insights: count3(insightCount?.n)
12034
+ queries: count4(queryCount?.n),
12035
+ competitors: count4(competitorCount?.n),
12036
+ runs: count4(runCount?.n),
12037
+ snapshots: count4(snapshotCount?.n),
12038
+ insights: count4(insightCount?.n)
12037
12039
  },
12038
12040
  detachedRows: {
12039
- auditLog: count3(auditLogCount?.n)
12041
+ auditLog: count4(auditLogCount?.n)
12040
12042
  }
12041
12043
  });
12042
12044
  });
@@ -12722,14 +12724,14 @@ async function queryRoutes(app, opts) {
12722
12724
  validProviders: validNames
12723
12725
  });
12724
12726
  }
12725
- const count3 = body.count ?? 5;
12727
+ const count4 = body.count ?? 5;
12726
12728
  if (!opts.onGenerateQueries) {
12727
12729
  throw notImplemented("Query generation is not supported in this deployment");
12728
12730
  }
12729
12731
  const existingRows = app.db.select().from(queries).where(eq8(queries.projectId, project.id)).all();
12730
12732
  const existingQueries = existingRows.map((r) => r.query);
12731
12733
  try {
12732
- const generated = await opts.onGenerateQueries(provider, count3, {
12734
+ const generated = await opts.onGenerateQueries(provider, count4, {
12733
12735
  domain: project.canonicalDomain,
12734
12736
  displayName: project.displayName,
12735
12737
  country: project.country,
@@ -12855,14 +12857,14 @@ async function queryRoutes(app, opts) {
12855
12857
  validProviders: validNames
12856
12858
  });
12857
12859
  }
12858
- const count3 = body.count ?? 5;
12860
+ const count4 = body.count ?? 5;
12859
12861
  if (!opts.onGenerateQueries) {
12860
12862
  throw notImplemented("Keyword generation is not supported in this deployment");
12861
12863
  }
12862
12864
  const existingRows = app.db.select().from(queries).where(eq8(queries.projectId, project.id)).all();
12863
12865
  const existingQueries = existingRows.map((r) => r.query);
12864
12866
  try {
12865
- const generated = await opts.onGenerateQueries(provider, count3, {
12867
+ const generated = await opts.onGenerateQueries(provider, count4, {
12866
12868
  domain: project.canonicalDomain,
12867
12869
  displayName: project.displayName,
12868
12870
  country: project.country,
@@ -18706,19 +18708,19 @@ function groupChangesFromRows(rows, identities) {
18706
18708
  });
18707
18709
  }
18708
18710
  function countsFromRows(rows, groupChanges) {
18709
- const count3 = (status) => rows.filter((row) => row.status === status).length;
18710
- const ambiguousRows = count3("ambiguous");
18711
- const unmatchedRows = count3("unmatched");
18712
- const invalidRows = count3("invalid");
18713
- const proposedRows = count3("proposed");
18714
- const excludedRows = count3("excluded");
18711
+ const count4 = (status) => rows.filter((row) => row.status === status).length;
18712
+ const ambiguousRows = count4("ambiguous");
18713
+ const unmatchedRows = count4("unmatched");
18714
+ const invalidRows = count4("invalid");
18715
+ const proposedRows = count4("proposed");
18716
+ const excludedRows = count4("excluded");
18715
18717
  return {
18716
18718
  dataRows: rows.length,
18717
- matchedRows: count3("matched"),
18719
+ matchedRows: count4("matched"),
18718
18720
  ambiguousRows,
18719
18721
  unmatchedRows,
18720
18722
  invalidRows,
18721
- duplicateRows: count3("duplicate"),
18723
+ duplicateRows: count4("duplicate"),
18722
18724
  proposedRows,
18723
18725
  excludedRows,
18724
18726
  needsAttention: ambiguousRows + unmatchedRows + invalidRows + proposedRows + excludedRows,
@@ -24080,9 +24082,9 @@ function workloadDiff(before, after) {
24080
24082
  nextSweepNodes: after.size,
24081
24083
  nextSweepProviderCalls: sum(after.values()),
24082
24084
  addedNodes: added.length,
24083
- addedProviderCalls: sum(added.map(([, count3]) => count3)),
24085
+ addedProviderCalls: sum(added.map(([, count4]) => count4)),
24084
24086
  removedNodes: removed.length,
24085
- removedProviderCalls: sum(removed.map(([, count3]) => count3))
24087
+ removedProviderCalls: sum(removed.map(([, count4]) => count4))
24086
24088
  };
24087
24089
  }
24088
24090
  function candidateDiff(state, nextRows, plan, reusedQueryIds, mutatedQueryIds, scopedRemovalQueryIds) {
@@ -25166,7 +25168,7 @@ function normalizeCompetitorList2(domains) {
25166
25168
  }
25167
25169
 
25168
25170
  // ../api-routes/src/history.ts
25169
- import { and as and21, asc as asc3, desc as desc9, eq as eq27, gte as gte5, inArray as inArray11 } from "drizzle-orm";
25171
+ import { and as and21, asc as asc3, count, desc as desc9, eq as eq27, gte as gte5, inArray as inArray11 } from "drizzle-orm";
25170
25172
 
25171
25173
  // ../api-routes/src/notification-redaction.ts
25172
25174
  var REDACTED_URL = {
@@ -25210,6 +25212,38 @@ function redactNotificationDiff(value) {
25210
25212
 
25211
25213
  // ../api-routes/src/history.ts
25212
25214
  async function historyRoutes(app) {
25215
+ app.post("/projects/:name/results/clear", async (request) => {
25216
+ requireAdminSession(request);
25217
+ requireScope(request, "runs.write");
25218
+ const project = resolveProject(app.db, request.params.name);
25219
+ const parsed = resultsClearRequestSchema.safeParse(request.body);
25220
+ if (!parsed.success) throw validationError("Invalid history cleanup request", { issues: parsed.error.issues });
25221
+ const input = parsed.data;
25222
+ return app.db.transaction((tx) => {
25223
+ const active = tx.select({ id: runs.id }).from(runs).where(and21(eq27(runs.projectId, project.id), eq27(runs.kind, RunKinds["answer-visibility"]), inArray11(runs.status, ["queued", "running"]))).get();
25224
+ const activeResearch = tx.select({ id: researchRuns.id }).from(researchRuns).where(and21(eq27(researchRuns.projectId, project.id), inArray11(researchRuns.status, ["queued", "running"]))).get();
25225
+ if (active || activeResearch) throw runInProgress(project.name, active ? "answer-visibility" : "research", (active ?? activeResearch).id);
25226
+ const selectedRuns = tx.select({ id: runs.id, kind: runs.kind }).from(runs).where(and21(eq27(runs.projectId, project.id), inArray11(runs.id, input.runIds))).all();
25227
+ const selectedResearch = tx.select({ id: researchRuns.id }).from(researchRuns).where(and21(eq27(researchRuns.projectId, project.id), inArray11(researchRuns.id, input.researchRunIds))).all();
25228
+ if (selectedRuns.length !== input.runIds.length || selectedResearch.length !== input.researchRunIds.length) throw notFound("Selected saved run in project", project.name);
25229
+ if (selectedRuns.some((run) => run.kind !== RunKinds["answer-visibility"])) throw validationError("Only answer-visibility runs and research batches can be cleared. Site Health and integration runs are preserved.");
25230
+ const result = {
25231
+ dryRun: !input.confirm,
25232
+ runIds: input.runIds,
25233
+ researchRunIds: input.researchRunIds,
25234
+ querySnapshots: tx.select({ value: count() }).from(querySnapshots).where(inArray11(querySnapshots.runId, input.runIds)).get().value,
25235
+ researchQueries: tx.select({ value: count() }).from(researchRunQueries).where(inArray11(researchRunQueries.researchRunId, input.researchRunIds)).get().value,
25236
+ insights: tx.select({ value: count() }).from(insights).where(inArray11(insights.runId, input.runIds)).get().value,
25237
+ healthSnapshots: tx.select({ value: count() }).from(healthSnapshots).where(inArray11(healthSnapshots.runId, input.runIds)).get().value
25238
+ };
25239
+ if (input.confirm) {
25240
+ tx.delete(researchRuns).where(and21(eq27(researchRuns.projectId, project.id), inArray11(researchRuns.id, input.researchRunIds))).run();
25241
+ tx.delete(runs).where(and21(eq27(runs.projectId, project.id), inArray11(runs.id, input.runIds))).run();
25242
+ writeAuditLog(tx, { projectId: project.id, actor: request.principal ? `${request.principal.kind}:${request.principal.id}` : "api", action: "results.cleared", entityType: "project", entityId: project.id, diff: result, userAgent: request.headers["user-agent"] });
25243
+ }
25244
+ return result;
25245
+ });
25246
+ });
25213
25247
  app.get("/projects/:name/history", async (request, reply) => {
25214
25248
  const project = resolveProject(app.db, request.params.name);
25215
25249
  const filters = [eq27(auditLog.projectId, project.id)];
@@ -26225,10 +26259,10 @@ function computeQueryChanges(projectQueries, cutoff) {
26225
26259
  }
26226
26260
  const days = [...byDay.entries()].sort((a, b) => a[0].localeCompare(b[0]));
26227
26261
  if (days.length <= 1) return [];
26228
- return days.slice(1).map(([date, count3]) => ({
26262
+ return days.slice(1).map(([date, count4]) => ({
26229
26263
  date: (/* @__PURE__ */ new Date(date + "T00:00:00.000Z")).toISOString(),
26230
- delta: count3,
26231
- label: `+${count3} kp`
26264
+ delta: count4,
26265
+ label: `+${count4} kp`
26232
26266
  }));
26233
26267
  }
26234
26268
  function pooledRate(buckets, rateKey) {
@@ -26303,15 +26337,15 @@ function buildRankedList(domains, limit) {
26303
26337
  function buildCategoryCounts(counts2) {
26304
26338
  let grandTotal = 0;
26305
26339
  for (const domains of counts2.values()) {
26306
- for (const count3 of domains.values()) grandTotal += count3;
26340
+ for (const count4 of domains.values()) grandTotal += count4;
26307
26341
  }
26308
26342
  const result = [];
26309
26343
  for (const [category, domains] of counts2) {
26310
26344
  let categoryTotal = 0;
26311
26345
  const domainEntries = [];
26312
- for (const [domain, count3] of domains) {
26313
- categoryTotal += count3;
26314
- domainEntries.push({ domain, count: count3 });
26346
+ for (const [domain, count4] of domains) {
26347
+ categoryTotal += count4;
26348
+ domainEntries.push({ domain, count: count4 });
26315
26349
  }
26316
26350
  domainEntries.sort((a, b) => b.count - a.count);
26317
26351
  result.push({
@@ -27587,8 +27621,8 @@ function gscDateRange(report) {
27587
27621
  const end = summary2?.periodEnd || gsc?.periodEnd || gsc?.trend.at(-1)?.date || "";
27588
27622
  return formatDateRange(start, end);
27589
27623
  }
27590
- function pluralize(count3, singular, plural = `${singular}s`) {
27591
- return count3 === 1 ? singular : plural;
27624
+ function pluralize(count4, singular, plural = `${singular}s`) {
27625
+ return count4 === 1 ? singular : plural;
27592
27626
  }
27593
27627
  var PROVIDER_DISPLAY_NAMES = {
27594
27628
  gemini: "Gemini",
@@ -29422,9 +29456,9 @@ function renderInsights(report) {
29422
29456
  );
29423
29457
  }
29424
29458
  const haveDeduped = list.every((i) => typeof i.instanceCount === "number");
29425
- const rows = (haveDeduped ? list.map((i) => ({ rep: i, count: i.instanceCount })) : groupInsights(list).map((g) => ({ rep: g.representative, count: g.count }))).map(({ rep: i, count: count3 }) => {
29459
+ const rows = (haveDeduped ? list.map((i) => ({ rep: i, count: i.instanceCount })) : groupInsights(list).map((g) => ({ rep: g.representative, count: g.count }))).map(({ rep: i, count: count4 }) => {
29426
29460
  const tone = severityTone(i.severity);
29427
- const countChip = count3 > 1 ? ` <span class="badge tone-neutral">\xD7 ${count3}</span>` : "";
29461
+ const countChip = count4 > 1 ? ` <span class="badge tone-neutral">\xD7 ${count4}</span>` : "";
29428
29462
  return `<tr>
29429
29463
  <td class="col-severity"><span class="badge tone-${tone}">${escapeHtml2(reportSeverityLabel(i.severity))}</span></td>
29430
29464
  <td class="col-title">${escapeHtml2(i.title)}${countChip}</td>
@@ -30881,9 +30915,9 @@ function contentActionVerb(action) {
30881
30915
  return "Add schema to";
30882
30916
  }
30883
30917
  }
30884
- function confidenceFromEvidence(count3) {
30885
- if (count3 >= 3) return "high";
30886
- if (count3 >= 1) return "medium";
30918
+ function confidenceFromEvidence(count4) {
30919
+ if (count4 >= 3) return "high";
30920
+ if (count4 >= 1) return "medium";
30887
30921
  return "low";
30888
30922
  }
30889
30923
  function actionAudienceMatches2(action, audience) {
@@ -32057,9 +32091,9 @@ function summarizeVerificationHits(rows) {
32057
32091
  return { counts: counts2, unsupportedRows, unsupportedHits };
32058
32092
  }
32059
32093
  function summarizeServer(crawlers, fetches, referrals) {
32060
- const total = referrals.reduce((count3, row) => count3 + row.sessionsOrHits, 0);
32061
- const paid = referrals.reduce((count3, row) => count3 + row.paidSessionsOrHits, 0);
32062
- const organic = referrals.reduce((count3, row) => count3 + row.organicSessionsOrHits, 0);
32094
+ const total = referrals.reduce((count4, row) => count4 + row.sessionsOrHits, 0);
32095
+ const paid = referrals.reduce((count4, row) => count4 + row.paidSessionsOrHits, 0);
32096
+ const organic = referrals.reduce((count4, row) => count4 + row.organicSessionsOrHits, 0);
32063
32097
  const crawlerHits = summarizeVerificationHits(crawlers);
32064
32098
  const userFetchHits = summarizeVerificationHits(fetches);
32065
32099
  return {
@@ -32208,10 +32242,10 @@ function buildOrganicEvidence(db, projectName, periodDays) {
32208
32242
  const gaWindow = gaStartDate && gaEndDate ? gaRows.filter((row) => inRange(row.date, gaStartDate, gaEndDate)) : [];
32209
32243
  const legacyGaCohorts = gaCohorts.map((cohort) => ({
32210
32244
  ...cohort,
32211
- organicSessions: gaRows.filter((row) => inRange(row.date, cohort.startDate, cohort.endDate)).reduce((count3, row) => count3 + row.organicSessions, 0)
32245
+ organicSessions: gaRows.filter((row) => inRange(row.date, cohort.startDate, cohort.endDate)).reduce((count4, row) => count4 + row.organicSessions, 0)
32212
32246
  }));
32213
32247
  let ga4 = gaRows.length ? {
32214
- organicSessions: gaWindow.reduce((count3, row) => count3 + row.organicSessions, 0),
32248
+ organicSessions: gaWindow.reduce((count4, row) => count4 + row.organicSessions, 0),
32215
32249
  cohorts: legacyGaCohorts
32216
32250
  } : null;
32217
32251
  if (nativeAcquisitionActive && nativeAcquisition.periods.length === 0) {
@@ -32234,8 +32268,8 @@ function buildOrganicEvidence(db, projectName, periodDays) {
32234
32268
  const allGaAiRows = db.select().from(gaAiReferrals).where(and30(eq35(gaAiReferrals.projectId, project.id), gte10(gaAiReferrals.date, gaStartDate ?? startDate), lte8(gaAiReferrals.date, gaEndDate ?? endDate))).all().filter((row) => row.sourceDimension === "session");
32235
32269
  const aiRows = gaStartDate && gaEndDate ? allGaAiRows.filter((row) => inRange(row.date, gaStartDate, gaEndDate)) : [];
32236
32270
  const gaAiReferralSummary = allGaAiRows.length ? {
32237
- paidSessions: aiRows.filter((row) => row.trafficClass === AiReferralTrafficClasses.paid).reduce((count3, row) => count3 + row.sessions, 0),
32238
- organicSessions: aiRows.filter((row) => row.trafficClass === AiReferralTrafficClasses.organic).reduce((count3, row) => count3 + row.sessions, 0)
32271
+ paidSessions: aiRows.filter((row) => row.trafficClass === AiReferralTrafficClasses.paid).reduce((count4, row) => count4 + row.sessions, 0),
32272
+ organicSessions: aiRows.filter((row) => row.trafficClass === AiReferralTrafficClasses.organic).reduce((count4, row) => count4 + row.sessions, 0)
32239
32273
  } : null;
32240
32274
  const serverSources = db.select().from(trafficSources).where(eq35(trafficSources.projectId, project.id)).all();
32241
32275
  const serverStart = `${startDate}T00:00:00`;
@@ -34279,6 +34313,8 @@ var SCHEMA_TABLE = {
34279
34313
  ResearchRunCreate: researchRunCreateSchema,
34280
34314
  ResearchRunDetailDto: researchRunDetailSchema,
34281
34315
  ResearchRunListDto: researchRunListSchema,
34316
+ ResultsClearRequest: resultsClearRequestSchema,
34317
+ ResultsClearResponse: resultsClearResponseSchema,
34282
34318
  DoctorReportDto: doctorReportSchema,
34283
34319
  GA4AiReferralDailyDto: ga4AiReferralDailyDtoSchema,
34284
34320
  GA4AiReferralHistoryEntry: ga4AiReferralHistoryEntrySchema,
@@ -35995,6 +36031,16 @@ var routeCatalog = [
35995
36031
  requestBody: { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/QueryTrackingCommitRequest" } } } },
35996
36032
  responses: { 200: jsonResponse("Actual committed revision, or unchanged no-op.", "QueryTrackingCommitResponse"), 400: errorResponse("Invalid mutation or review token."), 403: errorResponse("Write access required."), 404: errorResponse("Project or source not found."), 409: errorResponse("Workspace changed or a sweep is using the live query catalog.") }
35997
36033
  },
36034
+ {
36035
+ method: "post",
36036
+ path: "/api/v1/projects/{name}/results/clear",
36037
+ summary: "Preview or clear selected saved visibility and research results",
36038
+ tags: ["runs"],
36039
+ description: "Administrator or runs.write key only. Exact run IDs are required; confirm defaults to false for a non-mutating preview. Refuses active visibility or research work and any selected non-visibility run. Keeps queries, measurement plans, Site Health, backlinks, schedules, audit history, and usage accounting. Back up stored evidence before confirming deletion.",
36040
+ parameters: [nameParameter],
36041
+ requestBody: { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/ResultsClearRequest" } } } },
36042
+ responses: { 200: jsonResponse("Selected history previewed or cleared.", "ResultsClearResponse"), 400: errorResponse("Invalid selection or unsupported run kind."), 403: errorResponse("Administrator or runs.write scope required."), 404: errorResponse("Project or selected run not found."), 409: errorResponse("Visibility or research work is active.") }
36043
+ },
35998
36044
  {
35999
36045
  method: "get",
36000
36046
  path: "/api/v1/projects/{name}/research/runs",
@@ -44426,12 +44472,12 @@ async function getLodging(accessToken, locationName, opts = {}) {
44426
44472
  }
44427
44473
  }
44428
44474
  function countPopulatedGroups(lodging) {
44429
- let count3 = 0;
44475
+ let count4 = 0;
44430
44476
  for (const [key, value] of Object.entries(lodging)) {
44431
44477
  if (key === "name" || key === "metadata") continue;
44432
- if (isPopulated(value)) count3++;
44478
+ if (isPopulated(value)) count4++;
44433
44479
  }
44434
- return count3;
44480
+ return count4;
44435
44481
  }
44436
44482
  function isPopulated(value) {
44437
44483
  if (value === null || value === void 0) return false;
@@ -49475,7 +49521,7 @@ function createActivationStore(app, request) {
49475
49521
  function countManifest(manifest) {
49476
49522
  return {
49477
49523
  adGroupCount: manifest.campaign.adGroups.length,
49478
- adCount: manifest.campaign.adGroups.reduce((count3, group) => count3 + group.ads.length, 0)
49524
+ adCount: manifest.campaign.adGroups.reduce((count4, group) => count4 + group.ads.length, 0)
49479
49525
  };
49480
49526
  }
49481
49527
  function resolveAdsActivationMaxEntities(env = process.env) {
@@ -80975,7 +81021,7 @@ function dedupeStrings(input) {
80975
81021
 
80976
81022
  // ../api-routes/src/technical-aeo.ts
80977
81023
  import crypto48 from "crypto";
80978
- import { and as and53, asc as asc11, count, desc as desc27, eq as eq64, inArray as inArray24, isNotNull as isNotNull5, isNull as isNull11, lt as lt10, or as or13, sql as sql24 } from "drizzle-orm";
81024
+ import { and as and53, asc as asc11, count as count2, desc as desc27, eq as eq64, inArray as inArray24, isNotNull as isNotNull5, isNull as isNull11, lt as lt10, or as or13, sql as sql24 } from "drizzle-orm";
80979
81025
  import { alias } from "drizzle-orm/sqlite-core";
80980
81026
  var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
80981
81027
  ...SiteCrawlFetchedStates,
@@ -81416,7 +81462,7 @@ async function technicalAeoRoutes(app, opts) {
81416
81462
  const conds = [eq64(siteAuditPages.projectId, project.id), eq64(siteAuditPages.runId, latest.runId)];
81417
81463
  if (statusFilter) conds.push(eq64(siteAuditPages.status, statusFilter));
81418
81464
  const where = and53(...conds);
81419
- const totalRow = app.db.select({ value: count() }).from(siteAuditPages).where(where).get();
81465
+ const totalRow = app.db.select({ value: count2() }).from(siteAuditPages).where(where).get();
81420
81466
  const total = totalRow?.value ?? 0;
81421
81467
  const limit = parsePositiveInt(request.query.limit, 100, 500);
81422
81468
  const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
@@ -82403,7 +82449,7 @@ async function technicalAeoRoutes(app, opts) {
82403
82449
  };
82404
82450
  }
82405
82451
  const where = and53(...filters);
82406
- const total = app.db.select({ value: count() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
82452
+ const total = app.db.select({ value: count2() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
82407
82453
  const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
82408
82454
  const nextOffset = offset + rows.length;
82409
82455
  return {
@@ -82526,7 +82572,7 @@ async function technicalAeoRoutes(app, opts) {
82526
82572
  const followable = parseBoolean2(request.query.followable);
82527
82573
  if (followable != null) filters.push(eq64(siteCrawlEdges.followable, followable));
82528
82574
  const where = and53(...filters);
82529
- const total = app.db.select({ value: count() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
82575
+ const total = app.db.select({ value: count2() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
82530
82576
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
82531
82577
  const offset = decodeCursor(request.query.cursor);
82532
82578
  const rows = app.db.select().from(siteCrawlEdges).where(where).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(limit).offset(offset).all();
@@ -82642,7 +82688,7 @@ async function technicalAeoRoutes(app, opts) {
82642
82688
  // promise a page the list cannot fill.
82643
82689
  isNotNull5(sql24`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
82644
82690
  );
82645
- const total = app.db.select({ value: count() }).from(siteCrawlFindings).where(where).get()?.value ?? 0;
82691
+ const total = app.db.select({ value: count2() }).from(siteCrawlFindings).where(where).get()?.value ?? 0;
82646
82692
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
82647
82693
  const offset = decodeCursor(request.query.cursor);
82648
82694
  const rows = app.db.select().from(siteCrawlFindings).where(where).orderBy(asc11(siteCrawlFindings.findingKey)).limit(limit).offset(offset).all();
@@ -82780,7 +82826,7 @@ async function technicalAeoRoutes(app, opts) {
82780
82826
  eq64(siteCrawlPages.attemptId, attempt.id),
82781
82827
  eq64(siteCrawlPages.auditState, "success")
82782
82828
  );
82783
- const pagesAudited = tx.select({ value: count() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
82829
+ const pagesAudited = tx.select({ value: count2() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
82784
82830
  if (state !== "collecting") {
82785
82831
  return { ...base, pagesAudited, examples: [] };
82786
82832
  }
@@ -82884,7 +82930,7 @@ async function technicalAeoRoutes(app, opts) {
82884
82930
 
82885
82931
  // ../api-routes/src/research.ts
82886
82932
  import crypto49 from "crypto";
82887
- import { and as and54, count as count2, desc as desc28, eq as eq65, gte as gte17, lt as lt11, sql as sql25 } from "drizzle-orm";
82933
+ import { and as and54, count as count3, desc as desc28, eq as eq65, gte as gte17, lt as lt11, sql as sql25 } from "drizzle-orm";
82888
82934
  var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
82889
82935
  async function researchRoutes(app, opts) {
82890
82936
  const viewerDailyRunLimit = opts.viewerDailyRunLimit ?? DEFAULT_VIEWER_RESEARCH_DAILY_RUN_LIMIT;
@@ -82930,7 +82976,7 @@ async function researchRoutes(app, opts) {
82930
82976
  }
82931
82977
  if (initiatedBy?.kind === "user" && initiatedBy.role === UserRoles.viewer) {
82932
82978
  const { start, end, date } = utcDayBounds(now);
82933
- const used = tx.select({ value: count2() }).from(researchRuns).where(and54(
82979
+ const used = tx.select({ value: count3() }).from(researchRuns).where(and54(
82934
82980
  eq65(researchRuns.projectId, project.id),
82935
82981
  gte17(researchRuns.createdAt, start),
82936
82982
  lt11(researchRuns.createdAt, end),
@@ -82956,7 +83002,15 @@ async function researchRoutes(app, opts) {
82956
83002
  const requested = Number.parseInt(request.query.limit ?? "", 10);
82957
83003
  const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
82958
83004
  const runs2 = app.db.select().from(researchRuns).where(eq65(researchRuns.projectId, project.id)).orderBy(desc28(researchRuns.createdAt)).limit(limit).all().map(serializeRun);
82959
- return { runs: runs2 };
83005
+ const configured = new Set(opts.configuredProviderNames ?? []);
83006
+ const providers = (opts.providerAdapters ?? []).filter((adapter) => adapter.mode === "api" && !isBrowserProvider(adapter.name) && configured.has(adapter.name)).map((adapter) => ({
83007
+ name: adapter.name,
83008
+ displayName: adapter.displayName,
83009
+ modelConfigurable: adapter.modelConfigurable,
83010
+ defaultModel: project.providerModels[adapter.name] || adapter.defaultModel,
83011
+ knownModels: adapter.knownModels.map((model) => ({ id: model.id, displayName: model.displayName }))
83012
+ }));
83013
+ return { runs: runs2, providers };
82960
83014
  });
82961
83015
  app.get("/projects/:name/research/runs/:runId", async (request) => {
82962
83016
  const project = resolveProject(app.db, request.params.name);
@@ -13158,6 +13158,23 @@ var resultsExportDtoSchema = z41.object({
13158
13158
  recordCount: z41.number().int().nonnegative(),
13159
13159
  records: z41.array(resultsExportRecordSchema)
13160
13160
  });
13161
+ var resultsClearRequestSchema = z41.object({
13162
+ runIds: z41.array(z41.string().trim().min(1)).max(100).default([]),
13163
+ researchRunIds: z41.array(z41.string().trim().min(1)).max(100).default([]),
13164
+ confirm: z41.boolean().default(false)
13165
+ }).strict().superRefine((value, ctx) => {
13166
+ if (value.runIds.length + value.researchRunIds.length === 0) ctx.addIssue({ code: "custom", message: "Select at least one saved run." });
13167
+ if (new Set(value.runIds).size !== value.runIds.length || new Set(value.researchRunIds).size !== value.researchRunIds.length) ctx.addIssue({ code: "custom", message: "Run IDs must be unique." });
13168
+ });
13169
+ var resultsClearResponseSchema = z41.object({
13170
+ dryRun: z41.boolean(),
13171
+ runIds: z41.array(z41.string()),
13172
+ researchRunIds: z41.array(z41.string()),
13173
+ querySnapshots: z41.number().int().nonnegative(),
13174
+ researchQueries: z41.number().int().nonnegative(),
13175
+ insights: z41.number().int().nonnegative(),
13176
+ healthSnapshots: z41.number().int().nonnegative()
13177
+ });
13161
13178
 
13162
13179
  // ../contracts/src/ga.ts
13163
13180
  import { z as z43 } from "zod";
@@ -16185,7 +16202,17 @@ var researchRunQuerySchema = z56.object({
16185
16202
  createdAt: z56.string()
16186
16203
  });
16187
16204
  var researchRunDetailSchema = researchRunSummarySchema.extend({ queries: z56.array(researchRunQuerySchema) });
16188
- var researchRunListSchema = z56.object({ runs: z56.array(researchRunSummarySchema) });
16205
+ var researchProviderOptionSchema = z56.object({
16206
+ name: z56.string(),
16207
+ displayName: z56.string(),
16208
+ modelConfigurable: z56.boolean(),
16209
+ defaultModel: z56.string(),
16210
+ knownModels: z56.array(z56.object({ id: z56.string(), displayName: z56.string() }))
16211
+ });
16212
+ var researchRunListSchema = z56.object({
16213
+ runs: z56.array(researchRunSummarySchema),
16214
+ providers: z56.array(researchProviderOptionSchema).optional()
16215
+ });
16189
16216
 
16190
16217
  // ../contracts/src/ads.ts
16191
16218
  import { z as z57 } from "zod";
@@ -17938,6 +17965,8 @@ export {
17938
17965
  visibilityCompareDtoSchema,
17939
17966
  calendarMonthBounds,
17940
17967
  resultsExportDtoSchema,
17968
+ resultsClearRequestSchema,
17969
+ resultsClearResponseSchema,
17941
17970
  AiReferralTrafficClasses,
17942
17971
  classifyAiReferralTrafficClass,
17943
17972
  aiReferralClassCounts,
@@ -88,6 +88,7 @@ import {
88
88
  queryTrackingPreviewRequestSchema,
89
89
  reportPeriodSchema,
90
90
  researchRunCreateSchema,
91
+ resultsClearRequestSchema,
91
92
  runTriggerRequestSchema,
92
93
  schedulableRunKindSchema,
93
94
  scheduleUpsertRequestSchema,
@@ -98,7 +99,7 @@ import {
98
99
  trafficEventKindSchema,
99
100
  trafficSeriesGranularitySchema,
100
101
  visibilityReportRequestSchema
101
- } from "./chunk-LNBX7RTN.js";
102
+ } from "./chunk-HIIOVQSH.js";
102
103
 
103
104
  // src/cli-error.ts
104
105
  function isMachineFormat(format) {
@@ -2342,6 +2343,22 @@ var postApiV1ProjectsByNameQueryTrackingCommit = (options) => {
2342
2343
  }
2343
2344
  });
2344
2345
  };
2346
+ var postApiV1ProjectsByNameResultsClear = (options) => {
2347
+ return (options.client ?? client).post({
2348
+ security: [
2349
+ {
2350
+ scheme: "bearer",
2351
+ type: "http"
2352
+ }
2353
+ ],
2354
+ url: "/api/v1/projects/{name}/results/clear",
2355
+ ...options,
2356
+ headers: {
2357
+ "Content-Type": "application/json",
2358
+ ...options.headers
2359
+ }
2360
+ });
2361
+ };
2345
2362
  var getApiV1ProjectsByNameResearchRunsByRunId = (options) => {
2346
2363
  return (options.client ?? client).get({
2347
2364
  security: [
@@ -8028,6 +8045,14 @@ var ApiClient = class {
8028
8045
  );
8029
8046
  }
8030
8047
  // ── Research query runs ────────────────────────────────────────────────
8048
+ /** Preview or clear exact saved visibility/research run IDs. */
8049
+ async clearResults(project, request) {
8050
+ return this.invoke(() => postApiV1ProjectsByNameResultsClear({
8051
+ client: this.heyClient,
8052
+ path: { name: project },
8053
+ body: request
8054
+ }));
8055
+ }
8031
8056
  /**
8032
8057
  * Start one saved research batch. Research results are deliberately kept
8033
8058
  * outside the tracked-query basket and visibility-run history.
@@ -9866,6 +9891,17 @@ var canonryMcpTools = [
9866
9891
  openApiOperations: ["GET /api/v1/projects/{name}/export"],
9867
9892
  handler: (client2, input) => client2.getExport(input.project)
9868
9893
  }),
9894
+ defineTool({
9895
+ name: "canonry_results_clear",
9896
+ title: "Preview or clear saved results",
9897
+ description: "Clear only explicitly selected visibility runs and research batches. Preview is the default; back up evidence and set confirm=true to delete. Refuses active work and other run kinds. Preserves queries, plans, schedules, Site Health, backlinks, usage, and the audit trail.",
9898
+ access: "write",
9899
+ tier: "monitoring",
9900
+ inputSchema: resultsClearRequestSchema.safeExtend({ project: projectNameSchema }),
9901
+ annotations: writeAnnotations({ idempotentHint: false, destructiveHint: true }),
9902
+ openApiOperations: ["POST /api/v1/projects/{name}/results/clear"],
9903
+ handler: (client2, input) => client2.clearResults(input.project, { runIds: input.runIds, researchRunIds: input.researchRunIds, confirm: input.confirm })
9904
+ }),
9869
9905
  defineTool({
9870
9906
  name: "canonry_project_history",
9871
9907
  title: "Get project history",