@canonry/canonry 4.136.2 → 4.137.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 (29) hide show
  1. package/README.md +2 -1
  2. package/assets/assets/{AuditHistoryPanel-DtUGiN21.js → AuditHistoryPanel-B6GEmlVM.js} +1 -1
  3. package/assets/assets/{BacklinksPage-BGwtmwfz.js → BacklinksPage-Cr3QegNo.js} +1 -1
  4. package/assets/assets/{ChartPrimitives-Df2UC1g3.js → ChartPrimitives-rhsGzU1A.js} +1 -1
  5. package/assets/assets/{HistoryPage-AtaMVEUx.js → HistoryPage-B3HrxyPP.js} +1 -1
  6. package/assets/assets/ProjectPage-D3WiL3RI.js +8 -0
  7. package/assets/assets/{RunRow-Cmf-5xVo.js → RunRow-MsV1RLPm.js} +1 -1
  8. package/assets/assets/{RunsPage-BAPy9Yri.js → RunsPage-BVlv0j3Y.js} +1 -1
  9. package/assets/assets/{SettingsPage-BXO7WpTo.js → SettingsPage-Dfd-T4o9.js} +1 -1
  10. package/assets/assets/{TrafficPage-IH-wiPu2.js → TrafficPage-Bl1jj73m.js} +1 -1
  11. package/assets/assets/{TrafficSourceDetailPage-CTTN-P52.js → TrafficSourceDetailPage-CinQcKmY.js} +1 -1
  12. package/assets/assets/{arrow-left-BwzAIeJ_.js → arrow-left-CHU6s_Xn.js} +1 -1
  13. package/assets/assets/extract-error-message-BDdyZgyf.js +1 -0
  14. package/assets/assets/index-BNulljlk.js +213 -0
  15. package/assets/assets/{trash-2-C50OeBAV.js → trash-2-BE5oH1Tj.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-GGIRLRAY.js → chunk-7RF5EDUO.js} +99 -3
  18. package/dist/{chunk-YVMQWVNY.js → chunk-PBTB3FCN.js} +117 -12
  19. package/dist/{chunk-SMFUCXHI.js → chunk-UVWI6VPP.js} +1 -1
  20. package/dist/{chunk-MXR3GI5L.js → chunk-XEX5CLQW.js} +113 -15
  21. package/dist/cli.js +147 -33
  22. package/dist/index.d.ts +2 -0
  23. package/dist/index.js +4 -4
  24. package/dist/{intelligence-service-BD6BHTEJ.js → intelligence-service-JEFHUGVX.js} +2 -2
  25. package/dist/mcp.js +2 -2
  26. package/package.json +6 -6
  27. package/assets/assets/ProjectPage-OlBjxXNx.js +0 -8
  28. package/assets/assets/extract-error-message-BIcIoJLN.js +0 -1
  29. package/assets/assets/index-BPpSrJY0.js +0 -210
@@ -1 +1 @@
1
- import{c as a}from"./index-BPpSrJY0.js";const c=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],d=a("circle-check",c);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],k=a("circle-question-mark",e);const t=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=a("download",t);const o=[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]],r=a("external-link",o);const n=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],y=a("trash-2",n);export{d as C,s as D,r as E,y as T,k as a};
1
+ import{c as a}from"./index-BNulljlk.js";const c=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],d=a("circle-check",c);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],k=a("circle-question-mark",e);const t=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=a("download",t);const o=[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]],r=a("external-link",o);const n=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],y=a("trash-2",n);export{d as C,s as D,r as E,y as T,k as a};
package/assets/index.html CHANGED
@@ -12,7 +12,7 @@
12
12
  <link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
13
13
  <link rel="apple-touch-icon" href="./apple-touch-icon.png" />
14
14
  <title>Canonry</title>
15
- <script type="module" crossorigin src="./assets/index-BPpSrJY0.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-BNulljlk.js"></script>
16
16
  <link rel="modulepreload" crossorigin href="./assets/vendor-tanstack-Be8JdV5M.js">
17
17
  <link rel="modulepreload" crossorigin href="./assets/vendor-radix-CQ77EO2y.js">
18
18
  <link rel="modulepreload" crossorigin href="./assets/vendor-recharts-DSxTZhaH.js">
