@ainyc/canonry 1.45.3 → 1.46.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,7 +12,7 @@
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-BR8Krvpj.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-Cxg_4UWs.js"></script>
16
16
  <link rel="stylesheet" crossorigin href="./assets/index--ev1Bjls.css">
17
17
  </head>
18
18
  <body>
@@ -997,6 +997,17 @@ var scheduleUpsertRequestSchema = z10.object({
997
997
  import { z as z11 } from "zod";
998
998
  var visibilityMetricModeSchema = z11.enum(["answer", "citation"]);
999
999
  var VisibilityMetricModes = visibilityMetricModeSchema.enum;
1000
+ function parseWindow(value) {
1001
+ if (value === "7d" || value === "30d" || value === "90d" || value === "all") return value;
1002
+ return "all";
1003
+ }
1004
+ function windowCutoff(window) {
1005
+ if (window === "all") return null;
1006
+ const days = window === "7d" ? 7 : window === "30d" ? 30 : 90;
1007
+ const d = /* @__PURE__ */ new Date();
1008
+ d.setDate(d.getDate() - days);
1009
+ return d.toISOString();
1010
+ }
1000
1011
 
1001
1012
  // ../contracts/src/source-categories.ts
1002
1013
  var SOURCE_CATEGORY_RULES = [
@@ -3143,17 +3154,6 @@ function parseGroundingSources(rawResponse) {
3143
3154
  (s) => typeof s.uri === "string" && !isProviderInfraDomain(s.uri)
3144
3155
  );
3145
3156
  }
3146
- function parseWindow(value) {
3147
- if (value === "7d" || value === "30d" || value === "90d" || value === "all") return value;
3148
- return "all";
3149
- }
3150
- function windowCutoff(window) {
3151
- if (window === "all") return null;
3152
- const days = window === "7d" ? 7 : window === "30d" ? 30 : 90;
3153
- const d = /* @__PURE__ */ new Date();
3154
- d.setDate(d.getDate() - days);
3155
- return d.toISOString();
3156
- }
3157
3157
  function bucketSizeForSpan(spanDays) {
3158
3158
  if (spanDays <= 14) return 1;
3159
3159
  if (spanDays <= 60) return 7;
@@ -4506,7 +4506,8 @@ var routeCatalog = [
4506
4506
  { name: "endDate", in: "query", description: "Filter by end date.", schema: stringSchema },
4507
4507
  { name: "query", in: "query", description: "Filter by search query.", schema: stringSchema },
4508
4508
  { name: "page", in: "query", description: "Filter by page URL.", schema: stringSchema },
4509
- limitQueryParameter
4509
+ limitQueryParameter,
4510
+ analyticsWindowParameter
4510
4511
  ],
4511
4512
  responses: {
4512
4513
  200: { description: "GSC performance rows returned." },
@@ -5332,7 +5333,7 @@ var routeCatalog = [
5332
5333
  path: "/api/v1/projects/{name}/ga/traffic",
5333
5334
  summary: "Get GA4 landing page traffic and AI referral sources",
5334
5335
  tags: ["ga4"],
5335
- parameters: [nameParameter, limitQueryParameter],
5336
+ parameters: [nameParameter, limitQueryParameter, analyticsWindowParameter],
5336
5337
  responses: {
5337
5338
  200: { description: "GA4 traffic data returned." },
5338
5339
  400: { description: "GA4 is not connected." },
@@ -5344,7 +5345,7 @@ var routeCatalog = [
5344
5345
  path: "/api/v1/projects/{name}/ga/ai-referral-history",
5345
5346
  summary: "Get AI referral sessions per day grouped by source",
5346
5347
  tags: ["ga4"],
5347
- parameters: [nameParameter],
5348
+ parameters: [nameParameter, analyticsWindowParameter],
5348
5349
  responses: {
5349
5350
  200: { description: "AI referral history returned." },
5350
5351
  400: { description: "GA4 is not connected." },
@@ -5356,7 +5357,7 @@ var routeCatalog = [
5356
5357
  path: "/api/v1/projects/{name}/ga/social-referral-history",
5357
5358
  summary: "Get social media referral sessions per day grouped by source",
5358
5359
  tags: ["ga4"],
5359
- parameters: [nameParameter],
5360
+ parameters: [nameParameter, analyticsWindowParameter],
5360
5361
  responses: {
5361
5362
  200: { description: "Social referral history returned." },
5362
5363
  400: { description: "GA4 is not connected." },
@@ -5392,7 +5393,7 @@ var routeCatalog = [
5392
5393
  path: "/api/v1/projects/{name}/ga/session-history",
5393
5394
  summary: "Get total sessions per day for the project",
5394
5395
  tags: ["ga4"],
5395
- parameters: [nameParameter],
5396
+ parameters: [nameParameter, analyticsWindowParameter],
5396
5397
  responses: {
5397
5398
  200: { description: "Session history returned." },
5398
5399
  400: { description: "GA4 is not connected." },
@@ -6879,9 +6880,9 @@ async function googleRoutes(app, opts) {
6879
6880
  const project = resolveProject(app.db, request.params.name);
6880
6881
  let redirectUri;
6881
6882
  if (publicUrl) {
6882
- redirectUri = publicUrl.replace(/\/$/, "") + (opts.routePrefix ?? "/api/v1") + "/google/callback";
6883
+ redirectUri = publicUrl.replace(/\/$/, "") + "/api/v1/google/callback";
6883
6884
  } else if (opts.publicUrl) {
6884
- redirectUri = opts.publicUrl.replace(/\/$/, "") + (opts.routePrefix ?? "/api/v1") + "/google/callback";
6885
+ redirectUri = opts.publicUrl.replace(/\/$/, "") + "/api/v1/google/callback";
6885
6886
  } else {
6886
6887
  const proto = request.headers["x-forwarded-proto"] ?? "http";
6887
6888
  const host = request.headers.host ?? "localhost:4100";
@@ -7050,8 +7051,10 @@ async function googleRoutes(app, opts) {
7050
7051
  app.get("/projects/:name/google/gsc/performance", async (request) => {
7051
7052
  const project = resolveProject(app.db, request.params.name);
7052
7053
  const { startDate, endDate, query, page, limit } = request.query;
7054
+ const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
7053
7055
  const conditions = [eq14(gscSearchData.projectId, project.id)];
7054
7056
  if (startDate) conditions.push(sql2`${gscSearchData.date} >= ${startDate}`);
7057
+ else if (cutoffDate) conditions.push(sql2`${gscSearchData.date} >= ${cutoffDate}`);
7055
7058
  if (endDate) conditions.push(sql2`${gscSearchData.date} <= ${endDate}`);
7056
7059
  if (query) conditions.push(sql2`${gscSearchData.query} LIKE ${"%" + query + "%"}`);
7057
7060
  if (page) conditions.push(sql2`${gscSearchData.page} LIKE ${"%" + page + "%"}`);
@@ -8629,24 +8632,41 @@ async function ga4Routes(app, opts) {
8629
8632
  const project = resolveProject(app.db, request.params.name);
8630
8633
  requireGa4Connection(opts, project.name, project.canonicalDomain);
8631
8634
  const limit = Math.max(1, Math.min(parseInt(request.query.limit ?? "50", 10) || 50, 500));
8632
- const summary = app.db.select({
8635
+ const window = parseWindow(request.query.window);
8636
+ const cutoff = windowCutoff(window);
8637
+ const cutoffDate = cutoff?.slice(0, 10) ?? null;
8638
+ const snapshotConditions = [eq17(gaTrafficSnapshots.projectId, project.id)];
8639
+ if (cutoffDate) snapshotConditions.push(sql3`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
8640
+ const aiConditions = [eq17(gaAiReferrals.projectId, project.id)];
8641
+ if (cutoffDate) aiConditions.push(sql3`${gaAiReferrals.date} >= ${cutoffDate}`);
8642
+ const socialConditions = [eq17(gaSocialReferrals.projectId, project.id)];
8643
+ if (cutoffDate) socialConditions.push(sql3`${gaSocialReferrals.date} >= ${cutoffDate}`);
8644
+ const summaryRow = cutoffDate ? app.db.select({
8645
+ totalSessions: sql3`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
8646
+ totalOrganicSessions: sql3`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
8647
+ totalUsers: sql3`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
8648
+ }).from(gaTrafficSnapshots).where(and6(...snapshotConditions)).get() : app.db.select({
8633
8649
  totalSessions: gaTrafficSummaries.totalSessions,
8634
8650
  totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
8635
8651
  totalUsers: gaTrafficSummaries.totalUsers
8636
8652
  }).from(gaTrafficSummaries).where(eq17(gaTrafficSummaries.projectId, project.id)).get();
8653
+ const summaryMeta = app.db.select({
8654
+ periodStart: gaTrafficSummaries.periodStart,
8655
+ periodEnd: gaTrafficSummaries.periodEnd
8656
+ }).from(gaTrafficSummaries).where(eq17(gaTrafficSummaries.projectId, project.id)).get();
8637
8657
  const rows = app.db.select({
8638
8658
  landingPage: gaTrafficSnapshots.landingPage,
8639
8659
  sessions: sql3`SUM(${gaTrafficSnapshots.sessions})`,
8640
8660
  organicSessions: sql3`SUM(${gaTrafficSnapshots.organicSessions})`,
8641
8661
  users: sql3`SUM(${gaTrafficSnapshots.users})`
8642
- }).from(gaTrafficSnapshots).where(eq17(gaTrafficSnapshots.projectId, project.id)).groupBy(gaTrafficSnapshots.landingPage).orderBy(sql3`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
8662
+ }).from(gaTrafficSnapshots).where(and6(...snapshotConditions)).groupBy(gaTrafficSnapshots.landingPage).orderBy(sql3`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
8643
8663
  const aiReferrals = app.db.select({
8644
8664
  source: gaAiReferrals.source,
8645
8665
  medium: gaAiReferrals.medium,
8646
8666
  sourceDimension: gaAiReferrals.sourceDimension,
8647
8667
  sessions: sql3`SUM(${gaAiReferrals.sessions})`,
8648
8668
  users: sql3`SUM(${gaAiReferrals.users})`
8649
- }).from(gaAiReferrals).where(eq17(gaAiReferrals.projectId, project.id)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).orderBy(sql3`SUM(${gaAiReferrals.sessions}) DESC`).all();
8669
+ }).from(gaAiReferrals).where(and6(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).orderBy(sql3`SUM(${gaAiReferrals.sessions}) DESC`).all();
8650
8670
  const aiDeduped = app.db.select({
8651
8671
  sessions: sql3`SUM(max_sessions)`,
8652
8672
  users: sql3`SUM(max_users)`
@@ -8656,7 +8676,7 @@ async function ga4Routes(app, opts) {
8656
8676
  MAX(sessions) AS max_sessions,
8657
8677
  MAX(users) AS max_users
8658
8678
  FROM ga_ai_referrals
8659
- WHERE project_id = ${project.id}
8679
+ WHERE project_id = ${project.id}${cutoffDate ? sql3` AND date >= ${cutoffDate}` : sql3``}
8660
8680
  GROUP BY date, source, medium
8661
8681
  )`
8662
8682
  ).get();
@@ -8666,17 +8686,17 @@ async function ga4Routes(app, opts) {
8666
8686
  channelGroup: gaSocialReferrals.channelGroup,
8667
8687
  sessions: sql3`SUM(${gaSocialReferrals.sessions})`,
8668
8688
  users: sql3`SUM(${gaSocialReferrals.users})`
8669
- }).from(gaSocialReferrals).where(eq17(gaSocialReferrals.projectId, project.id)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql3`SUM(${gaSocialReferrals.sessions}) DESC`).all();
8689
+ }).from(gaSocialReferrals).where(and6(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql3`SUM(${gaSocialReferrals.sessions}) DESC`).all();
8670
8690
  const socialTotals = app.db.select({
8671
8691
  sessions: sql3`SUM(${gaSocialReferrals.sessions})`,
8672
8692
  users: sql3`SUM(${gaSocialReferrals.users})`
8673
- }).from(gaSocialReferrals).where(eq17(gaSocialReferrals.projectId, project.id)).get();
8693
+ }).from(gaSocialReferrals).where(and6(...socialConditions)).get();
8674
8694
  const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq17(gaTrafficSummaries.projectId, project.id)).orderBy(desc7(gaTrafficSummaries.syncedAt)).limit(1).get();
8675
- const total = summary?.totalSessions ?? 0;
8695
+ const total = summaryRow?.totalSessions ?? 0;
8676
8696
  return {
8677
8697
  totalSessions: total,
8678
- totalOrganicSessions: summary?.totalOrganicSessions ?? 0,
8679
- totalUsers: summary?.totalUsers ?? 0,
8698
+ totalOrganicSessions: summaryRow?.totalOrganicSessions ?? 0,
8699
+ totalUsers: summaryRow?.totalUsers ?? 0,
8680
8700
  topPages: rows.map((r) => ({
8681
8701
  landingPage: r.landingPage,
8682
8702
  sessions: r.sessions ?? 0,
@@ -8701,15 +8721,25 @@ async function ga4Routes(app, opts) {
8701
8721
  })),
8702
8722
  socialSessions: socialTotals?.sessions ?? 0,
8703
8723
  socialUsers: socialTotals?.users ?? 0,
8704
- organicSharePct: total > 0 ? Math.round((summary?.totalOrganicSessions ?? 0) / total * 100) : 0,
8724
+ organicSharePct: total > 0 ? Math.round((summaryRow?.totalOrganicSessions ?? 0) / total * 100) : 0,
8705
8725
  aiSharePct: total > 0 ? Math.round((aiDeduped?.sessions ?? 0) / total * 100) : 0,
8706
8726
  socialSharePct: total > 0 ? Math.round((socialTotals?.sessions ?? 0) / total * 100) : 0,
8707
- lastSyncedAt: latestSync?.syncedAt ?? null
8727
+ lastSyncedAt: latestSync?.syncedAt ?? null,
8728
+ periodStart: (() => {
8729
+ const start = cutoffDate ?? summaryMeta?.periodStart ?? null;
8730
+ const end = summaryMeta?.periodEnd ?? null;
8731
+ if (start && end && start > end) return summaryMeta?.periodStart ?? null;
8732
+ return start;
8733
+ })(),
8734
+ periodEnd: summaryMeta?.periodEnd ?? null
8708
8735
  };
8709
8736
  });
8710
8737
  app.get("/projects/:name/ga/ai-referral-history", async (request, _reply) => {
8711
8738
  const project = resolveProject(app.db, request.params.name);
8712
8739
  requireGa4Connection(opts, project.name, project.canonicalDomain);
8740
+ const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
8741
+ const conditions = [eq17(gaAiReferrals.projectId, project.id)];
8742
+ if (cutoffDate) conditions.push(sql3`${gaAiReferrals.date} >= ${cutoffDate}`);
8713
8743
  const rows = app.db.select({
8714
8744
  date: gaAiReferrals.date,
8715
8745
  source: gaAiReferrals.source,
@@ -8717,12 +8747,15 @@ async function ga4Routes(app, opts) {
8717
8747
  sourceDimension: gaAiReferrals.sourceDimension,
8718
8748
  sessions: gaAiReferrals.sessions,
8719
8749
  users: gaAiReferrals.users
8720
- }).from(gaAiReferrals).where(eq17(gaAiReferrals.projectId, project.id)).orderBy(gaAiReferrals.date).all();
8750
+ }).from(gaAiReferrals).where(and6(...conditions)).orderBy(gaAiReferrals.date).all();
8721
8751
  return rows;
8722
8752
  });
8723
8753
  app.get("/projects/:name/ga/social-referral-history", async (request, _reply) => {
8724
8754
  const project = resolveProject(app.db, request.params.name);
8725
8755
  requireGa4Connection(opts, project.name, project.canonicalDomain);
8756
+ const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
8757
+ const conditions = [eq17(gaSocialReferrals.projectId, project.id)];
8758
+ if (cutoffDate) conditions.push(sql3`${gaSocialReferrals.date} >= ${cutoffDate}`);
8726
8759
  const rows = app.db.select({
8727
8760
  date: gaSocialReferrals.date,
8728
8761
  source: gaSocialReferrals.source,
@@ -8730,7 +8763,7 @@ async function ga4Routes(app, opts) {
8730
8763
  channelGroup: gaSocialReferrals.channelGroup,
8731
8764
  sessions: gaSocialReferrals.sessions,
8732
8765
  users: gaSocialReferrals.users
8733
- }).from(gaSocialReferrals).where(eq17(gaSocialReferrals.projectId, project.id)).orderBy(gaSocialReferrals.date).all();
8766
+ }).from(gaSocialReferrals).where(and6(...conditions)).orderBy(gaSocialReferrals.date).all();
8734
8767
  return rows;
8735
8768
  });
8736
8769
  app.get("/projects/:name/ga/social-referral-trend", async (request, _reply) => {
@@ -8863,12 +8896,15 @@ async function ga4Routes(app, opts) {
8863
8896
  app.get("/projects/:name/ga/session-history", async (request, _reply) => {
8864
8897
  const project = resolveProject(app.db, request.params.name);
8865
8898
  requireGa4Connection(opts, project.name, project.canonicalDomain);
8899
+ const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
8900
+ const conditions = [eq17(gaTrafficSnapshots.projectId, project.id)];
8901
+ if (cutoffDate) conditions.push(sql3`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
8866
8902
  const rows = app.db.select({
8867
8903
  date: gaTrafficSnapshots.date,
8868
8904
  sessions: sql3`SUM(${gaTrafficSnapshots.sessions})`,
8869
8905
  organicSessions: sql3`SUM(${gaTrafficSnapshots.organicSessions})`,
8870
8906
  users: sql3`SUM(${gaTrafficSnapshots.users})`
8871
- }).from(gaTrafficSnapshots).where(eq17(gaTrafficSnapshots.projectId, project.id)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
8907
+ }).from(gaTrafficSnapshots).where(and6(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
8872
8908
  return rows.map((r) => ({
8873
8909
  date: r.date,
8874
8910
  sessions: r.sessions ?? 0,
@@ -14359,7 +14395,7 @@ var RunCoordinator = class {
14359
14395
  }
14360
14396
  async onRunCompleted(runId, projectId) {
14361
14397
  try {
14362
- this.intelligenceService.analyzeAndPersist(runId, projectId);
14398
+ await this.intelligenceService.analyzeAndPersist(runId, projectId);
14363
14399
  } catch (err) {
14364
14400
  log6.error("intelligence.failed", { runId, error: err instanceof Error ? err.message : String(err) });
14365
14401
  }