@ainyc/canonry 1.45.2 → 1.46.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.
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-Dh9YyROK.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." },
@@ -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,
package/dist/cli.js CHANGED
@@ -28,7 +28,7 @@ import {
28
28
  setGoogleAuthConfig,
29
29
  showFirstRunNotice,
30
30
  trackEvent
31
- } from "./chunk-WNOUK4KA.js";
31
+ } from "./chunk-22RIKNII.js";
32
32
  import {
33
33
  apiKeys,
34
34
  competitors,
@@ -909,11 +909,13 @@ var ApiClient = class {
909
909
  async gaCoverage(project) {
910
910
  return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/coverage`);
911
911
  }
912
- async gaAiReferralHistory(project) {
913
- return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/ai-referral-history`);
912
+ async gaAiReferralHistory(project, params) {
913
+ const qs = params ? "?" + new URLSearchParams(params).toString() : "";
914
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/ai-referral-history${qs}`);
914
915
  }
915
- async gaSocialReferralHistory(project) {
916
- return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/social-referral-history`);
916
+ async gaSocialReferralHistory(project, params) {
917
+ const qs = params ? "?" + new URLSearchParams(params).toString() : "";
918
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/social-referral-history${qs}`);
917
919
  }
918
920
  async gaSocialReferralTrend(project) {
919
921
  return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/social-referral-trend`);
@@ -921,8 +923,9 @@ var ApiClient = class {
921
923
  async gaAttributionTrend(project) {
922
924
  return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/attribution-trend`);
923
925
  }
924
- async gaSessionHistory(project) {
925
- return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/session-history`);
926
+ async gaSessionHistory(project, params) {
927
+ const qs = params ? "?" + new URLSearchParams(params).toString() : "";
928
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/session-history${qs}`);
926
929
  }
927
930
  async wordpressConnect(project, body) {
928
931
  return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/connect`, body);
@@ -1781,13 +1784,18 @@ async function gaTraffic(project, opts) {
1781
1784
  const client = getClient3();
1782
1785
  const params = {};
1783
1786
  if (opts?.limit) params.limit = String(opts.limit);
1787
+ if (opts?.window) params.window = opts.window;
1784
1788
  const result = await client.gaTraffic(project, Object.keys(params).length > 0 ? params : void 0);
1785
1789
  if (opts?.format === "json") {
1786
1790
  console.log(JSON.stringify(result, null, 2));
1787
1791
  return;
1788
1792
  }
1789
1793
  if (result.topPages.length === 0 && result.aiReferrals.length === 0 && result.socialReferrals.length === 0) {
1790
- console.log('No GA4 traffic data. Run "canonry ga sync <project>" first.');
1794
+ if (!result.lastSyncedAt) {
1795
+ console.log('No GA4 traffic data. Run "canonry ga sync <project>" first.');
1796
+ } else {
1797
+ console.log(`No GA4 traffic data for the selected period.${opts?.window ? ` Try a wider window or omit --window.` : ""}`);
1798
+ }
1791
1799
  return;
1792
1800
  }
1793
1801
  console.log(`GA4 Traffic for "${project}"
@@ -1847,15 +1855,15 @@ async function gaTraffic(project, opts) {
1847
1855
  Last synced: ${result.lastSyncedAt}`);
1848
1856
  }
1849
1857
  }
1850
- async function gaAiReferralHistory(project, format) {
1858
+ async function gaAiReferralHistory(project, opts) {
1851
1859
  const client = getClient3();
1852
- const result = await client.gaAiReferralHistory(project);
1853
- if (format === "json") {
1860
+ const result = await client.gaAiReferralHistory(project, opts?.window ? { window: opts.window } : void 0);
1861
+ if (opts?.format === "json") {
1854
1862
  console.log(JSON.stringify(result, null, 2));
1855
1863
  return;
1856
1864
  }
1857
1865
  if (result.length === 0) {
1858
- console.log('No AI referral history. Run "canonry ga sync <project>" first.');
1866
+ console.log(`No AI referral history.${opts?.window ? " Try a wider window or omit --window." : ' Run "canonry ga sync <project>" first.'}`);
1859
1867
  return;
1860
1868
  }
1861
1869
  const dateWidth = 12;
@@ -1872,15 +1880,15 @@ async function gaAiReferralHistory(project, format) {
1872
1880
  );
1873
1881
  }
1874
1882
  }
1875
- async function gaSocialReferralHistory(project, format) {
1883
+ async function gaSocialReferralHistory(project, opts) {
1876
1884
  const client = getClient3();
1877
- const result = await client.gaSocialReferralHistory(project);
1878
- if (format === "json") {
1885
+ const result = await client.gaSocialReferralHistory(project, opts?.window ? { window: opts.window } : void 0);
1886
+ if (opts?.format === "json") {
1879
1887
  console.log(JSON.stringify(result, null, 2));
1880
1888
  return;
1881
1889
  }
1882
1890
  if (result.length === 0) {
1883
- console.log('No social referral history. Run "canonry ga sync <project>" first.');
1891
+ console.log(`No social referral history.${opts?.window ? " Try a wider window or omit --window." : ' Run "canonry ga sync <project>" first.'}`);
1884
1892
  return;
1885
1893
  }
1886
1894
  const dateWidth = 12;
@@ -1897,6 +1905,28 @@ async function gaSocialReferralHistory(project, format) {
1897
1905
  );
1898
1906
  }
1899
1907
  }
1908
+ async function gaSessionHistory(project, opts) {
1909
+ const client = getClient3();
1910
+ const result = await client.gaSessionHistory(project, opts?.window ? { window: opts.window } : void 0);
1911
+ if (opts?.format === "json") {
1912
+ console.log(JSON.stringify(result, null, 2));
1913
+ return;
1914
+ }
1915
+ if (result.length === 0) {
1916
+ console.log(`No session history.${opts?.window ? " Try a wider window or omit --window." : ' Run "canonry ga sync <project>" first.'}`);
1917
+ return;
1918
+ }
1919
+ const dateWidth = 12;
1920
+ console.log(`GA4 Session History for "${project}":
1921
+ `);
1922
+ console.log(` ${"DATE".padEnd(dateWidth)} ${"SESSIONS".padEnd(10)}${"ORGANIC".padEnd(10)}${"USERS".padEnd(8)}`);
1923
+ console.log(` ${"\u2500".repeat(dateWidth)} ${"\u2500".repeat(10)}${"\u2500".repeat(10)}${"\u2500".repeat(8)}`);
1924
+ for (const row of result) {
1925
+ console.log(
1926
+ ` ${row.date.padEnd(dateWidth)} ${String(row.sessions).padEnd(10)}${String(row.organicSessions).padEnd(10)}${String(row.users).padEnd(8)}`
1927
+ );
1928
+ }
1929
+ }
1900
1930
  async function gaCoverage(project, format) {
1901
1931
  const client = getClient3();
1902
1932
  const result = await client.gaCoverage(project);
@@ -2034,9 +2064,12 @@ async function gaAttribution(project, opts) {
2034
2064
  const m = trend.socialBiggestMover;
2035
2065
  console.log(` Social Mover: ${m.source} (${m.changePct >= 0 ? "+" : ""}${m.changePct}%, ${m.sessionsPrev7d}\u2192${m.sessions7d} sessions/7d)`);
2036
2066
  }
2037
- if (traffic.lastSyncedAt) {
2067
+ if (traffic.periodStart && traffic.periodEnd) {
2038
2068
  console.log(`
2039
- Last synced: ${traffic.lastSyncedAt}`);
2069
+ Period: ${traffic.periodStart} to ${traffic.periodEnd}`);
2070
+ }
2071
+ if (traffic.lastSyncedAt) {
2072
+ console.log(` Last synced: ${traffic.lastSyncedAt}`);
2040
2073
  }
2041
2074
  return;
2042
2075
  }
@@ -2053,7 +2086,9 @@ async function gaAttribution(project, opts) {
2053
2086
  socialSharePct: traffic.socialSharePct,
2054
2087
  organicSharePct: traffic.organicSharePct,
2055
2088
  aiReferrals: traffic.aiReferrals,
2056
- socialReferrals: traffic.socialReferrals
2089
+ socialReferrals: traffic.socialReferrals,
2090
+ periodStart: traffic.periodStart,
2091
+ periodEnd: traffic.periodEnd
2057
2092
  }, null, 2));
2058
2093
  return;
2059
2094
  }
@@ -2091,9 +2126,12 @@ async function gaAttribution(project, opts) {
2091
2126
  console.log(` ${ref.source.padEnd(25)} ${String(ref.sessions).padEnd(8)} sessions (${chanLabel})`);
2092
2127
  }
2093
2128
  }
2094
- if (traffic.lastSyncedAt) {
2129
+ if (traffic.periodStart && traffic.periodEnd) {
2095
2130
  console.log(`
2096
- Last synced: ${traffic.lastSyncedAt}`);
2131
+ Period: ${traffic.periodStart} to ${traffic.periodEnd}`);
2132
+ }
2133
+ if (traffic.lastSyncedAt) {
2134
+ console.log(` Last synced: ${traffic.lastSyncedAt}`);
2097
2135
  }
2098
2136
  }
2099
2137
 
@@ -2158,16 +2196,19 @@ var GA_CLI_COMMANDS = [
2158
2196
  },
2159
2197
  {
2160
2198
  path: ["ga", "traffic"],
2161
- usage: "canonry ga traffic <project> [--limit 50] [--format json]",
2199
+ usage: "canonry ga traffic <project> [--limit 50] [--window 30d] [--format json]",
2162
2200
  options: {
2163
- limit: stringOption()
2201
+ limit: stringOption(),
2202
+ window: stringOption()
2164
2203
  },
2165
2204
  run: async (input) => {
2166
- const project = requireProject(input, "ga.traffic", "canonry ga traffic <project> [--limit 50] [--format json]");
2205
+ const project = requireProject(input, "ga.traffic", "canonry ga traffic <project> [--limit 50] [--window 30d] [--format json]");
2167
2206
  const limitStr = getString(input.values, "limit");
2168
2207
  const limit = limitStr ? parseInt(limitStr, 10) : void 0;
2208
+ const window = getString(input.values, "window");
2169
2209
  await gaTraffic(project, {
2170
2210
  limit,
2211
+ window,
2171
2212
  format: input.format
2172
2213
  });
2173
2214
  }
@@ -2182,18 +2223,44 @@ var GA_CLI_COMMANDS = [
2182
2223
  },
2183
2224
  {
2184
2225
  path: ["ga", "ai-referral-history"],
2185
- usage: "canonry ga ai-referral-history <project> [--format json]",
2226
+ usage: "canonry ga ai-referral-history <project> [--window 30d] [--format json]",
2227
+ options: {
2228
+ window: stringOption()
2229
+ },
2186
2230
  run: async (input) => {
2187
- const project = requireProject(input, "ga.ai-referral-history", "canonry ga ai-referral-history <project> [--format json]");
2188
- await gaAiReferralHistory(project, input.format);
2231
+ const project = requireProject(input, "ga.ai-referral-history", "canonry ga ai-referral-history <project> [--window 30d] [--format json]");
2232
+ await gaAiReferralHistory(project, {
2233
+ window: getString(input.values, "window"),
2234
+ format: input.format
2235
+ });
2189
2236
  }
2190
2237
  },
2191
2238
  {
2192
2239
  path: ["ga", "social-referral-history"],
2193
- usage: "canonry ga social-referral-history <project> [--format json]",
2240
+ usage: "canonry ga social-referral-history <project> [--window 30d] [--format json]",
2241
+ options: {
2242
+ window: stringOption()
2243
+ },
2244
+ run: async (input) => {
2245
+ const project = requireProject(input, "ga.social-referral-history", "canonry ga social-referral-history <project> [--window 30d] [--format json]");
2246
+ await gaSocialReferralHistory(project, {
2247
+ window: getString(input.values, "window"),
2248
+ format: input.format
2249
+ });
2250
+ }
2251
+ },
2252
+ {
2253
+ path: ["ga", "session-history"],
2254
+ usage: "canonry ga session-history <project> [--window 30d] [--format json]",
2255
+ options: {
2256
+ window: stringOption()
2257
+ },
2194
2258
  run: async (input) => {
2195
- const project = requireProject(input, "ga.social-referral-history", "canonry ga social-referral-history <project> [--format json]");
2196
- await gaSocialReferralHistory(project, input.format);
2259
+ const project = requireProject(input, "ga.session-history", "canonry ga session-history <project> [--window 30d] [--format json]");
2260
+ await gaSessionHistory(project, {
2261
+ window: getString(input.values, "window"),
2262
+ format: input.format
2263
+ });
2197
2264
  }
2198
2265
  },
2199
2266
  {
@@ -2231,7 +2298,7 @@ var GA_CLI_COMMANDS = [
2231
2298
  unknownSubcommand(input.positionals[0], {
2232
2299
  command: "ga",
2233
2300
  usage: "canonry ga <subcommand> <project> [args]",
2234
- available: ["connect", "disconnect", "status", "sync", "traffic", "coverage", "ai-referral-history", "social-referral-history", "social-referral-summary", "attribution"]
2301
+ available: ["connect", "disconnect", "status", "sync", "traffic", "coverage", "ai-referral-history", "social-referral-history", "session-history", "social-referral-summary", "attribution"]
2235
2302
  });
2236
2303
  }
2237
2304
  }
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createServer,
3
3
  loadConfig
4
- } from "./chunk-WNOUK4KA.js";
4
+ } from "./chunk-22RIKNII.js";
5
5
  import "./chunk-HO22LHTY.js";
6
6
  export {
7
7
  createServer,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "1.45.2",
3
+ "version": "1.46.0",
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",
@@ -54,20 +54,20 @@
54
54
  "@types/node-cron": "^3.0.11",
55
55
  "tsup": "^8.5.1",
56
56
  "tsx": "^4.19.0",
57
- "@ainyc/canonry-api-routes": "0.0.0",
58
57
  "@ainyc/canonry-config": "0.0.0",
59
- "@ainyc/canonry-contracts": "0.0.0",
60
- "@ainyc/canonry-integration-bing": "0.0.0",
61
- "@ainyc/canonry-intelligence": "0.0.0",
58
+ "@ainyc/canonry-api-routes": "0.0.0",
62
59
  "@ainyc/canonry-db": "0.0.0",
60
+ "@ainyc/canonry-intelligence": "0.0.0",
61
+ "@ainyc/canonry-integration-bing": "0.0.0",
63
62
  "@ainyc/canonry-integration-google": "0.0.0",
64
- "@ainyc/canonry-integration-wordpress": "0.0.0",
63
+ "@ainyc/canonry-contracts": "0.0.0",
64
+ "@ainyc/canonry-provider-gemini": "0.0.0",
65
65
  "@ainyc/canonry-provider-cdp": "0.0.0",
66
- "@ainyc/canonry-provider-claude": "0.0.0",
67
66
  "@ainyc/canonry-provider-local": "0.0.0",
68
- "@ainyc/canonry-provider-gemini": "0.0.0",
69
- "@ainyc/canonry-provider-openai": "0.0.0",
70
- "@ainyc/canonry-provider-perplexity": "0.0.0"
67
+ "@ainyc/canonry-integration-wordpress": "0.0.0",
68
+ "@ainyc/canonry-provider-claude": "0.0.0",
69
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
70
+ "@ainyc/canonry-provider-openai": "0.0.0"
71
71
  },
72
72
  "scripts": {
73
73
  "build": "tsup && tsx build-web.ts",