@@ -53,6 +53,22 @@ function quotaExceeded(metric, details) {
53
53
  function providerError(message, details) {
54
54
  return new AppError("PROVIDER_ERROR", message, 502, details);
55
55
  }
56
+ function noProvider(projectName, details) {
57
+ return new AppError(
58
+ "NO_PROVIDER",
59
+ `No runnable answer provider is configured for '${projectName}'. Configure a provider before starting a run.`,
60
+ 503,
61
+ { projectName, ...details }
62
+ );
63
+ }
64
+ function noQueries(projectName) {
65
+ return new AppError(
66
+ "NO_QUERIES",
67
+ `Project '${projectName}' has no tracked queries. Add at least one query before starting a run.`,
68
+ 422,
69
+ { projectName }
70
+ );
71
+ }
56
72
  function runInProgress(projectName) {
57
73
  return new AppError("RUN_IN_PROGRESS", `A run is already in progress for '${projectName}'`, 409);
58
74
  }
@@ -9364,14 +9380,14 @@ function round(value, dp = 4) {
9364
9380
  const f = 10 ** dp;
9365
9381
  return (Math.round(value * f) + 0) / f;
9366
9382
  }
9367
- function wilsonInterval(successes, n, z39 = 1.96) {
9383
+ function wilsonInterval(successes, n, z40 = 1.96) {
9368
9384
  if (!Number.isFinite(n) || n <= 0) return null;
9369
9385
  const s = Math.max(0, Math.min(successes, n));
9370
9386
  const p = s / n;
9371
- const z210 = z39 * z39;
9387
+ const z210 = z40 * z40;
9372
9388
  const denom = 1 + z210 / n;
9373
9389
  const center = (p + z210 / (2 * n)) / denom;
9374
- const margin = z39 / denom * Math.sqrt(p * (1 - p) / n + z210 / (4 * n * n));
9390
+ const margin = z40 / denom * Math.sqrt(p * (1 - p) / n + z210 / (4 * n * n));
9375
9391
  return {
9376
9392
  low: round(Math.max(0, center - margin)),
9377
9393
  high: round(Math.min(1, center + margin))
@@ -10471,6 +10487,80 @@ function serializeForInlineScript(value) {
10471
10487
  }
10472
10488
 
10473
10489
  // ../contracts/src/telemetry.ts
10490
+ import { z as z39 } from "zod";
10491
+ var ONBOARDING_FLOW_VERSION = 1;
10492
+ var onboardingStepSchema = z39.enum([
10493
+ "system",
10494
+ "project",
10495
+ "queries",
10496
+ "competitors",
10497
+ "run"
10498
+ ]);
10499
+ var onboardingCountBucketSchema = z39.enum([
10500
+ "0",
10501
+ "1",
10502
+ "2-3",
10503
+ "4-5",
10504
+ "6-10",
10505
+ "11+"
10506
+ ]);
10507
+ function bucketOnboardingCount(value) {
10508
+ if (!Number.isFinite(value) || value <= 0) return "0";
10509
+ if (value < 2) return "1";
10510
+ if (value < 4) return "2-3";
10511
+ if (value < 6) return "4-5";
10512
+ if (value < 11) return "6-10";
10513
+ return "11+";
10514
+ }
10515
+ var onboardingBlockReasonSchema = z39.enum([
10516
+ "api_unavailable",
10517
+ "database_unavailable",
10518
+ "worker_unavailable",
10519
+ "no_provider",
10520
+ "no_queries",
10521
+ "provider_save_failed",
10522
+ "project_create_failed",
10523
+ "query_save_failed",
10524
+ "run_rejected",
10525
+ "run_failed",
10526
+ "run_cancelled",
10527
+ "unknown"
10528
+ ]);
10529
+ var onboardingEventBaseSchema = z39.object({
10530
+ eventId: z39.string().uuid(),
10531
+ flowVersion: z39.literal(ONBOARDING_FLOW_VERSION),
10532
+ onboardingSessionId: z39.string().uuid()
10533
+ });
10534
+ var onboardingTelemetryEventSchema = z39.discriminatedUnion("event", [
10535
+ onboardingEventBaseSchema.extend({
10536
+ event: z39.literal("onboarding.started"),
10537
+ step: onboardingStepSchema,
10538
+ resumed: z39.boolean()
10539
+ }).strict(),
10540
+ onboardingEventBaseSchema.extend({
10541
+ event: z39.literal("onboarding.step_completed"),
10542
+ step: onboardingStepSchema,
10543
+ method: z39.enum(["existing", "inline", "manual", "generated", "skipped", "automatic"]),
10544
+ countBucket: onboardingCountBucketSchema.optional()
10545
+ }).strict(),
10546
+ onboardingEventBaseSchema.extend({
10547
+ event: z39.literal("onboarding.blocked"),
10548
+ step: onboardingStepSchema,
10549
+ action: z39.enum(["continue", "configure_provider", "generate_queries", "save", "launch_run", "retry_run"]),
10550
+ reasonCode: onboardingBlockReasonSchema
10551
+ }).strict(),
10552
+ onboardingEventBaseSchema.extend({
10553
+ event: z39.literal("run.requested"),
10554
+ origin: z39.literal("dashboard_setup"),
10555
+ result: z39.enum(["queued", "rejected"]),
10556
+ providerCountBucket: onboardingCountBucketSchema,
10557
+ queryCountBucket: onboardingCountBucketSchema,
10558
+ reasonCode: onboardingBlockReasonSchema.optional()
10559
+ }).strict()
10560
+ ]);
10561
+ var telemetryEventAcceptedDtoSchema = z39.object({
10562
+ accepted: z39.boolean()
10563
+ });
10474
10564
  var GHOST_TELEMETRY_TEST_LOCATIONS = /* @__PURE__ */ new Set(["nyc", "lax", "chi"]);
10475
10565
  function isGhostTelemetryEvent(eventName, properties) {
10476
10566
  if (eventName !== "run.completed" && eventName !== "run.aborted") return false;
@@ -10505,6 +10595,8 @@ export {
10505
10595
  forbidden,
10506
10596
  quotaExceeded,
10507
10597
  providerError,
10598
+ noProvider,
10599
+ noQueries,
10508
10600
  runInProgress,
10509
10601
  operationInProgress,
10510
10602
  runNotCancellable,
@@ -10901,5 +10993,9 @@ export {
10901
10993
  normalizeIdTokens,
10902
10994
  embedClientConfigForRequest,
10903
10995
  serializeForInlineScript,
10996
+ ONBOARDING_FLOW_VERSION,
10997
+ bucketOnboardingCount,
10998
+ onboardingTelemetryEventSchema,
10999
+ telemetryEventAcceptedDtoSchema,
10904
11000
  isGhostTelemetryEvent
10905
11001
  };
@@ -264,6 +264,8 @@ import {
264
264
  mentionStateFromAnswerMentioned,
265
265
  missingDependency,
266
266
  modelIdsEquivalent,
267
+ noProvider,
268
+ noQueries,
267
269
  normalizeIdTokens,
268
270
  normalizeModelId,
269
271
  normalizeProjectAliases,
@@ -273,6 +275,7 @@ import {
273
275
  notFound,
274
276
  notImplemented,
275
277
  notificationDtoSchema,
278
+ onboardingTelemetryEventSchema,
276
279
  operationInProgress,
277
280
  orderLocationsDefaultFirst,
278
281
  organicEvidenceDtoSchema,
@@ -338,6 +341,7 @@ import {
338
341
  summarizeCheckResults,
339
342
  surfaceClassFromCompetitorType,
340
343
  surfaceClassLabel,
344
+ telemetryEventAcceptedDtoSchema,
341
345
  textContainsAnyBrandAlias,
342
346
  textContainsBrandAlias,
343
347
  textContainsDomain,
@@ -373,7 +377,7 @@ import {
373
377
  wordpressSchemaDeployResultDtoSchema,
374
378
  wordpressSchemaStatusResultDtoSchema,
375
379
  wordpressStatusDtoSchema
376
- } from "./chunk-GGIRLRAY.js";
380
+ } from "./chunk-7RF5EDUO.js";
377
381
 
378
382
  // src/intelligence-service.ts
379
383
  import { eq as eq44, desc as desc21, asc as asc9, and as and35, ne as ne6, or as or10, inArray as inArray15, gte as gte12, lte as lte8 } from "drizzle-orm";
@@ -7969,9 +7973,20 @@ async function runRoutes(app, opts) {
7969
7973
  rawProviders.splice(0, rawProviders.length, ...normalized);
7970
7974
  }
7971
7975
  const providers = rawProviders?.length ? rawProviders : void 0;
7976
+ const shouldPreflight = trigger !== RunTriggers.probe && opts.getRunnableProviderNames !== void 0;
7977
+ const trackedRows = body.queries?.length || shouldPreflight ? app.db.select({ query: queries.query }).from(queries).where(eq8(queries.projectId, project.id)).all() : [];
7978
+ if (shouldPreflight) {
7979
+ const preflightError = answerVisibilityPreflightError({
7980
+ projectName: project.name,
7981
+ projectProviders: project.providers,
7982
+ requestedProviders: providers,
7983
+ runnableProviderNames: opts.getRunnableProviderNames(),
7984
+ trackedQueryCount: trackedRows.length
7985
+ });
7986
+ if (preflightError) throw preflightError;
7987
+ }
7972
7988
  let scopedQueries = null;
7973
7989
  if (body.queries?.length) {
7974
- const trackedRows = app.db.select({ query: queries.query }).from(queries).where(eq8(queries.projectId, project.id)).all();
7975
7990
  const tracked = new Set(trackedRows.map((r) => r.query));
7976
7991
  const missing = body.queries.filter((q) => !tracked.has(q));
7977
7992
  if (missing.length) {
@@ -8135,7 +8150,28 @@ async function runRoutes(app, opts) {
8135
8150
  const providers = rawProviders?.length ? rawProviders : void 0;
8136
8151
  const now = (/* @__PURE__ */ new Date()).toISOString();
8137
8152
  const results = [];
8153
+ const runnableProviderNames = opts.getRunnableProviderNames?.();
8138
8154
  for (const project of allProjects) {
8155
+ if (runnableProviderNames) {
8156
+ const trackedQuery = app.db.select({ id: queries.id }).from(queries).where(eq8(queries.projectId, project.id)).limit(1).get();
8157
+ const preflightError = answerVisibilityPreflightError({
8158
+ projectName: project.name,
8159
+ projectProviders: project.providers,
8160
+ requestedProviders: providers,
8161
+ runnableProviderNames,
8162
+ trackedQueryCount: trackedQuery ? 1 : 0
8163
+ });
8164
+ if (preflightError) {
8165
+ results.push({
8166
+ projectName: project.name,
8167
+ projectId: project.id,
8168
+ status: "error",
8169
+ error: preflightError.message,
8170
+ errorCode: preflightError.code
8171
+ });
8172
+ continue;
8173
+ }
8174
+ }
8139
8175
  const projectLocations = project.locations;
8140
8176
  let resolvedLocation;
8141
8177
  if (project.defaultLocation) {
@@ -8199,6 +8235,26 @@ async function runRoutes(app, opts) {
8199
8235
  return reply.send(loadRunDetail(app, run));
8200
8236
  });
8201
8237
  }
8238
+ function answerVisibilityPreflightError(input) {
8239
+ if (input.trackedQueryCount === 0) {
8240
+ return noQueries(input.projectName);
8241
+ }
8242
+ const availableProviders = normalizeProviderNames(input.runnableProviderNames);
8243
+ const requestedProviders = normalizeProviderNames(input.requestedProviders ?? []);
8244
+ const projectProviders = normalizeProviderNames(input.projectProviders);
8245
+ const selectedProviders = requestedProviders.length > 0 ? requestedProviders : projectProviders.length > 0 ? projectProviders : availableProviders;
8246
+ const available = new Set(availableProviders);
8247
+ const runnableProviders = selectedProviders.filter((provider) => available.has(provider));
8248
+ if (runnableProviders.length > 0) return null;
8249
+ return noProvider(input.projectName, {
8250
+ availableProviders,
8251
+ selectedProviders,
8252
+ selectionSource: requestedProviders.length > 0 ? "request" : projectProviders.length > 0 ? "project" : "instance"
8253
+ });
8254
+ }
8255
+ function normalizeProviderNames(providerNames) {
8256
+ return [...new Set(providerNames.map((name) => name.trim().toLowerCase()).filter(Boolean))];
8257
+ }
8202
8258
  function parseRunTriggerRequest(value) {
8203
8259
  const result = runTriggerRequestSchema.safeParse(value);
8204
8260
  if (result.success) return result.data;
@@ -17456,6 +17512,7 @@ var SCHEMA_TABLE = {
17456
17512
  LatestProjectRunDto: latestProjectRunDtoSchema,
17457
17513
  LocationContext: locationContextSchema,
17458
17514
  NotificationDto: notificationDtoSchema,
17515
+ OnboardingTelemetryEvent: onboardingTelemetryEventSchema,
17459
17516
  OrganicEvidenceDto: organicEvidenceDtoSchema,
17460
17517
  ProjectDto: projectDtoSchema,
17461
17518
  ProjectUpsertRequest: projectUpsertRequestSchema,
@@ -17477,6 +17534,7 @@ var SCHEMA_TABLE = {
17477
17534
  SnapshotListResponse: snapshotListResponseSchema,
17478
17535
  SnapshotReportDto: snapshotReportSchema,
17479
17536
  SourceBreakdownDto: sourceBreakdownDtoSchema,
17537
+ TelemetryEventAcceptedDto: telemetryEventAcceptedDtoSchema,
17480
17538
  TrafficBackfillResponse: trafficBackfillResponseSchema,
17481
17539
  TrafficEventsResponse: trafficEventsResponseSchema,
17482
17540
  TrafficSourceDetailDto: trafficSourceDetailDtoSchema,
@@ -18502,7 +18560,9 @@ var routeCatalog = [
18502
18560
  },
18503
18561
  responses: {
18504
18562
  201: jsonResponse("Run queued.", "RunDto"),
18505
- 409: errorResponse("Run already in progress.")
18563
+ 422: errorResponse("Project has no tracked queries."),
18564
+ 409: errorResponse("Run already in progress."),
18565
+ 503: errorResponse("No runnable answer provider is configured.")
18506
18566
  }
18507
18567
  },
18508
18568
  {
@@ -19127,6 +19187,24 @@ var routeCatalog = [
19127
19187
  501: errorResponse("Telemetry configuration is not available.")
19128
19188
  }
19129
19189
  },
19190
+ {
19191
+ method: "post",
19192
+ path: "/api/v1/telemetry/onboarding",
19193
+ summary: "Record a privacy-safe onboarding milestone",
19194
+ tags: ["telemetry"],
19195
+ requestBody: {
19196
+ required: true,
19197
+ content: {
19198
+ "application/json": {
19199
+ schema: { $ref: "#/components/schemas/OnboardingTelemetryEvent" }
19200
+ }
19201
+ }
19202
+ },
19203
+ responses: {
19204
+ 202: jsonResponse("Onboarding milestone accepted.", "TelemetryEventAcceptedDto"),
19205
+ 400: errorResponse("Invalid onboarding telemetry event.")
19206
+ }
19207
+ },
19130
19208
  {
19131
19209
  method: "get",
19132
19210
  path: "/api/v1/screenshots/{snapshotId}",
@@ -22777,6 +22855,19 @@ async function telemetryRoutes(app, opts) {
22777
22855
  anonymousId: status?.anonymousId ? status.anonymousId.slice(0, 8) + "..." : void 0
22778
22856
  };
22779
22857
  });
22858
+ app.post("/telemetry/onboarding", async (request, reply) => {
22859
+ const parsed = onboardingTelemetryEventSchema.safeParse(request.body);
22860
+ if (!parsed.success) {
22861
+ throw validationError("Invalid onboarding telemetry event", {
22862
+ issues: parsed.error.issues.map((issue) => ({
22863
+ code: issue.code,
22864
+ path: issue.path.join(".")
22865
+ }))
22866
+ });
22867
+ }
22868
+ opts.recordOnboardingEvent?.(parsed.data);
22869
+ return reply.status(202).send({ accepted: Boolean(opts.recordOnboardingEvent) });
22870
+ });
22780
22871
  }
22781
22872
 
22782
22873
  // ../api-routes/src/schedules.ts
@@ -24798,6 +24889,24 @@ function stableStringify3(value) {
24798
24889
  }
24799
24890
 
24800
24891
  // ../api-routes/src/google.ts
24892
+ var GOOGLE_OAUTH_COMPLETE_MESSAGE = "canonry:google-oauth-complete";
24893
+ function googleOAuthSuccessHtml(type) {
24894
+ const message = JSON.stringify({
24895
+ type: GOOGLE_OAUTH_COMPLETE_MESSAGE,
24896
+ connectionType: type
24897
+ });
24898
+ return `<html><body style="font-family:system-ui;text-align:center;padding:60px">
24899
+ <h2>Connected successfully!</h2>
24900
+ <p>Google ${type.toUpperCase()} has been linked to your domain.</p>
24901
+ <p style="color:#888">This window closes automatically.</p>
24902
+ <script>
24903
+ if (window.opener && !window.opener.closed) {
24904
+ window.opener.postMessage(${message}, '*')
24905
+ window.close()
24906
+ }
24907
+ </script>
24908
+ </body></html>`;
24909
+ }
24801
24910
  function isOnProjectDomain(url, projectDomain) {
24802
24911
  const urlHost = hostOf(url);
24803
24912
  const projectHost = hostOf(projectDomain);
@@ -25123,13 +25232,7 @@ async function googleRoutes(app, opts) {
25123
25232
  entityId: type,
25124
25233
  diff: { domain, type, propertyId }
25125
25234
  });
25126
- return reply.type("text/html").send(
25127
- `<html><body style="font-family:system-ui;text-align:center;padding:60px">
25128
- <h2>Connected successfully!</h2>
25129
- <p>Google ${type.toUpperCase()} has been linked to your domain.</p>
25130
- <p style="color:#888">You can close this tab.</p>
25131
- </body></html>`
25132
- );
25235
+ return reply.type("text/html").send(googleOAuthSuccessHtml(type));
25133
25236
  }
25134
25237
  app.get("/google/callback", async (request, reply) => {
25135
25238
  return handleOAuthCallback(request, reply);
@@ -44159,7 +44262,8 @@ async function apiRoutes(app, opts) {
44159
44262
  await api.register(competitorRoutes);
44160
44263
  await api.register(runRoutes, {
44161
44264
  onRunCreated: opts.onRunCreated,
44162
- validProviderNames: opts.providerAdapters?.map((a) => a.name)
44265
+ validProviderNames: opts.providerAdapters?.map((a) => a.name),
44266
+ getRunnableProviderNames: opts.getRunnableProviderNames
44163
44267
  });
44164
44268
  await api.register(applyRoutes, {
44165
44269
  onScheduleUpdated: opts.onScheduleUpdated,
@@ -44210,7 +44314,8 @@ async function apiRoutes(app, opts) {
44210
44314
  });
44211
44315
  await api.register(telemetryRoutes, {
44212
44316
  getTelemetryStatus: opts.getTelemetryStatus,
44213
- setTelemetryEnabled: opts.setTelemetryEnabled
44317
+ setTelemetryEnabled: opts.setTelemetryEnabled,
44318
+ recordOnboardingEvent: opts.recordOnboardingEvent
44214
44319
  });
44215
44320
  await api.register(adsRoutes, {
44216
44321
  adsCredentialStore: opts.adsCredentialStore,
@@ -42,7 +42,7 @@ import {
42
42
  trafficConnectWordpressRequestSchema,
43
43
  trafficEventKindSchema,
44
44
  trafficSeriesGranularitySchema
45
- } from "./chunk-GGIRLRAY.js";
45
+ } from "./chunk-7RF5EDUO.js";
46
46
 
47
47
  // src/config.ts
48
48
  import fs from "fs";
@@ -10,7 +10,7 @@ import {
10
10
  loadConfig,
11
11
  loadConfigRaw,
12
12
  saveConfigPatch
13
- } from "./chunk-SMFUCXHI.js";
13
+ } from "./chunk-UVWI6VPP.js";
14
14
  import {
15
15
  CC_CACHE_DIR,
16
16
  DUCKDB_SPEC,
@@ -119,7 +119,7 @@ import {
119
119
  siteAuditPages,
120
120
  siteAuditSnapshots,
121
121
  usageCounters
122
- } from "./chunk-YVMQWVNY.js";
122
+ } from "./chunk-PBTB3FCN.js";
123
123
  import {
124
124
  AGENT_MEMORY_VALUE_MAX_BYTES,
125
125
  AGENT_PROVIDER_IDS,
@@ -135,6 +135,7 @@ import {
135
135
  LLM_CAPABILITIES,
136
136
  LlmCapabilities,
137
137
  MemorySources,
138
+ ONBOARDING_FLOW_VERSION,
138
139
  ProviderNames,
139
140
  ResearchQueryStatuses,
140
141
  ResearchRunStatuses,
@@ -159,6 +160,7 @@ import {
159
160
  agentMemoryUpsertRequestSchema,
160
161
  authInvalid,
161
162
  authRequired,
163
+ bucketOnboardingCount,
162
164
  buildRunErrorFromMessages,
163
165
  classifySkillFile,
164
166
  coerceSkillManifest,
@@ -199,7 +201,7 @@ import {
199
201
  validationError,
200
202
  winnabilityClassLabel,
201
203
  withRetry
202
- } from "./chunk-GGIRLRAY.js";
204
+ } from "./chunk-7RF5EDUO.js";
203
205
 
204
206
  // src/telemetry.ts
205
207
  import crypto from "crypto";
@@ -214,8 +216,42 @@ var ANON_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]
214
216
  function shouldDropTelemetryEvent(event, properties) {
215
217
  return isGhostTelemetryEvent(event, properties);
216
218
  }
219
+ function bucketCliCommandDuration(durationMs) {
220
+ const duration = Number.isFinite(durationMs) ? Math.max(0, durationMs) : 0;
221
+ if (duration < 1e3) return "under_1s";
222
+ if (duration < 1e4) return "1s_to_10s";
223
+ if (duration < 6e4) return "10s_to_1m";
224
+ if (duration < 3e5) return "1m_to_5m";
225
+ if (duration < 18e5) return "5m_to_30m";
226
+ return "30m_or_more";
227
+ }
228
+ function trackCliCommandFinished(input) {
229
+ trackEvent(
230
+ "cli.command.finished",
231
+ {
232
+ command: input.command,
233
+ success: input.success,
234
+ duration_bucket: bucketCliCommandDuration(input.durationMs),
235
+ ...input.setupState ? { setup_state: input.setupState } : {}
236
+ },
237
+ input.errorCode ? { errorCode: input.errorCode } : void 0
238
+ );
239
+ }
217
240
  var SESSION_ID = crypto.randomUUID();
218
241
  var CURRENT_SOURCE = "cli";
242
+ var TELEMETRY_SOURCE_VALUES = /* @__PURE__ */ new Set([
243
+ "cli",
244
+ "cli-server",
245
+ "api",
246
+ "mcp-server",
247
+ "wp-plugin",
248
+ "dashboard",
249
+ "agent-runtime"
250
+ ]);
251
+ function envSourceOverride() {
252
+ const raw = process.env.CANONRY_TELEMETRY_SOURCE?.trim();
253
+ return raw && TELEMETRY_SOURCE_VALUES.has(raw) ? raw : void 0;
254
+ }
219
255
  function setTelemetrySource(source) {
220
256
  CURRENT_SOURCE = source;
221
257
  }
@@ -331,9 +367,10 @@ function trackEvent(event, properties, options) {
331
367
  const anonymousId = getOrCreateAnonymousId();
332
368
  if (!anonymousId) return;
333
369
  const payload = {
370
+ eventId: options?.eventId ?? crypto.randomUUID(),
334
371
  anonymousId,
335
372
  sessionId: SESSION_ID,
336
- source: options?.source ?? CURRENT_SOURCE,
373
+ source: options?.source ?? envSourceOverride() ?? CURRENT_SOURCE,
337
374
  event,
338
375
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
339
376
  version: VERSION,
@@ -347,13 +384,17 @@ function trackEvent(event, properties, options) {
347
384
  const controller = new AbortController();
348
385
  const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
349
386
  timeout.unref();
350
- fetch(TELEMETRY_ENDPOINT, {
351
- method: "POST",
352
- headers: { "Content-Type": "application/json" },
353
- body: JSON.stringify(payload),
354
- signal: controller.signal
355
- }).catch(() => {
356
- }).finally(() => clearTimeout(timeout));
387
+ try {
388
+ void fetch(TELEMETRY_ENDPOINT, {
389
+ method: "POST",
390
+ headers: { "Content-Type": "application/json" },
391
+ body: JSON.stringify(payload),
392
+ signal: controller.signal
393
+ }).catch(() => {
394
+ }).finally(() => clearTimeout(timeout));
395
+ } catch {
396
+ clearTimeout(timeout);
397
+ }
357
398
  }
358
399
 
359
400
  // src/update-check.ts
@@ -3071,7 +3112,7 @@ import crypto4 from "crypto";
3071
3112
  import fs2 from "fs";
3072
3113
  import path3 from "path";
3073
3114
  import os3 from "os";
3074
- import { and as and2, eq as eq2, inArray, sql as sql2 } from "drizzle-orm";
3115
+ import { and as and2, eq as eq2, inArray, ne, sql as sql2 } from "drizzle-orm";
3075
3116
 
3076
3117
  // src/run-telemetry.ts
3077
3118
  import crypto2 from "crypto";
@@ -3278,10 +3319,18 @@ function classifyOneProviderError(message) {
3278
3319
  return "UNKNOWN";
3279
3320
  }
3280
3321
  var JobRunner = class {
3322
+ /**
3323
+ * Invoked exactly when `activation.completed` is emitted: the project's
3324
+ * first non-empty, non-probe answer-visibility result. The serve process
3325
+ * uses it to thank the operator once; it is a UX hook, not telemetry, so it
3326
+ * fires (and is given) independently of whether telemetry is enabled.
3327
+ */
3328
+ onFirstActivation;
3281
3329
  db;
3282
3330
  registry;
3283
3331
  onRunCompleted;
3284
- constructor(db, registry) {
3332
+ constructor(db, registry, opts) {
3333
+ this.onFirstActivation = opts?.onFirstActivation;
3285
3334
  this.db = db;
3286
3335
  this.registry = registry;
3287
3336
  }
@@ -3553,6 +3602,19 @@ var JobRunner = class {
3553
3602
  }),
3554
3603
  failureCode ? { errorCode: failureCode } : void 0
3555
3604
  );
3605
+ if (existingRun.kind === "answer-visibility" && runTrigger !== "probe" && totalSnapshotsInserted > 0 && !this.hasPriorActivation(projectId, runId)) {
3606
+ trackEvent("activation.completed", {
3607
+ flowVersion: ONBOARDING_FLOW_VERSION,
3608
+ status: finalStatus,
3609
+ providerCountBucket: bucketOnboardingCount(executionContext.providerCount),
3610
+ queryCountBucket: bucketOnboardingCount(executionContext.queryCount),
3611
+ snapshotCountBucket: bucketOnboardingCount(totalSnapshotsInserted)
3612
+ });
3613
+ try {
3614
+ this.onFirstActivation?.();
3615
+ } catch {
3616
+ }
3617
+ }
3556
3618
  this.incrementUsage(projectId, "runs", 1);
3557
3619
  if (this.onRunCompleted) {
3558
3620
  this.onRunCompleted(runId, projectId).catch((err) => {
@@ -3641,8 +3703,18 @@ var JobRunner = class {
3641
3703
  }
3642
3704
  providerReservations.clear();
3643
3705
  }
3706
+ hasPriorActivation(projectId, currentRunId) {
3707
+ return this.db.select({ id: querySnapshots.id }).from(querySnapshots).innerJoin(runs, eq2(querySnapshots.runId, runs.id)).where(and2(
3708
+ eq2(runs.projectId, projectId),
3709
+ eq2(runs.kind, "answer-visibility"),
3710
+ inArray(runs.status, ["completed", "partial"]),
3711
+ ne(runs.trigger, "probe"),
3712
+ ne(runs.id, currentRunId)
3713
+ )).limit(1).get() !== void 0;
3714
+ }
3644
3715
  getRunState(runId) {
3645
3716
  return this.db.select({
3717
+ kind: runs.kind,
3646
3718
  status: runs.status,
3647
3719
  finishedAt: runs.finishedAt,
3648
3720
  error: runs.error,
@@ -3697,6 +3769,21 @@ function buildPhases(input) {
3697
3769
  return { setup_ms, provider_call_ms, total_ms };
3698
3770
  }
3699
3771
 
3772
+ // src/activation-notice.ts
3773
+ var ACTIVATION_NOTICE = "\n\u2605 First sweep finished. Your AI visibility baseline is in the dashboard.\n If Canonry is useful, a GitHub star helps others find it:\n https://github.com/Canonry/canonry\n\n";
3774
+ function maybeShowActivationNotice(io = {}) {
3775
+ const isTTY = io.isTTY ?? Boolean(process.stderr.isTTY);
3776
+ const write = io.write ?? ((line) => process.stderr.write(line));
3777
+ try {
3778
+ if (!isTTY) return;
3779
+ const raw = loadConfigRaw();
3780
+ if (!raw || raw.activationNoticeShown === true) return;
3781
+ saveConfigPatch({ activationNoticeShown: true });
3782
+ write(ACTIVATION_NOTICE);
3783
+ } catch {
3784
+ }
3785
+ }
3786
+
3700
3787
  // src/gsc-sync.ts
3701
3788
  import crypto5 from "crypto";
3702
3789
  import { eq as eq3, and as and3, sql as sql3 } from "drizzle-orm";
@@ -7032,7 +7119,7 @@ function readStoredGroundingSources(rawResponse) {
7032
7119
  return result;
7033
7120
  }
7034
7121
  async function backfillInsightsCommand(project, opts) {
7035
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-BD6BHTEJ.js");
7122
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-JEFHUGVX.js");
7036
7123
  const config = loadConfig();
7037
7124
  const db = createClient(config.database);
7038
7125
  migrate(db);
@@ -11930,7 +12017,12 @@ async function createServer(opts) {
11930
12017
  }
11931
12018
  const port = opts.config.port ?? 4100;
11932
12019
  const serverUrl = `http://localhost:${port}`;
11933
- const jobRunner = new JobRunner(opts.db, registry);
12020
+ const jobRunner = new JobRunner(opts.db, registry, {
12021
+ // The one-time first-sweep thank-you. Lives on the serve console because
12022
+ // runs execute here, including the foreground serve that init hands off
12023
+ // to. TTY-gated inside, so supervised deployments never see it.
12024
+ onFirstActivation: () => maybeShowActivationNotice()
12025
+ });
11934
12026
  jobRunner.recoverStaleRuns();
11935
12027
  const notifier = new Notifier(opts.db, serverUrl);
11936
12028
  const intelligenceService = new IntelligenceService(opts.db);
@@ -13059,6 +13151,7 @@ async function createServer(opts) {
13059
13151
  app.log.error({ runId, err }, "Job runner failed");
13060
13152
  });
13061
13153
  },
13154
+ getRunnableProviderNames: () => registry.getAll().map((provider) => provider.adapter.name),
13062
13155
  onProviderUpdate: (providerName, apiKey, model, baseUrl, incomingQuota) => {
13063
13156
  const name = providerName;
13064
13157
  if (!adapterMap[name]) return null;
@@ -13196,6 +13289,10 @@ async function createServer(opts) {
13196
13289
  saveConfigPatch(config);
13197
13290
  opts.config.telemetry = enabled;
13198
13291
  },
13292
+ recordOnboardingEvent: (event) => {
13293
+ const { event: eventName, eventId, ...properties } = event;
13294
+ trackEvent(eventName, properties, { source: "dashboard", eventId });
13295
+ },
13199
13296
  onCdpConfigure: async (host, port2) => {
13200
13297
  if (!opts.config.cdp) opts.config.cdp = {};
13201
13298
  opts.config.cdp.host = host;
@@ -13434,6 +13531,7 @@ function parseQueryResponse(raw, count) {
13434
13531
  }
13435
13532
 
13436
13533
  export {
13534
+ trackCliCommandFinished,
13437
13535
  setTelemetrySource,
13438
13536
  isTelemetryEnabled,
13439
13537
  getOrCreateAnonymousId,