@ainyc/canonry 5.1.1 → 5.1.3

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 (27) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +2 -0
  2. package/assets/assets/{AuditHistoryPanel-9F_74jKS.js → AuditHistoryPanel-Dc_pymFg.js} +1 -1
  3. package/assets/assets/{BacklinksPage-DmF2rbwy.js → BacklinksPage-BkSo48YX.js} +1 -1
  4. package/assets/assets/{HistoryPage-DKopwAjo.js → HistoryPage-BbswsdU7.js} +1 -1
  5. package/assets/assets/{MeasurementPropertyPage-Dpd_DYQv.js → MeasurementPropertyPage-BbgtalkM.js} +1 -1
  6. package/assets/assets/{ProjectPage-b1-57SzP.js → ProjectPage-CAcRQp-T.js} +1 -1
  7. package/assets/assets/{RunRow-BjrSRp6X.js → RunRow-2cNW8FHz.js} +1 -1
  8. package/assets/assets/{RunsPage-BFrymZ7D.js → RunsPage-Cz3lJ6VJ.js} +1 -1
  9. package/assets/assets/{SettingsPage-DdVa6pLK.js → SettingsPage-BQwB0r9k.js} +1 -1
  10. package/assets/assets/{SiteHealthSection-CizmiBjA.js → SiteHealthSection-C0aJ_UhN.js} +3 -3
  11. package/assets/assets/{TrafficPage-N7sZgb9x.js → TrafficPage-DTic3zhO.js} +1 -1
  12. package/assets/assets/{TrafficSourceDetailPage-BNQmMJ5D.js → TrafficSourceDetailPage-BKfabnCw.js} +1 -1
  13. package/assets/assets/{extract-error-message-Bh9mRIz9.js → extract-error-message-BzB8j3fC.js} +1 -1
  14. package/assets/assets/{index-BnR1Tu9j.js → index-DQGEzxrN.js} +3 -3
  15. package/assets/assets/{react-sigma_core.esm.min-DGaYwjLZ.js → react-sigma_core.esm.min-rYv0aKKx.js} +1 -1
  16. package/assets/assets/{v2-overview-adapter-eqN-8OK5.js → v2-overview-adapter-CWWPfsm-.js} +1 -1
  17. package/assets/index.html +1 -1
  18. package/dist/{chunk-ZWVXUPLP.js → chunk-5ZOLPHTO.js} +286 -96
  19. package/dist/{chunk-MEQGGODM.js → chunk-AEE5DGTE.js} +92 -8
  20. package/dist/{chunk-LHHJVGH7.js → chunk-EBUX3C5H.js} +110 -15
  21. package/dist/{chunk-66KGNC4U.js → chunk-KJM4DRZN.js} +2 -2
  22. package/dist/{chunk-3DL3KV6C.js → chunk-MMSPU72Z.js} +113 -1
  23. package/dist/cli.js +25 -26
  24. package/dist/index.js +4 -4
  25. package/dist/{intelligence-service-7XX5ZRYE.js → intelligence-service-25NQCETC.js} +2 -2
  26. package/dist/mcp.js +49 -7
  27. package/package.json +7 -7
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AGENT_MEMORY_KEY_MAX_LENGTH,
3
3
  AGENT_MEMORY_VALUE_MAX_BYTES,
4
+ CANONRY_NPM_PACKAGE_URL,
4
5
  CodingAgents,
5
6
  DEFAULT_VIEWER_RESEARCH_DAILY_RUN_LIMIT,
6
7
  DISCOVERY_MAX_PROBES_CAP,
