@canonry/canonry 6.2.0 → 6.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +12 -0
  2. package/assets/assets/{AuditHistoryPanel-CuEMfkaA.js → AuditHistoryPanel-BUYWIjlG.js} +1 -1
  3. package/assets/assets/{BacklinksPage-CftadZ1-.js → BacklinksPage-aBcxpN_9.js} +1 -1
  4. package/assets/assets/{HistoryPage-DouRgshN.js → HistoryPage-PoBuxiY_.js} +1 -1
  5. package/assets/assets/{MeasurementPropertyPage-qfqv2qs9.js → MeasurementPropertyPage-DyjyqHac.js} +1 -1
  6. package/assets/assets/{ProjectPage-DqEVwe4t.js → ProjectPage-Mmv5YW-K.js} +1 -1
  7. package/assets/assets/{RunsPage-CUkMs0gB.js → RunsPage-CHHxOpzo.js} +1 -1
  8. package/assets/assets/{SettingsPage-LLQ40tSh.js → SettingsPage-CNKnrLXC.js} +1 -1
  9. package/assets/assets/{SiteHealthSection-Ba7foDiU.js → SiteHealthSection-C9RivT7G.js} +3 -3
  10. package/assets/assets/{TrafficPage-Cu_ZR3--.js → TrafficPage-CY-ibXnb.js} +1 -1
  11. package/assets/assets/{TrafficSourceDetailPage-NKZq7jf9.js → TrafficSourceDetailPage-BgiPXubs.js} +1 -1
  12. package/assets/assets/{extract-error-message-BdQYrz9N.js → extract-error-message-H1dzzfBP.js} +1 -1
  13. package/assets/assets/{index-HOeLz1ZL.js → index-DSCun3I9.js} +21 -21
  14. package/assets/assets/{react-sigma_core.esm.min-CZaoE5cU.js → react-sigma_core.esm.min-uiYtR_DU.js} +1 -1
  15. package/assets/assets/{v2-overview-adapter-BVx4nI49.js → v2-overview-adapter-BQokN2eb.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-2RFHQ6XT.js → chunk-4WZUR7XX.js} +240 -119
  18. package/dist/{chunk-4EKOKWQT.js → chunk-5FYTFE5T.js} +2 -2
  19. package/dist/{chunk-Y2B7JRSB.js → chunk-5GY5WBAF.js} +3 -3
  20. package/dist/{chunk-ENI3GOWW.js → chunk-7CVTTQRV.js} +95 -26
  21. package/dist/{chunk-W3GDZZNF.js → chunk-DERWXLHX.js} +15 -1
  22. package/dist/{chunk-6EPN4XY5.js → chunk-EV5G5QRR.js} +324 -280
  23. package/dist/{chunk-V6JUBTNL.js → chunk-ZZEPF3A5.js} +23 -3
  24. package/dist/cli.js +53 -8
  25. package/dist/{demo-server-CH7MOBXW.js → demo-server-AIFR77TP.js} +4 -4
  26. package/dist/index.js +6 -6
  27. package/dist/{intelligence-service-4IONMOFB.js → intelligence-service-SYISPW25.js} +2 -2
  28. package/dist/mcp.js +4 -4
  29. package/package.json +13 -13
@@ -5,7 +5,7 @@ import {
5
5
  siteCrawlGraphNodes,
6
6
  siteCrawlPages,
7
7
  siteCrawlSnapshots
8
- } from "./chunk-ENI3GOWW.js";
8
+ } from "./chunk-7CVTTQRV.js";
9
9
  import {
10
10
  SITE_CRAWL_GRAPH_MAX_EDGES,
11
11
  SITE_CRAWL_GRAPH_MAX_NODES,
@@ -13,7 +13,7 @@ import {
13
13
  TEMPLATE_LINK_RATIO_THRESHOLD,
14
14
  factorStatusFromScore,
15
15
  roundPreservingTotal
16
- } from "./chunk-6EPN4XY5.js";
16
+ } from "./chunk-EV5G5QRR.js";
17
17
 
18
18
  // src/site-audit-factors.ts
