@ainyc/canonry 5.0.1 → 5.1.2

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 (26) hide show
  1. package/assets/assets/{AuditHistoryPanel-9F_74jKS.js → AuditHistoryPanel-Dc_pymFg.js} +1 -1
  2. package/assets/assets/{BacklinksPage-DmF2rbwy.js → BacklinksPage-BkSo48YX.js} +1 -1
  3. package/assets/assets/{HistoryPage-DKopwAjo.js → HistoryPage-BbswsdU7.js} +1 -1
  4. package/assets/assets/{MeasurementPropertyPage-Dpd_DYQv.js → MeasurementPropertyPage-BbgtalkM.js} +1 -1
  5. package/assets/assets/{ProjectPage-b1-57SzP.js → ProjectPage-CAcRQp-T.js} +1 -1
  6. package/assets/assets/{RunRow-BjrSRp6X.js → RunRow-2cNW8FHz.js} +1 -1
  7. package/assets/assets/{RunsPage-BFrymZ7D.js → RunsPage-Cz3lJ6VJ.js} +1 -1
  8. package/assets/assets/{SettingsPage-DdVa6pLK.js → SettingsPage-BQwB0r9k.js} +1 -1
  9. package/assets/assets/{SiteHealthSection-CizmiBjA.js → SiteHealthSection-C0aJ_UhN.js} +3 -3
  10. package/assets/assets/{TrafficPage-N7sZgb9x.js → TrafficPage-DTic3zhO.js} +1 -1
  11. package/assets/assets/{TrafficSourceDetailPage-BNQmMJ5D.js → TrafficSourceDetailPage-BKfabnCw.js} +1 -1
  12. package/assets/assets/{extract-error-message-Bh9mRIz9.js → extract-error-message-BzB8j3fC.js} +1 -1
  13. package/assets/assets/{index-BnR1Tu9j.js → index-DQGEzxrN.js} +3 -3
  14. package/assets/assets/{react-sigma_core.esm.min-DGaYwjLZ.js → react-sigma_core.esm.min-rYv0aKKx.js} +1 -1
  15. package/assets/assets/{v2-overview-adapter-eqN-8OK5.js → v2-overview-adapter-CWWPfsm-.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-MEQGGODM.js → chunk-5K2NUQOV.js} +34 -5
  18. package/dist/{chunk-3DL3KV6C.js → chunk-5ZIUROAQ.js} +48 -0
  19. package/dist/{chunk-GMNZNLVX.js → chunk-BS4ZC7FG.js} +258 -27
  20. package/dist/{chunk-66KGNC4U.js → chunk-NKEKDWGX.js} +2 -2
  21. package/dist/{chunk-LHHJVGH7.js → chunk-WSYJ4IT7.js} +43 -15
  22. package/dist/cli.js +17 -15
  23. package/dist/index.js +4 -4
  24. package/dist/{intelligence-service-7XX5ZRYE.js → intelligence-service-D2HY2S5F.js} +2 -2
  25. package/dist/mcp.js +5 -4
  26. package/package.json +11 -11