@@ -9,6 +10,7 @@ import {
9
10
  RESEARCH_RUN_SCOPE,
10
11
  SKILL_MANIFEST_FILENAME,
11
12
  SkillsClients,
13
+ USAGE_TELEMETRY_HEADERS,
12
14
  adsActivateTreeRequestSchema,
13
15
  adsAdCreateRequestSchema,
14
16
  adsAdGroupCreateRequestSchema,
@@ -27,9 +29,11 @@ import {
27
29
  canonicalizeGtmResourceSelection,
28
30
  classifySkillFile,
29
31
  coerceSkillManifest,
32
+ compareSemver,
30
33
  competitorBatchRequestSchema,
31
34
  competitorLandscapeQuerySchema,
32
35
  describeError,
36
+ detectAgentRuntime,
33
37
  discoveryBucketSchema,
34
38
  discoveryCompetitorTypeSchema,
35
39
  discoveryPromoteRequestSchema,
@@ -38,8 +42,10 @@ import {
38
42
  gaMeasurementHostScopeSchema,
39
43
  googleAdsMetricsWindowSchema,
40
44
  gscPerformanceOrderBySchema,
45
+ isInstallMethod,
41
46
  isReadOnlyKey,
42
47
  isSensitiveDiagnosticQueryKey,
48
+ isStrictSemver,
43
49
  keywordBatchRequestSchema,
44
50
  keywordGenerateRequestSchema,
45
51
  logQuerySchema,
@@ -82,6 +88,7 @@ import {
82
88
  measurementQueryTemplateApplyRequestSchema,
83
89
  measurementQueryTemplateUpsertRequestSchema,
84
90
  measurementQuestionResultQuerySchema,
91
+ normalizeAgentSlug,
85
92
  normalizeTelemetryStatus,
86
93
  notificationCreateRequestSchema,
87
94
  notificationEventSchema,
@@ -114,8 +121,10 @@ import {
114
121
  trafficConnectWordpressRequestSchema,
115
122
  trafficEventKindSchema,
116
123
  trafficSeriesGranularitySchema,
124
+ upgradeCaveatFor,
125
+ upgradeCommandFor,
117
126
  visibilityReportRequestSchema
118
- } from "./chunk-3DL3KV6C.js";
127
+ } from "./chunk-MMSPU72Z.js";
119
128
 
120
129
  // src/cli-error.ts
121
130
  function isMachineFormat(format) {
@@ -609,6 +618,9 @@ function configExists() {
609
618
  return fs.existsSync(getConfigPath());
610
619
  }
611
620
 
621
+ // src/client.ts
622
+ import { AsyncLocalStorage } from "async_hooks";
623
+
612
624
  // src/client-reliability.ts
613
625
  function parseRetryAfterMs(header, now = Date.now()) {
614
626
  if (!header) return void 0;
@@ -6021,6 +6033,21 @@ function createClient2(opts) {
6021
6033
  }
6022
6034
 
6023
6035
  // src/client.ts
6036
+ function parseServerUpdateAvailable(value) {
6037
+ if (!value || typeof value !== "object") return null;
6038
+ const { current, latest, installMethod } = value;
6039
+ if (typeof current !== "string" || current.length > 32 || !isStrictSemver(current)) return null;
6040
+ if (typeof latest !== "string" || latest.length > 32 || !isStrictSemver(latest)) return null;
6041
+ if (compareSemver(latest, current) <= 0) return null;
6042
+ const method = installMethod === void 0 ? "npm" : installMethod;
6043
+ if (!isInstallMethod(method)) return null;
6044
+ return { current, latest, installMethod: method, upgradeCommand: upgradeCommandFor(method), url: CANONRY_NPM_PACKAGE_URL };
6045
+ }
6046
+ var usageTagStore = new AsyncLocalStorage();
6047
+ function runWithUsageTags(client2, tags, fn) {
6048
+ if (!(client2 instanceof ApiClient)) return fn();
6049
+ return usageTagStore.run(tags, fn);
6050
+ }
6024
6051
  var SAFE_CLIENT_NAME = /^\w[\w./-]{0,127}$/;
6025
6052
  var SAFE_ACTOR_SESSION = /^\w[\w.:-]{0,127}$/;
6026
6053
  function safeClientName(value) {
@@ -6049,12 +6076,24 @@ var ApiClient = class {
6049
6076
  this.probeSkipped = opts?.skipProbe ?? false;
6050
6077
  this.heyClientBaseUrl = this.originUrl;
6051
6078
  const actorSession = safeActorSession(opts?.actorSession);
6079
+ const surface = opts?.surface ?? "cli";
6080
+ const serverHosted = surface === "mcp-http" || surface === "aero";
6052
6081
  this.requestHeaders = {
6053
6082
  authorization: `Bearer ${this.apiKey}`,
6054
6083
  "user-agent": safeClientName(opts?.clientName),
6055
- ...actorSession ? { "x-canonry-actor-session": actorSession } : {}
6084
+ ...actorSession ? { "x-canonry-actor-session": actorSession } : {},
6085
+ [USAGE_TELEMETRY_HEADERS.surface]: surface,
6086
+ ...serverHosted ? {} : { [USAGE_TELEMETRY_HEADERS.agent]: detectAgentRuntime(process.env) }
6056
6087
  };
6057
6088
  this.heyClient = createClient2({ baseUrl: this.originUrl, apiKey: this.apiKey, headers: this.requestHeaders });
6089
+ this.heyClient.interceptors.request.use((request) => {
6090
+ const tags = usageTagStore.getStore();
6091
+ if (tags?.mcpTool) request.headers.set(USAGE_TELEMETRY_HEADERS.mcpTool, tags.mcpTool);
6092
+ if (tags?.mcpCall) request.headers.set(USAGE_TELEMETRY_HEADERS.mcpCall, tags.mcpCall);
6093
+ const mcpClient = normalizeAgentSlug(tags?.mcpClient);
6094
+ if (mcpClient) request.headers.set(USAGE_TELEMETRY_HEADERS.mcpClient, mcpClient);
6095
+ return request;
6096
+ });
6058
6097
  }
6059
6098
  /**
6060
6099
  * On first API call, probe /health to auto-discover basePath when the user
@@ -6330,7 +6369,7 @@ var ApiClient = class {
6330
6369
  let res;
6331
6370
  try {
6332
6371
  res = await fetch(url, {
6333
- headers: { Authorization: `Bearer ${this.apiKey}` }
6372
+ headers: { ...this.requestHeaders }
6334
6373
  });
6335
6374
  } catch (err) {
6336
6375
  const message = describeError(err);
@@ -7072,6 +7111,22 @@ var ApiClient = class {
7072
7111
  async getApiKeySelf() {
7073
7112
  return this.invoke(() => getApiV1KeysSelf({ client: this.heyClient }));
7074
7113
  }
7114
+ /**
7115
+ * The connected server's update notice from `/health`, or null. Best-effort
7116
+ * and bounded (2s), never throws: `canonry-mcp` uses it to tell agents that
7117
+ * never run the CLI about a newer release.
7118
+ */
7119
+ async getServerUpdateAvailable() {
7120
+ try {
7121
+ await this.probeBasePath();
7122
+ const res = await fetch(`${this.originUrl}/health`, { signal: AbortSignal.timeout(2e3) });
7123
+ if (!res.ok) return null;
7124
+ const body = await res.json();
7125
+ return parseServerUpdateAvailable(body?.updateAvailable);
7126
+ } catch {
7127
+ return null;
7128
+ }
7129
+ }
7075
7130
  async createApiKey(body) {
7076
7131
  return this.invoke(() => postApiV1Keys({ client: this.heyClient, body }));
7077
7132
  }
@@ -8939,6 +8994,7 @@ var ApiClient = class {
8939
8994
  };
8940
8995
 
8941
8996
  // src/mcp/server.ts
8997
+ import { randomUUID } from "crypto";
8942
8998
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8943
8999
  import { z as z5 } from "zod";
8944
9000
 
@@ -13115,15 +13171,34 @@ function createCanonryMcpServer(options = {}) {
13115
13171
  return createCanonryMcpServerWithCatalog(options).server;
13116
13172
  }
13117
13173
  var SERVER_INSTRUCTIONS = OPERATIONS_GUIDE.initialize;
13174
+ function updateNoticeInstructions(update) {
13175
+ const upgrade = update.installMethod === "docker" ? update.upgradeCommand : `${update.upgradeCommand}, then restart the Canonry server`;
13176
+ const caveat = upgradeCaveatFor(update.installMethod);
13177
+ return `Update available (UPDATE_AVAILABLE): canonry ${update.latest} is published; the connected Canonry server runs ${update.current}. Tell the operator. Upgrade: ${upgrade}. ${caveat ? `${caveat} ` : ""}Only upgrade with the operator's approval.`;
13178
+ }
13179
+ function updateNoticePayload(update) {
13180
+ const caveat = upgradeCaveatFor(update.installMethod);
13181
+ return { code: "UPDATE_AVAILABLE", ...update, ...caveat ? { note: caveat } : {} };
13182
+ }
13183
+ function readUpdateAvailable(getter) {
13184
+ try {
13185
+ return getter?.() ?? null;
13186
+ } catch {
13187
+ return null;
13188
+ }
13189
+ }
13118
13190
  function createCanonryMcpServerWithCatalog(options = {}) {
13119
- const clientFactory = options.clientFactory ?? (() => createApiClient({ clientName: "canonry-mcp" }));
13191
+ const clientFactory = options.clientFactory ?? (() => createApiClient({ clientName: "canonry-mcp", surface: "mcp-stdio", actorSession: randomUUID() }));
13120
13192
  const client2 = clientFactory();
13121
13193
  const scope = options.scope ?? "all";
13194
+ const update = readUpdateAvailable(options.updateAvailable);
13122
13195
  const server = new McpServer({
13123
13196
  name: "canonry",
13124
13197
  version: PACKAGE_VERSION
13125
13198
  }, {
13126
- instructions: SERVER_INSTRUCTIONS
13199
+ instructions: update ? `${SERVER_INSTRUCTIONS.trimEnd()}
13200
+
13201
+ ${updateNoticeInstructions(update)}` : SERVER_INSTRUCTIONS
13127
13202
  });
13128
13203
  server.validateToolInput = async (_tool, args) => args;
13129
13204
  const entries = [];
@@ -13141,7 +13216,11 @@ function createCanonryMcpServerWithCatalog(options = {}) {
13141
13216
  },
13142
13217
  async (input) => withToolErrors(async () => {
13143
13218
  const parsed = tool.inputSchema.parse(input ?? {});
13144
- return handler(client2, parsed);
13219
+ return runWithUsageTags(client2, {
13220
+ mcpTool: tool.name,
13221
+ mcpCall: randomUUID(),
13222
+ mcpClient: server.server.getClientVersion()?.name
13223
+ }, () => handler(client2, parsed));
13145
13224
  })
13146
13225
  );
13147
13226
  entries.push({ tool, registered });
@@ -13150,7 +13229,7 @@ function createCanonryMcpServerWithCatalog(options = {}) {
13150
13229
  const catalog = new DynamicToolCatalog(server, entries, scope, { eager });
13151
13230
  catalog.applyInitialEnablement();
13152
13231
  const mode = options.tiers !== void 0 ? "hosted-fixed-catalog" : eager ? "stdio-fixed-catalog" : "stdio-progressive";
13153
- registerMetaTools(server, catalog, { includeToolkitLoader: options.tiers === void 0, mode });
13232
+ registerMetaTools(server, catalog, { includeToolkitLoader: options.tiers === void 0, mode, updateAvailable: options.updateAvailable });
13154
13233
  server.registerResource("canonry-agent-operations-v1", OPERATIONS_GUIDE.resourceUri, {
13155
13234
  title: "Canonry Operations Guide v1",
13156
13235
  description: "Optional public operations guidance. Use canonry_help when resources are unavailable.",
@@ -13177,7 +13256,11 @@ function registerMetaTools(server, catalog, opts) {
13177
13256
  async (input) => {
13178
13257
  try {
13179
13258
  const parsed = helpInputSchema.parse(input ?? {});
13180
- const result = operationsHelp(catalog.helpResult(), opts.mode, parsed.intent, parsed.includeCatalog);
13259
+ const update = readUpdateAvailable(opts.updateAvailable);
13260
+ const result = {
13261
+ ...operationsHelp(catalog.helpResult(), opts.mode, parsed.intent, parsed.includeCatalog),
13262
+ ...update ? { updateAvailable: updateNoticePayload(update) } : {}
13263
+ };
13181
13264
  return { ...jsonToolResult(result), structuredContent: result };
13182
13265
  } catch (error) {
13183
13266
  return errorToolResult(error);
@@ -13618,6 +13701,7 @@ export {
13618
13701
  emitInstallSummary,
13619
13702
  getMissingUserSkillsNudge,
13620
13703
  parseSkillsClient,
13704
+ runWithUsageTags,
13621
13705
  createApiClient,
13622
13706
  ApiClient,
13623
13707
  measurementDraftOperationSchema,
@@ -124,6 +124,7 @@ import {
124
124
  TrafficSourceAuthModes,
125
125
  TrafficSourceStatuses,
126
126
  TrafficSourceTypes,
127
+ USAGE_TELEMETRY_HEADERS,
127
128
  UserRoles,
128
129
  VerificationStatuses,
129
130
  WILDCARD_SCOPE,
@@ -240,6 +241,7 @@ import {
240
241
  clusterByCosine,
241
242
  coerceSkillManifest,
242
243
  compactDateToIso,
244
+ compareSemver,
243
245
  competitorBatchRequestSchema,
244
246
  competitorDtoSchema,
245
247
  competitorLandscapeQuerySchema,
@@ -390,6 +392,7 @@ import {
390
392
  isLocationRedirectStatus,
391
393
  isReadOnlyKey,
392
394
  isRetryableHttpError,
395
+ isStrictSemver,
393
396
  isTemplateLinkRatio,
394
397
  isTrendBaseline,
395
398
  isVertexGroundingRedirect,
@@ -677,6 +680,7 @@ import {
677
680
  trafficStatusResponseSchema,
678
681
  trafficSyncResponseSchema,
679
682
  unsupportedKind,
683
+ upgradeCaveatFor,
680
684
  userDtoSchema,
681
685
  userListDtoSchema,
682
686
  validationError,
@@ -702,7 +706,7 @@ import {
702
706
  wordpressSchemaDeployResultDtoSchema,
703
707
  wordpressSchemaStatusResultDtoSchema,
704
708
  wordpressStatusDtoSchema
705
- } from "./chunk-3DL3KV6C.js";
709
+ } from "./chunk-MMSPU72Z.js";
706
710
 
707
711
  // src/intelligence-service.ts
708
712
  import { eq as eq67, desc as desc29, asc as asc12, and as and57, ne as ne11, or as or16, inArray as inArray25, gte as gte18, lte as lte15, isNull as isNull12, sql as sql27, exists } from "drizzle-orm";
@@ -11551,7 +11555,11 @@ function requestAuditIdentity(request) {
11551
11555
  credentialId: request.apiKey?.id ?? principal.id
11552
11556
  };
11553
11557
  }
11554
- function registerRequestContext(app) {
11558
+ function headerValue(value) {
11559
+ const first = Array.isArray(value) ? value[0] : value;
11560
+ return first ? first.slice(0, MAX_REQUEST_CONTEXT_LENGTH) : void 0;
11561
+ }
11562
+ function registerRequestContext(app, options = {}) {
11555
11563
  app.addHook("onRequest", (request, reply, done) => {
11556
11564
  reply.header("x-request-id", request.id);
11557
11565
  requestContext.run({
@@ -11575,6 +11583,29 @@ function registerRequestContext(app) {
11575
11583
  populateIdentity(request);
11576
11584
  const context = requestContext.getStore();
11577
11585
  if (context) context.statusCode = reply.statusCode;
11586
+ const route = request.routeOptions.url;
11587
+ if (options.onRequestCompleted && route) {
11588
+ try {
11589
+ const kind = request.principal?.kind;
11590
+ options.onRequestCompleted({
11591
+ method: request.method,
11592
+ route,
11593
+ statusCode: reply.statusCode,
11594
+ durationMs: reply.elapsedTime,
11595
+ userAgent: headerValue(request.headers["user-agent"]),
11596
+ actorSession: headerValue(request.headers["x-canonry-actor-session"]),
11597
+ principalKind: kind === "user" ? "user" : kind ? "api-key" : void 0,
11598
+ usageLabels: {
11599
+ surface: headerValue(request.headers[USAGE_TELEMETRY_HEADERS.surface]),
11600
+ agent: headerValue(request.headers[USAGE_TELEMETRY_HEADERS.agent]),
11601
+ mcpClient: headerValue(request.headers[USAGE_TELEMETRY_HEADERS.mcpClient]),
11602
+ mcpTool: headerValue(request.headers[USAGE_TELEMETRY_HEADERS.mcpTool]),
11603
+ mcpCall: headerValue(request.headers[USAGE_TELEMETRY_HEADERS.mcpCall])
11604
+ }
11605
+ });
11606
+ } catch {
11607
+ }
11608
+ }
11578
11609
  done();
11579
11610
  if (context) context.completed = true;
11580
11611
  });
@@ -12885,13 +12916,13 @@ function activePlanVersionRow(db, projectId) {
12885
12916
  if (!version) throw new Error(`Measurement plan ${projectId} points to missing version ${pointer.activeVersionId}`);
12886
12917
  return version;
12887
12918
  }
12888
- function headerValue(request, name) {
12919
+ function headerValue2(request, name) {
12889
12920
  const raw = request.headers[name];
12890
12921
  const value = Array.isArray(raw) ? raw[0] : raw;
12891
12922
  return typeof value === "string" && value.trim() ? value.trim() : null;
12892
12923
  }
12893
12924
  function requireIfMatch(request) {
12894
- const value = headerValue(request, "if-match");
12925
+ const value = headerValue2(request, "if-match");
12895
12926
  if (!value) throw measurementDraftEtagRequired();
12896
12927
  return value;
12897
12928
  }
@@ -12902,7 +12933,7 @@ function assertDraftEtag(row, ifMatch) {
12902
12933
  }
12903
12934
  }
12904
12935
  function requireIdempotencyKey(request, operation) {
12905
- const value = headerValue(request, "idempotency-key");
12936
+ const value = headerValue2(request, "idempotency-key");
12906
12937
  if (!value) throw measurementIdempotencyKeyRequired(operation);
12907
12938
  return value;
12908
12939
  }
@@ -21091,20 +21122,20 @@ function loadDraft(app, projectId, projectName) {
21091
21122
  }
21092
21123
  return { row, authoring: parsed.data };
21093
21124
  }
21094
- function headerValue2(request, name) {
21125
+ function headerValue3(request, name) {
21095
21126
  const value = request.headers[name];
21096
21127
  const first = Array.isArray(value) ? value[0] : value;
21097
21128
  return typeof first === "string" && first.trim() ? first.trim() : void 0;
21098
21129
  }
21099
21130
  function requireCurrentEtag(request, draft) {
21100
- const supplied = headerValue2(request, "if-match");
21131
+ const supplied = headerValue3(request, "if-match");
21101
21132
  if (!supplied) throw measurementDraftEtagRequired();
21102
21133
  if (parseMeasurementDraftEtagVersion(supplied) !== draft.row.etagVersion) {
21103
21134
  throw measurementDraftEtagStale(measurementDraftEtag(draft.row.etagVersion), supplied);
21104
21135
  }
21105
21136
  }
21106
21137
  function requireIdempotencyKey2(request, operation) {
21107
- const supplied = headerValue2(request, "idempotency-key");
21138
+ const supplied = headerValue3(request, "idempotency-key");
21108
21139
  if (!supplied) throw measurementIdempotencyKeyRequired(operation);
21109
21140
  return supplied;
21110
21141
  }
@@ -44501,9 +44532,9 @@ async function withGa4Limit(fn) {
44501
44532
  releaseGa4Slot();
44502
44533
  }
44503
44534
  }
44504
- function parseRetryAfter(headerValue3) {
44505
- if (!headerValue3) return void 0;
44506
- const trimmed = headerValue3.trim();
44535
+ function parseRetryAfter(headerValue4) {
44536
+ if (!headerValue4) return void 0;
44537
+ const trimmed = headerValue4.trim();
44507
44538
  const asNum = Number(trimmed);
44508
44539
  if (Number.isFinite(asNum) && asNum >= 0) return asNum;
44509
44540
  const asDate = Date.parse(trimmed);
@@ -73847,9 +73878,9 @@ var VercelLogsApiError = class extends Error {
73847
73878
  body;
73848
73879
  retryAfterSeconds;
73849
73880
  };
73850
- function parseRetryAfter2(headerValue3) {
73851
- if (!headerValue3) return void 0;
73852
- const trimmed = headerValue3.trim();
73881
+ function parseRetryAfter2(headerValue4) {
73882
+ if (!headerValue4) return void 0;
73883
+ const trimmed = headerValue4.trim();
73853
73884
  const asNum = Number(trimmed);
73854
73885
  if (Number.isFinite(asNum) && asNum >= 0) return asNum;
73855
73886
  const asDate = Date.parse(trimmed);
@@ -81274,11 +81305,72 @@ var GOOGLE_MARKETING_DOCTOR_CHECKS = createGoogleMarketingDoctorChecks(
81274
81305
  (ctx) => ctx.getGoogleMarketingDoctorInput?.(ctx)
81275
81306
  );
81276
81307
 
81308
+ // ../api-routes/src/doctor/checks/version.ts
81309
+ var versionCurrentCheck = {
81310
+ id: "canonry.version.current",
81311
+ category: CheckCategories.config,
81312
+ scope: CheckScopes.global,
81313
+ title: "Canonry version",
81314
+ run: (ctx) => {
81315
+ if (!ctx.getUpdateStatus) {
81316
+ return {
81317
+ status: CheckStatuses.skipped,
81318
+ code: "version.status-unavailable",
81319
+ summary: "This deployment does not report update status."
81320
+ };
81321
+ }
81322
+ const status = ctx.getUpdateStatus();
81323
+ if (!status.enabled) {
81324
+ return {
81325
+ status: CheckStatuses.skipped,
81326
+ code: "version.check-disabled",
81327
+ summary: `Update check is off (${status.disabledBy ?? "opted out"}); running ${status.current}.`,
81328
+ details: { current: status.current, ...status.disabledBy ? { disabledBy: status.disabledBy } : {} }
81329
+ };
81330
+ }
81331
+ if (!status.latest || !isStrictSemver(status.latest)) {
81332
+ return {
81333
+ status: CheckStatuses.skipped,
81334
+ code: "version.latest-unknown",
81335
+ summary: `Latest published version is not known yet (running ${status.current}).`,
81336
+ remediation: "The npm registry has not been reached yet. Re-run doctor in a moment; offline hosts stay skipped.",
81337
+ details: { current: status.current }
81338
+ };
81339
+ }
81340
+ if (compareSemver(status.latest, status.current) > 0) {
81341
+ const caveat = upgradeCaveatFor(status.installMethod);
81342
+ return {
81343
+ status: CheckStatuses.warn,
81344
+ code: "version.outdated",
81345
+ summary: `canonry ${status.latest} is available; this server runs ${status.current}.`,
81346
+ remediation: status.installMethod === "docker" ? `Upgrade the container: ${status.upgradeCommand}.` : `Run \`${status.upgradeCommand}\`, then restart the server (\`canonry stop && canonry start\`, or restart \`canonry serve\`).${caveat ? ` ${caveat}` : ""}`,
81347
+ details: {
81348
+ current: status.current,
81349
+ latest: status.latest,
81350
+ installMethod: status.installMethod,
81351
+ upgradeCommand: status.upgradeCommand,
81352
+ url: status.url
81353
+ }
81354
+ };
81355
+ }
81356
+ return {
81357
+ status: CheckStatuses.ok,
81358
+ code: "version.current",
81359
+ // "latest" would overstate it: when the registry is unreachable this can
81360
+ // come from an older on-disk cache, which proves only that nothing newer is known.
81361
+ summary: `No newer canonry known (running ${status.current}; latest seen ${status.latest}).`,
81362
+ details: { current: status.current, latest: status.latest }
81363
+ };
81364
+ }
81365
+ };
81366
+ var VERSION_CHECKS = [versionCurrentCheck];
81367
+
81277
81368
  // ../api-routes/src/doctor/registry.ts
81278
81369
  var ALL_CHECKS = [
81279
81370
  // Runtime-state checks run first so file-system gone errors surface
81280
81371
  // before any auth/integration checks try to touch the (orphaned) DB.
81281
81372
  ...RUNTIME_STATE_CHECKS,
81373
+ ...VERSION_CHECKS,
81282
81374
  ...GOOGLE_AUTH_CHECKS,
81283
81375
  ...GBP_AUTH_CHECKS,
81284
81376
  ...PLACES_CHECKS,
@@ -81384,6 +81476,7 @@ async function doctorRoutes(app, opts) {
81384
81476
  runtimeStatePaths: opts.runtimeStatePaths,
81385
81477
  bundledSkills: opts.bundledSkills,
81386
81478
  getAgentPluginState: opts.getAgentPluginState,
81479
+ getUpdateStatus: opts.getUpdateStatus,
81387
81480
  getGoogleMarketingDoctorInput: opts.getGoogleMarketingDoctorInput
81388
81481
  };
81389
81482
  return runChecks(ctx, ALL_CHECKS, { checkIds });
@@ -81413,6 +81506,7 @@ async function doctorRoutes(app, opts) {
81413
81506
  runtimeStatePaths: opts.runtimeStatePaths,
81414
81507
  bundledSkills: opts.bundledSkills,
81415
81508
  getAgentPluginState: opts.getAgentPluginState,
81509
+ getUpdateStatus: opts.getUpdateStatus,
81416
81510
  getGoogleMarketingDoctorInput: opts.getGoogleMarketingDoctorInput
81417
81511
  };
81418
81512
  return runChecks(ctx, ALL_CHECKS, { checkIds });
@@ -84469,7 +84563,7 @@ async function apiRoutes(app, opts) {
84469
84563
  });
84470
84564
  }
84471
84565
  await app.register(async (api) => {
84472
- registerRequestContext(api);
84566
+ registerRequestContext(api, { onRequestCompleted: opts.onRequestCompleted });
84473
84567
  await api.register(rateLimit, {
84474
84568
  global: false,
84475
84569
  hook: "preHandler",
@@ -84722,6 +84816,7 @@ async function apiRoutes(app, opts) {
84722
84816
  runtimeStatePaths: opts.runtimeStatePaths,
84723
84817
  bundledSkills: opts.bundledSkills,
84724
84818
  getAgentPluginState: opts.getAgentPluginState,
84819
+ getUpdateStatus: opts.getUpdateStatus,
84725
84820
  getGoogleMarketingDoctorInput: opts.getGoogleMarketingDoctorInput
84726
84821
  });
84727
84822
  if (opts.registerAuthenticatedRoutes) {
@@ -5,11 +5,11 @@ import {
5
5
  installSkills,
6
6
  loadConfigRaw,
7
7
  saveConfigPatch
8
- } from "./chunk-MEQGGODM.js";
8
+ } from "./chunk-AEE5DGTE.js";
9
9
  import {
10
10
  SKILL_MANIFEST_FILENAME,
11
11
  SkillsClients
12
- } from "./chunk-3DL3KV6C.js";
12
+ } from "./chunk-MMSPU72Z.js";
13
13
 
14
14
  // src/skills-autosync.ts
15
15
  import fs from "fs";
@@ -17782,6 +17782,49 @@ function isGhostTelemetryEvent(eventName, properties) {
17782
17782
  const location = typeof properties.location === "string" ? properties.location.trim().toLowerCase() : "";
17783
17783
  return GHOST_TELEMETRY_TEST_LOCATIONS.has(location);
17784
17784
  }
17785
+ var usageSurfaceSchema = z60.enum(["cli", "mcp-stdio", "mcp-http", "aero", "api", "dashboard"]);
17786
+ var USAGE_TELEMETRY_HEADERS = {
17787
+ surface: "x-canonry-surface",
17788
+ agent: "x-canonry-agent",
17789
+ mcpClient: "x-canonry-mcp-client",
17790
+ mcpTool: "x-canonry-mcp-tool",
17791
+ mcpCall: "x-canonry-mcp-call"
17792
+ };
17793
+ var AGENT_NONE = "none";
17794
+ var AGENT_SLUG_PATTERN = /^[a-z0-9][a-z0-9._-]{0,39}$/;
17795
+ function normalizeAgentSlug(value) {
17796
+ if (!value) return null;
17797
+ const slug = value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").slice(0, 40).replace(/^[-._]+|[-._]+$/g, "");
17798
+ return AGENT_SLUG_PATTERN.test(slug) ? slug : null;
17799
+ }
17800
+ var AI_AGENT_ALIASES = {
17801
+ "claude-code": "claude",
17802
+ "github-copilot-cli": "github-copilot"
17803
+ };
17804
+ function agentFromAiAgentVariable(value) {
17805
+ const name = value.trim().toLowerCase().replace(/_agent$/, "").replace(/_\d[\d._-]*$/, "");
17806
+ const slug = normalizeAgentSlug(name);
17807
+ return slug ? AI_AGENT_ALIASES[slug] ?? slug : null;
17808
+ }
17809
+ function detectAgentRuntime(env) {
17810
+ const explicit = normalizeAgentSlug(env.CANONRY_AGENT);
17811
+ if (explicit) return explicit;
17812
+ if (env.AI_AGENT?.trim()) {
17813
+ const named = agentFromAiAgentVariable(env.AI_AGENT);
17814
+ if (named) return named;
17815
+ }
17816
+ if (env.CURSOR_TRACE_ID) return "cursor";
17817
+ if (env.CURSOR_AGENT || env.CURSOR_EXTENSION_HOST_ROLE === "agent-exec") return "cursor-cli";
17818
+ if (env.GEMINI_CLI) return "gemini";
17819
+ if (env.CODEX_SANDBOX || env.CODEX_CI || env.CODEX_THREAD_ID) return "codex";
17820
+ if (env.ANTIGRAVITY_AGENT) return "antigravity";
17821
+ if (env.AUGMENT_AGENT) return "augment-cli";
17822
+ if (env.OPENCODE_CLIENT) return "opencode";
17823
+ if (env.CLAUDECODE || env.CLAUDE_CODE) return env.CLAUDE_CODE_IS_COWORK ? "cowork" : "claude";
17824
+ if (env.REPL_ID) return "replit";
17825
+ if (env.COPILOT_MODEL || env.COPILOT_ALLOW_ALL || env.COPILOT_GITHUB_TOKEN) return "github-copilot";
17826
+ return AGENT_NONE;
17827
+ }
17785
17828
 
17786
17829
  // ../contracts/src/trend-stability.ts
17787
17830
  var MIN_TREND_POINTS = 4;
@@ -18011,6 +18054,63 @@ function redactEmbeddedUrls(value) {
18011
18054
  });
18012
18055
  }
18013
18056
 
18057
+ // ../contracts/src/semver.ts
18058
+ var STRICT_SEMVER = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?$/i;
18059
+ var MAX_SEMVER_LENGTH = 128;
18060
+ function isStrictSemver(value) {
18061
+ return value.length <= MAX_SEMVER_LENGTH && STRICT_SEMVER.test(value);
18062
+ }
18063
+ function compareSemver(a, b) {
18064
+ const parse = (v) => {
18065
+ const parts = (v.split(/[-+]/)[0] ?? "").split(".");
18066
+ if (parts.length < 3) return null;
18067
+ const nums = parts.slice(0, 3).map(Number);
18068
+ if (!nums.every((n) => Number.isInteger(n) && n >= 0)) return null;
18069
+ return [nums[0], nums[1], nums[2]];
18070
+ };
18071
+ const pa = parse(a);
18072
+ const pb = parse(b);
18073
+ if (!pa || !pb) return 0;
18074
+ for (let i = 0; i < 3; i++) {
18075
+ if (pa[i] > pb[i]) return 1;
18076
+ if (pa[i] < pb[i]) return -1;
18077
+ }
18078
+ return 0;
18079
+ }
18080
+
18081
+ // ../contracts/src/update-notice.ts
18082
+ var CANONRY_NPM_PACKAGE = "@canonry/canonry";
18083
+ var CANONRY_NPM_PACKAGE_URL = `https://www.npmjs.com/package/${CANONRY_NPM_PACKAGE}`;
18084
+ var INSTALL_METHODS = ["npm", "homebrew", "docker"];
18085
+ function isInstallMethod(value) {
18086
+ return typeof value === "string" && INSTALL_METHODS.includes(value);
18087
+ }
18088
+ function upgradeCommandFor(method) {
18089
+ switch (method) {
18090
+ case "npm":
18091
+ return `npm install -g ${CANONRY_NPM_PACKAGE}`;
18092
+ case "homebrew":
18093
+ return "brew upgrade canonry";
18094
+ case "docker":
18095
+ return "pull or rebuild your canonry image, then recreate the container";
18096
+ }
18097
+ }
18098
+ function upgradeCaveatFor(method) {
18099
+ switch (method) {
18100
+ case "homebrew":
18101
+ return "Homebrew can trail npm briefly; if brew says canonry is up to date, retry later.";
18102
+ case "npm":
18103
+ case "docker":
18104
+ return null;
18105
+ }
18106
+ }
18107
+ function updateCheckEnvOptOut(env) {
18108
+ if (env.CANONRY_DISABLE_UPDATE_CHECK === "1") return "CANONRY_DISABLE_UPDATE_CHECK";
18109
+ if (env.DO_NOT_TRACK === "1") return "DO_NOT_TRACK";
18110
+ if (env.CI) return "CI";
18111
+ return null;
18112
+ }
18113
+
18014
18114
  export {
18015
18115
  __export,
18016
18116
  apiKeyDtoSchema,
@@ -18793,6 +18893,11 @@ export {
18793
18893
  normalizeOnboardingEventForCollection,
18794
18894
  telemetryEventAcceptedDtoSchema,
18795
18895
  isGhostTelemetryEvent,
18896
+ usageSurfaceSchema,
18897
+ USAGE_TELEMETRY_HEADERS,
18898
+ AGENT_NONE,
18899
+ normalizeAgentSlug,
18900
+ detectAgentRuntime,
18796
18901
  MIN_TREND_POINTS,
18797
18902
  isTrendBaseline,
18798
18903
  operationalLogEntryDtoSchema,
@@ -18801,5 +18906,12 @@ export {
18801
18906
  isSensitiveDiagnosticQueryKey,
18802
18907
  redactLogValue,
18803
18908
  redactLogString,
18804
- diagnosticIdentity
18909
+ diagnosticIdentity,
18910
+ isStrictSemver,
18911
+ compareSemver,
18912
+ CANONRY_NPM_PACKAGE_URL,
18913
+ isInstallMethod,
18914
+ upgradeCommandFor,
18915
+ upgradeCaveatFor,
18916
+ updateCheckEnvOptOut
18805
18917
  };