@canonry/canonry 7.32.0 → 7.44.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 (43) hide show
  1. package/assets/agent-workspace/skills/aero/references/agent-operations.md +9 -0
  2. package/assets/agent-workspace/skills/aero/references/site-health.md +12 -0
  3. package/assets/agent-workspace/skills/canonry/SKILL.md +9 -0
  4. package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +24 -0
  5. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +207 -4
  6. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +14 -2
  7. package/assets/assets/{AuditHistoryPanel-cQyVA85P.js → AuditHistoryPanel-CvANw3ds.js} +1 -1
  8. package/assets/assets/{BacklinksPage-UGZ_9N7F.js → BacklinksPage-C6x9ELjk.js} +1 -1
  9. package/assets/assets/{HistoryPage-UJVFZ5LK.js → HistoryPage-r36HX1cC.js} +1 -1
  10. package/assets/assets/{MeasurementPropertyPage-ChpOxQfx.js → MeasurementPropertyPage-ComoCzR4.js} +3 -3
  11. package/assets/assets/ProjectPage-CIeU-buk.js +8 -0
  12. package/assets/assets/RunsPage-DI6eJATZ.js +1 -0
  13. package/assets/assets/{SettingsPage-DeKxjuTr.js → SettingsPage-Bw7GdXxs.js} +1 -1
  14. package/assets/assets/{SiteHealthSection-COeVsgop.js → SiteHealthSection-C-JaNKpS.js} +3 -3
  15. package/assets/assets/{TrafficPage-DmMmeJKK.js → TrafficPage-Cnsp276v.js} +1 -1
  16. package/assets/assets/{TrafficSourceDetailPage-CUehseZe.js → TrafficSourceDetailPage-CBIaJk7e.js} +1 -1
  17. package/assets/assets/{extract-error-message-BsxdS7Po.js → extract-error-message-C_Owa0n9.js} +1 -1
  18. package/assets/assets/index-ZS9sSTMS.js +86 -0
  19. package/assets/assets/{react-sigma_core.esm.min--MbbBWzJ.js → react-sigma_core.esm.min-ugyh-ooq.js} +1 -1
  20. package/assets/assets/{v2-overview-adapter-D-IScpdp.js → v2-overview-adapter-BLSrVQ7K.js} +1 -1
  21. package/assets/index.html +1 -1
  22. package/dist/chunk-3JVF3ULD.js +550 -0
  23. package/dist/chunk-45Z5RJZW.js +562 -0
  24. package/dist/{chunk-QFXD7GR5.js → chunk-5LY7EG4U.js} +238 -20
  25. package/dist/chunk-EJRV7RBZ.js +23066 -0
  26. package/dist/{chunk-WRHA54BN.js → chunk-G47XJVAS.js} +5885 -3900
  27. package/dist/{chunk-42XUY243.js → chunk-GZ2KIONF.js} +11507 -10391
  28. package/dist/chunk-HGMYEDAQ.js +79 -0
  29. package/dist/{chunk-2C6NWFN4.js → chunk-JVBI6VBT.js} +2 -2
  30. package/dist/{chunk-ZIETXJYU.js → chunk-MPWBFKGK.js} +201 -104
  31. package/dist/{chunk-K5EGSSXC.js → chunk-QQYQDTFZ.js} +142 -47
  32. package/dist/{chunk-FW4GX2LO.js → chunk-SW54HXSZ.js} +3 -3
  33. package/dist/cli-catalog-PNJEQV5H.js +15 -0
  34. package/dist/cli-commands-4UT2CRXX.js +15 -0
  35. package/dist/cli.js +136 -22525
  36. package/dist/{demo-server-G5OK7SWE.js → demo-server-WRHR25HF.js} +5 -4
  37. package/dist/index.js +7 -6
  38. package/dist/{intelligence-service-3ZUVTIO7.js → intelligence-service-QXXRUB2G.js} +2 -2
  39. package/dist/mcp.js +30 -15
  40. package/package.json +10 -10
  41. package/assets/assets/ProjectPage-CvxuPSiC.js +0 -8
  42. package/assets/assets/RunsPage-C6U_YcDY.js +0 -1
  43. package/assets/assets/index-ahEr6Oc-.js +0 -86
@@ -1,11 +1,14 @@
1
+ import {
2
+ PACKAGE_VERSION,
3
+ canonryMcpTools,
4
+ toolOperationAccess
5
+ } from "./chunk-MPWBFKGK.js";
1
6
  import {
2
7
  CliError,
3
8
  EXIT_SYSTEM_ERROR,
4
9
  EXIT_USER_ERROR,
5
- PACKAGE_VERSION,
6
- canonryMcpTools,
7
10
  isMachineFormat
8
- } from "./chunk-ZIETXJYU.js";
11
+ } from "./chunk-HGMYEDAQ.js";
9
12
  import {
10
13
  AGENT_PROVIDER_IDS,
11
14
  CANONRY_NPM_PACKAGE_URL,
@@ -13,8 +16,11 @@ import {
13
16
  DEFAULT_VIEWER_RESEARCH_DAILY_RUN_LIMIT,
14
17
  OPERATIONAL_LOG_FIELDS_HEADER,
15
18
  OPERATIONAL_LOG_OPT_IN_CONTEXT_FIELDS,
19
+ OPERATION_ACCESS_HEADER,
20
+ OperationAccesses,
16
21
  RESEARCH_RUN_SCOPE,
17
22
  SKILL_MANIFEST_FILENAME,
23
+ STORED_ONLY_HEADER,
18
24
  SkillsClients,
19
25
  USAGE_TELEMETRY_HEADERS,
20
26
  agentPluginClientLabel,
@@ -31,8 +37,10 @@ import {
31
37
  isRetiredAgentProviderId,
32
38
  isSensitiveDiagnosticQueryKey,
33
39
  isStrictSemver,
40
+ matchOperationTemplate,
34
41
  normalizeAgentSlug,
35
42
  normalizeTelemetryStatus,
43
+ operationAccess,
36
44
  operationalLogListReadSchema,
37
45
  providerBatchConfigSchema,
38
46
  providerPricingSchema,
@@ -49,7 +57,7 @@ import {
49
57
  skillsClientSchema,
50
58
  upgradeCaveatFor,
51
59
  upgradeCommandFor
52
- } from "./chunk-42XUY243.js";
60
+ } from "./chunk-GZ2KIONF.js";
53
61
 
54
62
  // src/config.ts
55
63
  import fs from "fs";
@@ -1505,6 +1513,22 @@ var getApiV1ProjectsByName = (options) => {
1505
1513
  ...options
1506
1514
  });
1507
1515
  };
