@ainyc/canonry 1.34.0 → 1.35.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,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-BPuIj1DV.js"></script>
16
- <link rel="stylesheet" crossorigin href="./assets/index-CxMuEW6I.css">
15
+ <script type="module" crossorigin src="./assets/index-DyipkdOb.js"></script>
16
+ <link rel="stylesheet" crossorigin href="./assets/index-B9SBdBOm.css">
17
17
  </head>
18
18
  <body>
19
19
  <div id="root"></div>
@@ -1074,6 +1074,13 @@ var ga4TrafficSummaryDtoSchema = z11.object({
1074
1074
  aiReferrals: z11.array(ga4AiReferralDtoSchema),
1075
1075
  lastSyncedAt: z11.string().nullable()
1076
1076
  });
1077
+ var ga4AiReferralHistoryEntrySchema = z11.object({
1078
+ date: z11.string(),
1079
+ source: z11.string(),
1080
+ medium: z11.string(),
1081
+ sessions: z11.number(),
1082
+ users: z11.number()
1083
+ });
1077
1084
 
1078
1085
  // ../contracts/src/answer-visibility.ts
1079
1086
  var GENERIC_TOKENS = /* @__PURE__ */ new Set([
@@ -5754,6 +5761,18 @@ var routeCatalog = [
5754
5761
  404: { description: "Project not found." }
5755
5762
  }
5756
5763
  },
5764
+ {
5765
+ method: "get",
5766
+ path: "/api/v1/projects/{name}/ga/ai-referral-history",
5767
+ summary: "Get AI referral sessions per day grouped by source",
5768
+ tags: ["ga4"],
5769
+ parameters: [nameParameter],
5770
+ responses: {
5771
+ 200: { description: "AI referral history returned." },
5772
+ 400: { description: "GA4 is not connected." },
5773
+ 404: { description: "Project not found." }
5774
+ }
5775
+ },
5757
5776
  {
5758
5777
  method: "get",
5759
5778
  path: "/api/v1/projects/{name}/ga/coverage",
@@ -6263,6 +6282,7 @@ var GSC_MAX_ROWS_PER_REQUEST = 25e3;
6263
6282
  var GSC_DATA_LAG_DAYS = 3;
6264
6283
  var INDEXING_API_BASE = "https://indexing.googleapis.com/v3";
6265
6284
  var INDEXING_API_DAILY_LIMIT = 200;
6285
+ var GOOGLE_REQUEST_TIMEOUT_MS = 3e4;
6266
6286
 
6267
6287
  // ../integration-google/src/types.ts
6268
6288
  var GoogleAuthError = class extends Error {
@@ -6303,7 +6323,8 @@ async function exchangeCode(clientId, clientSecret, code, redirectUri) {
6303
6323
  code,
6304
6324
  redirect_uri: redirectUri,
6305
6325
  grant_type: "authorization_code"
6306
- })
6326
+ }),
6327
+ signal: AbortSignal.timeout(GOOGLE_REQUEST_TIMEOUT_MS)
6307
6328
  });
6308
6329
  if (!res.ok) {
6309
6330
  const body = await res.text();
@@ -6320,7 +6341,8 @@ async function refreshAccessToken(clientId, clientSecret, currentRefreshToken) {
6320
6341
  client_secret: clientSecret,
6321
6342
  refresh_token: currentRefreshToken,
6322
6343
  grant_type: "refresh_token"
6323
- })
6344
+ }),
6345
+ signal: AbortSignal.timeout(GOOGLE_REQUEST_TIMEOUT_MS)
6324
6346
  });
