@ainyc/canonry 4.139.1 → 4.142.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.
Files changed (30) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +33 -5
  2. package/assets/agent-workspace/skills/canonry/references/indexing.md +2 -1
  3. package/assets/assets/{AuditHistoryPanel-FiMbN4_a.js → AuditHistoryPanel-CfaF5B_e.js} +1 -1
  4. package/assets/assets/{BacklinksPage-CLRIsKHN.js → BacklinksPage-BIytWBpL.js} +1 -1
  5. package/assets/assets/{ChartPrimitives-BuTqG5NR.js → ChartPrimitives-0tQT6GE7.js} +1 -1
  6. package/assets/assets/{HistoryPage-BWRbsFAT.js → HistoryPage-Bn6IH8WV.js} +1 -1
  7. package/assets/assets/{ProjectPage-DQ2kIj-L.js → ProjectPage-DsQic9wX.js} +8 -8
  8. package/assets/assets/{RunRow-BZobaeDq.js → RunRow-RHd8Bd52.js} +1 -1
  9. package/assets/assets/{RunsPage-DhrY4Ouc.js → RunsPage-BsDqqQFX.js} +1 -1
  10. package/assets/assets/{SettingsPage-C6VcJEgB.js → SettingsPage-CX2LkFDB.js} +1 -1
  11. package/assets/assets/{TrafficPage-KqYl5HUH.js → TrafficPage-I19b7i9m.js} +1 -1
  12. package/assets/assets/{TrafficSourceDetailPage-mMJi-QZr.js → TrafficSourceDetailPage-DOLEAPsc.js} +1 -1
  13. package/assets/assets/{arrow-left-CpLow2Kp.js → arrow-left-BG9o86__.js} +1 -1
  14. package/assets/assets/{extract-error-message-dHq11Y4U.js → extract-error-message-D0cgM2XG.js} +1 -1
  15. package/assets/assets/index-BDo9nl4O.css +1 -0
  16. package/assets/assets/index-CYGBt2f0.js +213 -0
  17. package/assets/assets/{trash-2-CnEPNBqu.js → trash-2-C_THWxO_.js} +1 -1
  18. package/assets/index.html +2 -2
  19. package/dist/{chunk-IYTHIAAE.js → chunk-D7NJUGNQ.js} +55 -10
  20. package/dist/{chunk-OA2EGCUS.js → chunk-LS6SEZVF.js} +120 -4
  21. package/dist/{chunk-DJHSOPMU.js → chunk-NR6OGEV3.js} +326 -67
  22. package/dist/{chunk-YLHZQ7HQ.js → chunk-UFPP5CSN.js} +16 -5
  23. package/dist/cli.js +203 -63
  24. package/dist/index.d.ts +6 -0
  25. package/dist/index.js +4 -4
  26. package/dist/{intelligence-service-BGH2MGHB.js → intelligence-service-EOUP37XN.js} +2 -2
  27. package/dist/mcp.js +2 -2
  28. package/package.json +8 -8
  29. package/assets/assets/index-CntuM6Th.css +0 -1
  30. package/assets/assets/index-iRW7X23W.js +0 -213
@@ -10,7 +10,7 @@ import {
10
10
  loadConfig,
11
11
  loadConfigRaw,
12
12
  saveConfigPatch
13
- } from "./chunk-IYTHIAAE.js";
13
+ } from "./chunk-D7NJUGNQ.js";
14
14
  import {
15
15
  CC_CACHE_DIR,
16
16
  DUCKDB_SPEC,
@@ -123,7 +123,7 @@ import {
123
123
  siteAuditSnapshots,
124
124
  toAlertView,
125
125
  usageCounters
126
- } from "./chunk-DJHSOPMU.js";
126
+ } from "./chunk-NR6OGEV3.js";
127
127
  import {
128
128
  AGENT_MEMORY_VALUE_MAX_BYTES,
129
129
  AGENT_PROVIDER_IDS,
@@ -210,7 +210,7 @@ import {
210
210
  validationError,
211
211
  winnabilityClassLabel,
212
212
  withRetry
213
- } from "./chunk-OA2EGCUS.js";
213
+ } from "./chunk-LS6SEZVF.js";
214
214
 
215
215
  // src/telemetry.ts
216
216
  import crypto from "crypto";
@@ -7178,7 +7178,7 @@ function readStoredGroundingSources(rawResponse) {
7178
7178
  return result;
7179
7179
  }