@@ -15,8 +15,9 @@ import {
15
15
  loadConfig,
16
16
  loadConfigRaw,
17
17
  resolveOperatorApiKeyIds,
18
+ runWithUsageTags,
18
19
  saveConfigPatch
19
- } from "./chunk-MEQGGODM.js";
20
+ } from "./chunk-5K2NUQOV.js";
20
21
  import {
21
22
  CC_CACHE_DIR,
22
23
  DUCKDB_SPEC,
@@ -167,9 +168,10 @@ import {
167
168
  siteCrawlSnapshots,
168
169
  toAlertView,
169
170
  usageCounters
170
- } from "./chunk-LHHJVGH7.js";
171
+ } from "./chunk-WSYJ4IT7.js";
171
172
  import {
172
173
  AGENT_MEMORY_VALUE_MAX_BYTES,
174
+ AGENT_NONE,
173
175
  AGENT_PROVIDER_IDS,
174
176
  AI_ENGINE_DOMAINS,
175
177
  AI_ENGINE_SELF_DOMAINS,
@@ -247,6 +249,7 @@ import {
247
249
  deriveIndexCoverage,
248
250
  deriveSiteHealthState,
249
251
  describeError,
252
+ detectAgentRuntime,
250
253
  determineAnswerMentioned,
251
254
  dollarsToMicros,
252
255
  effectiveBrandNames,
@@ -283,6 +286,7 @@ import {
283
286
  mapWithConcurrency,
284
287
  maskTelemetryAnonymousId,
285
288
  nextScheduleUpdatedAt,
289
+ normalizeAgentSlug,
286
290
  normalizeIdTokens,
287
291
  normalizeMeasurementExecutionQueryText,
288
292
  normalizeServedModel,
@@ -309,10 +313,19 @@ import {
309
313
  templateLinkUbiquityAvailable,
310
314
  textContainsBrandAlias,
311
315
  textContainsDomain,
316
+ usageSurfaceSchema,
312
317
  validationError,
313
318
  winnabilityClassLabel,
314
319
  withRetry
315
- } from "./chunk-3DL3KV6C.js";
320
+ } from "./chunk-5ZIUROAQ.js";
321
+
322
+ // src/runtime-context.ts
323
+ function cliRuntimeContext(env = process.env, stdio = process) {
324
+ return {
325
+ agent: detectAgentRuntime(env),
326
+ interactive: Boolean(stdio.stdin.isTTY && stdio.stdout.isTTY)
327
+ };
328
+ }
316
329
 
317
330
  // src/telemetry.ts
318
331
  import crypto from "crypto";
@@ -343,7 +356,8 @@ function trackCliCommandFinished(input) {
343
356
  command: input.command,
344
357
  success: input.success,
345
358
  duration_bucket: bucketCliCommandDuration(input.durationMs),
346
- ...input.setupState ? { setup_state: input.setupState } : {}
359
+ ...input.setupState ? { setup_state: input.setupState } : {},
360
+ ...cliRuntimeContext()
347
361
  },
348
362
  input.errorCode ? { errorCode: input.errorCode } : void 0
349
363
  );
@@ -418,7 +432,8 @@ function getOrCreateAnonymousId() {
418
432
  try {
419
433
  const config = loadConfig();
420
434
  if (config.anonymousId) return config.anonymousId;
421
- const id = crypto.randomUUID();
435
+ const carried = preConfigAnonymousId?.configPath === getConfigPath() ? preConfigAnonymousId.id : void 0;
436
+ const id = carried ?? crypto.randomUUID();
422
437
  config.anonymousId = id;
423
438
  try {
424
439
  saveConfigPatch(config);
@@ -430,8 +445,11 @@ function getOrCreateAnonymousId() {
430
445
  return getDeterministicAnonymousId();
431
446
  }
432
447
  }
433
- return getDeterministicAnonymousId();
448
+ const fallback = getDeterministicAnonymousId();
449
+ if (fallback) preConfigAnonymousId = { configPath: getConfigPath(), id: fallback };
450
+ return fallback;
434
451
  }
452
+ var preConfigAnonymousId;
435
453
  function readEnvAnonymousId() {
436
454
  const raw = process.env[ANON_ID_ENV_VAR]?.trim();
437
455
  if (!raw) return void 0;
@@ -506,10 +524,19 @@ function detectAndTrackUpgrade() {
506
524
  trackEvent("cli.upgraded", { fromVersion: lastSeen, toVersion: VERSION });
507
525
  }
508
526
  function trackEvent(event, properties, options) {
509
- if (!isTelemetryEnabled()) return;
510
- if (shouldDropTelemetryEvent(event, properties)) return;
527
+ void deliverEvent(event, properties, options);
528
+ }
529
+ function setTelemetryPreference(enabled, method) {
530
+ loadConfig();
531
+ const announce = !enabled && isTelemetryEnabled();
532
+ saveConfigPatch({ telemetry: enabled });
533
+ return announce ? deliverEvent("telemetry.disabled", { method }, void 0, { preferenceChecked: true }) : Promise.resolve();
534
+ }
535
+ function deliverEvent(event, properties, options, delivery = {}) {
536
+ if (!delivery.preferenceChecked && !isTelemetryEnabled()) return Promise.resolve();
537
+ if (shouldDropTelemetryEvent(event, properties)) return Promise.resolve();
511
538
  const anonymousId = getOrCreateAnonymousId();
512
- if (!anonymousId) return;
539
+ if (!anonymousId) return Promise.resolve();
513
540
  const payload = {
514
541
  eventId: options?.eventId ?? crypto.randomUUID(),
515
542
  anonymousId,
@@ -529,15 +556,15 @@ function trackEvent(event, properties, options) {
529
556
  const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
530
557
  timeout.unref();
531
558
  try {
532
- void fetch(TELEMETRY_ENDPOINT, {
559
+ return fetch(TELEMETRY_ENDPOINT, {
533
560
  method: "POST",
534
561
  headers: { "Content-Type": "application/json" },
535
562
  body: JSON.stringify(payload),
536
563
  signal: controller.signal
537
- }).catch(() => {
538
- }).finally(() => clearTimeout(timeout));
564
+ }).then(() => void 0, () => void 0).finally(() => clearTimeout(timeout));
539
565
  } catch {
540
566
  clearTimeout(timeout);
567
+ return Promise.resolve();
541
568
  }
542
569
  }
543
570
 
@@ -6280,13 +6307,93 @@ function removeWordpressConnection(config, projectName) {
6280
6307
  return true;
6281
6308
  }
6282
6309
 
6310
+ // src/usage-telemetry.ts
6311
+ var API_REQUEST_BUCKET_CAPACITY = 20;
6312
+ var API_REQUEST_REFILL_PER_MS = 1 / 1e4;
6313
+ var MAX_TRACKED_MCP_SESSIONS = 2e3;
6314
+ var MAX_ROUTE_LENGTH = 200;
6315
+ var SKIPPED_ROUTE_PATTERN = /(?:^|\/)(?:health|openapi\.json|telemetry)(?:\/|$)/;
6316
+ var MCP_CALL_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6317
+ var MCP_TOOL_PATTERN = /^[a-z][a-z0-9_]{0,79}$/;
6318
+ function classifyUsageSurface(info) {
6319
+ const labelled = usageSurfaceSchema.safeParse(info.usageLabels.surface);
6320
+ if (labelled.success) return labelled.data;
6321
+ const ua = info.userAgent ?? "";
6322
+ if (ua.startsWith("canonry-cli")) return "cli";
6323
+ if (ua.startsWith("canonry-mcp/")) return "mcp-http";
6324
+ if (ua === "canonry-mcp") return "mcp-stdio";
6325
+ if (ua.startsWith("Mozilla/")) return "dashboard";
6326
+ return "api";
6327
+ }
6328
+ function statusClass(statusCode) {
6329
+ if (statusCode >= 500) return "5xx";
6330
+ if (statusCode >= 400) return "4xx";
6331
+ if (statusCode >= 300) return "3xx";
6332
+ return "2xx";
6333
+ }
6334
+ function createApiUsageTelemetry(options = {}) {
6335
+ const emit = options.emit ?? trackEvent;
6336
+ const now = options.now ?? Date.now;
6337
+ let tokens = API_REQUEST_BUCKET_CAPACITY;
6338
+ let refilledAt = now();
6339
+ let droppedBefore = 0;
6340
+ const seenSessions = /* @__PURE__ */ new Set();
6341
+ const takeToken = () => {
6342
+ const at = now();
6343
+ tokens = Math.min(API_REQUEST_BUCKET_CAPACITY, tokens + (at - refilledAt) * API_REQUEST_REFILL_PER_MS);
6344
+ refilledAt = at;
6345
+ if (tokens < 1) return false;
6346
+ tokens -= 1;
6347
+ return true;
6348
+ };
6349
+ return (info) => {
6350
+ if (info.method === "OPTIONS" || info.method === "HEAD") return;
6351
+ if (SKIPPED_ROUTE_PATTERN.test(info.route)) return;
6352
+ const surface = classifyUsageSurface(info);
6353
+ if (surface === "cli" || surface === "dashboard") return;
6354
+ const labels = info.usageLabels;
6355
+ const mcpClient = normalizeAgentSlug(labels.mcpClient);
6356
+ const detected = normalizeAgentSlug(labels.agent);
6357
+ const agent = detected && detected !== AGENT_NONE ? detected : mcpClient ?? detected ?? AGENT_NONE;
6358
+ const mcpTool = labels.mcpTool && MCP_TOOL_PATTERN.test(labels.mcpTool) ? labels.mcpTool : void 0;
6359
+ const mcpCallId = labels.mcpCall && MCP_CALL_ID_PATTERN.test(labels.mcpCall) ? labels.mcpCall.toLowerCase() : void 0;
6360
+ const isMcp = surface === "mcp-stdio" || surface === "mcp-http";
6361
+ if (isMcp && mcpTool && info.actorSession && !seenSessions.has(info.actorSession)) {
6362
+ if (seenSessions.size >= MAX_TRACKED_MCP_SESSIONS) {
6363
+ const oldest = seenSessions.values().next().value;
6364
+ if (oldest !== void 0) seenSessions.delete(oldest);
6365
+ }
6366
+ seenSessions.add(info.actorSession);
6367
+ emit("mcp.session.started", { surface, agent, ...mcpClient ? { mcpClient } : {} }, { source: "cli-server" });
6368
+ }
6369
+ if (!takeToken()) {
6370
+ droppedBefore += 1;
6371
+ return;
6372
+ }
6373
+ const properties = {
6374
+ surface,
6375
+ agent,
6376
+ method: info.method,
6377
+ route: info.route.slice(0, MAX_ROUTE_LENGTH),
6378
+ statusClass: statusClass(info.statusCode),
6379
+ durationBucket: bucketCliCommandDuration(info.durationMs),
6380
+ ...mcpClient ? { mcpClient } : {},
6381
+ ...mcpTool ? { mcpTool } : {},
6382
+ ...mcpCallId ? { mcpCallId } : {},
6383
+ ...droppedBefore > 0 ? { droppedBefore } : {}
6384
+ };
6385
+ droppedBefore = 0;
6386
+ emit("api.request", properties, { source: "cli-server" });
6387
+ };
6388
+ }
6389
+
6283
6390
  // src/instance-identity.ts
6284
6391
  function nonBlank(value) {
6285
6392
  const trimmed = value?.trim();
6286
6393
  return trimmed ? trimmed : void 0;
6287
6394
  }
6288
6395
  function resolveBuildCommit(env = process.env) {
6289
- const embedded = true ? "92ebeea19d64dffdadfcfaf8dcb2696e34c8636b" : void 0;
6396
+ const embedded = true ? "267b6410fa27a2bf032d4957047859e1f9eee6d1" : void 0;
6290
6397
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
6291
6398
  }
6292
6399
  function resolveInstanceIdentity(env = process.env) {
@@ -6342,6 +6449,30 @@ function buildRunCompletedProps(input) {
6342
6449
  if (input.location) props.location = input.location;
6343
6450
  return props;
6344
6451
  }
6452
+ function buildSiteAuditCompletedProps(input) {
6453
+ const props = {
6454
+ status: input.status,
6455
+ durationMs: Date.now() - input.startTime
6456
+ };
6457
+ if (input.trigger) props.trigger = input.trigger;
6458
+ const domainHash = hashDomain(input.canonicalDomain ?? null);
6459
+ if (domainHash) props.domainHash = domainHash;
6460
+ const crawl = input.crawl;
6461
+ if (!crawl) return props;
6462
+ props.complete = crawl.complete;
6463
+ if (crawl.termination) props.termination = crawl.termination;
6464
+ props.pagesDiscovered = crawl.pagesDiscovered;
6465
+ props.pagesFetched = crawl.pagesFetched;
6466
+ props.pagesAudited = crawl.pagesAudited;
6467
+ props.pagesErrored = crawl.pagesErrored;
6468
+ if (crawl.pagesAudited > 0 && typeof crawl.aggregateScore === "number" && Number.isFinite(crawl.aggregateScore)) {
6469
+ props.aggregateScore = crawl.aggregateScore;
6470
+ }
6471
+ props.pageBudget = crawl.pageBudget;
6472
+ props.checkDeadLinks = crawl.checkDeadLinks;
6473
+ if (crawl.checkDeadLinks) props.deadLinksFound = crawl.deadLinksFound;
6474
+ return props;
6475
+ }
6345
6476
 
6346
6477
  // src/provider-execution-gate.ts
6347
6478
  var ProviderExecutionGate = class {
@@ -6610,7 +6741,14 @@ var JobRunner = class {
6610
6741
  this.registry = registry;
6611
6742
  }
6612
6743
  recoverStaleRuns() {
6613
- const stale = this.db.select({ id: runs.id, projectId: runs.projectId, kind: runs.kind, status: runs.status }).from(runs).where(inArray(runs.status, ["running", "queued"])).all();
6744
+ const stale = this.db.select({
6745
+ id: runs.id,
6746
+ projectId: runs.projectId,
6747
+ kind: runs.kind,
6748
+ status: runs.status,
6749
+ trigger: runs.trigger,
6750
+ startedAt: runs.startedAt
6751
+ }).from(runs).where(inArray(runs.status, ["running", "queued"])).all();
6614
6752
  if (stale.length === 0) return;
6615
6753
  const now = (/* @__PURE__ */ new Date()).toISOString();
6616
6754
  for (const run of stale) {
@@ -6628,6 +6766,35 @@ var JobRunner = class {
6628
6766
  });
6629
6767
  if (!recovered) continue;
6630
6768
  log2.warn("run.recovered-stale", { runId: run.id, previousStatus: run.status });
6769
+ if (run.kind === RunKinds["site-audit"]) this.trackRecoveredSiteAudit(run);
6770
+ }
6771
+ }
6772
+ /**
6773
+ * A crawl the process died under never reaches `executeSiteAudit`'s terminal
6774
+ * telemetry, so without this every interrupted audit vanishes from the data.
6775
+ * Emitted after the recovery transaction commits. `durationMs` runs from the
6776
+ * crawl's start to this recovery, so it includes the downtime and is an upper
6777
+ * bound; a run that was still queued has no start and reports 0.
6778
+ *
6779
+ * The source is set explicitly: recovery runs during server construction,
6780
+ * before `canonry serve` switches the process source to `cli-server`.
6781
+ */
6782
+ trackRecoveredSiteAudit(run) {
6783
+ try {
6784
+ const startedAt = run.startedAt ? Date.parse(run.startedAt) : Number.NaN;
6785
+ const project = this.db.select({ canonicalDomain: projects.canonicalDomain }).from(projects).where(eq2(projects.id, run.projectId)).get();
6786
+ trackEvent(
6787
+ "site_audit.completed",
6788
+ buildSiteAuditCompletedProps({
6789
+ status: "failed",
6790
+ startTime: Number.isFinite(startedAt) ? startedAt : Date.now(),
6791
+ trigger: run.trigger,
6792
+ canonicalDomain: project?.canonicalDomain ?? null
6793
+ }),
6794
+ { errorCode: "SERVER_RESTARTED", source: "cli-server" }
6795
+ );
6796
+ } catch (err) {
6797
+ log2.warn("telemetry.recovered-site-audit-failed", { runId: run.id, error: describeError(err) });
6631
6798
  }
6632
6799
  }
6633
6800
  async executeRun(runId, projectId, providerOverride, locationOverride) {
@@ -7149,6 +7316,7 @@ var JobRunner = class {
7149
7316
  if (existingRun.kind === "answer-visibility" && runTrigger !== "probe" && totalSnapshotsInserted > 0 && !this.hasPriorActivation(projectId, runId)) {
7150
7317
  trackEvent("activation.completed", {
7151
7318
  flowVersion: ONBOARDING_FLOW_VERSION,
7319
+ kind: "answer_visibility",
7152
7320
  status: finalStatus,
7153
7321
  providerCountBucket: bucketOnboardingCount(executionContext.providerCount),
7154
7322
  queryCountBucket: bucketOnboardingCount(executionContext.queryCount),
@@ -10031,7 +10199,7 @@ function buildDiscoveryInsightTitle(input) {
10031
10199
 
10032
10200
  // src/execute-site-audit.ts
10033
10201
  import crypto19 from "crypto";
10034
- import { and as and13, eq as eq15, sql as sql6 } from "drizzle-orm";
10202
+ import { and as and13, eq as eq15, ne as ne3, sql as sql6 } from "drizzle-orm";
10035
10203
  import { runSiteCrawl } from "@canonry/aeo-audit";
10036
10204
 
10037
10205
  // src/site-audit-root.ts
@@ -11108,10 +11276,35 @@ function deadLinkCheckedCount(edges, pages) {
11108
11276
  }
11109
11277
  return new Set([...edges].filter((edge) => edge.type === "anchor" && edge.classification === "internal" && attemptedUrls.has(edge.to)).map((edge) => edge.to)).size;
11110
11278
  }
11279
+ function trackSiteAuditOutcome(db, input) {
11280
+ try {
11281
+ const errorCode = input.status === "cancelled" ? "RUN_CANCELLED" : input.status === "failed" ? "UNKNOWN" : void 0;
11282
+ trackEvent(
11283
+ "site_audit.completed",
11284
+ buildSiteAuditCompletedProps(input),
11285
+ errorCode ? { errorCode } : void 0
11286
+ );
11287
+ const pagesAudited = input.crawl?.pagesAudited ?? 0;
11288
+ if (pagesAudited === 0 || input.trigger === RunTriggers.probe) return;
11289
+ const prior = db.select({ id: siteAuditSnapshots.id }).from(siteAuditSnapshots).where(and13(eq15(siteAuditSnapshots.projectId, input.projectId), ne3(siteAuditSnapshots.runId, input.runId))).limit(1).get();
11290
+ if (prior) return;
11291
+ trackEvent("activation.completed", {
11292
+ flowVersion: ONBOARDING_FLOW_VERSION,
11293
+ kind: "site_health",
11294
+ status: input.status,
11295
+ pagesAuditedBucket: bucketOnboardingCount(pagesAudited)
11296
+ });
11297
+ } catch (error) {
11298
+ log13.warn("telemetry.failed", { runId: input.runId, projectId: input.projectId, error: describeError(error) });
11299
+ }
11300
+ }
11111
11301
  async function executeSiteAudit(db, runId, projectId, opts = {}) {
11112
11302
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
11113
11303
  const claim = db.update(runs).set({ status: "running", startedAt }).where(and13(eq15(runs.id, runId), eq15(runs.projectId, projectId), eq15(runs.status, "queued"))).run();
11114
11304
  if (claim.changes === 0) return;
11305
+ const startTime = Date.now();
11306
+ const trigger = db.select({ trigger: runs.trigger }).from(runs).where(eq15(runs.id, runId)).get()?.trigger ?? null;
11307
+ let canonicalDomain = null;
11115
11308
  const attemptNumber = (db.select({ value: sql6`coalesce(max(${siteCrawlAttempts.attemptNumber}), 0)` }).from(siteCrawlAttempts).where(eq15(siteCrawlAttempts.runId, runId)).get()?.value ?? 0) + 1;
11116
11309
  const attemptId = crypto19.randomUUID();
11117
11310
  db.insert(siteCrawlAttempts).values({
@@ -11391,6 +11584,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
11391
11584
  try {
11392
11585
  const project = db.select().from(projects).where(eq15(projects.id, projectId)).get();
11393
11586
  if (!project) throw new Error(`Project not found: ${projectId}`);
11587
+ canonicalDomain = project.canonicalDomain;
11394
11588
  const homepageUrl = toHomepageUrl(project.canonicalDomain);
11395
11589
  const maxPages = clampSiteAuditLimit(opts.maxPages ?? opts.limit);
11396
11590
  const maxEdges = clampSiteAuditEdgeLimit(opts.maxEdges);
@@ -11610,6 +11804,26 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
11610
11804
  throw new Error(`Site audit run was no longer running before publication: ${runId}`);
11611
11805
  }
11612
11806
  log13.info("completed", { runId, projectId, status: terminalStatus, complete: crawlSummary.complete, pages: crawlSummary.pagesObserved });
11807
+ trackSiteAuditOutcome(db, {
11808
+ runId,
11809
+ projectId,
11810
+ status: crawlSummary.complete ? "completed" : "partial",
11811
+ startTime,
11812
+ trigger,
11813
+ canonicalDomain,
11814
+ crawl: {
11815
+ complete: crawlSummary.complete,
11816
+ termination: crawlSummary.terminationReason,
11817
+ pagesDiscovered: crawlSummary.pagesDiscovered,
11818
+ pagesFetched: crawlSummary.pagesFetched,
11819
+ pagesAudited: crawlSummary.auditRollup.auditedPages,
11820
+ pagesErrored: errorCount,
11821
+ aggregateScore: crawlSummary.auditRollup.aggregateScore,
11822
+ pageBudget: maxPages,
11823
+ checkDeadLinks: opts.checkDeadLinks ?? false,
11824
+ deadLinksFound
11825
+ }
11826
+ });
11613
11827
  } catch (error) {
11614
11828
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
11615
11829
  const cancelled = isCancelled(error, opts.signal);
@@ -11628,6 +11842,14 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
11628
11842
  }).where(and13(eq15(runs.id, runId), eq15(runs.status, "running"))).run();
11629
11843
  });
11630
11844
  log13.error("failed", { runId, projectId, cancelled, error: message });
11845
+ trackSiteAuditOutcome(db, {
11846
+ runId,
11847
+ projectId,
11848
+ status: cancelled ? "cancelled" : "failed",
11849
+ startTime,
11850
+ trigger,
11851
+ canonicalDomain
11852
+ });
11631
11853
  throw error;
11632
11854
  }
11633
11855
  }
@@ -12097,7 +12319,7 @@ function readStoredGroundingSources(rawResponse) {
12097
12319
  return result;
12098
12320
  }
12099
12321
  async function backfillInsightsCommand(project, opts) {
12100
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-7XX5ZRYE.js");
12322
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-D2HY2S5F.js");
12101
12323
  const config = loadConfig();
12102
12324
  const db = createClient(config.database);
12103
12325
  migrate(db);
@@ -14191,6 +14413,7 @@ import { Type as Type3 } from "@sinclair/typebox";
14191
14413
 
14192
14414
  // src/agent/mcp-to-agent-tool.ts
14193
14415
  import { Type as Type2 } from "@sinclair/typebox";
14416
+ import { randomUUID } from "crypto";
14194
14417
  var MAX_TOOL_RESULT_CHARS = 2e4;
14195
14418
  var TRUNCATION_NOTE = "... (truncated \u2014 result too large)";
14196
14419
  function serializeResult(value) {
@@ -14340,7 +14563,7 @@ function mcpToAgentTool(tool, ctx) {
14340
14563
  const parameters = Type2.Unsafe(visibleSchema);
14341
14564
  const execute = async (_toolCallId, params) => {
14342
14565
  const handlerInput = hadProject ? { ...params, project: ctx.projectName } : params;
14343
- const result = await tool.handler(ctx.client, handlerInput);
14566
+ const result = await runWithUsageTags(ctx.client, { mcpTool: tool.name, mcpCall: randomUUID() }, () => tool.handler(ctx.client, handlerInput));
14344
14567
  return textResult2(result);
14345
14568
  };
14346
14569
  return {
@@ -15854,6 +16077,7 @@ function registerMcpHttpRoutes(scope, opts) {
15854
16077
  const client = new ApiClient(opts.selfApiUrl, sessionKey?.raw ?? bearer, {
15855
16078
  skipProbe: true,
15856
16079
  clientName: `canonry-mcp/${PACKAGE_VERSION}`,
16080
+ surface: "mcp-http",
15857
16081
  // Correlation only: this is never accepted as caller identity or authority.
15858
16082
  actorSession: crypto26.randomUUID()
15859
16083
  });
@@ -17608,7 +17832,7 @@ function cloneGtmConfig(config) {
17608
17832
  function createGoogleMarketingConfigCredentialStore(input) {
17609
17833
  const persistConfigPatch = input.saveConfigPatch ?? saveConfigPatch;
17610
17834
  const env = input.env ?? process.env;
17611
- const randomUUID = input.randomUUID ?? crypto27.randomUUID;
17835
+ const randomUUID2 = input.randomUUID ?? crypto27.randomUUID;
17612
17836
  const snapshot = () => ({
17613
17837
  googleAds: cloneGoogleAdsConfig(input.config.googleAds),
17614
17838
  gtm: cloneGtmConfig(input.config.gtm)
@@ -17656,7 +17880,7 @@ function createGoogleMarketingConfigCredentialStore(input) {
17656
17880
  upsertGoogleAdsConnection(input.config, {
17657
17881
  projectId: project.id,
17658
17882
  projectName: project.name,
17659
- credentialGeneration: randomUUID(),
17883
+ credentialGeneration: randomUUID2(),
17660
17884
  ...credential.accessToken ? { accessToken: credential.accessToken } : {},
17661
17885
  refreshToken: credential.refreshToken ?? null,
17662
17886
  tokenExpiresAt: credential.expiresAt ?? null,
@@ -17668,7 +17892,7 @@ function createGoogleMarketingConfigCredentialStore(input) {
17668
17892
  upsertGtmConnection(input.config, {
17669
17893
  projectId: project.id,
17670
17894
  projectName: project.name,
17671
- credentialGeneration: randomUUID(),
17895
+ credentialGeneration: randomUUID2(),
17672
17896
  ...credential.accessToken ? { accessToken: credential.accessToken } : {},
17673
17897
  refreshToken: credential.refreshToken ?? null,
17674
17898
  tokenExpiresAt: credential.expiresAt ?? null,
@@ -17781,6 +18005,10 @@ async function createServer(opts) {
17781
18005
  const notifier = new Notifier(opts.db, serverUrl);
17782
18006
  const intelligenceService = new IntelligenceService(opts.db);
17783
18007
  const aeroClient = new ApiClient(opts.config.apiUrl, opts.config.apiKey, {
18008
+ skipProbe: true,
18009
+ surface: "aero"
18010
+ });
18011
+ const schedulerClient = new ApiClient(opts.config.apiUrl, opts.config.apiKey, {
17784
18012
  skipProbe: true
17785
18013
  });
17786
18014
  const agentEnabled = resolveAgentEnabled(process.env, opts.config);
@@ -18221,7 +18449,7 @@ async function createServer(opts) {
18221
18449
  getRunnableProviderNames: () => registry.getAll().map((provider) => provider.adapter.name),
18222
18450
  getEffectiveProviderModels: () => effectiveProviderModels(registry),
18223
18451
  onTrafficSyncRequested: (projectName, sourceId) => {
18224
- aeroClient.trafficSync(projectName, sourceId).catch((err) => {
18452
+ schedulerClient.trafficSync(projectName, sourceId).catch((err) => {
18225
18453
  app.log.error(
18226
18454
  {
18227
18455
  projectName,
@@ -18241,7 +18469,7 @@ async function createServer(opts) {
18241
18469
  onDoctorRequested: (projectName) => {
18242
18470
  void (async () => {
18243
18471
  try {
18244
- const report = await aeroClient.runDoctor({ project: projectName });
18472
+ const report = await schedulerClient.runDoctor({ project: projectName });
18245
18473
  const project = opts.db.select().from(projects).where(eq26(projects.name, projectName)).get();
18246
18474
  if (!project) {
18247
18475
  app.log.warn({ projectName }, "doctor schedule fired for an unknown project");
@@ -18257,7 +18485,7 @@ async function createServer(opts) {
18257
18485
  })();
18258
18486
  },
18259
18487
  onDataRefreshRequested: (projectName) => {
18260
- void refreshAllIntegrations(aeroClient, projectName);
18488
+ void refreshAllIntegrations(schedulerClient, projectName);
18261
18489
  },
18262
18490
  onBacklinksSyncRequested: (projectName) => {
18263
18491
  void (async () => {
@@ -18282,7 +18510,7 @@ async function createServer(opts) {
18282
18510
  );
18283
18511
  return;
18284
18512
  }
18285
- aeroClient.backlinksTriggerSync(probed.release).catch((err) => {
18513
+ schedulerClient.backlinksTriggerSync(probed.release).catch((err) => {
18286
18514
  app.log.error(
18287
18515
  {
18288
18516
  projectName,
@@ -18783,6 +19011,9 @@ async function createServer(opts) {
18783
19011
  await app.register(apiRoutes, {
18784
19012
  db: opts.db,
18785
19013
  routePrefix: apiPrefix,
19014
+ // Agent-surface usage (MCP, Aero, raw API). CLI and dashboard requests are
19015
+ // measured elsewhere and skipped inside.
19016
+ onRequestCompleted: createApiUsageTelemetry(),
18786
19017
  skipAuth: false,
18787
19018
  sessionCookieName: SESSION_COOKIE_NAME,
18788
19019
  resolveSessionApiKeyId,
@@ -19313,9 +19544,7 @@ async function createServer(opts) {
19313
19544
  listOperationalLogs: (query) => operationalLogs.list(query),
19314
19545
  getTelemetryStatus,
19315
19546
  setTelemetryEnabled: (enabled) => {
19316
- const config = loadConfig();
19317
- config.telemetry = enabled;
19318
- saveConfigPatch(config);
19547
+ void setTelemetryPreference(enabled, "api");
19319
19548
  opts.config.telemetry = enabled;
19320
19549
  },
19321
19550
  recordOnboardingEvent: (event) => {
@@ -19647,6 +19876,7 @@ function parseQueryResponse(raw, count2) {
19647
19876
  }
19648
19877
 
19649
19878
  export {
19879
+ cliRuntimeContext,
19650
19880
  trackCliCommandFinished,
19651
19881
  setTelemetrySource,
19652
19882
  getTelemetryStatus,
@@ -19656,6 +19886,7 @@ export {
19656
19886
  showFirstRunNotice,
19657
19887
  detectAndTrackUpgrade,
19658
19888
  trackEvent,
19889
+ setTelemetryPreference,
19659
19890
  backfillAnswerVisibilityCommand,
19660
19891
  backfillNormalizedPaths,
19661
19892
  backfillNormalizedPathsCommand,
@@ -5,11 +5,11 @@ import {
5
5
  installSkills,
6
6
  loadConfigRaw,
7
7
  saveConfigPatch
8
- } from "./chunk-MEQGGODM.js";
8
+ } from "./chunk-5K2NUQOV.js";
9
9
  import {
10
10
  SKILL_MANIFEST_FILENAME,
11
11
  SkillsClients
12
- } from "./chunk-3DL3KV6C.js";
12
+ } from "./chunk-5ZIUROAQ.js";
13
13
 
14
14
  // src/skills-autosync.ts
15
15
  import fs from "fs";