6325
6347
  if (!res.ok) {
6326
6348
  const body = await res.text();
@@ -6344,7 +6366,8 @@ async function gscFetch(accessToken, url, opts) {
6344
6366
  const res = await fetch(url, {
6345
6367
  method,
6346
6368
  headers,
6347
- body: opts?.body != null ? JSON.stringify(opts.body) : void 0
6369
+ body: opts?.body != null ? JSON.stringify(opts.body) : void 0,
6370
+ signal: AbortSignal.timeout(GOOGLE_REQUEST_TIMEOUT_MS)
6348
6371
  });
6349
6372
  if (res.status === 401) {
6350
6373
  const body = await res.text().catch(() => "");
@@ -6446,6 +6469,7 @@ var GA4_SCOPE = "https://www.googleapis.com/auth/analytics.readonly";
6446
6469
  var GOOGLE_TOKEN_URL2 = "https://oauth2.googleapis.com/token";
6447
6470
  var GA4_DEFAULT_SYNC_DAYS = 30;
6448
6471
  var GA4_MAX_SYNC_DAYS = 90;
6472
+ var GA4_REQUEST_TIMEOUT_MS = 3e4;
6449
6473
 
6450
6474
  // ../integration-google-analytics/src/types.ts
6451
6475
  var GA4ApiError = class extends Error {
@@ -6491,7 +6515,8 @@ async function getAccessToken(clientEmail, privateKey) {
6491
6515
  body: new URLSearchParams({
6492
6516
  grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
6493
6517
  assertion: jwt
6494
- })
6518
+ }),
6519
+ signal: AbortSignal.timeout(GA4_REQUEST_TIMEOUT_MS)
6495
6520
  });
6496
6521
  if (!res.ok) {
6497
6522
  const body = await res.text().catch(() => "");
@@ -6509,7 +6534,8 @@ async function runReport(accessToken, propertyId, request) {
6509
6534
  "Authorization": `Bearer ${accessToken}`,
6510
6535
  "Content-Type": "application/json"
6511
6536
  },
6512
- body: JSON.stringify(request)
6537
+ body: JSON.stringify(request),
6538
+ signal: AbortSignal.timeout(GA4_REQUEST_TIMEOUT_MS)
6513
6539
  });
6514
6540
  if (res.status === 401 || res.status === 403) {
6515
6541
  const body = await res.text().catch(() => "");
@@ -6549,7 +6575,8 @@ async function batchRunReports(accessToken, propertyId, requests) {
6549
6575
  "Authorization": `Bearer ${accessToken}`,
6550
6576
  "Content-Type": "application/json"
6551
6577
  },
6552
- body: JSON.stringify({ requests })
6578
+ body: JSON.stringify({ requests }),
6579
+ signal: AbortSignal.timeout(GA4_REQUEST_TIMEOUT_MS)
6553
6580
  });