7180
7180
  async function backfillInsightsCommand(project, opts) {
7181
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-BGH2MGHB.js");
7181
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-EOUP37XN.js");
7182
7182
  const config = loadConfig();
7183
7183
  const db = createClient(config.database);
7184
7184
  migrate(db);
@@ -12011,6 +12011,9 @@ function resolveDashboardRequirePassword(env, config) {
12011
12011
  function resolveDashboardShowResourceLinks(env, config) {
12012
12012
  return parseBooleanEnv(env.CANONRY_DASHBOARD_SHOW_RESOURCE_LINKS) ?? config.dashboard?.showResourceLinks ?? true;
12013
12013
  }
12014
+ function resolveDashboardShowUpdateNotification(env, config) {
12015
+ return parseBooleanEnv(env.CANONRY_DASHBOARD_SHOW_UPDATE_NOTIFICATION) ?? config.dashboard?.showUpdateNotification ?? true;
12016
+ }
12014
12017
  var API_ADAPTERS = [
12015
12018
  geminiAdapter,
12016
12019
  openaiAdapter,
@@ -12876,6 +12879,7 @@ async function createServer(opts) {
12876
12879
  }
12877
12880
  const dashboardRequirePassword = resolveDashboardRequirePassword(process.env, opts.config);
12878
12881
  const dashboardShowResourceLinks = resolveDashboardShowResourceLinks(process.env, opts.config);
12882
+ const dashboardShowUpdateNotification = resolveDashboardShowUpdateNotification(process.env, opts.config);
12879
12883
  app.log.info(
12880
12884
  { dashboardRequirePassword },
12881
12885
  "Dashboard password gate resolved"
@@ -13625,8 +13629,15 @@ async function createServer(opts) {
13625
13629
  const injectConfig = (html, projectTabsOverride, themeOverride, renderTokenOverride) => {
13626
13630
  const clientConfig = {};
13627
13631
  if (basePath) clientConfig.basePath = basePath;
13632
+ const dashboardConfig = {};
13628
13633
  if (!dashboardShowResourceLinks) {
13629
- clientConfig.dashboard = { showResourceLinks: false };
13634
+ dashboardConfig.showResourceLinks = false;
13635
+ }
13636
+ if (!dashboardShowUpdateNotification) {
13637
+ dashboardConfig.showUpdateNotification = false;
13638
+ }
13639
+ if (Object.keys(dashboardConfig).length > 0) {
13640
+ clientConfig.dashboard = dashboardConfig;
13630
13641
  }
13631
13642
  if (embed.enabled) {
13632
13643
  const embedClient = embedClientConfigForRequest(embed, projectTabsOverride, themeOverride, renderTokenOverride);
package/dist/cli.js CHANGED
@@ -30,7 +30,7 @@ import {
30
30
  showFirstRunNotice,
31
31
  trackCliCommandFinished,
32
32
  trackEvent
33
- } from "./chunk-YLHZQ7HQ.js";
33
+ } from "./chunk-UFPP5CSN.js";
34
34
  import {
35
35
  CliError,
36
36
  EXIT_SYSTEM_ERROR,
@@ -47,7 +47,7 @@ import {
47
47
  saveConfig,
48
48
  saveConfigPatch,
49
49
  usageError
50
- } from "./chunk-IYTHIAAE.js";
50
+ } from "./chunk-D7NJUGNQ.js";
51
51
  import {
52
52
  apiKeys,
53
53
  createClient,
@@ -55,7 +55,7 @@ import {
55
55
  projects,
56
56
  queries,
57
57
  renderReportHtml
58
- } from "./chunk-DJHSOPMU.js";
58
+ } from "./chunk-NR6OGEV3.js";
59
59
  import {
60
60
  AdsDeliverySnapshotStatuses,
61
61
  AdsHistoricalCampaignRollupStatuses,
@@ -101,7 +101,7 @@ import {
101
101
  providerQuotaPolicySchema,
102
102
  resolveProviderInput,
103
103
  winnabilityClassSchema
104
- } from "./chunk-OA2EGCUS.js";
104
+ } from "./chunk-LS6SEZVF.js";
105
105
 
106
106
  // src/cli.ts
107
107
  import { pathToFileURL } from "url";
@@ -3824,6 +3824,16 @@ Usage: ${usage}`, {
3824
3824
  function getClient7() {
3825
3825
  return createApiClient();
3826
3826
  }
3827
+ function rangeParams(opts, extra) {
3828
+ const params = { ...extra };
3829
+ if (opts?.window) params.window = opts.window;
3830
+ if (opts?.startDate) params.startDate = opts.startDate;
3831
+ if (opts?.endDate) params.endDate = opts.endDate;
3832
+ return Object.keys(params).length > 0 ? params : void 0;
3833
+ }
3834
+ function isRangeScoped(opts) {
3835
+ return Boolean(opts?.window || opts?.startDate || opts?.endDate);
3836
+ }
3827
3837
  async function gaConnect(project, opts) {
3828
3838
  if (!opts.propertyId) {
3829
3839
  throw new CliError({
@@ -3925,10 +3935,8 @@ async function gaSync(project, opts) {
3925
3935
  }
3926
3936
  async function gaTraffic(project, opts) {
3927
3937
  const client = getClient7();
3928
- const params = {};
3929
- if (opts?.limit) params.limit = String(opts.limit);
3930
- if (opts?.window) params.window = opts.window;
3931
- const result = await client.gaTraffic(project, Object.keys(params).length > 0 ? params : void 0);
3938
+ const params = rangeParams(opts, opts?.limit ? { limit: String(opts.limit) } : void 0);
3939
+ const result = await client.gaTraffic(project, params);
3932
3940
  if (isMachineFormat(opts?.format)) {
3933
3941
  console.log(JSON.stringify(result, null, 2));
3934
3942
  return;
@@ -3937,7 +3945,7 @@ async function gaTraffic(project, opts) {
3937
3945
  if (!result.lastSyncedAt) {
3938
3946
  console.log('No GA4 traffic data. Run "canonry ga sync <project>" first.');
3939
3947
  } else {
3940
- console.log(`No GA4 traffic data for the selected period.${opts?.window ? ` Try a wider window or omit --window.` : ""}`);
3948
+ console.log(`No GA4 traffic data for the selected period.${isRangeScoped(opts) ? " Try a wider range, or omit --window / --start / --end." : ""}`);
3941
3949
  }
3942
3950
  return;
3943
3951
  }
@@ -4061,16 +4069,16 @@ async function gaMeasurementAnalysis(project, opts) {
4061
4069
  }
4062
4070
  async function gaAiReferralHistory(project, opts) {
4063
4071
  const client = getClient7();
4064
- const result = await client.gaAiReferralHistory(project, opts?.window ? { window: opts.window } : void 0);
4072
+ const result = await client.gaAiReferralHistory(project, rangeParams(opts));
4065
4073
  if (opts?.format === "json") {
4066
4074
  console.log(JSON.stringify(result, null, 2));
4067
4075
  return;
4068
4076
  } else if (opts?.format === "jsonl") {
4069
- emitJsonl(result.map((row) => ({ project, window: opts?.window, ...row })));
4077
+ emitJsonl(result.map((row) => ({ project, window: opts?.window, startDate: opts?.startDate, endDate: opts?.endDate, ...row })));
4070
4078
  return;
4071
4079
  }
4072
4080
  if (result.length === 0) {
4073
- console.log(`No AI referral history.${opts?.window ? " Try a wider window or omit --window." : ' Run "canonry ga sync <project>" first.'}`);
4081
+ console.log(`No AI referral history.${isRangeScoped(opts) ? " Try a wider range, or omit --window / --start / --end." : ' Run "canonry ga sync <project>" first.'}`);
4074
4082
  return;
4075
4083
  }
4076
4084
  const dateWidth = 12;
@@ -4089,16 +4097,16 @@ async function gaAiReferralHistory(project, opts) {
4089
4097
  }
4090
4098
  async function gaAiReferralDaily(project, opts) {
4091
4099
  const client = getClient7();
4092
- const result = await client.gaAiReferralDaily(project, opts?.window ? { window: opts.window } : void 0);
4100
+ const result = await client.gaAiReferralDaily(project, rangeParams(opts));
4093
4101
  if (opts?.format === "json") {
4094
4102
  console.log(JSON.stringify(result, null, 2));
4095
4103
  return;
4096
4104
  } else if (opts?.format === "jsonl") {
4097
- emitJsonl(result.days.map((day) => ({ project, window: opts?.window, ...day })));
4105
+ emitJsonl(result.days.map((day) => ({ project, window: opts?.window, startDate: opts?.startDate, endDate: opts?.endDate, ...day })));
4098
4106
  return;
4099
4107
  }
4100
4108
  if (result.days.length === 0) {
4101
- console.log(`No AI referral sessions.${opts?.window ? " Try a wider window or omit --window." : ' Run "canonry ga sync <project>" first.'}`);
4109
+ console.log(`No AI referral sessions.${isRangeScoped(opts) ? " Try a wider range, or omit --window / --start / --end." : ' Run "canonry ga sync <project>" first.'}`);
4102
4110
  return;
4103
4111
  }
4104
4112
  const dateWidth = 12;
@@ -4119,16 +4127,16 @@ async function gaAiReferralDaily(project, opts) {
4119
4127
  }
4120
4128
  async function gaSocialReferralHistory(project, opts) {
4121
4129
  const client = getClient7();
4122
- const result = await client.gaSocialReferralHistory(project, opts?.window ? { window: opts.window } : void 0);
4130
+ const result = await client.gaSocialReferralHistory(project, rangeParams(opts));
4123
4131
  if (opts?.format === "json") {
4124
4132
  console.log(JSON.stringify(result, null, 2));
4125
4133
  return;
4126
4134
  } else if (opts?.format === "jsonl") {
4127
- emitJsonl(result.map((row) => ({ project, window: opts?.window, ...row })));
4135
+ emitJsonl(result.map((row) => ({ project, window: opts?.window, startDate: opts?.startDate, endDate: opts?.endDate, ...row })));
4128
4136
  return;
4129
4137
  }
4130
4138
  if (result.length === 0) {
4131
- console.log(`No social referral history.${opts?.window ? " Try a wider window or omit --window." : ' Run "canonry ga sync <project>" first.'}`);
4139
+ console.log(`No social referral history.${isRangeScoped(opts) ? " Try a wider range, or omit --window / --start / --end." : ' Run "canonry ga sync <project>" first.'}`);
4132
4140
  return;
4133
4141
  }
4134
4142
  const dateWidth = 12;
@@ -4147,16 +4155,16 @@ async function gaSocialReferralHistory(project, opts) {
4147
4155
  }
4148
4156
  async function gaSessionHistory(project, opts) {
4149
4157
  const client = getClient7();
4150
- const result = await client.gaSessionHistory(project, opts?.window ? { window: opts.window } : void 0);
4158
+ const result = await client.gaSessionHistory(project, rangeParams(opts));
4151
4159
  if (opts?.format === "json") {
4152
4160
  console.log(JSON.stringify(result, null, 2));
4153
4161
  return;
4154
4162
  } else if (opts?.format === "jsonl") {
4155
- emitJsonl(result.map((row) => ({ project, window: opts?.window, ...row })));
4163
+ emitJsonl(result.map((row) => ({ project, window: opts?.window, startDate: opts?.startDate, endDate: opts?.endDate, ...row })));
4156
4164
  return;
4157
4165
  }
4158
4166
  if (result.length === 0) {
4159
- console.log(`No session history.${opts?.window ? " Try a wider window or omit --window." : ' Run "canonry ga sync <project>" first.'}`);
4167
+ console.log(`No session history.${isRangeScoped(opts) ? " Try a wider range, or omit --window / --start / --end." : ' Run "canonry ga sync <project>" first.'}`);
4160
4168
  return;
4161
4169
  }
4162
4170
  const dateWidth = 12;
@@ -4449,6 +4457,19 @@ async function gaAttribution(project, opts) {
4449
4457
  }
4450
4458
 
4451
4459
  // src/cli-commands/ga.ts
4460
+ var RANGE_OPTIONS = {
4461
+ window: stringOption(),
4462
+ start: stringOption(),
4463
+ end: stringOption()
4464
+ };
4465
+ var RANGE_USAGE = "[--window 30d] [--start YYYY-MM-DD] [--end YYYY-MM-DD]";
4466
+ function rangeValues(values) {
4467
+ return {
4468
+ window: getString(values, "window"),
4469
+ startDate: getString(values, "start"),
4470
+ endDate: getString(values, "end")
4471
+ };
4472
+ }
4452
4473
  var GA_CLI_COMMANDS = [
4453
4474
  {
4454
4475
  path: ["ga", "connect"],
@@ -4530,19 +4551,18 @@ var GA_CLI_COMMANDS = [
4530
4551
  },
4531
4552
  {
4532
4553
  path: ["ga", "traffic"],
4533
- usage: "canonry ga traffic <project> [--limit 50] [--window 30d] [--format json]",
4554
+ usage: `canonry ga traffic <project> [--limit 50] ${RANGE_USAGE} [--format json]`,
4534
4555
  options: {
4535
4556
  limit: stringOption(),
4536
- window: stringOption()
4557
+ ...RANGE_OPTIONS
4537
4558
  },
4538
4559
  run: async (input) => {
4539
- const project = requireProject(input, "ga.traffic", "canonry ga traffic <project> [--limit 50] [--window 30d] [--format json]");
4560
+ const project = requireProject(input, "ga.traffic", `canonry ga traffic <project> [--limit 50] ${RANGE_USAGE} [--format json]`);
4540
4561
  const limitStr = getString(input.values, "limit");
4541
4562
  const limit = limitStr ? parseInt(limitStr, 10) : void 0;
4542
- const window = getString(input.values, "window");
4543
4563
  await gaTraffic(project, {
4544
4564
  limit,
4545
- window,
4565
+ ...rangeValues(input.values),
4546
4566
  format: input.format
4547
4567
  });
4548
4568
  }
@@ -4557,56 +4577,48 @@ var GA_CLI_COMMANDS = [
4557
4577
  },
4558
4578
  {
4559
4579
  path: ["ga", "ai-referral-history"],
4560
- usage: "canonry ga ai-referral-history <project> [--window 30d] [--format json]",
4561
- options: {
4562
- window: stringOption()
4563
- },
4580
+ usage: `canonry ga ai-referral-history <project> ${RANGE_USAGE} [--format json]`,
4581
+ options: { ...RANGE_OPTIONS },
4564
4582
  run: async (input) => {
4565
- const project = requireProject(input, "ga.ai-referral-history", "canonry ga ai-referral-history <project> [--window 30d] [--format json]");
4583
+ const project = requireProject(input, "ga.ai-referral-history", `canonry ga ai-referral-history <project> ${RANGE_USAGE} [--format json]`);
4566
4584
  await gaAiReferralHistory(project, {
4567
- window: getString(input.values, "window"),
4585
+ ...rangeValues(input.values),
4568
4586
  format: input.format
4569
4587
  });
4570
4588
  }
4571
4589
  },
4572
4590
  {
4573
4591
  path: ["ga", "ai-referral-daily"],
4574
- usage: "canonry ga ai-referral-daily <project> [--window 30d] [--format json]",
4575
- options: {
4576
- window: stringOption()
4577
- },
4592
+ usage: `canonry ga ai-referral-daily <project> ${RANGE_USAGE} [--format json]`,
4593
+ options: { ...RANGE_OPTIONS },
4578
4594
  run: async (input) => {
4579
- const project = requireProject(input, "ga.ai-referral-daily", "canonry ga ai-referral-daily <project> [--window 30d] [--format json]");
4595
+ const project = requireProject(input, "ga.ai-referral-daily", `canonry ga ai-referral-daily <project> ${RANGE_USAGE} [--format json]`);
4580
4596
  await gaAiReferralDaily(project, {
4581
- window: getString(input.values, "window"),
4597
+ ...rangeValues(input.values),
4582
4598
  format: input.format
4583
4599
  });
4584
4600
  }
4585
4601
  },
4586
4602
  {
4587
4603
  path: ["ga", "social-referral-history"],
4588
- usage: "canonry ga social-referral-history <project> [--window 30d] [--format json]",
4589
- options: {
4590
- window: stringOption()
4591
- },
4604
+ usage: `canonry ga social-referral-history <project> ${RANGE_USAGE} [--format json]`,
4605
+ options: { ...RANGE_OPTIONS },
4592
4606
  run: async (input) => {
4593
- const project = requireProject(input, "ga.social-referral-history", "canonry ga social-referral-history <project> [--window 30d] [--format json]");
4607
+ const project = requireProject(input, "ga.social-referral-history", `canonry ga social-referral-history <project> ${RANGE_USAGE} [--format json]`);
4594
4608
  await gaSocialReferralHistory(project, {
4595
- window: getString(input.values, "window"),
4609
+ ...rangeValues(input.values),
4596
4610
  format: input.format
4597
4611
  });
4598
4612
  }
4599
4613
  },
4600
4614
  {
4601
4615
  path: ["ga", "session-history"],
4602
- usage: "canonry ga session-history <project> [--window 30d] [--format json]",
4603
- options: {
4604
- window: stringOption()
4605
- },
4616
+ usage: `canonry ga session-history <project> ${RANGE_USAGE} [--format json]`,
4617
+ options: { ...RANGE_OPTIONS },
4606
4618
  run: async (input) => {
4607
- const project = requireProject(input, "ga.session-history", "canonry ga session-history <project> [--window 30d] [--format json]");
4619
+ const project = requireProject(input, "ga.session-history", `canonry ga session-history <project> ${RANGE_USAGE} [--format json]`);
4608
4620
  await gaSessionHistory(project, {
4609
- window: getString(input.values, "window"),
4621
+ ...rangeValues(input.values),
4610
4622
  format: input.format
4611
4623
  });
4612
4624
  }
@@ -6378,10 +6390,58 @@ async function googlePerformanceDaily(project, opts) {
6378
6390
  );
6379
6391
  }
6380
6392
  }
6393
+ async function googleTopPages(project, opts) {
6394
+ const client = getClient11();
6395
+ const params = {};
6396
+ if (opts.window) params.window = opts.window;
6397
+ if (opts.startDate) params.startDate = opts.startDate;
6398
+ if (opts.endDate) params.endDate = opts.endDate;
6399
+ if (opts.limit) params.limit = String(opts.limit);
6400
+ const data = await client.gscTopPages(project, Object.keys(params).length > 0 ? params : void 0);
6401
+ if (opts.format === "json") {
6402
+ console.log(JSON.stringify(data, null, 2));
6403
+ return;
6404
+ } else if (opts.format === "jsonl") {
6405
+ emitJsonl(data.rows.map((row) => ({ project, ...row })));
6406
+ return;
6407
+ }
6408
+ if (data.rows.length === 0) {
6409
+ console.log('No GSC page data found in this window. Run "canonry google sync" first.');
6410
+ return;
6411
+ }
6412
+ console.log(`Top pages by clicks (${data.rows.length} page${data.rows.length === 1 ? "" : "s"}):
6413
+ `);
6414
+ const pageWidth = Math.min(60, Math.max(20, ...data.rows.map((row) => row.page.length)));
6415
+ console.log(` ${"PAGE".padEnd(pageWidth)}${"CLICKS".padStart(10)}${"IMPR".padStart(12)}${"CTR".padStart(10)}`);
6416
+ console.log(` ${"\u2500".repeat(pageWidth)}${"\u2500".repeat(10)}${"\u2500".repeat(12)}${"\u2500".repeat(10)}`);
6417
+ for (const row of data.rows) {
6418
+ const page = row.page.length > pageWidth ? row.page.slice(0, pageWidth - 3) + "..." : row.page;
6419
+ console.log(
6420
+ ` ${page.padEnd(pageWidth)}${row.clicks.toLocaleString().padStart(10)}${row.impressions.toLocaleString().padStart(12)}${(row.ctr * 100).toFixed(2).padStart(9)}%`
6421
+ );
6422
+ }
6423
+ console.log();
6424
+ if (data.totals) {
6425
+ const { clicks, impressions, ctr, days } = data.totals;
6426
+ console.log(`Property total (${days} day${days === 1 ? "" : "s"}, source: ${data.totalsSource}):`);
6427
+ console.log(` Clicks: ${clicks.toLocaleString()}`);
6428
+ console.log(` Impressions: ${impressions.toLocaleString()}`);
6429
+ console.log(` CTR: ${(ctr * 100).toFixed(2)}%`);
6430
+ console.log();
6431
+ console.log(" The page rows above are a ranking. They do not add up to this total:");
6432
+ console.log(" Google withholds rare queries and repeats an impression per page.");
6433
+ } else {
6434
+ console.log("Property total: not available for this window.");
6435
+ console.log(' Adding up the page rows would not give it. Run "canonry google sync" to fetch it.');
6436
+ }
6437
+ }
6381
6438
  async function googlePerformance(project, opts) {
6382
6439
  const client = getClient11();
6383
6440
  const params = {};
6384
- if (opts.days) {
6441
+ if (opts.startDate || opts.endDate) {
6442
+ if (opts.startDate) params.startDate = opts.startDate;
6443
+ if (opts.endDate) params.endDate = opts.endDate;
6444
+ } else if (opts.days) {
6385
6445
  const end = /* @__PURE__ */ new Date();
6386
6446
  const start = /* @__PURE__ */ new Date();
6387
6447
  start.setDate(start.getDate() - opts.days);
@@ -6390,19 +6450,36 @@ async function googlePerformance(project, opts) {
6390
6450
  }
6391
6451
  if (opts.keyword) params.query = opts.keyword;
6392
6452
  if (opts.page) params.page = opts.page;
6393
- const rows = await client.gscPerformance(project, Object.keys(params).length > 0 ? params : void 0);
6453
+ if (opts.limit !== void 0) params.limit = String(opts.limit);
6454
+ if (opts.offset !== void 0) params.offset = String(opts.offset);
6455
+ if (opts.orderBy) params.orderBy = opts.orderBy;
6456
+ const data = await client.gscPerformance(project, Object.keys(params).length > 0 ? params : void 0);
6457
+ const { rows, totalMatching, truncated, latestAvailableDate } = data;
6394
6458
  if (opts.format === "json") {
6395
- console.log(JSON.stringify(rows, null, 2));
6459
+ console.log(JSON.stringify(data, null, 2));
6396
6460
  return;
6397
6461
  } else if (opts.format === "jsonl") {
6398
6462
  emitJsonl(rows.map((row) => ({ project, ...row })));
6399
6463
  return;
6400
6464
  }
6401
6465
  if (rows.length === 0) {
6466
+ const requestedEnd = params.endDate;
6467
+ if (totalMatching > 0 && opts.offset !== void 0 && opts.offset >= totalMatching) {
6468
+ console.log(
6469
+ `Offset ${opts.offset} is past the end of the result set (${totalMatching.toLocaleString()} matching rows). Lower --offset to page through them.`
6470
+ );
6471
+ return;
6472
+ }
6473
+ if (latestAvailableDate && requestedEnd && requestedEnd > latestAvailableDate) {
6474
+ console.log(
6475
+ `No GSC data through ${requestedEnd}. GSC reporting lag: the latest date this project holds is ${latestAvailableDate}. Syncing again will not backfill dates Google has not reported yet.`
6476
+ );
6477
+ return;
6478
+ }
6402
6479
  console.log('No GSC data found. Run "canonry google sync" first.');
6403
6480
  return;
6404
6481
  }
6405
- console.log(`GSC performance data (${rows.length} rows):
6482
+ console.log(`GSC performance data (${rows.length} of ${totalMatching.toLocaleString()} matching rows):
6406
6483
  `);
6407
6484
  console.log(` ${"DATE".padEnd(12)}${"QUERY".padEnd(30)}${"CLICKS".padEnd(8)}${"IMPR".padEnd(8)}${"CTR".padEnd(8)}${"POS".padEnd(6)}`);
6408
6485
  console.log(` ${"\u2500".repeat(12)}${"\u2500".repeat(30)}${"\u2500".repeat(8)}${"\u2500".repeat(8)}${"\u2500".repeat(8)}${"\u2500".repeat(6)}`);
@@ -6414,7 +6491,13 @@ async function googlePerformance(project, opts) {
6414
6491
  }
6415
6492
  if (rows.length > 50) {
6416
6493
  console.log(`
6417
- ... and ${rows.length - 50} more rows (use --format json for full output)`);
6494
+ ... and ${rows.length - 50} more rows on this page (use --format json for full output)`);
6495
+ }
6496
+ if (truncated) {
6497
+ console.log("\n This is one page. Use --limit / --offset to page through the rest, --order-by to change the ranking.");
6498
+ }
6499
+ if (latestAvailableDate) {
6500
+ console.log(` Latest date held for this project: ${latestAvailableDate}`);
6418
6501
  }
6419
6502
  }
6420
6503
  async function googleInspect(project, url, format) {
@@ -6937,6 +7020,7 @@ async function googleDeindexed(project, format) {
6937
7020
  }
6938
7021
 
6939
7022
  // src/cli-commands/google.ts
7023
+ var GOOGLE_PERFORMANCE_USAGE = "canonry google performance <project> [--days <n> | --start <YYYY-MM-DD> --end <YYYY-MM-DD>] [--keyword <kw>] [--page <url>] [--limit <n>] [--offset <n>] [--order-by clicks|impressions|date] [--format json]";
6940
7024
  var GOOGLE_CLI_COMMANDS = [
6941
7025
  {
6942
7026
  path: ["google", "connect"],
@@ -7076,22 +7160,53 @@ var GOOGLE_CLI_COMMANDS = [
7076
7160
  },
7077
7161
  {
7078
7162
  path: ["google", "performance"],
7079
- usage: "canonry google performance <project> [--days <n>] [--keyword <kw>] [--page <url>] [--format json]",
7163
+ usage: GOOGLE_PERFORMANCE_USAGE,
7080
7164
  options: {
7081
7165
  days: stringOption(),
7166
+ start: stringOption(),
7167
+ end: stringOption(),
7082
7168
  keyword: stringOption(),
7083
- page: stringOption()
7169
+ page: stringOption(),
7170
+ limit: stringOption(),
7171
+ offset: stringOption(),
7172
+ "order-by": stringOption()
7084
7173
  },
7085
7174
  run: async (input) => {
7086
- const project = requireProject(input, "google.performance", "canonry google performance <project> [--days <n>] [--keyword <kw>] [--page <url>] [--format json]");
7175
+ const project = requireProject(input, "google.performance", GOOGLE_PERFORMANCE_USAGE);
7176
+ const startDate = getString(input.values, "start");
7177
+ const endDate = getString(input.values, "end");
7178
+ const days = parseIntegerOption(input, "days", {
7179
+ command: "google.performance",
7180
+ usage: GOOGLE_PERFORMANCE_USAGE,
7181
+ message: "--days must be an integer"
7182
+ });
7183
+ if (days !== void 0 && (startDate || endDate)) {
7184
+ throw usageError(
7185
+ `Error: --days cannot be combined with --start/--end. Pass one window.
7186
+ Usage: ${GOOGLE_PERFORMANCE_USAGE}`,
7187
+ {
7188
+ message: "--days cannot be combined with --start/--end",
7189
+ details: { command: "google.performance", usage: GOOGLE_PERFORMANCE_USAGE }
7190
+ }
7191
+ );
7192
+ }
7087
7193
  await googlePerformance(project, {
7088
- days: parseIntegerOption(input, "days", {
7089
- command: "google.performance",
7090
- usage: "canonry google performance <project> [--days <n>] [--keyword <kw>] [--page <url>] [--format json]",
7091
- message: "--days must be an integer"
7092
- }),
7194
+ days,
7195
+ startDate,
7196
+ endDate,
7093
7197
  keyword: getString(input.values, "keyword"),
7094
7198
  page: getString(input.values, "page"),
7199
+ limit: parseIntegerOption(input, "limit", {
7200
+ command: "google.performance",
7201
+ usage: GOOGLE_PERFORMANCE_USAGE,
7202
+ message: "--limit must be an integer"
7203
+ }),
7204
+ offset: parseIntegerOption(input, "offset", {
7205
+ command: "google.performance",
7206
+ usage: GOOGLE_PERFORMANCE_USAGE,
7207
+ message: "--offset must be an integer"
7208
+ }),
7209
+ orderBy: getString(input.values, "order-by"),
7095
7210
  format: input.format
7096
7211
  });
7097
7212
  }
@@ -7114,6 +7229,31 @@ var GOOGLE_CLI_COMMANDS = [
7114
7229
  });
7115
7230
  }
7116
7231
  },
7232
+ {
7233
+ path: ["google", "top-pages"],
7234
+ usage: "canonry google top-pages <project> [--window 7d|30d|90d|all] [--start <YYYY-MM-DD>] [--end <YYYY-MM-DD>] [--limit <n>] [--format json]",
7235
+ options: {
7236
+ window: stringOption(),
7237
+ start: stringOption(),
7238
+ end: stringOption(),
7239
+ limit: stringOption()
7240
+ },
7241
+ run: async (input) => {
7242
+ const usage = "canonry google top-pages <project> [--window 7d|30d|90d|all] [--start <YYYY-MM-DD>] [--end <YYYY-MM-DD>] [--limit <n>] [--format json]";
7243
+ const project = requireProject(input, "google.top-pages", usage);
7244
+ await googleTopPages(project, {
7245
+ window: getString(input.values, "window"),
7246
+ startDate: getString(input.values, "start"),
7247
+ endDate: getString(input.values, "end"),
7248
+ limit: parseIntegerOption(input, "limit", {
7249
+ command: "google.top-pages",
7250
+ usage,
7251
+ message: "--limit must be an integer"
7252
+ }),
7253
+ format: input.format
7254
+ });
7255
+ }
7256
+ },
7117
7257
  {
7118
7258
  path: ["google", "inspect"],
7119
7259
  usage: "canonry google inspect <project> <url> [--format json]",
package/dist/index.d.ts CHANGED
@@ -196,6 +196,12 @@ interface DashboardConfigEntry {
196
196
  * quieter branded UI.
197
197
  */
198
198
  showResourceLinks?: boolean;
199
+ /**
200
+ * Whether the dashboard sidebar shows the available-version notification.
201
+ * Defaults to true. Disable without turning off the underlying update check
202
+ * or CLI update notice.
203
+ */
204
+ showUpdateNotification?: boolean;
199
205
  }
200
206
  /**
201
207
  * Google Places API config — supplemental rendered-listing data for GBP
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-YLHZQ7HQ.js";
3
+ } from "./chunk-UFPP5CSN.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-IYTHIAAE.js";
7
- import "./chunk-DJHSOPMU.js";
8
- import "./chunk-OA2EGCUS.js";
6
+ } from "./chunk-D7NJUGNQ.js";
7
+ import "./chunk-NR6OGEV3.js";
8
+ import "./chunk-LS6SEZVF.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-DJHSOPMU.js";
4
- import "./chunk-OA2EGCUS.js";
3
+ } from "./chunk-NR6OGEV3.js";
4
+ import "./chunk-LS6SEZVF.js";
5
5
  export {
6
6
  IntelligenceService
7
7
  };
package/dist/mcp.js CHANGED
@@ -3,10 +3,10 @@ import {
3
3
  PACKAGE_VERSION,
4
4
  canonryMcpTools,
5
5
  createApiClient
6
- } from "./chunk-IYTHIAAE.js";
6
+ } from "./chunk-D7NJUGNQ.js";
7
7
  import {
8
8
  isReadOnlyKey
9
- } from "./chunk-OA2EGCUS.js";
9
+ } from "./chunk-LS6SEZVF.js";
10
10
 
11
11
  // src/mcp/cli.ts
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "4.139.1",
3
+ "version": "4.142.0",
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",
@@ -66,26 +66,26 @@
66
66
  "tsup": "^8.5.1",
67
67
  "tsx": "^4.19.0",
68
68
  "@ainyc/canonry-api-client": "0.0.0",
69
- "@ainyc/canonry-api-routes": "0.0.0",
70
69
  "@ainyc/canonry-config": "0.0.0",
70
+ "@ainyc/canonry-api-routes": "0.0.0",
71
71
  "@ainyc/canonry-contracts": "0.0.0",
72
72
  "@ainyc/canonry-db": "0.0.0",
73
73
  "@ainyc/canonry-integration-bing": "0.0.0",
74
- "@ainyc/canonry-integration-openai-ads": "0.0.0",
75
- "@ainyc/canonry-integration-commoncrawl": "0.0.0",
76
74
  "@ainyc/canonry-integration-cloud-run": "0.0.0",
75
+ "@ainyc/canonry-integration-commoncrawl": "0.0.0",
76
+ "@ainyc/canonry-integration-openai-ads": "0.0.0",
77
77
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
78
78
  "@ainyc/canonry-integration-google": "0.0.0",
79
+ "@ainyc/canonry-integration-google-places": "0.0.0",
79
80
  "@ainyc/canonry-integration-traffic": "0.0.0",
80
81
  "@ainyc/canonry-integration-wordpress": "0.0.0",
81
82
  "@ainyc/canonry-intelligence": "0.0.0",
82
- "@ainyc/canonry-integration-google-places": "0.0.0",
83
- "@ainyc/canonry-provider-cdp": "0.0.0",
84
83
  "@ainyc/canonry-provider-claude": "0.0.0",
85
84
  "@ainyc/canonry-provider-gemini": "0.0.0",
85
+ "@ainyc/canonry-provider-cdp": "0.0.0",
86
86
  "@ainyc/canonry-provider-local": "0.0.0",
87
- "@ainyc/canonry-provider-perplexity": "0.0.0",
88
- "@ainyc/canonry-provider-openai": "0.0.0"
87
+ "@ainyc/canonry-provider-openai": "0.0.0",
88
+ "@ainyc/canonry-provider-perplexity": "0.0.0"
89
89
  },
90
90
  "scripts": {
91
91
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",