1516
+ var patchApiV1ProjectsByName = (options) => {
1517
+ return (options.client ?? client).patch({
1518
+ security: [
1519
+ {
1520
+ scheme: "bearer",
1521
+ type: "http"
1522
+ }
1523
+ ],
1524
+ url: "/api/v1/projects/{name}",
1525
+ ...options,
1526
+ headers: {
1527
+ "Content-Type": "application/json",
1528
+ ...options.headers
1529
+ }
1530
+ });
1531
+ };
1508
1532
  var putApiV1ProjectsByName = (options) => {
1509
1533
  return (options.client ?? client).put({
1510
1534
  security: [
@@ -2949,6 +2973,34 @@ var getApiV1ProjectsByNameAnalyticsCompetitors = (options) => {
2949
2973
  ...options
2950
2974
  });
2951
2975
  };
2976
+ var getApiV1ProjectsByNameCompetitorSuggestions = (options) => {
2977
+ return (options.client ?? client).get({
2978
+ security: [
2979
+ {
2980
+ scheme: "bearer",
2981
+ type: "http"
2982
+ }
2983
+ ],
2984
+ url: "/api/v1/projects/{name}/competitor-suggestions",
2985
+ ...options
2986
+ });
2987
+ };
2988
+ var postApiV1ProjectsByNameCompetitorChangesPreview = (options) => {
2989
+ return (options.client ?? client).post({
2990
+ security: [
2991
+ {
2992
+ scheme: "bearer",
2993
+ type: "http"
2994
+ }
2995
+ ],
2996
+ url: "/api/v1/projects/{name}/competitor-changes/preview",
2997
+ ...options,
2998
+ headers: {
2999
+ "Content-Type": "application/json",
3000
+ ...options.headers
3001
+ }
3002
+ });
3003
+ };
2952
3004
  var getApiV1ProjectsByNameRuns = (options) => {
2953
3005
  return (options.client ?? client).get({
2954
3006
  security: [
@@ -3057,6 +3109,22 @@ var postApiV1Apply = (options) => {
3057
3109
  }
3058
3110
  });
3059
3111
  };
3112
+ var postApiV1ApplyPreview = (options) => {
3113
+ return (options.client ?? client).post({
3114
+ security: [
3115
+ {
3116
+ scheme: "bearer",
3117
+ type: "http"
3118
+ }
3119
+ ],
3120
+ url: "/api/v1/apply/preview",
3121
+ ...options,
3122
+ headers: {
3123
+ "Content-Type": "application/json",
3124
+ ...options.headers
3125
+ }
3126
+ });
3127
+ };
3060
3128
  var getApiV1ProjectsByNameHistory = (options) => {
3061
3129
  return (options.client ?? client).get({
3062
3130
  security: [
@@ -5245,6 +5313,18 @@ var getApiV1ProjectsByNameGaProperties = (options) => {
5245
5313
  ...options
5246
5314
  });
5247
5315
  };
5316
+ var getApiV1ProjectsByNameGaAcquisition = (options) => {
5317
+ return (options.client ?? client).get({
5318
+ security: [
5319
+ {
5320
+ scheme: "bearer",
5321
+ type: "http"
5322
+ }
5323
+ ],
5324
+ url: "/api/v1/projects/{name}/ga/acquisition",
5325
+ ...options
5326
+ });
5327
+ };
5248
5328
  var getApiV1ProjectsByNameGaMeasurementAnalysis = (options) => {
5249
5329
  return (options.client ?? client).get({
5250
5330
  security: [
@@ -5629,6 +5709,18 @@ var getApiV1Doctor = (options) => {
5629
5709
  ...options
5630
5710
  });
5631
5711
  };
5712
+ var getApiV1Status = (options) => {
5713
+ return (options?.client ?? client).get({
5714
+ security: [
5715
+ {
5716
+ scheme: "bearer",
5717
+ type: "http"
5718
+ }
5719
+ ],
5720
+ url: "/api/v1/status",
5721
+ ...options
5722
+ });
5723
+ };
5632
5724
  var getApiV1ProjectsByNameDoctor = (options) => {
5633
5725
  return (options.client ?? client).get({
5634
5726
  security: [
@@ -5977,6 +6069,18 @@ var getApiV1ProjectsByNameTrafficReferralAssessment = (options) => {
5977
6069
  ...options
5978
6070
  });
5979
6071
  };
6072
+ var getApiV1ProjectsByNameTrafficSamples = (options) => {
6073
+ return (options.client ?? client).get({
6074
+ security: [
6075
+ {
6076
+ scheme: "bearer",
6077
+ type: "http"
6078
+ }
6079
+ ],
6080
+ url: "/api/v1/projects/{name}/traffic/samples",
6081
+ ...options
6082
+ });
6083
+ };
5980
6084
  var getApiV1ProjectsByNameTrafficEvents = (options) => {
5981
6085
  return (options.client ?? client).get({
5982
6086
  security: [
@@ -6153,6 +6257,30 @@ var getApiV1ProjectsByNameTechnicalAeoChanges = (options) => {
6153
6257
  ...options
6154
6258
  });
6155
6259
  };
6260
+ var getApiV1ProjectsByNameTechnicalAeoIssues = (options) => {
6261
+ return (options.client ?? client).get({
6262
+ security: [
6263
+ {
6264
+ scheme: "bearer",
6265
+ type: "http"
6266
+ }
6267
+ ],
6268
+ url: "/api/v1/projects/{name}/technical-aeo/issues",
6269
+ ...options
6270
+ });
6271
+ };
6272
+ var getApiV1ProjectsByNameTechnicalAeoIssuesByIssueIdPages = (options) => {
6273
+ return (options.client ?? client).get({
6274
+ security: [
6275
+ {
6276
+ scheme: "bearer",
6277
+ type: "http"
6278
+ }
6279
+ ],
6280
+ url: "/api/v1/projects/{name}/technical-aeo/issues/{issueId}/pages",
6281
+ ...options
6282
+ });
6283
+ };
6156
6284
  var getApiV1ProjectsByNameTechnicalAeoCrawlPagesAudit = (options) => {
6157
6285
  return (options.client ?? client).get({
6158
6286
  security: [
@@ -6477,6 +6605,30 @@ function runWithUsageTags(client2, tags, fn) {
6477
6605
  if (!(client2 instanceof ApiClient)) return fn();
6478
6606
  return usageTagStore.run(tags, fn);
6479
6607
  }
6608
+ function classifyStoredOnlyRequest(method, url) {
6609
+ const operation = matchOperationTemplate(method, url);
6610
+ const separator = operation.indexOf(" ");
6611
+ return {
6612
+ operation,
6613
+ access: operationAccess(operation.slice(0, separator), operation.slice(separator + 1), OperationAccesses.live)
6614
+ };
6615
+ }
6616
+ function storedOnlyRequestHeaders(method, url) {
6617
+ if (!["1", "true"].includes((process.env.CANONRY_STORED_ONLY ?? "").toLowerCase())) return {};
6618
+ const { operation, access } = classifyStoredOnlyRequest(method, url);
6619
+ if (access !== OperationAccesses.stored) {
6620
+ throw new CliError({
6621
+ code: "STORED_ONLY_REFUSED",
6622
+ message: `Stored-only mode refuses ${access} operation ${operation}`,
6623
+ details: { operation, access },
6624
+ exitCode: EXIT_USER_ERROR
6625
+ });
6626
+ }
6627
+ return { [STORED_ONLY_HEADER]: "1" };
6628
+ }
6629
+ function storedOnlyProbeHeaders() {
6630
+ return ["1", "true"].includes((process.env.CANONRY_STORED_ONLY ?? "").toLowerCase()) ? { [STORED_ONLY_HEADER]: "1" } : {};
6631
+ }
6480
6632
  var SAFE_CLIENT_NAME = /^\w[\w./-]{0,127}$/;
6481
6633
  var SAFE_ACTOR_SESSION = /^\w[\w.:-]{0,127}$/;
6482
6634
  function safeClientName(value) {
@@ -6548,6 +6700,7 @@ var ApiClient = class {
6548
6700
  };
6549
6701
  this.heyClient = createClient2({ baseUrl: this.originUrl, apiKey: this.apiKey, headers: this.requestHeaders });
6550
6702
  this.heyClient.interceptors.request.use((request) => {
6703
+ for (const [key, value] of Object.entries(storedOnlyRequestHeaders(request.method, request.url.toString()))) request.headers.set(key, value);
6551
6704
  const tags = usageTagStore.getStore();
6552
6705
  if (tags?.mcpTool) request.headers.set(USAGE_TELEMETRY_HEADERS.mcpTool, tags.mcpTool);
6553
6706
  if (tags?.mcpCall) request.headers.set(USAGE_TELEMETRY_HEADERS.mcpCall, tags.mcpCall);
@@ -6569,6 +6722,7 @@ var ApiClient = class {
6569
6722
  try {
6570
6723
  const origin = new URL(this.originUrl).origin;
6571
6724
  const res = await fetch(`${origin}/health`, {
6725
+ headers: storedOnlyProbeHeaders(),
6572
6726
  signal: AbortSignal.timeout(2e3)
6573
6727
  });
6574
6728
  if (res.ok) {
@@ -6626,7 +6780,7 @@ var ApiClient = class {
6626
6780
  if (traceEnabled) {
6627
6781
  const durMs = Date.now() - traceStart;
6628
6782
  process.stderr.write(
6629
- `[trace] ${result.request.method} ${redactRequestTarget(result.request.url)} \u2192 ${result.response.status} (${durMs}ms)
6783
+ `[trace] ${result.request.method} ${redactRequestTarget(result.request.url)} \u2192 ${result.response.status} (${durMs}ms) access=${result.response.headers.get(OPERATION_ACCESS_HEADER) ?? "unknown"}
6630
6784
  `
6631
6785
  );
6632
6786
  }
@@ -6729,10 +6883,13 @@ var ApiClient = class {
6729
6883
  * and releases the response when done.
6730
6884
  */
6731
6885
  async streamPost(path3, body, signal) {
6886
+ const candidateUrl = `${this.originUrl}/api/v1${path3}`;
6887
+ const storedOnlyHeaders = storedOnlyRequestHeaders("POST", candidateUrl);
6732
6888
  await this.probeBasePath();
6733
6889
  const url = `${this.originUrl}/api/v1${path3}`;
6734
6890
  const headers = {
6735
6891
  ...this.requestHeaders,
6892
+ ...storedOnlyHeaders,
6736
6893
  "Content-Type": "application/json",
6737
6894
  Accept: "text/event-stream"
6738
6895
  };
@@ -6772,6 +6929,11 @@ var ApiClient = class {
6772
6929
  () => putApiV1ProjectsByName({ client: this.heyClient, path: { name }, body })
6773
6930
  );
6774
6931
  }
6932
+ async patchProject(name, body) {
6933
+ return this.invoke(
6934
+ () => patchApiV1ProjectsByName({ client: this.heyClient, path: { name }, body })
6935
+ );
6936
+ }
6775
6937
  async listProjects() {
6776
6938
  return this.invoke(() => getApiV1Projects({ client: this.heyClient }));
6777
6939
  }
@@ -6786,19 +6948,26 @@ var ApiClient = class {
6786
6948
  () => getApiV1ProjectsByNameDeletePreview({ client: this.heyClient, path: { name } })
6787
6949
  );
6788
6950
  }
6789
- async getStatus(project) {
6951
+ async getStatus(opts) {
6790
6952
  return this.invoke(
6791
- () => getApiV1ProjectsByName({ client: this.heyClient, path: { name: project } })
6953
+ () => getApiV1Status({ client: this.heyClient, query: { project: opts?.project, problems: opts?.problems } })
6792
6954
  );
6793
6955
  }
6794
- async getExport(project) {
6956
+ async getExport(project, opts) {
6795
6957
  return this.invoke(
6796
- () => getApiV1ProjectsByNameExport({ client: this.heyClient, path: { name: project } })
6958
+ () => getApiV1ProjectsByNameExport({
6959
+ client: this.heyClient,
6960
+ path: { name: project },
6961
+ query: { includeSecrets: opts?.includeSecrets }
6962
+ })
6797
6963
  );
6798
6964
  }
6799
6965
  async apply(config) {
6800
6966
  return this.invoke(() => postApiV1Apply({ client: this.heyClient, body: config }));
6801
6967
  }
6968
+ async previewApply(config) {
6969
+ return this.invoke(() => postApiV1ApplyPreview({ client: this.heyClient, body: config }));
6970
+ }
6802
6971
  async getProjectOverview(project, opts) {
6803
6972
  return this.invoke(
6804
6973
  () => getApiV1ProjectsByNameOverview({
@@ -6834,10 +7003,11 @@ var ApiClient = class {
6834
7003
  if (opts.until) params.set("until", opts.until);
6835
7004
  if (opts.includeProbes) params.set("includeProbes", "true");
6836
7005
  const url = `${this.originUrl}/api/v1/projects/${encodeURIComponent(project)}/results/export?${params.toString()}`;
7006
+ const storedOnlyHeaders = storedOnlyRequestHeaders("GET", url);
6837
7007
  let res;
6838
7008
  try {
6839
7009
  res = await fetch(url, {
6840
- headers: { ...this.requestHeaders }
7010
+ headers: { ...this.requestHeaders, ...storedOnlyHeaders }
6841
7011
  });
6842
7012
  } catch (err) {
6843
7013
  const message = describeError(err);
@@ -6941,13 +7111,27 @@ var ApiClient = class {
6941
7111
  () => getApiV1ProjectsByNameCompetitors({ client: this.heyClient, path: { name: project } })
6942
7112
  );
6943
7113
  }
7114
+ async suggestCompetitors(project, query = {}) {
7115
+ return this.invoke(
7116
+ () => getApiV1ProjectsByNameCompetitorSuggestions({
7117
+ client: this.heyClient,
7118
+ path: { name: project },
7119
+ query
7120
+ })
7121
+ );
7122
+ }
7123
+ async previewCompetitorChange(project, request) {
7124
+ return this.invoke(
7125
+ () => postApiV1ProjectsByNameCompetitorChangesPreview({ client: this.heyClient, path: { name: project }, body: request })
7126
+ );
7127
+ }
6944
7128
  /** Each entry is a domain or `{ domain, aliases }`; stated aliases are added to that competitor's list. */
6945
- async appendCompetitors(project, competitors) {
7129
+ async appendCompetitors(project, competitors, options) {
6946
7130
  return this.invoke(
6947
7131
  () => postApiV1ProjectsByNameCompetitors({
6948
7132
  client: this.heyClient,
6949
7133
  path: { name: project },
6950
- body: { competitors }
7134
+ body: { competitors, ...options?.acknowledgeBulkAdd !== void 0 ? { acknowledgeBulkAdd: options.acknowledgeBulkAdd } : {} }
6951
7135
  })
6952
7136
  );
6953
7137
  }
@@ -7519,13 +7703,13 @@ var ApiClient = class {
7519
7703
  })
7520
7704
  );
7521
7705
  }
7522
- async listRuns(project, limit, kind, status, excludeKind, excludeKinds) {
7706
+ async listRuns(project, limit, kind, status, excludeKind, excludeKinds, since) {
7523
7707
  return this.invoke(
7524
7708
  () => getApiV1ProjectsByNameRuns({
7525
7709
  client: this.heyClient,
7526
7710
  path: { name: project },
7527
7711
  // Filters arrive as free CLI strings; the server validates them against the enums.
7528
- query: { limit, kind, status, excludeKind, excludeKinds }
7712
+ query: { limit, kind, status, excludeKind, excludeKinds, since }
7529
7713
  })
7530
7714
  );
7531
7715
  }
@@ -7641,6 +7825,7 @@ var ApiClient = class {
7641
7825
  * never run the CLI about a newer release.
7642
7826
  */
7643
7827
  async getServerUpdateAvailable() {
7828
+ if (storedOnlyProbeHeaders()[STORED_ONLY_HEADER]) return null;
7644
7829
  try {
7645
7830
  await this.probeBasePath();
7646
7831
  const res = await fetch(`${this.originUrl}/health`, { signal: AbortSignal.timeout(2e3) });
@@ -8653,6 +8838,9 @@ var ApiClient = class {
8653
8838
  () => getApiV1ProjectsByNameGaProperties({ client: this.heyClient, path: { name: project } })
8654
8839
  );
8655
8840
  }
8841
+ async gaAcquisition(project, query) {
8842
+ return this.invoke(() => getApiV1ProjectsByNameGaAcquisition({ client: this.heyClient, path: { name: project }, query }));
8843
+ }
8656
8844
  async gaMeasurementAnalysis(project, params) {
8657
8845
  return this.invoke(
8658
8846
  () => getApiV1ProjectsByNameGaMeasurementAnalysis({
@@ -8852,6 +9040,9 @@ var ApiClient = class {
8852
9040
  query
8853
9041
  }));
8854
9042
  }
9043
+ async trafficListSamples(project, query) {
9044
+ return this.invoke(() => getApiV1ProjectsByNameTrafficSamples({ client: this.heyClient, path: { name: project }, query }));
9045
+ }
8855
9046
  async trafficListEvents(project, params) {
8856
9047
  return this.invoke(
8857
9048
  () => getApiV1ProjectsByNameTrafficEvents({
@@ -9048,6 +9239,26 @@ var ApiClient = class {
9048
9239
  })
9049
9240
  );
9050
9241
  }
9242
+ /** Stored findings across audited pages, including score-independent duplicate groups. */
9243
+ async getSiteAuditIssues(project, opts) {
9244
+ return this.invoke(
9245
+ () => getApiV1ProjectsByNameTechnicalAeoIssues({
9246
+ client: this.heyClient,
9247
+ path: { name: project },
9248
+ query: { runId: opts?.runId, cursor: opts?.cursor, limit: opts?.limit }
9249
+ })
9250
+ );
9251
+ }
9252
+ /** Page evidence for one stored audit issue; continuation stays pinned to its crawl. */
9253
+ async getSiteAuditIssuePages(project, issueId, opts) {
9254
+ return this.invoke(
9255
+ () => getApiV1ProjectsByNameTechnicalAeoIssuesByIssueIdPages({
9256
+ client: this.heyClient,
9257
+ path: { name: project, issueId },
9258
+ query: { runId: opts?.runId, cursor: opts?.cursor, limit: opts?.limit }
9259
+ })
9260
+ );
9261
+ }
9051
9262
  /** Bounded semantic Site Health neighborhood; this never returns visualization layout. */
9052
9263
  async getSiteHealthSubgraph(project, opts) {
9053
9264
  return this.invoke(
@@ -9884,6 +10095,7 @@ var DynamicToolCatalog = class {
9884
10095
  eager: this.eager,
9885
10096
  loadedToolkits: [...this.loaded].sort(),
9886
10097
  coreTools: this.entries.filter((entry) => entry.tool.tier === "core").map((entry) => entry.tool.name),
10098
+ liveTools: this.entries.filter((entry) => entry.tool.tier === "core" && toolOperationAccess(entry.tool) === OperationAccesses.live).map((entry) => entry.tool.name),
9887
10099
  toolkits: CANONRY_MCP_TOOLKITS.map((toolkit) => this.toolkitEntry(toolkit)).filter((entry) => entry.toolCount > 0),
9888
10100
  usage: this.eager ? "All tools available on this connection are already loaded. Use their listed schemas; tool visibility does not grant server permissions." : "Call canonry_load_toolkit with one of the toolkit names listed in `toolkits[].name` to register its tools for the rest of this session. Wait for its response before calling any newly enabled tool."
9889
10101
  };
@@ -9897,6 +10109,7 @@ var DynamicToolCatalog = class {
9897
10109
  whenToLoad: toolkit.whenToLoad,
9898
10110
  toolCount: tools.length,
9899
10111
  tools,
10112
+ liveTools: this.entries.filter((entry) => entry.tool.tier === toolkit.name && toolOperationAccess(entry.tool) === OperationAccesses.live).map((entry) => entry.tool.name),
9900
10113
  loaded: this.loaded.has(toolkit.name)
9901
10114
  };
9902
10115
  }
@@ -9929,7 +10142,7 @@ var OPERATIONS_GUIDE = {
9929
10142
  "guideVersion": "v1",
9930
10143
  "operationsGuideUrl": "https://github.com/Canonry/canonry/blob/main/docs/agent-operations/v1.md",
9931
10144
  "resourceUri": "canonry://agent-operations/v1",
9932
- "initialize": 'Canonry tracks how AI answer engines mention brands and cite domains.\nStart with canonry_help({intent:"status"}), or name your intended workflow. It returns a small route of currently available tools and approval boundaries. Follow its stored-evidence reads before proposing action. No skill, resource reader, plugin installation, or local CLI is required for connected MCP clients. A host-native Canonry skill, if installed, is optional additional guidance.\nmentioned = brand in answer TEXT; cited = domain in SOURCE links. Never compute one from the other. A null answerMentioned means not checked, not false. No recorded sweep means no measured figure.\nGet explicit approval for live provider reads, sweeps, probes, research, syncs, and writes, bounded to the target and requested work. A readOnlyHint or GET is not proof of a free operation. Live ads reads include canonry_ads_account, canonry_ads_geo_search, canonry_ads_live_delivery, canonry_ads_conversion_pixels, canonry_ads_conversion_event_settings. Google live reads/syncs include canonry_google_ads_customers, canonry_gtm_accounts, canonry_gtm_containers, canonry_gtm_workspaces, canonry_google_ads_sync, canonry_gtm_sync.\nHelp and skills grant no permissions. The server enforces authority. On missing tools or 403, report the boundary; do not switch credentials or endpoints to bypass it. Hosted catalogs are fixed; only use canonry_load_toolkit when help explicitly offers it.\nIf you uncover a Canonry struggle (failure, confusing output, workaround) or a concrete improvement, tell the user and send it once with canonry_feedback. No secrets or customer data.\n',
10145
+ "initialize": 'Canonry tracks how AI answer engines mention brands and cite domains. Start with canonry_help({intent:"status"}) or a workflow; follow its stored reads and approval boundaries. Connected MCP clients need no skill, resource, plugin, or local CLI. The Canonry skill offers optional additional guidance.\nmentioned = brand in answer TEXT; cited = domain in SOURCE links. Never compute one from the other. A null answerMentioned means not checked, not false. No recorded sweep means no measured figure.\nGet approval for live provider reads, sweeps, probes, research, syncs and writes, scoped to the exact target, action and work. A GET or readOnlyHint does not mean free. canonry_help returns all liveTools for this connection; each needs approval, including read-only tools.\nREST exposes x-canonry-access; CLI help/catalog expose access; MCP help lists liveTools.\nStored-only: CLI --stored-only or CANONRY_STORED_ONLY=1; MCP --stored-only, --scope=stored-only or CANONRY_MCP_SCOPE=stored-only. It refuses live/write work (including doctor) before execution and suppresses housekeeping. CLI refusal is STORED_ONLY_REFUSED. Explicit exports may write files; server accounting remains separate from business mutations.\nHelp and skills grant no permissions; the server enforces authority. Report missing tools or 403; never bypass them with other credentials or endpoints. Hosted catalogs are fixed; use canonry_load_toolkit only when help offers it.\nReport concrete Canonry failures, confusing output, workarounds or improvements to the user and once via canonry_feedback. Exclude secrets and customer data.\n',
9933
10146
  "approvalBoundary": [
9934
10147
  "provider reads",
9935
10148
  "sweeps",
@@ -9962,10 +10175,11 @@ var OPERATIONS_GUIDE = {
9962
10175
  "health"
9963
10176
  ],
9964
10177
  "next": [
10178
+ "canonry_status",
9965
10179
  "canonry_projects_list",
9966
10180
  "canonry_project_overview"
9967
10181
  ],
9968
- "guidance": "Select an accessible project, then read its stored overview. Report freshness and missing evidence; do not start a sweep to fill a gap."
10182
+ "guidance": "Read canonry_status first for stored integration health, freshness, and repeated failures across accessible projects, then inspect a project overview for detail. Report missing evidence; do not start a sweep or sync to fill a gap."
9969
10183
  },
9970
10184
  "diagnose": {
9971
10185
  "keywords": [
@@ -10030,12 +10244,13 @@ var OPERATIONS_GUIDE = {
10030
10244
  "gtm"
10031
10245
  ],
10032
10246
  "next": [
10247
+ "canonry_status",
10033
10248
  "canonry_projects_list",
10034
10249
  "canonry_project_get",
10035
10250
  "canonry_ga_status",
10036
10251
  "canonry_google_ads_status"
10037
10252
  ],
10038
- "guidance": "Inspect stored connection status and snapshot freshness. Use the currently listed specialist tools for stored evidence. Connecting, selecting resources, syncing, and live provider reads require scoped approval; never request credentials in chat."
10253
+ "guidance": "Read canonry_status first for the stored instance or project health view, then use specialist tools for detail. Connecting, selecting resources, syncing, and live provider reads require scoped approval; never request credentials in chat."
10039
10254
  },
10040
10255
  "reports": {
10041
10256
  "keywords": [
@@ -10052,7 +10267,7 @@ var OPERATIONS_GUIDE = {
10052
10267
  "guidance": "Build the requested summary from stored project overview, organic evidence, and the relevant visibility, measurement, integration, or traffic reads. Retain evidence dates, scope, sample sizes, missing data, and separate mention/citation signals; do not generate fresh runs implicitly."
10053
10268
  }
10054
10269
  },
10055
- "markdown": "# Canonry Operations Guide v1\n\nCanonry is an agent-first AI visibility platform. MCP is the universal entry\npoint for connected agents. Host-native skills are optional upgrades, not a\nprerequisite or a permission mechanism.\n\n## Connect and choose a route\n\nRead the initialization guidance, then call `canonry_help` with an `intent`:\n`status`, `diagnose`, `operations`, `prospecting`, `measurement`, `integrations`,\n`reports`, or a short task\ndescription. Select an accessible project with `canonry_projects_list` before\nusing its exact name in project tools. Inspect each listed tool's input schema;\nhelp suggests tool names, not invented arguments or authorization.\n\nHelp returns a versioned, compact route: connection `mode`, available `next`\ntools, workflow guidance, approval boundaries, and this guide's URL. It performs\nno provider calls, reads no project data, and changes no permissions.\n`next` lists stored reads. Optional `actions` lists loaded tools for work that\nrequires approval; listing an action does not authorize or execute it.\n`includeCatalog: true` additionally returns toolkit details when needed.\n\nHosted connections use a fixed catalog. Help only suggests tools offered by that\nconnection; it never tells a hosted agent to dynamically load a toolkit. A\nprogressive local stdio connection may return `loadToolkits`: call\n`canonry_load_toolkit` with one returned name, await it, then call help again.\nLoading only changes local tool discovery, never server authority.\n\nThe optional `canonry://agent-operations/v1` MCP resource contains this same\nguide. If the host cannot read resources or open links, continue through help.\nDo not install a plugin, local runtime, or skill merely to use connected MCP.\nAn installed Codex or Claude Canonry skill contains a generated copy of this\nguide plus links to host-native references. It does not replace runtime help.\n\n## Vocabulary and evidence\n\n- **Mentioned** means the brand appears in answer text. **Cited** means its\n domain appears in source links. Either, both, or neither can occur; never\n compute one signal from the other.\n- `answerMentioned: null` means not checked, not false. Missing runs and empty\n populations mean no measurement, not zero visibility.\n- Preserve project, time window, provider, requested/served model, location,\n sample size, and query class when comparing evidence. Use server-returned\n metrics; do not invent a score from incompatible populations.\n- Simple projects and Advanced portfolios share the workflow. For Advanced\n results, preserve Property, Target, market, plan revision, and class scope.\n Groups organize navigation; do not infer an unrequested fan-out.\n- Research is isolated evidence, not tracked measurement. A probe still spends\n quota and persists evidence but is excluded from normal tracking metrics.\n\n## Workflows\n\n**Status:** read the stored overview and freshness first. Say when evidence is\nmissing instead of silently creating it.\n\n**Setup:** Page Health needs no answer-engine provider; AI Visibility does.\nAfter a Page Health result on an install with no provider (a `NO_PROVIDER`\nnotice, or doctor's `providers.none-configured`), propose AI Visibility setup\nas the next step. The operator adds the provider key in the dashboard or in\ntheir own terminal; never ask for it in chat.\nRerunning `canonry bootstrap` after a provider change reloads saved provider\nsettings on a matching running local server and reports whether they became\nactive. If the server is offline, start it to apply the saved settings. For an\nexplicit reload of the connected server's own config, use\n`canonry settings reload-providers` or `canonry_providers_reload` with install\nadministrator authority. Remote servers are never reloaded automatically by\nbootstrap, and a local server that belongs to another install is left alone.\nPorts and database settings still need a restart.\n\n**Diagnose:** inspect stored history and comparable evidence. Explain what\nchanged separately from why it might have changed. A hypothesis is not a\nmeasured cause. Propose bounded verification if stored evidence is insufficient.\n\n**Prospecting:** generate a one-shot company snapshot without creating a project.\nInspect stored provider settings first. Agree on the company, domain, selected\nproviders, and queries before starting the quota-spending snapshot action.\nBrowser-only selection requires manual queries. Progressive stdio help offers\nthe discovery toolkit when this connection permits snapshots; load it, then\ncall help again. Fixed catalogs offer only already available actions. Read-only\nand restricted connections must not bypass missing snapshot access.\n\n**Measurement:** inspect the existing setup and results before proposing edits.\nKeep research, query tracking, plan publication, and sweep execution separate.\nFor direct research, submit the final editable query text in one context. For a\nreviewed batch, submit each explicit destination with its final text and one\nidempotency key. Pattern substitution happens in the client before either\nrequest; choosing a market or Property records a destination only and never\nrewrites a query or creates an automatic fan-out. `research.run` does not\nauthorize saving patterns, changing tracking, publishing plans, or settings.\nUse a supported preview where available, inspect its exact destination and\nrevision, then seek approval for the actual change. A preview may itself require\nwrite permission; never treat a dry-run flag as a universal safety guarantee.\n\n**Integrations:** inspect stored connection state and snapshot freshness first.\nProvider configuration evidence does not prove a browser event fired or a\nconversion was recorded. Connection, resource selection, refresh/sync, and live\nreads are separate actions. Credentials belong in the operator's secure setup\nflow, never in chat, tool arguments, reports, or public guidance.\n\n**Reports:** use saved evidence for the requested period and scope. Keep mention\nand citation signals separate, include dates and sample sizes, and state missing\nor stale inputs. For Advanced Property mention rankings, use\n`canonry_measurement_portfolio_summary` and its `mentionRanking.strongest`,\n`.weakest`, and `.excluded` lists. It defaults to non-brand questions; state the\nreturned class and report branded results separately. An unavailable portfolio\naggregate does not invalidate available Property mention rates. Flag excluded\nProperties individually; do not silently replace mention ranking with citation\nranking. Compact `nextCursor` completes only `pageList`; use `list` to enumerate\none ranking, markets, observed names or cited domains, preserving filters.\nFirst-page sibling lists are bounded summaries. Keep sample sizes and ties\nvisible. Preparing a report does not\nauthorize new measurement.\n\n## Authority and approval\n\n### Agent operations\n\nUse `canonry_key_self` (CLI `canonry key whoami --format json`) to inspect the\ncurrent credential's scopes, project boundary, and host-derived `operator`\nauthority without exposing its token. Missing `operator` means unapproved.\n`canonry_settings_get` and\n`canonry_telemetry_get` describe the connected server, not the agent's local\nmachine. Telemetry reports configured preference, effective state, and any\nenvironment override; inspecting status never creates an anonymous identifier.\nAfter approval, `canonry_telemetry_update` changes that preference and\n`canonry_provider_settings_update` changes an already-configured provider's\nmodel/quota. Both require `settings.write`; neither accepts credentials.\nServer telemetry reads and updates additionally require operator authority.\nOrdinary audit-history reads omit internal telemetry events and their state.\n\nOperator authority is deny-by-default and separate from customer admin roles.\nThe deployment owner must approve a dedicated, instance-wide API key's ID in\nthe server environment variable `CANONRY_OPERATOR_KEY_IDS` (comma-separated IDs),\nthen restart the server. Empty/unset approves nobody; wildcards are invalid.\nKeep the bearer private to internal operators; never approve a customer-held or\nshared proxy/bootstrap key. Use `logs.read` for read-only diagnostics, adding\n`settings.write` only when telemetry control is required. Ordinary key creation,\naccount roles, OAuth consent, and caller headers cannot grant operator status.\nRevoking an approved key invalidates it immediately. Host enrollment is a trust\nbootstrap step, intentionally unavailable through customer-facing APIs.\nAPI, CLI, and MCP enforce the same boundary; MCP hides internal tools unless\nthe server confirms operator authority, including in explicit read-only mode.\nProject analytics, research, and normal project permissions are unchanged.\n\n`canonry_logs_list` reads bounded, redacted runtime events from both the\napplication logger and Fastify request/error logging. It requires an\ninstance-wide `logs.read` grant (or wildcard) and a host-approved direct bearer.\nBrowser sessions, OAuth/delegated credentials, customer admins, and project-scoped\nkeys cannot use it, even with a project filter or a matching allowlist ID.\nA `logs.read`-only key is read-only automatically, without a\nsecond `read` marker. Named `*.read` scopes cannot grant mutations; an explicit\nwrite grant is needed and remains subject to its route gates. Returned messages\nare sanitized and bounded; raw request or response bodies, headers, cookies,\nprovider payloads, and stacks are not\npart of the queryable surface. The same secret-redaction policy runs before\nconsole output and storage. Do not deliberately log secrets: redaction is a\ndefense in depth, not permission to put credentials into diagnostic strings.\nOpaque escaped payloads containing secret assignments are omitted when safe\npartial masking cannot be guaranteed; correlate their retained error codes and IDs.\n\nFile-backed hosts retain runtime logs in SQLite across restarts, bounded to\n10,000 events and seven days. In-memory hosts report `retention: \"process\"`.\nFilter by `actor`, `requestId`, `runId`, `projectId`, `module`, `level`, or an\ninclusive `since`/`until` interval. Keep filters unchanged when resuming an\nopaque cursor; retention eviction can invalidate it. Inspect `retentionPolicy`,\n`captureErrors`, `dropped`, `truncated`, and `retention` before drawing\nconclusions. Missing logs are not proof that an action did not happen. Use\n`canonry_project_history` or `canonry_history_global` for persistent audit events;\noffset pages have deterministic ordering but are not snapshots of concurrent writes.\n\nAudit `actor` comes from authenticated identity (`user:<id>` or `api-key:<id>`),\nnot a caller-supplied header. A delegated MCP credential records its originating\nuser as actor and the actual credential in `credentialId`. `requestId` correlates\nHTTP events; `userAgent` and `actorSession` are bounded, untrusted client hints,\nnever identity or permission grants. Older audit rows are not backfilled with\nidentities the server cannot prove.\n\nBoth shipped HTTP hosts issue restart-safe UUID request IDs and return them in\n`x-request-id`. Use that value to correlate a CLI/API failure with log entries;\nHTTP diagnostics retain the method and route template, not raw URL parameters.\nRequest-bound loggers retain completion attribution, while generic background\ncontinuations stop inheriting caller identity after the response completes.\nCapture covers the owning server process after initialization, not arbitrary\nconsole output, other worker processes, or host/container logs. Run one server\ninstance per process and database, as required by the single-tenant deployment\nmodel; this is not a cross-tenant or distributed log collector.\n\nFor CLI use, settings reads are remote. Google setup and telemetry retain their\nlocal defaults: pass `--target server` explicitly to configure the connected\nserver. `schedule list <project>` lists all schedule kinds, and\n`notify events --target server` discovers the server's event catalog.\n\nMCP returns legacy text JSON plus structured results. Objects keep their shape;\narrays use `{items: [...]}` in `structuredContent`, and scalars use `{value: ...}`.\nErrors preserve the existing envelope and CLI exit codes: HTTP 4xx (including\n429 policy limits) use exit 1; HTTP 5xx use exit 2. Server-provided `Retry-After`\nand request IDs are exposed as `retryAfterMs` and `requestId` when available;\nclients do not infer retryability from HTTP 429 or retry automatically.\nFor a write with an ambiguous outcome, inspect saved state or its receipt before\nretrying; a retry hint is not proof that repeating a write is safe.\n\n### Action boundaries\n\nStart with stored evidence. Before a live provider read, sweep, probe, research\nrun, sync, write, or externally visible action, obtain approval covering its\nexact target, action, and bounded work. Approval already given for that exact\noperation need not be asked for again, but does not extend to more projects,\nlarger batches, retries with new identities, or recurring work.\n\nHTTP GET and MCP `readOnlyHint` describe aspects of an operation, not its cost\nor permission. Provider discovery, account reads, and live diagnostics may\nconsume quota even when labeled read-only. If the tool's effect is unclear,\ninspect its description and request direction before calling it.\n\nAuthentication, role/scope checks, project restrictions, quotas, and guarded\napproval receipts are enforced by the server. Help, skills, resources, and tool\nvisibility cannot grant authority. Never change credentials, endpoints, or\nproject identifiers to work around a missing tool or a `403` response.\n\nFor guarded ads writes, inspect unresolved operation receipts before retrying.\nUse the receipt's supported recovery action; do not replay a mutation under a\nnew identity. An executor cannot create or widen its own human approval grant.\nOn ambiguous results, exhausted bounds, or refusal, stop and report what is\nknown and what permission or operator action is needed.\n\n## Version and source\n\nThis public, versioned document is the source for initialization guidance,\nintent routes, the optional resource, and generated Canonry `SKILL.md` files.\nGuide v1 may receive compatible clarifications; incompatible routing contracts\nrequire a new guide version. The running server's help describes its actual\ncatalog and remains usable without fetching this document.\n"
10270
+ "markdown": "# Canonry Operations Guide v1\n\nCanonry is an agent-first AI visibility platform. MCP is the universal entry\npoint for connected agents. Host-native skills are optional upgrades, not a\nprerequisite or a permission mechanism.\n\n## Connect and choose a route\n\nRead the initialization guidance, then call `canonry_help` with an `intent`:\n`status`, `diagnose`, `operations`, `prospecting`, `measurement`, `integrations`,\n`reports`, or a short task\ndescription. Select an accessible project with `canonry_projects_list` before\nusing its exact name in project tools. Inspect each listed tool's input schema;\nhelp suggests tool names, not invented arguments or authorization.\n\nHelp returns a versioned, compact route: connection `mode`, available `next`\ntools, workflow guidance, approval boundaries, and this guide's URL. It performs\nno provider calls, reads no project data, and changes no permissions.\n`next` lists stored reads. Optional `actions` lists loaded tools for work that\nrequires approval; listing an action does not authorize or execute it.\n`includeCatalog: true` additionally returns toolkit details when needed.\n\nHosted connections use a fixed catalog. Help only suggests tools offered by that\nconnection; it never tells a hosted agent to dynamically load a toolkit. A\nprogressive local stdio connection may return `loadToolkits`: call\n`canonry_load_toolkit` with one returned name, await it, then call help again.\nLoading only changes local tool discovery, never server authority.\n\nThe optional `canonry://agent-operations/v1` MCP resource contains this same\nguide. If the host cannot read resources or open links, continue through help.\nDo not install a plugin, local runtime, or skill merely to use connected MCP.\nAn installed Codex or Claude Canonry skill contains a generated copy of this\nguide plus links to host-native references. It does not replace runtime help.\n\n## Vocabulary and evidence\n\n- **Mentioned** means the brand appears in answer text. **Cited** means its\n domain appears in source links. Either, both, or neither can occur; never\n compute one signal from the other.\n- `answerMentioned: null` means not checked, not false. Missing runs and empty\n populations mean no measurement, not zero visibility.\n- Preserve project, time window, provider, requested/served model, location,\n sample size, and query class when comparing evidence. Use server-returned\n metrics; do not invent a score from incompatible populations.\n- Simple projects and Advanced portfolios share the workflow. For Advanced\n results, preserve Property, Target, market, plan revision, and class scope.\n Groups organize navigation; do not infer an unrequested fan-out.\n- Research is isolated evidence, not tracked measurement. A probe still spends\n quota and persists evidence but is excluded from normal tracking metrics.\n\n## Workflows\n\n**Status:** read the stored overview and freshness first. Say when evidence is\nmissing instead of silently creating it.\n\n**Setup:** Page Health needs no answer-engine provider; AI Visibility does.\nAfter a Page Health result on an install with no provider (a `NO_PROVIDER`\nnotice, or doctor's `providers.none-configured`), propose AI Visibility setup\nas the next step. The operator adds the provider key in the dashboard or in\ntheir own terminal; never ask for it in chat.\nRerunning `canonry bootstrap` after a provider change reloads saved provider\nsettings on a matching running local server and reports whether they became\nactive. If the server is offline, start it to apply the saved settings. For an\nexplicit reload of the connected server's own config, use\n`canonry settings reload-providers` or `canonry_providers_reload` with install\nadministrator authority. Remote servers are never reloaded automatically by\nbootstrap, and a local server that belongs to another install is left alone.\nPorts and database settings still need a restart.\n\n**Diagnose:** inspect stored history and comparable evidence. Explain what\nchanged separately from why it might have changed. A hypothesis is not a\nmeasured cause. Propose bounded verification if stored evidence is insufficient.\n\n**Site Health:** read `canonry_site_health_issues` for stored technical findings,\naffected-page counts and example URLs, then `canonry_site_health_issue_pages`\nfor a selected issue's remaining pages. Pin subsequent reads to the returned\nrun. Inspect coverage and omissions: a passing score can still have findings,\nan older scan may lack cross-page evidence, and a partial scan cannot establish\nsite-wide absence. Advisory findings require judgment before proposing a repair.\nAudit evidence informs technical repairs and existing-page recommendations;\nit does not itself establish a new-blog opportunity or authorize any edit.\n\n**Prospecting:** generate a one-shot company snapshot without creating a project.\nInspect stored provider settings first. Agree on the company, domain, selected\nproviders, and queries before starting the quota-spending snapshot action.\nBrowser-only selection requires manual queries. Progressive stdio help offers\nthe discovery toolkit when this connection permits snapshots; load it, then\ncall help again. Fixed catalogs offer only already available actions. Read-only\nand restricted connections must not bypass missing snapshot access.\n\n**Measurement:** inspect the existing setup and results before proposing edits.\nKeep research, query tracking, plan publication, and sweep execution separate.\nFor direct research, submit the final editable query text in one context. For a\nreviewed batch, submit each explicit destination with its final text and one\nidempotency key. Pattern substitution happens in the client before either\nrequest; choosing a market or Property records a destination only and never\nrewrites a query or creates an automatic fan-out. `research.run` does not\nauthorize saving patterns, changing tracking, publishing plans, or settings.\nUse a supported preview where available, inspect its exact destination and\nrevision, then seek approval for the actual change. A preview may itself require\nwrite permission; never treat a dry-run flag as a universal safety guarantee.\n\n**Integrations:** inspect stored connection state and snapshot freshness first.\nProvider configuration evidence does not prove a browser event fired or a\nconversion was recorded. Connection, resource selection, refresh/sync, and live\nreads are separate actions. Credentials belong in the operator's secure setup\nflow, never in chat, tool arguments, reports, or public guidance.\n\n**Reports:** use saved evidence for the requested period and scope. Keep mention\nand citation signals separate, include dates and sample sizes, and state missing\nor stale inputs. For Advanced Property mention rankings, use\n`canonry_measurement_portfolio_summary` and its `mentionRanking.strongest`,\n`.weakest`, and `.excluded` lists. It defaults to non-brand questions; state the\nreturned class and report branded results separately. An unavailable portfolio\naggregate does not invalidate available Property mention rates. Flag excluded\nProperties individually; do not silently replace mention ranking with citation\nranking. Compact `nextCursor` completes only `pageList`; use `list` to enumerate\none ranking, markets, observed names or cited domains, preserving filters.\nFirst-page sibling lists are bounded summaries. Keep sample sizes and ties\nvisible. Preparing a report does not\nauthorize new measurement.\n\n## Authority and approval\n\n### Agent operations\n\nUse `canonry_key_self` (CLI `canonry key whoami --format json`) to inspect the\ncurrent credential's scopes, project boundary, and host-derived `operator`\nauthority without exposing its token. Missing `operator` means unapproved.\n`canonry_settings_get` and\n`canonry_telemetry_get` describe the connected server, not the agent's local\nmachine. Telemetry reports configured preference, effective state, and any\nenvironment override; inspecting status never creates an anonymous identifier.\nAfter approval, `canonry_telemetry_update` changes that preference and\n`canonry_provider_settings_update` changes an already-configured provider's\nmodel/quota. Both require `settings.write`; neither accepts credentials.\nServer telemetry reads and updates additionally require operator authority.\nOrdinary audit-history reads omit internal telemetry events and their state.\n\nOperator authority is deny-by-default and separate from customer admin roles.\nThe deployment owner must approve a dedicated, instance-wide API key's ID in\nthe server environment variable `CANONRY_OPERATOR_KEY_IDS` (comma-separated IDs),\nthen restart the server. Empty/unset approves nobody; wildcards are invalid.\nKeep the bearer private to internal operators; never approve a customer-held or\nshared proxy/bootstrap key. Use `logs.read` for read-only diagnostics, adding\n`settings.write` only when telemetry control is required. Ordinary key creation,\naccount roles, OAuth consent, and caller headers cannot grant operator status.\nRevoking an approved key invalidates it immediately. Host enrollment is a trust\nbootstrap step, intentionally unavailable through customer-facing APIs.\nAPI, CLI, and MCP enforce the same boundary; MCP hides internal tools unless\nthe server confirms operator authority, including in explicit read-only mode.\nProject analytics, research, and normal project permissions are unchanged.\n\n`canonry_logs_list` reads bounded, redacted runtime events from both the\napplication logger and Fastify request/error logging. It requires an\ninstance-wide `logs.read` grant (or wildcard) and a host-approved direct bearer.\nBrowser sessions, OAuth/delegated credentials, customer admins, and project-scoped\nkeys cannot use it, even with a project filter or a matching allowlist ID.\nA `logs.read`-only key is read-only automatically, without a\nsecond `read` marker. Named `*.read` scopes cannot grant mutations; an explicit\nwrite grant is needed and remains subject to its route gates. Returned messages\nare sanitized and bounded; raw request or response bodies, headers, cookies,\nprovider payloads, and stacks are not\npart of the queryable surface. The same secret-redaction policy runs before\nconsole output and storage. Do not deliberately log secrets: redaction is a\ndefense in depth, not permission to put credentials into diagnostic strings.\nOpaque escaped payloads containing secret assignments are omitted when safe\npartial masking cannot be guaranteed; correlate their retained error codes and IDs.\n\nFile-backed hosts retain runtime logs in SQLite across restarts, bounded to\n10,000 events and seven days. In-memory hosts report `retention: \"process\"`.\nFilter by `actor`, `requestId`, `runId`, `projectId`, `module`, `level`, or an\ninclusive `since`/`until` interval. Keep filters unchanged when resuming an\nopaque cursor; retention eviction can invalidate it. Inspect `retentionPolicy`,\n`captureErrors`, `dropped`, `truncated`, and `retention` before drawing\nconclusions. Missing logs are not proof that an action did not happen. Use\n`canonry_project_history` or `canonry_history_global` for persistent audit events;\noffset pages have deterministic ordering but are not snapshots of concurrent writes.\n\nAudit `actor` comes from authenticated identity (`user:<id>` or `api-key:<id>`),\nnot a caller-supplied header. A delegated MCP credential records its originating\nuser as actor and the actual credential in `credentialId`. `requestId` correlates\nHTTP events; `userAgent` and `actorSession` are bounded, untrusted client hints,\nnever identity or permission grants. Older audit rows are not backfilled with\nidentities the server cannot prove.\n\nBoth shipped HTTP hosts issue restart-safe UUID request IDs and return them in\n`x-request-id`. Use that value to correlate a CLI/API failure with log entries;\nHTTP diagnostics retain the method and route template, not raw URL parameters.\nRequest-bound loggers retain completion attribution, while generic background\ncontinuations stop inheriting caller identity after the response completes.\nCapture covers the owning server process after initialization, not arbitrary\nconsole output, other worker processes, or host/container logs. Run one server\ninstance per process and database, as required by the single-tenant deployment\nmodel; this is not a cross-tenant or distributed log collector.\n\nFor CLI use, settings reads are remote. Google setup and telemetry retain their\nlocal defaults: pass `--target server` explicitly to configure the connected\nserver. `schedule list <project>` lists all schedule kinds, and\n`notify events --target server` discovers the server's event catalog.\n\nMCP returns legacy text JSON plus structured results. Objects keep their shape;\narrays use `{items: [...]}` in `structuredContent`, and scalars use `{value: ...}`.\nErrors preserve the existing envelope and CLI exit codes: HTTP 4xx (including\n429 policy limits) use exit 1; HTTP 5xx use exit 2. Server-provided `Retry-After`\nand request IDs are exposed as `retryAfterMs` and `requestId` when available;\nclients do not infer retryability from HTTP 429 or retry automatically.\nFor a write with an ambiguous outcome, inspect saved state or its receipt before\nretrying; a retry hint is not proof that repeating a write is safe.\n\n### Action boundaries\n\nStart with stored evidence. Before a live provider read, sweep, probe, research\nrun, sync, write, or externally visible action, obtain approval covering its\nexact target, action, and bounded work. Approval already given for that exact\noperation need not be asked for again, but does not extend to more projects,\nlarger batches, retries with new identities, or recurring work.\n\nHTTP GET and MCP `readOnlyHint` describe aspects of an operation, not its cost\nor permission. Provider discovery, account reads, and live diagnostics may\nconsume quota even when labeled read-only. If the tool's effect is unclear,\ninspect its description and request direction before calling it.\n\nAuthentication, role/scope checks, project restrictions, quotas, and guarded\napproval receipts are enforced by the server. Help, skills, resources, and tool\nvisibility cannot grant authority. Never change credentials, endpoints, or\nproject identifiers to work around a missing tool or a `403` response.\n\nFor guarded ads writes, inspect unresolved operation receipts before retrying.\nUse the receipt's supported recovery action; do not replay a mutation under a\nnew identity. An executor cannot create or widen its own human approval grant.\nOn ambiguous results, exhausted bounds, or refusal, stop and report what is\nknown and what permission or operator action is needed.\n\n## Version and source\n\nThis public, versioned document is the source for initialization guidance,\nintent routes, the optional resource, and generated Canonry `SKILL.md` files.\nGuide v1 may receive compatible clarifications; incompatible routing contracts\nrequire a new guide version. The running server's help describes its actual\ncatalog and remains usable without fetching this document.\n"
10056
10271
  };
10057
10272
 
10058
10273
  // src/mcp/operations-guide.ts
@@ -10071,6 +10286,8 @@ function operationsHelp(catalog, mode, intent = "", includeCatalog = false) {
10071
10286
  }
10072
10287
  const wanted = [...reads, ...requestedActions];
10073
10288
  const loadToolkits = mode === "stdio-progressive" ? catalog.toolkits.filter((t) => !t.loaded && t.tools.some((name) => wanted.includes(name))).map((t) => t.name) : [];
10289
+ const liveTools = [...catalog.liveTools];
10290
+ for (const toolkit of catalog.toolkits) liveTools.push(...toolkit.liveTools);
10074
10291
  return {
10075
10292
  guideVersion: OPERATIONS_GUIDE.guideVersion,
10076
10293
  mode,
@@ -10085,7 +10302,8 @@ function operationsHelp(catalog, mode, intent = "", includeCatalog = false) {
10085
10302
  authority: OPERATIONS_GUIDE.authority,
10086
10303
  operationsGuideUrl: OPERATIONS_GUIDE.operationsGuideUrl,
10087
10304
  ...loadToolkits.length ? { loadToolkits } : {},
10088
- ...includeCatalog ? catalog : {}
10305
+ ...includeCatalog ? catalog : {},
10306
+ liveTools: [...new Set(liveTools)].sort()
10089
10307
  };
10090
10308
  }
10091
10309
 
@@ -10209,7 +10427,7 @@ function getCanonryMcpTools(scope = "all", tiers, credentialScopes, operator = f
10209
10427
  const readOnly = scope === "read-only" || credentialScopes !== void 0 && isReadOnlyKey(credentialScopes);
10210
10428
  const restricted = credentialScopes && restrictedWriteScopes(credentialScopes);
10211
10429
  const authorized = canonryMcpTools.filter((tool) => !tool.requiresOperator || operator);
10212
- const byScope = readOnly ? authorized.filter((tool) => tool.access === "read") : authorized.filter((tool) => {
10430
+ const byScope = scope === "stored-only" ? authorized.filter((tool) => toolOperationAccess(tool) === OperationAccesses.stored) : readOnly ? authorized.filter((tool) => tool.access === "read") : authorized.filter((tool) => {
10213
10431
  if (!restricted || tool.access === "read") return true;
10214
10432
  if (tool.requiredScope && restricted.includes(tool.requiredScope)) return true;
10215
10433
  return restricted.some((grant) => grant !== RESEARCH_RUN_SCOPE) && tool.tier === "ads";