@ainyc/canonry 1.24.1 → 1.25.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.
package/assets/index.html CHANGED
@@ -12,8 +12,8 @@
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-BUzyFRxr.js"></script>
16
- <link rel="stylesheet" crossorigin href="./assets/index-BidvmvWJ.css">
15
+ <script type="module" crossorigin src="./assets/index-cbbEFy_S.js"></script>
16
+ <link rel="stylesheet" crossorigin href="./assets/index-D1_RT3XF.css">
17
17
  </head>
18
18
  <body>
19
19
  <div id="root"></div>
@@ -3016,7 +3016,8 @@ async function analyticsRoutes(app) {
3016
3016
  buckets: [],
3017
3017
  overall: { citationRate: 0, cited: 0, total: 0 },
3018
3018
  byProvider: {},
3019
- trend: "stable"
3019
+ trend: "stable",
3020
+ keywordChanges: []
3020
3021
  });
3021
3022
  }
3022
3023
  const runIds = projectRuns.map((r) => r.id);
@@ -3027,6 +3028,8 @@ async function analyticsRoutes(app) {
3027
3028
  citationState: querySnapshots.citationState,
3028
3029
  createdAt: querySnapshots.createdAt
3029
3030
  }).from(querySnapshots).where(inArray2(querySnapshots.runId, runIds)).all();
3031
+ const projectKeywords = app.db.select({ id: keywords.id, createdAt: keywords.createdAt }).from(keywords).where(eq10(keywords.projectId, project.id)).all();
3032
+ const keywordCreatedAt = new Map(projectKeywords.map((k) => [k.id, k.createdAt]));
3030
3033
  const overall = computeProviderMetric(allSnapshots);
3031
3034
  const byProvider = {};
3032
3035
  const providers = new Set(allSnapshots.map((s) => s.provider));
@@ -3037,9 +3040,10 @@ async function analyticsRoutes(app) {
3037
3040
  const latest = new Date(projectRuns[projectRuns.length - 1].createdAt);
3038
3041
  const spanDays = Math.max(1, Math.ceil((latest.getTime() - earliest.getTime()) / 864e5));
3039
3042
  const bucketSize = bucketSizeForSpan(spanDays);
3040
- const buckets = computeBuckets(allSnapshots, projectRuns, bucketSize);
3043
+ const buckets = computeBuckets(allSnapshots, projectRuns, bucketSize, keywordCreatedAt);
3041
3044
  const trend = computeTrend(buckets);
3042
- return reply.send({ window, buckets, overall, byProvider, trend });
3045
+ const keywordChanges = computeKeywordChanges(projectKeywords, cutoff);
3046
+ return reply.send({ window, buckets, overall, byProvider, trend, keywordChanges });
3043
3047
  });