6554
6581
  if (res.status === 401 || res.status === 403) {
6555
6582
  const body = await res.text().catch(() => "");
@@ -7488,6 +7515,7 @@ import { eq as eq14, and as and3, desc as desc5 } from "drizzle-orm";
7488
7515
  var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
7489
7516
  var BING_SUBMIT_URL_BATCH_LIMIT = 500;
7490
7517
  var BING_SUBMIT_URL_DAILY_LIMIT = 1e4;
7518
+ var BING_REQUEST_TIMEOUT_MS = 3e4;
7491
7519
 
7492
7520
  // ../integration-bing/src/types.ts
7493
7521
  var BingApiError = class extends Error {
@@ -7515,7 +7543,8 @@ async function bingFetch(apiKey, endpoint, opts) {
7515
7543
  const res = await fetch(url, {
7516
7544
  method,
7517
7545
  headers,
7518
- body: opts?.body != null ? JSON.stringify(opts.body) : void 0
7546
+ body: opts?.body != null ? JSON.stringify(opts.body) : void 0,
7547
+ signal: AbortSignal.timeout(BING_REQUEST_TIMEOUT_MS)
7519
7548
  });
7520
7549
  if (res.status === 401 || res.status === 403) {
7521
7550
  const body = await res.text().catch(() => "");
@@ -8463,6 +8492,18 @@ async function ga4Routes(app, opts) {
8463
8492
  lastSyncedAt: latestSync?.syncedAt ?? null
8464
8493
  };
8465
8494
  });
8495
+ app.get("/projects/:name/ga/ai-referral-history", async (request, _reply) => {
8496
+ const project = resolveProject(app.db, request.params.name);
8497
+ requireGa4Connection(opts, project.name, project.canonicalDomain);
8498
+ const rows = app.db.select({
8499
+ date: gaAiReferrals.date,
8500
+ source: gaAiReferrals.source,
8501
+ medium: gaAiReferrals.medium,
8502
+ sessions: gaAiReferrals.sessions,
8503
+ users: gaAiReferrals.users
8504
+ }).from(gaAiReferrals).where(eq16(gaAiReferrals.projectId, project.id)).orderBy(gaAiReferrals.date).all();
8505
+ return rows;
8506
+ });
8466
8507
  app.get("/projects/:name/ga/coverage", async (request, _reply) => {
8467
8508
  const project = resolveProject(app.db, request.params.name);
8468
8509
  requireGa4Connection(opts, project.name, project.canonicalDomain);
@@ -8606,6 +8647,8 @@ function parseSchemaPageEntry(entry) {
8606
8647
 
8607
8648
  // ../integration-wordpress/src/wordpress-client.ts
8608
8649
  import crypto17 from "crypto";
8650
+ var WP_REQUEST_TIMEOUT_MS = 3e4;
8651
+ var WP_FETCH_TEXT_TIMEOUT_MS = 15e3;
8609
8652
  var PAGE_FIELDS = "id,slug,status,link,modified,modified_gmt,title,content,meta";
8610
8653
  var PAGE_LIST_FIELDS = "id,slug,status,link,modified,modified_gmt,title";
8611
8654
  var VERIFY_PAGE_FIELDS = "id,status";
@@ -8662,7 +8705,8 @@ async function fetchJson(connection, siteUrl, path7, init) {
8662
8705
  "Authorization": `Basic ${encodeBasicAuth(connection.username, connection.appPassword)}`,
8663
8706
  ...init?.body != null ? { "Content-Type": "application/json" } : {},
8664
8707
  ...init?.headers ?? {}
8665
- }
8708
+ },
8709
+ signal: AbortSignal.timeout(WP_REQUEST_TIMEOUT_MS)
8666
8710
  });
8667
8711
  if (res.status === 401 || res.status === 403) {
8668
8712
  const text2 = await res.text().catch(() => "");
@@ -8705,7 +8749,7 @@ async function fetchPageCollectionSummary(connection, siteUrl, options) {
8705
8749
  }
8706
8750
  async function fetchText(url) {
8707
8751
  try {
8708
- const res = await fetch(url);
8752
+ const res = await fetch(url, { signal: AbortSignal.timeout(WP_FETCH_TEXT_TIMEOUT_MS) });
8709
8753
  if (!res.ok) return null;
8710
8754
  return await res.text();
8711
8755
  } catch {
package/dist/cli.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  setGoogleAuthConfig,
27
27
  showFirstRunNotice,
28
28
  trackEvent
29
- } from "./chunk-4SRBJCNX.js";
29
+ } from "./chunk-ETP5IOHC.js";
30
30
 
31
31
  // src/cli.ts
32
32
  import { pathToFileURL } from "url";
@@ -667,6 +667,9 @@ var ApiClient = class {
667
667
  async gaCoverage(project) {
668
668
  return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/coverage`);
669
669
  }
670
+ async gaAiReferralHistory(project) {
671
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/ai-referral-history`);
672
+ }
670
673
  async wordpressConnect(project, body) {
671
674
  return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/connect`, body);
672
675
  }
@@ -1516,6 +1519,29 @@ async function gaTraffic(project, opts) {
1516
1519
  Last synced: ${result.lastSyncedAt}`);
1517
1520
  }
1518
1521
  }
1522
+ async function gaAiReferralHistory(project, format) {
1523
+ const client = getClient3();
1524
+ const result = await client.gaAiReferralHistory(project);
1525
+ if (format === "json") {
1526
+ console.log(JSON.stringify(result, null, 2));
1527
+ return;
1528
+ }
1529
+ if (result.length === 0) {
1530
+ console.log('No AI referral history. Run "canonry ga sync <project>" first.');
1531
+ return;
1532
+ }
1533
+ const dateWidth = 12;
1534
+ const sourceWidth = Math.min(30, Math.max(10, ...result.map((r) => r.source.length)));
1535
+ console.log(`GA4 AI Referral History for "${project}":
1536
+ `);
1537
+ console.log(` ${"DATE".padEnd(dateWidth)} ${"SOURCE".padEnd(sourceWidth)} ${"SESSIONS".padEnd(10)}${"USERS".padEnd(8)}`);
1538
+ console.log(` ${"\u2500".repeat(dateWidth)} ${"\u2500".repeat(sourceWidth)} ${"\u2500".repeat(10)}${"\u2500".repeat(8)}`);
1539
+ for (const row of result) {
1540
+ console.log(
1541
+ ` ${row.date.padEnd(dateWidth)} ${row.source.padEnd(sourceWidth)} ${String(row.sessions).padEnd(10)}${String(row.users).padEnd(8)}`
1542
+ );
1543
+ }
1544
+ }
1519
1545
  async function gaCoverage(project, format) {
1520
1546
  const client = getClient3();
1521
1547
  const result = await client.gaCoverage(project);
@@ -1620,14 +1646,22 @@ var GA_CLI_COMMANDS = [
1620
1646
  await gaCoverage(project, input.format);
1621
1647
  }
1622
1648
  },
1649
+ {
1650
+ path: ["ga", "ai-referral-history"],
1651
+ usage: "canonry ga ai-referral-history <project> [--format json]",
1652
+ run: async (input) => {
1653
+ const project = requireProject(input, "ga.ai-referral-history", "canonry ga ai-referral-history <project> [--format json]");
1654
+ await gaAiReferralHistory(project, input.format);
1655
+ }
1656
+ },
1623
1657
  {
1624
1658
  path: ["ga"],
1625
- usage: "canonry ga <connect|disconnect|status|sync|traffic|coverage> <project> [args]",
1659
+ usage: "canonry ga <connect|disconnect|status|sync|traffic|coverage|ai-referral-history> <project> [args]",
1626
1660
  run: async (input) => {
1627
1661
  unknownSubcommand(input.positionals[0], {
1628
1662
  command: "ga",
1629
- usage: "canonry ga <connect|disconnect|status|sync|traffic|coverage> <project> [args]",
1630
- available: ["connect", "disconnect", "status", "sync", "traffic", "coverage"]
1663
+ usage: "canonry ga <connect|disconnect|status|sync|traffic|coverage|ai-referral-history> <project> [args]",
1664
+ available: ["connect", "disconnect", "status", "sync", "traffic", "coverage", "ai-referral-history"]
1631
1665
  });
1632
1666
  }
1633
1667
  }
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createServer,
3
3
  loadConfig
4
- } from "./chunk-4SRBJCNX.js";
4
+ } from "./chunk-ETP5IOHC.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.34.0",
3
+ "version": "1.35.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",
@@ -55,18 +55,18 @@
55
55
  "tsup": "^8.5.1",
