@ainyc/canonry 4.27.1 → 4.27.2

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.
@@ -2612,7 +2612,7 @@ function buildCategoryCounts(counts) {
2612
2612
  }
2613
2613
 
2614
2614
  // ../api-routes/src/intelligence.ts
2615
- import { eq as eq11, desc as desc4, and as and3 } from "drizzle-orm";
2615
+ import { eq as eq11, desc as desc4, and as and3, inArray as inArray3 } from "drizzle-orm";
2616
2616
  function emptyHealthSnapshot(projectId) {
2617
2617
  return {
2618
2618
  id: `no-data:${projectId}`,
@@ -2656,6 +2656,44 @@ function mapHealthRow(r) {
2656
2656
  status: "ready"
2657
2657
  };
2658
2658
  }
2659
+ function aggregateHealthSnapshots(projectId, rows) {
2660
+ if (rows.length === 1) return mapHealthRow(rows[0]);
2661
+ let totalPairs = 0;
2662
+ let citedPairs = 0;
2663
+ const mergedProviders = {};
2664
+ let newestCreatedAt = "";
2665
+ const runIds = [];
2666
+ for (const row of rows) {
2667
+ totalPairs += row.totalPairs;
2668
+ citedPairs += row.citedPairs;
2669
+ if (row.createdAt > newestCreatedAt) newestCreatedAt = row.createdAt;
2670
+ if (row.runId) runIds.push(row.runId);
2671
+ const providerBreakdown = parseJsonColumn(row.providerBreakdown, {});
2672
+ for (const [provider, entry] of Object.entries(providerBreakdown)) {
2673
+ const existing = mergedProviders[provider] ?? { total: 0, cited: 0, citedRate: 0 };
2674
+ existing.total += entry.total;
2675
+ existing.cited += entry.cited;
2676
+ mergedProviders[provider] = existing;
2677
+ }
2678
+ }
2679
+ for (const entry of Object.values(mergedProviders)) {
2680
+ entry.citedRate = entry.total > 0 ? entry.cited / entry.total : 0;
2681
+ }
2682
+ const overallCitedRate = totalPairs > 0 ? citedPairs / totalPairs : 0;
2683
+ return {
2684
+ // Synthetic id so consumers can tell this is an aggregate; concatenate
2685
+ // source runIds for traceability without inventing a new schema column.
2686
+ id: `group:${runIds.join(",")}`,
2687
+ projectId,
2688
+ runId: runIds[0] ?? null,
2689
+ overallCitedRate,
2690
+ totalPairs,
2691
+ citedPairs,
2692
+ providerBreakdown: mergedProviders,
2693
+ createdAt: newestCreatedAt,
2694
+ status: "ready"
2695
+ };
2696
+ }
2659
2697
  async function intelligenceRoutes(app) {
2660
2698
  app.get("/projects/:name/insights", async (request, reply) => {
2661
2699
  const project = resolveProject(app.db, request.params.name);
@@ -2687,11 +2725,27 @@ async function intelligenceRoutes(app) {
2687
2725
  });
2688
2726
  app.get("/projects/:name/health/latest", async (request, reply) => {
2689
2727
  const project = resolveProject(app.db, request.params.name);
2690
- const row = app.db.select().from(healthSnapshots).where(eq11(healthSnapshots.projectId, project.id)).orderBy(desc4(healthSnapshots.createdAt)).limit(1).get();
2691
- if (!row) {
2728
+ const projectVisRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(and3(
2729
+ eq11(runs.projectId, project.id),
2730
+ eq11(runs.kind, RunKinds["answer-visibility"]),
2731
+ inArray3(runs.status, [RunStatuses.completed, RunStatuses.partial])
2732
+ )).orderBy(desc4(runs.createdAt), desc4(runs.id)).all();
2733
+ const latestGroup = groupRunsByCreatedAt(projectVisRuns)[0] ?? [];
2734
+ const latestGroupRunIds = latestGroup.map((r) => r.id);
2735
+ if (latestGroupRunIds.length > 0) {
2736
+ const groupRows = app.db.select().from(healthSnapshots).where(and3(
2737
+ eq11(healthSnapshots.projectId, project.id),
2738
+ inArray3(healthSnapshots.runId, latestGroupRunIds)
2739
+ )).all();
2740
+ if (groupRows.length > 0) {
2741
+ return reply.send(aggregateHealthSnapshots(project.id, groupRows));
2742
+ }
2743
+ }
2744
+ const fallback = app.db.select().from(healthSnapshots).where(eq11(healthSnapshots.projectId, project.id)).orderBy(desc4(healthSnapshots.createdAt)).limit(1).get();
2745
+ if (!fallback) {
2692
2746
  return reply.send(emptyHealthSnapshot(project.id));
2693
2747
  }
2694
- return reply.send(mapHealthRow(row));
2748
+ return reply.send(mapHealthRow(fallback));
2695
2749
  });
2696
2750
  app.get("/projects/:name/health/history", async (request, reply) => {
2697
2751
  const project = resolveProject(app.db, request.params.name);
@@ -2703,7 +2757,7 @@ async function intelligenceRoutes(app) {
2703
2757
  }
2704
2758
 
2705
2759
  // ../api-routes/src/report.ts
2706
- import { and as and5, desc as desc6, eq as eq13, gte, inArray as inArray4, lt, lte, ne, or as or2, sql as sql3 } from "drizzle-orm";
2760
+ import { and as and5, desc as desc6, eq as eq13, gte, inArray as inArray5, lt, lte, ne, or as or2, sql as sql3 } from "drizzle-orm";
2707
2761
 
2708
2762
  // ../api-routes/src/report-renderer.ts
2709
2763
  var COLORS = {
@@ -4939,7 +4993,7 @@ function renderReportHtml(report, opts = {}) {
4939
4993
  }
4940
4994
 
4941
4995
  // ../api-routes/src/content-data.ts
4942
- import { and as and4, eq as eq12, desc as desc5, inArray as inArray3 } from "drizzle-orm";
4996
+ import { and as and4, eq as eq12, desc as desc5, inArray as inArray4 } from "drizzle-orm";
4943
4997
  var RECENT_RUNS_WINDOW = 5;
4944
4998
  function loadOrchestratorInput(db, project, locationFilter = void 0) {
4945
4999
  const projectId = project.id;
@@ -5069,7 +5123,7 @@ function listRecentAnswerVisibilityRunIds(db, projectId, limit, locationFilter)
5069
5123
  // Queued/running/failed/cancelled runs may have partial or no
5070
5124
  // snapshots; including them risks pointing latestRunId at a run with
5071
5125
  // no usable evidence.
5072
- inArray3(runs.status, [RunStatuses.completed, RunStatuses.partial])
5126
+ inArray4(runs.status, [RunStatuses.completed, RunStatuses.partial])
5073
5127
  )
5074
5128
  ).orderBy(desc5(runs.createdAt)).all();
5075
5129
  const filtered = locationFilter === void 0 ? rows : rows.filter((r) => (r.location ?? null) === locationFilter);
@@ -5111,7 +5165,7 @@ function buildCandidateQueries(opts) {
5111
5165
  const queryRows = opts.db.select({ id: queries.id, text: queries.query }).from(queries).where(eq12(queries.projectId, opts.projectId)).all();
5112
5166
  const queryIdByText = new Map(queryRows.map((r) => [r.text, r.id]));
5113
5167
  const candidateQueryIds = opts.candidateQueryStrings.map((q) => queryIdByText.get(q)).filter((id) => Boolean(id));
5114
- const snapshotRows = opts.db.select().from(querySnapshots).where(inArray3(querySnapshots.runId, opts.recentRunIds)).all().filter((r) => candidateQueryIds.includes(r.queryId));
5168
+ const snapshotRows = opts.db.select().from(querySnapshots).where(inArray4(querySnapshots.runId, opts.recentRunIds)).all().filter((r) => candidateQueryIds.includes(r.queryId));
5115
5169
  const snapshotsByQuery = /* @__PURE__ */ new Map();
5116
5170
  for (const row of snapshotRows) {
5117
5171
  const list = snapshotsByQuery.get(row.queryId) ?? [];
@@ -5329,7 +5383,7 @@ function loadSnapshotsForRun(db, runId) {
5329
5383
  }
5330
5384
  function loadSnapshotsForRunIds(db, runIds) {
5331
5385
  if (runIds.length === 0) return [];
5332
- const rows = db.select().from(querySnapshots).where(inArray4(querySnapshots.runId, [...runIds])).all();
5386
+ const rows = db.select().from(querySnapshots).where(inArray5(querySnapshots.runId, [...runIds])).all();
5333
5387
  return rows.map((r) => ({
5334
5388
  id: r.id,
5335
5389
  runId: r.runId,
@@ -5909,7 +5963,7 @@ function buildInsightList(db, projectId, locationFilter) {
5909
5963
  )
5910
5964
  ).orderBy(desc6(runs.createdAt)).all().filter((r) => locationFilter === void 0 || (r.location ?? null) === locationFilter).slice(0, INSIGHT_LOOKBACK_RUNS).map((r) => r.id);
5911
5965
  if (recentRunIds.length === 0) return [];
5912
- const rows = db.select().from(insights).where(and5(eq13(insights.projectId, projectId), inArray4(insights.runId, recentRunIds))).orderBy(desc6(insights.createdAt)).all();
5966
+ const rows = db.select().from(insights).where(and5(eq13(insights.projectId, projectId), inArray5(insights.runId, recentRunIds))).orderBy(desc6(insights.createdAt)).all();
5913
5967
  const severityRank = { critical: 0, high: 1, medium: 2, low: 3 };
5914
5968
  const flat = rows.filter((r) => !r.dismissed).map((r) => {
5915
5969
  const recommendation = parseJsonColumn(r.recommendation, null);
@@ -6671,7 +6725,7 @@ async function reportRoutes(app) {
6671
6725
  }
6672
6726
 
6673
6727
  // ../api-routes/src/citations.ts
6674
- import { eq as eq14, inArray as inArray5 } from "drizzle-orm";
6728
+ import { eq as eq14, inArray as inArray6 } from "drizzle-orm";
6675
6729
  async function citationRoutes(app) {
6676
6730
  app.get("/projects/:name/citations/visibility", async (request, reply) => {
6677
6731
  const project = resolveProject(app.db, request.params.name);
@@ -6695,7 +6749,7 @@ async function citationRoutes(app) {
6695
6749
  competitorOverlap: querySnapshots.competitorOverlap,
6696
6750
  answerMentioned: querySnapshots.answerMentioned,
6697
6751
  createdAt: querySnapshots.createdAt
6698
- }).from(querySnapshots).where(inArray5(querySnapshots.runId, projectRuns.map((r) => r.id))).all();
6752
+ }).from(querySnapshots).where(inArray6(querySnapshots.runId, projectRuns.map((r) => r.id))).all();
6699
6753
  if (rawSnapshots.length === 0) {
6700
6754
  return reply.send(emptyCitationVisibility("no-runs-yet"));
6701
6755
  }
@@ -6835,7 +6889,7 @@ function normalizeDomain2(domain) {
6835
6889
  }
6836
6890
 
6837
6891
  // ../api-routes/src/composites.ts
6838
- import { eq as eq15, and as and6, desc as desc7, sql as sql4, like, or as or3, inArray as inArray6 } from "drizzle-orm";
6892
+ import { eq as eq15, and as and6, desc as desc7, sql as sql4, like, or as or3, inArray as inArray7 } from "drizzle-orm";
6839
6893
  var TOP_INSIGHT_LIMIT = 5;
6840
6894
  var SEARCH_HIT_HARD_LIMIT = 50;
6841
6895
  var SEARCH_SNIPPET_RADIUS = 80;
@@ -7040,7 +7094,7 @@ function loadSnapshotsByRunIds(app, runIds) {
7040
7094
  citationState: querySnapshots.citationState,
7041
7095
  competitorOverlap: querySnapshots.competitorOverlap,
7042
7096
  citedDomains: querySnapshots.citedDomains
7043
- }).from(querySnapshots).where(inArray6(querySnapshots.runId, [...runIds])).all();
7097
+ }).from(querySnapshots).where(inArray7(querySnapshots.runId, [...runIds])).all();
7044
7098
  for (const row of rows) {
7045
7099
  const list = result.get(row.runId) ?? [];
7046
7100
  list.push({
@@ -22614,7 +22668,7 @@ import crypto24 from "crypto";
22614
22668
  import fs7 from "fs";
22615
22669
  import path9 from "path";
22616
22670
  import os5 from "os";
22617
- import { and as and16, eq as eq27, inArray as inArray7, sql as sql10 } from "drizzle-orm";
22671
+ import { and as and16, eq as eq27, inArray as inArray8, sql as sql10 } from "drizzle-orm";
22618
22672
 
22619
22673
  // src/run-telemetry.ts
22620
22674
  import crypto23 from "crypto";
@@ -22955,7 +23009,7 @@ var JobRunner = class {
22955
23009
  this.registry = registry;
22956
23010
  }
22957
23011
  recoverStaleRuns() {
22958
- const stale = this.db.select({ id: runs.id, status: runs.status }).from(runs).where(inArray7(runs.status, ["running", "queued"])).all();
23012
+ const stale = this.db.select({ id: runs.id, status: runs.status }).from(runs).where(inArray8(runs.status, ["running", "queued"])).all();
22959
23013
  if (stale.length === 0) return;
22960
23014
  const now = (/* @__PURE__ */ new Date()).toISOString();
22961
23015
  for (const run of stale) {
@@ -23018,7 +23072,7 @@ var JobRunner = class {
23018
23072
  }
23019
23073
  log.info("run.dispatch", { runId, providerCount: activeProviders.length, providers: activeProviders.map((p) => p.adapter.name) });
23020
23074
  const scopedQueryNames = parseJsonColumn(existingRun.queries, null);
23021
- projectQueries = scopedQueryNames ? this.db.select().from(queries).where(and16(eq27(queries.projectId, projectId), inArray7(queries.query, scopedQueryNames))).all() : this.db.select().from(queries).where(eq27(queries.projectId, projectId)).all();
23075
+ projectQueries = scopedQueryNames ? this.db.select().from(queries).where(and16(eq27(queries.projectId, projectId), inArray8(queries.query, scopedQueryNames))).all() : this.db.select().from(queries).where(eq27(queries.projectId, projectId)).all();
23022
23076
  const projectCompetitors = this.db.select().from(competitors).where(eq27(competitors.projectId, projectId)).all();
23023
23077
  const competitorDomains = projectCompetitors.map((c) => c.domain);
23024
23078
  const allDomains = effectiveDomains({
@@ -24672,7 +24726,7 @@ var Scheduler = class {
24672
24726
  };
24673
24727
 
24674
24728
  // src/notifier.ts
24675
- import { eq as eq35, desc as desc16, and as and22, inArray as inArray8, or as or4 } from "drizzle-orm";
24729
+ import { eq as eq35, desc as desc16, and as and22, inArray as inArray9, or as or4 } from "drizzle-orm";
24676
24730
  import crypto31 from "crypto";
24677
24731
  var log10 = createLogger("Notifier");
24678
24732
  var Notifier = class {
@@ -24825,13 +24879,13 @@ var Notifier = class {
24825
24879
  provider: querySnapshots.provider,
24826
24880
  location: querySnapshots.location,
24827
24881
  citationState: querySnapshots.citationState
24828
- }).from(querySnapshots).leftJoin(queries, eq35(querySnapshots.queryId, queries.id)).where(inArray8(querySnapshots.runId, currentRunIds)).all();
24882
+ }).from(querySnapshots).leftJoin(queries, eq35(querySnapshots.queryId, queries.id)).where(inArray9(querySnapshots.runId, currentRunIds)).all();
24829
24883
  const previousSnapshots = this.db.select({
24830
24884
  queryId: querySnapshots.queryId,
24831
24885
  provider: querySnapshots.provider,
24832
24886
  location: querySnapshots.location,
24833
24887
  citationState: querySnapshots.citationState
24834
- }).from(querySnapshots).where(inArray8(querySnapshots.runId, previousRunIds)).all();
24888
+ }).from(querySnapshots).where(inArray9(querySnapshots.runId, previousRunIds)).all();
24835
24889
  const prevMap = /* @__PURE__ */ new Map();
24836
24890
  for (const s of previousSnapshots) {
24837
24891
  prevMap.set(`${s.queryId}:${s.provider}:${s.location ?? ""}`, s.citationState);
package/dist/cli.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  setTelemetrySource,
21
21
  showFirstRunNotice,
22
22
  trackEvent
23
- } from "./chunk-QNXGCQEM.js";
23
+ } from "./chunk-ICWFH4JA.js";
24
24
  import {
25
25
  CliError,
26
26
  EXIT_SYSTEM_ERROR,
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-QNXGCQEM.js";
3
+ } from "./chunk-ICWFH4JA.js";
4
4
  import {
5
5
  loadConfig
6
6
  } from "./chunk-2FAEQ56I.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "4.27.1",
3
+ "version": "4.27.2",
4
4
  "type": "module",
5
5
  "description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
6
6
  "license": "FSL-1.1-ALv2",
@@ -60,22 +60,22 @@
60
60
  "tsup": "^8.5.1",
61
61
  "tsx": "^4.19.0",
62
62
  "@ainyc/canonry-api-routes": "0.0.0",
63
- "@ainyc/canonry-config": "0.0.0",
64
63
  "@ainyc/canonry-contracts": "0.0.0",
65
- "@ainyc/canonry-db": "0.0.0",
66
- "@ainyc/canonry-integration-bing": "0.0.0",
67
- "@ainyc/canonry-integration-cloud-run": "0.0.0",
68
- "@ainyc/canonry-integration-google": "0.0.0",
64
+ "@ainyc/canonry-config": "0.0.0",
69
65
  "@ainyc/canonry-intelligence": "0.0.0",
70
66
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
67
+ "@ainyc/canonry-integration-cloud-run": "0.0.0",
68
+ "@ainyc/canonry-db": "0.0.0",
69
+ "@ainyc/canonry-integration-bing": "0.0.0",
71
70
  "@ainyc/canonry-integration-traffic": "0.0.0",
72
- "@ainyc/canonry-provider-cdp": "0.0.0",
71
+ "@ainyc/canonry-integration-google": "0.0.0",
73
72
  "@ainyc/canonry-integration-wordpress": "0.0.0",
73
+ "@ainyc/canonry-provider-cdp": "0.0.0",
74
74
  "@ainyc/canonry-provider-claude": "0.0.0",
75
+ "@ainyc/canonry-provider-gemini": "0.0.0",
75
76
  "@ainyc/canonry-provider-local": "0.0.0",
76
- "@ainyc/canonry-provider-openai": "0.0.0",
77
77
  "@ainyc/canonry-provider-perplexity": "0.0.0",
78
- "@ainyc/canonry-provider-gemini": "0.0.0"
78
+ "@ainyc/canonry-provider-openai": "0.0.0"
79
79
  },
80
80
  "scripts": {
81
81
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",