3044
3048
  app.get("/projects/:name/analytics/gaps", async (request, reply) => {
3045
3049
  const project = resolveProjectSafe5(app, request.params.name, reply);
@@ -3231,7 +3235,7 @@ function computeProviderMetric(snapshots) {
3231
3235
  total
3232
3236
  };
3233
3237
  }
3234
- function computeBuckets(snapshots, projectRuns, bucketDays) {
3238
+ function computeBuckets(snapshots, projectRuns, bucketDays, keywordCreatedAt) {
3235
3239
  if (projectRuns.length === 0) return [];
3236
3240
  const earliest = new Date(projectRuns[0].createdAt);
3237
3241
  const latest = new Date(projectRuns[projectRuns.length - 1].createdAt);
@@ -3245,19 +3249,44 @@ function computeBuckets(snapshots, projectRuns, bucketDays) {
3245
3249
  const endISO = end.toISOString();
3246
3250
  const inBucket = snapshots.filter((s) => s.createdAt >= startISO && s.createdAt < endISO);
3247
3251
  if (inBucket.length > 0) {
3248
- const metric = computeProviderMetric(inBucket);
3252
+ let usable = inBucket;
3253
+ if (keywordCreatedAt) {
3254
+ const eligible = inBucket.filter((s) => {
3255
+ const kwCreated = keywordCreatedAt.get(s.keywordId);
3256
+ return kwCreated !== void 0 && kwCreated < startISO;
3257
+ });
3258
+ if (eligible.length > 0) usable = eligible;
3259
+ }
3260
+ const metric = computeProviderMetric(usable);
3261
+ const keywordCount = new Set(usable.map((s) => s.keywordId)).size;
3249
3262
  buckets.push({
3250
3263
  startDate: startISO,
3251
3264
  endDate: endISO,
3252
3265
  citationRate: metric.citationRate,
3253
3266
  cited: metric.cited,
3254
- total: metric.total
3267
+ total: metric.total,
3268
+ keywordCount
3255
3269
  });
3256
3270
  }
3257
3271
  start = end;
3258
3272
  }
3259
3273
  return buckets;
3260
3274
  }
3275
+ function computeKeywordChanges(projectKeywords, cutoff) {
3276
+ const byDay = /* @__PURE__ */ new Map();
3277
+ for (const kw of projectKeywords) {
3278
+ if (cutoff && kw.createdAt < cutoff) continue;
3279
+ const day = kw.createdAt.slice(0, 10);
3280
+ byDay.set(day, (byDay.get(day) ?? 0) + 1);
3281
+ }
3282
+ const days = [...byDay.entries()].sort((a, b) => a[0].localeCompare(b[0]));
3283
+ if (days.length <= 1) return [];
3284
+ return days.slice(1).map(([date, count]) => ({
3285
+ date: (/* @__PURE__ */ new Date(date + "T00:00:00.000Z")).toISOString(),
3286
+ delta: count,
3287
+ label: `+${count} kp`
3288
+ }));
3289
+ }
3261
3290
  function computeTrend(buckets) {
3262
3291
  const nonEmpty = buckets.filter((b) => b.total > 0);
3263
3292
  if (nonEmpty.length < 2) return "stable";
package/dist/cli.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  setGoogleAuthConfig,
20
20
  showFirstRunNotice,
21
21
  trackEvent
22
- } from "./chunk-QMUO2JYU.js";
22
+ } from "./chunk-JEZMB3YG.js";
23
23
 
24
24
  // src/cli.ts
25
25
  import { pathToFileURL } from "url";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createServer,
3
3
  loadConfig
4
- } from "./chunk-QMUO2JYU.js";
4
+ } from "./chunk-JEZMB3YG.js";
5
5
  export {
6
6
  createServer,
7
7
  loadConfig
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "1.24.1",
3
+ "version": "1.25.1",
4
4
  "type": "module",
5
5
  "description": "The ultimate open-source AEO monitoring tool - track how answer engines cite your domain",
6
6
  "license": "FSL-1.1-ALv2",
@@ -55,15 +55,15 @@
55
55
  "@ainyc/canonry-api-routes": "0.0.0",
56
56
  "@ainyc/canonry-config": "0.0.0",
57
57
  "@ainyc/canonry-contracts": "0.0.0",
58
- "@ainyc/canonry-provider-claude": "0.0.0",
59
58
  "@ainyc/canonry-db": "0.0.0",
60
- "@ainyc/canonry-provider-cdp": "0.0.0",
59
+ "@ainyc/canonry-provider-claude": "0.0.0",
60
+ "@ainyc/canonry-provider-gemini": "0.0.0",
61
61
  "@ainyc/canonry-provider-local": "0.0.0",
62
- "@ainyc/canonry-integration-bing": "0.0.0",
63
62
  "@ainyc/canonry-integration-google": "0.0.0",
63
+ "@ainyc/canonry-provider-cdp": "0.0.0",
64
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
64
65
  "@ainyc/canonry-provider-openai": "0.0.0",
65
- "@ainyc/canonry-provider-gemini": "0.0.0",
66
- "@ainyc/canonry-provider-perplexity": "0.0.0"
66
+ "@ainyc/canonry-integration-bing": "0.0.0"
67
67
  },
68
68
  "scripts": {
69
69
  "build": "tsup && tsx build-web.ts",