19
19
  function recordedSharePct(factor) {
@@ -4,14 +4,14 @@ import {
4
4
  installSkills,
5
5
  loadConfigRaw,
6
6
  saveConfigPatch
7
- } from "./chunk-V6JUBTNL.js";
7
+ } from "./chunk-ZZEPF3A5.js";
8
8
  import {
9
9
  PACKAGE_VERSION
10
- } from "./chunk-W3GDZZNF.js";
10
+ } from "./chunk-DERWXLHX.js";
11
11
  import {
12
12
  SKILL_MANIFEST_FILENAME,
13
13
  SkillsClients
14
- } from "./chunk-6EPN4XY5.js";
14
+ } from "./chunk-EV5G5QRR.js";
15
15
 
16
16
  // src/skills-autosync.ts
17
17
  import fs from "fs";
@@ -354,6 +354,8 @@ import {
354
354
  extractAnswerMentions,
355
355
  extractDomainsFromText,
356
356
  factorStatusFromScore,
357
+ feedbackAcceptedDtoSchema,
358
+ feedbackSubmissionSchema,
357
359
  filterBrandedSeedCandidates,
358
360
  filterCapturedCitedUrls,
359
361
  findDuplicateLocationLabels,
@@ -869,7 +871,7 @@ import {
869
871
  wordpressSchemaDeployResultDtoSchema,
870
872
  wordpressSchemaStatusResultDtoSchema,
871
873
  wordpressStatusDtoSchema
872
- } from "./chunk-6EPN4XY5.js";
874
+ } from "./chunk-EV5G5QRR.js";
873
875
 
874
876
  // src/intelligence-service.ts
875
877
  import { eq as eq82, desc as desc36, asc as asc15, and as and69, ne as ne14, or as or20, inArray as inArray32, gte as gte22, lte as lte20, isNull as isNull16, sql as sql31, exists } from "drizzle-orm";
@@ -9001,6 +9003,10 @@ function cancelProject(db, projectId, now, reason) {
9001
9003
  )).run();
9002
9004
  recountJobs(db, affected, now);
9003
9005
  }
9006
+ function dispatchedInLastMinute(db, now) {
9007
+ const since = new Date(Date.parse(now) - 6e4).toISOString();
9008
+ return db.select({ requests: sql4`count(*)`, tokens: sql4`coalesce(sum(${sentimentAttempts.estimatedInputTokens}), 0)` }).from(sentimentAttempts).where(gt(sentimentAttempts.dispatchedAt, since)).get();
9009
+ }
9004
9010
  var SentimentRepository = class {
9005
9011
  constructor(db) {
9006
9012
  this.db = db;
@@ -9209,6 +9215,10 @@ var SentimentRepository = class {
9209
9215
  return claimed;
9210
9216
  }, { behavior: "immediate" });
9211
9217
  }