56
56
  "tsx": "^4.19.0",
57
57
  "@ainyc/canonry-api-routes": "0.0.0",
58
- "@ainyc/canonry-contracts": "0.0.0",
58
+ "@ainyc/canonry-config": "0.0.0",
59
59
  "@ainyc/canonry-integration-bing": "0.0.0",
60
60
  "@ainyc/canonry-db": "0.0.0",
61
+ "@ainyc/canonry-contracts": "0.0.0",
61
62
  "@ainyc/canonry-integration-google": "0.0.0",
62
63
  "@ainyc/canonry-integration-wordpress": "0.0.0",
63
- "@ainyc/canonry-config": "0.0.0",
64
+ "@ainyc/canonry-provider-cdp": "0.0.0",
64
65
  "@ainyc/canonry-provider-claude": "0.0.0",
65
66
  "@ainyc/canonry-provider-local": "0.0.0",
66
- "@ainyc/canonry-provider-gemini": "0.0.0",
67
- "@ainyc/canonry-provider-perplexity": "0.0.0",
68
67
  "@ainyc/canonry-provider-openai": "0.0.0",
69
- "@ainyc/canonry-provider-cdp": "0.0.0"
68
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
69
+ "@ainyc/canonry-provider-gemini": "0.0.0"
70
70
  },
71
71
  "scripts": {
72
72
  "build": "tsup && tsx build-web.ts",