9218
+ /** Requests and estimated input tokens dispatched in the minute before `now`: the window `startAttempt` enforces. */
9219
+ recentDispatch(now) {
9220
+ return dispatchedInLastMinute(this.db, now);
9221
+ }
9212
9222
  startAttempt(input) {
9213
9223
  return this.db.transaction((tx) => {
9214
9224
  const work = tx.select().from(sentimentWorkItems).where(and(
@@ -9223,8 +9233,7 @@ var SentimentRepository = class {
9223
9233
  if (input.maxAttempts !== void 0 && work.attemptCount - work.attemptBudgetStart >= input.maxAttempts) return void 0;
9224
9234
  const estimate = input.estimatedInputTokens ?? 0;
9225
9235
  if (!Number.isSafeInteger(estimate) || estimate < 0) throw new Error("Invalid sentiment token estimate");
9226
- const since = new Date(Date.parse(input.now) - 6e4).toISOString();
9227
- const used = tx.select({ requests: sql4`count(*)`, tokens: sql4`coalesce(sum(${sentimentAttempts.estimatedInputTokens}), 0)` }).from(sentimentAttempts).where(gt(sentimentAttempts.dispatchedAt, since)).get();
9236
+ const used = dispatchedInLastMinute(tx, input.now);
9228
9237
  if (input.maxRequestsPerMinute !== void 0 && used.requests >= input.maxRequestsPerMinute) return void 0;
9229
9238
  if (input.maxInputTokensPerMinute !== void 0 && used.tokens + estimate > input.maxInputTokensPerMinute) return void 0;
9230
9239
  const attemptNumber = work.attemptCount + 1;
@@ -9299,6 +9308,14 @@ var SentimentRepository = class {
9299
9308
  return true;
9300
9309
  }, { behavior: "immediate" });
9301
9310
  }
9311
+ /** Hand claimed work back to the queue untouched: no error, no retry time, no attempt counted. */
9312
+ releaseWork(input) {
9313
+ return this.db.transaction((tx) => {
9314
+ const changed = tx.update(sentimentWorkItems).set({ status: "pending", leaseOwner: null, leaseExpiresAt: null, updatedAt: input.now }).where(and(eq2(sentimentWorkItems.id, input.workItemId), eq2(sentimentWorkItems.leaseOwner, input.owner), eq2(sentimentWorkItems.status, "running"))).returning().all();
9315
+ if (changed.length > 0) shiftJobs(tx, changed[0].id, "running", "pending", input.now);
9316
+ return changed.length > 0;
9317
+ }, { behavior: "immediate" });
9318
+ }
9302
9319
  failWork(input) {
9303
9320
  return this.db.transaction((tx) => {
9304
9321
  const changed = tx.update(sentimentWorkItems).set({
@@ -12466,7 +12483,7 @@ function resolveTrustProxy(raw) {
12466
12483
  }
12467
12484
  var FORWARDED_HEADERS = ["x-forwarded-for", "forwarded", "x-real-ip"];
12468
12485
  function hasForwardedHeaders(request) {
12469
- return FORWARDED_HEADERS.some((header) => request.headers[header] !== void 0);
12486
+ return FORWARDED_HEADERS.some((header2) => request.headers[header2] !== void 0);
12470
12487
  }
12471
12488
  function resolveCallerKey(request, trustProxyConfigured) {
12472
12489
  const socketAddress = request.socket.remoteAddress ?? request.ip;
@@ -12496,9 +12513,9 @@ function cookieIsSecure(request, configured) {
12496
12513
  function anyUsersExist(db) {
12497
12514
  return db.select({ id: users.id }).from(users).limit(1).get() !== void 0;
12498
12515
  }
12499
- function parseCookieHeader(header) {
12500
- if (!header) return {};
12501
- return header.split(";").map((part) => part.trim()).filter(Boolean).reduce((cookies, part) => {
12516
+ function parseCookieHeader(header2) {
12517
+ if (!header2) return {};
12518
+ return header2.split(";").map((part) => part.trim()).filter(Boolean).reduce((cookies, part) => {
12502
12519
  const eqIdx = part.indexOf("=");
12503
12520
  if (eqIdx <= 0) return cookies;
12504
12521
  const name = part.slice(0, eqIdx).trim();
@@ -13018,11 +13035,11 @@ async function authPlugin(app, opts = {}) {
13018
13035
  app.addHook("onRequest", async (request, reply) => {
13019
13036
  const url = request.url.split("?")[0];
13020
13037
  if (shouldSkipAuth(url)) return;
13021
- const header = request.headers.authorization;
13038
+ const header2 = request.headers.authorization;
13022
13039
  let key;
13023
13040
  let keyArrivedInCookie = false;
13024
- if (header) {
13025
- const parts = header.split(" ");
13041
+ if (header2) {
13042
+ const parts = header2.split(" ");
13026
13043
  if (parts.length !== 2 || parts[0] !== "Bearer") {
13027
13044
  throw authRequired();
13028
13045
  }
@@ -22569,11 +22586,11 @@ function parseCsvRecords(value) {
22569
22586
  return records.filter((record) => !record.physicalBlank);
22570
22587
  }
22571
22588
  function parseHeader(records) {
22572
- const header = records.at(0);
22573
- if (!header) csvError("csv-header-missing", "CSV must include a property and group header row.");
22589
+ const header2 = records.at(0);
22590
+ if (!header2) csvError("csv-header-missing", "CSV must include a property and group header row.");
22574
22591
  const allowed = /* @__PURE__ */ new Set(["property", "group", "url"]);
22575
22592
  const indices = /* @__PURE__ */ new Map();
22576
- for (const [index2, raw] of header.fields.entries()) {
22593
+ for (const [index2, raw] of header2.fields.entries()) {
22577
22594
  const name = normalizeGroupMembershipLabel(raw);
22578
22595
  if (!allowed.has(name)) {
22579
22596
  csvError("csv-header-invalid", `CSV header "${raw}" is not supported. Use property, group, and optional url.`, 400, { header: raw });
@@ -22588,7 +22605,7 @@ function parseHeader(records) {
22588
22605
  csvError("csv-header-missing", `CSV is missing the required "${required2}" header.`, 400, { header: required2 });
22589
22606
  }
22590
22607
  }
22591
- return { indices, length: header.fields.length };
22608
+ return { indices, length: header2.fields.length };
22592
22609
  }
22593
22610
  function parseGroupMembershipCsv(csv) {
22594
22611
  const byteLength = Buffer.byteLength(csv, "utf8");
@@ -22601,7 +22618,7 @@ function parseGroupMembershipCsv(csv) {
22601
22618
  );
22602
22619
  }
22603
22620
  const records = parseCsvRecords(csv);
22604
- const header = parseHeader(records);
22621
+ const header2 = parseHeader(records);
22605
22622
  const dataRecords = records.slice(1);
22606
22623
  if (dataRecords.length > MEASUREMENT_GROUP_MEMBERSHIP_CSV_MAX_DATA_ROWS) {
22607
22624
  csvError(
@@ -22611,9 +22628,9 @@ function parseGroupMembershipCsv(csv) {
22611
22628
  { rowCount: dataRecords.length, maxRows: MEASUREMENT_GROUP_MEMBERSHIP_CSV_MAX_DATA_ROWS }
22612
22629
  );
22613
22630
  }
22614
- const propertyIndex = header.indices.get("property");
22615
- const groupIndex = header.indices.get("group");
22616
- const urlIndex = header.indices.get("url");
22631
+ const propertyIndex = header2.indices.get("property");
22632
+ const groupIndex = header2.indices.get("group");
22633
+ const urlIndex = header2.indices.get("url");
22617
22634
  const rows = dataRecords.map((record, index2) => {
22618
22635
  const property = record.fields[propertyIndex] ?? "";
22619
22636
  const group = record.fields[groupIndex] ?? "";
@@ -22626,7 +22643,7 @@ function parseGroupMembershipCsv(csv) {
22626
22643
  url,
22627
22644
  normalizedProperty: normalizeGroupMembershipLabel(property),
22628
22645
  normalizedGroupLabel: normalizeGroupMembershipLabel(group),
22629
- columnCountValid: record.fields.length === header.length
22646
+ columnCountValid: record.fields.length === header2.length
22630
22647
  };
22631
22648
  });
22632
22649
  return { sourceChecksum: sourceChecksum(csv), rows };
@@ -33766,7 +33783,7 @@ function renderWinsLosses(insights2, heading, emptyMessage, audience) {
33766
33783
  </tr>`;
33767
33784
  }).join("");
33768
33785
  const headerCells = (isClient ? REPORT_SECTION_COPY["whats-changed"].client : REPORT_SECTION_COPY["whats-changed"].agency).insightHeaders;
33769
- const headers = `<tr>${headerCells.map((header) => `<th>${header}</th>`).join("")}</tr>`;
33786
+ const headers = `<tr>${headerCells.map((header2) => `<th>${header2}</th>`).join("")}</tr>`;
33770
33787
  return `<div class="chart-card"><h3>${escapeHtml2(heading)}</h3>
33771
33788
  <div class="table-scroll"><table class="report-table">
33772
33789
  <thead>${headers}</thead>
@@ -39924,6 +39941,8 @@ var SCHEMA_TABLE = {
39924
39941
  SnapshotRequest: z.object(snapshotRequestInputSchema.shape).omit({ phrases: true }),
39925
39942
  SourceBreakdownDto: sourceBreakdownDtoSchema,
39926
39943
  TelemetryEventAcceptedDto: telemetryEventAcceptedDtoSchema,
39944
+ FeedbackSubmission: feedbackSubmissionSchema,
39945
+ FeedbackAcceptedDto: feedbackAcceptedDtoSchema,
39927
39946
  TelemetryStatusDto: telemetryStatusDtoSchema,
39928
39947
  OperationalLogListDto: operationalLogListDtoSchema,
39929
39948
  CloudflareWorkerIngestRequest: cloudflareWorkerIngestRequestSchema,
@@ -43053,6 +43072,28 @@ var routeCatalog = [
43053
43072
  400: errorResponse("Invalid onboarding telemetry event.")
43054
43073
  }
43055
43074
  },
43075
+ {
43076
+ method: "post",
43077
+ path: "/api/v1/feedback",
43078
+ summary: "Send product feedback about Canonry",
43079
+ description: "Forwards a struggle, bug, improvement, or other note about Canonry itself to the Canonry team (canonry.ai). Free text is redacted for credentials before it leaves the machine; never include customer data. The server adds version, OS, and the calling surface and agent; the anonymous install id is attached only while telemetry is enabled.",
43080
+ tags: ["feedback"],
43081
+ requestBody: {
43082
+ required: true,
43083
+ content: {
43084
+ "application/json": {
43085
+ schema: { $ref: "#/components/schemas/FeedbackSubmission" }
43086
+ }
43087
+ }
43088
+ },
43089
+ responses: {
43090
+ 202: jsonResponse("Feedback accepted by the collector.", "FeedbackAcceptedDto"),
43091
+ 400: errorResponse("Invalid feedback."),
43092
+ 429: errorResponse("Feedback rate limit reached; try again later."),
43093
+ 501: errorResponse("Feedback is not available in this deployment."),
43094
+ 502: errorResponse("The feedback collector could not be reached or rejected the submission.")
43095
+ }
43096
+ },
43056
43097
  {
43057
43098
  method: "get",
43058
43099
  path: "/api/v1/screenshots/{snapshotId}",
@@ -47791,6 +47832,31 @@ async function telemetryRoutes(app, opts) {
47791
47832
  });
47792
47833
  }
47793
47834
 
47835
+ // ../api-routes/src/feedback.ts
47836
+ function header(value) {
47837
+ return Array.isArray(value) ? value[0] : value;
47838
+ }
47839
+ async function feedbackRoutes(app, opts) {
47840
+ app.post("/feedback", async (request, reply) => {
47841
+ const parsed = feedbackSubmissionSchema.safeParse(request.body);
47842
+ if (!parsed.success) {
47843
+ throw validationError("Invalid feedback", {
47844
+ issues: parsed.error.issues.map((issue) => ({ code: issue.code, path: issue.path.join(".") }))
47845
+ });
47846
+ }
47847
+ if (!opts.submitFeedback) {
47848
+ throw notImplemented("Feedback is not available in this deployment");
47849
+ }
47850
+ const accepted = await opts.submitFeedback(parsed.data, {
47851
+ userAgent: header(request.headers["user-agent"]),
47852
+ surface: header(request.headers[USAGE_TELEMETRY_HEADERS.surface]),
47853
+ agent: header(request.headers[USAGE_TELEMETRY_HEADERS.agent]),
47854
+ mcpClient: header(request.headers[USAGE_TELEMETRY_HEADERS.mcpClient])
47855
+ });
47856
+ return reply.status(202).send(accepted);
47857
+ });
47858
+ }
47859
+
47794
47860
  // ../api-routes/src/operational-logs.ts
47795
47861
  async function operationalLogsRoutes(app, opts = {}) {
47796
47862
  app.get("/operations/logs", async (request, reply) => {
@@ -47811,8 +47877,8 @@ async function operationalLogsRoutes(app, opts = {}) {
47811
47877
  return withoutUnrequestedFields(page3, requestedFields(request.headers[OPERATIONAL_LOG_FIELDS_HEADER]));
47812
47878
  });
47813
47879
  }
47814
- function requestedFields(header) {
47815
- const names = (Array.isArray(header) ? header : [header ?? ""]).flatMap((value) => value.split(",")).map((name) => name.trim());
47880
+ function requestedFields(header2) {
47881
+ const names = (Array.isArray(header2) ? header2 : [header2 ?? ""]).flatMap((value) => value.split(",")).map((name) => name.trim());
47816
47882
  return new Set(OPERATIONAL_LOG_OPT_IN_CONTEXT_FIELDS.filter((field) => names.includes(field)));
47817
47883
  }
47818
47884
  function withoutUnrequestedFields(page3, requested) {
@@ -49250,7 +49316,7 @@ function createServiceAccountJwt(clientEmail, privateKey, scope) {
49250
49316
  validatePrivateKey(privateKey);
49251
49317
  validateScope(scope);
49252
49318
  const now = Math.floor(Date.now() / 1e3);
49253
- const header = { alg: "RS256", typ: "JWT" };
49319
+ const header2 = { alg: "RS256", typ: "JWT" };
49254
49320
  const payload = {
49255
49321
  iss: clientEmail,
49256
49322
  scope,
@@ -49260,7 +49326,7 @@ function createServiceAccountJwt(clientEmail, privateKey, scope) {
49260
49326
  // 1 hour
49261
49327
  };
49262
49328
  const encode = (obj) => Buffer.from(JSON.stringify(obj)).toString("base64url");
49263
- const headerB64 = encode(header);
49329
+ const headerB64 = encode(header2);
49264
49330
  const payloadB64 = encode(payload);
49265
49331
  const signingInput = `${headerB64}.${payloadB64}`;
49266
49332
  const sign = crypto30.createSign("RSA-SHA256");
@@ -63466,7 +63532,7 @@ function createServiceAccountJwt2(clientEmail, privateKey, scope) {
63466
63532
  if (!privateKey) throw new CloudRunAuthError("privateKey is required");
63467
63533
  if (!scope) throw new CloudRunAuthError("scope is required");
63468
63534
  const now = Math.floor(Date.now() / 1e3);
63469
- const header = { alg: "RS256", typ: "JWT" };
63535
+ const header2 = { alg: "RS256", typ: "JWT" };
63470
63536
  const payload = {
63471
63537
  iss: clientEmail,
63472
63538
  scope,
@@ -63475,7 +63541,7 @@ function createServiceAccountJwt2(clientEmail, privateKey, scope) {
63475
63541
  exp: now + 3600
63476
63542
  };
63477
63543
  const encode = (obj) => Buffer.from(JSON.stringify(obj)).toString("base64url");
63478
- const headerB64 = encode(header);
63544
+ const headerB64 = encode(header2);
63479
63545
  const payloadB64 = encode(payload);
63480
63546
  const signingInput = `${headerB64}.${payloadB64}`;
63481
63547
  const sign = crypto43.createSign("RSA-SHA256");
@@ -90767,6 +90833,9 @@ async function apiRoutes(app, opts) {
90767
90833
  setTelemetryEnabled: opts.setTelemetryEnabled,
90768
90834
  recordOnboardingEvent: opts.recordOnboardingEvent
90769
90835
  });
90836
+ await api.register(feedbackRoutes, {
90837
+ submitFeedback: opts.submitFeedback
90838
+ });
90770
90839
  await api.register(adsRoutes, {
90771
90840
  adsCredentialStore: opts.adsCredentialStore,
90772
90841
  verifyAdsAccount: opts.verifyAdsAccount,
@@ -33,6 +33,8 @@ import {
33
33
  discoveryCompetitorTypeSchema,
34
34
  discoveryPromoteRequestSchema,
35
35
  discoveryRunRequestSchema,
36
+ feedbackAcceptedDtoSchema,
37
+ feedbackSubmissionSchema,
36
38
  gaMeasurementAnalysisWindowSchema,
37
39
  gaMeasurementHostScopeSchema,
38
40
  googleAdsMetricsWindowSchema,
@@ -128,7 +130,7 @@ import {
128
130
  trafficSeriesGranularitySchema,
129
131
  visibilityCompareSelectionSchema,
130
132
  visibilityReportRequestSchema
131
- } from "./chunk-6EPN4XY5.js";
133
+ } from "./chunk-EV5G5QRR.js";
132
134
 
133
135
  // src/cli-error.ts
134
136
  function isMachineFormat(format) {
@@ -3122,6 +3124,18 @@ var canonryMcpTools = [
3122
3124
  openApiOperations: ["POST /api/v1/projects/{name}/runs"],
3123
3125
  handler: (client, input) => client.triggerRun(input.project, input.request)
3124
3126
  }),
3127
+ defineTool({
3128
+ name: "canonry_feedback",
3129
+ title: "Send feedback about Canonry",
3130
+ description: "Send feedback about Canonry itself to the Canonry team (canonry.ai). Use it when you uncover a struggle (a failure, confusing output, or something you had to work around) or have a concrete improvement. Tell the user what you are sending first, and send one report per issue. Be specific: the command or tool, the error code, and what you did instead. Never include secrets, API keys, or customer data; credentials are redacted before sending anyway. Not for questions about the user's own project data.",
3131
+ access: "write",
3132
+ tier: "core",
3133
+ inputSchema: feedbackSubmissionSchema,
3134
+ outputSchema: feedbackAcceptedDtoSchema,
3135
+ annotations: writeAnnotations({ idempotentHint: false, openWorldHint: true }),
3136
+ openApiOperations: ["POST /api/v1/feedback"],
3137
+ handler: (client, input) => client.sendFeedback(input)
3138
+ }),
3125
3139
  defineTool({
3126
3140
  name: "canonry_run_cancel",
3127
3141
  title: "Cancel run",